| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644464546464647464846494650465146524653465446554656465746584659466046614662466346644665466646674668466946704671467246734674467546764677467846794680468146824683468446854686468746884689469046914692469346944695469646974698469947004701470247034704470547064707470847094710471147124713471447154716471747184719472047214722472347244725472647274728472947304731473247334734473547364737473847394740474147424743474447454746474747484749475047514752475347544755475647574758475947604761476247634764476547664767476847694770477147724773477447754776477747784779478047814782478347844785478647874788478947904791479247934794479547964797479847994800480148024803480448054806480748084809481048114812481348144815481648174818481948204821482248234824482548264827482848294830483148324833483448354836483748384839484048414842484348444845484648474848484948504851485248534854485548564857485848594860486148624863486448654866486748684869487048714872487348744875487648774878487948804881488248834884488548864887488848894890489148924893489448954896489748984899490049014902490349044905490649074908490949104911491249134914491549164917491849194920492149224923492449254926492749284929493049314932493349344935493649374938493949404941494249434944494549464947494849494950495149524953495449554956495749584959496049614962496349644965496649674968496949704971497249734974497549764977497849794980498149824983498449854986498749884989499049914992499349944995499649974998499950005001500250035004500550065007500850095010501150125013501450155016501750185019502050215022502350245025502650275028502950305031503250335034503550365037503850395040504150425043504450455046504750485049505050515052505350545055505650575058505950605061506250635064506550665067506850695070507150725073507450755076507750785079508050815082508350845085508650875088508950905091509250935094509550965097509850995100510151025103510451055106510751085109511051115112511351145115511651175118511951205121512251235124512551265127512851295130513151325133513451355136513751385139514051415142514351445145514651475148514951505151515251535154515551565157515851595160516151625163516451655166516751685169517051715172517351745175517651775178517951805181518251835184518551865187518851895190519151925193519451955196519751985199520052015202520352045205520652075208520952105211521252135214521552165217521852195220522152225223522452255226522752285229523052315232523352345235523652375238523952405241524252435244524552465247524852495250525152525253525452555256525752585259526052615262526352645265526652675268526952705271527252735274527552765277527852795280528152825283528452855286528752885289529052915292529352945295529652975298529953005301530253035304530553065307530853095310531153125313531453155316531753185319532053215322532353245325532653275328532953305331533253335334533553365337533853395340534153425343534453455346534753485349535053515352535353545355535653575358535953605361536253635364536553665367536853695370537153725373537453755376537753785379538053815382538353845385538653875388538953905391539253935394539553965397539853995400540154025403540454055406540754085409541054115412541354145415541654175418541954205421542254235424542554265427542854295430543154325433543454355436543754385439544054415442544354445445544654475448544954505451545254535454545554565457545854595460546154625463546454655466546754685469547054715472547354745475547654775478547954805481548254835484548554865487548854895490549154925493549454955496549754985499550055015502550355045505550655075508550955105511551255135514551555165517551855195520552155225523552455255526552755285529553055315532553355345535553655375538553955405541554255435544554555465547554855495550555155525553555455555556555755585559556055615562556355645565556655675568556955705571557255735574557555765577557855795580558155825583558455855586558755885589559055915592559355945595559655975598559956005601560256035604560556065607560856095610561156125613561456155616561756185619562056215622562356245625562656275628562956305631563256335634563556365637563856395640564156425643564456455646564756485649565056515652565356545655565656575658565956605661566256635664566556665667566856695670567156725673567456755676567756785679568056815682568356845685568656875688568956905691569256935694569556965697569856995700570157025703570457055706570757085709571057115712571357145715571657175718571957205721572257235724572557265727572857295730573157325733573457355736573757385739574057415742574357445745574657475748574957505751575257535754575557565757575857595760576157625763576457655766576757685769577057715772577357745775577657775778577957805781578257835784578557865787578857895790579157925793579457955796579757985799580058015802580358045805580658075808580958105811581258135814581558165817581858195820582158225823582458255826582758285829583058315832583358345835583658375838583958405841584258435844584558465847584858495850585158525853585458555856585758585859586058615862586358645865586658675868586958705871587258735874587558765877587858795880588158825883588458855886588758885889589058915892589358945895589658975898589959005901590259035904590559065907590859095910591159125913591459155916591759185919592059215922592359245925592659275928592959305931593259335934593559365937593859395940594159425943594459455946594759485949595059515952595359545955595659575958595959605961596259635964596559665967596859695970597159725973597459755976597759785979598059815982598359845985598659875988598959905991599259935994599559965997599859996000600160026003600460056006600760086009601060116012601360146015601660176018601960206021602260236024602560266027602860296030603160326033603460356036603760386039604060416042604360446045604660476048604960506051605260536054605560566057605860596060606160626063606460656066606760686069607060716072607360746075607660776078607960806081608260836084608560866087608860896090609160926093609460956096609760986099610061016102610361046105610661076108610961106111611261136114611561166117611861196120612161226123612461256126612761286129613061316132613361346135613661376138613961406141614261436144614561466147614861496150615161526153615461556156615761586159616061616162616361646165616661676168616961706171617261736174617561766177617861796180618161826183618461856186618761886189619061916192619361946195619661976198619962006201620262036204620562066207620862096210621162126213621462156216621762186219622062216222622362246225622662276228622962306231623262336234623562366237623862396240624162426243624462456246624762486249625062516252625362546255625662576258625962606261626262636264626562666267626862696270627162726273627462756276627762786279628062816282628362846285628662876288628962906291629262936294629562966297629862996300630163026303630463056306630763086309631063116312631363146315631663176318631963206321632263236324632563266327632863296330633163326333633463356336633763386339634063416342634363446345634663476348634963506351635263536354635563566357635863596360636163626363636463656366636763686369637063716372637363746375637663776378637963806381638263836384638563866387638863896390639163926393639463956396639763986399640064016402640364046405640664076408640964106411641264136414641564166417641864196420642164226423642464256426642764286429643064316432643364346435643664376438643964406441644264436444644564466447644864496450645164526453645464556456645764586459646064616462646364646465646664676468646964706471647264736474647564766477647864796480648164826483648464856486648764886489649064916492649364946495649664976498649965006501650265036504650565066507650865096510651165126513651465156516651765186519652065216522652365246525652665276528652965306531653265336534653565366537653865396540654165426543654465456546654765486549655065516552655365546555655665576558655965606561656265636564656565666567656865696570657165726573657465756576657765786579658065816582658365846585658665876588658965906591659265936594659565966597659865996600660166026603660466056606660766086609661066116612661366146615661666176618661966206621662266236624662566266627662866296630663166326633663466356636663766386639664066416642664366446645664666476648664966506651665266536654665566566657665866596660666166626663666466656666666766686669667066716672667366746675667666776678667966806681668266836684668566866687668866896690669166926693669466956696669766986699670067016702670367046705670667076708670967106711671267136714671567166717671867196720672167226723672467256726672767286729673067316732673367346735673667376738673967406741674267436744674567466747674867496750675167526753675467556756675767586759676067616762676367646765676667676768676967706771677267736774677567766777677867796780678167826783678467856786678767886789679067916792679367946795679667976798679968006801680268036804680568066807680868096810681168126813681468156816681768186819682068216822682368246825682668276828682968306831683268336834683568366837683868396840684168426843684468456846684768486849685068516852685368546855685668576858685968606861686268636864686568666867686868696870687168726873687468756876687768786879688068816882688368846885688668876888688968906891689268936894689568966897689868996900690169026903690469056906690769086909691069116912691369146915691669176918691969206921692269236924692569266927692869296930693169326933693469356936693769386939694069416942694369446945694669476948694969506951695269536954695569566957695869596960696169626963696469656966696769686969697069716972697369746975697669776978697969806981698269836984698569866987698869896990699169926993699469956996699769986999700070017002700370047005700670077008700970107011701270137014701570167017701870197020702170227023702470257026702770287029703070317032703370347035703670377038703970407041704270437044704570467047704870497050705170527053705470557056705770587059706070617062706370647065706670677068706970707071707270737074707570767077707870797080708170827083708470857086708770887089709070917092709370947095709670977098709971007101710271037104710571067107710871097110711171127113711471157116711771187119712071217122712371247125712671277128712971307131713271337134713571367137713871397140714171427143714471457146714771487149715071517152715371547155715671577158715971607161716271637164716571667167716871697170717171727173717471757176717771787179718071817182718371847185718671877188718971907191719271937194719571967197719871997200720172027203720472057206720772087209721072117212721372147215721672177218721972207221722272237224722572267227722872297230723172327233723472357236723772387239724072417242724372447245724672477248724972507251725272537254725572567257725872597260726172627263726472657266726772687269727072717272727372747275727672777278727972807281728272837284728572867287728872897290729172927293729472957296729772987299730073017302730373047305730673077308730973107311731273137314731573167317731873197320732173227323732473257326732773287329733073317332733373347335733673377338733973407341734273437344734573467347734873497350735173527353735473557356735773587359736073617362736373647365736673677368736973707371737273737374737573767377737873797380738173827383738473857386738773887389739073917392739373947395739673977398739974007401740274037404740574067407740874097410741174127413741474157416741774187419742074217422742374247425742674277428742974307431743274337434743574367437743874397440744174427443744474457446744774487449745074517452745374547455745674577458745974607461746274637464746574667467746874697470747174727473747474757476747774787479748074817482748374847485748674877488748974907491749274937494749574967497749874997500750175027503750475057506750775087509751075117512751375147515751675177518751975207521752275237524752575267527752875297530753175327533753475357536753775387539754075417542754375447545754675477548754975507551755275537554755575567557755875597560756175627563756475657566756775687569757075717572757375747575757675777578757975807581758275837584758575867587758875897590759175927593759475957596759775987599760076017602760376047605760676077608760976107611761276137614761576167617761876197620762176227623762476257626762776287629763076317632763376347635763676377638763976407641764276437644764576467647764876497650765176527653765476557656765776587659766076617662766376647665766676677668766976707671767276737674767576767677767876797680768176827683768476857686768776887689769076917692769376947695769676977698769977007701770277037704770577067707770877097710771177127713771477157716771777187719772077217722772377247725772677277728772977307731773277337734773577367737773877397740774177427743774477457746774777487749775077517752775377547755775677577758775977607761776277637764776577667767776877697770777177727773777477757776777777787779778077817782778377847785778677877788778977907791779277937794779577967797779877997800780178027803780478057806780778087809781078117812781378147815781678177818781978207821782278237824782578267827782878297830783178327833783478357836783778387839784078417842784378447845784678477848784978507851785278537854785578567857785878597860786178627863786478657866786778687869787078717872787378747875787678777878787978807881788278837884788578867887788878897890789178927893789478957896789778987899790079017902790379047905790679077908790979107911791279137914791579167917791879197920792179227923792479257926792779287929793079317932793379347935793679377938793979407941794279437944794579467947794879497950795179527953795479557956795779587959796079617962796379647965796679677968796979707971797279737974797579767977797879797980798179827983798479857986798779887989799079917992799379947995799679977998799980008001800280038004800580068007800880098010801180128013801480158016801780188019802080218022802380248025802680278028802980308031803280338034803580368037803880398040804180428043804480458046804780488049805080518052805380548055805680578058805980608061806280638064806580668067806880698070807180728073807480758076807780788079808080818082808380848085808680878088808980908091809280938094809580968097809880998100810181028103810481058106810781088109811081118112811381148115811681178118811981208121812281238124812581268127812881298130813181328133813481358136813781388139814081418142814381448145814681478148814981508151815281538154815581568157815881598160816181628163816481658166816781688169817081718172817381748175817681778178817981808181818281838184818581868187818881898190819181928193819481958196819781988199820082018202820382048205820682078208820982108211821282138214821582168217821882198220822182228223822482258226822782288229823082318232823382348235823682378238823982408241824282438244824582468247824882498250825182528253825482558256825782588259826082618262826382648265826682678268826982708271827282738274827582768277827882798280828182828283828482858286828782888289829082918292829382948295829682978298829983008301830283038304830583068307830883098310831183128313831483158316831783188319832083218322832383248325832683278328832983308331833283338334833583368337833883398340834183428343834483458346834783488349835083518352835383548355835683578358835983608361836283638364836583668367836883698370837183728373837483758376837783788379838083818382838383848385838683878388838983908391839283938394839583968397839883998400840184028403840484058406840784088409841084118412841384148415841684178418841984208421842284238424842584268427842884298430843184328433843484358436843784388439844084418442844384448445844684478448844984508451845284538454845584568457845884598460846184628463846484658466846784688469847084718472847384748475847684778478847984808481848284838484848584868487848884898490849184928493849484958496849784988499850085018502850385048505850685078508850985108511851285138514851585168517851885198520852185228523852485258526852785288529853085318532853385348535853685378538853985408541854285438544854585468547854885498550855185528553855485558556855785588559856085618562856385648565856685678568856985708571857285738574857585768577857885798580858185828583858485858586858785888589859085918592859385948595859685978598859986008601860286038604860586068607860886098610861186128613861486158616861786188619862086218622862386248625862686278628862986308631863286338634863586368637863886398640864186428643864486458646864786488649865086518652865386548655865686578658865986608661866286638664866586668667866886698670867186728673867486758676867786788679868086818682868386848685868686878688868986908691869286938694869586968697869886998700870187028703870487058706870787088709871087118712871387148715871687178718871987208721872287238724872587268727872887298730873187328733873487358736873787388739874087418742874387448745874687478748874987508751875287538754875587568757875887598760876187628763876487658766876787688769877087718772877387748775877687778778877987808781878287838784878587868787878887898790879187928793879487958796879787988799880088018802880388048805880688078808880988108811881288138814881588168817881888198820882188228823882488258826882788288829883088318832883388348835883688378838883988408841884288438844884588468847884888498850885188528853885488558856885788588859886088618862886388648865886688678868886988708871887288738874887588768877887888798880888188828883888488858886888788888889889088918892889388948895889688978898889989008901890289038904890589068907890889098910891189128913891489158916891789188919892089218922892389248925892689278928892989308931893289338934893589368937893889398940894189428943894489458946894789488949895089518952895389548955895689578958895989608961896289638964896589668967896889698970897189728973897489758976897789788979898089818982898389848985898689878988898989908991899289938994899589968997899889999000900190029003900490059006900790089009901090119012901390149015901690179018901990209021902290239024902590269027902890299030903190329033903490359036903790389039904090419042904390449045904690479048904990509051905290539054905590569057905890599060906190629063906490659066906790689069907090719072907390749075907690779078907990809081908290839084908590869087908890899090909190929093909490959096909790989099910091019102910391049105910691079108910991109111911291139114911591169117911891199120912191229123912491259126912791289129913091319132913391349135913691379138913991409141914291439144914591469147914891499150915191529153915491559156915791589159916091619162916391649165916691679168916991709171917291739174917591769177917891799180918191829183918491859186918791889189919091919192919391949195919691979198919992009201920292039204920592069207920892099210921192129213921492159216921792189219922092219222922392249225922692279228922992309231923292339234923592369237923892399240924192429243924492459246924792489249925092519252925392549255925692579258925992609261926292639264926592669267926892699270927192729273927492759276927792789279928092819282928392849285928692879288928992909291929292939294929592969297929892999300930193029303930493059306930793089309931093119312931393149315931693179318931993209321932293239324932593269327932893299330933193329333933493359336933793389339934093419342934393449345934693479348934993509351935293539354935593569357935893599360936193629363936493659366936793689369937093719372937393749375937693779378937993809381938293839384938593869387938893899390939193929393939493959396939793989399940094019402940394049405940694079408940994109411941294139414941594169417941894199420942194229423942494259426942794289429943094319432943394349435943694379438943994409441944294439444944594469447944894499450945194529453945494559456945794589459946094619462946394649465946694679468946994709471947294739474947594769477947894799480948194829483948494859486948794889489949094919492949394949495949694979498949995009501950295039504950595069507950895099510951195129513951495159516951795189519952095219522952395249525952695279528952995309531953295339534953595369537953895399540954195429543954495459546954795489549955095519552955395549555955695579558955995609561956295639564956595669567956895699570957195729573957495759576957795789579958095819582958395849585958695879588958995909591959295939594959595969597959895999600960196029603960496059606960796089609961096119612961396149615961696179618961996209621962296239624962596269627962896299630963196329633963496359636963796389639964096419642964396449645964696479648964996509651965296539654965596569657965896599660966196629663966496659666966796689669967096719672967396749675967696779678967996809681968296839684968596869687968896899690969196929693969496959696969796989699970097019702970397049705970697079708970997109711971297139714971597169717971897199720972197229723972497259726972797289729973097319732973397349735973697379738973997409741974297439744974597469747974897499750975197529753975497559756975797589759976097619762976397649765976697679768976997709771977297739774977597769777977897799780978197829783978497859786978797889789979097919792979397949795979697979798979998009801980298039804980598069807980898099810981198129813981498159816981798189819982098219822982398249825982698279828982998309831983298339834983598369837983898399840984198429843984498459846984798489849985098519852985398549855985698579858985998609861986298639864986598669867986898699870987198729873987498759876987798789879988098819882988398849885988698879888988998909891989298939894989598969897989898999900990199029903990499059906990799089909991099119912991399149915991699179918991999209921992299239924992599269927992899299930993199329933993499359936993799389939994099419942994399449945994699479948994999509951995299539954995599569957995899599960996199629963996499659966996799689969997099719972997399749975997699779978997999809981998299839984998599869987998899899990999199929993999499959996999799989999100001000110002100031000410005100061000710008100091001010011100121001310014100151001610017100181001910020100211002210023100241002510026100271002810029100301003110032100331003410035100361003710038100391004010041100421004310044100451004610047100481004910050100511005210053100541005510056100571005810059100601006110062100631006410065100661006710068100691007010071100721007310074100751007610077100781007910080100811008210083100841008510086100871008810089100901009110092100931009410095100961009710098100991010010101101021010310104101051010610107101081010910110101111011210113101141011510116101171011810119101201012110122101231012410125101261012710128101291013010131101321013310134101351013610137101381013910140101411014210143101441014510146101471014810149101501015110152101531015410155101561015710158101591016010161101621016310164101651016610167101681016910170101711017210173101741017510176101771017810179101801018110182101831018410185101861018710188101891019010191101921019310194101951019610197101981019910200102011020210203102041020510206102071020810209102101021110212102131021410215102161021710218102191022010221102221022310224102251022610227102281022910230102311023210233102341023510236102371023810239102401024110242102431024410245102461024710248102491025010251102521025310254102551025610257102581025910260102611026210263102641026510266102671026810269102701027110272102731027410275102761027710278102791028010281102821028310284102851028610287102881028910290102911029210293102941029510296102971029810299103001030110302103031030410305103061030710308103091031010311103121031310314103151031610317103181031910320103211032210323103241032510326103271032810329103301033110332103331033410335103361033710338103391034010341103421034310344103451034610347103481034910350103511035210353103541035510356103571035810359103601036110362103631036410365103661036710368103691037010371103721037310374103751037610377103781037910380103811038210383103841038510386103871038810389103901039110392103931039410395103961039710398103991040010401104021040310404104051040610407104081040910410104111041210413104141041510416104171041810419104201042110422104231042410425104261042710428104291043010431104321043310434104351043610437104381043910440104411044210443104441044510446104471044810449104501045110452104531045410455104561045710458104591046010461104621046310464104651046610467104681046910470104711047210473104741047510476104771047810479104801048110482104831048410485104861048710488104891049010491104921049310494104951049610497104981049910500105011050210503105041050510506105071050810509105101051110512105131051410515105161051710518105191052010521105221052310524105251052610527105281052910530105311053210533105341053510536105371053810539105401054110542105431054410545105461054710548105491055010551105521055310554105551055610557105581055910560105611056210563105641056510566105671056810569105701057110572105731057410575105761057710578105791058010581105821058310584105851058610587105881058910590105911059210593105941059510596105971059810599106001060110602106031060410605106061060710608106091061010611106121061310614106151061610617106181061910620106211062210623106241062510626106271062810629106301063110632106331063410635106361063710638106391064010641106421064310644106451064610647106481064910650106511065210653106541065510656106571065810659106601066110662106631066410665106661066710668106691067010671106721067310674106751067610677106781067910680106811068210683106841068510686106871068810689106901069110692106931069410695106961069710698106991070010701107021070310704107051070610707107081070910710107111071210713107141071510716107171071810719107201072110722107231072410725107261072710728107291073010731107321073310734107351073610737107381073910740107411074210743107441074510746107471074810749107501075110752107531075410755107561075710758107591076010761107621076310764107651076610767107681076910770107711077210773107741077510776107771077810779107801078110782107831078410785107861078710788107891079010791107921079310794107951079610797107981079910800108011080210803108041080510806108071080810809108101081110812108131081410815108161081710818108191082010821108221082310824108251082610827108281082910830108311083210833108341083510836108371083810839108401084110842108431084410845108461084710848108491085010851108521085310854108551085610857108581085910860108611086210863108641086510866108671086810869108701087110872108731087410875108761087710878108791088010881108821088310884108851088610887108881088910890108911089210893108941089510896108971089810899109001090110902109031090410905109061090710908109091091010911109121091310914109151091610917109181091910920109211092210923109241092510926109271092810929109301093110932109331093410935109361093710938109391094010941109421094310944109451094610947109481094910950109511095210953109541095510956109571095810959109601096110962109631096410965109661096710968109691097010971109721097310974109751097610977109781097910980109811098210983109841098510986109871098810989109901099110992109931099410995109961099710998109991100011001110021100311004110051100611007110081100911010110111101211013110141101511016110171101811019110201102111022110231102411025110261102711028110291103011031110321103311034110351103611037110381103911040110411104211043110441104511046110471104811049110501105111052110531105411055110561105711058110591106011061110621106311064110651106611067110681106911070110711107211073110741107511076110771107811079110801108111082110831108411085110861108711088110891109011091110921109311094110951109611097110981109911100111011110211103111041110511106111071110811109111101111111112111131111411115111161111711118111191112011121111221112311124111251112611127111281112911130111311113211133111341113511136111371113811139111401114111142111431114411145111461114711148111491115011151111521115311154111551115611157111581115911160111611116211163111641116511166111671116811169111701117111172111731117411175111761117711178111791118011181111821118311184111851118611187111881118911190111911119211193111941119511196111971119811199112001120111202112031120411205112061120711208112091121011211112121121311214112151121611217112181121911220112211122211223112241122511226112271122811229112301123111232112331123411235112361123711238112391124011241112421124311244112451124611247112481124911250112511125211253112541125511256112571125811259112601126111262112631126411265112661126711268112691127011271112721127311274112751127611277112781127911280112811128211283112841128511286112871128811289112901129111292112931129411295112961129711298112991130011301113021130311304113051130611307113081130911310113111131211313113141131511316113171131811319113201132111322113231132411325113261132711328113291133011331113321133311334113351133611337113381133911340113411134211343113441134511346113471134811349113501135111352113531135411355113561135711358113591136011361113621136311364113651136611367113681136911370113711137211373113741137511376113771137811379113801138111382113831138411385113861138711388113891139011391113921139311394113951139611397113981139911400114011140211403114041140511406114071140811409114101141111412114131141411415114161141711418114191142011421114221142311424114251142611427114281142911430114311143211433114341143511436114371143811439114401144111442114431144411445114461144711448114491145011451114521145311454114551145611457114581145911460114611146211463114641146511466114671146811469114701147111472114731147411475114761147711478114791148011481114821148311484114851148611487114881148911490114911149211493114941149511496114971149811499115001150111502115031150411505115061150711508115091151011511115121151311514115151151611517115181151911520115211152211523115241152511526115271152811529115301153111532115331153411535115361153711538115391154011541115421154311544115451154611547115481154911550115511155211553115541155511556115571155811559115601156111562115631156411565115661156711568115691157011571115721157311574115751157611577115781157911580115811158211583115841158511586115871158811589115901159111592115931159411595115961159711598115991160011601116021160311604116051160611607116081160911610116111161211613116141161511616116171161811619116201162111622116231162411625116261162711628116291163011631116321163311634116351163611637116381163911640116411164211643116441164511646116471164811649116501165111652116531165411655116561165711658116591166011661116621166311664116651166611667116681166911670116711167211673116741167511676116771167811679116801168111682116831168411685116861168711688116891169011691116921169311694116951169611697116981169911700117011170211703117041170511706117071170811709117101171111712117131171411715117161171711718117191172011721117221172311724117251172611727117281172911730117311173211733117341173511736117371173811739117401174111742117431174411745117461174711748117491175011751117521175311754117551175611757117581175911760117611176211763117641176511766117671176811769117701177111772117731177411775117761177711778117791178011781117821178311784117851178611787117881178911790117911179211793117941179511796117971179811799118001180111802118031180411805118061180711808118091181011811118121181311814118151181611817118181181911820118211182211823118241182511826118271182811829118301183111832118331183411835118361183711838118391184011841118421184311844118451184611847118481184911850118511185211853118541185511856118571185811859118601186111862118631186411865118661186711868118691187011871118721187311874118751187611877118781187911880118811188211883118841188511886118871188811889118901189111892118931189411895118961189711898118991190011901119021190311904119051190611907119081190911910119111191211913119141191511916119171191811919119201192111922119231192411925119261192711928119291193011931119321193311934119351193611937119381193911940119411194211943119441194511946119471194811949119501195111952119531195411955119561195711958119591196011961119621196311964119651196611967119681196911970119711197211973119741197511976119771197811979119801198111982119831198411985119861198711988119891199011991119921199311994119951199611997119981199912000120011200212003120041200512006120071200812009120101201112012120131201412015120161201712018120191202012021120221202312024120251202612027120281202912030120311203212033120341203512036120371203812039120401204112042120431204412045120461204712048120491205012051120521205312054120551205612057120581205912060120611206212063120641206512066120671206812069120701207112072120731207412075120761207712078120791208012081120821208312084120851208612087120881208912090120911209212093120941209512096120971209812099121001210112102121031210412105121061210712108121091211012111121121211312114121151211612117121181211912120121211212212123121241212512126121271212812129121301213112132121331213412135121361213712138121391214012141121421214312144121451214612147121481214912150121511215212153121541215512156121571215812159121601216112162121631216412165121661216712168121691217012171121721217312174121751217612177121781217912180121811218212183121841218512186121871218812189121901219112192121931219412195121961219712198121991220012201122021220312204122051220612207122081220912210122111221212213122141221512216122171221812219122201222112222122231222412225122261222712228122291223012231122321223312234122351223612237122381223912240122411224212243122441224512246122471224812249122501225112252122531225412255122561225712258122591226012261122621226312264122651226612267122681226912270122711227212273122741227512276122771227812279122801228112282122831228412285122861228712288122891229012291122921229312294122951229612297122981229912300123011230212303123041230512306123071230812309123101231112312123131231412315123161231712318123191232012321123221232312324123251232612327123281232912330123311233212333123341233512336123371233812339123401234112342123431234412345123461234712348123491235012351123521235312354123551235612357123581235912360123611236212363123641236512366123671236812369123701237112372123731237412375123761237712378123791238012381123821238312384123851238612387123881238912390123911239212393123941239512396123971239812399124001240112402124031240412405124061240712408124091241012411124121241312414124151241612417124181241912420124211242212423124241242512426124271242812429124301243112432124331243412435124361243712438124391244012441124421244312444124451244612447124481244912450124511245212453124541245512456124571245812459124601246112462124631246412465124661246712468124691247012471124721247312474124751247612477124781247912480124811248212483124841248512486124871248812489124901249112492124931249412495124961249712498124991250012501125021250312504125051250612507125081250912510125111251212513125141251512516125171251812519125201252112522125231252412525125261252712528125291253012531125321253312534125351253612537125381253912540125411254212543125441254512546125471254812549125501255112552125531255412555125561255712558125591256012561125621256312564125651256612567125681256912570125711257212573125741257512576125771257812579125801258112582125831258412585125861258712588125891259012591125921259312594125951259612597125981259912600126011260212603126041260512606126071260812609126101261112612126131261412615126161261712618126191262012621126221262312624126251262612627126281262912630126311263212633126341263512636126371263812639126401264112642126431264412645126461264712648126491265012651126521265312654126551265612657126581265912660126611266212663126641266512666126671266812669126701267112672126731267412675126761267712678126791268012681126821268312684126851268612687126881268912690126911269212693126941269512696126971269812699127001270112702127031270412705127061270712708127091271012711127121271312714127151271612717127181271912720127211272212723127241272512726127271272812729127301273112732127331273412735127361273712738127391274012741127421274312744127451274612747127481274912750127511275212753127541275512756127571275812759127601276112762127631276412765127661276712768127691277012771127721277312774127751277612777127781277912780127811278212783127841278512786127871278812789127901279112792127931279412795127961279712798127991280012801128021280312804128051280612807128081280912810128111281212813128141281512816128171281812819128201282112822128231282412825128261282712828128291283012831128321283312834128351283612837128381283912840128411284212843128441284512846128471284812849128501285112852128531285412855128561285712858128591286012861128621286312864128651286612867128681286912870128711287212873128741287512876128771287812879128801288112882128831288412885128861288712888128891289012891128921289312894128951289612897128981289912900129011290212903129041290512906129071290812909129101291112912129131291412915129161291712918129191292012921129221292312924129251292612927129281292912930129311293212933129341293512936129371293812939129401294112942129431294412945129461294712948129491295012951129521295312954129551295612957129581295912960129611296212963129641296512966129671296812969129701297112972129731297412975129761297712978129791298012981129821298312984129851298612987129881298912990129911299212993129941299512996129971299812999130001300113002130031300413005130061300713008130091301013011130121301313014130151301613017130181301913020130211302213023130241302513026130271302813029130301303113032130331303413035130361303713038130391304013041130421304313044130451304613047130481304913050130511305213053130541305513056130571305813059130601306113062130631306413065130661306713068130691307013071130721307313074130751307613077130781307913080130811308213083130841308513086130871308813089130901309113092130931309413095130961309713098130991310013101131021310313104131051310613107131081310913110131111311213113131141311513116131171311813119131201312113122131231312413125131261312713128131291313013131131321313313134131351313613137131381313913140131411314213143131441314513146131471314813149131501315113152131531315413155131561315713158131591316013161131621316313164131651316613167131681316913170131711317213173131741317513176131771317813179131801318113182131831318413185131861318713188131891319013191131921319313194131951319613197131981319913200132011320213203132041320513206132071320813209132101321113212132131321413215132161321713218132191322013221132221322313224132251322613227132281322913230132311323213233132341323513236132371323813239132401324113242132431324413245132461324713248132491325013251132521325313254132551325613257132581325913260132611326213263132641326513266132671326813269132701327113272132731327413275132761327713278132791328013281132821328313284132851328613287132881328913290132911329213293132941329513296132971329813299133001330113302133031330413305133061330713308133091331013311133121331313314133151331613317133181331913320133211332213323133241332513326133271332813329133301333113332133331333413335133361333713338133391334013341133421334313344133451334613347133481334913350133511335213353133541335513356133571335813359133601336113362133631336413365133661336713368133691337013371133721337313374133751337613377133781337913380133811338213383133841338513386133871338813389133901339113392133931339413395133961339713398133991340013401134021340313404134051340613407134081340913410134111341213413134141341513416134171341813419134201342113422134231342413425134261342713428134291343013431134321343313434134351343613437134381343913440134411344213443134441344513446134471344813449134501345113452134531345413455134561345713458134591346013461134621346313464134651346613467134681346913470134711347213473134741347513476134771347813479134801348113482134831348413485134861348713488134891349013491134921349313494134951349613497134981349913500135011350213503135041350513506135071350813509135101351113512135131351413515135161351713518135191352013521135221352313524135251352613527135281352913530135311353213533135341353513536135371353813539135401354113542135431354413545135461354713548135491355013551135521355313554135551355613557135581355913560135611356213563135641356513566135671356813569135701357113572135731357413575135761357713578135791358013581135821358313584135851358613587135881358913590135911359213593135941359513596135971359813599136001360113602136031360413605136061360713608136091361013611136121361313614136151361613617136181361913620136211362213623136241362513626136271362813629136301363113632136331363413635136361363713638136391364013641136421364313644136451364613647136481364913650136511365213653136541365513656136571365813659136601366113662136631366413665136661366713668136691367013671136721367313674136751367613677136781367913680136811368213683136841368513686136871368813689136901369113692136931369413695136961369713698136991370013701137021370313704137051370613707137081370913710137111371213713137141371513716137171371813719137201372113722137231372413725137261372713728137291373013731137321373313734137351373613737137381373913740137411374213743137441374513746137471374813749137501375113752137531375413755137561375713758137591376013761137621376313764137651376613767137681376913770137711377213773137741377513776137771377813779137801378113782137831378413785137861378713788137891379013791137921379313794137951379613797137981379913800138011380213803138041380513806138071380813809138101381113812138131381413815138161381713818138191382013821138221382313824138251382613827138281382913830138311383213833138341383513836138371383813839138401384113842138431384413845138461384713848138491385013851138521385313854138551385613857138581385913860138611386213863138641386513866138671386813869138701387113872138731387413875138761387713878138791388013881138821388313884138851388613887138881388913890138911389213893138941389513896138971389813899139001390113902139031390413905139061390713908139091391013911139121391313914139151391613917139181391913920139211392213923139241392513926139271392813929139301393113932139331393413935139361393713938139391394013941139421394313944139451394613947139481394913950139511395213953139541395513956139571395813959139601396113962139631396413965139661396713968139691397013971139721397313974139751397613977139781397913980139811398213983139841398513986139871398813989139901399113992139931399413995139961399713998139991400014001140021400314004140051400614007140081400914010140111401214013140141401514016140171401814019140201402114022140231402414025140261402714028140291403014031140321403314034140351403614037140381403914040140411404214043140441404514046140471404814049140501405114052140531405414055140561405714058140591406014061140621406314064140651406614067140681406914070140711407214073140741407514076140771407814079140801408114082140831408414085140861408714088140891409014091140921409314094140951409614097140981409914100141011410214103141041410514106141071410814109141101411114112141131411414115141161411714118141191412014121141221412314124141251412614127141281412914130141311413214133141341413514136141371413814139141401414114142141431414414145141461414714148141491415014151141521415314154141551415614157141581415914160141611416214163141641416514166141671416814169141701417114172141731417414175141761417714178141791418014181141821418314184141851418614187141881418914190141911419214193141941419514196141971419814199142001420114202142031420414205142061420714208142091421014211142121421314214142151421614217142181421914220142211422214223142241422514226142271422814229142301423114232142331423414235142361423714238142391424014241142421424314244142451424614247142481424914250142511425214253142541425514256142571425814259142601426114262142631426414265142661426714268142691427014271142721427314274142751427614277142781427914280142811428214283142841428514286142871428814289142901429114292142931429414295142961429714298142991430014301143021430314304143051430614307143081430914310143111431214313143141431514316143171431814319143201432114322143231432414325143261432714328143291433014331143321433314334143351433614337143381433914340143411434214343143441434514346143471434814349143501435114352143531435414355143561435714358143591436014361143621436314364143651436614367143681436914370143711437214373143741437514376143771437814379143801438114382143831438414385143861438714388143891439014391143921439314394143951439614397143981439914400144011440214403144041440514406144071440814409144101441114412144131441414415144161441714418144191442014421144221442314424144251442614427144281442914430144311443214433144341443514436144371443814439144401444114442144431444414445144461444714448144491445014451144521445314454144551445614457144581445914460144611446214463144641446514466144671446814469144701447114472144731447414475144761447714478144791448014481144821448314484144851448614487144881448914490144911449214493144941449514496144971449814499145001450114502145031450414505145061450714508145091451014511145121451314514145151451614517145181451914520145211452214523145241452514526145271452814529145301453114532145331453414535145361453714538145391454014541145421454314544145451454614547145481454914550145511455214553145541455514556145571455814559145601456114562145631456414565145661456714568145691457014571145721457314574145751457614577145781457914580145811458214583145841458514586145871458814589145901459114592145931459414595145961459714598145991460014601146021460314604146051460614607146081460914610146111461214613146141461514616146171461814619146201462114622146231462414625146261462714628146291463014631146321463314634146351463614637146381463914640146411464214643146441464514646146471464814649146501465114652146531465414655146561465714658146591466014661146621466314664146651466614667146681466914670146711467214673146741467514676146771467814679146801468114682146831468414685146861468714688146891469014691146921469314694146951469614697146981469914700147011470214703147041470514706147071470814709147101471114712147131471414715147161471714718147191472014721147221472314724147251472614727147281472914730147311473214733147341473514736147371473814739147401474114742147431474414745147461474714748147491475014751147521475314754147551475614757147581475914760147611476214763147641476514766147671476814769147701477114772147731477414775147761477714778147791478014781147821478314784147851478614787147881478914790147911479214793147941479514796147971479814799148001480114802148031480414805148061480714808148091481014811148121481314814148151481614817148181481914820148211482214823148241482514826148271482814829148301483114832148331483414835148361483714838148391484014841148421484314844148451484614847148481484914850148511485214853148541485514856148571485814859148601486114862148631486414865148661486714868148691487014871148721487314874148751487614877148781487914880148811488214883148841488514886148871488814889148901489114892148931489414895148961489714898148991490014901149021490314904149051490614907149081490914910149111491214913149141491514916149171491814919149201492114922149231492414925149261492714928149291493014931149321493314934149351493614937149381493914940149411494214943149441494514946149471494814949149501495114952149531495414955149561495714958149591496014961149621496314964149651496614967149681496914970149711497214973149741497514976149771497814979149801498114982149831498414985149861498714988149891499014991149921499314994149951499614997149981499915000150011500215003150041500515006150071500815009150101501115012150131501415015150161501715018150191502015021150221502315024150251502615027150281502915030150311503215033150341503515036150371503815039150401504115042150431504415045150461504715048150491505015051150521505315054150551505615057150581505915060150611506215063150641506515066150671506815069150701507115072150731507415075150761507715078150791508015081150821508315084150851508615087150881508915090150911509215093150941509515096150971509815099151001510115102151031510415105151061510715108151091511015111151121511315114151151511615117151181511915120151211512215123151241512515126151271512815129151301513115132151331513415135151361513715138151391514015141151421514315144151451514615147151481514915150151511515215153151541515515156151571515815159151601516115162151631516415165151661516715168151691517015171151721517315174151751517615177151781517915180151811518215183151841518515186151871518815189151901519115192151931519415195151961519715198151991520015201152021520315204152051520615207152081520915210152111521215213152141521515216152171521815219152201522115222152231522415225152261522715228152291523015231152321523315234152351523615237152381523915240152411524215243152441524515246152471524815249152501525115252152531525415255152561525715258152591526015261152621526315264152651526615267152681526915270152711527215273152741527515276152771527815279152801528115282152831528415285152861528715288152891529015291152921529315294152951529615297152981529915300153011530215303153041530515306153071530815309153101531115312153131531415315153161531715318153191532015321153221532315324153251532615327153281532915330153311533215333153341533515336153371533815339153401534115342153431534415345153461534715348153491535015351153521535315354153551535615357153581535915360153611536215363153641536515366153671536815369153701537115372153731537415375153761537715378153791538015381153821538315384153851538615387153881538915390153911539215393153941539515396153971539815399154001540115402154031540415405154061540715408154091541015411154121541315414154151541615417154181541915420154211542215423154241542515426154271542815429154301543115432154331543415435154361543715438154391544015441154421544315444154451544615447154481544915450154511545215453154541545515456154571545815459154601546115462154631546415465154661546715468154691547015471154721547315474154751547615477154781547915480154811548215483154841548515486154871548815489154901549115492154931549415495154961549715498154991550015501155021550315504155051550615507155081550915510155111551215513155141551515516155171551815519155201552115522155231552415525155261552715528155291553015531155321553315534155351553615537155381553915540155411554215543155441554515546155471554815549155501555115552155531555415555155561555715558155591556015561155621556315564155651556615567155681556915570155711557215573155741557515576155771557815579155801558115582155831558415585155861558715588155891559015591155921559315594155951559615597155981559915600156011560215603156041560515606156071560815609156101561115612156131561415615156161561715618156191562015621156221562315624156251562615627156281562915630156311563215633156341563515636156371563815639156401564115642156431564415645156461564715648156491565015651156521565315654156551565615657156581565915660156611566215663156641566515666156671566815669156701567115672156731567415675156761567715678156791568015681156821568315684156851568615687156881568915690156911569215693156941569515696156971569815699157001570115702157031570415705157061570715708157091571015711157121571315714157151571615717157181571915720157211572215723157241572515726157271572815729157301573115732157331573415735157361573715738157391574015741157421574315744157451574615747157481574915750157511575215753157541575515756157571575815759157601576115762157631576415765157661576715768157691577015771157721577315774157751577615777157781577915780157811578215783157841578515786157871578815789157901579115792157931579415795157961579715798157991580015801158021580315804158051580615807158081580915810158111581215813158141581515816158171581815819158201582115822158231582415825158261582715828158291583015831158321583315834158351583615837158381583915840158411584215843158441584515846158471584815849158501585115852158531585415855158561585715858158591586015861158621586315864158651586615867158681586915870158711587215873158741587515876158771587815879158801588115882158831588415885158861588715888158891589015891158921589315894158951589615897158981589915900159011590215903159041590515906159071590815909159101591115912159131591415915159161591715918159191592015921159221592315924159251592615927159281592915930159311593215933159341593515936159371593815939159401594115942159431594415945159461594715948159491595015951159521595315954159551595615957159581595915960159611596215963159641596515966159671596815969159701597115972159731597415975159761597715978159791598015981159821598315984159851598615987159881598915990159911599215993159941599515996159971599815999160001600116002160031600416005160061600716008160091601016011160121601316014160151601616017160181601916020160211602216023160241602516026160271602816029160301603116032160331603416035160361603716038160391604016041160421604316044160451604616047160481604916050160511605216053160541605516056160571605816059160601606116062160631606416065160661606716068160691607016071160721607316074160751607616077160781607916080160811608216083160841608516086160871608816089160901609116092160931609416095160961609716098160991610016101161021610316104161051610616107161081610916110161111611216113161141611516116161171611816119161201612116122161231612416125161261612716128161291613016131161321613316134161351613616137161381613916140161411614216143161441614516146161471614816149161501615116152161531615416155161561615716158161591616016161161621616316164161651616616167161681616916170161711617216173161741617516176161771617816179161801618116182161831618416185161861618716188161891619016191161921619316194161951619616197161981619916200162011620216203162041620516206162071620816209162101621116212162131621416215162161621716218162191622016221162221622316224162251622616227162281622916230162311623216233162341623516236162371623816239162401624116242162431624416245162461624716248162491625016251162521625316254162551625616257162581625916260162611626216263162641626516266162671626816269162701627116272162731627416275162761627716278162791628016281162821628316284162851628616287162881628916290162911629216293162941629516296162971629816299163001630116302163031630416305163061630716308163091631016311163121631316314163151631616317163181631916320163211632216323163241632516326163271632816329163301633116332163331633416335163361633716338163391634016341163421634316344163451634616347163481634916350163511635216353163541635516356163571635816359163601636116362163631636416365163661636716368163691637016371163721637316374163751637616377163781637916380163811638216383163841638516386163871638816389163901639116392163931639416395163961639716398163991640016401164021640316404164051640616407164081640916410164111641216413164141641516416164171641816419164201642116422164231642416425164261642716428164291643016431164321643316434164351643616437164381643916440164411644216443164441644516446164471644816449164501645116452164531645416455164561645716458164591646016461164621646316464164651646616467164681646916470164711647216473164741647516476164771647816479164801648116482164831648416485164861648716488164891649016491164921649316494164951649616497164981649916500165011650216503165041650516506165071650816509165101651116512165131651416515165161651716518165191652016521165221652316524165251652616527165281652916530165311653216533165341653516536165371653816539165401654116542165431654416545165461654716548165491655016551165521655316554165551655616557165581655916560165611656216563165641656516566165671656816569165701657116572165731657416575165761657716578165791658016581165821658316584165851658616587165881658916590165911659216593165941659516596165971659816599166001660116602166031660416605166061660716608166091661016611166121661316614166151661616617166181661916620166211662216623166241662516626166271662816629166301663116632166331663416635166361663716638166391664016641166421664316644166451664616647166481664916650166511665216653166541665516656166571665816659166601666116662166631666416665166661666716668166691667016671166721667316674166751667616677166781667916680166811668216683166841668516686166871668816689166901669116692166931669416695166961669716698166991670016701167021670316704167051670616707167081670916710167111671216713167141671516716167171671816719167201672116722167231672416725167261672716728167291673016731167321673316734167351673616737167381673916740167411674216743167441674516746167471674816749167501675116752167531675416755167561675716758167591676016761167621676316764167651676616767167681676916770167711677216773167741677516776167771677816779167801678116782167831678416785167861678716788167891679016791167921679316794167951679616797167981679916800168011680216803168041680516806168071680816809168101681116812168131681416815168161681716818168191682016821168221682316824168251682616827168281682916830168311683216833168341683516836168371683816839168401684116842168431684416845168461684716848168491685016851168521685316854168551685616857168581685916860168611686216863168641686516866168671686816869168701687116872168731687416875168761687716878168791688016881168821688316884168851688616887168881688916890168911689216893168941689516896168971689816899169001690116902169031690416905169061690716908169091691016911169121691316914169151691616917169181691916920169211692216923169241692516926169271692816929169301693116932169331693416935169361693716938169391694016941169421694316944169451694616947169481694916950169511695216953169541695516956169571695816959169601696116962169631696416965169661696716968169691697016971169721697316974169751697616977169781697916980169811698216983169841698516986169871698816989169901699116992169931699416995169961699716998169991700017001170021700317004170051700617007170081700917010170111701217013170141701517016170171701817019170201702117022170231702417025170261702717028170291703017031170321703317034170351703617037170381703917040170411704217043170441704517046170471704817049170501705117052170531705417055170561705717058170591706017061170621706317064170651706617067170681706917070170711707217073170741707517076170771707817079170801708117082170831708417085170861708717088170891709017091170921709317094170951709617097170981709917100171011710217103171041710517106171071710817109171101711117112171131711417115171161711717118171191712017121171221712317124171251712617127171281712917130171311713217133171341713517136171371713817139171401714117142171431714417145171461714717148171491715017151171521715317154171551715617157171581715917160171611716217163171641716517166171671716817169171701717117172171731717417175171761717717178171791718017181171821718317184171851718617187171881718917190171911719217193171941719517196171971719817199172001720117202172031720417205172061720717208172091721017211172121721317214172151721617217172181721917220172211722217223172241722517226172271722817229172301723117232172331723417235172361723717238172391724017241172421724317244172451724617247172481724917250172511725217253172541725517256172571725817259172601726117262172631726417265172661726717268172691727017271172721727317274172751727617277172781727917280172811728217283172841728517286172871728817289172901729117292172931729417295172961729717298172991730017301173021730317304173051730617307173081730917310173111731217313173141731517316173171731817319173201732117322173231732417325173261732717328173291733017331173321733317334173351733617337173381733917340173411734217343173441734517346173471734817349173501735117352173531735417355173561735717358173591736017361173621736317364173651736617367173681736917370173711737217373173741737517376173771737817379173801738117382173831738417385173861738717388173891739017391173921739317394173951739617397173981739917400174011740217403174041740517406174071740817409174101741117412174131741417415174161741717418174191742017421174221742317424174251742617427174281742917430174311743217433174341743517436174371743817439174401744117442174431744417445174461744717448174491745017451174521745317454174551745617457174581745917460174611746217463174641746517466174671746817469174701747117472174731747417475174761747717478174791748017481174821748317484174851748617487174881748917490174911749217493174941749517496174971749817499175001750117502175031750417505175061750717508175091751017511175121751317514175151751617517175181751917520175211752217523175241752517526175271752817529175301753117532175331753417535175361753717538175391754017541175421754317544175451754617547175481754917550175511755217553175541755517556175571755817559175601756117562175631756417565175661756717568175691757017571175721757317574175751757617577175781757917580175811758217583175841758517586175871758817589175901759117592175931759417595175961759717598175991760017601176021760317604176051760617607176081760917610176111761217613176141761517616176171761817619176201762117622176231762417625176261762717628176291763017631176321763317634176351763617637176381763917640176411764217643176441764517646176471764817649176501765117652176531765417655176561765717658176591766017661176621766317664176651766617667176681766917670176711767217673176741767517676176771767817679176801768117682176831768417685176861768717688176891769017691176921769317694176951769617697176981769917700177011770217703177041770517706177071770817709177101771117712177131771417715177161771717718177191772017721177221772317724177251772617727177281772917730177311773217733177341773517736177371773817739177401774117742177431774417745177461774717748177491775017751177521775317754177551775617757177581775917760177611776217763177641776517766177671776817769177701777117772177731777417775177761777717778177791778017781177821778317784177851778617787177881778917790177911779217793177941779517796177971779817799178001780117802178031780417805178061780717808178091781017811178121781317814178151781617817178181781917820178211782217823178241782517826178271782817829178301783117832178331783417835178361783717838178391784017841178421784317844178451784617847178481784917850178511785217853178541785517856178571785817859178601786117862178631786417865178661786717868178691787017871178721787317874178751787617877178781787917880178811788217883178841788517886178871788817889178901789117892178931789417895178961789717898178991790017901179021790317904179051790617907179081790917910179111791217913179141791517916179171791817919179201792117922179231792417925179261792717928179291793017931179321793317934179351793617937179381793917940179411794217943179441794517946179471794817949179501795117952179531795417955179561795717958179591796017961179621796317964179651796617967179681796917970179711797217973179741797517976179771797817979179801798117982179831798417985179861798717988179891799017991179921799317994179951799617997179981799918000180011800218003180041800518006180071800818009180101801118012180131801418015180161801718018180191802018021180221802318024180251802618027180281802918030180311803218033180341803518036180371803818039180401804118042180431804418045180461804718048180491805018051180521805318054180551805618057180581805918060180611806218063180641806518066180671806818069180701807118072180731807418075180761807718078180791808018081180821808318084180851808618087180881808918090180911809218093180941809518096180971809818099181001810118102181031810418105181061810718108181091811018111181121811318114181151811618117181181811918120181211812218123181241812518126181271812818129181301813118132181331813418135181361813718138181391814018141181421814318144181451814618147181481814918150181511815218153181541815518156181571815818159181601816118162181631816418165181661816718168181691817018171181721817318174181751817618177181781817918180181811818218183181841818518186181871818818189181901819118192181931819418195181961819718198181991820018201182021820318204182051820618207182081820918210182111821218213182141821518216182171821818219182201822118222182231822418225182261822718228182291823018231182321823318234182351823618237182381823918240182411824218243182441824518246182471824818249182501825118252182531825418255182561825718258182591826018261182621826318264182651826618267182681826918270182711827218273182741827518276182771827818279182801828118282182831828418285182861828718288182891829018291182921829318294182951829618297182981829918300183011830218303183041830518306183071830818309183101831118312183131831418315183161831718318183191832018321183221832318324183251832618327183281832918330183311833218333183341833518336183371833818339183401834118342183431834418345183461834718348183491835018351183521835318354183551835618357183581835918360183611836218363183641836518366183671836818369183701837118372183731837418375183761837718378183791838018381183821838318384183851838618387183881838918390183911839218393183941839518396183971839818399184001840118402184031840418405184061840718408184091841018411184121841318414184151841618417184181841918420184211842218423184241842518426184271842818429184301843118432184331843418435184361843718438184391844018441184421844318444184451844618447184481844918450184511845218453184541845518456184571845818459184601846118462184631846418465184661846718468184691847018471184721847318474184751847618477184781847918480184811848218483184841848518486184871848818489184901849118492184931849418495184961849718498184991850018501185021850318504185051850618507185081850918510185111851218513185141851518516185171851818519185201852118522185231852418525185261852718528185291853018531185321853318534185351853618537185381853918540185411854218543185441854518546185471854818549185501855118552185531855418555185561855718558185591856018561185621856318564185651856618567185681856918570185711857218573185741857518576185771857818579185801858118582185831858418585185861858718588185891859018591185921859318594185951859618597185981859918600186011860218603186041860518606186071860818609186101861118612186131861418615186161861718618186191862018621186221862318624186251862618627186281862918630186311863218633186341863518636186371863818639186401864118642186431864418645186461864718648186491865018651186521865318654186551865618657186581865918660186611866218663186641866518666186671866818669186701867118672186731867418675186761867718678186791868018681186821868318684186851868618687186881868918690186911869218693186941869518696186971869818699187001870118702187031870418705187061870718708187091871018711187121871318714187151871618717187181871918720187211872218723187241872518726187271872818729187301873118732187331873418735187361873718738187391874018741187421874318744187451874618747187481874918750187511875218753187541875518756187571875818759187601876118762187631876418765187661876718768187691877018771187721877318774187751877618777187781877918780187811878218783187841878518786187871878818789187901879118792187931879418795187961879718798187991880018801188021880318804188051880618807188081880918810188111881218813188141881518816188171881818819188201882118822188231882418825188261882718828188291883018831188321883318834188351883618837188381883918840188411884218843188441884518846188471884818849188501885118852188531885418855188561885718858188591886018861188621886318864188651886618867188681886918870188711887218873188741887518876188771887818879188801888118882188831888418885188861888718888188891889018891188921889318894188951889618897188981889918900189011890218903189041890518906189071890818909189101891118912189131891418915189161891718918189191892018921189221892318924189251892618927189281892918930189311893218933189341893518936189371893818939189401894118942189431894418945189461894718948189491895018951189521895318954189551895618957189581895918960189611896218963189641896518966189671896818969189701897118972189731897418975189761897718978189791898018981189821898318984189851898618987189881898918990189911899218993189941899518996189971899818999190001900119002190031900419005190061900719008190091901019011190121901319014190151901619017190181901919020190211902219023190241902519026190271902819029190301903119032190331903419035190361903719038190391904019041190421904319044190451904619047190481904919050190511905219053190541905519056190571905819059190601906119062190631906419065190661906719068190691907019071190721907319074190751907619077190781907919080190811908219083190841908519086190871908819089190901909119092190931909419095190961909719098190991910019101191021910319104191051910619107191081910919110191111911219113191141911519116191171911819119191201912119122191231912419125191261912719128191291913019131191321913319134191351913619137191381913919140191411914219143191441914519146191471914819149191501915119152191531915419155191561915719158191591916019161191621916319164191651916619167191681916919170191711917219173191741917519176191771917819179191801918119182191831918419185191861918719188191891919019191191921919319194191951919619197191981919919200192011920219203192041920519206192071920819209192101921119212192131921419215192161921719218192191922019221192221922319224192251922619227192281922919230192311923219233192341923519236192371923819239192401924119242192431924419245192461924719248192491925019251192521925319254192551925619257192581925919260192611926219263192641926519266192671926819269192701927119272192731927419275192761927719278192791928019281192821928319284192851928619287192881928919290192911929219293192941929519296192971929819299193001930119302193031930419305193061930719308193091931019311193121931319314193151931619317193181931919320193211932219323193241932519326193271932819329193301933119332193331933419335193361933719338193391934019341193421934319344193451934619347193481934919350193511935219353193541935519356193571935819359193601936119362193631936419365193661936719368193691937019371193721937319374193751937619377193781937919380193811938219383193841938519386193871938819389193901939119392193931939419395193961939719398193991940019401194021940319404194051940619407194081940919410194111941219413194141941519416194171941819419194201942119422194231942419425194261942719428194291943019431194321943319434194351943619437194381943919440194411944219443194441944519446194471944819449194501945119452194531945419455194561945719458194591946019461194621946319464194651946619467194681946919470194711947219473194741947519476194771947819479194801948119482194831948419485194861948719488194891949019491194921949319494194951949619497194981949919500195011950219503195041950519506195071950819509195101951119512195131951419515195161951719518195191952019521195221952319524195251952619527195281952919530195311953219533195341953519536195371953819539195401954119542195431954419545195461954719548195491955019551195521955319554195551955619557195581955919560195611956219563195641956519566195671956819569195701957119572195731957419575195761957719578195791958019581195821958319584195851958619587195881958919590195911959219593195941959519596195971959819599196001960119602196031960419605196061960719608196091961019611196121961319614196151961619617196181961919620196211962219623196241962519626196271962819629196301963119632196331963419635196361963719638196391964019641196421964319644196451964619647196481964919650196511965219653196541965519656196571965819659196601966119662196631966419665196661966719668196691967019671196721967319674196751967619677196781967919680196811968219683196841968519686196871968819689196901969119692196931969419695196961969719698196991970019701197021970319704197051970619707197081970919710197111971219713197141971519716197171971819719197201972119722197231972419725197261972719728197291973019731197321973319734197351973619737197381973919740197411974219743197441974519746197471974819749197501975119752197531975419755197561975719758197591976019761197621976319764197651976619767197681976919770197711977219773197741977519776197771977819779197801978119782197831978419785197861978719788197891979019791197921979319794197951979619797197981979919800198011980219803198041980519806198071980819809198101981119812198131981419815198161981719818198191982019821198221982319824198251982619827198281982919830198311983219833198341983519836198371983819839198401984119842198431984419845198461984719848198491985019851198521985319854198551985619857198581985919860198611986219863198641986519866198671986819869198701987119872198731987419875198761987719878198791988019881198821988319884198851988619887198881988919890198911989219893198941989519896198971989819899199001990119902199031990419905199061990719908199091991019911199121991319914199151991619917199181991919920199211992219923199241992519926199271992819929199301993119932199331993419935199361993719938199391994019941199421994319944199451994619947199481994919950199511995219953199541995519956199571995819959199601996119962199631996419965199661996719968199691997019971199721997319974199751997619977199781997919980199811998219983199841998519986199871998819989199901999119992199931999419995199961999719998199992000020001200022000320004200052000620007200082000920010200112001220013200142001520016200172001820019200202002120022200232002420025200262002720028200292003020031200322003320034200352003620037200382003920040200412004220043200442004520046200472004820049200502005120052200532005420055200562005720058200592006020061200622006320064200652006620067200682006920070200712007220073200742007520076200772007820079200802008120082200832008420085200862008720088200892009020091200922009320094200952009620097200982009920100201012010220103201042010520106201072010820109201102011120112201132011420115201162011720118201192012020121201222012320124201252012620127201282012920130201312013220133201342013520136201372013820139201402014120142201432014420145201462014720148201492015020151201522015320154201552015620157201582015920160201612016220163201642016520166201672016820169201702017120172201732017420175201762017720178201792018020181201822018320184201852018620187201882018920190201912019220193201942019520196201972019820199202002020120202202032020420205202062020720208202092021020211202122021320214202152021620217202182021920220202212022220223202242022520226202272022820229202302023120232202332023420235202362023720238202392024020241202422024320244202452024620247202482024920250202512025220253202542025520256202572025820259202602026120262202632026420265202662026720268202692027020271202722027320274202752027620277202782027920280202812028220283202842028520286202872028820289202902029120292202932029420295202962029720298202992030020301203022030320304203052030620307203082030920310203112031220313203142031520316203172031820319203202032120322203232032420325203262032720328203292033020331203322033320334203352033620337203382033920340203412034220343203442034520346203472034820349203502035120352203532035420355203562035720358203592036020361203622036320364203652036620367203682036920370203712037220373203742037520376203772037820379203802038120382203832038420385203862038720388203892039020391203922039320394203952039620397203982039920400204012040220403204042040520406204072040820409204102041120412204132041420415204162041720418204192042020421204222042320424204252042620427204282042920430204312043220433204342043520436204372043820439204402044120442204432044420445204462044720448204492045020451204522045320454204552045620457204582045920460204612046220463204642046520466204672046820469204702047120472204732047420475204762047720478204792048020481204822048320484204852048620487204882048920490204912049220493204942049520496204972049820499205002050120502205032050420505205062050720508205092051020511205122051320514205152051620517205182051920520205212052220523205242052520526205272052820529205302053120532205332053420535205362053720538205392054020541205422054320544205452054620547205482054920550205512055220553205542055520556205572055820559205602056120562205632056420565205662056720568205692057020571205722057320574205752057620577205782057920580205812058220583205842058520586205872058820589205902059120592205932059420595205962059720598205992060020601206022060320604206052060620607206082060920610206112061220613206142061520616206172061820619206202062120622206232062420625206262062720628206292063020631206322063320634206352063620637206382063920640206412064220643206442064520646206472064820649206502065120652206532065420655206562065720658206592066020661206622066320664206652066620667206682066920670206712067220673206742067520676206772067820679206802068120682206832068420685206862068720688206892069020691206922069320694206952069620697206982069920700207012070220703207042070520706207072070820709207102071120712207132071420715207162071720718207192072020721207222072320724207252072620727207282072920730207312073220733207342073520736207372073820739207402074120742207432074420745207462074720748207492075020751207522075320754207552075620757207582075920760207612076220763207642076520766207672076820769207702077120772207732077420775207762077720778207792078020781207822078320784207852078620787207882078920790207912079220793207942079520796207972079820799208002080120802208032080420805208062080720808208092081020811208122081320814208152081620817208182081920820208212082220823208242082520826208272082820829208302083120832208332083420835208362083720838208392084020841208422084320844208452084620847208482084920850208512085220853208542085520856208572085820859208602086120862208632086420865208662086720868208692087020871208722087320874208752087620877208782087920880208812088220883208842088520886208872088820889208902089120892208932089420895208962089720898208992090020901209022090320904209052090620907209082090920910209112091220913209142091520916209172091820919209202092120922209232092420925209262092720928209292093020931209322093320934209352093620937209382093920940209412094220943209442094520946209472094820949209502095120952209532095420955209562095720958209592096020961209622096320964209652096620967209682096920970209712097220973209742097520976209772097820979209802098120982209832098420985209862098720988209892099020991209922099320994209952099620997209982099921000210012100221003210042100521006210072100821009210102101121012210132101421015210162101721018210192102021021210222102321024210252102621027210282102921030210312103221033210342103521036210372103821039210402104121042210432104421045210462104721048210492105021051210522105321054210552105621057210582105921060210612106221063210642106521066210672106821069210702107121072210732107421075210762107721078210792108021081210822108321084210852108621087210882108921090210912109221093210942109521096210972109821099211002110121102211032110421105211062110721108211092111021111211122111321114211152111621117211182111921120211212112221123211242112521126211272112821129211302113121132211332113421135211362113721138211392114021141211422114321144211452114621147211482114921150211512115221153211542115521156211572115821159211602116121162211632116421165211662116721168211692117021171211722117321174211752117621177211782117921180211812118221183211842118521186211872118821189211902119121192211932119421195211962119721198211992120021201212022120321204212052120621207212082120921210212112121221213212142121521216212172121821219212202122121222212232122421225212262122721228212292123021231212322123321234212352123621237212382123921240212412124221243212442124521246212472124821249212502125121252212532125421255212562125721258212592126021261212622126321264212652126621267212682126921270212712127221273212742127521276212772127821279212802128121282212832128421285212862128721288212892129021291212922129321294212952129621297212982129921300213012130221303213042130521306213072130821309213102131121312213132131421315213162131721318213192132021321213222132321324213252132621327213282132921330213312133221333213342133521336213372133821339213402134121342213432134421345213462134721348213492135021351213522135321354213552135621357213582135921360213612136221363213642136521366213672136821369213702137121372213732137421375213762137721378213792138021381213822138321384213852138621387213882138921390213912139221393213942139521396213972139821399214002140121402214032140421405214062140721408214092141021411214122141321414214152141621417214182141921420214212142221423214242142521426214272142821429214302143121432214332143421435214362143721438214392144021441214422144321444214452144621447214482144921450214512145221453214542145521456214572145821459214602146121462214632146421465214662146721468214692147021471214722147321474214752147621477214782147921480214812148221483214842148521486214872148821489214902149121492214932149421495214962149721498214992150021501215022150321504215052150621507215082150921510215112151221513215142151521516215172151821519215202152121522215232152421525215262152721528215292153021531215322153321534215352153621537215382153921540215412154221543215442154521546215472154821549215502155121552215532155421555215562155721558215592156021561215622156321564215652156621567215682156921570215712157221573215742157521576215772157821579215802158121582215832158421585215862158721588215892159021591215922159321594215952159621597215982159921600216012160221603216042160521606216072160821609216102161121612216132161421615216162161721618216192162021621216222162321624216252162621627216282162921630216312163221633216342163521636216372163821639216402164121642216432164421645216462164721648216492165021651216522165321654216552165621657216582165921660216612166221663216642166521666216672166821669216702167121672216732167421675216762167721678216792168021681216822168321684216852168621687216882168921690216912169221693216942169521696216972169821699217002170121702217032170421705217062170721708217092171021711217122171321714217152171621717217182171921720217212172221723217242172521726217272172821729217302173121732217332173421735217362173721738217392174021741217422174321744217452174621747217482174921750217512175221753217542175521756217572175821759217602176121762217632176421765217662176721768217692177021771217722177321774217752177621777217782177921780217812178221783217842178521786217872178821789217902179121792217932179421795217962179721798217992180021801218022180321804218052180621807218082180921810218112181221813218142181521816218172181821819218202182121822218232182421825218262182721828218292183021831218322183321834218352183621837218382183921840218412184221843218442184521846218472184821849218502185121852218532185421855218562185721858218592186021861218622186321864218652186621867218682186921870218712187221873218742187521876218772187821879218802188121882218832188421885218862188721888218892189021891218922189321894218952189621897218982189921900219012190221903219042190521906219072190821909219102191121912219132191421915219162191721918219192192021921219222192321924219252192621927219282192921930219312193221933219342193521936219372193821939219402194121942219432194421945219462194721948219492195021951219522195321954219552195621957219582195921960219612196221963219642196521966219672196821969219702197121972219732197421975219762197721978219792198021981219822198321984219852198621987219882198921990219912199221993219942199521996219972199821999220002200122002220032200422005220062200722008220092201022011220122201322014220152201622017220182201922020220212202222023220242202522026220272202822029220302203122032220332203422035220362203722038220392204022041220422204322044220452204622047220482204922050220512205222053220542205522056220572205822059220602206122062220632206422065220662206722068220692207022071220722207322074220752207622077220782207922080220812208222083220842208522086220872208822089220902209122092220932209422095220962209722098220992210022101221022210322104221052210622107221082210922110221112211222113221142211522116221172211822119221202212122122221232212422125221262212722128221292213022131221322213322134221352213622137221382213922140221412214222143221442214522146221472214822149221502215122152221532215422155221562215722158221592216022161221622216322164221652216622167221682216922170221712217222173221742217522176221772217822179221802218122182221832218422185221862218722188221892219022191221922219322194221952219622197221982219922200222012220222203222042220522206222072220822209222102221122212222132221422215222162221722218222192222022221222222222322224222252222622227222282222922230222312223222233222342223522236222372223822239222402224122242222432224422245222462224722248222492225022251222522225322254222552225622257222582225922260222612226222263222642226522266222672226822269222702227122272222732227422275222762227722278222792228022281222822228322284222852228622287222882228922290222912229222293222942229522296222972229822299223002230122302223032230422305223062230722308223092231022311223122231322314223152231622317223182231922320223212232222323223242232522326223272232822329223302233122332223332233422335223362233722338223392234022341223422234322344223452234622347223482234922350223512235222353223542235522356223572235822359223602236122362223632236422365223662236722368223692237022371223722237322374223752237622377223782237922380223812238222383223842238522386223872238822389223902239122392223932239422395223962239722398223992240022401224022240322404224052240622407224082240922410224112241222413224142241522416224172241822419224202242122422224232242422425224262242722428224292243022431224322243322434224352243622437224382243922440224412244222443224442244522446224472244822449224502245122452224532245422455224562245722458224592246022461224622246322464224652246622467224682246922470224712247222473224742247522476224772247822479224802248122482224832248422485224862248722488224892249022491224922249322494224952249622497224982249922500225012250222503225042250522506225072250822509225102251122512225132251422515225162251722518225192252022521225222252322524225252252622527225282252922530225312253222533225342253522536225372253822539225402254122542225432254422545225462254722548225492255022551225522255322554225552255622557225582255922560225612256222563225642256522566225672256822569225702257122572225732257422575225762257722578225792258022581225822258322584225852258622587225882258922590225912259222593225942259522596225972259822599226002260122602226032260422605226062260722608226092261022611226122261322614226152261622617226182261922620226212262222623226242262522626226272262822629226302263122632226332263422635226362263722638226392264022641226422264322644226452264622647226482264922650226512265222653226542265522656226572265822659226602266122662226632266422665226662266722668226692267022671226722267322674226752267622677226782267922680226812268222683226842268522686226872268822689226902269122692226932269422695226962269722698226992270022701227022270322704227052270622707227082270922710227112271222713227142271522716227172271822719227202272122722227232272422725227262272722728227292273022731227322273322734227352273622737227382273922740227412274222743227442274522746227472274822749227502275122752227532275422755227562275722758227592276022761227622276322764227652276622767227682276922770227712277222773227742277522776227772277822779227802278122782227832278422785227862278722788227892279022791227922279322794227952279622797227982279922800228012280222803228042280522806228072280822809228102281122812228132281422815228162281722818228192282022821228222282322824228252282622827228282282922830228312283222833228342283522836228372283822839228402284122842228432284422845228462284722848228492285022851228522285322854228552285622857228582285922860228612286222863228642286522866228672286822869228702287122872228732287422875228762287722878228792288022881228822288322884228852288622887228882288922890228912289222893228942289522896228972289822899229002290122902229032290422905229062290722908229092291022911229122291322914229152291622917229182291922920229212292222923229242292522926229272292822929229302293122932229332293422935229362293722938229392294022941229422294322944229452294622947229482294922950229512295222953229542295522956229572295822959229602296122962229632296422965229662296722968229692297022971229722297322974229752297622977229782297922980229812298222983229842298522986229872298822989229902299122992229932299422995229962299722998229992300023001230022300323004230052300623007230082300923010230112301223013230142301523016230172301823019230202302123022230232302423025230262302723028230292303023031230322303323034230352303623037230382303923040230412304223043230442304523046230472304823049230502305123052230532305423055230562305723058230592306023061230622306323064230652306623067230682306923070230712307223073230742307523076230772307823079230802308123082230832308423085230862308723088230892309023091230922309323094230952309623097230982309923100231012310223103231042310523106231072310823109231102311123112231132311423115231162311723118231192312023121231222312323124231252312623127231282312923130231312313223133231342313523136231372313823139231402314123142231432314423145231462314723148231492315023151231522315323154231552315623157231582315923160231612316223163231642316523166231672316823169231702317123172231732317423175231762317723178231792318023181231822318323184231852318623187231882318923190231912319223193231942319523196231972319823199232002320123202232032320423205232062320723208232092321023211232122321323214232152321623217232182321923220232212322223223232242322523226232272322823229232302323123232232332323423235232362323723238232392324023241232422324323244232452324623247232482324923250232512325223253232542325523256232572325823259232602326123262232632326423265232662326723268232692327023271232722327323274232752327623277232782327923280232812328223283232842328523286232872328823289232902329123292232932329423295232962329723298232992330023301233022330323304233052330623307233082330923310233112331223313233142331523316233172331823319233202332123322233232332423325233262332723328233292333023331233322333323334233352333623337233382333923340233412334223343233442334523346233472334823349233502335123352233532335423355233562335723358233592336023361233622336323364233652336623367233682336923370233712337223373233742337523376233772337823379233802338123382233832338423385233862338723388233892339023391233922339323394233952339623397233982339923400234012340223403234042340523406234072340823409234102341123412234132341423415234162341723418234192342023421234222342323424234252342623427234282342923430234312343223433234342343523436234372343823439234402344123442234432344423445234462344723448234492345023451234522345323454234552345623457234582345923460234612346223463234642346523466234672346823469234702347123472234732347423475234762347723478234792348023481234822348323484234852348623487234882348923490234912349223493234942349523496234972349823499235002350123502235032350423505235062350723508235092351023511235122351323514235152351623517235182351923520235212352223523235242352523526235272352823529235302353123532235332353423535235362353723538235392354023541235422354323544235452354623547235482354923550235512355223553235542355523556235572355823559235602356123562235632356423565235662356723568235692357023571235722357323574235752357623577235782357923580235812358223583235842358523586235872358823589235902359123592235932359423595235962359723598235992360023601236022360323604236052360623607236082360923610236112361223613236142361523616236172361823619236202362123622236232362423625236262362723628236292363023631236322363323634236352363623637236382363923640236412364223643236442364523646236472364823649236502365123652236532365423655236562365723658236592366023661236622366323664236652366623667236682366923670236712367223673236742367523676236772367823679236802368123682236832368423685236862368723688236892369023691236922369323694236952369623697236982369923700237012370223703237042370523706237072370823709237102371123712237132371423715237162371723718237192372023721237222372323724237252372623727237282372923730237312373223733237342373523736237372373823739237402374123742237432374423745237462374723748237492375023751237522375323754237552375623757237582375923760237612376223763237642376523766237672376823769237702377123772237732377423775237762377723778237792378023781237822378323784237852378623787237882378923790237912379223793237942379523796237972379823799238002380123802238032380423805238062380723808238092381023811238122381323814238152381623817238182381923820238212382223823238242382523826238272382823829238302383123832238332383423835238362383723838238392384023841238422384323844238452384623847238482384923850238512385223853238542385523856238572385823859238602386123862238632386423865238662386723868238692387023871238722387323874238752387623877238782387923880238812388223883238842388523886238872388823889238902389123892238932389423895238962389723898238992390023901239022390323904239052390623907239082390923910239112391223913239142391523916239172391823919239202392123922239232392423925239262392723928239292393023931239322393323934239352393623937239382393923940239412394223943239442394523946239472394823949239502395123952239532395423955239562395723958239592396023961239622396323964239652396623967239682396923970239712397223973239742397523976239772397823979239802398123982239832398423985239862398723988239892399023991239922399323994239952399623997239982399924000240012400224003240042400524006240072400824009240102401124012240132401424015240162401724018240192402024021240222402324024240252402624027240282402924030240312403224033240342403524036240372403824039240402404124042240432404424045240462404724048240492405024051240522405324054240552405624057240582405924060240612406224063240642406524066240672406824069240702407124072240732407424075240762407724078240792408024081240822408324084240852408624087240882408924090240912409224093240942409524096240972409824099241002410124102241032410424105241062410724108241092411024111241122411324114241152411624117241182411924120241212412224123241242412524126241272412824129241302413124132241332413424135241362413724138241392414024141241422414324144241452414624147241482414924150241512415224153241542415524156241572415824159241602416124162241632416424165241662416724168241692417024171241722417324174241752417624177241782417924180241812418224183241842418524186241872418824189241902419124192241932419424195241962419724198241992420024201242022420324204242052420624207242082420924210242112421224213242142421524216242172421824219242202422124222242232422424225242262422724228242292423024231242322423324234242352423624237242382423924240242412424224243242442424524246242472424824249242502425124252242532425424255242562425724258242592426024261242622426324264242652426624267242682426924270242712427224273242742427524276242772427824279242802428124282242832428424285242862428724288242892429024291242922429324294242952429624297242982429924300243012430224303243042430524306243072430824309243102431124312243132431424315243162431724318243192432024321243222432324324243252432624327243282432924330243312433224333243342433524336243372433824339243402434124342243432434424345243462434724348243492435024351243522435324354243552435624357243582435924360243612436224363243642436524366243672436824369243702437124372243732437424375243762437724378243792438024381243822438324384243852438624387243882438924390243912439224393243942439524396243972439824399244002440124402244032440424405244062440724408244092441024411244122441324414244152441624417244182441924420244212442224423244242442524426244272442824429244302443124432244332443424435244362443724438244392444024441244422444324444244452444624447244482444924450244512445224453244542445524456244572445824459244602446124462244632446424465244662446724468244692447024471244722447324474244752447624477244782447924480244812448224483244842448524486244872448824489244902449124492244932449424495244962449724498244992450024501245022450324504245052450624507245082450924510245112451224513245142451524516245172451824519245202452124522245232452424525245262452724528245292453024531245322453324534245352453624537245382453924540245412454224543245442454524546245472454824549245502455124552245532455424555245562455724558245592456024561245622456324564245652456624567245682456924570245712457224573245742457524576245772457824579245802458124582245832458424585245862458724588245892459024591245922459324594245952459624597245982459924600246012460224603246042460524606246072460824609246102461124612246132461424615246162461724618246192462024621246222462324624246252462624627246282462924630246312463224633246342463524636246372463824639246402464124642246432464424645246462464724648246492465024651246522465324654246552465624657246582465924660246612466224663246642466524666246672466824669246702467124672246732467424675246762467724678246792468024681246822468324684246852468624687246882468924690246912469224693246942469524696246972469824699247002470124702247032470424705247062470724708247092471024711247122471324714247152471624717247182471924720247212472224723247242472524726247272472824729247302473124732247332473424735247362473724738247392474024741247422474324744247452474624747247482474924750247512475224753247542475524756247572475824759247602476124762247632476424765247662476724768247692477024771247722477324774247752477624777247782477924780247812478224783247842478524786247872478824789247902479124792247932479424795247962479724798247992480024801248022480324804248052480624807248082480924810248112481224813248142481524816248172481824819248202482124822248232482424825248262482724828248292483024831248322483324834248352483624837248382483924840248412484224843248442484524846248472484824849248502485124852248532485424855248562485724858248592486024861248622486324864248652486624867248682486924870248712487224873248742487524876248772487824879248802488124882248832488424885248862488724888248892489024891248922489324894248952489624897248982489924900249012490224903249042490524906249072490824909249102491124912249132491424915249162491724918249192492024921249222492324924249252492624927249282492924930249312493224933249342493524936249372493824939249402494124942249432494424945249462494724948249492495024951249522495324954249552495624957249582495924960249612496224963249642496524966249672496824969249702497124972249732497424975249762497724978249792498024981249822498324984249852498624987249882498924990249912499224993249942499524996249972499824999250002500125002250032500425005250062500725008250092501025011250122501325014250152501625017250182501925020250212502225023250242502525026250272502825029250302503125032250332503425035250362503725038250392504025041250422504325044250452504625047250482504925050250512505225053250542505525056250572505825059250602506125062250632506425065250662506725068250692507025071250722507325074250752507625077250782507925080250812508225083250842508525086250872508825089250902509125092250932509425095250962509725098250992510025101251022510325104251052510625107251082510925110251112511225113251142511525116251172511825119251202512125122251232512425125251262512725128251292513025131251322513325134251352513625137251382513925140251412514225143251442514525146251472514825149251502515125152251532515425155251562515725158251592516025161251622516325164251652516625167251682516925170251712517225173251742517525176251772517825179251802518125182251832518425185251862518725188251892519025191251922519325194251952519625197251982519925200252012520225203252042520525206252072520825209252102521125212252132521425215252162521725218252192522025221252222522325224252252522625227252282522925230252312523225233252342523525236252372523825239252402524125242252432524425245252462524725248252492525025251252522525325254252552525625257252582525925260252612526225263252642526525266252672526825269252702527125272252732527425275252762527725278252792528025281252822528325284252852528625287252882528925290252912529225293252942529525296252972529825299253002530125302253032530425305253062530725308253092531025311253122531325314253152531625317253182531925320253212532225323253242532525326253272532825329253302533125332253332533425335253362533725338253392534025341253422534325344253452534625347253482534925350253512535225353253542535525356253572535825359253602536125362253632536425365253662536725368253692537025371253722537325374253752537625377253782537925380253812538225383253842538525386253872538825389253902539125392253932539425395253962539725398253992540025401254022540325404254052540625407254082540925410254112541225413254142541525416254172541825419254202542125422254232542425425254262542725428254292543025431254322543325434254352543625437254382543925440254412544225443254442544525446254472544825449254502545125452254532545425455254562545725458254592546025461254622546325464254652546625467254682546925470254712547225473254742547525476254772547825479254802548125482254832548425485254862548725488254892549025491254922549325494254952549625497254982549925500255012550225503255042550525506255072550825509255102551125512255132551425515255162551725518255192552025521255222552325524255252552625527255282552925530255312553225533255342553525536255372553825539255402554125542255432554425545255462554725548255492555025551255522555325554255552555625557255582555925560255612556225563255642556525566255672556825569255702557125572255732557425575255762557725578255792558025581255822558325584255852558625587255882558925590255912559225593255942559525596255972559825599256002560125602256032560425605256062560725608256092561025611256122561325614256152561625617256182561925620256212562225623256242562525626256272562825629256302563125632256332563425635256362563725638256392564025641256422564325644256452564625647256482564925650256512565225653256542565525656256572565825659256602566125662256632566425665256662566725668256692567025671256722567325674256752567625677256782567925680256812568225683256842568525686256872568825689256902569125692256932569425695256962569725698256992570025701257022570325704257052570625707257082570925710257112571225713257142571525716257172571825719257202572125722257232572425725257262572725728257292573025731257322573325734257352573625737257382573925740257412574225743257442574525746257472574825749257502575125752257532575425755257562575725758257592576025761257622576325764257652576625767257682576925770257712577225773257742577525776257772577825779257802578125782257832578425785257862578725788257892579025791257922579325794257952579625797257982579925800258012580225803258042580525806258072580825809258102581125812258132581425815258162581725818258192582025821258222582325824258252582625827258282582925830258312583225833258342583525836258372583825839258402584125842258432584425845258462584725848258492585025851258522585325854258552585625857258582585925860258612586225863258642586525866258672586825869258702587125872258732587425875258762587725878258792588025881258822588325884258852588625887258882588925890258912589225893258942589525896258972589825899259002590125902259032590425905259062590725908259092591025911259122591325914259152591625917259182591925920259212592225923259242592525926259272592825929259302593125932259332593425935259362593725938259392594025941259422594325944259452594625947259482594925950259512595225953259542595525956259572595825959259602596125962259632596425965259662596725968259692597025971259722597325974259752597625977259782597925980259812598225983259842598525986259872598825989259902599125992259932599425995259962599725998259992600026001260022600326004260052600626007260082600926010260112601226013260142601526016260172601826019260202602126022260232602426025260262602726028260292603026031260322603326034260352603626037260382603926040260412604226043260442604526046260472604826049260502605126052260532605426055260562605726058260592606026061260622606326064260652606626067260682606926070260712607226073260742607526076260772607826079260802608126082260832608426085260862608726088260892609026091260922609326094260952609626097260982609926100261012610226103261042610526106261072610826109261102611126112261132611426115261162611726118261192612026121261222612326124261252612626127261282612926130261312613226133261342613526136261372613826139261402614126142261432614426145261462614726148261492615026151261522615326154261552615626157261582615926160261612616226163261642616526166261672616826169261702617126172261732617426175261762617726178261792618026181261822618326184261852618626187261882618926190261912619226193261942619526196261972619826199262002620126202262032620426205262062620726208262092621026211262122621326214262152621626217262182621926220262212622226223262242622526226262272622826229262302623126232262332623426235262362623726238262392624026241262422624326244262452624626247262482624926250262512625226253262542625526256262572625826259262602626126262262632626426265262662626726268262692627026271262722627326274262752627626277262782627926280262812628226283262842628526286262872628826289262902629126292262932629426295262962629726298262992630026301263022630326304263052630626307263082630926310263112631226313263142631526316263172631826319263202632126322263232632426325263262632726328263292633026331263322633326334263352633626337263382633926340263412634226343263442634526346263472634826349263502635126352263532635426355263562635726358263592636026361263622636326364263652636626367263682636926370263712637226373263742637526376263772637826379263802638126382263832638426385263862638726388263892639026391263922639326394263952639626397263982639926400264012640226403264042640526406264072640826409264102641126412264132641426415264162641726418264192642026421264222642326424264252642626427264282642926430264312643226433264342643526436264372643826439264402644126442264432644426445264462644726448264492645026451264522645326454264552645626457264582645926460264612646226463264642646526466264672646826469264702647126472264732647426475264762647726478264792648026481264822648326484264852648626487264882648926490264912649226493264942649526496264972649826499265002650126502265032650426505265062650726508265092651026511265122651326514265152651626517265182651926520265212652226523265242652526526265272652826529265302653126532265332653426535265362653726538265392654026541265422654326544265452654626547265482654926550265512655226553265542655526556265572655826559265602656126562265632656426565265662656726568265692657026571265722657326574265752657626577265782657926580265812658226583265842658526586265872658826589265902659126592265932659426595265962659726598265992660026601266022660326604266052660626607266082660926610266112661226613266142661526616266172661826619266202662126622266232662426625266262662726628266292663026631266322663326634266352663626637266382663926640266412664226643266442664526646266472664826649266502665126652266532665426655266562665726658266592666026661266622666326664266652666626667266682666926670266712667226673266742667526676266772667826679266802668126682266832668426685266862668726688266892669026691266922669326694266952669626697266982669926700267012670226703267042670526706267072670826709267102671126712267132671426715267162671726718267192672026721267222672326724267252672626727267282672926730267312673226733267342673526736267372673826739267402674126742267432674426745267462674726748267492675026751267522675326754267552675626757267582675926760267612676226763267642676526766267672676826769267702677126772267732677426775267762677726778267792678026781267822678326784267852678626787267882678926790267912679226793267942679526796267972679826799268002680126802268032680426805268062680726808268092681026811268122681326814268152681626817268182681926820268212682226823268242682526826268272682826829268302683126832268332683426835268362683726838268392684026841268422684326844268452684626847268482684926850268512685226853268542685526856268572685826859268602686126862268632686426865268662686726868268692687026871268722687326874268752687626877268782687926880268812688226883268842688526886268872688826889268902689126892268932689426895268962689726898268992690026901269022690326904269052690626907269082690926910269112691226913269142691526916269172691826919269202692126922269232692426925269262692726928269292693026931269322693326934269352693626937269382693926940269412694226943269442694526946269472694826949269502695126952269532695426955269562695726958269592696026961269622696326964269652696626967269682696926970269712697226973269742697526976269772697826979269802698126982269832698426985269862698726988269892699026991269922699326994269952699626997269982699927000270012700227003270042700527006270072700827009270102701127012270132701427015270162701727018270192702027021270222702327024270252702627027270282702927030270312703227033270342703527036270372703827039270402704127042270432704427045270462704727048270492705027051270522705327054270552705627057270582705927060270612706227063270642706527066270672706827069270702707127072270732707427075270762707727078270792708027081270822708327084270852708627087270882708927090270912709227093270942709527096270972709827099271002710127102271032710427105271062710727108271092711027111271122711327114271152711627117271182711927120271212712227123271242712527126271272712827129271302713127132271332713427135271362713727138271392714027141271422714327144271452714627147271482714927150271512715227153271542715527156271572715827159271602716127162271632716427165271662716727168271692717027171271722717327174271752717627177271782717927180271812718227183271842718527186271872718827189271902719127192271932719427195271962719727198271992720027201272022720327204272052720627207272082720927210272112721227213272142721527216272172721827219272202722127222272232722427225272262722727228272292723027231272322723327234272352723627237272382723927240272412724227243272442724527246272472724827249272502725127252272532725427255272562725727258272592726027261272622726327264272652726627267272682726927270272712727227273272742727527276272772727827279272802728127282272832728427285272862728727288272892729027291272922729327294272952729627297272982729927300273012730227303273042730527306273072730827309273102731127312273132731427315273162731727318273192732027321273222732327324273252732627327273282732927330273312733227333273342733527336273372733827339273402734127342273432734427345273462734727348273492735027351273522735327354273552735627357273582735927360273612736227363273642736527366273672736827369273702737127372273732737427375273762737727378273792738027381273822738327384273852738627387273882738927390273912739227393273942739527396273972739827399274002740127402274032740427405274062740727408274092741027411274122741327414274152741627417274182741927420274212742227423274242742527426274272742827429274302743127432274332743427435274362743727438274392744027441274422744327444274452744627447274482744927450274512745227453274542745527456274572745827459274602746127462274632746427465274662746727468274692747027471274722747327474274752747627477274782747927480274812748227483274842748527486274872748827489274902749127492274932749427495274962749727498274992750027501275022750327504275052750627507275082750927510275112751227513275142751527516275172751827519275202752127522275232752427525275262752727528275292753027531275322753327534275352753627537275382753927540275412754227543275442754527546275472754827549275502755127552275532755427555275562755727558275592756027561275622756327564275652756627567275682756927570275712757227573275742757527576275772757827579275802758127582275832758427585275862758727588275892759027591275922759327594275952759627597275982759927600276012760227603276042760527606276072760827609276102761127612276132761427615276162761727618276192762027621276222762327624276252762627627276282762927630276312763227633276342763527636276372763827639276402764127642276432764427645276462764727648276492765027651276522765327654276552765627657276582765927660276612766227663276642766527666276672766827669276702767127672276732767427675276762767727678276792768027681276822768327684276852768627687276882768927690276912769227693276942769527696276972769827699277002770127702277032770427705277062770727708277092771027711277122771327714277152771627717277182771927720277212772227723277242772527726277272772827729277302773127732277332773427735277362773727738277392774027741277422774327744277452774627747277482774927750277512775227753277542775527756277572775827759277602776127762277632776427765277662776727768277692777027771277722777327774277752777627777277782777927780277812778227783277842778527786277872778827789277902779127792277932779427795277962779727798277992780027801278022780327804278052780627807278082780927810278112781227813278142781527816278172781827819278202782127822278232782427825278262782727828278292783027831278322783327834278352783627837278382783927840278412784227843278442784527846278472784827849278502785127852278532785427855278562785727858278592786027861278622786327864278652786627867278682786927870278712787227873278742787527876278772787827879278802788127882278832788427885278862788727888278892789027891278922789327894278952789627897278982789927900279012790227903279042790527906279072790827909279102791127912279132791427915279162791727918279192792027921279222792327924279252792627927279282792927930279312793227933279342793527936279372793827939279402794127942279432794427945279462794727948279492795027951279522795327954279552795627957279582795927960279612796227963279642796527966279672796827969279702797127972279732797427975279762797727978279792798027981279822798327984279852798627987279882798927990279912799227993279942799527996279972799827999280002800128002280032800428005280062800728008280092801028011280122801328014280152801628017280182801928020280212802228023280242802528026280272802828029280302803128032280332803428035280362803728038280392804028041280422804328044280452804628047280482804928050280512805228053280542805528056280572805828059280602806128062280632806428065280662806728068280692807028071280722807328074280752807628077280782807928080280812808228083280842808528086280872808828089280902809128092280932809428095280962809728098280992810028101281022810328104281052810628107281082810928110281112811228113281142811528116281172811828119281202812128122281232812428125281262812728128281292813028131281322813328134281352813628137281382813928140281412814228143281442814528146281472814828149281502815128152281532815428155281562815728158281592816028161281622816328164281652816628167281682816928170281712817228173281742817528176281772817828179281802818128182281832818428185281862818728188281892819028191281922819328194281952819628197281982819928200282012820228203282042820528206282072820828209282102821128212282132821428215282162821728218282192822028221282222822328224282252822628227282282822928230282312823228233282342823528236282372823828239282402824128242282432824428245282462824728248282492825028251282522825328254282552825628257282582825928260282612826228263282642826528266282672826828269282702827128272282732827428275282762827728278282792828028281282822828328284282852828628287282882828928290282912829228293282942829528296282972829828299283002830128302283032830428305283062830728308283092831028311283122831328314283152831628317283182831928320283212832228323283242832528326283272832828329283302833128332283332833428335283362833728338283392834028341283422834328344283452834628347283482834928350283512835228353283542835528356283572835828359283602836128362283632836428365283662836728368283692837028371283722837328374283752837628377283782837928380283812838228383283842838528386283872838828389283902839128392283932839428395283962839728398283992840028401284022840328404284052840628407284082840928410284112841228413284142841528416284172841828419284202842128422284232842428425284262842728428284292843028431284322843328434284352843628437284382843928440284412844228443284442844528446284472844828449284502845128452284532845428455284562845728458284592846028461284622846328464284652846628467284682846928470284712847228473284742847528476284772847828479284802848128482284832848428485284862848728488284892849028491284922849328494284952849628497284982849928500285012850228503285042850528506285072850828509285102851128512285132851428515285162851728518285192852028521285222852328524285252852628527285282852928530285312853228533285342853528536285372853828539285402854128542285432854428545285462854728548285492855028551285522855328554285552855628557285582855928560285612856228563285642856528566285672856828569285702857128572285732857428575285762857728578285792858028581285822858328584285852858628587285882858928590285912859228593285942859528596285972859828599286002860128602286032860428605286062860728608286092861028611286122861328614286152861628617286182861928620286212862228623286242862528626286272862828629286302863128632286332863428635286362863728638286392864028641286422864328644286452864628647286482864928650286512865228653286542865528656286572865828659286602866128662286632866428665286662866728668286692867028671286722867328674286752867628677286782867928680286812868228683286842868528686286872868828689286902869128692286932869428695286962869728698286992870028701287022870328704287052870628707287082870928710287112871228713287142871528716287172871828719287202872128722287232872428725287262872728728287292873028731287322873328734287352873628737287382873928740287412874228743287442874528746287472874828749287502875128752287532875428755287562875728758287592876028761287622876328764287652876628767287682876928770287712877228773287742877528776287772877828779287802878128782287832878428785287862878728788287892879028791287922879328794287952879628797287982879928800288012880228803288042880528806288072880828809288102881128812288132881428815288162881728818288192882028821288222882328824288252882628827288282882928830288312883228833288342883528836288372883828839288402884128842288432884428845288462884728848288492885028851288522885328854288552885628857288582885928860288612886228863288642886528866288672886828869288702887128872288732887428875288762887728878288792888028881288822888328884288852888628887288882888928890288912889228893288942889528896288972889828899289002890128902289032890428905289062890728908289092891028911289122891328914289152891628917289182891928920289212892228923289242892528926289272892828929289302893128932289332893428935289362893728938289392894028941289422894328944289452894628947289482894928950289512895228953289542895528956289572895828959289602896128962289632896428965289662896728968289692897028971289722897328974289752897628977289782897928980289812898228983289842898528986289872898828989289902899128992289932899428995289962899728998289992900029001290022900329004290052900629007290082900929010290112901229013290142901529016290172901829019290202902129022290232902429025290262902729028290292903029031290322903329034290352903629037290382903929040290412904229043290442904529046290472904829049290502905129052290532905429055290562905729058290592906029061290622906329064290652906629067290682906929070290712907229073290742907529076290772907829079290802908129082290832908429085290862908729088290892909029091290922909329094290952909629097290982909929100291012910229103291042910529106291072910829109291102911129112291132911429115291162911729118291192912029121291222912329124291252912629127291282912929130291312913229133291342913529136291372913829139291402914129142291432914429145291462914729148291492915029151291522915329154291552915629157291582915929160291612916229163291642916529166291672916829169291702917129172291732917429175291762917729178291792918029181291822918329184291852918629187291882918929190291912919229193291942919529196291972919829199292002920129202292032920429205292062920729208292092921029211292122921329214292152921629217292182921929220292212922229223292242922529226292272922829229292302923129232292332923429235292362923729238292392924029241292422924329244292452924629247292482924929250292512925229253292542925529256292572925829259292602926129262292632926429265292662926729268292692927029271292722927329274292752927629277292782927929280292812928229283292842928529286292872928829289292902929129292292932929429295292962929729298292992930029301293022930329304293052930629307293082930929310293112931229313293142931529316293172931829319293202932129322293232932429325293262932729328293292933029331293322933329334293352933629337293382933929340293412934229343293442934529346293472934829349293502935129352293532935429355293562935729358293592936029361293622936329364293652936629367293682936929370293712937229373293742937529376293772937829379293802938129382293832938429385293862938729388293892939029391293922939329394293952939629397293982939929400294012940229403294042940529406294072940829409294102941129412294132941429415294162941729418294192942029421294222942329424294252942629427294282942929430294312943229433294342943529436294372943829439294402944129442294432944429445294462944729448294492945029451294522945329454294552945629457294582945929460294612946229463294642946529466294672946829469294702947129472294732947429475294762947729478294792948029481294822948329484294852948629487294882948929490294912949229493294942949529496294972949829499295002950129502295032950429505295062950729508295092951029511295122951329514295152951629517295182951929520295212952229523295242952529526295272952829529295302953129532295332953429535295362953729538295392954029541295422954329544295452954629547295482954929550295512955229553295542955529556295572955829559295602956129562295632956429565295662956729568295692957029571295722957329574295752957629577295782957929580295812958229583295842958529586295872958829589295902959129592295932959429595295962959729598295992960029601296022960329604296052960629607296082960929610296112961229613296142961529616296172961829619296202962129622296232962429625296262962729628296292963029631296322963329634296352963629637296382963929640296412964229643296442964529646296472964829649296502965129652296532965429655296562965729658296592966029661296622966329664296652966629667296682966929670296712967229673296742967529676296772967829679296802968129682296832968429685296862968729688296892969029691296922969329694296952969629697296982969929700297012970229703297042970529706297072970829709297102971129712297132971429715297162971729718297192972029721297222972329724297252972629727297282972929730297312973229733297342973529736297372973829739297402974129742297432974429745297462974729748297492975029751297522975329754297552975629757297582975929760297612976229763297642976529766297672976829769297702977129772297732977429775297762977729778297792978029781297822978329784297852978629787297882978929790297912979229793297942979529796297972979829799298002980129802298032980429805298062980729808298092981029811298122981329814298152981629817298182981929820298212982229823298242982529826298272982829829298302983129832298332983429835298362983729838298392984029841298422984329844298452984629847298482984929850298512985229853298542985529856298572985829859298602986129862298632986429865298662986729868298692987029871298722987329874298752987629877298782987929880298812988229883298842988529886298872988829889298902989129892298932989429895298962989729898298992990029901299022990329904299052990629907299082990929910299112991229913299142991529916299172991829919299202992129922299232992429925299262992729928299292993029931299322993329934299352993629937299382993929940299412994229943299442994529946299472994829949299502995129952299532995429955299562995729958299592996029961299622996329964299652996629967299682996929970299712997229973299742997529976299772997829979299802998129982299832998429985299862998729988299892999029991299922999329994299952999629997299982999930000300013000230003300043000530006300073000830009300103001130012300133001430015300163001730018300193002030021300223002330024300253002630027300283002930030300313003230033300343003530036300373003830039300403004130042300433004430045300463004730048300493005030051300523005330054300553005630057300583005930060300613006230063300643006530066300673006830069300703007130072300733007430075300763007730078300793008030081300823008330084300853008630087300883008930090300913009230093300943009530096300973009830099301003010130102301033010430105301063010730108301093011030111301123011330114301153011630117301183011930120301213012230123301243012530126301273012830129301303013130132301333013430135301363013730138301393014030141301423014330144301453014630147301483014930150301513015230153301543015530156301573015830159301603016130162301633016430165301663016730168301693017030171301723017330174301753017630177301783017930180301813018230183301843018530186301873018830189301903019130192301933019430195301963019730198301993020030201302023020330204302053020630207302083020930210302113021230213302143021530216302173021830219302203022130222302233022430225302263022730228302293023030231302323023330234302353023630237302383023930240302413024230243302443024530246302473024830249302503025130252302533025430255302563025730258302593026030261302623026330264302653026630267302683026930270302713027230273302743027530276302773027830279302803028130282302833028430285302863028730288302893029030291302923029330294302953029630297302983029930300303013030230303303043030530306303073030830309303103031130312303133031430315303163031730318303193032030321303223032330324303253032630327303283032930330303313033230333303343033530336303373033830339303403034130342303433034430345303463034730348303493035030351303523035330354303553035630357303583035930360303613036230363303643036530366303673036830369303703037130372303733037430375303763037730378303793038030381303823038330384303853038630387303883038930390303913039230393303943039530396303973039830399304003040130402304033040430405304063040730408304093041030411304123041330414304153041630417304183041930420304213042230423304243042530426304273042830429304303043130432304333043430435304363043730438304393044030441304423044330444304453044630447304483044930450304513045230453304543045530456304573045830459304603046130462304633046430465304663046730468304693047030471304723047330474304753047630477304783047930480304813048230483304843048530486304873048830489304903049130492304933049430495304963049730498304993050030501305023050330504305053050630507305083050930510305113051230513305143051530516305173051830519305203052130522305233052430525305263052730528305293053030531305323053330534305353053630537305383053930540305413054230543305443054530546305473054830549305503055130552305533055430555305563055730558305593056030561305623056330564305653056630567305683056930570305713057230573305743057530576305773057830579305803058130582305833058430585305863058730588305893059030591305923059330594305953059630597305983059930600306013060230603306043060530606306073060830609306103061130612306133061430615306163061730618306193062030621306223062330624306253062630627306283062930630306313063230633306343063530636306373063830639306403064130642306433064430645306463064730648306493065030651306523065330654306553065630657306583065930660306613066230663306643066530666306673066830669306703067130672306733067430675306763067730678306793068030681306823068330684306853068630687306883068930690306913069230693306943069530696306973069830699307003070130702307033070430705307063070730708307093071030711307123071330714307153071630717307183071930720307213072230723307243072530726307273072830729307303073130732307333073430735307363073730738307393074030741307423074330744307453074630747307483074930750307513075230753307543075530756307573075830759307603076130762307633076430765307663076730768307693077030771307723077330774307753077630777307783077930780307813078230783307843078530786307873078830789307903079130792307933079430795307963079730798307993080030801308023080330804308053080630807308083080930810308113081230813308143081530816308173081830819308203082130822308233082430825308263082730828308293083030831308323083330834308353083630837308383083930840308413084230843308443084530846308473084830849308503085130852308533085430855308563085730858308593086030861308623086330864308653086630867308683086930870308713087230873308743087530876308773087830879308803088130882308833088430885308863088730888308893089030891308923089330894308953089630897308983089930900309013090230903309043090530906309073090830909309103091130912309133091430915309163091730918309193092030921309223092330924309253092630927309283092930930309313093230933309343093530936309373093830939309403094130942309433094430945309463094730948309493095030951309523095330954309553095630957309583095930960309613096230963309643096530966309673096830969309703097130972309733097430975309763097730978309793098030981309823098330984309853098630987309883098930990309913099230993309943099530996309973099830999310003100131002310033100431005310063100731008310093101031011310123101331014310153101631017310183101931020310213102231023310243102531026310273102831029310303103131032310333103431035310363103731038310393104031041310423104331044310453104631047310483104931050310513105231053310543105531056310573105831059310603106131062310633106431065310663106731068310693107031071310723107331074310753107631077310783107931080310813108231083310843108531086310873108831089310903109131092310933109431095310963109731098310993110031101311023110331104311053110631107311083110931110311113111231113311143111531116311173111831119311203112131122311233112431125311263112731128311293113031131311323113331134311353113631137311383113931140311413114231143311443114531146311473114831149311503115131152311533115431155311563115731158311593116031161311623116331164311653116631167311683116931170311713117231173311743117531176311773117831179311803118131182311833118431185311863118731188311893119031191311923119331194311953119631197311983119931200312013120231203312043120531206312073120831209312103121131212312133121431215312163121731218312193122031221312223122331224312253122631227312283122931230312313123231233312343123531236312373123831239312403124131242312433124431245312463124731248312493125031251312523125331254312553125631257312583125931260312613126231263312643126531266312673126831269312703127131272312733127431275312763127731278312793128031281312823128331284312853128631287312883128931290312913129231293312943129531296312973129831299313003130131302313033130431305313063130731308313093131031311313123131331314313153131631317313183131931320313213132231323313243132531326313273132831329313303133131332313333133431335313363133731338313393134031341313423134331344313453134631347313483134931350313513135231353313543135531356313573135831359313603136131362313633136431365313663136731368313693137031371313723137331374313753137631377313783137931380313813138231383313843138531386313873138831389313903139131392313933139431395313963139731398313993140031401314023140331404314053140631407314083140931410314113141231413314143141531416314173141831419314203142131422314233142431425314263142731428314293143031431314323143331434314353143631437314383143931440314413144231443314443144531446314473144831449314503145131452314533145431455314563145731458314593146031461314623146331464314653146631467314683146931470314713147231473314743147531476314773147831479314803148131482314833148431485314863148731488314893149031491314923149331494314953149631497314983149931500315013150231503315043150531506315073150831509315103151131512315133151431515315163151731518315193152031521315223152331524315253152631527315283152931530315313153231533315343153531536315373153831539315403154131542315433154431545315463154731548315493155031551315523155331554315553155631557315583155931560315613156231563315643156531566315673156831569315703157131572315733157431575315763157731578315793158031581315823158331584315853158631587315883158931590315913159231593315943159531596315973159831599316003160131602316033160431605316063160731608316093161031611316123161331614316153161631617316183161931620316213162231623316243162531626316273162831629316303163131632316333163431635316363163731638316393164031641316423164331644316453164631647316483164931650316513165231653316543165531656316573165831659316603166131662316633166431665316663166731668316693167031671316723167331674316753167631677316783167931680316813168231683316843168531686316873168831689316903169131692316933169431695316963169731698316993170031701317023170331704317053170631707317083170931710317113171231713317143171531716317173171831719317203172131722317233172431725317263172731728317293173031731317323173331734317353173631737317383173931740317413174231743317443174531746317473174831749317503175131752317533175431755317563175731758317593176031761317623176331764317653176631767317683176931770317713177231773317743177531776317773177831779317803178131782317833178431785317863178731788317893179031791317923179331794317953179631797317983179931800318013180231803318043180531806318073180831809318103181131812318133181431815318163181731818318193182031821318223182331824318253182631827318283182931830318313183231833318343183531836318373183831839318403184131842318433184431845318463184731848318493185031851318523185331854318553185631857318583185931860318613186231863318643186531866318673186831869318703187131872318733187431875318763187731878318793188031881318823188331884318853188631887318883188931890318913189231893318943189531896318973189831899319003190131902319033190431905319063190731908319093191031911319123191331914319153191631917319183191931920319213192231923319243192531926319273192831929319303193131932319333193431935319363193731938319393194031941319423194331944319453194631947319483194931950319513195231953319543195531956319573195831959319603196131962319633196431965319663196731968319693197031971319723197331974319753197631977319783197931980319813198231983319843198531986319873198831989319903199131992319933199431995319963199731998319993200032001320023200332004320053200632007320083200932010320113201232013320143201532016320173201832019320203202132022320233202432025320263202732028320293203032031320323203332034320353203632037320383203932040320413204232043320443204532046320473204832049320503205132052320533205432055320563205732058320593206032061320623206332064320653206632067320683206932070320713207232073320743207532076320773207832079320803208132082320833208432085320863208732088320893209032091320923209332094320953209632097320983209932100321013210232103321043210532106321073210832109321103211132112321133211432115321163211732118321193212032121321223212332124321253212632127321283212932130321313213232133321343213532136321373213832139321403214132142321433214432145321463214732148321493215032151321523215332154321553215632157321583215932160321613216232163321643216532166321673216832169321703217132172321733217432175321763217732178321793218032181321823218332184321853218632187321883218932190321913219232193321943219532196321973219832199322003220132202322033220432205322063220732208322093221032211322123221332214322153221632217322183221932220322213222232223322243222532226322273222832229322303223132232322333223432235322363223732238322393224032241322423224332244322453224632247322483224932250322513225232253322543225532256322573225832259322603226132262322633226432265322663226732268322693227032271322723227332274322753227632277322783227932280322813228232283322843228532286322873228832289322903229132292322933229432295322963229732298322993230032301323023230332304323053230632307323083230932310323113231232313323143231532316323173231832319323203232132322323233232432325323263232732328323293233032331323323233332334323353233632337323383233932340323413234232343323443234532346323473234832349323503235132352323533235432355323563235732358323593236032361323623236332364323653236632367323683236932370323713237232373323743237532376323773237832379323803238132382323833238432385323863238732388323893239032391323923239332394323953239632397323983239932400324013240232403324043240532406324073240832409324103241132412324133241432415324163241732418324193242032421324223242332424324253242632427324283242932430324313243232433324343243532436324373243832439324403244132442324433244432445324463244732448324493245032451324523245332454324553245632457324583245932460324613246232463324643246532466324673246832469324703247132472324733247432475324763247732478324793248032481324823248332484324853248632487324883248932490324913249232493324943249532496324973249832499325003250132502325033250432505325063250732508325093251032511325123251332514325153251632517325183251932520325213252232523325243252532526325273252832529325303253132532325333253432535325363253732538325393254032541325423254332544325453254632547325483254932550325513255232553325543255532556325573255832559325603256132562325633256432565325663256732568325693257032571325723257332574325753257632577325783257932580325813258232583325843258532586325873258832589325903259132592325933259432595325963259732598325993260032601326023260332604326053260632607326083260932610326113261232613326143261532616326173261832619326203262132622326233262432625326263262732628326293263032631326323263332634326353263632637326383263932640326413264232643326443264532646326473264832649326503265132652326533265432655326563265732658326593266032661326623266332664326653266632667326683266932670326713267232673326743267532676326773267832679326803268132682326833268432685326863268732688326893269032691326923269332694326953269632697326983269932700327013270232703327043270532706327073270832709327103271132712327133271432715327163271732718327193272032721327223272332724327253272632727327283272932730327313273232733327343273532736327373273832739327403274132742327433274432745327463274732748327493275032751327523275332754327553275632757327583275932760327613276232763327643276532766327673276832769327703277132772327733277432775327763277732778327793278032781327823278332784327853278632787327883278932790327913279232793327943279532796327973279832799328003280132802328033280432805328063280732808328093281032811328123281332814328153281632817328183281932820328213282232823328243282532826328273282832829328303283132832328333283432835328363283732838328393284032841328423284332844328453284632847328483284932850328513285232853328543285532856328573285832859328603286132862328633286432865328663286732868328693287032871328723287332874328753287632877328783287932880328813288232883328843288532886328873288832889328903289132892328933289432895328963289732898328993290032901329023290332904329053290632907329083290932910329113291232913329143291532916329173291832919329203292132922329233292432925329263292732928329293293032931329323293332934329353293632937329383293932940329413294232943329443294532946329473294832949329503295132952329533295432955329563295732958329593296032961329623296332964329653296632967329683296932970329713297232973329743297532976329773297832979329803298132982329833298432985329863298732988329893299032991329923299332994329953299632997329983299933000330013300233003330043300533006330073300833009330103301133012330133301433015330163301733018330193302033021330223302333024330253302633027330283302933030330313303233033330343303533036330373303833039330403304133042330433304433045330463304733048330493305033051330523305333054330553305633057330583305933060330613306233063330643306533066330673306833069330703307133072330733307433075330763307733078330793308033081330823308333084330853308633087330883308933090330913309233093330943309533096330973309833099331003310133102331033310433105331063310733108331093311033111331123311333114331153311633117331183311933120331213312233123331243312533126331273312833129331303313133132331333313433135331363313733138331393314033141331423314333144331453314633147331483314933150331513315233153331543315533156331573315833159331603316133162331633316433165331663316733168331693317033171331723317333174331753317633177331783317933180331813318233183331843318533186331873318833189331903319133192331933319433195331963319733198331993320033201332023320333204332053320633207332083320933210332113321233213332143321533216332173321833219332203322133222332233322433225332263322733228332293323033231332323323333234332353323633237332383323933240332413324233243332443324533246332473324833249332503325133252332533325433255332563325733258332593326033261332623326333264332653326633267332683326933270332713327233273332743327533276332773327833279332803328133282332833328433285332863328733288332893329033291332923329333294332953329633297332983329933300333013330233303333043330533306333073330833309333103331133312333133331433315333163331733318333193332033321333223332333324333253332633327333283332933330333313333233333333343333533336333373333833339333403334133342333433334433345333463334733348333493335033351333523335333354333553335633357333583335933360333613336233363333643336533366333673336833369333703337133372333733337433375333763337733378333793338033381333823338333384333853338633387333883338933390333913339233393333943339533396333973339833399334003340133402334033340433405334063340733408334093341033411334123341333414334153341633417334183341933420334213342233423334243342533426334273342833429334303343133432334333343433435334363343733438334393344033441334423344333444334453344633447334483344933450334513345233453334543345533456334573345833459334603346133462334633346433465334663346733468334693347033471334723347333474334753347633477334783347933480334813348233483334843348533486334873348833489334903349133492334933349433495334963349733498334993350033501335023350333504335053350633507335083350933510335113351233513335143351533516335173351833519335203352133522335233352433525335263352733528335293353033531335323353333534335353353633537335383353933540335413354233543335443354533546335473354833549335503355133552335533355433555335563355733558335593356033561335623356333564335653356633567335683356933570335713357233573335743357533576335773357833579335803358133582335833358433585335863358733588335893359033591335923359333594335953359633597335983359933600336013360233603336043360533606336073360833609336103361133612336133361433615336163361733618336193362033621336223362333624336253362633627336283362933630336313363233633336343363533636336373363833639336403364133642336433364433645336463364733648336493365033651336523365333654336553365633657336583365933660336613366233663336643366533666336673366833669336703367133672336733367433675336763367733678336793368033681336823368333684336853368633687336883368933690336913369233693336943369533696336973369833699337003370133702337033370433705337063370733708337093371033711337123371333714337153371633717337183371933720337213372233723337243372533726337273372833729337303373133732337333373433735337363373733738337393374033741337423374333744337453374633747337483374933750337513375233753337543375533756337573375833759337603376133762337633376433765337663376733768337693377033771337723377333774337753377633777337783377933780337813378233783337843378533786337873378833789337903379133792337933379433795337963379733798337993380033801338023380333804338053380633807338083380933810338113381233813338143381533816338173381833819338203382133822338233382433825338263382733828338293383033831338323383333834338353383633837338383383933840338413384233843338443384533846338473384833849338503385133852338533385433855338563385733858338593386033861338623386333864338653386633867338683386933870338713387233873338743387533876338773387833879338803388133882338833388433885338863388733888338893389033891338923389333894338953389633897338983389933900339013390233903339043390533906339073390833909339103391133912339133391433915339163391733918339193392033921339223392333924339253392633927339283392933930339313393233933339343393533936339373393833939339403394133942339433394433945339463394733948339493395033951339523395333954339553395633957339583395933960339613396233963339643396533966339673396833969339703397133972339733397433975339763397733978339793398033981339823398333984339853398633987339883398933990339913399233993339943399533996339973399833999340003400134002340033400434005340063400734008340093401034011340123401334014340153401634017340183401934020340213402234023340243402534026340273402834029340303403134032340333403434035340363403734038340393404034041340423404334044340453404634047340483404934050340513405234053340543405534056340573405834059340603406134062340633406434065340663406734068340693407034071340723407334074340753407634077340783407934080340813408234083340843408534086340873408834089340903409134092340933409434095340963409734098340993410034101341023410334104341053410634107341083410934110341113411234113341143411534116341173411834119341203412134122341233412434125341263412734128341293413034131341323413334134341353413634137341383413934140341413414234143341443414534146341473414834149341503415134152341533415434155341563415734158341593416034161341623416334164341653416634167341683416934170341713417234173341743417534176341773417834179341803418134182341833418434185341863418734188341893419034191341923419334194341953419634197341983419934200342013420234203342043420534206342073420834209342103421134212342133421434215342163421734218342193422034221342223422334224342253422634227342283422934230342313423234233342343423534236342373423834239342403424134242342433424434245342463424734248342493425034251342523425334254342553425634257342583425934260342613426234263342643426534266342673426834269342703427134272342733427434275342763427734278342793428034281342823428334284342853428634287342883428934290342913429234293342943429534296342973429834299343003430134302343033430434305343063430734308343093431034311343123431334314343153431634317343183431934320343213432234323343243432534326343273432834329343303433134332343333433434335343363433734338343393434034341343423434334344343453434634347343483434934350343513435234353343543435534356343573435834359343603436134362343633436434365343663436734368343693437034371343723437334374343753437634377343783437934380343813438234383343843438534386343873438834389343903439134392343933439434395343963439734398343993440034401344023440334404344053440634407344083440934410344113441234413344143441534416344173441834419344203442134422344233442434425344263442734428344293443034431344323443334434344353443634437344383443934440344413444234443344443444534446344473444834449344503445134452344533445434455344563445734458344593446034461344623446334464344653446634467344683446934470344713447234473344743447534476344773447834479344803448134482344833448434485344863448734488344893449034491344923449334494344953449634497344983449934500345013450234503345043450534506345073450834509345103451134512345133451434515345163451734518345193452034521345223452334524345253452634527345283452934530345313453234533345343453534536345373453834539345403454134542345433454434545345463454734548345493455034551345523455334554345553455634557345583455934560345613456234563345643456534566345673456834569345703457134572345733457434575345763457734578345793458034581345823458334584345853458634587345883458934590345913459234593345943459534596345973459834599346003460134602346033460434605346063460734608346093461034611346123461334614346153461634617346183461934620346213462234623346243462534626346273462834629346303463134632346333463434635346363463734638346393464034641346423464334644346453464634647346483464934650346513465234653346543465534656346573465834659346603466134662346633466434665346663466734668346693467034671346723467334674346753467634677346783467934680346813468234683346843468534686346873468834689346903469134692346933469434695346963469734698346993470034701347023470334704347053470634707347083470934710347113471234713347143471534716347173471834719347203472134722347233472434725347263472734728347293473034731347323473334734347353473634737347383473934740347413474234743347443474534746347473474834749347503475134752347533475434755347563475734758347593476034761347623476334764347653476634767347683476934770347713477234773347743477534776347773477834779347803478134782347833478434785347863478734788347893479034791347923479334794347953479634797347983479934800348013480234803348043480534806348073480834809348103481134812348133481434815348163481734818348193482034821348223482334824348253482634827348283482934830348313483234833348343483534836348373483834839348403484134842348433484434845348463484734848348493485034851348523485334854348553485634857348583485934860348613486234863348643486534866348673486834869348703487134872348733487434875348763487734878348793488034881348823488334884348853488634887348883488934890348913489234893348943489534896348973489834899349003490134902349033490434905349063490734908349093491034911349123491334914349153491634917349183491934920349213492234923349243492534926349273492834929349303493134932349333493434935349363493734938349393494034941349423494334944349453494634947349483494934950349513495234953349543495534956349573495834959349603496134962349633496434965349663496734968349693497034971349723497334974349753497634977349783497934980349813498234983349843498534986349873498834989349903499134992349933499434995349963499734998349993500035001350023500335004350053500635007350083500935010350113501235013350143501535016350173501835019350203502135022350233502435025350263502735028350293503035031350323503335034350353503635037350383503935040350413504235043350443504535046350473504835049350503505135052350533505435055350563505735058350593506035061350623506335064350653506635067350683506935070350713507235073350743507535076350773507835079350803508135082350833508435085350863508735088350893509035091350923509335094350953509635097350983509935100351013510235103351043510535106351073510835109351103511135112351133511435115351163511735118351193512035121351223512335124351253512635127351283512935130351313513235133351343513535136351373513835139351403514135142351433514435145351463514735148351493515035151351523515335154351553515635157351583515935160351613516235163351643516535166351673516835169351703517135172351733517435175351763517735178351793518035181351823518335184351853518635187351883518935190351913519235193351943519535196351973519835199352003520135202352033520435205352063520735208352093521035211352123521335214352153521635217352183521935220352213522235223352243522535226352273522835229352303523135232352333523435235352363523735238352393524035241352423524335244352453524635247352483524935250352513525235253352543525535256352573525835259352603526135262352633526435265352663526735268352693527035271352723527335274352753527635277352783527935280352813528235283352843528535286352873528835289352903529135292352933529435295352963529735298352993530035301353023530335304353053530635307353083530935310353113531235313353143531535316353173531835319353203532135322353233532435325353263532735328353293533035331353323533335334353353533635337353383533935340353413534235343353443534535346353473534835349353503535135352353533535435355353563535735358353593536035361353623536335364353653536635367353683536935370353713537235373353743537535376353773537835379353803538135382353833538435385353863538735388353893539035391353923539335394353953539635397353983539935400354013540235403354043540535406354073540835409354103541135412354133541435415354163541735418354193542035421354223542335424354253542635427354283542935430354313543235433354343543535436354373543835439354403544135442354433544435445354463544735448354493545035451354523545335454354553545635457354583545935460354613546235463354643546535466354673546835469354703547135472354733547435475354763547735478354793548035481354823548335484354853548635487354883548935490354913549235493354943549535496354973549835499355003550135502355033550435505355063550735508355093551035511355123551335514355153551635517355183551935520355213552235523355243552535526355273552835529355303553135532355333553435535355363553735538355393554035541355423554335544355453554635547355483554935550355513555235553355543555535556355573555835559355603556135562355633556435565355663556735568355693557035571355723557335574355753557635577355783557935580355813558235583355843558535586355873558835589355903559135592355933559435595355963559735598355993560035601356023560335604356053560635607356083560935610356113561235613356143561535616356173561835619356203562135622356233562435625356263562735628356293563035631356323563335634356353563635637356383563935640356413564235643356443564535646356473564835649356503565135652356533565435655356563565735658356593566035661356623566335664356653566635667356683566935670356713567235673356743567535676356773567835679356803568135682356833568435685356863568735688356893569035691356923569335694356953569635697356983569935700357013570235703357043570535706357073570835709357103571135712357133571435715357163571735718357193572035721357223572335724357253572635727357283572935730357313573235733357343573535736357373573835739357403574135742357433574435745357463574735748357493575035751357523575335754357553575635757357583575935760357613576235763357643576535766357673576835769357703577135772357733577435775357763577735778357793578035781357823578335784357853578635787357883578935790357913579235793357943579535796357973579835799358003580135802358033580435805358063580735808358093581035811358123581335814358153581635817358183581935820358213582235823358243582535826358273582835829358303583135832358333583435835358363583735838358393584035841358423584335844358453584635847358483584935850358513585235853358543585535856358573585835859358603586135862358633586435865358663586735868358693587035871358723587335874358753587635877358783587935880358813588235883358843588535886358873588835889358903589135892358933589435895358963589735898358993590035901359023590335904359053590635907359083590935910359113591235913359143591535916359173591835919359203592135922359233592435925359263592735928359293593035931359323593335934359353593635937359383593935940359413594235943359443594535946359473594835949359503595135952359533595435955359563595735958359593596035961359623596335964359653596635967359683596935970359713597235973359743597535976359773597835979359803598135982359833598435985359863598735988359893599035991359923599335994359953599635997359983599936000360013600236003360043600536006360073600836009360103601136012360133601436015360163601736018360193602036021360223602336024360253602636027360283602936030360313603236033360343603536036360373603836039360403604136042360433604436045360463604736048360493605036051360523605336054360553605636057360583605936060360613606236063360643606536066360673606836069360703607136072360733607436075360763607736078360793608036081360823608336084360853608636087360883608936090360913609236093360943609536096360973609836099361003610136102361033610436105361063610736108361093611036111361123611336114361153611636117361183611936120361213612236123361243612536126361273612836129361303613136132361333613436135361363613736138361393614036141361423614336144361453614636147361483614936150361513615236153361543615536156361573615836159361603616136162361633616436165361663616736168361693617036171361723617336174361753617636177361783617936180361813618236183361843618536186361873618836189361903619136192361933619436195361963619736198361993620036201362023620336204362053620636207362083620936210362113621236213362143621536216362173621836219362203622136222362233622436225362263622736228362293623036231362323623336234362353623636237362383623936240362413624236243362443624536246362473624836249362503625136252362533625436255362563625736258362593626036261362623626336264362653626636267362683626936270362713627236273362743627536276362773627836279362803628136282362833628436285362863628736288362893629036291362923629336294362953629636297362983629936300363013630236303363043630536306363073630836309363103631136312363133631436315363163631736318363193632036321363223632336324363253632636327363283632936330363313633236333363343633536336363373633836339363403634136342363433634436345363463634736348363493635036351363523635336354363553635636357363583635936360363613636236363363643636536366363673636836369363703637136372363733637436375363763637736378363793638036381363823638336384363853638636387363883638936390363913639236393363943639536396363973639836399364003640136402364033640436405364063640736408364093641036411364123641336414364153641636417364183641936420364213642236423364243642536426364273642836429364303643136432364333643436435364363643736438364393644036441364423644336444364453644636447364483644936450364513645236453364543645536456364573645836459364603646136462364633646436465364663646736468364693647036471364723647336474364753647636477364783647936480364813648236483364843648536486364873648836489364903649136492364933649436495364963649736498364993650036501365023650336504365053650636507365083650936510365113651236513365143651536516365173651836519365203652136522365233652436525365263652736528365293653036531365323653336534365353653636537365383653936540365413654236543365443654536546365473654836549365503655136552365533655436555365563655736558365593656036561365623656336564365653656636567365683656936570365713657236573365743657536576365773657836579365803658136582365833658436585365863658736588365893659036591365923659336594365953659636597365983659936600366013660236603366043660536606366073660836609366103661136612366133661436615366163661736618366193662036621366223662336624366253662636627366283662936630366313663236633366343663536636366373663836639366403664136642366433664436645366463664736648366493665036651366523665336654366553665636657366583665936660366613666236663366643666536666366673666836669366703667136672366733667436675366763667736678366793668036681366823668336684366853668636687366883668936690366913669236693366943669536696366973669836699367003670136702367033670436705367063670736708367093671036711367123671336714367153671636717367183671936720367213672236723367243672536726367273672836729367303673136732367333673436735367363673736738367393674036741367423674336744367453674636747367483674936750367513675236753367543675536756367573675836759367603676136762367633676436765367663676736768367693677036771367723677336774367753677636777367783677936780367813678236783367843678536786367873678836789367903679136792367933679436795367963679736798367993680036801368023680336804368053680636807368083680936810368113681236813368143681536816368173681836819368203682136822368233682436825368263682736828368293683036831368323683336834368353683636837368383683936840368413684236843368443684536846368473684836849368503685136852368533685436855368563685736858368593686036861368623686336864368653686636867368683686936870368713687236873368743687536876368773687836879368803688136882368833688436885368863688736888368893689036891368923689336894368953689636897368983689936900369013690236903369043690536906369073690836909369103691136912369133691436915369163691736918369193692036921369223692336924369253692636927369283692936930369313693236933369343693536936369373693836939369403694136942369433694436945369463694736948369493695036951369523695336954369553695636957369583695936960369613696236963369643696536966369673696836969369703697136972369733697436975369763697736978369793698036981369823698336984369853698636987369883698936990369913699236993369943699536996369973699836999370003700137002370033700437005370063700737008370093701037011370123701337014370153701637017370183701937020370213702237023370243702537026370273702837029370303703137032370333703437035370363703737038370393704037041370423704337044370453704637047370483704937050370513705237053370543705537056370573705837059370603706137062370633706437065370663706737068370693707037071370723707337074370753707637077370783707937080370813708237083370843708537086370873708837089370903709137092370933709437095370963709737098370993710037101371023710337104371053710637107371083710937110371113711237113371143711537116371173711837119371203712137122371233712437125371263712737128371293713037131371323713337134371353713637137371383713937140371413714237143371443714537146371473714837149371503715137152371533715437155371563715737158371593716037161371623716337164371653716637167371683716937170371713717237173371743717537176371773717837179371803718137182371833718437185371863718737188371893719037191371923719337194371953719637197371983719937200372013720237203372043720537206372073720837209372103721137212372133721437215372163721737218372193722037221372223722337224372253722637227372283722937230372313723237233372343723537236372373723837239372403724137242372433724437245372463724737248372493725037251372523725337254372553725637257372583725937260372613726237263372643726537266372673726837269372703727137272372733727437275372763727737278372793728037281372823728337284372853728637287372883728937290372913729237293372943729537296372973729837299373003730137302373033730437305373063730737308373093731037311373123731337314373153731637317373183731937320373213732237323373243732537326373273732837329373303733137332373333733437335373363733737338373393734037341373423734337344373453734637347373483734937350373513735237353373543735537356373573735837359373603736137362373633736437365373663736737368373693737037371373723737337374373753737637377373783737937380373813738237383373843738537386373873738837389373903739137392373933739437395373963739737398373993740037401374023740337404374053740637407374083740937410374113741237413374143741537416374173741837419374203742137422374233742437425374263742737428374293743037431374323743337434374353743637437374383743937440374413744237443374443744537446374473744837449374503745137452374533745437455374563745737458374593746037461374623746337464374653746637467374683746937470374713747237473374743747537476374773747837479374803748137482374833748437485374863748737488374893749037491374923749337494374953749637497374983749937500375013750237503375043750537506375073750837509375103751137512375133751437515375163751737518375193752037521375223752337524375253752637527375283752937530375313753237533375343753537536375373753837539375403754137542375433754437545375463754737548375493755037551375523755337554375553755637557375583755937560375613756237563375643756537566375673756837569375703757137572375733757437575375763757737578375793758037581375823758337584375853758637587375883758937590375913759237593375943759537596375973759837599376003760137602376033760437605376063760737608376093761037611376123761337614376153761637617376183761937620376213762237623376243762537626376273762837629376303763137632376333763437635376363763737638376393764037641376423764337644376453764637647376483764937650376513765237653376543765537656376573765837659376603766137662376633766437665376663766737668376693767037671376723767337674376753767637677376783767937680376813768237683376843768537686376873768837689376903769137692376933769437695376963769737698376993770037701377023770337704377053770637707377083770937710377113771237713377143771537716377173771837719377203772137722377233772437725377263772737728377293773037731377323773337734377353773637737377383773937740377413774237743377443774537746377473774837749377503775137752377533775437755377563775737758377593776037761377623776337764377653776637767377683776937770377713777237773377743777537776377773777837779377803778137782377833778437785377863778737788377893779037791377923779337794377953779637797377983779937800378013780237803378043780537806378073780837809378103781137812378133781437815378163781737818378193782037821378223782337824378253782637827378283782937830378313783237833378343783537836378373783837839378403784137842378433784437845378463784737848378493785037851378523785337854378553785637857378583785937860378613786237863378643786537866378673786837869378703787137872378733787437875378763787737878378793788037881378823788337884378853788637887378883788937890378913789237893378943789537896378973789837899379003790137902379033790437905379063790737908379093791037911379123791337914379153791637917379183791937920379213792237923379243792537926379273792837929379303793137932379333793437935379363793737938379393794037941379423794337944379453794637947379483794937950379513795237953379543795537956379573795837959379603796137962379633796437965379663796737968379693797037971379723797337974379753797637977379783797937980379813798237983379843798537986379873798837989379903799137992379933799437995379963799737998379993800038001380023800338004380053800638007380083800938010380113801238013380143801538016380173801838019380203802138022380233802438025380263802738028380293803038031380323803338034380353803638037380383803938040380413804238043380443804538046380473804838049380503805138052380533805438055380563805738058380593806038061380623806338064380653806638067380683806938070380713807238073380743807538076380773807838079380803808138082380833808438085380863808738088380893809038091380923809338094380953809638097380983809938100381013810238103381043810538106381073810838109381103811138112381133811438115381163811738118381193812038121381223812338124381253812638127381283812938130381313813238133381343813538136381373813838139381403814138142381433814438145381463814738148381493815038151381523815338154381553815638157381583815938160381613816238163381643816538166381673816838169381703817138172381733817438175381763817738178381793818038181381823818338184381853818638187381883818938190381913819238193381943819538196381973819838199382003820138202382033820438205382063820738208382093821038211382123821338214382153821638217382183821938220382213822238223382243822538226382273822838229382303823138232382333823438235382363823738238382393824038241382423824338244382453824638247382483824938250382513825238253382543825538256382573825838259382603826138262382633826438265382663826738268382693827038271382723827338274382753827638277382783827938280382813828238283382843828538286382873828838289382903829138292382933829438295382963829738298382993830038301383023830338304383053830638307383083830938310383113831238313383143831538316383173831838319383203832138322383233832438325383263832738328383293833038331383323833338334383353833638337383383833938340383413834238343383443834538346383473834838349383503835138352383533835438355383563835738358383593836038361383623836338364383653836638367383683836938370383713837238373383743837538376383773837838379383803838138382383833838438385383863838738388383893839038391383923839338394383953839638397383983839938400384013840238403384043840538406384073840838409384103841138412384133841438415384163841738418384193842038421384223842338424384253842638427384283842938430384313843238433384343843538436384373843838439384403844138442384433844438445384463844738448384493845038451384523845338454384553845638457384583845938460384613846238463384643846538466384673846838469384703847138472384733847438475384763847738478384793848038481384823848338484384853848638487384883848938490384913849238493384943849538496384973849838499385003850138502385033850438505385063850738508385093851038511385123851338514385153851638517385183851938520385213852238523385243852538526385273852838529385303853138532385333853438535385363853738538385393854038541385423854338544385453854638547385483854938550385513855238553385543855538556385573855838559385603856138562385633856438565385663856738568385693857038571385723857338574385753857638577385783857938580385813858238583385843858538586385873858838589385903859138592385933859438595385963859738598385993860038601386023860338604386053860638607386083860938610386113861238613386143861538616386173861838619386203862138622386233862438625386263862738628386293863038631386323863338634386353863638637386383863938640386413864238643386443864538646386473864838649386503865138652386533865438655386563865738658386593866038661386623866338664386653866638667386683866938670386713867238673386743867538676386773867838679386803868138682386833868438685386863868738688386893869038691386923869338694386953869638697386983869938700387013870238703387043870538706387073870838709387103871138712387133871438715387163871738718387193872038721387223872338724387253872638727387283872938730387313873238733387343873538736387373873838739387403874138742387433874438745387463874738748387493875038751387523875338754387553875638757387583875938760387613876238763387643876538766387673876838769387703877138772387733877438775387763877738778387793878038781387823878338784387853878638787387883878938790387913879238793387943879538796387973879838799388003880138802388033880438805388063880738808388093881038811388123881338814388153881638817388183881938820388213882238823388243882538826388273882838829388303883138832388333883438835388363883738838388393884038841388423884338844388453884638847388483884938850388513885238853388543885538856388573885838859388603886138862388633886438865388663886738868388693887038871388723887338874388753887638877388783887938880388813888238883388843888538886388873888838889388903889138892388933889438895388963889738898388993890038901389023890338904389053890638907389083890938910389113891238913389143891538916389173891838919389203892138922389233892438925389263892738928389293893038931389323893338934389353893638937389383893938940389413894238943389443894538946389473894838949389503895138952389533895438955389563895738958389593896038961389623896338964389653896638967389683896938970389713897238973389743897538976389773897838979389803898138982389833898438985389863898738988389893899038991389923899338994389953899638997389983899939000390013900239003390043900539006390073900839009390103901139012390133901439015390163901739018390193902039021390223902339024390253902639027390283902939030390313903239033390343903539036390373903839039390403904139042390433904439045390463904739048390493905039051390523905339054390553905639057390583905939060390613906239063390643906539066390673906839069390703907139072390733907439075390763907739078390793908039081390823908339084390853908639087390883908939090390913909239093390943909539096390973909839099391003910139102391033910439105391063910739108391093911039111391123911339114391153911639117391183911939120391213912239123391243912539126391273912839129391303913139132391333913439135391363913739138391393914039141391423914339144391453914639147391483914939150391513915239153391543915539156391573915839159391603916139162391633916439165391663916739168391693917039171391723917339174391753917639177391783917939180391813918239183391843918539186391873918839189391903919139192391933919439195391963919739198391993920039201392023920339204392053920639207392083920939210392113921239213392143921539216392173921839219392203922139222392233922439225392263922739228392293923039231392323923339234392353923639237392383923939240392413924239243392443924539246392473924839249392503925139252392533925439255392563925739258392593926039261392623926339264392653926639267392683926939270392713927239273392743927539276392773927839279392803928139282392833928439285392863928739288392893929039291392923929339294392953929639297392983929939300393013930239303393043930539306393073930839309393103931139312393133931439315393163931739318393193932039321393223932339324393253932639327393283932939330393313933239333393343933539336393373933839339393403934139342393433934439345393463934739348393493935039351393523935339354393553935639357393583935939360393613936239363393643936539366393673936839369393703937139372393733937439375393763937739378393793938039381393823938339384393853938639387393883938939390393913939239393393943939539396393973939839399394003940139402394033940439405394063940739408394093941039411394123941339414394153941639417394183941939420394213942239423394243942539426394273942839429394303943139432394333943439435394363943739438394393944039441394423944339444394453944639447394483944939450394513945239453394543945539456394573945839459394603946139462394633946439465394663946739468394693947039471394723947339474394753947639477394783947939480394813948239483394843948539486394873948839489394903949139492394933949439495394963949739498394993950039501395023950339504395053950639507395083950939510395113951239513395143951539516395173951839519395203952139522395233952439525395263952739528395293953039531395323953339534395353953639537395383953939540395413954239543395443954539546395473954839549395503955139552395533955439555395563955739558395593956039561395623956339564395653956639567395683956939570395713957239573395743957539576395773957839579395803958139582395833958439585395863958739588395893959039591395923959339594395953959639597395983959939600396013960239603396043960539606396073960839609396103961139612396133961439615396163961739618396193962039621396223962339624396253962639627396283962939630396313963239633396343963539636396373963839639396403964139642396433964439645396463964739648396493965039651396523965339654396553965639657396583965939660396613966239663396643966539666396673966839669396703967139672396733967439675396763967739678396793968039681396823968339684396853968639687396883968939690396913969239693396943969539696396973969839699397003970139702397033970439705397063970739708397093971039711397123971339714397153971639717397183971939720397213972239723397243972539726397273972839729397303973139732397333973439735397363973739738397393974039741397423974339744397453974639747397483974939750397513975239753397543975539756397573975839759397603976139762397633976439765397663976739768397693977039771397723977339774397753977639777397783977939780397813978239783397843978539786397873978839789397903979139792397933979439795397963979739798397993980039801398023980339804398053980639807398083980939810398113981239813398143981539816398173981839819398203982139822398233982439825398263982739828398293983039831398323983339834398353983639837398383983939840398413984239843398443984539846398473984839849398503985139852398533985439855398563985739858398593986039861398623986339864398653986639867398683986939870398713987239873398743987539876398773987839879398803988139882398833988439885398863988739888398893989039891398923989339894398953989639897398983989939900399013990239903399043990539906399073990839909399103991139912399133991439915399163991739918399193992039921399223992339924399253992639927399283992939930399313993239933399343993539936399373993839939399403994139942399433994439945399463994739948399493995039951399523995339954399553995639957399583995939960399613996239963399643996539966399673996839969399703997139972399733997439975399763997739978399793998039981399823998339984399853998639987399883998939990399913999239993399943999539996399973999839999400004000140002400034000440005400064000740008400094001040011400124001340014400154001640017400184001940020400214002240023400244002540026400274002840029400304003140032400334003440035400364003740038400394004040041400424004340044400454004640047400484004940050400514005240053400544005540056400574005840059400604006140062400634006440065400664006740068400694007040071400724007340074400754007640077400784007940080400814008240083400844008540086400874008840089400904009140092400934009440095400964009740098400994010040101401024010340104401054010640107401084010940110401114011240113401144011540116401174011840119401204012140122401234012440125401264012740128401294013040131401324013340134401354013640137401384013940140401414014240143401444014540146401474014840149401504015140152401534015440155401564015740158401594016040161401624016340164401654016640167401684016940170401714017240173401744017540176401774017840179401804018140182401834018440185401864018740188401894019040191401924019340194401954019640197401984019940200402014020240203402044020540206402074020840209402104021140212402134021440215402164021740218402194022040221402224022340224402254022640227402284022940230402314023240233402344023540236402374023840239402404024140242402434024440245402464024740248402494025040251402524025340254402554025640257402584025940260402614026240263402644026540266402674026840269402704027140272402734027440275402764027740278402794028040281402824028340284402854028640287402884028940290402914029240293402944029540296402974029840299403004030140302403034030440305403064030740308403094031040311403124031340314403154031640317403184031940320403214032240323403244032540326403274032840329403304033140332403334033440335403364033740338403394034040341403424034340344403454034640347403484034940350403514035240353403544035540356403574035840359403604036140362403634036440365403664036740368403694037040371403724037340374403754037640377403784037940380403814038240383403844038540386403874038840389403904039140392403934039440395403964039740398403994040040401404024040340404404054040640407404084040940410404114041240413404144041540416404174041840419404204042140422404234042440425404264042740428404294043040431404324043340434404354043640437404384043940440404414044240443404444044540446404474044840449404504045140452404534045440455404564045740458404594046040461404624046340464404654046640467404684046940470404714047240473404744047540476404774047840479404804048140482404834048440485404864048740488404894049040491404924049340494404954049640497404984049940500405014050240503405044050540506405074050840509405104051140512405134051440515405164051740518405194052040521405224052340524405254052640527405284052940530405314053240533405344053540536405374053840539405404054140542405434054440545405464054740548405494055040551405524055340554405554055640557405584055940560405614056240563405644056540566405674056840569405704057140572405734057440575405764057740578405794058040581405824058340584405854058640587405884058940590405914059240593405944059540596405974059840599406004060140602406034060440605406064060740608406094061040611406124061340614406154061640617406184061940620406214062240623406244062540626406274062840629406304063140632406334063440635406364063740638406394064040641406424064340644406454064640647406484064940650406514065240653406544065540656406574065840659406604066140662406634066440665406664066740668406694067040671406724067340674406754067640677406784067940680406814068240683406844068540686406874068840689406904069140692406934069440695406964069740698406994070040701407024070340704407054070640707407084070940710407114071240713407144071540716407174071840719407204072140722407234072440725407264072740728407294073040731407324073340734407354073640737407384073940740407414074240743407444074540746407474074840749407504075140752407534075440755407564075740758407594076040761407624076340764407654076640767407684076940770407714077240773407744077540776407774077840779407804078140782407834078440785407864078740788407894079040791407924079340794407954079640797407984079940800408014080240803408044080540806408074080840809408104081140812408134081440815408164081740818408194082040821408224082340824408254082640827408284082940830408314083240833408344083540836408374083840839408404084140842408434084440845408464084740848408494085040851408524085340854408554085640857408584085940860408614086240863408644086540866408674086840869408704087140872408734087440875408764087740878408794088040881408824088340884408854088640887408884088940890408914089240893408944089540896408974089840899409004090140902409034090440905409064090740908409094091040911409124091340914409154091640917409184091940920409214092240923409244092540926409274092840929409304093140932409334093440935409364093740938409394094040941409424094340944409454094640947409484094940950409514095240953409544095540956409574095840959409604096140962409634096440965409664096740968409694097040971409724097340974409754097640977409784097940980409814098240983409844098540986409874098840989409904099140992409934099440995409964099740998409994100041001410024100341004410054100641007410084100941010410114101241013410144101541016410174101841019410204102141022410234102441025410264102741028410294103041031410324103341034410354103641037410384103941040410414104241043410444104541046410474104841049410504105141052410534105441055410564105741058410594106041061410624106341064410654106641067410684106941070410714107241073410744107541076410774107841079410804108141082410834108441085410864108741088410894109041091410924109341094410954109641097410984109941100411014110241103411044110541106411074110841109411104111141112411134111441115411164111741118411194112041121411224112341124411254112641127411284112941130411314113241133411344113541136411374113841139411404114141142411434114441145411464114741148411494115041151411524115341154411554115641157411584115941160411614116241163411644116541166411674116841169411704117141172411734117441175411764117741178411794118041181411824118341184411854118641187411884118941190411914119241193411944119541196411974119841199412004120141202412034120441205412064120741208412094121041211412124121341214412154121641217412184121941220412214122241223412244122541226412274122841229412304123141232412334123441235412364123741238412394124041241412424124341244412454124641247412484124941250412514125241253412544125541256412574125841259412604126141262412634126441265412664126741268412694127041271412724127341274412754127641277412784127941280412814128241283412844128541286412874128841289412904129141292412934129441295412964129741298412994130041301413024130341304413054130641307413084130941310413114131241313413144131541316413174131841319413204132141322413234132441325413264132741328413294133041331413324133341334413354133641337413384133941340413414134241343413444134541346413474134841349413504135141352413534135441355413564135741358413594136041361413624136341364413654136641367413684136941370413714137241373413744137541376413774137841379413804138141382413834138441385413864138741388413894139041391413924139341394413954139641397413984139941400414014140241403414044140541406414074140841409414104141141412414134141441415414164141741418414194142041421414224142341424414254142641427414284142941430414314143241433414344143541436414374143841439414404144141442414434144441445414464144741448414494145041451414524145341454414554145641457414584145941460414614146241463414644146541466414674146841469414704147141472414734147441475414764147741478414794148041481414824148341484414854148641487414884148941490414914149241493414944149541496414974149841499415004150141502415034150441505415064150741508415094151041511415124151341514415154151641517415184151941520415214152241523415244152541526415274152841529415304153141532415334153441535415364153741538415394154041541415424154341544415454154641547415484154941550415514155241553415544155541556415574155841559415604156141562415634156441565415664156741568415694157041571415724157341574415754157641577415784157941580415814158241583415844158541586415874158841589415904159141592415934159441595415964159741598415994160041601416024160341604416054160641607416084160941610416114161241613416144161541616416174161841619416204162141622416234162441625416264162741628416294163041631416324163341634416354163641637416384163941640416414164241643416444164541646416474164841649416504165141652416534165441655416564165741658416594166041661416624166341664416654166641667416684166941670416714167241673416744167541676416774167841679416804168141682416834168441685416864168741688416894169041691416924169341694416954169641697416984169941700417014170241703417044170541706417074170841709417104171141712417134171441715417164171741718417194172041721417224172341724417254172641727417284172941730417314173241733417344173541736417374173841739417404174141742417434174441745417464174741748417494175041751417524175341754417554175641757417584175941760417614176241763417644176541766417674176841769417704177141772417734177441775417764177741778417794178041781417824178341784417854178641787417884178941790417914179241793417944179541796417974179841799418004180141802418034180441805418064180741808418094181041811418124181341814418154181641817418184181941820418214182241823418244182541826418274182841829418304183141832418334183441835418364183741838418394184041841418424184341844418454184641847418484184941850418514185241853418544185541856418574185841859418604186141862418634186441865418664186741868418694187041871418724187341874418754187641877418784187941880418814188241883418844188541886418874188841889418904189141892418934189441895418964189741898418994190041901419024190341904419054190641907419084190941910419114191241913419144191541916419174191841919419204192141922419234192441925419264192741928419294193041931419324193341934419354193641937419384193941940419414194241943419444194541946419474194841949419504195141952419534195441955419564195741958419594196041961419624196341964419654196641967419684196941970419714197241973419744197541976419774197841979419804198141982419834198441985419864198741988419894199041991419924199341994419954199641997419984199942000420014200242003420044200542006420074200842009420104201142012420134201442015420164201742018420194202042021420224202342024420254202642027420284202942030420314203242033420344203542036420374203842039420404204142042420434204442045420464204742048420494205042051420524205342054420554205642057420584205942060420614206242063420644206542066420674206842069420704207142072420734207442075420764207742078420794208042081420824208342084420854208642087420884208942090420914209242093420944209542096420974209842099421004210142102421034210442105421064210742108421094211042111421124211342114421154211642117421184211942120421214212242123421244212542126421274212842129421304213142132421334213442135421364213742138421394214042141421424214342144421454214642147421484214942150421514215242153421544215542156421574215842159421604216142162421634216442165421664216742168421694217042171421724217342174421754217642177421784217942180421814218242183421844218542186421874218842189421904219142192421934219442195421964219742198421994220042201422024220342204422054220642207422084220942210422114221242213422144221542216422174221842219422204222142222422234222442225422264222742228422294223042231422324223342234422354223642237422384223942240422414224242243422444224542246422474224842249422504225142252422534225442255422564225742258422594226042261422624226342264422654226642267422684226942270422714227242273422744227542276422774227842279422804228142282422834228442285422864228742288422894229042291422924229342294422954229642297422984229942300423014230242303423044230542306423074230842309423104231142312423134231442315423164231742318423194232042321423224232342324423254232642327423284232942330423314233242333423344233542336423374233842339423404234142342423434234442345423464234742348423494235042351423524235342354423554235642357423584235942360423614236242363423644236542366423674236842369423704237142372423734237442375423764237742378423794238042381423824238342384423854238642387423884238942390423914239242393423944239542396423974239842399424004240142402424034240442405424064240742408424094241042411424124241342414424154241642417424184241942420424214242242423424244242542426424274242842429424304243142432424334243442435424364243742438424394244042441424424244342444424454244642447424484244942450424514245242453424544245542456424574245842459424604246142462424634246442465424664246742468424694247042471424724247342474424754247642477424784247942480424814248242483424844248542486424874248842489424904249142492424934249442495424964249742498424994250042501425024250342504425054250642507425084250942510425114251242513425144251542516425174251842519425204252142522425234252442525425264252742528425294253042531425324253342534425354253642537425384253942540425414254242543425444254542546425474254842549425504255142552425534255442555425564255742558425594256042561425624256342564425654256642567425684256942570425714257242573425744257542576425774257842579425804258142582425834258442585425864258742588425894259042591425924259342594425954259642597425984259942600426014260242603426044260542606426074260842609426104261142612426134261442615426164261742618426194262042621426224262342624426254262642627426284262942630426314263242633426344263542636426374263842639426404264142642426434264442645426464264742648426494265042651426524265342654426554265642657426584265942660426614266242663426644266542666426674266842669426704267142672426734267442675426764267742678426794268042681426824268342684426854268642687426884268942690426914269242693426944269542696426974269842699427004270142702427034270442705427064270742708427094271042711427124271342714427154271642717427184271942720427214272242723427244272542726427274272842729427304273142732427334273442735427364273742738427394274042741427424274342744427454274642747427484274942750427514275242753427544275542756427574275842759427604276142762427634276442765427664276742768427694277042771427724277342774427754277642777427784277942780427814278242783427844278542786427874278842789427904279142792427934279442795427964279742798427994280042801428024280342804428054280642807428084280942810428114281242813428144281542816428174281842819428204282142822428234282442825428264282742828428294283042831428324283342834428354283642837428384283942840428414284242843428444284542846428474284842849428504285142852428534285442855428564285742858428594286042861428624286342864428654286642867428684286942870428714287242873428744287542876428774287842879428804288142882428834288442885428864288742888428894289042891428924289342894428954289642897428984289942900429014290242903429044290542906429074290842909429104291142912429134291442915429164291742918429194292042921429224292342924429254292642927429284292942930429314293242933429344293542936429374293842939429404294142942429434294442945429464294742948429494295042951429524295342954429554295642957429584295942960429614296242963429644296542966429674296842969429704297142972429734297442975429764297742978429794298042981429824298342984429854298642987429884298942990429914299242993429944299542996429974299842999430004300143002430034300443005430064300743008430094301043011430124301343014430154301643017430184301943020430214302243023430244302543026430274302843029430304303143032430334303443035430364303743038430394304043041430424304343044430454304643047430484304943050430514305243053430544305543056430574305843059430604306143062430634306443065430664306743068430694307043071430724307343074430754307643077430784307943080430814308243083430844308543086430874308843089430904309143092430934309443095430964309743098430994310043101431024310343104431054310643107431084310943110431114311243113431144311543116431174311843119431204312143122431234312443125431264312743128431294313043131431324313343134431354313643137431384313943140431414314243143431444314543146431474314843149431504315143152431534315443155431564315743158431594316043161431624316343164431654316643167431684316943170431714317243173431744317543176431774317843179431804318143182431834318443185431864318743188431894319043191431924319343194431954319643197431984319943200432014320243203432044320543206432074320843209432104321143212432134321443215432164321743218432194322043221432224322343224432254322643227432284322943230432314323243233432344323543236432374323843239432404324143242432434324443245432464324743248432494325043251432524325343254432554325643257432584325943260432614326243263432644326543266432674326843269432704327143272432734327443275432764327743278432794328043281432824328343284432854328643287432884328943290432914329243293432944329543296432974329843299433004330143302433034330443305433064330743308433094331043311433124331343314433154331643317433184331943320433214332243323433244332543326433274332843329433304333143332433334333443335433364333743338433394334043341433424334343344433454334643347433484334943350433514335243353433544335543356433574335843359433604336143362433634336443365433664336743368433694337043371433724337343374433754337643377433784337943380433814338243383433844338543386433874338843389433904339143392433934339443395433964339743398433994340043401434024340343404434054340643407434084340943410434114341243413434144341543416434174341843419434204342143422434234342443425434264342743428434294343043431434324343343434434354343643437434384343943440434414344243443434444344543446434474344843449434504345143452434534345443455434564345743458434594346043461434624346343464434654346643467434684346943470434714347243473434744347543476434774347843479434804348143482434834348443485434864348743488434894349043491434924349343494434954349643497434984349943500435014350243503435044350543506435074350843509435104351143512435134351443515435164351743518435194352043521435224352343524435254352643527435284352943530435314353243533435344353543536435374353843539435404354143542435434354443545435464354743548435494355043551435524355343554435554355643557435584355943560435614356243563435644356543566435674356843569435704357143572435734357443575435764357743578435794358043581435824358343584435854358643587435884358943590435914359243593435944359543596435974359843599436004360143602436034360443605436064360743608436094361043611436124361343614436154361643617436184361943620436214362243623436244362543626436274362843629436304363143632436334363443635436364363743638436394364043641436424364343644436454364643647436484364943650436514365243653436544365543656436574365843659436604366143662436634366443665436664366743668436694367043671436724367343674436754367643677436784367943680436814368243683436844368543686436874368843689436904369143692436934369443695436964369743698436994370043701437024370343704437054370643707437084370943710437114371243713437144371543716437174371843719437204372143722437234372443725437264372743728437294373043731437324373343734437354373643737437384373943740437414374243743437444374543746437474374843749437504375143752437534375443755437564375743758437594376043761437624376343764437654376643767437684376943770437714377243773437744377543776437774377843779437804378143782437834378443785437864378743788437894379043791437924379343794437954379643797437984379943800438014380243803438044380543806438074380843809438104381143812438134381443815438164381743818438194382043821438224382343824438254382643827438284382943830438314383243833438344383543836438374383843839438404384143842438434384443845438464384743848438494385043851438524385343854438554385643857438584385943860438614386243863438644386543866438674386843869438704387143872438734387443875438764387743878438794388043881438824388343884438854388643887438884388943890438914389243893438944389543896438974389843899439004390143902439034390443905439064390743908439094391043911439124391343914439154391643917439184391943920439214392243923439244392543926439274392843929439304393143932439334393443935439364393743938439394394043941439424394343944439454394643947439484394943950439514395243953439544395543956439574395843959439604396143962439634396443965439664396743968439694397043971439724397343974439754397643977439784397943980439814398243983439844398543986439874398843989439904399143992439934399443995439964399743998439994400044001440024400344004440054400644007440084400944010440114401244013440144401544016440174401844019440204402144022440234402444025440264402744028440294403044031440324403344034440354403644037440384403944040440414404244043440444404544046440474404844049440504405144052440534405444055440564405744058440594406044061440624406344064440654406644067440684406944070440714407244073440744407544076440774407844079440804408144082440834408444085440864408744088440894409044091440924409344094440954409644097440984409944100441014410244103441044410544106441074410844109441104411144112441134411444115441164411744118441194412044121441224412344124441254412644127441284412944130441314413244133441344413544136441374413844139441404414144142441434414444145441464414744148441494415044151441524415344154441554415644157441584415944160441614416244163441644416544166441674416844169441704417144172441734417444175441764417744178441794418044181441824418344184441854418644187441884418944190441914419244193441944419544196441974419844199442004420144202442034420444205442064420744208442094421044211442124421344214442154421644217442184421944220442214422244223442244422544226442274422844229442304423144232442334423444235442364423744238442394424044241442424424344244442454424644247442484424944250442514425244253442544425544256442574425844259442604426144262442634426444265442664426744268442694427044271442724427344274442754427644277442784427944280442814428244283442844428544286442874428844289442904429144292442934429444295442964429744298442994430044301443024430344304443054430644307443084430944310443114431244313443144431544316443174431844319443204432144322443234432444325443264432744328443294433044331443324433344334443354433644337443384433944340443414434244343443444434544346443474434844349443504435144352443534435444355443564435744358443594436044361443624436344364443654436644367443684436944370443714437244373443744437544376443774437844379443804438144382443834438444385443864438744388443894439044391443924439344394443954439644397443984439944400444014440244403444044440544406444074440844409444104441144412444134441444415444164441744418444194442044421444224442344424444254442644427444284442944430444314443244433444344443544436444374443844439444404444144442444434444444445444464444744448444494445044451444524445344454444554445644457444584445944460444614446244463444644446544466444674446844469444704447144472444734447444475444764447744478444794448044481444824448344484444854448644487444884448944490444914449244493444944449544496444974449844499445004450144502445034450444505445064450744508445094451044511445124451344514445154451644517445184451944520445214452244523445244452544526445274452844529445304453144532445334453444535445364453744538445394454044541445424454344544445454454644547445484454944550445514455244553445544455544556445574455844559445604456144562445634456444565445664456744568445694457044571445724457344574445754457644577445784457944580445814458244583445844458544586445874458844589445904459144592445934459444595445964459744598445994460044601446024460344604446054460644607446084460944610446114461244613446144461544616446174461844619446204462144622446234462444625446264462744628446294463044631446324463344634446354463644637446384463944640446414464244643446444464544646446474464844649446504465144652446534465444655446564465744658446594466044661446624466344664446654466644667446684466944670446714467244673446744467544676446774467844679446804468144682446834468444685446864468744688446894469044691446924469344694446954469644697446984469944700447014470244703447044470544706447074470844709447104471144712447134471444715447164471744718447194472044721447224472344724447254472644727447284472944730447314473244733447344473544736447374473844739447404474144742447434474444745447464474744748447494475044751447524475344754447554475644757447584475944760447614476244763447644476544766447674476844769447704477144772447734477444775447764477744778447794478044781447824478344784447854478644787447884478944790447914479244793447944479544796447974479844799448004480144802448034480444805448064480744808448094481044811448124481344814448154481644817448184481944820448214482244823448244482544826448274482844829448304483144832448334483444835448364483744838448394484044841448424484344844448454484644847448484484944850448514485244853448544485544856448574485844859448604486144862448634486444865448664486744868448694487044871448724487344874448754487644877448784487944880448814488244883448844488544886448874488844889448904489144892448934489444895448964489744898448994490044901449024490344904449054490644907449084490944910449114491244913449144491544916449174491844919449204492144922449234492444925449264492744928449294493044931449324493344934449354493644937449384493944940449414494244943449444494544946449474494844949449504495144952449534495444955449564495744958449594496044961449624496344964449654496644967449684496944970449714497244973449744497544976449774497844979449804498144982449834498444985449864498744988449894499044991449924499344994449954499644997449984499945000450014500245003450044500545006450074500845009450104501145012450134501445015450164501745018450194502045021450224502345024450254502645027450284502945030450314503245033450344503545036450374503845039450404504145042450434504445045450464504745048450494505045051450524505345054450554505645057450584505945060450614506245063450644506545066450674506845069450704507145072450734507445075450764507745078450794508045081450824508345084450854508645087450884508945090450914509245093450944509545096450974509845099451004510145102451034510445105451064510745108451094511045111451124511345114451154511645117451184511945120451214512245123451244512545126451274512845129451304513145132451334513445135451364513745138451394514045141451424514345144451454514645147451484514945150451514515245153451544515545156451574515845159451604516145162451634516445165451664516745168451694517045171451724517345174451754517645177451784517945180451814518245183451844518545186451874518845189451904519145192451934519445195451964519745198451994520045201452024520345204452054520645207452084520945210452114521245213452144521545216452174521845219452204522145222452234522445225452264522745228452294523045231452324523345234452354523645237452384523945240452414524245243452444524545246452474524845249452504525145252452534525445255452564525745258452594526045261452624526345264452654526645267452684526945270452714527245273452744527545276452774527845279452804528145282452834528445285452864528745288452894529045291452924529345294452954529645297452984529945300453014530245303453044530545306453074530845309453104531145312453134531445315453164531745318453194532045321453224532345324453254532645327453284532945330453314533245333453344533545336453374533845339453404534145342453434534445345453464534745348453494535045351453524535345354453554535645357453584535945360453614536245363453644536545366453674536845369453704537145372453734537445375453764537745378453794538045381453824538345384453854538645387453884538945390453914539245393453944539545396453974539845399454004540145402454034540445405454064540745408454094541045411454124541345414454154541645417454184541945420454214542245423454244542545426454274542845429454304543145432454334543445435454364543745438454394544045441454424544345444454454544645447454484544945450454514545245453454544545545456454574545845459454604546145462454634546445465454664546745468454694547045471454724547345474454754547645477454784547945480454814548245483454844548545486454874548845489454904549145492454934549445495454964549745498454994550045501455024550345504455054550645507455084550945510455114551245513455144551545516455174551845519455204552145522455234552445525455264552745528455294553045531455324553345534455354553645537455384553945540455414554245543455444554545546455474554845549455504555145552455534555445555455564555745558455594556045561455624556345564455654556645567455684556945570455714557245573455744557545576455774557845579455804558145582455834558445585455864558745588455894559045591455924559345594455954559645597455984559945600456014560245603456044560545606456074560845609456104561145612456134561445615456164561745618456194562045621456224562345624456254562645627456284562945630456314563245633456344563545636456374563845639456404564145642456434564445645456464564745648456494565045651456524565345654456554565645657456584565945660456614566245663456644566545666456674566845669456704567145672456734567445675456764567745678456794568045681456824568345684456854568645687456884568945690456914569245693456944569545696456974569845699457004570145702457034570445705457064570745708457094571045711457124571345714457154571645717457184571945720457214572245723457244572545726457274572845729457304573145732457334573445735457364573745738457394574045741457424574345744457454574645747457484574945750457514575245753457544575545756457574575845759457604576145762457634576445765457664576745768457694577045771457724577345774457754577645777457784577945780457814578245783457844578545786457874578845789457904579145792457934579445795457964579745798457994580045801458024580345804458054580645807458084580945810458114581245813458144581545816458174581845819458204582145822458234582445825458264582745828458294583045831458324583345834458354583645837458384583945840458414584245843458444584545846458474584845849458504585145852458534585445855458564585745858458594586045861458624586345864458654586645867458684586945870458714587245873458744587545876458774587845879458804588145882458834588445885458864588745888458894589045891458924589345894458954589645897458984589945900459014590245903459044590545906459074590845909459104591145912459134591445915459164591745918459194592045921459224592345924459254592645927459284592945930459314593245933459344593545936459374593845939459404594145942459434594445945459464594745948459494595045951459524595345954459554595645957459584595945960459614596245963459644596545966459674596845969459704597145972459734597445975459764597745978459794598045981459824598345984459854598645987459884598945990459914599245993459944599545996459974599845999460004600146002460034600446005460064600746008460094601046011460124601346014460154601646017460184601946020460214602246023460244602546026460274602846029460304603146032460334603446035460364603746038460394604046041460424604346044460454604646047460484604946050460514605246053460544605546056460574605846059460604606146062460634606446065460664606746068460694607046071460724607346074460754607646077460784607946080460814608246083460844608546086460874608846089460904609146092460934609446095460964609746098460994610046101461024610346104461054610646107461084610946110461114611246113461144611546116461174611846119461204612146122461234612446125461264612746128461294613046131461324613346134461354613646137461384613946140461414614246143461444614546146461474614846149461504615146152461534615446155461564615746158461594616046161461624616346164461654616646167461684616946170461714617246173461744617546176461774617846179461804618146182461834618446185461864618746188461894619046191461924619346194461954619646197461984619946200462014620246203462044620546206462074620846209462104621146212462134621446215462164621746218462194622046221462224622346224462254622646227462284622946230462314623246233462344623546236462374623846239462404624146242462434624446245462464624746248462494625046251462524625346254462554625646257462584625946260462614626246263462644626546266462674626846269462704627146272462734627446275462764627746278462794628046281462824628346284462854628646287462884628946290462914629246293462944629546296462974629846299463004630146302463034630446305463064630746308463094631046311463124631346314463154631646317463184631946320463214632246323463244632546326463274632846329463304633146332463334633446335463364633746338463394634046341463424634346344463454634646347463484634946350463514635246353463544635546356463574635846359463604636146362463634636446365463664636746368463694637046371463724637346374463754637646377463784637946380463814638246383463844638546386463874638846389463904639146392463934639446395463964639746398463994640046401464024640346404464054640646407464084640946410464114641246413464144641546416464174641846419464204642146422464234642446425464264642746428464294643046431464324643346434464354643646437464384643946440464414644246443464444644546446464474644846449464504645146452464534645446455464564645746458464594646046461464624646346464464654646646467464684646946470464714647246473464744647546476464774647846479464804648146482464834648446485464864648746488464894649046491464924649346494464954649646497464984649946500465014650246503465044650546506465074650846509465104651146512465134651446515465164651746518465194652046521465224652346524465254652646527465284652946530465314653246533465344653546536465374653846539465404654146542465434654446545465464654746548465494655046551465524655346554465554655646557465584655946560465614656246563465644656546566465674656846569465704657146572465734657446575465764657746578465794658046581465824658346584465854658646587465884658946590465914659246593465944659546596465974659846599466004660146602466034660446605466064660746608466094661046611466124661346614466154661646617466184661946620466214662246623466244662546626466274662846629466304663146632466334663446635466364663746638466394664046641466424664346644466454664646647466484664946650466514665246653466544665546656466574665846659466604666146662466634666446665466664666746668466694667046671466724667346674466754667646677466784667946680466814668246683466844668546686466874668846689466904669146692466934669446695466964669746698466994670046701467024670346704467054670646707467084670946710467114671246713467144671546716467174671846719467204672146722467234672446725467264672746728467294673046731467324673346734467354673646737467384673946740467414674246743467444674546746467474674846749467504675146752467534675446755467564675746758467594676046761467624676346764467654676646767467684676946770467714677246773467744677546776467774677846779467804678146782467834678446785467864678746788467894679046791467924679346794467954679646797467984679946800468014680246803468044680546806468074680846809468104681146812468134681446815468164681746818468194682046821468224682346824468254682646827468284682946830468314683246833468344683546836468374683846839468404684146842468434684446845468464684746848468494685046851468524685346854468554685646857468584685946860468614686246863468644686546866468674686846869468704687146872468734687446875468764687746878468794688046881468824688346884468854688646887468884688946890468914689246893468944689546896468974689846899469004690146902469034690446905469064690746908469094691046911469124691346914469154691646917469184691946920469214692246923469244692546926469274692846929469304693146932469334693446935469364693746938469394694046941469424694346944469454694646947469484694946950469514695246953469544695546956469574695846959469604696146962469634696446965469664696746968469694697046971469724697346974469754697646977469784697946980469814698246983469844698546986469874698846989469904699146992469934699446995469964699746998469994700047001470024700347004470054700647007470084700947010470114701247013470144701547016470174701847019470204702147022470234702447025470264702747028470294703047031470324703347034470354703647037470384703947040470414704247043470444704547046470474704847049470504705147052470534705447055470564705747058470594706047061470624706347064470654706647067470684706947070470714707247073470744707547076470774707847079470804708147082470834708447085470864708747088470894709047091470924709347094470954709647097470984709947100471014710247103471044710547106471074710847109471104711147112471134711447115471164711747118471194712047121471224712347124471254712647127471284712947130471314713247133471344713547136471374713847139471404714147142471434714447145471464714747148471494715047151471524715347154471554715647157471584715947160471614716247163471644716547166471674716847169471704717147172471734717447175471764717747178471794718047181471824718347184471854718647187471884718947190471914719247193471944719547196471974719847199472004720147202472034720447205472064720747208472094721047211472124721347214472154721647217472184721947220472214722247223472244722547226472274722847229472304723147232472334723447235472364723747238472394724047241472424724347244472454724647247472484724947250472514725247253472544725547256472574725847259472604726147262472634726447265472664726747268472694727047271472724727347274472754727647277472784727947280472814728247283472844728547286472874728847289472904729147292472934729447295472964729747298472994730047301473024730347304473054730647307473084730947310473114731247313473144731547316473174731847319473204732147322473234732447325473264732747328473294733047331473324733347334473354733647337473384733947340473414734247343473444734547346473474734847349473504735147352473534735447355473564735747358473594736047361473624736347364473654736647367473684736947370473714737247373473744737547376473774737847379473804738147382473834738447385473864738747388473894739047391473924739347394473954739647397473984739947400474014740247403474044740547406474074740847409474104741147412474134741447415474164741747418474194742047421474224742347424474254742647427474284742947430474314743247433474344743547436474374743847439474404744147442474434744447445474464744747448474494745047451474524745347454474554745647457474584745947460474614746247463474644746547466474674746847469474704747147472474734747447475474764747747478474794748047481474824748347484474854748647487474884748947490474914749247493474944749547496474974749847499475004750147502475034750447505475064750747508475094751047511475124751347514475154751647517475184751947520475214752247523475244752547526475274752847529475304753147532475334753447535475364753747538475394754047541475424754347544475454754647547475484754947550475514755247553475544755547556475574755847559475604756147562475634756447565475664756747568475694757047571475724757347574475754757647577475784757947580475814758247583475844758547586475874758847589475904759147592475934759447595475964759747598475994760047601476024760347604476054760647607476084760947610476114761247613476144761547616476174761847619476204762147622476234762447625476264762747628476294763047631476324763347634476354763647637476384763947640476414764247643476444764547646476474764847649476504765147652476534765447655476564765747658476594766047661476624766347664476654766647667476684766947670476714767247673476744767547676476774767847679476804768147682476834768447685476864768747688476894769047691476924769347694476954769647697476984769947700477014770247703477044770547706477074770847709477104771147712477134771447715477164771747718477194772047721477224772347724477254772647727477284772947730477314773247733477344773547736477374773847739477404774147742477434774447745477464774747748477494775047751477524775347754477554775647757477584775947760477614776247763477644776547766477674776847769477704777147772477734777447775477764777747778477794778047781477824778347784477854778647787477884778947790477914779247793477944779547796477974779847799478004780147802478034780447805478064780747808478094781047811478124781347814478154781647817478184781947820478214782247823478244782547826478274782847829478304783147832478334783447835478364783747838478394784047841478424784347844478454784647847478484784947850478514785247853478544785547856478574785847859478604786147862478634786447865478664786747868478694787047871478724787347874478754787647877478784787947880478814788247883478844788547886478874788847889478904789147892478934789447895478964789747898478994790047901479024790347904479054790647907479084790947910479114791247913479144791547916479174791847919479204792147922479234792447925479264792747928479294793047931479324793347934479354793647937479384793947940479414794247943479444794547946479474794847949479504795147952479534795447955479564795747958479594796047961479624796347964479654796647967479684796947970479714797247973479744797547976479774797847979479804798147982479834798447985479864798747988479894799047991479924799347994479954799647997479984799948000480014800248003480044800548006480074800848009480104801148012480134801448015480164801748018480194802048021480224802348024480254802648027480284802948030480314803248033480344803548036480374803848039480404804148042480434804448045480464804748048480494805048051480524805348054480554805648057480584805948060480614806248063480644806548066480674806848069480704807148072480734807448075480764807748078480794808048081480824808348084480854808648087480884808948090480914809248093480944809548096480974809848099481004810148102481034810448105481064810748108481094811048111481124811348114481154811648117481184811948120481214812248123481244812548126481274812848129481304813148132481334813448135481364813748138481394814048141481424814348144481454814648147481484814948150481514815248153481544815548156481574815848159481604816148162481634816448165481664816748168481694817048171481724817348174481754817648177481784817948180481814818248183481844818548186481874818848189481904819148192481934819448195481964819748198481994820048201482024820348204482054820648207482084820948210482114821248213482144821548216482174821848219482204822148222482234822448225482264822748228482294823048231482324823348234482354823648237482384823948240482414824248243482444824548246482474824848249482504825148252482534825448255482564825748258482594826048261482624826348264482654826648267482684826948270482714827248273482744827548276482774827848279482804828148282482834828448285482864828748288482894829048291482924829348294482954829648297482984829948300483014830248303483044830548306483074830848309483104831148312483134831448315483164831748318483194832048321483224832348324483254832648327483284832948330483314833248333483344833548336483374833848339483404834148342483434834448345483464834748348483494835048351483524835348354483554835648357483584835948360483614836248363483644836548366483674836848369483704837148372483734837448375483764837748378483794838048381483824838348384483854838648387483884838948390483914839248393483944839548396483974839848399484004840148402484034840448405484064840748408484094841048411484124841348414484154841648417484184841948420484214842248423484244842548426484274842848429484304843148432484334843448435484364843748438484394844048441484424844348444484454844648447484484844948450484514845248453484544845548456484574845848459484604846148462484634846448465484664846748468484694847048471484724847348474484754847648477484784847948480484814848248483484844848548486484874848848489484904849148492484934849448495484964849748498484994850048501485024850348504485054850648507485084850948510485114851248513485144851548516485174851848519485204852148522485234852448525485264852748528485294853048531485324853348534485354853648537485384853948540485414854248543485444854548546485474854848549485504855148552485534855448555485564855748558485594856048561485624856348564485654856648567485684856948570485714857248573485744857548576485774857848579485804858148582485834858448585485864858748588485894859048591485924859348594485954859648597485984859948600486014860248603486044860548606486074860848609486104861148612486134861448615486164861748618486194862048621486224862348624486254862648627486284862948630486314863248633486344863548636486374863848639486404864148642486434864448645486464864748648486494865048651486524865348654486554865648657486584865948660486614866248663486644866548666486674866848669486704867148672486734867448675486764867748678486794868048681486824868348684486854868648687486884868948690486914869248693486944869548696486974869848699487004870148702487034870448705487064870748708487094871048711487124871348714487154871648717487184871948720487214872248723487244872548726487274872848729487304873148732487334873448735487364873748738487394874048741487424874348744487454874648747487484874948750487514875248753487544875548756487574875848759487604876148762487634876448765487664876748768487694877048771487724877348774487754877648777487784877948780487814878248783487844878548786487874878848789487904879148792487934879448795487964879748798487994880048801488024880348804488054880648807488084880948810488114881248813488144881548816488174881848819488204882148822488234882448825488264882748828488294883048831488324883348834488354883648837488384883948840488414884248843488444884548846488474884848849488504885148852488534885448855488564885748858488594886048861488624886348864488654886648867488684886948870488714887248873488744887548876488774887848879488804888148882488834888448885488864888748888488894889048891488924889348894488954889648897488984889948900489014890248903489044890548906489074890848909489104891148912489134891448915489164891748918489194892048921489224892348924489254892648927489284892948930489314893248933489344893548936489374893848939489404894148942489434894448945489464894748948489494895048951489524895348954489554895648957489584895948960489614896248963489644896548966489674896848969489704897148972489734897448975489764897748978489794898048981489824898348984489854898648987489884898948990489914899248993489944899548996489974899848999490004900149002490034900449005490064900749008490094901049011490124901349014490154901649017490184901949020490214902249023490244902549026490274902849029490304903149032490334903449035490364903749038490394904049041490424904349044490454904649047490484904949050490514905249053490544905549056490574905849059490604906149062490634906449065490664906749068490694907049071490724907349074490754907649077490784907949080490814908249083490844908549086490874908849089490904909149092490934909449095490964909749098490994910049101491024910349104491054910649107491084910949110491114911249113491144911549116491174911849119491204912149122491234912449125491264912749128491294913049131491324913349134491354913649137491384913949140491414914249143491444914549146491474914849149491504915149152491534915449155491564915749158491594916049161491624916349164491654916649167491684916949170491714917249173491744917549176491774917849179491804918149182491834918449185491864918749188491894919049191491924919349194491954919649197491984919949200492014920249203492044920549206492074920849209492104921149212492134921449215492164921749218492194922049221492224922349224492254922649227492284922949230492314923249233492344923549236492374923849239492404924149242492434924449245492464924749248492494925049251492524925349254492554925649257492584925949260492614926249263492644926549266492674926849269492704927149272492734927449275492764927749278492794928049281492824928349284492854928649287492884928949290492914929249293492944929549296492974929849299493004930149302493034930449305493064930749308493094931049311493124931349314493154931649317493184931949320493214932249323493244932549326493274932849329493304933149332493334933449335493364933749338493394934049341493424934349344493454934649347493484934949350493514935249353493544935549356493574935849359493604936149362493634936449365493664936749368493694937049371493724937349374493754937649377493784937949380493814938249383493844938549386493874938849389493904939149392493934939449395493964939749398493994940049401494024940349404494054940649407494084940949410494114941249413494144941549416494174941849419494204942149422494234942449425494264942749428494294943049431494324943349434494354943649437494384943949440494414944249443494444944549446494474944849449494504945149452494534945449455494564945749458494594946049461494624946349464494654946649467494684946949470494714947249473494744947549476494774947849479494804948149482494834948449485494864948749488494894949049491494924949349494494954949649497494984949949500495014950249503495044950549506495074950849509495104951149512495134951449515495164951749518495194952049521495224952349524495254952649527495284952949530495314953249533495344953549536495374953849539495404954149542495434954449545495464954749548495494955049551495524955349554495554955649557495584955949560495614956249563495644956549566495674956849569495704957149572495734957449575495764957749578495794958049581495824958349584495854958649587495884958949590495914959249593495944959549596495974959849599496004960149602496034960449605496064960749608496094961049611496124961349614496154961649617496184961949620496214962249623496244962549626496274962849629496304963149632496334963449635496364963749638496394964049641496424964349644496454964649647496484964949650496514965249653496544965549656496574965849659496604966149662496634966449665496664966749668496694967049671496724967349674496754967649677496784967949680496814968249683496844968549686496874968849689496904969149692496934969449695496964969749698496994970049701497024970349704497054970649707497084970949710497114971249713497144971549716497174971849719497204972149722497234972449725497264972749728497294973049731497324973349734497354973649737497384973949740497414974249743497444974549746497474974849749497504975149752497534975449755497564975749758497594976049761497624976349764497654976649767497684976949770497714977249773497744977549776497774977849779497804978149782497834978449785497864978749788497894979049791497924979349794497954979649797497984979949800498014980249803498044980549806498074980849809498104981149812498134981449815498164981749818498194982049821498224982349824498254982649827498284982949830498314983249833498344983549836498374983849839498404984149842498434984449845498464984749848498494985049851498524985349854498554985649857498584985949860498614986249863498644986549866498674986849869498704987149872498734987449875498764987749878498794988049881498824988349884498854988649887498884988949890498914989249893498944989549896498974989849899499004990149902499034990449905499064990749908499094991049911499124991349914499154991649917499184991949920499214992249923499244992549926499274992849929499304993149932499334993449935499364993749938499394994049941499424994349944499454994649947499484994949950499514995249953499544995549956499574995849959499604996149962499634996449965499664996749968499694997049971499724997349974499754997649977499784997949980499814998249983499844998549986499874998849989499904999149992499934999449995499964999749998499995000050001500025000350004500055000650007500085000950010500115001250013500145001550016500175001850019500205002150022500235002450025500265002750028500295003050031500325003350034500355003650037500385003950040500415004250043500445004550046500475004850049500505005150052500535005450055500565005750058500595006050061500625006350064500655006650067500685006950070500715007250073500745007550076500775007850079500805008150082500835008450085500865008750088500895009050091500925009350094500955009650097500985009950100501015010250103501045010550106501075010850109501105011150112501135011450115501165011750118501195012050121501225012350124501255012650127501285012950130501315013250133501345013550136501375013850139501405014150142501435014450145501465014750148501495015050151501525015350154501555015650157501585015950160501615016250163501645016550166501675016850169501705017150172501735017450175501765017750178501795018050181501825018350184501855018650187501885018950190501915019250193501945019550196501975019850199502005020150202502035020450205502065020750208502095021050211502125021350214502155021650217502185021950220502215022250223502245022550226502275022850229502305023150232502335023450235502365023750238502395024050241502425024350244502455024650247502485024950250502515025250253502545025550256502575025850259502605026150262502635026450265502665026750268502695027050271502725027350274502755027650277502785027950280502815028250283502845028550286502875028850289502905029150292502935029450295502965029750298502995030050301503025030350304503055030650307503085030950310503115031250313503145031550316503175031850319503205032150322503235032450325503265032750328503295033050331503325033350334503355033650337503385033950340503415034250343503445034550346503475034850349503505035150352503535035450355503565035750358503595036050361503625036350364503655036650367503685036950370503715037250373503745037550376503775037850379503805038150382503835038450385503865038750388503895039050391503925039350394503955039650397503985039950400504015040250403504045040550406504075040850409504105041150412504135041450415504165041750418504195042050421504225042350424504255042650427504285042950430504315043250433504345043550436504375043850439504405044150442504435044450445504465044750448504495045050451504525045350454504555045650457504585045950460504615046250463504645046550466504675046850469504705047150472504735047450475504765047750478504795048050481504825048350484504855048650487504885048950490504915049250493504945049550496504975049850499505005050150502505035050450505505065050750508505095051050511505125051350514505155051650517505185051950520505215052250523505245052550526505275052850529505305053150532505335053450535505365053750538505395054050541505425054350544505455054650547505485054950550505515055250553505545055550556505575055850559505605056150562505635056450565505665056750568505695057050571505725057350574505755057650577505785057950580505815058250583505845058550586505875058850589505905059150592505935059450595505965059750598505995060050601506025060350604506055060650607506085060950610506115061250613506145061550616506175061850619506205062150622506235062450625506265062750628506295063050631506325063350634506355063650637506385063950640506415064250643506445064550646506475064850649506505065150652506535065450655506565065750658506595066050661506625066350664506655066650667506685066950670506715067250673506745067550676506775067850679506805068150682506835068450685506865068750688506895069050691506925069350694506955069650697506985069950700507015070250703507045070550706507075070850709507105071150712507135071450715507165071750718507195072050721507225072350724507255072650727507285072950730507315073250733507345073550736507375073850739507405074150742507435074450745507465074750748507495075050751507525075350754507555075650757507585075950760507615076250763507645076550766507675076850769507705077150772507735077450775507765077750778507795078050781507825078350784507855078650787507885078950790507915079250793507945079550796507975079850799508005080150802508035080450805508065080750808508095081050811508125081350814508155081650817508185081950820508215082250823508245082550826508275082850829508305083150832508335083450835508365083750838508395084050841508425084350844508455084650847508485084950850508515085250853508545085550856508575085850859508605086150862508635086450865508665086750868508695087050871508725087350874508755087650877508785087950880508815088250883508845088550886508875088850889508905089150892508935089450895508965089750898508995090050901509025090350904509055090650907509085090950910509115091250913509145091550916509175091850919509205092150922509235092450925509265092750928509295093050931509325093350934509355093650937509385093950940509415094250943509445094550946509475094850949509505095150952509535095450955509565095750958509595096050961509625096350964509655096650967509685096950970509715097250973509745097550976509775097850979509805098150982509835098450985509865098750988509895099050991509925099350994509955099650997509985099951000510015100251003510045100551006510075100851009510105101151012510135101451015510165101751018510195102051021510225102351024510255102651027510285102951030510315103251033510345103551036510375103851039510405104151042510435104451045510465104751048510495105051051510525105351054510555105651057510585105951060510615106251063510645106551066510675106851069510705107151072510735107451075510765107751078510795108051081510825108351084510855108651087510885108951090510915109251093510945109551096510975109851099511005110151102511035110451105511065110751108511095111051111511125111351114511155111651117511185111951120511215112251123511245112551126511275112851129511305113151132511335113451135511365113751138511395114051141511425114351144511455114651147511485114951150511515115251153511545115551156511575115851159511605116151162511635116451165511665116751168511695117051171511725117351174511755117651177511785117951180511815118251183511845118551186511875118851189511905119151192511935119451195511965119751198511995120051201512025120351204512055120651207512085120951210512115121251213512145121551216512175121851219512205122151222512235122451225512265122751228512295123051231512325123351234512355123651237512385123951240512415124251243512445124551246512475124851249512505125151252512535125451255512565125751258512595126051261512625126351264512655126651267512685126951270512715127251273512745127551276512775127851279512805128151282512835128451285512865128751288512895129051291512925129351294512955129651297512985129951300513015130251303513045130551306513075130851309513105131151312513135131451315513165131751318513195132051321513225132351324513255132651327513285132951330513315133251333513345133551336513375133851339513405134151342513435134451345513465134751348513495135051351513525135351354513555135651357513585135951360513615136251363513645136551366513675136851369513705137151372513735137451375513765137751378513795138051381513825138351384513855138651387513885138951390513915139251393513945139551396513975139851399514005140151402514035140451405514065140751408514095141051411514125141351414514155141651417514185141951420514215142251423514245142551426514275142851429514305143151432514335143451435514365143751438514395144051441514425144351444514455144651447514485144951450514515145251453514545145551456514575145851459514605146151462514635146451465514665146751468514695147051471514725147351474514755147651477514785147951480514815148251483514845148551486514875148851489514905149151492514935149451495514965149751498514995150051501515025150351504515055150651507515085150951510515115151251513515145151551516515175151851519515205152151522515235152451525515265152751528515295153051531515325153351534515355153651537515385153951540515415154251543515445154551546515475154851549515505155151552515535155451555515565155751558515595156051561515625156351564515655156651567515685156951570515715157251573515745157551576515775157851579515805158151582515835158451585515865158751588515895159051591515925159351594515955159651597515985159951600516015160251603516045160551606516075160851609516105161151612516135161451615516165161751618516195162051621516225162351624516255162651627516285162951630516315163251633516345163551636516375163851639516405164151642516435164451645516465164751648516495165051651516525165351654516555165651657516585165951660516615166251663516645166551666516675166851669516705167151672516735167451675516765167751678516795168051681516825168351684516855168651687516885168951690516915169251693516945169551696516975169851699517005170151702517035170451705517065170751708517095171051711517125171351714517155171651717517185171951720517215172251723517245172551726517275172851729517305173151732517335173451735517365173751738517395174051741517425174351744517455174651747517485174951750517515175251753517545175551756517575175851759517605176151762517635176451765517665176751768517695177051771517725177351774517755177651777517785177951780517815178251783517845178551786517875178851789517905179151792517935179451795517965179751798517995180051801518025180351804518055180651807518085180951810518115181251813518145181551816518175181851819518205182151822518235182451825518265182751828518295183051831518325183351834518355183651837518385183951840518415184251843518445184551846518475184851849518505185151852518535185451855518565185751858518595186051861518625186351864518655186651867518685186951870518715187251873518745187551876518775187851879518805188151882518835188451885518865188751888518895189051891518925189351894518955189651897518985189951900519015190251903519045190551906519075190851909519105191151912519135191451915519165191751918519195192051921519225192351924519255192651927519285192951930519315193251933519345193551936519375193851939519405194151942519435194451945519465194751948519495195051951519525195351954519555195651957519585195951960519615196251963519645196551966519675196851969519705197151972519735197451975519765197751978519795198051981519825198351984519855198651987519885198951990519915199251993519945199551996519975199851999520005200152002520035200452005520065200752008520095201052011520125201352014520155201652017520185201952020520215202252023520245202552026520275202852029520305203152032520335203452035520365203752038520395204052041520425204352044520455204652047520485204952050520515205252053520545205552056520575205852059520605206152062520635206452065520665206752068520695207052071520725207352074520755207652077520785207952080520815208252083520845208552086520875208852089520905209152092520935209452095520965209752098520995210052101521025210352104521055210652107521085210952110521115211252113521145211552116521175211852119521205212152122521235212452125521265212752128521295213052131521325213352134521355213652137521385213952140521415214252143521445214552146521475214852149521505215152152521535215452155521565215752158521595216052161521625216352164521655216652167521685216952170521715217252173521745217552176521775217852179521805218152182521835218452185521865218752188521895219052191521925219352194521955219652197521985219952200522015220252203522045220552206522075220852209522105221152212522135221452215522165221752218522195222052221522225222352224522255222652227522285222952230522315223252233522345223552236522375223852239522405224152242522435224452245522465224752248522495225052251522525225352254522555225652257522585225952260522615226252263522645226552266522675226852269522705227152272522735227452275522765227752278522795228052281522825228352284522855228652287522885228952290522915229252293522945229552296522975229852299523005230152302523035230452305523065230752308523095231052311523125231352314523155231652317523185231952320523215232252323523245232552326523275232852329523305233152332523335233452335523365233752338523395234052341523425234352344523455234652347523485234952350523515235252353523545235552356523575235852359523605236152362523635236452365523665236752368523695237052371523725237352374523755237652377523785237952380523815238252383523845238552386523875238852389523905239152392523935239452395523965239752398523995240052401524025240352404524055240652407524085240952410524115241252413524145241552416524175241852419524205242152422524235242452425524265242752428524295243052431524325243352434524355243652437524385243952440524415244252443524445244552446524475244852449524505245152452524535245452455524565245752458524595246052461524625246352464524655246652467524685246952470524715247252473524745247552476524775247852479524805248152482524835248452485524865248752488524895249052491524925249352494524955249652497524985249952500525015250252503525045250552506525075250852509525105251152512525135251452515525165251752518525195252052521525225252352524525255252652527525285252952530525315253252533525345253552536525375253852539525405254152542525435254452545525465254752548525495255052551525525255352554525555255652557525585255952560525615256252563525645256552566525675256852569525705257152572525735257452575525765257752578525795258052581525825258352584525855258652587525885258952590525915259252593525945259552596525975259852599526005260152602526035260452605526065260752608526095261052611526125261352614526155261652617526185261952620526215262252623526245262552626526275262852629526305263152632526335263452635526365263752638526395264052641526425264352644526455264652647526485264952650526515265252653526545265552656526575265852659526605266152662526635266452665526665266752668526695267052671526725267352674526755267652677526785267952680526815268252683526845268552686526875268852689526905269152692526935269452695526965269752698526995270052701527025270352704527055270652707527085270952710527115271252713527145271552716527175271852719527205272152722527235272452725527265272752728527295273052731527325273352734527355273652737527385273952740527415274252743527445274552746527475274852749527505275152752527535275452755527565275752758527595276052761527625276352764527655276652767527685276952770527715277252773527745277552776527775277852779527805278152782527835278452785527865278752788527895279052791527925279352794527955279652797527985279952800528015280252803528045280552806528075280852809528105281152812528135281452815528165281752818528195282052821528225282352824528255282652827528285282952830528315283252833528345283552836528375283852839528405284152842528435284452845528465284752848528495285052851528525285352854528555285652857528585285952860528615286252863528645286552866528675286852869528705287152872528735287452875528765287752878528795288052881528825288352884528855288652887528885288952890528915289252893528945289552896528975289852899529005290152902529035290452905529065290752908529095291052911529125291352914529155291652917529185291952920529215292252923529245292552926529275292852929529305293152932529335293452935529365293752938529395294052941529425294352944529455294652947529485294952950529515295252953529545295552956529575295852959529605296152962529635296452965529665296752968529695297052971529725297352974529755297652977529785297952980529815298252983529845298552986529875298852989529905299152992529935299452995529965299752998529995300053001530025300353004530055300653007530085300953010530115301253013530145301553016530175301853019530205302153022530235302453025530265302753028530295303053031530325303353034530355303653037530385303953040530415304253043530445304553046530475304853049530505305153052530535305453055530565305753058530595306053061530625306353064530655306653067530685306953070530715307253073530745307553076530775307853079530805308153082530835308453085530865308753088530895309053091530925309353094530955309653097530985309953100531015310253103531045310553106531075310853109531105311153112531135311453115531165311753118531195312053121531225312353124531255312653127531285312953130531315313253133531345313553136531375313853139531405314153142531435314453145531465314753148531495315053151531525315353154531555315653157531585315953160531615316253163531645316553166531675316853169531705317153172531735317453175531765317753178531795318053181531825318353184531855318653187531885318953190531915319253193531945319553196531975319853199532005320153202532035320453205532065320753208532095321053211532125321353214532155321653217532185321953220532215322253223532245322553226532275322853229532305323153232532335323453235532365323753238532395324053241532425324353244532455324653247532485324953250532515325253253532545325553256532575325853259532605326153262532635326453265532665326753268532695327053271532725327353274532755327653277532785327953280532815328253283532845328553286532875328853289532905329153292532935329453295532965329753298532995330053301533025330353304533055330653307533085330953310533115331253313533145331553316533175331853319533205332153322533235332453325533265332753328533295333053331533325333353334533355333653337533385333953340533415334253343533445334553346533475334853349533505335153352533535335453355533565335753358533595336053361533625336353364533655336653367533685336953370533715337253373533745337553376533775337853379533805338153382533835338453385533865338753388533895339053391533925339353394533955339653397533985339953400534015340253403534045340553406534075340853409534105341153412534135341453415534165341753418534195342053421534225342353424534255342653427534285342953430534315343253433534345343553436534375343853439534405344153442534435344453445534465344753448534495345053451534525345353454534555345653457534585345953460534615346253463534645346553466534675346853469534705347153472534735347453475534765347753478534795348053481534825348353484534855348653487534885348953490534915349253493534945349553496534975349853499535005350153502535035350453505535065350753508535095351053511535125351353514535155351653517535185351953520535215352253523535245352553526535275352853529535305353153532535335353453535535365353753538535395354053541535425354353544535455354653547535485354953550535515355253553535545355553556535575355853559535605356153562535635356453565535665356753568535695357053571535725357353574535755357653577535785357953580535815358253583535845358553586535875358853589535905359153592535935359453595535965359753598535995360053601536025360353604536055360653607536085360953610536115361253613536145361553616536175361853619536205362153622536235362453625536265362753628536295363053631536325363353634536355363653637536385363953640536415364253643536445364553646536475364853649536505365153652536535365453655536565365753658536595366053661536625366353664536655366653667536685366953670536715367253673536745367553676536775367853679536805368153682536835368453685536865368753688536895369053691536925369353694536955369653697536985369953700537015370253703537045370553706537075370853709537105371153712537135371453715537165371753718537195372053721537225372353724537255372653727537285372953730537315373253733537345373553736537375373853739537405374153742537435374453745537465374753748537495375053751537525375353754537555375653757537585375953760537615376253763537645376553766537675376853769537705377153772537735377453775537765377753778537795378053781537825378353784537855378653787537885378953790537915379253793537945379553796537975379853799538005380153802538035380453805538065380753808538095381053811538125381353814538155381653817538185381953820538215382253823538245382553826538275382853829538305383153832538335383453835538365383753838538395384053841538425384353844538455384653847538485384953850538515385253853538545385553856538575385853859538605386153862538635386453865538665386753868538695387053871538725387353874538755387653877538785387953880538815388253883538845388553886538875388853889538905389153892538935389453895538965389753898538995390053901539025390353904539055390653907539085390953910539115391253913539145391553916539175391853919539205392153922539235392453925539265392753928539295393053931539325393353934539355393653937539385393953940539415394253943539445394553946539475394853949539505395153952539535395453955539565395753958539595396053961539625396353964539655396653967539685396953970539715397253973539745397553976539775397853979539805398153982539835398453985539865398753988539895399053991539925399353994539955399653997539985399954000540015400254003540045400554006540075400854009540105401154012540135401454015540165401754018540195402054021540225402354024540255402654027540285402954030540315403254033540345403554036540375403854039540405404154042540435404454045540465404754048540495405054051540525405354054540555405654057540585405954060540615406254063540645406554066540675406854069540705407154072540735407454075540765407754078540795408054081540825408354084540855408654087540885408954090540915409254093540945409554096540975409854099541005410154102541035410454105541065410754108541095411054111541125411354114541155411654117541185411954120541215412254123541245412554126541275412854129541305413154132541335413454135541365413754138541395414054141541425414354144541455414654147541485414954150541515415254153541545415554156541575415854159541605416154162541635416454165541665416754168541695417054171541725417354174541755417654177541785417954180541815418254183541845418554186541875418854189541905419154192541935419454195541965419754198541995420054201542025420354204542055420654207542085420954210542115421254213542145421554216542175421854219542205422154222542235422454225542265422754228542295423054231542325423354234542355423654237542385423954240542415424254243542445424554246542475424854249542505425154252542535425454255542565425754258542595426054261542625426354264542655426654267542685426954270542715427254273542745427554276542775427854279542805428154282542835428454285542865428754288542895429054291542925429354294542955429654297542985429954300543015430254303543045430554306543075430854309543105431154312543135431454315543165431754318543195432054321543225432354324543255432654327543285432954330543315433254333543345433554336543375433854339543405434154342543435434454345543465434754348543495435054351543525435354354543555435654357543585435954360543615436254363543645436554366543675436854369543705437154372543735437454375543765437754378543795438054381543825438354384543855438654387543885438954390543915439254393543945439554396543975439854399544005440154402544035440454405544065440754408544095441054411544125441354414544155441654417544185441954420544215442254423544245442554426544275442854429544305443154432544335443454435544365443754438544395444054441544425444354444544455444654447544485444954450544515445254453544545445554456544575445854459544605446154462544635446454465544665446754468544695447054471544725447354474544755447654477544785447954480544815448254483544845448554486544875448854489544905449154492544935449454495544965449754498544995450054501545025450354504545055450654507545085450954510545115451254513545145451554516545175451854519545205452154522545235452454525545265452754528545295453054531545325453354534545355453654537545385453954540545415454254543545445454554546545475454854549545505455154552545535455454555545565455754558545595456054561545625456354564545655456654567545685456954570545715457254573545745457554576545775457854579545805458154582545835458454585545865458754588545895459054591545925459354594545955459654597545985459954600546015460254603546045460554606546075460854609546105461154612546135461454615546165461754618546195462054621546225462354624546255462654627546285462954630546315463254633546345463554636546375463854639546405464154642546435464454645546465464754648546495465054651546525465354654546555465654657546585465954660546615466254663546645466554666546675466854669546705467154672546735467454675546765467754678546795468054681546825468354684546855468654687546885468954690546915469254693546945469554696546975469854699547005470154702547035470454705547065470754708547095471054711547125471354714547155471654717547185471954720547215472254723547245472554726547275472854729547305473154732547335473454735547365473754738547395474054741547425474354744547455474654747547485474954750547515475254753547545475554756547575475854759547605476154762547635476454765547665476754768547695477054771547725477354774547755477654777547785477954780547815478254783547845478554786547875478854789547905479154792547935479454795547965479754798547995480054801548025480354804548055480654807548085480954810548115481254813548145481554816548175481854819548205482154822548235482454825548265482754828548295483054831548325483354834548355483654837548385483954840548415484254843548445484554846548475484854849548505485154852548535485454855548565485754858548595486054861548625486354864548655486654867548685486954870548715487254873548745487554876548775487854879548805488154882548835488454885548865488754888548895489054891548925489354894548955489654897548985489954900549015490254903549045490554906549075490854909549105491154912549135491454915549165491754918549195492054921549225492354924549255492654927549285492954930549315493254933549345493554936549375493854939549405494154942549435494454945549465494754948549495495054951549525495354954549555495654957549585495954960549615496254963549645496554966549675496854969549705497154972549735497454975549765497754978549795498054981549825498354984549855498654987549885498954990549915499254993549945499554996549975499854999550005500155002550035500455005550065500755008550095501055011550125501355014550155501655017550185501955020550215502255023550245502555026550275502855029550305503155032550335503455035550365503755038550395504055041550425504355044550455504655047550485504955050550515505255053550545505555056550575505855059550605506155062550635506455065550665506755068550695507055071550725507355074550755507655077550785507955080550815508255083550845508555086550875508855089550905509155092550935509455095550965509755098550995510055101551025510355104551055510655107551085510955110551115511255113551145511555116551175511855119551205512155122551235512455125551265512755128551295513055131551325513355134551355513655137551385513955140551415514255143551445514555146551475514855149551505515155152551535515455155551565515755158551595516055161551625516355164551655516655167551685516955170551715517255173551745517555176551775517855179551805518155182551835518455185551865518755188551895519055191551925519355194551955519655197551985519955200552015520255203552045520555206552075520855209552105521155212552135521455215552165521755218552195522055221552225522355224552255522655227552285522955230552315523255233552345523555236552375523855239552405524155242552435524455245552465524755248552495525055251552525525355254552555525655257552585525955260552615526255263552645526555266552675526855269552705527155272552735527455275552765527755278552795528055281552825528355284552855528655287552885528955290552915529255293552945529555296552975529855299553005530155302553035530455305553065530755308553095531055311553125531355314553155531655317553185531955320553215532255323553245532555326553275532855329553305533155332553335533455335553365533755338553395534055341553425534355344553455534655347553485534955350553515535255353553545535555356553575535855359553605536155362553635536455365553665536755368553695537055371553725537355374553755537655377553785537955380553815538255383553845538555386553875538855389553905539155392553935539455395553965539755398553995540055401554025540355404554055540655407554085540955410554115541255413554145541555416554175541855419554205542155422554235542455425554265542755428554295543055431554325543355434554355543655437554385543955440554415544255443554445544555446554475544855449554505545155452554535545455455554565545755458554595546055461554625546355464554655546655467554685546955470554715547255473554745547555476554775547855479554805548155482554835548455485554865548755488554895549055491554925549355494554955549655497554985549955500555015550255503555045550555506555075550855509555105551155512555135551455515555165551755518555195552055521555225552355524555255552655527555285552955530555315553255533555345553555536555375553855539555405554155542555435554455545555465554755548555495555055551555525555355554555555555655557555585555955560555615556255563555645556555566555675556855569555705557155572555735557455575555765557755578555795558055581555825558355584555855558655587555885558955590555915559255593555945559555596555975559855599556005560155602556035560455605556065560755608556095561055611556125561355614556155561655617556185561955620556215562255623556245562555626556275562855629556305563155632556335563455635556365563755638556395564055641556425564355644556455564655647556485564955650556515565255653556545565555656556575565855659556605566155662556635566455665556665566755668556695567055671556725567355674556755567655677556785567955680556815568255683556845568555686556875568855689556905569155692556935569455695556965569755698556995570055701557025570355704557055570655707557085570955710557115571255713557145571555716557175571855719557205572155722557235572455725557265572755728557295573055731557325573355734557355573655737557385573955740557415574255743557445574555746557475574855749557505575155752557535575455755557565575755758557595576055761557625576355764557655576655767557685576955770557715577255773557745577555776557775577855779557805578155782557835578455785557865578755788557895579055791557925579355794557955579655797557985579955800558015580255803558045580555806558075580855809558105581155812558135581455815558165581755818558195582055821558225582355824558255582655827558285582955830558315583255833558345583555836558375583855839558405584155842558435584455845558465584755848558495585055851558525585355854558555585655857558585585955860558615586255863558645586555866558675586855869558705587155872558735587455875558765587755878558795588055881558825588355884558855588655887558885588955890558915589255893558945589555896558975589855899559005590155902559035590455905559065590755908559095591055911559125591355914559155591655917559185591955920559215592255923559245592555926559275592855929559305593155932559335593455935559365593755938559395594055941559425594355944559455594655947559485594955950559515595255953559545595555956559575595855959559605596155962559635596455965559665596755968559695597055971559725597355974559755597655977559785597955980559815598255983559845598555986559875598855989559905599155992559935599455995559965599755998559995600056001560025600356004560055600656007560085600956010560115601256013560145601556016560175601856019560205602156022560235602456025560265602756028560295603056031560325603356034560355603656037560385603956040560415604256043560445604556046560475604856049560505605156052560535605456055560565605756058560595606056061560625606356064560655606656067560685606956070560715607256073560745607556076560775607856079560805608156082560835608456085560865608756088560895609056091560925609356094560955609656097560985609956100561015610256103561045610556106561075610856109561105611156112561135611456115561165611756118561195612056121561225612356124561255612656127561285612956130561315613256133561345613556136561375613856139561405614156142561435614456145561465614756148561495615056151561525615356154561555615656157561585615956160561615616256163561645616556166561675616856169561705617156172561735617456175561765617756178561795618056181561825618356184561855618656187561885618956190561915619256193561945619556196561975619856199562005620156202562035620456205562065620756208562095621056211562125621356214562155621656217562185621956220562215622256223562245622556226562275622856229562305623156232562335623456235562365623756238562395624056241562425624356244562455624656247562485624956250562515625256253562545625556256562575625856259562605626156262562635626456265562665626756268562695627056271562725627356274562755627656277562785627956280562815628256283562845628556286562875628856289562905629156292562935629456295562965629756298562995630056301563025630356304563055630656307563085630956310563115631256313563145631556316563175631856319563205632156322563235632456325563265632756328563295633056331563325633356334563355633656337563385633956340563415634256343563445634556346563475634856349563505635156352563535635456355563565635756358563595636056361563625636356364563655636656367563685636956370563715637256373563745637556376563775637856379563805638156382563835638456385563865638756388563895639056391563925639356394563955639656397563985639956400564015640256403564045640556406564075640856409564105641156412564135641456415564165641756418564195642056421564225642356424564255642656427564285642956430564315643256433564345643556436564375643856439564405644156442564435644456445564465644756448564495645056451564525645356454564555645656457564585645956460564615646256463564645646556466564675646856469564705647156472564735647456475564765647756478564795648056481564825648356484564855648656487564885648956490564915649256493564945649556496564975649856499565005650156502565035650456505565065650756508565095651056511565125651356514565155651656517565185651956520565215652256523565245652556526565275652856529565305653156532565335653456535565365653756538565395654056541565425654356544565455654656547565485654956550565515655256553565545655556556565575655856559565605656156562565635656456565565665656756568565695657056571565725657356574565755657656577565785657956580565815658256583565845658556586565875658856589565905659156592565935659456595565965659756598565995660056601566025660356604566055660656607566085660956610566115661256613566145661556616566175661856619566205662156622566235662456625566265662756628566295663056631566325663356634566355663656637566385663956640566415664256643566445664556646566475664856649566505665156652566535665456655566565665756658566595666056661566625666356664566655666656667566685666956670566715667256673566745667556676566775667856679566805668156682566835668456685566865668756688566895669056691566925669356694566955669656697566985669956700567015670256703567045670556706567075670856709567105671156712567135671456715567165671756718567195672056721567225672356724567255672656727567285672956730567315673256733567345673556736567375673856739567405674156742567435674456745567465674756748567495675056751567525675356754567555675656757567585675956760567615676256763567645676556766567675676856769567705677156772567735677456775567765677756778567795678056781567825678356784567855678656787567885678956790567915679256793567945679556796567975679856799568005680156802568035680456805568065680756808568095681056811568125681356814568155681656817568185681956820568215682256823568245682556826568275682856829568305683156832568335683456835568365683756838568395684056841568425684356844568455684656847568485684956850568515685256853568545685556856568575685856859568605686156862568635686456865568665686756868568695687056871568725687356874568755687656877568785687956880568815688256883568845688556886568875688856889568905689156892568935689456895568965689756898568995690056901569025690356904569055690656907569085690956910569115691256913569145691556916569175691856919569205692156922569235692456925569265692756928569295693056931569325693356934569355693656937569385693956940569415694256943569445694556946569475694856949569505695156952569535695456955569565695756958569595696056961569625696356964569655696656967569685696956970569715697256973569745697556976569775697856979569805698156982569835698456985569865698756988569895699056991569925699356994569955699656997569985699957000570015700257003570045700557006570075700857009570105701157012570135701457015570165701757018570195702057021570225702357024570255702657027570285702957030570315703257033570345703557036570375703857039570405704157042570435704457045570465704757048570495705057051570525705357054570555705657057570585705957060570615706257063570645706557066570675706857069570705707157072570735707457075570765707757078570795708057081570825708357084570855708657087570885708957090570915709257093570945709557096570975709857099571005710157102571035710457105571065710757108571095711057111571125711357114571155711657117571185711957120571215712257123571245712557126571275712857129571305713157132571335713457135571365713757138571395714057141571425714357144571455714657147571485714957150571515715257153571545715557156571575715857159571605716157162571635716457165571665716757168571695717057171571725717357174571755717657177571785717957180571815718257183571845718557186571875718857189571905719157192571935719457195571965719757198571995720057201572025720357204572055720657207572085720957210572115721257213572145721557216572175721857219572205722157222572235722457225572265722757228572295723057231572325723357234572355723657237572385723957240572415724257243572445724557246572475724857249572505725157252572535725457255572565725757258572595726057261572625726357264572655726657267572685726957270572715727257273572745727557276572775727857279572805728157282572835728457285572865728757288572895729057291572925729357294572955729657297572985729957300573015730257303573045730557306573075730857309573105731157312573135731457315573165731757318573195732057321573225732357324573255732657327573285732957330573315733257333573345733557336573375733857339573405734157342573435734457345573465734757348573495735057351573525735357354573555735657357573585735957360573615736257363573645736557366573675736857369573705737157372573735737457375573765737757378573795738057381573825738357384573855738657387573885738957390573915739257393573945739557396573975739857399574005740157402574035740457405574065740757408574095741057411574125741357414574155741657417574185741957420574215742257423574245742557426574275742857429574305743157432574335743457435574365743757438574395744057441574425744357444574455744657447574485744957450574515745257453574545745557456574575745857459574605746157462574635746457465574665746757468574695747057471574725747357474574755747657477574785747957480574815748257483574845748557486574875748857489574905749157492574935749457495574965749757498574995750057501575025750357504575055750657507575085750957510575115751257513575145751557516575175751857519575205752157522575235752457525575265752757528575295753057531575325753357534575355753657537575385753957540575415754257543575445754557546575475754857549575505755157552575535755457555575565755757558575595756057561575625756357564575655756657567575685756957570575715757257573575745757557576575775757857579575805758157582575835758457585575865758757588575895759057591575925759357594575955759657597575985759957600576015760257603576045760557606576075760857609576105761157612576135761457615576165761757618576195762057621576225762357624576255762657627576285762957630576315763257633576345763557636576375763857639576405764157642576435764457645576465764757648576495765057651576525765357654576555765657657576585765957660576615766257663576645766557666576675766857669576705767157672576735767457675576765767757678576795768057681576825768357684576855768657687576885768957690576915769257693576945769557696576975769857699577005770157702577035770457705577065770757708577095771057711577125771357714577155771657717577185771957720577215772257723577245772557726577275772857729577305773157732577335773457735577365773757738577395774057741577425774357744577455774657747577485774957750577515775257753577545775557756577575775857759577605776157762577635776457765577665776757768577695777057771577725777357774577755777657777577785777957780577815778257783577845778557786577875778857789577905779157792577935779457795577965779757798577995780057801578025780357804578055780657807578085780957810578115781257813578145781557816578175781857819578205782157822578235782457825578265782757828578295783057831578325783357834578355783657837578385783957840578415784257843578445784557846578475784857849578505785157852578535785457855578565785757858578595786057861578625786357864578655786657867578685786957870578715787257873578745787557876578775787857879578805788157882578835788457885578865788757888578895789057891578925789357894578955789657897578985789957900579015790257903579045790557906579075790857909579105791157912579135791457915579165791757918579195792057921579225792357924579255792657927579285792957930579315793257933579345793557936579375793857939579405794157942579435794457945579465794757948579495795057951579525795357954579555795657957579585795957960579615796257963579645796557966579675796857969579705797157972579735797457975579765797757978579795798057981579825798357984579855798657987579885798957990579915799257993579945799557996579975799857999580005800158002580035800458005580065800758008580095801058011580125801358014580155801658017580185801958020580215802258023580245802558026580275802858029580305803158032580335803458035580365803758038580395804058041580425804358044580455804658047580485804958050580515805258053580545805558056580575805858059580605806158062580635806458065580665806758068580695807058071580725807358074580755807658077580785807958080580815808258083580845808558086580875808858089580905809158092580935809458095580965809758098580995810058101581025810358104581055810658107581085810958110581115811258113581145811558116581175811858119581205812158122581235812458125581265812758128581295813058131581325813358134581355813658137581385813958140581415814258143581445814558146581475814858149581505815158152581535815458155581565815758158581595816058161581625816358164581655816658167581685816958170581715817258173581745817558176581775817858179581805818158182581835818458185581865818758188581895819058191581925819358194581955819658197581985819958200582015820258203582045820558206582075820858209582105821158212582135821458215582165821758218582195822058221582225822358224582255822658227582285822958230582315823258233582345823558236582375823858239582405824158242582435824458245582465824758248582495825058251582525825358254582555825658257582585825958260582615826258263582645826558266582675826858269582705827158272582735827458275582765827758278582795828058281582825828358284582855828658287582885828958290582915829258293582945829558296582975829858299583005830158302583035830458305583065830758308583095831058311583125831358314583155831658317583185831958320583215832258323583245832558326583275832858329583305833158332583335833458335583365833758338583395834058341583425834358344583455834658347583485834958350583515835258353583545835558356583575835858359583605836158362583635836458365583665836758368583695837058371583725837358374583755837658377583785837958380583815838258383583845838558386583875838858389583905839158392583935839458395583965839758398583995840058401584025840358404584055840658407584085840958410584115841258413584145841558416584175841858419584205842158422584235842458425584265842758428584295843058431584325843358434584355843658437584385843958440584415844258443584445844558446584475844858449584505845158452584535845458455584565845758458584595846058461584625846358464584655846658467584685846958470584715847258473584745847558476584775847858479584805848158482584835848458485584865848758488584895849058491584925849358494584955849658497584985849958500585015850258503585045850558506585075850858509585105851158512585135851458515585165851758518585195852058521585225852358524585255852658527585285852958530585315853258533585345853558536585375853858539585405854158542585435854458545585465854758548585495855058551585525855358554585555855658557585585855958560585615856258563585645856558566585675856858569585705857158572585735857458575585765857758578585795858058581585825858358584585855858658587585885858958590585915859258593585945859558596585975859858599586005860158602586035860458605586065860758608586095861058611586125861358614586155861658617586185861958620586215862258623586245862558626586275862858629586305863158632586335863458635586365863758638586395864058641586425864358644586455864658647586485864958650586515865258653586545865558656586575865858659586605866158662586635866458665586665866758668586695867058671586725867358674586755867658677586785867958680586815868258683586845868558686586875868858689586905869158692586935869458695586965869758698586995870058701587025870358704587055870658707587085870958710587115871258713587145871558716587175871858719587205872158722587235872458725587265872758728587295873058731587325873358734587355873658737587385873958740587415874258743587445874558746587475874858749587505875158752587535875458755587565875758758587595876058761587625876358764587655876658767587685876958770587715877258773587745877558776587775877858779587805878158782587835878458785587865878758788587895879058791587925879358794587955879658797587985879958800588015880258803588045880558806588075880858809588105881158812588135881458815588165881758818588195882058821588225882358824588255882658827588285882958830588315883258833588345883558836588375883858839588405884158842588435884458845588465884758848588495885058851588525885358854588555885658857588585885958860588615886258863588645886558866588675886858869588705887158872588735887458875588765887758878588795888058881588825888358884588855888658887588885888958890588915889258893588945889558896588975889858899589005890158902589035890458905589065890758908589095891058911589125891358914589155891658917589185891958920589215892258923589245892558926589275892858929589305893158932589335893458935589365893758938589395894058941589425894358944589455894658947589485894958950589515895258953589545895558956589575895858959589605896158962589635896458965589665896758968589695897058971589725897358974589755897658977589785897958980589815898258983589845898558986589875898858989589905899158992589935899458995589965899758998589995900059001590025900359004590055900659007590085900959010590115901259013590145901559016590175901859019590205902159022590235902459025590265902759028590295903059031590325903359034590355903659037590385903959040590415904259043590445904559046590475904859049590505905159052590535905459055590565905759058590595906059061590625906359064590655906659067590685906959070590715907259073590745907559076590775907859079590805908159082590835908459085590865908759088590895909059091590925909359094590955909659097590985909959100591015910259103591045910559106591075910859109591105911159112591135911459115591165911759118591195912059121591225912359124591255912659127591285912959130591315913259133591345913559136591375913859139591405914159142591435914459145591465914759148591495915059151591525915359154591555915659157591585915959160591615916259163591645916559166591675916859169591705917159172591735917459175591765917759178591795918059181591825918359184591855918659187591885918959190591915919259193591945919559196591975919859199592005920159202592035920459205592065920759208592095921059211592125921359214592155921659217592185921959220592215922259223592245922559226592275922859229592305923159232592335923459235592365923759238592395924059241592425924359244592455924659247592485924959250592515925259253592545925559256592575925859259592605926159262592635926459265592665926759268592695927059271592725927359274592755927659277592785927959280592815928259283592845928559286592875928859289592905929159292592935929459295592965929759298592995930059301593025930359304593055930659307593085930959310593115931259313593145931559316593175931859319593205932159322593235932459325593265932759328593295933059331593325933359334593355933659337593385933959340593415934259343593445934559346593475934859349593505935159352593535935459355593565935759358593595936059361593625936359364593655936659367593685936959370593715937259373593745937559376593775937859379593805938159382593835938459385593865938759388593895939059391593925939359394593955939659397593985939959400594015940259403594045940559406594075940859409594105941159412594135941459415594165941759418594195942059421594225942359424594255942659427594285942959430594315943259433594345943559436594375943859439594405944159442594435944459445594465944759448594495945059451594525945359454594555945659457594585945959460594615946259463594645946559466594675946859469594705947159472594735947459475594765947759478594795948059481594825948359484594855948659487594885948959490594915949259493594945949559496594975949859499595005950159502595035950459505595065950759508595095951059511595125951359514595155951659517595185951959520595215952259523595245952559526595275952859529595305953159532595335953459535595365953759538595395954059541595425954359544595455954659547595485954959550595515955259553595545955559556595575955859559595605956159562595635956459565595665956759568595695957059571595725957359574595755957659577595785957959580595815958259583595845958559586595875958859589595905959159592595935959459595595965959759598595995960059601596025960359604596055960659607596085960959610596115961259613596145961559616596175961859619596205962159622596235962459625596265962759628596295963059631596325963359634596355963659637596385963959640596415964259643596445964559646596475964859649596505965159652596535965459655596565965759658596595966059661596625966359664596655966659667596685966959670596715967259673596745967559676596775967859679596805968159682596835968459685596865968759688596895969059691596925969359694596955969659697596985969959700597015970259703597045970559706597075970859709597105971159712597135971459715597165971759718597195972059721597225972359724597255972659727597285972959730597315973259733597345973559736597375973859739597405974159742597435974459745597465974759748597495975059751597525975359754597555975659757597585975959760597615976259763597645976559766597675976859769597705977159772597735977459775597765977759778597795978059781597825978359784597855978659787597885978959790597915979259793597945979559796597975979859799598005980159802598035980459805598065980759808598095981059811598125981359814598155981659817598185981959820598215982259823598245982559826598275982859829598305983159832598335983459835598365983759838598395984059841598425984359844598455984659847598485984959850598515985259853598545985559856598575985859859598605986159862598635986459865598665986759868598695987059871598725987359874598755987659877598785987959880598815988259883598845988559886598875988859889598905989159892598935989459895598965989759898598995990059901599025990359904599055990659907599085990959910599115991259913599145991559916599175991859919599205992159922599235992459925599265992759928599295993059931599325993359934599355993659937599385993959940599415994259943599445994559946599475994859949599505995159952599535995459955599565995759958599595996059961599625996359964599655996659967599685996959970599715997259973599745997559976599775997859979599805998159982599835998459985599865998759988599895999059991599925999359994599955999659997599985999960000600016000260003600046000560006600076000860009600106001160012600136001460015600166001760018600196002060021600226002360024600256002660027600286002960030600316003260033600346003560036600376003860039600406004160042600436004460045600466004760048600496005060051600526005360054600556005660057600586005960060600616006260063600646006560066600676006860069600706007160072600736007460075600766007760078600796008060081600826008360084600856008660087600886008960090600916009260093600946009560096600976009860099601006010160102601036010460105601066010760108601096011060111601126011360114601156011660117601186011960120601216012260123601246012560126601276012860129601306013160132601336013460135601366013760138601396014060141601426014360144601456014660147601486014960150601516015260153601546015560156601576015860159601606016160162601636016460165601666016760168601696017060171601726017360174601756017660177601786017960180601816018260183601846018560186601876018860189601906019160192601936019460195601966019760198601996020060201602026020360204602056020660207602086020960210602116021260213602146021560216602176021860219602206022160222602236022460225602266022760228602296023060231602326023360234602356023660237602386023960240602416024260243602446024560246602476024860249602506025160252602536025460255602566025760258602596026060261602626026360264602656026660267602686026960270602716027260273602746027560276602776027860279602806028160282602836028460285602866028760288602896029060291602926029360294602956029660297602986029960300603016030260303603046030560306603076030860309603106031160312603136031460315603166031760318603196032060321603226032360324603256032660327603286032960330603316033260333603346033560336603376033860339603406034160342603436034460345603466034760348603496035060351603526035360354603556035660357603586035960360603616036260363603646036560366603676036860369603706037160372603736037460375603766037760378603796038060381603826038360384603856038660387603886038960390603916039260393603946039560396603976039860399604006040160402604036040460405604066040760408604096041060411604126041360414604156041660417604186041960420604216042260423604246042560426604276042860429604306043160432604336043460435604366043760438604396044060441604426044360444604456044660447604486044960450604516045260453604546045560456604576045860459604606046160462604636046460465604666046760468604696047060471604726047360474604756047660477604786047960480604816048260483604846048560486604876048860489604906049160492604936049460495604966049760498604996050060501605026050360504605056050660507605086050960510605116051260513605146051560516605176051860519605206052160522605236052460525605266052760528605296053060531605326053360534605356053660537605386053960540605416054260543605446054560546605476054860549605506055160552605536055460555605566055760558605596056060561605626056360564605656056660567605686056960570605716057260573605746057560576605776057860579605806058160582605836058460585605866058760588605896059060591605926059360594605956059660597605986059960600606016060260603606046060560606606076060860609606106061160612606136061460615606166061760618606196062060621606226062360624606256062660627606286062960630606316063260633606346063560636606376063860639606406064160642606436064460645606466064760648606496065060651606526065360654606556065660657606586065960660606616066260663606646066560666606676066860669606706067160672606736067460675606766067760678606796068060681606826068360684606856068660687606886068960690606916069260693606946069560696606976069860699607006070160702607036070460705607066070760708607096071060711607126071360714607156071660717607186071960720607216072260723607246072560726607276072860729607306073160732607336073460735607366073760738607396074060741607426074360744607456074660747607486074960750607516075260753607546075560756607576075860759607606076160762607636076460765607666076760768607696077060771607726077360774607756077660777607786077960780607816078260783607846078560786607876078860789607906079160792607936079460795607966079760798607996080060801608026080360804608056080660807608086080960810608116081260813608146081560816608176081860819608206082160822608236082460825608266082760828608296083060831608326083360834608356083660837608386083960840608416084260843608446084560846608476084860849608506085160852608536085460855608566085760858608596086060861608626086360864608656086660867608686086960870608716087260873608746087560876608776087860879608806088160882608836088460885608866088760888608896089060891608926089360894608956089660897608986089960900609016090260903609046090560906609076090860909609106091160912609136091460915609166091760918609196092060921609226092360924609256092660927609286092960930609316093260933609346093560936609376093860939609406094160942609436094460945609466094760948609496095060951609526095360954609556095660957609586095960960609616096260963609646096560966609676096860969609706097160972609736097460975609766097760978609796098060981609826098360984609856098660987609886098960990609916099260993609946099560996609976099860999610006100161002610036100461005610066100761008610096101061011610126101361014610156101661017610186101961020610216102261023610246102561026610276102861029610306103161032610336103461035610366103761038610396104061041610426104361044610456104661047610486104961050610516105261053610546105561056610576105861059610606106161062610636106461065610666106761068610696107061071610726107361074610756107661077610786107961080610816108261083610846108561086610876108861089610906109161092610936109461095610966109761098610996110061101611026110361104611056110661107611086110961110611116111261113611146111561116611176111861119611206112161122611236112461125611266112761128611296113061131611326113361134611356113661137611386113961140611416114261143611446114561146611476114861149611506115161152611536115461155611566115761158611596116061161611626116361164611656116661167611686116961170611716117261173611746117561176611776117861179611806118161182611836118461185611866118761188611896119061191611926119361194611956119661197611986119961200612016120261203612046120561206612076120861209612106121161212612136121461215612166121761218612196122061221612226122361224612256122661227612286122961230612316123261233612346123561236612376123861239612406124161242612436124461245612466124761248612496125061251612526125361254612556125661257612586125961260612616126261263612646126561266612676126861269612706127161272612736127461275612766127761278612796128061281612826128361284612856128661287612886128961290612916129261293612946129561296612976129861299613006130161302613036130461305613066130761308613096131061311613126131361314613156131661317613186131961320613216132261323613246132561326613276132861329613306133161332613336133461335613366133761338613396134061341613426134361344613456134661347613486134961350613516135261353613546135561356613576135861359613606136161362613636136461365613666136761368613696137061371613726137361374613756137661377613786137961380613816138261383613846138561386613876138861389613906139161392613936139461395613966139761398613996140061401614026140361404614056140661407614086140961410614116141261413614146141561416614176141861419614206142161422614236142461425614266142761428614296143061431614326143361434614356143661437614386143961440614416144261443614446144561446614476144861449614506145161452614536145461455614566145761458614596146061461614626146361464614656146661467614686146961470614716147261473614746147561476614776147861479614806148161482614836148461485614866148761488614896149061491614926149361494614956149661497614986149961500615016150261503615046150561506615076150861509615106151161512615136151461515615166151761518615196152061521615226152361524615256152661527615286152961530615316153261533615346153561536615376153861539615406154161542615436154461545615466154761548615496155061551615526155361554615556155661557615586155961560615616156261563615646156561566615676156861569615706157161572615736157461575615766157761578615796158061581615826158361584615856158661587615886158961590615916159261593615946159561596615976159861599616006160161602616036160461605616066160761608616096161061611616126161361614616156161661617616186161961620616216162261623616246162561626616276162861629616306163161632616336163461635616366163761638616396164061641616426164361644616456164661647616486164961650616516165261653616546165561656616576165861659616606166161662616636166461665616666166761668616696167061671616726167361674616756167661677616786167961680616816168261683616846168561686616876168861689616906169161692616936169461695616966169761698616996170061701617026170361704617056170661707617086170961710617116171261713617146171561716617176171861719617206172161722617236172461725617266172761728617296173061731617326173361734617356173661737617386173961740617416174261743617446174561746617476174861749617506175161752617536175461755617566175761758617596176061761617626176361764617656176661767617686176961770617716177261773617746177561776617776177861779617806178161782617836178461785617866178761788617896179061791617926179361794617956179661797617986179961800618016180261803618046180561806618076180861809618106181161812618136181461815618166181761818618196182061821618226182361824618256182661827618286182961830618316183261833618346183561836618376183861839618406184161842618436184461845618466184761848618496185061851618526185361854618556185661857618586185961860618616186261863618646186561866618676186861869618706187161872618736187461875618766187761878618796188061881618826188361884618856188661887618886188961890618916189261893618946189561896618976189861899619006190161902619036190461905619066190761908619096191061911619126191361914619156191661917619186191961920619216192261923619246192561926619276192861929619306193161932619336193461935619366193761938619396194061941619426194361944619456194661947619486194961950619516195261953619546195561956619576195861959619606196161962619636196461965619666196761968619696197061971619726197361974619756197661977619786197961980619816198261983619846198561986619876198861989619906199161992619936199461995619966199761998619996200062001620026200362004620056200662007620086200962010620116201262013620146201562016620176201862019620206202162022620236202462025620266202762028620296203062031620326203362034620356203662037620386203962040620416204262043620446204562046620476204862049620506205162052620536205462055620566205762058620596206062061620626206362064620656206662067620686206962070620716207262073620746207562076620776207862079620806208162082620836208462085620866208762088620896209062091620926209362094620956209662097620986209962100621016210262103621046210562106621076210862109621106211162112621136211462115621166211762118621196212062121621226212362124621256212662127621286212962130621316213262133621346213562136621376213862139621406214162142621436214462145621466214762148621496215062151621526215362154621556215662157621586215962160621616216262163621646216562166621676216862169621706217162172621736217462175621766217762178621796218062181621826218362184621856218662187621886218962190621916219262193621946219562196621976219862199622006220162202622036220462205622066220762208622096221062211622126221362214622156221662217622186221962220622216222262223622246222562226622276222862229622306223162232622336223462235622366223762238622396224062241622426224362244622456224662247622486224962250622516225262253622546225562256622576225862259622606226162262622636226462265622666226762268622696227062271622726227362274622756227662277622786227962280622816228262283622846228562286622876228862289622906229162292622936229462295622966229762298622996230062301623026230362304623056230662307623086230962310623116231262313623146231562316623176231862319623206232162322623236232462325623266232762328623296233062331623326233362334623356233662337623386233962340623416234262343623446234562346623476234862349623506235162352623536235462355623566235762358623596236062361623626236362364623656236662367623686236962370623716237262373623746237562376623776237862379623806238162382623836238462385623866238762388623896239062391623926239362394623956239662397623986239962400624016240262403624046240562406624076240862409624106241162412624136241462415624166241762418624196242062421624226242362424624256242662427624286242962430624316243262433624346243562436624376243862439624406244162442624436244462445624466244762448624496245062451624526245362454624556245662457624586245962460624616246262463624646246562466624676246862469624706247162472624736247462475624766247762478624796248062481624826248362484624856248662487624886248962490624916249262493624946249562496624976249862499625006250162502625036250462505625066250762508625096251062511625126251362514625156251662517625186251962520625216252262523625246252562526625276252862529625306253162532625336253462535625366253762538625396254062541625426254362544625456254662547625486254962550625516255262553625546255562556625576255862559625606256162562625636256462565625666256762568625696257062571625726257362574625756257662577625786257962580625816258262583625846258562586625876258862589625906259162592625936259462595625966259762598625996260062601626026260362604626056260662607626086260962610626116261262613626146261562616626176261862619626206262162622626236262462625626266262762628626296263062631626326263362634626356263662637626386263962640626416264262643626446264562646626476264862649626506265162652626536265462655626566265762658626596266062661626626266362664626656266662667626686266962670626716267262673626746267562676626776267862679626806268162682626836268462685626866268762688626896269062691626926269362694626956269662697626986269962700627016270262703627046270562706627076270862709627106271162712627136271462715627166271762718627196272062721627226272362724627256272662727627286272962730627316273262733627346273562736627376273862739627406274162742627436274462745627466274762748627496275062751627526275362754627556275662757627586275962760627616276262763627646276562766627676276862769627706277162772627736277462775627766277762778627796278062781627826278362784627856278662787627886278962790627916279262793627946279562796627976279862799628006280162802628036280462805628066280762808628096281062811628126281362814628156281662817628186281962820628216282262823628246282562826628276282862829628306283162832628336283462835628366283762838628396284062841628426284362844628456284662847628486284962850628516285262853628546285562856628576285862859628606286162862628636286462865628666286762868628696287062871628726287362874628756287662877628786287962880628816288262883628846288562886628876288862889628906289162892628936289462895628966289762898628996290062901629026290362904629056290662907629086290962910629116291262913629146291562916629176291862919629206292162922629236292462925629266292762928629296293062931629326293362934629356293662937629386293962940629416294262943629446294562946629476294862949629506295162952629536295462955629566295762958629596296062961629626296362964629656296662967629686296962970629716297262973629746297562976629776297862979629806298162982629836298462985629866298762988629896299062991629926299362994629956299662997629986299963000630016300263003630046300563006630076300863009630106301163012630136301463015630166301763018630196302063021630226302363024630256302663027630286302963030630316303263033630346303563036630376303863039630406304163042630436304463045630466304763048630496305063051630526305363054630556305663057630586305963060630616306263063630646306563066630676306863069630706307163072630736307463075630766307763078630796308063081630826308363084630856308663087630886308963090630916309263093630946309563096630976309863099631006310163102631036310463105631066310763108631096311063111631126311363114631156311663117631186311963120631216312263123631246312563126631276312863129631306313163132631336313463135631366313763138631396314063141631426314363144631456314663147631486314963150631516315263153631546315563156631576315863159631606316163162631636316463165631666316763168631696317063171631726317363174631756317663177631786317963180631816318263183631846318563186631876318863189631906319163192631936319463195631966319763198631996320063201632026320363204632056320663207632086320963210632116321263213632146321563216632176321863219632206322163222632236322463225632266322763228632296323063231632326323363234632356323663237632386323963240632416324263243632446324563246632476324863249632506325163252632536325463255632566325763258632596326063261632626326363264632656326663267632686326963270632716327263273632746327563276632776327863279632806328163282632836328463285632866328763288632896329063291632926329363294632956329663297632986329963300633016330263303633046330563306633076330863309633106331163312633136331463315633166331763318633196332063321633226332363324633256332663327633286332963330633316333263333633346333563336633376333863339633406334163342633436334463345633466334763348633496335063351633526335363354633556335663357633586335963360633616336263363633646336563366633676336863369633706337163372633736337463375633766337763378633796338063381633826338363384633856338663387633886338963390633916339263393633946339563396633976339863399634006340163402634036340463405634066340763408634096341063411634126341363414634156341663417634186341963420634216342263423634246342563426634276342863429634306343163432634336343463435634366343763438634396344063441634426344363444634456344663447634486344963450634516345263453634546345563456634576345863459634606346163462634636346463465634666346763468634696347063471634726347363474634756347663477634786347963480634816348263483634846348563486634876348863489634906349163492634936349463495634966349763498634996350063501635026350363504635056350663507635086350963510635116351263513635146351563516635176351863519635206352163522635236352463525635266352763528635296353063531635326353363534635356353663537635386353963540635416354263543635446354563546635476354863549635506355163552635536355463555635566355763558635596356063561635626356363564635656356663567635686356963570635716357263573635746357563576635776357863579635806358163582635836358463585635866358763588635896359063591635926359363594635956359663597635986359963600636016360263603636046360563606636076360863609636106361163612636136361463615636166361763618636196362063621636226362363624636256362663627636286362963630636316363263633636346363563636636376363863639636406364163642636436364463645636466364763648636496365063651636526365363654636556365663657636586365963660636616366263663636646366563666636676366863669636706367163672636736367463675636766367763678636796368063681636826368363684636856368663687636886368963690636916369263693636946369563696636976369863699637006370163702637036370463705637066370763708637096371063711637126371363714637156371663717637186371963720637216372263723637246372563726637276372863729637306373163732637336373463735637366373763738637396374063741637426374363744637456374663747637486374963750637516375263753637546375563756637576375863759637606376163762637636376463765637666376763768637696377063771637726377363774637756377663777637786377963780637816378263783637846378563786637876378863789637906379163792637936379463795637966379763798637996380063801638026380363804638056380663807638086380963810638116381263813638146381563816638176381863819638206382163822638236382463825638266382763828638296383063831638326383363834638356383663837638386383963840638416384263843638446384563846638476384863849638506385163852638536385463855638566385763858638596386063861638626386363864638656386663867638686386963870638716387263873638746387563876638776387863879638806388163882638836388463885638866388763888638896389063891638926389363894638956389663897638986389963900639016390263903639046390563906639076390863909639106391163912639136391463915639166391763918639196392063921639226392363924639256392663927639286392963930639316393263933639346393563936639376393863939639406394163942639436394463945639466394763948639496395063951639526395363954639556395663957639586395963960639616396263963639646396563966639676396863969639706397163972639736397463975639766397763978639796398063981639826398363984639856398663987639886398963990639916399263993639946399563996639976399863999640006400164002640036400464005640066400764008640096401064011640126401364014640156401664017640186401964020640216402264023640246402564026640276402864029640306403164032640336403464035640366403764038640396404064041640426404364044640456404664047640486404964050640516405264053640546405564056640576405864059640606406164062640636406464065640666406764068640696407064071640726407364074640756407664077640786407964080640816408264083640846408564086640876408864089640906409164092640936409464095640966409764098640996410064101641026410364104641056410664107641086410964110641116411264113641146411564116641176411864119641206412164122641236412464125641266412764128641296413064131641326413364134641356413664137641386413964140641416414264143641446414564146641476414864149641506415164152641536415464155641566415764158641596416064161641626416364164641656416664167641686416964170641716417264173641746417564176641776417864179641806418164182641836418464185641866418764188641896419064191641926419364194641956419664197641986419964200642016420264203642046420564206642076420864209642106421164212642136421464215642166421764218642196422064221642226422364224642256422664227642286422964230642316423264233642346423564236642376423864239642406424164242642436424464245642466424764248642496425064251642526425364254642556425664257642586425964260642616426264263642646426564266642676426864269642706427164272642736427464275642766427764278642796428064281642826428364284642856428664287642886428964290642916429264293642946429564296642976429864299643006430164302643036430464305643066430764308643096431064311643126431364314643156431664317643186431964320643216432264323643246432564326643276432864329643306433164332643336433464335643366433764338643396434064341643426434364344643456434664347643486434964350643516435264353643546435564356643576435864359643606436164362643636436464365643666436764368643696437064371643726437364374643756437664377643786437964380643816438264383643846438564386643876438864389643906439164392643936439464395643966439764398643996440064401644026440364404644056440664407644086440964410644116441264413644146441564416644176441864419644206442164422644236442464425644266442764428644296443064431644326443364434644356443664437644386443964440644416444264443644446444564446644476444864449644506445164452644536445464455644566445764458644596446064461644626446364464644656446664467644686446964470644716447264473644746447564476644776447864479644806448164482644836448464485644866448764488644896449064491644926449364494644956449664497644986449964500645016450264503645046450564506645076450864509645106451164512645136451464515645166451764518645196452064521645226452364524645256452664527645286452964530645316453264533645346453564536645376453864539645406454164542645436454464545645466454764548645496455064551645526455364554645556455664557645586455964560645616456264563645646456564566645676456864569645706457164572645736457464575645766457764578645796458064581645826458364584645856458664587645886458964590645916459264593645946459564596645976459864599646006460164602646036460464605646066460764608646096461064611646126461364614646156461664617646186461964620646216462264623646246462564626646276462864629646306463164632646336463464635646366463764638646396464064641646426464364644646456464664647646486464964650646516465264653646546465564656646576465864659646606466164662646636466464665646666466764668646696467064671646726467364674646756467664677646786467964680646816468264683646846468564686646876468864689646906469164692646936469464695646966469764698646996470064701647026470364704647056470664707647086470964710647116471264713647146471564716647176471864719647206472164722647236472464725647266472764728647296473064731647326473364734647356473664737647386473964740647416474264743647446474564746647476474864749647506475164752647536475464755647566475764758647596476064761647626476364764647656476664767647686476964770647716477264773647746477564776647776477864779647806478164782647836478464785647866478764788647896479064791647926479364794647956479664797647986479964800648016480264803648046480564806648076480864809648106481164812648136481464815648166481764818648196482064821648226482364824648256482664827648286482964830648316483264833648346483564836648376483864839648406484164842648436484464845648466484764848648496485064851648526485364854648556485664857648586485964860648616486264863648646486564866648676486864869648706487164872648736487464875648766487764878648796488064881648826488364884648856488664887648886488964890648916489264893648946489564896648976489864899649006490164902649036490464905649066490764908649096491064911649126491364914649156491664917649186491964920649216492264923649246492564926649276492864929649306493164932649336493464935649366493764938649396494064941649426494364944649456494664947649486494964950649516495264953649546495564956649576495864959649606496164962649636496464965649666496764968649696497064971649726497364974649756497664977649786497964980649816498264983649846498564986649876498864989649906499164992649936499464995649966499764998649996500065001650026500365004650056500665007650086500965010650116501265013650146501565016650176501865019650206502165022650236502465025650266502765028650296503065031650326503365034650356503665037650386503965040650416504265043650446504565046650476504865049650506505165052650536505465055650566505765058650596506065061650626506365064650656506665067650686506965070650716507265073650746507565076650776507865079650806508165082650836508465085650866508765088650896509065091650926509365094650956509665097650986509965100651016510265103651046510565106651076510865109651106511165112651136511465115651166511765118651196512065121651226512365124651256512665127651286512965130651316513265133651346513565136651376513865139651406514165142651436514465145651466514765148651496515065151651526515365154651556515665157651586515965160651616516265163651646516565166651676516865169651706517165172651736517465175651766517765178651796518065181651826518365184651856518665187651886518965190651916519265193651946519565196651976519865199652006520165202652036520465205652066520765208652096521065211652126521365214652156521665217652186521965220652216522265223652246522565226652276522865229652306523165232652336523465235652366523765238652396524065241652426524365244652456524665247652486524965250652516525265253652546525565256652576525865259652606526165262652636526465265652666526765268652696527065271652726527365274652756527665277652786527965280652816528265283652846528565286652876528865289652906529165292652936529465295652966529765298652996530065301653026530365304653056530665307653086530965310653116531265313653146531565316653176531865319653206532165322653236532465325653266532765328653296533065331653326533365334653356533665337653386533965340653416534265343653446534565346653476534865349653506535165352653536535465355653566535765358653596536065361653626536365364653656536665367653686536965370653716537265373653746537565376653776537865379653806538165382653836538465385653866538765388653896539065391653926539365394653956539665397653986539965400654016540265403654046540565406654076540865409654106541165412654136541465415654166541765418654196542065421654226542365424654256542665427654286542965430654316543265433654346543565436654376543865439654406544165442654436544465445654466544765448654496545065451654526545365454654556545665457654586545965460654616546265463654646546565466654676546865469654706547165472654736547465475654766547765478654796548065481654826548365484654856548665487654886548965490654916549265493654946549565496654976549865499655006550165502655036550465505655066550765508655096551065511655126551365514655156551665517655186551965520655216552265523655246552565526655276552865529655306553165532655336553465535655366553765538655396554065541655426554365544655456554665547655486554965550655516555265553655546555565556655576555865559655606556165562655636556465565655666556765568655696557065571655726557365574655756557665577655786557965580655816558265583655846558565586655876558865589655906559165592655936559465595655966559765598655996560065601656026560365604656056560665607656086560965610656116561265613656146561565616656176561865619656206562165622656236562465625656266562765628656296563065631656326563365634656356563665637656386563965640656416564265643656446564565646656476564865649656506565165652656536565465655656566565765658656596566065661656626566365664656656566665667656686566965670656716567265673656746567565676656776567865679656806568165682656836568465685656866568765688656896569065691656926569365694656956569665697656986569965700657016570265703657046570565706657076570865709657106571165712657136571465715657166571765718657196572065721657226572365724657256572665727657286572965730657316573265733657346573565736657376573865739657406574165742657436574465745657466574765748657496575065751657526575365754657556575665757657586575965760657616576265763657646576565766657676576865769657706577165772657736577465775657766577765778657796578065781657826578365784657856578665787657886578965790657916579265793657946579565796657976579865799658006580165802658036580465805658066580765808658096581065811658126581365814658156581665817658186581965820658216582265823658246582565826658276582865829658306583165832658336583465835658366583765838658396584065841658426584365844658456584665847658486584965850658516585265853658546585565856658576585865859658606586165862658636586465865658666586765868658696587065871658726587365874658756587665877658786587965880658816588265883658846588565886658876588865889658906589165892658936589465895658966589765898658996590065901659026590365904659056590665907659086590965910659116591265913659146591565916659176591865919659206592165922659236592465925659266592765928659296593065931659326593365934659356593665937659386593965940659416594265943659446594565946659476594865949659506595165952659536595465955659566595765958659596596065961659626596365964659656596665967659686596965970659716597265973659746597565976659776597865979659806598165982659836598465985659866598765988659896599065991659926599365994659956599665997659986599966000660016600266003660046600566006660076600866009660106601166012660136601466015660166601766018660196602066021660226602366024660256602666027660286602966030660316603266033660346603566036660376603866039660406604166042660436604466045660466604766048660496605066051660526605366054660556605666057660586605966060660616606266063660646606566066660676606866069660706607166072660736607466075660766607766078660796608066081660826608366084660856608666087660886608966090660916609266093660946609566096660976609866099661006610166102661036610466105661066610766108661096611066111661126611366114661156611666117661186611966120661216612266123661246612566126661276612866129661306613166132661336613466135661366613766138661396614066141661426614366144661456614666147661486614966150661516615266153661546615566156661576615866159661606616166162661636616466165661666616766168661696617066171661726617366174661756617666177661786617966180661816618266183661846618566186661876618866189661906619166192661936619466195661966619766198661996620066201662026620366204662056620666207662086620966210662116621266213662146621566216662176621866219662206622166222662236622466225662266622766228662296623066231662326623366234662356623666237662386623966240662416624266243662446624566246662476624866249662506625166252662536625466255662566625766258662596626066261662626626366264662656626666267662686626966270662716627266273662746627566276662776627866279662806628166282662836628466285662866628766288662896629066291662926629366294662956629666297662986629966300663016630266303663046630566306663076630866309663106631166312663136631466315663166631766318663196632066321663226632366324663256632666327663286632966330663316633266333663346633566336663376633866339663406634166342663436634466345663466634766348663496635066351663526635366354663556635666357663586635966360663616636266363663646636566366663676636866369663706637166372663736637466375663766637766378663796638066381663826638366384663856638666387663886638966390663916639266393663946639566396663976639866399664006640166402664036640466405664066640766408664096641066411664126641366414664156641666417664186641966420664216642266423664246642566426664276642866429664306643166432664336643466435664366643766438664396644066441664426644366444664456644666447664486644966450664516645266453664546645566456664576645866459664606646166462664636646466465664666646766468664696647066471664726647366474664756647666477664786647966480664816648266483664846648566486664876648866489664906649166492664936649466495664966649766498664996650066501665026650366504665056650666507665086650966510665116651266513665146651566516665176651866519665206652166522665236652466525665266652766528665296653066531665326653366534665356653666537665386653966540665416654266543665446654566546665476654866549665506655166552665536655466555665566655766558665596656066561665626656366564665656656666567665686656966570665716657266573665746657566576665776657866579665806658166582665836658466585665866658766588665896659066591665926659366594665956659666597665986659966600666016660266603666046660566606666076660866609666106661166612666136661466615666166661766618666196662066621666226662366624666256662666627666286662966630666316663266633666346663566636666376663866639666406664166642666436664466645666466664766648666496665066651666526665366654666556665666657666586665966660666616666266663666646666566666666676666866669666706667166672666736667466675666766667766678666796668066681666826668366684666856668666687666886668966690666916669266693666946669566696666976669866699667006670166702667036670466705667066670766708667096671066711667126671366714667156671666717667186671966720667216672266723667246672566726667276672866729667306673166732667336673466735667366673766738667396674066741667426674366744667456674666747667486674966750667516675266753667546675566756667576675866759667606676166762667636676466765667666676766768667696677066771667726677366774667756677666777667786677966780667816678266783667846678566786667876678866789667906679166792667936679466795667966679766798667996680066801668026680366804668056680666807668086680966810668116681266813668146681566816668176681866819668206682166822668236682466825668266682766828668296683066831668326683366834668356683666837668386683966840668416684266843668446684566846668476684866849668506685166852668536685466855668566685766858668596686066861668626686366864668656686666867668686686966870668716687266873668746687566876668776687866879668806688166882668836688466885668866688766888668896689066891668926689366894668956689666897668986689966900669016690266903669046690566906669076690866909669106691166912669136691466915669166691766918669196692066921669226692366924669256692666927669286692966930669316693266933669346693566936669376693866939669406694166942669436694466945669466694766948669496695066951669526695366954669556695666957669586695966960669616696266963669646696566966669676696866969669706697166972669736697466975669766697766978669796698066981669826698366984669856698666987669886698966990669916699266993669946699566996669976699866999670006700167002670036700467005670066700767008670096701067011670126701367014670156701667017670186701967020670216702267023670246702567026670276702867029670306703167032670336703467035670366703767038670396704067041670426704367044670456704667047670486704967050670516705267053670546705567056670576705867059670606706167062670636706467065670666706767068670696707067071670726707367074670756707667077670786707967080670816708267083670846708567086670876708867089670906709167092670936709467095670966709767098670996710067101671026710367104671056710667107671086710967110671116711267113671146711567116671176711867119671206712167122671236712467125671266712767128671296713067131671326713367134671356713667137671386713967140671416714267143671446714567146671476714867149671506715167152671536715467155671566715767158671596716067161671626716367164671656716667167671686716967170671716717267173671746717567176671776717867179671806718167182671836718467185671866718767188671896719067191671926719367194671956719667197671986719967200672016720267203672046720567206672076720867209672106721167212672136721467215672166721767218672196722067221672226722367224672256722667227672286722967230672316723267233672346723567236672376723867239672406724167242672436724467245672466724767248672496725067251672526725367254672556725667257672586725967260672616726267263672646726567266672676726867269672706727167272672736727467275672766727767278672796728067281672826728367284672856728667287672886728967290672916729267293672946729567296672976729867299673006730167302673036730467305673066730767308673096731067311673126731367314673156731667317673186731967320673216732267323673246732567326673276732867329673306733167332673336733467335673366733767338673396734067341673426734367344673456734667347673486734967350673516735267353673546735567356673576735867359673606736167362673636736467365673666736767368673696737067371673726737367374673756737667377673786737967380673816738267383673846738567386673876738867389673906739167392673936739467395673966739767398673996740067401674026740367404674056740667407674086740967410674116741267413674146741567416674176741867419674206742167422674236742467425674266742767428674296743067431674326743367434674356743667437674386743967440674416744267443674446744567446674476744867449674506745167452674536745467455674566745767458674596746067461674626746367464674656746667467674686746967470674716747267473674746747567476674776747867479674806748167482674836748467485674866748767488674896749067491674926749367494674956749667497674986749967500675016750267503675046750567506675076750867509675106751167512675136751467515675166751767518675196752067521675226752367524675256752667527675286752967530675316753267533675346753567536675376753867539675406754167542675436754467545675466754767548675496755067551675526755367554675556755667557675586755967560675616756267563675646756567566675676756867569675706757167572675736757467575675766757767578675796758067581675826758367584675856758667587675886758967590675916759267593675946759567596675976759867599676006760167602676036760467605676066760767608676096761067611676126761367614676156761667617676186761967620676216762267623676246762567626676276762867629676306763167632676336763467635676366763767638676396764067641676426764367644676456764667647676486764967650676516765267653676546765567656676576765867659676606766167662676636766467665676666766767668676696767067671676726767367674676756767667677676786767967680676816768267683676846768567686676876768867689676906769167692676936769467695676966769767698676996770067701677026770367704677056770667707677086770967710677116771267713677146771567716677176771867719677206772167722677236772467725677266772767728677296773067731677326773367734677356773667737677386773967740677416774267743677446774567746677476774867749677506775167752677536775467755677566775767758677596776067761677626776367764677656776667767677686776967770677716777267773677746777567776677776777867779677806778167782677836778467785677866778767788677896779067791677926779367794677956779667797677986779967800678016780267803678046780567806678076780867809678106781167812678136781467815678166781767818678196782067821678226782367824678256782667827678286782967830678316783267833678346783567836678376783867839678406784167842678436784467845678466784767848678496785067851678526785367854678556785667857678586785967860678616786267863678646786567866678676786867869678706787167872678736787467875678766787767878678796788067881678826788367884678856788667887678886788967890678916789267893678946789567896678976789867899679006790167902679036790467905679066790767908679096791067911679126791367914679156791667917679186791967920679216792267923679246792567926679276792867929679306793167932679336793467935679366793767938679396794067941679426794367944679456794667947679486794967950679516795267953679546795567956679576795867959679606796167962679636796467965679666796767968679696797067971679726797367974679756797667977679786797967980679816798267983679846798567986679876798867989679906799167992679936799467995679966799767998679996800068001680026800368004680056800668007680086800968010680116801268013680146801568016680176801868019680206802168022680236802468025680266802768028680296803068031680326803368034680356803668037680386803968040680416804268043680446804568046680476804868049680506805168052680536805468055680566805768058680596806068061680626806368064680656806668067680686806968070680716807268073680746807568076680776807868079680806808168082680836808468085680866808768088680896809068091680926809368094680956809668097680986809968100681016810268103681046810568106681076810868109681106811168112681136811468115681166811768118681196812068121681226812368124681256812668127681286812968130681316813268133681346813568136681376813868139681406814168142681436814468145681466814768148681496815068151681526815368154681556815668157681586815968160681616816268163681646816568166681676816868169681706817168172681736817468175681766817768178681796818068181681826818368184681856818668187681886818968190681916819268193681946819568196681976819868199682006820168202682036820468205682066820768208682096821068211682126821368214682156821668217682186821968220682216822268223682246822568226682276822868229682306823168232682336823468235682366823768238682396824068241682426824368244682456824668247682486824968250682516825268253682546825568256682576825868259682606826168262682636826468265682666826768268682696827068271682726827368274682756827668277682786827968280682816828268283682846828568286682876828868289682906829168292682936829468295682966829768298682996830068301683026830368304683056830668307683086830968310683116831268313683146831568316683176831868319683206832168322683236832468325683266832768328683296833068331683326833368334683356833668337683386833968340683416834268343683446834568346683476834868349683506835168352683536835468355683566835768358683596836068361683626836368364683656836668367683686836968370683716837268373683746837568376683776837868379683806838168382683836838468385683866838768388683896839068391683926839368394683956839668397683986839968400684016840268403684046840568406684076840868409684106841168412684136841468415684166841768418684196842068421684226842368424684256842668427684286842968430684316843268433684346843568436684376843868439684406844168442684436844468445684466844768448684496845068451684526845368454684556845668457684586845968460684616846268463684646846568466684676846868469684706847168472684736847468475684766847768478684796848068481684826848368484684856848668487684886848968490684916849268493684946849568496684976849868499685006850168502685036850468505685066850768508685096851068511685126851368514685156851668517685186851968520685216852268523685246852568526685276852868529685306853168532685336853468535685366853768538685396854068541685426854368544685456854668547685486854968550685516855268553685546855568556685576855868559685606856168562685636856468565685666856768568685696857068571685726857368574685756857668577685786857968580685816858268583685846858568586685876858868589685906859168592685936859468595685966859768598685996860068601686026860368604686056860668607686086860968610686116861268613686146861568616686176861868619686206862168622686236862468625686266862768628686296863068631686326863368634686356863668637686386863968640686416864268643686446864568646686476864868649686506865168652686536865468655686566865768658686596866068661686626866368664686656866668667686686866968670686716867268673686746867568676686776867868679686806868168682686836868468685686866868768688686896869068691686926869368694686956869668697686986869968700687016870268703687046870568706687076870868709687106871168712687136871468715687166871768718687196872068721687226872368724687256872668727687286872968730687316873268733687346873568736687376873868739687406874168742687436874468745687466874768748687496875068751687526875368754687556875668757687586875968760687616876268763687646876568766687676876868769687706877168772687736877468775687766877768778687796878068781687826878368784687856878668787687886878968790687916879268793687946879568796687976879868799688006880168802688036880468805688066880768808688096881068811688126881368814688156881668817688186881968820688216882268823688246882568826688276882868829688306883168832688336883468835688366883768838688396884068841688426884368844688456884668847688486884968850688516885268853688546885568856688576885868859688606886168862688636886468865688666886768868688696887068871688726887368874688756887668877688786887968880688816888268883688846888568886688876888868889688906889168892688936889468895688966889768898688996890068901689026890368904689056890668907689086890968910689116891268913689146891568916689176891868919689206892168922689236892468925689266892768928689296893068931689326893368934689356893668937689386893968940689416894268943689446894568946689476894868949689506895168952689536895468955689566895768958689596896068961689626896368964689656896668967689686896968970689716897268973689746897568976689776897868979689806898168982689836898468985689866898768988689896899068991689926899368994689956899668997689986899969000690016900269003690046900569006690076900869009690106901169012690136901469015690166901769018690196902069021690226902369024690256902669027690286902969030690316903269033690346903569036690376903869039690406904169042690436904469045690466904769048690496905069051690526905369054690556905669057690586905969060690616906269063690646906569066690676906869069690706907169072690736907469075690766907769078690796908069081690826908369084690856908669087690886908969090690916909269093690946909569096690976909869099691006910169102691036910469105691066910769108691096911069111691126911369114691156911669117691186911969120691216912269123691246912569126691276912869129691306913169132691336913469135691366913769138691396914069141691426914369144691456914669147691486914969150691516915269153691546915569156691576915869159691606916169162691636916469165691666916769168691696917069171691726917369174691756917669177691786917969180691816918269183691846918569186691876918869189691906919169192691936919469195691966919769198691996920069201692026920369204692056920669207692086920969210692116921269213692146921569216692176921869219692206922169222692236922469225692266922769228692296923069231692326923369234692356923669237692386923969240692416924269243692446924569246692476924869249692506925169252692536925469255692566925769258692596926069261692626926369264692656926669267692686926969270692716927269273692746927569276692776927869279692806928169282692836928469285692866928769288692896929069291692926929369294692956929669297692986929969300693016930269303693046930569306693076930869309693106931169312693136931469315693166931769318693196932069321693226932369324693256932669327693286932969330693316933269333693346933569336693376933869339693406934169342693436934469345693466934769348693496935069351693526935369354693556935669357693586935969360693616936269363693646936569366693676936869369693706937169372693736937469375693766937769378693796938069381693826938369384693856938669387693886938969390693916939269393693946939569396693976939869399694006940169402694036940469405694066940769408694096941069411694126941369414694156941669417694186941969420694216942269423694246942569426694276942869429694306943169432694336943469435694366943769438694396944069441694426944369444694456944669447694486944969450694516945269453694546945569456694576945869459694606946169462694636946469465694666946769468694696947069471694726947369474694756947669477694786947969480694816948269483694846948569486694876948869489694906949169492694936949469495694966949769498694996950069501695026950369504695056950669507695086950969510695116951269513695146951569516695176951869519695206952169522695236952469525695266952769528695296953069531695326953369534695356953669537695386953969540695416954269543695446954569546695476954869549695506955169552695536955469555695566955769558695596956069561695626956369564695656956669567695686956969570695716957269573695746957569576695776957869579695806958169582695836958469585695866958769588695896959069591695926959369594695956959669597695986959969600696016960269603696046960569606696076960869609696106961169612696136961469615696166961769618696196962069621696226962369624696256962669627696286962969630696316963269633696346963569636696376963869639696406964169642696436964469645696466964769648696496965069651696526965369654696556965669657696586965969660696616966269663696646966569666696676966869669696706967169672696736967469675696766967769678696796968069681696826968369684696856968669687696886968969690696916969269693696946969569696696976969869699697006970169702697036970469705697066970769708697096971069711697126971369714697156971669717697186971969720697216972269723697246972569726697276972869729697306973169732697336973469735697366973769738697396974069741697426974369744697456974669747697486974969750697516975269753697546975569756697576975869759697606976169762697636976469765697666976769768697696977069771697726977369774697756977669777697786977969780697816978269783697846978569786697876978869789697906979169792697936979469795697966979769798697996980069801698026980369804698056980669807698086980969810698116981269813698146981569816698176981869819698206982169822698236982469825698266982769828698296983069831698326983369834698356983669837698386983969840698416984269843698446984569846698476984869849698506985169852698536985469855698566985769858698596986069861698626986369864698656986669867698686986969870698716987269873698746987569876698776987869879698806988169882698836988469885698866988769888698896989069891698926989369894698956989669897698986989969900699016990269903699046990569906699076990869909699106991169912699136991469915699166991769918699196992069921699226992369924699256992669927699286992969930699316993269933699346993569936699376993869939699406994169942699436994469945699466994769948699496995069951699526995369954699556995669957699586995969960699616996269963699646996569966699676996869969699706997169972699736997469975699766997769978699796998069981699826998369984699856998669987699886998969990699916999269993699946999569996699976999869999700007000170002700037000470005700067000770008700097001070011700127001370014700157001670017700187001970020700217002270023700247002570026700277002870029700307003170032700337003470035700367003770038700397004070041700427004370044700457004670047700487004970050700517005270053700547005570056700577005870059700607006170062700637006470065700667006770068700697007070071700727007370074700757007670077700787007970080700817008270083700847008570086700877008870089700907009170092700937009470095700967009770098700997010070101701027010370104701057010670107701087010970110701117011270113701147011570116701177011870119701207012170122701237012470125701267012770128701297013070131701327013370134701357013670137701387013970140701417014270143701447014570146701477014870149701507015170152701537015470155701567015770158701597016070161701627016370164701657016670167701687016970170701717017270173701747017570176701777017870179701807018170182701837018470185701867018770188701897019070191701927019370194701957019670197701987019970200702017020270203702047020570206702077020870209702107021170212702137021470215702167021770218702197022070221702227022370224702257022670227702287022970230702317023270233702347023570236702377023870239702407024170242702437024470245702467024770248702497025070251702527025370254702557025670257702587025970260702617026270263702647026570266702677026870269702707027170272702737027470275702767027770278702797028070281702827028370284702857028670287702887028970290702917029270293702947029570296702977029870299703007030170302703037030470305703067030770308703097031070311703127031370314703157031670317703187031970320703217032270323703247032570326703277032870329703307033170332703337033470335703367033770338703397034070341703427034370344703457034670347703487034970350703517035270353703547035570356703577035870359703607036170362703637036470365703667036770368703697037070371703727037370374703757037670377703787037970380703817038270383703847038570386703877038870389703907039170392703937039470395703967039770398703997040070401704027040370404704057040670407704087040970410704117041270413704147041570416704177041870419704207042170422704237042470425704267042770428704297043070431704327043370434704357043670437704387043970440704417044270443704447044570446704477044870449704507045170452704537045470455704567045770458704597046070461704627046370464704657046670467704687046970470704717047270473704747047570476704777047870479704807048170482704837048470485704867048770488704897049070491704927049370494704957049670497704987049970500705017050270503705047050570506705077050870509705107051170512705137051470515705167051770518705197052070521705227052370524705257052670527705287052970530705317053270533705347053570536705377053870539705407054170542705437054470545705467054770548705497055070551705527055370554705557055670557705587055970560705617056270563705647056570566705677056870569705707057170572705737057470575705767057770578705797058070581705827058370584705857058670587705887058970590705917059270593705947059570596705977059870599706007060170602706037060470605706067060770608706097061070611706127061370614706157061670617706187061970620706217062270623706247062570626706277062870629706307063170632706337063470635706367063770638706397064070641706427064370644706457064670647706487064970650706517065270653706547065570656706577065870659706607066170662706637066470665706667066770668706697067070671706727067370674706757067670677706787067970680706817068270683706847068570686706877068870689706907069170692706937069470695706967069770698706997070070701707027070370704707057070670707707087070970710707117071270713707147071570716707177071870719707207072170722707237072470725707267072770728707297073070731707327073370734707357073670737707387073970740707417074270743707447074570746707477074870749707507075170752707537075470755707567075770758707597076070761707627076370764707657076670767707687076970770707717077270773707747077570776707777077870779707807078170782707837078470785707867078770788707897079070791707927079370794707957079670797707987079970800708017080270803708047080570806708077080870809708107081170812708137081470815708167081770818708197082070821708227082370824708257082670827708287082970830708317083270833708347083570836708377083870839708407084170842708437084470845708467084770848708497085070851708527085370854708557085670857708587085970860708617086270863708647086570866708677086870869708707087170872708737087470875708767087770878708797088070881708827088370884708857088670887708887088970890708917089270893708947089570896708977089870899709007090170902709037090470905709067090770908709097091070911709127091370914709157091670917709187091970920709217092270923709247092570926709277092870929709307093170932709337093470935709367093770938709397094070941709427094370944709457094670947709487094970950709517095270953709547095570956709577095870959709607096170962709637096470965709667096770968709697097070971709727097370974709757097670977709787097970980709817098270983709847098570986709877098870989709907099170992709937099470995709967099770998709997100071001710027100371004710057100671007710087100971010710117101271013710147101571016710177101871019710207102171022710237102471025710267102771028710297103071031710327103371034710357103671037710387103971040710417104271043710447104571046710477104871049710507105171052710537105471055710567105771058710597106071061710627106371064710657106671067710687106971070710717107271073710747107571076710777107871079710807108171082710837108471085710867108771088710897109071091710927109371094710957109671097710987109971100711017110271103711047110571106711077110871109711107111171112711137111471115711167111771118711197112071121711227112371124711257112671127711287112971130711317113271133711347113571136711377113871139711407114171142711437114471145711467114771148711497115071151711527115371154711557115671157711587115971160711617116271163711647116571166711677116871169711707117171172711737117471175711767117771178711797118071181711827118371184711857118671187711887118971190711917119271193711947119571196711977119871199712007120171202712037120471205712067120771208712097121071211712127121371214712157121671217712187121971220712217122271223712247122571226712277122871229712307123171232712337123471235712367123771238712397124071241712427124371244712457124671247712487124971250712517125271253712547125571256712577125871259712607126171262712637126471265712667126771268712697127071271712727127371274712757127671277712787127971280712817128271283712847128571286712877128871289712907129171292712937129471295712967129771298712997130071301713027130371304713057130671307713087130971310713117131271313713147131571316713177131871319713207132171322713237132471325713267132771328713297133071331713327133371334713357133671337713387133971340713417134271343713447134571346713477134871349713507135171352713537135471355713567135771358713597136071361713627136371364713657136671367713687136971370713717137271373713747137571376713777137871379713807138171382713837138471385713867138771388713897139071391713927139371394713957139671397713987139971400714017140271403714047140571406714077140871409714107141171412714137141471415714167141771418714197142071421714227142371424714257142671427714287142971430714317143271433714347143571436714377143871439714407144171442714437144471445714467144771448714497145071451714527145371454714557145671457714587145971460714617146271463714647146571466714677146871469714707147171472714737147471475714767147771478714797148071481714827148371484714857148671487714887148971490714917149271493714947149571496714977149871499715007150171502715037150471505715067150771508715097151071511715127151371514715157151671517715187151971520715217152271523715247152571526715277152871529715307153171532715337153471535715367153771538715397154071541715427154371544715457154671547715487154971550715517155271553715547155571556715577155871559715607156171562715637156471565715667156771568715697157071571715727157371574715757157671577715787157971580715817158271583715847158571586715877158871589715907159171592715937159471595715967159771598715997160071601716027160371604716057160671607716087160971610716117161271613716147161571616716177161871619716207162171622716237162471625716267162771628716297163071631716327163371634716357163671637716387163971640716417164271643716447164571646716477164871649716507165171652716537165471655716567165771658716597166071661716627166371664716657166671667716687166971670716717167271673716747167571676716777167871679716807168171682716837168471685716867168771688716897169071691716927169371694716957169671697716987169971700717017170271703717047170571706717077170871709717107171171712717137171471715717167171771718717197172071721717227172371724717257172671727717287172971730717317173271733717347173571736717377173871739717407174171742717437174471745717467174771748717497175071751717527175371754717557175671757717587175971760717617176271763717647176571766717677176871769717707177171772717737177471775717767177771778717797178071781717827178371784717857178671787717887178971790717917179271793717947179571796717977179871799718007180171802718037180471805718067180771808718097181071811718127181371814718157181671817718187181971820718217182271823718247182571826718277182871829718307183171832718337183471835718367183771838718397184071841718427184371844718457184671847718487184971850718517185271853718547185571856718577185871859718607186171862718637186471865718667186771868718697187071871718727187371874718757187671877718787187971880718817188271883718847188571886718877188871889718907189171892718937189471895718967189771898718997190071901719027190371904719057190671907719087190971910719117191271913719147191571916719177191871919719207192171922719237192471925719267192771928719297193071931719327193371934719357193671937719387193971940719417194271943719447194571946719477194871949719507195171952719537195471955719567195771958719597196071961719627196371964719657196671967719687196971970719717197271973719747197571976719777197871979719807198171982719837198471985719867198771988719897199071991719927199371994719957199671997719987199972000720017200272003720047200572006720077200872009720107201172012720137201472015720167201772018720197202072021720227202372024720257202672027720287202972030720317203272033720347203572036720377203872039720407204172042720437204472045720467204772048720497205072051720527205372054720557205672057720587205972060720617206272063720647206572066720677206872069720707207172072720737207472075720767207772078720797208072081720827208372084720857208672087720887208972090720917209272093720947209572096720977209872099721007210172102721037210472105721067210772108721097211072111721127211372114721157211672117721187211972120721217212272123721247212572126721277212872129721307213172132721337213472135721367213772138721397214072141721427214372144721457214672147721487214972150721517215272153721547215572156721577215872159721607216172162721637216472165721667216772168721697217072171721727217372174721757217672177721787217972180721817218272183721847218572186721877218872189721907219172192721937219472195721967219772198721997220072201722027220372204722057220672207722087220972210722117221272213722147221572216722177221872219722207222172222722237222472225722267222772228722297223072231722327223372234722357223672237722387223972240722417224272243722447224572246722477224872249722507225172252722537225472255722567225772258722597226072261722627226372264722657226672267722687226972270722717227272273722747227572276722777227872279722807228172282722837228472285722867228772288722897229072291722927229372294722957229672297722987229972300723017230272303723047230572306723077230872309723107231172312723137231472315723167231772318723197232072321723227232372324723257232672327723287232972330723317233272333723347233572336723377233872339723407234172342723437234472345723467234772348723497235072351723527235372354723557235672357723587235972360723617236272363723647236572366723677236872369723707237172372723737237472375723767237772378723797238072381723827238372384723857238672387723887238972390723917239272393723947239572396723977239872399724007240172402724037240472405724067240772408724097241072411724127241372414724157241672417724187241972420724217242272423724247242572426724277242872429724307243172432724337243472435724367243772438724397244072441724427244372444724457244672447724487244972450724517245272453724547245572456724577245872459724607246172462724637246472465724667246772468724697247072471724727247372474724757247672477724787247972480724817248272483724847248572486724877248872489724907249172492724937249472495724967249772498724997250072501725027250372504725057250672507725087250972510725117251272513725147251572516725177251872519725207252172522725237252472525725267252772528725297253072531725327253372534725357253672537725387253972540725417254272543725447254572546725477254872549725507255172552725537255472555725567255772558725597256072561725627256372564725657256672567725687256972570725717257272573725747257572576725777257872579725807258172582725837258472585725867258772588725897259072591725927259372594725957259672597725987259972600726017260272603726047260572606726077260872609726107261172612726137261472615726167261772618726197262072621726227262372624726257262672627726287262972630726317263272633726347263572636726377263872639726407264172642726437264472645726467264772648726497265072651726527265372654726557265672657726587265972660726617266272663726647266572666726677266872669726707267172672726737267472675726767267772678726797268072681726827268372684726857268672687726887268972690726917269272693726947269572696726977269872699727007270172702727037270472705727067270772708727097271072711727127271372714727157271672717727187271972720727217272272723727247272572726727277272872729727307273172732727337273472735727367273772738727397274072741727427274372744727457274672747727487274972750727517275272753727547275572756727577275872759727607276172762727637276472765727667276772768727697277072771727727277372774727757277672777727787277972780727817278272783727847278572786727877278872789727907279172792727937279472795727967279772798727997280072801728027280372804728057280672807728087280972810728117281272813728147281572816728177281872819728207282172822728237282472825728267282772828728297283072831728327283372834728357283672837728387283972840728417284272843728447284572846728477284872849728507285172852728537285472855728567285772858728597286072861728627286372864728657286672867728687286972870728717287272873728747287572876728777287872879728807288172882728837288472885728867288772888728897289072891728927289372894728957289672897728987289972900729017290272903729047290572906729077290872909729107291172912729137291472915729167291772918729197292072921729227292372924729257292672927729287292972930729317293272933729347293572936729377293872939729407294172942729437294472945729467294772948729497295072951729527295372954729557295672957729587295972960729617296272963729647296572966729677296872969729707297172972729737297472975729767297772978729797298072981729827298372984729857298672987729887298972990729917299272993729947299572996729977299872999730007300173002730037300473005730067300773008730097301073011730127301373014730157301673017730187301973020730217302273023730247302573026730277302873029730307303173032730337303473035730367303773038730397304073041730427304373044730457304673047730487304973050730517305273053730547305573056730577305873059730607306173062730637306473065730667306773068730697307073071730727307373074730757307673077730787307973080730817308273083730847308573086730877308873089730907309173092730937309473095730967309773098730997310073101731027310373104731057310673107731087310973110731117311273113731147311573116731177311873119731207312173122731237312473125731267312773128731297313073131731327313373134731357313673137731387313973140731417314273143731447314573146731477314873149731507315173152731537315473155731567315773158731597316073161731627316373164731657316673167731687316973170731717317273173731747317573176731777317873179731807318173182731837318473185731867318773188731897319073191731927319373194731957319673197731987319973200732017320273203732047320573206732077320873209732107321173212732137321473215732167321773218732197322073221732227322373224732257322673227732287322973230732317323273233732347323573236732377323873239732407324173242732437324473245732467324773248732497325073251732527325373254732557325673257732587325973260732617326273263732647326573266732677326873269732707327173272732737327473275732767327773278732797328073281732827328373284732857328673287732887328973290732917329273293732947329573296732977329873299733007330173302733037330473305733067330773308733097331073311733127331373314733157331673317733187331973320733217332273323733247332573326733277332873329733307333173332733337333473335733367333773338733397334073341733427334373344733457334673347733487334973350733517335273353733547335573356733577335873359733607336173362733637336473365733667336773368733697337073371733727337373374733757337673377733787337973380733817338273383733847338573386733877338873389733907339173392733937339473395733967339773398733997340073401734027340373404734057340673407734087340973410734117341273413734147341573416734177341873419734207342173422734237342473425734267342773428734297343073431734327343373434734357343673437734387343973440734417344273443734447344573446734477344873449734507345173452734537345473455734567345773458734597346073461734627346373464734657346673467734687346973470734717347273473734747347573476734777347873479734807348173482734837348473485734867348773488734897349073491734927349373494734957349673497734987349973500735017350273503735047350573506735077350873509735107351173512735137351473515735167351773518735197352073521735227352373524735257352673527735287352973530735317353273533735347353573536735377353873539735407354173542735437354473545735467354773548735497355073551735527355373554735557355673557735587355973560735617356273563735647356573566735677356873569735707357173572735737357473575735767357773578735797358073581735827358373584735857358673587735887358973590735917359273593735947359573596735977359873599736007360173602736037360473605736067360773608736097361073611736127361373614736157361673617736187361973620736217362273623736247362573626736277362873629736307363173632736337363473635736367363773638736397364073641736427364373644736457364673647736487364973650736517365273653736547365573656736577365873659736607366173662736637366473665736667366773668736697367073671736727367373674736757367673677736787367973680736817368273683736847368573686736877368873689736907369173692736937369473695736967369773698736997370073701737027370373704737057370673707737087370973710737117371273713737147371573716737177371873719737207372173722737237372473725737267372773728737297373073731737327373373734737357373673737737387373973740737417374273743737447374573746737477374873749737507375173752737537375473755737567375773758737597376073761737627376373764737657376673767737687376973770737717377273773737747377573776737777377873779737807378173782737837378473785737867378773788737897379073791737927379373794737957379673797737987379973800738017380273803738047380573806738077380873809738107381173812738137381473815738167381773818738197382073821738227382373824738257382673827738287382973830738317383273833738347383573836738377383873839738407384173842738437384473845738467384773848738497385073851738527385373854738557385673857738587385973860738617386273863738647386573866738677386873869738707387173872738737387473875738767387773878738797388073881738827388373884738857388673887738887388973890738917389273893738947389573896738977389873899739007390173902739037390473905739067390773908739097391073911739127391373914739157391673917739187391973920739217392273923739247392573926739277392873929739307393173932739337393473935739367393773938739397394073941739427394373944739457394673947739487394973950739517395273953739547395573956739577395873959739607396173962739637396473965739667396773968739697397073971739727397373974739757397673977739787397973980739817398273983739847398573986739877398873989739907399173992739937399473995739967399773998739997400074001740027400374004740057400674007740087400974010740117401274013740147401574016740177401874019740207402174022740237402474025740267402774028740297403074031740327403374034740357403674037740387403974040740417404274043740447404574046740477404874049740507405174052740537405474055740567405774058740597406074061740627406374064740657406674067740687406974070740717407274073740747407574076740777407874079740807408174082740837408474085740867408774088740897409074091740927409374094740957409674097740987409974100741017410274103741047410574106741077410874109741107411174112741137411474115741167411774118741197412074121741227412374124741257412674127741287412974130741317413274133741347413574136741377413874139741407414174142741437414474145741467414774148741497415074151741527415374154741557415674157741587415974160741617416274163741647416574166741677416874169741707417174172741737417474175741767417774178741797418074181741827418374184741857418674187741887418974190741917419274193741947419574196741977419874199742007420174202742037420474205742067420774208742097421074211742127421374214742157421674217742187421974220742217422274223742247422574226742277422874229742307423174232742337423474235742367423774238742397424074241742427424374244742457424674247742487424974250742517425274253742547425574256742577425874259742607426174262742637426474265742667426774268742697427074271742727427374274742757427674277742787427974280742817428274283742847428574286742877428874289742907429174292742937429474295742967429774298742997430074301743027430374304743057430674307743087430974310743117431274313743147431574316743177431874319743207432174322743237432474325743267432774328743297433074331743327433374334743357433674337743387433974340743417434274343743447434574346743477434874349743507435174352743537435474355743567435774358743597436074361743627436374364743657436674367743687436974370743717437274373743747437574376743777437874379743807438174382743837438474385743867438774388743897439074391743927439374394743957439674397743987439974400744017440274403744047440574406744077440874409744107441174412744137441474415744167441774418744197442074421744227442374424744257442674427744287442974430744317443274433744347443574436744377443874439744407444174442744437444474445744467444774448744497445074451744527445374454744557445674457744587445974460744617446274463744647446574466744677446874469744707447174472744737447474475744767447774478744797448074481744827448374484744857448674487744887448974490744917449274493744947449574496744977449874499745007450174502745037450474505745067450774508745097451074511745127451374514745157451674517745187451974520745217452274523745247452574526745277452874529745307453174532745337453474535745367453774538745397454074541745427454374544745457454674547745487454974550745517455274553745547455574556745577455874559745607456174562745637456474565745667456774568745697457074571745727457374574745757457674577745787457974580745817458274583745847458574586745877458874589745907459174592745937459474595745967459774598745997460074601746027460374604746057460674607746087460974610746117461274613746147461574616746177461874619746207462174622746237462474625746267462774628746297463074631746327463374634746357463674637746387463974640746417464274643746447464574646746477464874649746507465174652746537465474655746567465774658746597466074661746627466374664746657466674667746687466974670746717467274673746747467574676746777467874679746807468174682746837468474685746867468774688746897469074691746927469374694746957469674697746987469974700747017470274703747047470574706747077470874709747107471174712747137471474715747167471774718747197472074721747227472374724747257472674727747287472974730747317473274733747347473574736747377473874739747407474174742747437474474745747467474774748747497475074751747527475374754747557475674757747587475974760747617476274763747647476574766747677476874769747707477174772747737477474775747767477774778747797478074781747827478374784747857478674787747887478974790747917479274793747947479574796747977479874799748007480174802748037480474805748067480774808748097481074811748127481374814748157481674817748187481974820748217482274823748247482574826748277482874829748307483174832748337483474835748367483774838748397484074841748427484374844748457484674847748487484974850748517485274853748547485574856748577485874859748607486174862748637486474865748667486774868748697487074871748727487374874748757487674877748787487974880748817488274883748847488574886748877488874889748907489174892748937489474895748967489774898748997490074901749027490374904749057490674907749087490974910749117491274913749147491574916749177491874919749207492174922749237492474925749267492774928749297493074931749327493374934749357493674937749387493974940749417494274943749447494574946749477494874949749507495174952749537495474955749567495774958749597496074961749627496374964749657496674967749687496974970749717497274973749747497574976749777497874979749807498174982749837498474985749867498774988749897499074991749927499374994749957499674997749987499975000750017500275003750047500575006750077500875009750107501175012750137501475015750167501775018750197502075021750227502375024750257502675027750287502975030750317503275033750347503575036750377503875039750407504175042750437504475045750467504775048750497505075051750527505375054750557505675057750587505975060750617506275063750647506575066750677506875069750707507175072750737507475075750767507775078750797508075081750827508375084750857508675087750887508975090750917509275093750947509575096750977509875099751007510175102751037510475105751067510775108751097511075111751127511375114751157511675117751187511975120751217512275123751247512575126751277512875129751307513175132751337513475135751367513775138751397514075141751427514375144751457514675147751487514975150751517515275153751547515575156751577515875159751607516175162751637516475165751667516775168751697517075171751727517375174751757517675177751787517975180751817518275183751847518575186751877518875189751907519175192751937519475195751967519775198751997520075201752027520375204752057520675207752087520975210752117521275213752147521575216752177521875219752207522175222752237522475225752267522775228752297523075231752327523375234752357523675237752387523975240752417524275243752447524575246752477524875249752507525175252752537525475255752567525775258752597526075261752627526375264752657526675267752687526975270752717527275273752747527575276752777527875279752807528175282752837528475285752867528775288752897529075291752927529375294752957529675297752987529975300753017530275303753047530575306753077530875309753107531175312753137531475315753167531775318753197532075321753227532375324753257532675327753287532975330753317533275333753347533575336753377533875339753407534175342753437534475345753467534775348753497535075351753527535375354753557535675357753587535975360753617536275363753647536575366753677536875369753707537175372753737537475375753767537775378753797538075381753827538375384753857538675387753887538975390753917539275393753947539575396753977539875399754007540175402754037540475405754067540775408754097541075411754127541375414754157541675417754187541975420754217542275423754247542575426754277542875429754307543175432754337543475435754367543775438754397544075441754427544375444754457544675447754487544975450754517545275453754547545575456754577545875459754607546175462754637546475465754667546775468754697547075471754727547375474754757547675477754787547975480754817548275483754847548575486754877548875489754907549175492754937549475495754967549775498754997550075501755027550375504755057550675507755087550975510755117551275513755147551575516755177551875519755207552175522755237552475525755267552775528755297553075531755327553375534755357553675537755387553975540755417554275543755447554575546755477554875549755507555175552755537555475555755567555775558755597556075561755627556375564755657556675567755687556975570755717557275573755747557575576755777557875579755807558175582755837558475585755867558775588755897559075591755927559375594755957559675597755987559975600756017560275603756047560575606756077560875609756107561175612756137561475615756167561775618756197562075621756227562375624756257562675627756287562975630756317563275633756347563575636756377563875639756407564175642756437564475645756467564775648756497565075651756527565375654756557565675657756587565975660756617566275663756647566575666756677566875669756707567175672756737567475675756767567775678756797568075681756827568375684756857568675687756887568975690756917569275693756947569575696756977569875699757007570175702757037570475705757067570775708757097571075711757127571375714757157571675717757187571975720757217572275723757247572575726757277572875729757307573175732757337573475735757367573775738757397574075741757427574375744757457574675747757487574975750757517575275753757547575575756757577575875759757607576175762757637576475765757667576775768757697577075771757727577375774757757577675777757787577975780757817578275783757847578575786757877578875789757907579175792757937579475795757967579775798757997580075801758027580375804758057580675807758087580975810758117581275813758147581575816758177581875819758207582175822758237582475825758267582775828758297583075831758327583375834758357583675837758387583975840758417584275843758447584575846758477584875849758507585175852758537585475855758567585775858758597586075861758627586375864758657586675867758687586975870758717587275873758747587575876758777587875879758807588175882758837588475885758867588775888758897589075891758927589375894758957589675897758987589975900759017590275903759047590575906759077590875909759107591175912759137591475915759167591775918759197592075921759227592375924759257592675927759287592975930759317593275933759347593575936759377593875939759407594175942759437594475945759467594775948759497595075951759527595375954759557595675957759587595975960759617596275963759647596575966759677596875969759707597175972759737597475975759767597775978759797598075981759827598375984759857598675987759887598975990759917599275993759947599575996759977599875999760007600176002760037600476005760067600776008760097601076011760127601376014760157601676017760187601976020760217602276023760247602576026760277602876029760307603176032760337603476035760367603776038760397604076041760427604376044760457604676047760487604976050760517605276053760547605576056760577605876059760607606176062760637606476065760667606776068760697607076071760727607376074760757607676077760787607976080760817608276083760847608576086760877608876089760907609176092760937609476095760967609776098760997610076101761027610376104761057610676107761087610976110761117611276113761147611576116761177611876119761207612176122761237612476125761267612776128761297613076131761327613376134761357613676137761387613976140761417614276143761447614576146761477614876149761507615176152761537615476155761567615776158761597616076161761627616376164761657616676167761687616976170761717617276173761747617576176761777617876179761807618176182761837618476185761867618776188761897619076191761927619376194761957619676197761987619976200762017620276203762047620576206762077620876209762107621176212762137621476215762167621776218762197622076221762227622376224762257622676227762287622976230762317623276233762347623576236762377623876239762407624176242762437624476245762467624776248762497625076251762527625376254762557625676257762587625976260762617626276263762647626576266762677626876269762707627176272762737627476275762767627776278762797628076281762827628376284762857628676287762887628976290762917629276293762947629576296762977629876299763007630176302763037630476305763067630776308763097631076311763127631376314763157631676317763187631976320763217632276323763247632576326763277632876329763307633176332763337633476335763367633776338763397634076341763427634376344763457634676347763487634976350763517635276353763547635576356763577635876359763607636176362763637636476365763667636776368763697637076371763727637376374763757637676377763787637976380763817638276383763847638576386763877638876389763907639176392763937639476395763967639776398763997640076401764027640376404764057640676407764087640976410764117641276413764147641576416764177641876419764207642176422764237642476425764267642776428764297643076431764327643376434764357643676437764387643976440764417644276443764447644576446764477644876449764507645176452764537645476455764567645776458764597646076461764627646376464764657646676467764687646976470764717647276473764747647576476764777647876479764807648176482764837648476485764867648776488764897649076491764927649376494764957649676497764987649976500765017650276503765047650576506765077650876509765107651176512765137651476515765167651776518765197652076521765227652376524765257652676527765287652976530765317653276533765347653576536765377653876539765407654176542765437654476545765467654776548765497655076551765527655376554765557655676557765587655976560765617656276563765647656576566765677656876569765707657176572765737657476575765767657776578765797658076581765827658376584765857658676587765887658976590765917659276593765947659576596765977659876599766007660176602766037660476605766067660776608766097661076611766127661376614766157661676617766187661976620766217662276623766247662576626766277662876629766307663176632766337663476635766367663776638766397664076641766427664376644766457664676647766487664976650766517665276653766547665576656766577665876659766607666176662766637666476665766667666776668766697667076671766727667376674766757667676677766787667976680766817668276683766847668576686766877668876689766907669176692766937669476695766967669776698766997670076701767027670376704767057670676707767087670976710767117671276713767147671576716767177671876719767207672176722767237672476725767267672776728767297673076731767327673376734767357673676737767387673976740767417674276743767447674576746767477674876749767507675176752767537675476755767567675776758767597676076761767627676376764767657676676767767687676976770767717677276773767747677576776767777677876779767807678176782767837678476785767867678776788767897679076791767927679376794767957679676797767987679976800768017680276803768047680576806768077680876809768107681176812768137681476815768167681776818768197682076821768227682376824768257682676827768287682976830768317683276833768347683576836768377683876839768407684176842768437684476845768467684776848768497685076851768527685376854768557685676857768587685976860768617686276863768647686576866768677686876869768707687176872768737687476875768767687776878768797688076881768827688376884768857688676887768887688976890768917689276893768947689576896768977689876899769007690176902769037690476905769067690776908769097691076911769127691376914769157691676917769187691976920769217692276923769247692576926769277692876929769307693176932769337693476935769367693776938769397694076941769427694376944769457694676947769487694976950769517695276953769547695576956769577695876959769607696176962769637696476965769667696776968769697697076971769727697376974769757697676977769787697976980769817698276983769847698576986769877698876989769907699176992769937699476995769967699776998769997700077001770027700377004770057700677007770087700977010770117701277013770147701577016770177701877019770207702177022770237702477025770267702777028770297703077031770327703377034770357703677037770387703977040770417704277043770447704577046770477704877049770507705177052770537705477055770567705777058770597706077061770627706377064770657706677067770687706977070770717707277073770747707577076770777707877079770807708177082770837708477085770867708777088770897709077091770927709377094770957709677097770987709977100771017710277103771047710577106771077710877109771107711177112771137711477115771167711777118771197712077121771227712377124771257712677127771287712977130771317713277133771347713577136771377713877139771407714177142771437714477145771467714777148771497715077151771527715377154771557715677157771587715977160771617716277163771647716577166771677716877169771707717177172771737717477175771767717777178771797718077181771827718377184771857718677187771887718977190771917719277193771947719577196771977719877199772007720177202772037720477205772067720777208772097721077211772127721377214772157721677217772187721977220772217722277223772247722577226772277722877229772307723177232772337723477235772367723777238772397724077241772427724377244772457724677247772487724977250772517725277253772547725577256772577725877259772607726177262772637726477265772667726777268772697727077271772727727377274772757727677277772787727977280772817728277283772847728577286772877728877289772907729177292772937729477295772967729777298772997730077301773027730377304773057730677307773087730977310773117731277313773147731577316773177731877319773207732177322773237732477325773267732777328773297733077331773327733377334773357733677337773387733977340773417734277343773447734577346773477734877349773507735177352773537735477355773567735777358773597736077361773627736377364773657736677367773687736977370773717737277373773747737577376773777737877379773807738177382773837738477385773867738777388773897739077391773927739377394773957739677397773987739977400774017740277403774047740577406774077740877409774107741177412774137741477415774167741777418774197742077421774227742377424774257742677427774287742977430774317743277433774347743577436774377743877439774407744177442774437744477445774467744777448774497745077451774527745377454774557745677457774587745977460774617746277463774647746577466774677746877469774707747177472774737747477475774767747777478774797748077481774827748377484774857748677487774887748977490774917749277493774947749577496774977749877499775007750177502775037750477505775067750777508775097751077511775127751377514775157751677517775187751977520775217752277523775247752577526775277752877529775307753177532775337753477535775367753777538775397754077541775427754377544775457754677547775487754977550775517755277553775547755577556775577755877559775607756177562775637756477565775667756777568775697757077571775727757377574775757757677577775787757977580775817758277583775847758577586775877758877589775907759177592775937759477595775967759777598775997760077601776027760377604776057760677607776087760977610776117761277613776147761577616776177761877619776207762177622776237762477625776267762777628776297763077631776327763377634776357763677637776387763977640776417764277643776447764577646776477764877649776507765177652776537765477655776567765777658776597766077661776627766377664776657766677667776687766977670776717767277673776747767577676776777767877679776807768177682776837768477685776867768777688776897769077691776927769377694776957769677697776987769977700777017770277703777047770577706777077770877709777107771177712777137771477715777167771777718777197772077721777227772377724777257772677727777287772977730777317773277733777347773577736777377773877739777407774177742777437774477745777467774777748777497775077751777527775377754777557775677757777587775977760777617776277763777647776577766777677776877769777707777177772777737777477775777767777777778777797778077781777827778377784777857778677787777887778977790777917779277793777947779577796777977779877799778007780177802778037780477805778067780777808778097781077811778127781377814778157781677817778187781977820778217782277823778247782577826778277782877829778307783177832778337783477835778367783777838778397784077841778427784377844778457784677847778487784977850778517785277853778547785577856778577785877859778607786177862778637786477865778667786777868778697787077871778727787377874778757787677877778787787977880778817788277883778847788577886778877788877889778907789177892778937789477895778967789777898778997790077901779027790377904779057790677907779087790977910779117791277913779147791577916779177791877919779207792177922779237792477925779267792777928779297793077931779327793377934779357793677937779387793977940779417794277943779447794577946779477794877949779507795177952779537795477955779567795777958779597796077961779627796377964779657796677967779687796977970779717797277973779747797577976779777797877979779807798177982779837798477985779867798777988779897799077991779927799377994779957799677997779987799978000780017800278003780047800578006780077800878009780107801178012780137801478015780167801778018780197802078021780227802378024780257802678027780287802978030780317803278033780347803578036780377803878039780407804178042780437804478045780467804778048780497805078051780527805378054780557805678057780587805978060780617806278063780647806578066780677806878069780707807178072780737807478075780767807778078780797808078081780827808378084780857808678087780887808978090780917809278093780947809578096780977809878099781007810178102781037810478105781067810778108781097811078111781127811378114781157811678117781187811978120781217812278123781247812578126781277812878129781307813178132781337813478135781367813778138781397814078141781427814378144781457814678147781487814978150781517815278153781547815578156781577815878159781607816178162781637816478165781667816778168781697817078171781727817378174781757817678177781787817978180781817818278183781847818578186781877818878189781907819178192781937819478195781967819778198781997820078201782027820378204782057820678207782087820978210782117821278213782147821578216782177821878219782207822178222782237822478225782267822778228782297823078231782327823378234782357823678237782387823978240782417824278243782447824578246782477824878249782507825178252782537825478255782567825778258782597826078261782627826378264782657826678267782687826978270782717827278273782747827578276782777827878279782807828178282782837828478285782867828778288782897829078291782927829378294782957829678297782987829978300783017830278303783047830578306783077830878309783107831178312783137831478315783167831778318783197832078321783227832378324783257832678327783287832978330783317833278333783347833578336783377833878339783407834178342783437834478345783467834778348783497835078351783527835378354783557835678357783587835978360783617836278363783647836578366783677836878369783707837178372783737837478375783767837778378783797838078381783827838378384783857838678387783887838978390783917839278393783947839578396783977839878399784007840178402784037840478405784067840778408784097841078411784127841378414784157841678417784187841978420784217842278423784247842578426784277842878429784307843178432784337843478435784367843778438784397844078441784427844378444784457844678447784487844978450784517845278453784547845578456784577845878459784607846178462784637846478465784667846778468784697847078471784727847378474784757847678477784787847978480784817848278483784847848578486784877848878489784907849178492784937849478495784967849778498784997850078501785027850378504785057850678507785087850978510785117851278513785147851578516785177851878519785207852178522785237852478525785267852778528785297853078531785327853378534785357853678537785387853978540785417854278543785447854578546785477854878549785507855178552785537855478555785567855778558785597856078561785627856378564785657856678567785687856978570785717857278573785747857578576785777857878579785807858178582785837858478585785867858778588785897859078591785927859378594785957859678597785987859978600786017860278603786047860578606786077860878609786107861178612786137861478615786167861778618786197862078621786227862378624786257862678627786287862978630786317863278633786347863578636786377863878639786407864178642786437864478645786467864778648786497865078651786527865378654786557865678657786587865978660786617866278663786647866578666786677866878669786707867178672786737867478675786767867778678786797868078681786827868378684786857868678687786887868978690786917869278693786947869578696786977869878699787007870178702787037870478705787067870778708787097871078711787127871378714787157871678717787187871978720787217872278723787247872578726787277872878729787307873178732787337873478735787367873778738787397874078741787427874378744787457874678747787487874978750787517875278753787547875578756787577875878759787607876178762787637876478765787667876778768787697877078771787727877378774787757877678777787787877978780787817878278783787847878578786787877878878789787907879178792787937879478795787967879778798787997880078801788027880378804788057880678807788087880978810788117881278813788147881578816788177881878819788207882178822788237882478825788267882778828788297883078831788327883378834788357883678837788387883978840788417884278843788447884578846788477884878849788507885178852788537885478855788567885778858788597886078861788627886378864788657886678867788687886978870788717887278873788747887578876788777887878879788807888178882788837888478885788867888778888788897889078891788927889378894788957889678897788987889978900789017890278903789047890578906789077890878909789107891178912789137891478915789167891778918789197892078921789227892378924789257892678927789287892978930789317893278933789347893578936789377893878939789407894178942789437894478945789467894778948789497895078951789527895378954789557895678957789587895978960789617896278963789647896578966789677896878969789707897178972789737897478975789767897778978789797898078981789827898378984789857898678987789887898978990789917899278993789947899578996789977899878999790007900179002790037900479005790067900779008790097901079011790127901379014790157901679017790187901979020790217902279023790247902579026790277902879029790307903179032790337903479035790367903779038790397904079041790427904379044790457904679047790487904979050790517905279053790547905579056790577905879059790607906179062790637906479065790667906779068790697907079071790727907379074790757907679077790787907979080790817908279083790847908579086790877908879089790907909179092790937909479095790967909779098790997910079101791027910379104791057910679107791087910979110791117911279113791147911579116791177911879119791207912179122791237912479125791267912779128791297913079131791327913379134791357913679137791387913979140791417914279143791447914579146791477914879149791507915179152791537915479155791567915779158791597916079161791627916379164791657916679167791687916979170791717917279173791747917579176791777917879179791807918179182791837918479185791867918779188791897919079191791927919379194791957919679197791987919979200792017920279203792047920579206792077920879209792107921179212792137921479215792167921779218792197922079221792227922379224792257922679227792287922979230792317923279233792347923579236792377923879239792407924179242792437924479245792467924779248792497925079251792527925379254792557925679257792587925979260792617926279263792647926579266792677926879269792707927179272792737927479275792767927779278792797928079281792827928379284792857928679287792887928979290792917929279293792947929579296792977929879299793007930179302793037930479305793067930779308793097931079311793127931379314793157931679317793187931979320793217932279323793247932579326793277932879329793307933179332793337933479335793367933779338793397934079341793427934379344793457934679347793487934979350793517935279353793547935579356793577935879359793607936179362793637936479365793667936779368793697937079371793727937379374793757937679377793787937979380793817938279383793847938579386793877938879389793907939179392793937939479395793967939779398793997940079401794027940379404794057940679407794087940979410794117941279413794147941579416794177941879419794207942179422794237942479425794267942779428794297943079431794327943379434794357943679437794387943979440794417944279443794447944579446794477944879449794507945179452794537945479455794567945779458794597946079461794627946379464794657946679467794687946979470794717947279473794747947579476794777947879479794807948179482794837948479485794867948779488794897949079491794927949379494794957949679497794987949979500795017950279503795047950579506795077950879509795107951179512795137951479515795167951779518795197952079521795227952379524795257952679527795287952979530795317953279533795347953579536795377953879539795407954179542795437954479545795467954779548795497955079551795527955379554795557955679557795587955979560795617956279563795647956579566795677956879569795707957179572795737957479575795767957779578795797958079581795827958379584795857958679587795887958979590795917959279593795947959579596795977959879599796007960179602796037960479605796067960779608796097961079611796127961379614796157961679617796187961979620796217962279623796247962579626796277962879629796307963179632796337963479635796367963779638796397964079641796427964379644796457964679647796487964979650796517965279653796547965579656796577965879659796607966179662796637966479665796667966779668796697967079671796727967379674796757967679677796787967979680796817968279683796847968579686796877968879689796907969179692796937969479695796967969779698796997970079701797027970379704797057970679707797087970979710797117971279713797147971579716797177971879719797207972179722797237972479725797267972779728797297973079731797327973379734797357973679737797387973979740797417974279743797447974579746797477974879749797507975179752797537975479755797567975779758797597976079761797627976379764797657976679767797687976979770797717977279773797747977579776797777977879779797807978179782797837978479785797867978779788797897979079791797927979379794797957979679797797987979979800798017980279803798047980579806798077980879809798107981179812798137981479815798167981779818798197982079821798227982379824798257982679827798287982979830798317983279833798347983579836798377983879839798407984179842798437984479845798467984779848798497985079851798527985379854798557985679857798587985979860798617986279863798647986579866798677986879869798707987179872798737987479875798767987779878798797988079881798827988379884798857988679887798887988979890798917989279893798947989579896798977989879899799007990179902799037990479905799067990779908799097991079911799127991379914799157991679917799187991979920799217992279923799247992579926799277992879929799307993179932799337993479935799367993779938799397994079941799427994379944799457994679947799487994979950799517995279953799547995579956799577995879959799607996179962799637996479965799667996779968799697997079971799727997379974799757997679977799787997979980799817998279983799847998579986799877998879989799907999179992799937999479995799967999779998799998000080001800028000380004800058000680007800088000980010800118001280013800148001580016800178001880019800208002180022800238002480025800268002780028800298003080031800328003380034800358003680037800388003980040800418004280043800448004580046800478004880049800508005180052800538005480055800568005780058800598006080061800628006380064800658006680067800688006980070800718007280073800748007580076800778007880079800808008180082800838008480085800868008780088800898009080091800928009380094800958009680097800988009980100801018010280103801048010580106801078010880109801108011180112801138011480115801168011780118801198012080121801228012380124801258012680127801288012980130801318013280133801348013580136801378013880139801408014180142801438014480145801468014780148801498015080151801528015380154801558015680157801588015980160801618016280163801648016580166801678016880169801708017180172801738017480175801768017780178801798018080181801828018380184801858018680187801888018980190801918019280193801948019580196801978019880199802008020180202802038020480205802068020780208802098021080211802128021380214802158021680217802188021980220802218022280223802248022580226802278022880229802308023180232802338023480235802368023780238802398024080241802428024380244802458024680247802488024980250802518025280253802548025580256802578025880259802608026180262802638026480265802668026780268802698027080271802728027380274802758027680277802788027980280802818028280283802848028580286802878028880289802908029180292802938029480295802968029780298802998030080301803028030380304803058030680307803088030980310803118031280313803148031580316803178031880319803208032180322803238032480325803268032780328803298033080331803328033380334803358033680337803388033980340803418034280343803448034580346803478034880349803508035180352803538035480355803568035780358803598036080361803628036380364803658036680367803688036980370803718037280373803748037580376803778037880379803808038180382803838038480385803868038780388803898039080391803928039380394803958039680397803988039980400804018040280403804048040580406804078040880409804108041180412804138041480415804168041780418804198042080421804228042380424804258042680427804288042980430804318043280433804348043580436804378043880439804408044180442804438044480445804468044780448804498045080451804528045380454804558045680457804588045980460804618046280463804648046580466804678046880469804708047180472804738047480475804768047780478804798048080481804828048380484804858048680487804888048980490804918049280493804948049580496804978049880499805008050180502805038050480505805068050780508805098051080511805128051380514805158051680517805188051980520805218052280523805248052580526805278052880529805308053180532805338053480535805368053780538805398054080541805428054380544805458054680547805488054980550805518055280553805548055580556805578055880559805608056180562805638056480565805668056780568805698057080571805728057380574805758057680577805788057980580805818058280583805848058580586805878058880589805908059180592805938059480595805968059780598805998060080601806028060380604806058060680607806088060980610806118061280613806148061580616806178061880619806208062180622806238062480625806268062780628806298063080631806328063380634806358063680637806388063980640806418064280643806448064580646806478064880649806508065180652806538065480655806568065780658806598066080661806628066380664806658066680667806688066980670806718067280673806748067580676806778067880679806808068180682806838068480685806868068780688806898069080691806928069380694806958069680697806988069980700807018070280703807048070580706807078070880709807108071180712807138071480715807168071780718807198072080721807228072380724807258072680727807288072980730807318073280733807348073580736807378073880739807408074180742807438074480745807468074780748807498075080751807528075380754807558075680757807588075980760807618076280763807648076580766807678076880769807708077180772807738077480775807768077780778807798078080781807828078380784807858078680787807888078980790807918079280793807948079580796807978079880799808008080180802808038080480805808068080780808808098081080811808128081380814808158081680817808188081980820808218082280823808248082580826808278082880829808308083180832808338083480835808368083780838808398084080841808428084380844808458084680847808488084980850808518085280853808548085580856808578085880859808608086180862808638086480865808668086780868808698087080871808728087380874808758087680877808788087980880808818088280883808848088580886808878088880889808908089180892808938089480895808968089780898808998090080901809028090380904809058090680907809088090980910809118091280913809148091580916809178091880919809208092180922809238092480925809268092780928809298093080931809328093380934809358093680937809388093980940809418094280943809448094580946809478094880949809508095180952809538095480955809568095780958809598096080961809628096380964809658096680967809688096980970809718097280973809748097580976809778097880979809808098180982809838098480985809868098780988809898099080991809928099380994809958099680997809988099981000810018100281003810048100581006810078100881009810108101181012810138101481015810168101781018810198102081021810228102381024810258102681027810288102981030810318103281033810348103581036810378103881039810408104181042810438104481045810468104781048810498105081051810528105381054810558105681057810588105981060810618106281063810648106581066810678106881069810708107181072810738107481075810768107781078810798108081081810828108381084810858108681087810888108981090810918109281093810948109581096810978109881099811008110181102811038110481105811068110781108811098111081111811128111381114811158111681117811188111981120811218112281123811248112581126811278112881129811308113181132811338113481135811368113781138811398114081141811428114381144811458114681147811488114981150811518115281153811548115581156811578115881159811608116181162811638116481165811668116781168811698117081171811728117381174811758117681177811788117981180811818118281183811848118581186811878118881189811908119181192811938119481195811968119781198811998120081201812028120381204812058120681207812088120981210812118121281213812148121581216812178121881219812208122181222812238122481225812268122781228812298123081231812328123381234812358123681237812388123981240812418124281243812448124581246812478124881249812508125181252812538125481255812568125781258812598126081261812628126381264812658126681267812688126981270812718127281273812748127581276812778127881279812808128181282812838128481285812868128781288812898129081291812928129381294812958129681297812988129981300813018130281303813048130581306813078130881309813108131181312813138131481315813168131781318813198132081321813228132381324813258132681327813288132981330813318133281333813348133581336813378133881339813408134181342813438134481345813468134781348813498135081351813528135381354813558135681357813588135981360813618136281363813648136581366813678136881369813708137181372813738137481375813768137781378813798138081381813828138381384813858138681387813888138981390813918139281393813948139581396813978139881399814008140181402814038140481405814068140781408814098141081411814128141381414814158141681417814188141981420814218142281423814248142581426814278142881429814308143181432814338143481435814368143781438814398144081441814428144381444814458144681447814488144981450814518145281453814548145581456814578145881459814608146181462814638146481465814668146781468814698147081471814728147381474814758147681477814788147981480814818148281483814848148581486814878148881489814908149181492814938149481495814968149781498814998150081501815028150381504815058150681507815088150981510815118151281513815148151581516815178151881519815208152181522815238152481525815268152781528815298153081531815328153381534815358153681537815388153981540815418154281543815448154581546815478154881549 |
- /**
- * @license
- * Copyright 2010-2025 Three.js Authors
- * SPDX-License-Identifier: MIT
- */
- import { Color, Vector2, Vector3, Vector4, Matrix2, Matrix3, Matrix4, error, EventDispatcher, MathUtils, warn, WebGLCoordinateSystem, WebGPUCoordinateSystem, ColorManagement, SRGBTransfer, NoToneMapping, StaticDrawUsage, InterleavedBufferAttribute, InterleavedBuffer, DynamicDrawUsage, NoColorSpace, log as log$1, warnOnce, Texture, UnsignedIntType, IntType, NearestFilter, Sphere, BackSide, DoubleSide, Euler, CubeTexture, CubeReflectionMapping, CubeRefractionMapping, TangentSpaceNormalMap, NoNormalPacking, NormalRGPacking, NormalGAPacking, ObjectSpaceNormalMap, RGFormat, RED_GREEN_RGTC2_Format, RG11_EAC_Format, InstancedInterleavedBuffer, InstancedBufferAttribute, DataArrayTexture, FloatType, FramebufferTexture, LinearMipmapLinearFilter, DepthTexture, Material, LineBasicMaterial, LineDashedMaterial, NoBlending, MeshNormalMaterial, SRGBColorSpace, WebGLCubeRenderTarget, BoxGeometry, Mesh, Scene, LinearFilter, CubeCamera, EquirectangularReflectionMapping, EquirectangularRefractionMapping, AddOperation, MixOperation, MultiplyOperation, MeshBasicMaterial, MeshLambertMaterial, MeshPhongMaterial, DataTexture, HalfFloatType, ClampToEdgeWrapping, BufferGeometry, OrthographicCamera, PerspectiveCamera, RenderTarget, LinearSRGBColorSpace, RGBAFormat, CubeUVReflectionMapping, BufferAttribute, MeshStandardMaterial, MeshPhysicalMaterial, MeshToonMaterial, MeshMatcapMaterial, SpriteMaterial, PointsMaterial, ShadowMaterial, Uint32BufferAttribute, Uint16BufferAttribute, arrayNeedsUint32, Camera, DepthStencilFormat, DepthFormat, UnsignedInt248Type, UnsignedByteType, Plane, Object3D, LinearMipMapLinearFilter, Float32BufferAttribute, UVMapping, VSMShadowMap, LessCompare, BasicShadowMap, CubeDepthTexture, SphereGeometry, NormalBlending, LinearMipmapNearestFilter, NearestMipmapLinearFilter, Float16BufferAttribute, REVISION, ArrayCamera, PlaneGeometry, FrontSide, CustomBlending, AddEquation, ZeroFactor, CylinderGeometry, Quaternion, WebXRController, RAD2DEG, PCFShadowMap, FrustumArray, Frustum, RedIntegerFormat, RedFormat, ShortType, ByteType, UnsignedShortType, RGIntegerFormat, RGBIntegerFormat, RGBFormat, RGBAIntegerFormat, TimestampQuery, createCanvasElement, ReverseSubtractEquation, SubtractEquation, OneMinusDstAlphaFactor, OneMinusDstColorFactor, OneMinusSrcAlphaFactor, OneMinusSrcColorFactor, DstAlphaFactor, DstColorFactor, SrcAlphaSaturateFactor, SrcAlphaFactor, SrcColorFactor, OneFactor, CullFaceNone, CullFaceBack, CullFaceFront, MultiplyBlending, SubtractiveBlending, AdditiveBlending, NotEqualDepth, GreaterDepth, GreaterEqualDepth, EqualDepth, LessEqualDepth, LessDepth, AlwaysDepth, NeverDepth, UnsignedShort4444Type, UnsignedShort5551Type, UnsignedInt5999Type, UnsignedInt101111Type, AlphaFormat, RGB_S3TC_DXT1_Format, RGBA_S3TC_DXT1_Format, RGBA_S3TC_DXT3_Format, RGBA_S3TC_DXT5_Format, RGB_PVRTC_4BPPV1_Format, RGB_PVRTC_2BPPV1_Format, RGBA_PVRTC_4BPPV1_Format, RGBA_PVRTC_2BPPV1_Format, RGB_ETC1_Format, RGB_ETC2_Format, RGBA_ETC2_EAC_Format, R11_EAC_Format, SIGNED_R11_EAC_Format, SIGNED_RG11_EAC_Format, RGBA_ASTC_4x4_Format, RGBA_ASTC_5x4_Format, RGBA_ASTC_5x5_Format, RGBA_ASTC_6x5_Format, RGBA_ASTC_6x6_Format, RGBA_ASTC_8x5_Format, RGBA_ASTC_8x6_Format, RGBA_ASTC_8x8_Format, RGBA_ASTC_10x5_Format, RGBA_ASTC_10x6_Format, RGBA_ASTC_10x8_Format, RGBA_ASTC_10x10_Format, RGBA_ASTC_12x10_Format, RGBA_ASTC_12x12_Format, RGBA_BPTC_Format, RED_RGTC1_Format, SIGNED_RED_RGTC1_Format, SIGNED_RED_GREEN_RGTC2_Format, MirroredRepeatWrapping, RepeatWrapping, NearestMipmapNearestFilter, NotEqualCompare, GreaterCompare, GreaterEqualCompare, EqualCompare, LessEqualCompare, AlwaysCompare, NeverCompare, LinearTransfer, getByteLength, isTypedArray, NotEqualStencilFunc, GreaterStencilFunc, GreaterEqualStencilFunc, EqualStencilFunc, LessEqualStencilFunc, LessStencilFunc, AlwaysStencilFunc, NeverStencilFunc, DecrementWrapStencilOp, IncrementWrapStencilOp, DecrementStencilOp, IncrementStencilOp, InvertStencilOp, ReplaceStencilOp, ZeroStencilOp, KeepStencilOp, MaxEquation, MinEquation, SpotLight, PointLight, DirectionalLight, RectAreaLight, AmbientLight, HemisphereLight, LightProbe, LinearToneMapping, ReinhardToneMapping, CineonToneMapping, ACESFilmicToneMapping, AgXToneMapping, NeutralToneMapping, Group, Loader, FileLoader, MaterialLoader, ObjectLoader } from './three.core.js';
- export { AdditiveAnimationBlendMode, AnimationAction, AnimationClip, AnimationLoader, AnimationMixer, AnimationObjectGroup, AnimationUtils, ArcCurve, ArrowHelper, AttachedBindMode, Audio, AudioAnalyser, AudioContext, AudioListener, AudioLoader, AxesHelper, BasicDepthPacking, BatchedMesh, Bone, BooleanKeyframeTrack, Box2, Box3, Box3Helper, BoxHelper, BufferGeometryLoader, Cache, CameraHelper, CanvasTexture, CapsuleGeometry, CatmullRomCurve3, CircleGeometry, Clock, ColorKeyframeTrack, CompressedArrayTexture, CompressedCubeTexture, CompressedTexture, CompressedTextureLoader, ConeGeometry, ConstantAlphaFactor, ConstantColorFactor, Controls, CubeTextureLoader, CubicBezierCurve, CubicBezierCurve3, CubicInterpolant, CullFaceFrontBack, Curve, CurvePath, CustomToneMapping, Cylindrical, Data3DTexture, DataTextureLoader, DataUtils, DefaultLoadingManager, DetachedBindMode, DirectionalLightHelper, DiscreteInterpolant, DodecahedronGeometry, DynamicCopyUsage, DynamicReadUsage, EdgesGeometry, EllipseCurve, ExternalTexture, ExtrudeGeometry, Fog, FogExp2, GLBufferAttribute, GLSL1, GLSL3, GridHelper, HemisphereLightHelper, IcosahedronGeometry, ImageBitmapLoader, ImageLoader, ImageUtils, InstancedBufferGeometry, InstancedMesh, Int16BufferAttribute, Int32BufferAttribute, Int8BufferAttribute, Interpolant, InterpolateDiscrete, InterpolateLinear, InterpolateSmooth, InterpolationSamplingMode, InterpolationSamplingType, KeyframeTrack, LOD, LatheGeometry, Layers, Light, Line, Line3, LineCurve, LineCurve3, LineLoop, LineSegments, LinearInterpolant, LinearMipMapNearestFilter, LoaderUtils, LoadingManager, LoopOnce, LoopPingPong, LoopRepeat, MOUSE, MeshDepthMaterial, MeshDistanceMaterial, NearestMipMapLinearFilter, NearestMipMapNearestFilter, NormalAnimationBlendMode, NumberKeyframeTrack, OctahedronGeometry, OneMinusConstantAlphaFactor, OneMinusConstantColorFactor, PCFSoftShadowMap, Path, PlaneHelper, PointLightHelper, Points, PolarGridHelper, PolyhedronGeometry, PositionalAudio, PropertyBinding, PropertyMixer, QuadraticBezierCurve, QuadraticBezierCurve3, QuaternionKeyframeTrack, QuaternionLinearInterpolant, RGBADepthPacking, RGBDepthPacking, RGB_BPTC_SIGNED_Format, RGB_BPTC_UNSIGNED_Format, RGDepthPacking, RawShaderMaterial, Ray, Raycaster, RenderTarget3D, RingGeometry, ShaderMaterial, Shape, ShapeGeometry, ShapePath, ShapeUtils, Skeleton, SkeletonHelper, SkinnedMesh, Source, Spherical, SphericalHarmonics3, SplineCurve, SpotLightHelper, Sprite, StaticCopyUsage, StaticReadUsage, StereoCamera, StreamCopyUsage, StreamDrawUsage, StreamReadUsage, StringKeyframeTrack, TOUCH, TetrahedronGeometry, TextureLoader, TextureUtils, Timer, TorusGeometry, TorusKnotGeometry, Triangle, TriangleFanDrawMode, TriangleStripDrawMode, TrianglesDrawMode, TubeGeometry, Uint8BufferAttribute, Uint8ClampedBufferAttribute, Uniform, UniformsGroup, VectorKeyframeTrack, VideoFrameTexture, VideoTexture, WebGL3DRenderTarget, WebGLArrayRenderTarget, WebGLRenderTarget, WireframeGeometry, WrapAroundEnding, ZeroCurvatureEnding, ZeroSlopeEnding, getConsoleFunction, setConsoleFunction } from './three.core.js';
- const refreshUniforms = [
- 'alphaMap',
- 'alphaTest',
- 'anisotropy',
- 'anisotropyMap',
- 'anisotropyRotation',
- 'aoMap',
- 'aoMapIntensity',
- 'attenuationColor',
- 'attenuationDistance',
- 'bumpMap',
- 'clearcoat',
- 'clearcoatMap',
- 'clearcoatNormalMap',
- 'clearcoatNormalScale',
- 'clearcoatRoughness',
- 'color',
- 'dispersion',
- 'displacementMap',
- 'emissive',
- 'emissiveIntensity',
- 'emissiveMap',
- 'envMap',
- 'envMapIntensity',
- 'gradientMap',
- 'ior',
- 'iridescence',
- 'iridescenceIOR',
- 'iridescenceMap',
- 'iridescenceThicknessMap',
- 'lightMap',
- 'lightMapIntensity',
- 'map',
- 'matcap',
- 'metalness',
- 'metalnessMap',
- 'normalMap',
- 'normalScale',
- 'opacity',
- 'roughness',
- 'roughnessMap',
- 'sheen',
- 'sheenColor',
- 'sheenColorMap',
- 'sheenRoughnessMap',
- 'shininess',
- 'specular',
- 'specularColor',
- 'specularColorMap',
- 'specularIntensity',
- 'specularIntensityMap',
- 'specularMap',
- 'thickness',
- 'transmission',
- 'transmissionMap'
- ];
- /**
- * A WeakMap to cache lights data for node materials.
- * Cache lights data by render ID to avoid unnecessary recalculations.
- *
- * @private
- * @type {WeakMap<LightsNode,Object>}
- */
- const _lightsCache = new WeakMap();
- /**
- * This class is used by {@link WebGPURenderer} as management component.
- * It's primary purpose is to determine whether render objects require a
- * refresh right before they are going to be rendered or not.
- */
- class NodeMaterialObserver {
- /**
- * Constructs a new node material observer.
- *
- * @param {NodeBuilder} builder - The node builder.
- */
- constructor( builder ) {
- /**
- * A node material can be used by more than one render object so the
- * monitor must maintain a list of render objects.
- *
- * @type {WeakMap<RenderObject,Object>}
- */
- this.renderObjects = new WeakMap();
- /**
- * Whether the material uses node objects or not.
- *
- * @type {boolean}
- */
- this.hasNode = this.containsNode( builder );
- /**
- * Whether the node builder's 3D object is animated or not.
- *
- * @type {boolean}
- */
- this.hasAnimation = builder.object.isSkinnedMesh === true;
- /**
- * A list of all possible material uniforms
- *
- * @type {Array<string>}
- */
- this.refreshUniforms = refreshUniforms;
- /**
- * Holds the current render ID from the node frame.
- *
- * @type {number}
- * @default 0
- */
- this.renderId = 0;
- }
- /**
- * Returns `true` if the given render object is verified for the first time of this observer.
- *
- * @param {RenderObject} renderObject - The render object.
- * @return {boolean} Whether the given render object is verified for the first time of this observer.
- */
- firstInitialization( renderObject ) {
- const hasInitialized = this.renderObjects.has( renderObject );
- if ( hasInitialized === false ) {
- this.getRenderObjectData( renderObject );
- return true;
- }
- return false;
- }
- /**
- * Returns `true` if the current rendering produces motion vectors.
- *
- * @param {Renderer} renderer - The renderer.
- * @return {boolean} Whether the current rendering produces motion vectors or not.
- */
- needsVelocity( renderer ) {
- const mrt = renderer.getMRT();
- return ( mrt !== null && mrt.has( 'velocity' ) );
- }
- /**
- * Returns monitoring data for the given render object.
- *
- * @param {RenderObject} renderObject - The render object.
- * @return {Object} The monitoring data.
- */
- getRenderObjectData( renderObject ) {
- let data = this.renderObjects.get( renderObject );
- if ( data === undefined ) {
- const { geometry, material, object } = renderObject;
- data = {
- material: this.getMaterialData( material ),
- geometry: {
- id: geometry.id,
- attributes: this.getAttributesData( geometry.attributes ),
- indexVersion: geometry.index ? geometry.index.version : null,
- drawRange: { start: geometry.drawRange.start, count: geometry.drawRange.count }
- },
- worldMatrix: object.matrixWorld.clone()
- };
- if ( object.center ) {
- data.center = object.center.clone();
- }
- if ( object.morphTargetInfluences ) {
- data.morphTargetInfluences = object.morphTargetInfluences.slice();
- }
- if ( renderObject.bundle !== null ) {
- data.version = renderObject.bundle.version;
- }
- if ( data.material.transmission > 0 ) {
- const { width, height } = renderObject.context;
- data.bufferWidth = width;
- data.bufferHeight = height;
- }
- data.lights = this.getLightsData( renderObject.lightsNode.getLights() );
- this.renderObjects.set( renderObject, data );
- }
- return data;
- }
- /**
- * Returns an attribute data structure holding the attributes versions for
- * monitoring.
- *
- * @param {Object} attributes - The geometry attributes.
- * @return {Object} An object for monitoring the versions of attributes.
- */
- getAttributesData( attributes ) {
- const attributesData = {};
- for ( const name in attributes ) {
- const attribute = attributes[ name ];
- attributesData[ name ] = {
- version: attribute.version
- };
- }
- return attributesData;
- }
- /**
- * Returns `true` if the node builder's material uses
- * node properties.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {boolean} Whether the node builder's material uses node properties or not.
- */
- containsNode( builder ) {
- const material = builder.material;
- for ( const property in material ) {
- if ( material[ property ] && material[ property ].isNode )
- return true;
- }
- if ( builder.context.modelViewMatrix || builder.context.modelNormalViewMatrix || builder.context.getAO || builder.context.getShadow )
- return true;
- return false;
- }
- /**
- * Returns a material data structure holding the material property values for
- * monitoring.
- *
- * @param {Material} material - The material.
- * @return {Object} An object for monitoring material properties.
- */
- getMaterialData( material ) {
- const data = {};
- for ( const property of this.refreshUniforms ) {
- const value = material[ property ];
- if ( value === null || value === undefined ) continue;
- if ( typeof value === 'object' && value.clone !== undefined ) {
- if ( value.isTexture === true ) {
- data[ property ] = { id: value.id, version: value.version };
- } else {
- data[ property ] = value.clone();
- }
- } else {
- data[ property ] = value;
- }
- }
- return data;
- }
- /**
- * Returns `true` if the given render object has not changed its state.
- *
- * @param {RenderObject} renderObject - The render object.
- * @param {Array<Light>} lightsData - The current material lights.
- * @return {boolean} Whether the given render object has changed its state or not.
- */
- equals( renderObject, lightsData ) {
- const { object, material, geometry } = renderObject;
- const renderObjectData = this.getRenderObjectData( renderObject );
- // world matrix
- if ( renderObjectData.worldMatrix.equals( object.matrixWorld ) !== true ) {
- renderObjectData.worldMatrix.copy( object.matrixWorld );
- return false;
- }
- // material
- const materialData = renderObjectData.material;
- for ( const property in materialData ) {
- const value = materialData[ property ];
- const mtlValue = material[ property ];
- if ( value.equals !== undefined ) {
- if ( value.equals( mtlValue ) === false ) {
- value.copy( mtlValue );
- return false;
- }
- } else if ( mtlValue.isTexture === true ) {
- if ( value.id !== mtlValue.id || value.version !== mtlValue.version ) {
- value.id = mtlValue.id;
- value.version = mtlValue.version;
- return false;
- }
- } else if ( value !== mtlValue ) {
- materialData[ property ] = mtlValue;
- return false;
- }
- }
- if ( materialData.transmission > 0 ) {
- const { width, height } = renderObject.context;
- if ( renderObjectData.bufferWidth !== width || renderObjectData.bufferHeight !== height ) {
- renderObjectData.bufferWidth = width;
- renderObjectData.bufferHeight = height;
- return false;
- }
- }
- // geometry
- const storedGeometryData = renderObjectData.geometry;
- const attributes = geometry.attributes;
- const storedAttributes = storedGeometryData.attributes;
- const storedAttributeNames = Object.keys( storedAttributes );
- const currentAttributeNames = Object.keys( attributes );
- if ( storedGeometryData.id !== geometry.id ) {
- storedGeometryData.id = geometry.id;
- return false;
- }
- if ( storedAttributeNames.length !== currentAttributeNames.length ) {
- renderObjectData.geometry.attributes = this.getAttributesData( attributes );
- return false;
- }
- // compare each attribute
- for ( const name of storedAttributeNames ) {
- const storedAttributeData = storedAttributes[ name ];
- const attribute = attributes[ name ];
- if ( attribute === undefined ) {
- // attribute was removed
- delete storedAttributes[ name ];
- return false;
- }
- if ( storedAttributeData.version !== attribute.version ) {
- storedAttributeData.version = attribute.version;
- return false;
- }
- }
- // check index
- const index = geometry.index;
- const storedIndexVersion = storedGeometryData.indexVersion;
- const currentIndexVersion = index ? index.version : null;
- if ( storedIndexVersion !== currentIndexVersion ) {
- storedGeometryData.indexVersion = currentIndexVersion;
- return false;
- }
- // check drawRange
- if ( storedGeometryData.drawRange.start !== geometry.drawRange.start || storedGeometryData.drawRange.count !== geometry.drawRange.count ) {
- storedGeometryData.drawRange.start = geometry.drawRange.start;
- storedGeometryData.drawRange.count = geometry.drawRange.count;
- return false;
- }
- // morph targets
- if ( renderObjectData.morphTargetInfluences ) {
- let morphChanged = false;
- for ( let i = 0; i < renderObjectData.morphTargetInfluences.length; i ++ ) {
- if ( renderObjectData.morphTargetInfluences[ i ] !== object.morphTargetInfluences[ i ] ) {
- renderObjectData.morphTargetInfluences[ i ] = object.morphTargetInfluences[ i ];
- morphChanged = true;
- }
- }
- if ( morphChanged ) return false;
- }
- // lights
- if ( renderObjectData.lights ) {
- for ( let i = 0; i < lightsData.length; i ++ ) {
- if ( renderObjectData.lights[ i ].map !== lightsData[ i ].map ) {
- return false;
- }
- }
- }
- // center
- if ( renderObjectData.center ) {
- if ( renderObjectData.center.equals( object.center ) === false ) {
- renderObjectData.center.copy( object.center );
- return true;
- }
- }
- // bundle
- if ( renderObject.bundle !== null ) {
- renderObjectData.version = renderObject.bundle.version;
- }
- return true;
- }
- /**
- * Returns the lights data for the given material lights.
- *
- * @param {Array<Light>} materialLights - The material lights.
- * @return {Array<Object>} The lights data for the given material lights.
- */
- getLightsData( materialLights ) {
- const lights = [];
- for ( const light of materialLights ) {
- if ( light.isSpotLight === true && light.map !== null ) {
- // only add lights that have a map
- lights.push( { map: light.map.version } );
- }
- }
- return lights;
- }
- /**
- * Returns the lights for the given lights node and render ID.
- *
- * @param {LightsNode} lightsNode - The lights node.
- * @param {number} renderId - The render ID.
- * @return {Array<Object>} The lights for the given lights node and render ID.
- */
- getLights( lightsNode, renderId ) {
- if ( _lightsCache.has( lightsNode ) ) {
- const cached = _lightsCache.get( lightsNode );
- if ( cached.renderId === renderId ) {
- return cached.lightsData;
- }
- }
- const lightsData = this.getLightsData( lightsNode.getLights() );
- _lightsCache.set( lightsNode, { renderId, lightsData } );
- return lightsData;
- }
- /**
- * Checks if the given render object requires a refresh.
- *
- * @param {RenderObject} renderObject - The render object.
- * @param {NodeFrame} nodeFrame - The current node frame.
- * @return {boolean} Whether the given render object requires a refresh or not.
- */
- needsRefresh( renderObject, nodeFrame ) {
- if ( this.hasNode || this.hasAnimation || this.firstInitialization( renderObject ) || this.needsVelocity( nodeFrame.renderer ) )
- return true;
- const { renderId } = nodeFrame;
- if ( this.renderId !== renderId ) {
- this.renderId = renderId;
- return true;
- }
- const isStatic = renderObject.object.static === true;
- const isBundle = renderObject.bundle !== null && renderObject.bundle.static === true && this.getRenderObjectData( renderObject ).version === renderObject.bundle.version;
- if ( isStatic || isBundle )
- return false;
- const lightsData = this.getLights( renderObject.lightsNode, renderId );
- const notEqual = this.equals( renderObject, lightsData ) !== true;
- return notEqual;
- }
- }
- // cyrb53 (c) 2018 bryc (github.com/bryc). License: Public domain. Attribution appreciated.
- // A fast and simple 64-bit (or 53-bit) string hash function with decent collision resistance.
- // Largely inspired by MurmurHash2/3, but with a focus on speed/simplicity.
- // See https://stackoverflow.com/questions/7616461/generate-a-hash-from-string-in-javascript/52171480#52171480
- // https://github.com/bryc/code/blob/master/jshash/experimental/cyrb53.js
- function cyrb53( value, seed = 0 ) {
- let h1 = 0xdeadbeef ^ seed, h2 = 0x41c6ce57 ^ seed;
- if ( value instanceof Array ) {
- for ( let i = 0, val; i < value.length; i ++ ) {
- val = value[ i ];
- h1 = Math.imul( h1 ^ val, 2654435761 );
- h2 = Math.imul( h2 ^ val, 1597334677 );
- }
- } else {
- for ( let i = 0, ch; i < value.length; i ++ ) {
- ch = value.charCodeAt( i );
- h1 = Math.imul( h1 ^ ch, 2654435761 );
- h2 = Math.imul( h2 ^ ch, 1597334677 );
- }
- }
- h1 = Math.imul( h1 ^ ( h1 >>> 16 ), 2246822507 );
- h1 ^= Math.imul( h2 ^ ( h2 >>> 13 ), 3266489909 );
- h2 = Math.imul( h2 ^ ( h2 >>> 16 ), 2246822507 );
- h2 ^= Math.imul( h1 ^ ( h1 >>> 13 ), 3266489909 );
- return 4294967296 * ( 2097151 & h2 ) + ( h1 >>> 0 );
- }
- /**
- * Computes a hash for the given string.
- *
- * @private
- * @method
- * @param {string} str - The string to be hashed.
- * @return {number} The hash.
- */
- const hashString = ( str ) => cyrb53( str );
- /**
- * Computes a hash for the given array.
- *
- * @private
- * @method
- * @param {Array<number>} array - The array to be hashed.
- * @return {number} The hash.
- */
- const hashArray = ( array ) => cyrb53( array );
- /**
- * Computes a hash for the given list of parameters.
- *
- * @private
- * @method
- * @param {...number} params - A list of parameters.
- * @return {number} The hash.
- */
- const hash$1 = ( ...params ) => cyrb53( params );
- const typeFromLength = /*@__PURE__*/ new Map( [
- [ 1, 'float' ],
- [ 2, 'vec2' ],
- [ 3, 'vec3' ],
- [ 4, 'vec4' ],
- [ 9, 'mat3' ],
- [ 16, 'mat4' ]
- ] );
- const dataFromObject = /*@__PURE__*/ new WeakMap();
- /**
- * Returns the data type for the given the length.
- *
- * @private
- * @method
- * @param {number} length - The length.
- * @return {string} The data type.
- */
- function getTypeFromLength( length ) {
- return typeFromLength.get( length );
- }
- /**
- * Returns the typed array for the given data type.
- *
- * @private
- * @method
- * @param {string} type - The data type.
- * @return {TypedArray} The typed array.
- */
- function getTypedArrayFromType( type ) {
- // Handle component type for vectors and matrices
- if ( /[iu]?vec\d/.test( type ) ) {
- // Handle int vectors
- if ( type.startsWith( 'ivec' ) ) return Int32Array;
- // Handle uint vectors
- if ( type.startsWith( 'uvec' ) ) return Uint32Array;
- // Default to float vectors
- return Float32Array;
- }
- // Handle matrices (always float)
- if ( /mat\d/.test( type ) ) return Float32Array;
- // Basic types
- if ( /float/.test( type ) ) return Float32Array;
- if ( /uint/.test( type ) ) return Uint32Array;
- if ( /int/.test( type ) ) return Int32Array;
- throw new Error( `THREE.NodeUtils: Unsupported type: ${type}` );
- }
- /**
- * Returns the length for the given data type.
- *
- * @private
- * @method
- * @param {string} type - The data type.
- * @return {number} The length.
- */
- function getLengthFromType( type ) {
- if ( /float|int|uint/.test( type ) ) return 1;
- if ( /vec2/.test( type ) ) return 2;
- if ( /vec3/.test( type ) ) return 3;
- if ( /vec4/.test( type ) ) return 4;
- if ( /mat2/.test( type ) ) return 4;
- if ( /mat3/.test( type ) ) return 9;
- if ( /mat4/.test( type ) ) return 16;
- error( 'TSL: Unsupported type:', type );
- }
- /**
- * Returns the gpu memory length for the given data type.
- *
- * @private
- * @method
- * @param {string} type - The data type.
- * @return {number} The length.
- */
- function getMemoryLengthFromType( type ) {
- if ( /float|int|uint/.test( type ) ) return 1;
- if ( /vec2/.test( type ) ) return 2;
- if ( /vec3/.test( type ) ) return 3;
- if ( /vec4/.test( type ) ) return 4;
- if ( /mat2/.test( type ) ) return 4;
- if ( /mat3/.test( type ) ) return 12;
- if ( /mat4/.test( type ) ) return 16;
- error( 'TSL: Unsupported type:', type );
- }
- /**
- * Returns the alignment requirement for the given data type.
- *
- * @private
- * @method
- * @param {string} type - The data type.
- * @return {number} The alignment requirement in bytes.
- */
- function getAlignmentFromType( type ) {
- if ( /float|int|uint/.test( type ) ) return 4;
- if ( /vec2/.test( type ) ) return 8;
- if ( /vec3/.test( type ) ) return 16;
- if ( /vec4/.test( type ) ) return 16;
- if ( /mat2/.test( type ) ) return 8;
- if ( /mat3/.test( type ) ) return 16;
- if ( /mat4/.test( type ) ) return 16;
- error( 'TSL: Unsupported type:', type );
- }
- /**
- * Returns the data type for the given value.
- *
- * @private
- * @method
- * @param {any} value - The value.
- * @return {?string} The data type.
- */
- function getValueType( value ) {
- if ( value === undefined || value === null ) return null;
- const typeOf = typeof value;
- if ( value.isNode === true ) {
- return 'node';
- } else if ( typeOf === 'number' ) {
- return 'float';
- } else if ( typeOf === 'boolean' ) {
- return 'bool';
- } else if ( typeOf === 'string' ) {
- return 'string';
- } else if ( typeOf === 'function' ) {
- return 'shader';
- } else if ( value.isVector2 === true ) {
- return 'vec2';
- } else if ( value.isVector3 === true ) {
- return 'vec3';
- } else if ( value.isVector4 === true ) {
- return 'vec4';
- } else if ( value.isMatrix2 === true ) {
- return 'mat2';
- } else if ( value.isMatrix3 === true ) {
- return 'mat3';
- } else if ( value.isMatrix4 === true ) {
- return 'mat4';
- } else if ( value.isColor === true ) {
- return 'color';
- } else if ( value instanceof ArrayBuffer ) {
- return 'ArrayBuffer';
- }
- return null;
- }
- /**
- * Returns the value/object for the given data type and parameters.
- *
- * @private
- * @method
- * @param {string} type - The given type.
- * @param {...any} params - A parameter list.
- * @return {any} The value/object.
- */
- function getValueFromType( type, ...params ) {
- const last4 = type ? type.slice( -4 ) : undefined;
- if ( params.length === 1 ) { // ensure same behaviour as in NodeBuilder.format()
- if ( last4 === 'vec2' ) params = [ params[ 0 ], params[ 0 ] ];
- else if ( last4 === 'vec3' ) params = [ params[ 0 ], params[ 0 ], params[ 0 ] ];
- else if ( last4 === 'vec4' ) params = [ params[ 0 ], params[ 0 ], params[ 0 ], params[ 0 ] ];
- }
- if ( type === 'color' ) {
- return new Color( ...params );
- } else if ( last4 === 'vec2' ) {
- return new Vector2( ...params );
- } else if ( last4 === 'vec3' ) {
- return new Vector3( ...params );
- } else if ( last4 === 'vec4' ) {
- return new Vector4( ...params );
- } else if ( last4 === 'mat2' ) {
- return new Matrix2( ...params );
- } else if ( last4 === 'mat3' ) {
- return new Matrix3( ...params );
- } else if ( last4 === 'mat4' ) {
- return new Matrix4( ...params );
- } else if ( type === 'bool' ) {
- return params[ 0 ] || false;
- } else if ( ( type === 'float' ) || ( type === 'int' ) || ( type === 'uint' ) ) {
- return params[ 0 ] || 0;
- } else if ( type === 'string' ) {
- return params[ 0 ] || '';
- } else if ( type === 'ArrayBuffer' ) {
- return base64ToArrayBuffer( params[ 0 ] );
- }
- return null;
- }
- /**
- * Gets the object data that can be shared between different rendering steps.
- *
- * @private
- * @param {Object} object - The object to get the data for.
- * @return {Object} The object data.
- */
- function getDataFromObject( object ) {
- let data = dataFromObject.get( object );
- if ( data === undefined ) {
- data = {};
- dataFromObject.set( object, data );
- }
- return data;
- }
- /**
- * Converts the given array buffer to a Base64 string.
- *
- * @private
- * @method
- * @param {ArrayBuffer} arrayBuffer - The array buffer.
- * @return {string} The Base64 string.
- */
- function arrayBufferToBase64( arrayBuffer ) {
- let chars = '';
- const array = new Uint8Array( arrayBuffer );
- for ( let i = 0; i < array.length; i ++ ) {
- chars += String.fromCharCode( array[ i ] );
- }
- return btoa( chars );
- }
- /**
- * Converts the given Base64 string to an array buffer.
- *
- * @private
- * @method
- * @param {string} base64 - The Base64 string.
- * @return {ArrayBuffer} The array buffer.
- */
- function base64ToArrayBuffer( base64 ) {
- return Uint8Array.from( atob( base64 ), c => c.charCodeAt( 0 ) ).buffer;
- }
- var NodeUtils = /*#__PURE__*/Object.freeze({
- __proto__: null,
- arrayBufferToBase64: arrayBufferToBase64,
- base64ToArrayBuffer: base64ToArrayBuffer,
- getAlignmentFromType: getAlignmentFromType,
- getDataFromObject: getDataFromObject,
- getLengthFromType: getLengthFromType,
- getMemoryLengthFromType: getMemoryLengthFromType,
- getTypeFromLength: getTypeFromLength,
- getTypedArrayFromType: getTypedArrayFromType,
- getValueFromType: getValueFromType,
- getValueType: getValueType,
- hash: hash$1,
- hashArray: hashArray,
- hashString: hashString
- });
- /**
- * Possible shader stages.
- *
- * @property {string} VERTEX The vertex shader stage.
- * @property {string} FRAGMENT The fragment shader stage.
- */
- const NodeShaderStage = {
- VERTEX: 'vertex',
- FRAGMENT: 'fragment'
- };
- /**
- * Update types of a node.
- *
- * @property {string} NONE The update method is not executed.
- * @property {string} FRAME The update method is executed per frame.
- * @property {string} RENDER The update method is executed per render. A frame might be produced by multiple render calls so this value allows more detailed updates than FRAME.
- * @property {string} OBJECT The update method is executed per {@link Object3D} that uses the node for rendering.
- */
- const NodeUpdateType = {
- NONE: 'none',
- FRAME: 'frame',
- RENDER: 'render',
- OBJECT: 'object'
- };
- /**
- * Data types of a node.
- *
- * @property {string} BOOLEAN Boolean type.
- * @property {string} INTEGER Integer type.
- * @property {string} FLOAT Float type.
- * @property {string} VECTOR2 Two-dimensional vector type.
- * @property {string} VECTOR3 Three-dimensional vector type.
- * @property {string} VECTOR4 Four-dimensional vector type.
- * @property {string} MATRIX2 2x2 matrix type.
- * @property {string} MATRIX3 3x3 matrix type.
- * @property {string} MATRIX4 4x4 matrix type.
- */
- const NodeType = {
- BOOLEAN: 'bool',
- INTEGER: 'int',
- FLOAT: 'float',
- VECTOR2: 'vec2',
- VECTOR3: 'vec3',
- VECTOR4: 'vec4',
- MATRIX2: 'mat2',
- MATRIX3: 'mat3',
- MATRIX4: 'mat4'
- };
- /**
- * Access types of a node. These are relevant for compute and storage usage.
- *
- * @property {string} READ_ONLY Read-only access
- * @property {string} WRITE_ONLY Write-only access.
- * @property {string} READ_WRITE Read and write access.
- */
- const NodeAccess = {
- READ_ONLY: 'readOnly',
- WRITE_ONLY: 'writeOnly',
- READ_WRITE: 'readWrite',
- };
- const defaultShaderStages = [ 'fragment', 'vertex' ];
- const defaultBuildStages = [ 'setup', 'analyze', 'generate' ];
- const shaderStages = [ ...defaultShaderStages, 'compute' ];
- const vectorComponents = [ 'x', 'y', 'z', 'w' ];
- const _parentBuildStage = {
- analyze: 'setup',
- generate: 'analyze'
- };
- let _nodeId = 0;
- /**
- * Base class for all nodes.
- *
- * @augments EventDispatcher
- */
- class Node extends EventDispatcher {
- static get type() {
- return 'Node';
- }
- /**
- * Constructs a new node.
- *
- * @param {?string} nodeType - The node type.
- */
- constructor( nodeType = null ) {
- super();
- /**
- * The node type. This represents the result type of the node (e.g. `float` or `vec3`).
- *
- * @type {?string}
- * @default null
- */
- this.nodeType = nodeType;
- /**
- * The update type of the node's {@link Node#update} method. Possible values are listed in {@link NodeUpdateType}.
- *
- * @type {string}
- * @default 'none'
- */
- this.updateType = NodeUpdateType.NONE;
- /**
- * The update type of the node's {@link Node#updateBefore} method. Possible values are listed in {@link NodeUpdateType}.
- *
- * @type {string}
- * @default 'none'
- */
- this.updateBeforeType = NodeUpdateType.NONE;
- /**
- * The update type of the node's {@link Node#updateAfter} method. Possible values are listed in {@link NodeUpdateType}.
- *
- * @type {string}
- * @default 'none'
- */
- this.updateAfterType = NodeUpdateType.NONE;
- /**
- * The UUID of the node.
- *
- * @type {string}
- * @readonly
- */
- this.uuid = MathUtils.generateUUID();
- /**
- * The version of the node. The version automatically is increased when {@link Node#needsUpdate} is set to `true`.
- *
- * @type {number}
- * @readonly
- * @default 0
- */
- this.version = 0;
- /**
- * The name of the node.
- *
- * @type {string}
- * @default ''
- */
- this.name = '';
- /**
- * Whether this node is global or not. This property is relevant for the internal
- * node caching system. All nodes which should be declared just once should
- * set this flag to `true` (a typical example is {@link AttributeNode}).
- *
- * @type {boolean}
- * @default false
- */
- this.global = false;
- /**
- * Create a list of parents for this node during the build process.
- *
- * @type {boolean}
- * @default false
- */
- this.parents = false;
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isNode = true;
- // private
- this._beforeNodes = null;
- /**
- * The cache key of this node.
- *
- * @private
- * @type {?number}
- * @default null
- */
- this._cacheKey = null;
- /**
- * The cache key 's version.
- *
- * @private
- * @type {number}
- * @default 0
- */
- this._cacheKeyVersion = 0;
- Object.defineProperty( this, 'id', { value: _nodeId ++ } );
- }
- /**
- * Set this property to `true` when the node should be regenerated.
- *
- * @type {boolean}
- * @default false
- * @param {boolean} value
- */
- set needsUpdate( value ) {
- if ( value === true ) {
- this.version ++;
- }
- }
- /**
- * The type of the class. The value is usually the constructor name.
- *
- * @type {string}
- * @readonly
- */
- get type() {
- return this.constructor.type;
- }
- /**
- * Convenient method for defining {@link Node#update}.
- *
- * @param {Function} callback - The update method.
- * @param {string} updateType - The update type.
- * @return {Node} A reference to this node.
- */
- onUpdate( callback, updateType ) {
- this.updateType = updateType;
- this.update = callback.bind( this );
- return this;
- }
- /**
- * Convenient method for defining {@link Node#update}. Similar to {@link Node#onUpdate}, but
- * this method automatically sets the update type to `FRAME`.
- *
- * @param {Function} callback - The update method.
- * @return {Node} A reference to this node.
- */
- onFrameUpdate( callback ) {
- return this.onUpdate( callback, NodeUpdateType.FRAME );
- }
- /**
- * Convenient method for defining {@link Node#update}. Similar to {@link Node#onUpdate}, but
- * this method automatically sets the update type to `RENDER`.
- *
- * @param {Function} callback - The update method.
- * @return {Node} A reference to this node.
- */
- onRenderUpdate( callback ) {
- return this.onUpdate( callback, NodeUpdateType.RENDER );
- }
- /**
- * Convenient method for defining {@link Node#update}. Similar to {@link Node#onUpdate}, but
- * this method automatically sets the update type to `OBJECT`.
- *
- * @param {Function} callback - The update method.
- * @return {Node} A reference to this node.
- */
- onObjectUpdate( callback ) {
- return this.onUpdate( callback, NodeUpdateType.OBJECT );
- }
- /**
- * Convenient method for defining {@link Node#updateReference}.
- *
- * @param {Function} callback - The update method.
- * @return {Node} A reference to this node.
- */
- onReference( callback ) {
- this.updateReference = callback.bind( this );
- return this;
- }
- /**
- * Nodes might refer to other objects like materials. This method allows to dynamically update the reference
- * to such objects based on a given state (e.g. the current node frame or builder).
- *
- * @param {any} state - This method can be invocated in different contexts so `state` can refer to any object type.
- * @return {any} The updated reference.
- */
- updateReference( /*state*/ ) {
- return this;
- }
- /**
- * By default this method returns the value of the {@link Node#global} flag. This method
- * can be overwritten in derived classes if an analytical way is required to determine the
- * global cache referring to the current shader-stage.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {boolean} Whether this node is global or not.
- */
- isGlobal( /*builder*/ ) {
- return this.global;
- }
- /**
- * Generator function that can be used to iterate over the child nodes.
- *
- * @generator
- * @yields {Node} A child node.
- */
- * getChildren() {
- for ( const { childNode } of this._getChildren() ) {
- yield childNode;
- }
- }
- /**
- * Calling this method dispatches the `dispose` event. This event can be used
- * to register event listeners for clean up tasks.
- */
- dispose() {
- this.dispatchEvent( { type: 'dispose' } );
- }
- /**
- * Callback for {@link Node#traverse}.
- *
- * @callback traverseCallback
- * @param {Node} node - The current node.
- */
- /**
- * Can be used to traverse through the node's hierarchy.
- *
- * @param {traverseCallback} callback - A callback that is executed per node.
- */
- traverse( callback ) {
- callback( this );
- for ( const childNode of this.getChildren() ) {
- childNode.traverse( callback );
- }
- }
- /**
- * Returns the child nodes of this node.
- *
- * @private
- * @param {Set<Node>} [ignores=new Set()] - A set of nodes to ignore during the search to avoid circular references.
- * @returns {Array<Object>} An array of objects describing the child nodes.
- */
- _getChildren( ignores = new Set() ) {
- const children = [];
- // avoid circular references
- ignores.add( this );
- for ( const property of Object.getOwnPropertyNames( this ) ) {
- const object = this[ property ];
- // Ignore private properties and ignored nodes.
- if ( property.startsWith( '_' ) === true || ignores.has( object ) ) continue;
- if ( Array.isArray( object ) === true ) {
- for ( let i = 0; i < object.length; i ++ ) {
- const child = object[ i ];
- if ( child && child.isNode === true ) {
- children.push( { property, index: i, childNode: child } );
- }
- }
- } else if ( object && object.isNode === true ) {
- children.push( { property, childNode: object } );
- } else if ( object && Object.getPrototypeOf( object ) === Object.prototype ) {
- for ( const subProperty in object ) {
- // Ignore private sub-properties.
- if ( subProperty.startsWith( '_' ) === true ) continue;
- const child = object[ subProperty ];
- if ( child && child.isNode === true ) {
- children.push( { property, index: subProperty, childNode: child } );
- }
- }
- }
- }
- //
- return children;
- }
- /**
- * Returns the cache key for this node.
- *
- * @param {boolean} [force=false] - When set to `true`, a recomputation of the cache key is forced.
- * @param {Set<Node>} [ignores=null] - A set of nodes to ignore during the computation of the cache key.
- * @return {number} The cache key of the node.
- */
- getCacheKey( force = false, ignores = null ) {
- force = force || this.version !== this._cacheKeyVersion;
- if ( force === true || this._cacheKey === null ) {
- if ( ignores === null ) ignores = new Set();
- //
- const values = [];
- for ( const { property, childNode } of this._getChildren( ignores ) ) {
- values.push( hashString( property.slice( 0, -4 ) ), childNode.getCacheKey( force, ignores ) );
- }
- //
- this._cacheKey = hash$1( hashArray( values ), this.customCacheKey() );
- this._cacheKeyVersion = this.version;
- }
- return this._cacheKey;
- }
- /**
- * Generate a custom cache key for this node.
- *
- * @return {number} The cache key of the node.
- */
- customCacheKey() {
- return this.id;
- }
- /**
- * Returns the references to this node which is by default `this`.
- *
- * @return {Node} A reference to this node.
- */
- getScope() {
- return this;
- }
- /**
- * Returns the hash of the node which is used to identify the node. By default it's
- * the {@link Node#uuid} however derived node classes might have to overwrite this method
- * depending on their implementation.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {string} The hash.
- */
- getHash( /*builder*/ ) {
- return this.uuid;
- }
- /**
- * Returns the update type of {@link Node#update}.
- *
- * @return {NodeUpdateType} The update type.
- */
- getUpdateType() {
- return this.updateType;
- }
- /**
- * Returns the update type of {@link Node#updateBefore}.
- *
- * @return {NodeUpdateType} The update type.
- */
- getUpdateBeforeType() {
- return this.updateBeforeType;
- }
- /**
- * Returns the update type of {@link Node#updateAfter}.
- *
- * @return {NodeUpdateType} The update type.
- */
- getUpdateAfterType() {
- return this.updateAfterType;
- }
- /**
- * Certain types are composed of multiple elements. For example a `vec3`
- * is composed of three `float` values. This method returns the type of
- * these elements.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {string} The type of the node.
- */
- getElementType( builder ) {
- const type = this.getNodeType( builder );
- const elementType = builder.getElementType( type );
- return elementType;
- }
- /**
- * Returns the node member type for the given name.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @param {string} name - The name of the member.
- * @return {string} The type of the node.
- */
- getMemberType( /*builder, name*/ ) {
- return 'void';
- }
- /**
- * Returns the node's type.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {string} The type of the node.
- */
- getNodeType( builder ) {
- const nodeProperties = builder.getNodeProperties( this );
- if ( nodeProperties.outputNode ) {
- return nodeProperties.outputNode.getNodeType( builder );
- }
- return this.nodeType;
- }
- /**
- * This method is used during the build process of a node and ensures
- * equal nodes are not built multiple times but just once. For example if
- * `attribute( 'uv' )` is used multiple times by the user, the build
- * process makes sure to process just the first node.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {Node} The shared node if possible. Otherwise `this` is returned.
- */
- getShared( builder ) {
- const hash = this.getHash( builder );
- const nodeFromHash = builder.getNodeFromHash( hash );
- return nodeFromHash || this;
- }
- /**
- * Returns the number of elements in the node array.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {?number} The number of elements in the node array.
- */
- getArrayCount( /*builder*/ ) {
- return null;
- }
- /**
- * Represents the setup stage which is the first step of the build process, see {@link Node#build} method.
- * This method is often overwritten in derived modules to prepare the node which is used as a node's output/result.
- * If an output node is prepared, then it must be returned in the `return` statement of the derived module's setup function.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {?Node} The output node.
- */
- setup( builder ) {
- const nodeProperties = builder.getNodeProperties( this );
- let index = 0;
- for ( const childNode of this.getChildren() ) {
- nodeProperties[ 'node' + index ++ ] = childNode;
- }
- // return a outputNode if exists or null
- return nodeProperties.outputNode || null;
- }
- /**
- * Represents the analyze stage which is the second step of the build process, see {@link Node#build} method.
- * This stage analyzes the node hierarchy and ensures descendent nodes are built.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @param {?Node} output - The target output node.
- */
- analyze( builder, output = null ) {
- const usageCount = builder.increaseUsage( this );
- if ( this.parents === true ) {
- const nodeData = builder.getDataFromNode( this, 'any' );
- nodeData.stages = nodeData.stages || {};
- nodeData.stages[ builder.shaderStage ] = nodeData.stages[ builder.shaderStage ] || [];
- nodeData.stages[ builder.shaderStage ].push( output );
- }
- if ( usageCount === 1 ) {
- // node flow children
- const nodeProperties = builder.getNodeProperties( this );
- for ( const childNode of Object.values( nodeProperties ) ) {
- if ( childNode && childNode.isNode === true ) {
- childNode.build( builder, this );
- }
- }
- }
- }
- /**
- * Represents the generate stage which is the third step of the build process, see {@link Node#build} method.
- * This state builds the output node and returns the resulting shader string.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @param {?string} [output] - Can be used to define the output type.
- * @return {?string} The generated shader string.
- */
- generate( builder, output ) {
- const { outputNode } = builder.getNodeProperties( this );
- if ( outputNode && outputNode.isNode === true ) {
- return outputNode.build( builder, output );
- }
- }
- /**
- * The method can be implemented to update the node's internal state before it is used to render an object.
- * The {@link Node#updateBeforeType} property defines how often the update is executed.
- *
- * @abstract
- * @param {NodeFrame} frame - A reference to the current node frame.
- * @return {?boolean} An optional bool that indicates whether the implementation actually performed an update or not (e.g. due to caching).
- */
- updateBefore( /*frame*/ ) {
- warn( 'Abstract function.' );
- }
- /**
- * The method can be implemented to update the node's internal state after it was used to render an object.
- * The {@link Node#updateAfterType} property defines how often the update is executed.
- *
- * @abstract
- * @param {NodeFrame} frame - A reference to the current node frame.
- * @return {?boolean} An optional bool that indicates whether the implementation actually performed an update or not (e.g. due to caching).
- */
- updateAfter( /*frame*/ ) {
- warn( 'Abstract function.' );
- }
- /**
- * The method can be implemented to update the node's internal state when it is used to render an object.
- * The {@link Node#updateType} property defines how often the update is executed.
- *
- * @abstract
- * @param {NodeFrame} frame - A reference to the current node frame.
- * @return {?boolean} An optional bool that indicates whether the implementation actually performed an update or not (e.g. due to caching).
- */
- update( /*frame*/ ) {
- warn( 'Abstract function.' );
- }
- before( node ) {
- if ( this._beforeNodes === null ) this._beforeNodes = [];
- this._beforeNodes.push( node );
- return this;
- }
- /**
- * This method performs the build of a node. The behavior and return value depend on the current build stage:
- * - **setup**: Prepares the node and its children for the build process. This process can also create new nodes. Returns the node itself or a variant.
- * - **analyze**: Analyzes the node hierarchy for optimizations in the code generation stage. Returns `null`.
- * - **generate**: Generates the shader code for the node. Returns the generated shader string.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @param {?(string|Node)} [output=null] - Can be used to define the output type.
- * @return {?(Node|string)} The result of the build process, depending on the build stage.
- */
- build( builder, output = null ) {
- const refNode = this.getShared( builder );
- if ( this !== refNode ) {
- return refNode.build( builder, output );
- }
- //
- if ( this._beforeNodes !== null ) {
- const currentBeforeNodes = this._beforeNodes;
- this._beforeNodes = null;
- for ( const beforeNode of currentBeforeNodes ) {
- beforeNode.build( builder, output );
- }
- this._beforeNodes = currentBeforeNodes;
- }
- //
- const nodeData = builder.getDataFromNode( this );
- nodeData.buildStages = nodeData.buildStages || {};
- nodeData.buildStages[ builder.buildStage ] = true;
- const parentBuildStage = _parentBuildStage[ builder.buildStage ];
- if ( parentBuildStage && nodeData.buildStages[ parentBuildStage ] !== true ) {
- // force parent build stage (setup or analyze)
- const previousBuildStage = builder.getBuildStage();
- builder.setBuildStage( parentBuildStage );
- this.build( builder );
- builder.setBuildStage( previousBuildStage );
- }
- //
- builder.addNode( this );
- builder.addChain( this );
- /* Build stages expected results:
- - "setup" -> Node
- - "analyze" -> null
- - "generate" -> String
- */
- let result = null;
- const buildStage = builder.getBuildStage();
- if ( buildStage === 'setup' ) {
- this.updateReference( builder );
- const properties = builder.getNodeProperties( this );
- if ( properties.initialized !== true ) {
- //const stackNodesBeforeSetup = builder.stack.nodes.length;
- properties.initialized = true;
- properties.outputNode = this.setup( builder ) || properties.outputNode || null;
- /*if ( isNodeOutput && builder.stack.nodes.length !== stackNodesBeforeSetup ) {
- // !! no outputNode !!
- //outputNode = builder.stack;
- }*/
- for ( const childNode of Object.values( properties ) ) {
- if ( childNode && childNode.isNode === true ) {
- if ( childNode.parents === true ) {
- const childProperties = builder.getNodeProperties( childNode );
- childProperties.parents = childProperties.parents || [];
- childProperties.parents.push( this );
- }
- childNode.build( builder );
- }
- }
- }
- result = properties.outputNode;
- } else if ( buildStage === 'analyze' ) {
- this.analyze( builder, output );
- } else if ( buildStage === 'generate' ) {
- // If generate has just one argument, it means the output type is not required.
- // This means that the node does not handle output conversions internally,
- // so the value is stored in a cache and the builder handles the conversion
- // for all requested output types.
- const isGenerateOnce = this.generate.length < 2;
- if ( isGenerateOnce ) {
- const type = this.getNodeType( builder );
- const nodeData = builder.getDataFromNode( this );
- result = nodeData.snippet;
- if ( result === undefined ) {
- if ( nodeData.generated === undefined ) {
- nodeData.generated = true;
- result = this.generate( builder ) || '';
- nodeData.snippet = result;
- } else {
- warn( 'Node: Recursion detected.', this );
- result = '/* Recursion detected. */';
- }
- } else if ( nodeData.flowCodes !== undefined && builder.context.nodeBlock !== undefined ) {
- builder.addFlowCodeHierarchy( this, builder.context.nodeBlock );
- }
- result = builder.format( result, type, output );
- } else {
- result = this.generate( builder, output ) || '';
- }
- if ( result === '' && output !== null && output !== 'void' && output !== 'OutputType' ) {
- // if no snippet is generated, return a default value
- error( `TSL: Invalid generated code, expected a "${ output }".` );
- result = builder.generateConst( output );
- }
- }
- builder.removeChain( this );
- builder.addSequentialNode( this );
- return result;
- }
- /**
- * Returns the child nodes as a JSON object.
- *
- * @return {Generator<Object>} An iterable list of serialized child objects as JSON.
- */
- getSerializeChildren() {
- return this._getChildren();
- }
- /**
- * Serializes the node to JSON.
- *
- * @param {Object} json - The output JSON object.
- */
- serialize( json ) {
- const nodeChildren = this.getSerializeChildren();
- const inputNodes = {};
- for ( const { property, index, childNode } of nodeChildren ) {
- if ( index !== undefined ) {
- if ( inputNodes[ property ] === undefined ) {
- inputNodes[ property ] = Number.isInteger( index ) ? [] : {};
- }
- inputNodes[ property ][ index ] = childNode.toJSON( json.meta ).uuid;
- } else {
- inputNodes[ property ] = childNode.toJSON( json.meta ).uuid;
- }
- }
- if ( Object.keys( inputNodes ).length > 0 ) {
- json.inputNodes = inputNodes;
- }
- }
- /**
- * Deserializes the node from the given JSON.
- *
- * @param {Object} json - The JSON object.
- */
- deserialize( json ) {
- if ( json.inputNodes !== undefined ) {
- const nodes = json.meta.nodes;
- for ( const property in json.inputNodes ) {
- if ( Array.isArray( json.inputNodes[ property ] ) ) {
- const inputArray = [];
- for ( const uuid of json.inputNodes[ property ] ) {
- inputArray.push( nodes[ uuid ] );
- }
- this[ property ] = inputArray;
- } else if ( typeof json.inputNodes[ property ] === 'object' ) {
- const inputObject = {};
- for ( const subProperty in json.inputNodes[ property ] ) {
- const uuid = json.inputNodes[ property ][ subProperty ];
- inputObject[ subProperty ] = nodes[ uuid ];
- }
- this[ property ] = inputObject;
- } else {
- const uuid = json.inputNodes[ property ];
- this[ property ] = nodes[ uuid ];
- }
- }
- }
- }
- /**
- * Serializes the node into the three.js JSON Object/Scene format.
- *
- * @param {?Object} meta - An optional JSON object that already holds serialized data from other scene objects.
- * @return {Object} The serialized node.
- */
- toJSON( meta ) {
- const { uuid, type } = this;
- const isRoot = ( meta === undefined || typeof meta === 'string' );
- if ( isRoot ) {
- meta = {
- textures: {},
- images: {},
- nodes: {}
- };
- }
- // serialize
- let data = meta.nodes[ uuid ];
- if ( data === undefined ) {
- data = {
- uuid,
- type,
- meta,
- metadata: {
- version: 4.7,
- type: 'Node',
- generator: 'Node.toJSON'
- }
- };
- if ( isRoot !== true ) meta.nodes[ data.uuid ] = data;
- this.serialize( data );
- delete data.meta;
- }
- // TODO: Copied from Object3D.toJSON
- function extractFromCache( cache ) {
- const values = [];
- for ( const key in cache ) {
- const data = cache[ key ];
- delete data.metadata;
- values.push( data );
- }
- return values;
- }
- if ( isRoot ) {
- const textures = extractFromCache( meta.textures );
- const images = extractFromCache( meta.images );
- const nodes = extractFromCache( meta.nodes );
- if ( textures.length > 0 ) data.textures = textures;
- if ( images.length > 0 ) data.images = images;
- if ( nodes.length > 0 ) data.nodes = nodes;
- }
- return data;
- }
- }
- /**
- * Base class for representing element access on an array-like
- * node data structures.
- *
- * @augments Node
- */
- class ArrayElementNode extends Node { // @TODO: If extending from TempNode it breaks webgpu_compute
- static get type() {
- return 'ArrayElementNode';
- }
- /**
- * Constructs an array element node.
- *
- * @param {Node} node - The array-like node.
- * @param {Node} indexNode - The index node that defines the element access.
- */
- constructor( node, indexNode ) {
- super();
- /**
- * The array-like node.
- *
- * @type {Node}
- */
- this.node = node;
- /**
- * The index node that defines the element access.
- *
- * @type {Node}
- */
- this.indexNode = indexNode;
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isArrayElementNode = true;
- }
- /**
- * This method is overwritten since the node type is inferred from the array-like node.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {string} The node type.
- */
- getNodeType( builder ) {
- return this.node.getElementType( builder );
- }
- generate( builder ) {
- const indexType = this.indexNode.getNodeType( builder );
- const nodeSnippet = this.node.build( builder );
- const indexSnippet = this.indexNode.build( builder, ! builder.isVector( indexType ) && builder.isInteger( indexType ) ? indexType : 'uint' );
- return `${ nodeSnippet }[ ${ indexSnippet } ]`;
- }
- }
- /**
- * This module is part of the TSL core and usually not used in app level code.
- * It represents a convert operation during the shader generation process
- * meaning it converts the data type of a node to a target data type.
- *
- * @augments Node
- */
- class ConvertNode extends Node {
- static get type() {
- return 'ConvertNode';
- }
- /**
- * Constructs a new convert node.
- *
- * @param {Node} node - The node which type should be converted.
- * @param {string} convertTo - The target node type. Multiple types can be defined by separating them with a `|` sign.
- */
- constructor( node, convertTo ) {
- super();
- /**
- * The node which type should be converted.
- *
- * @type {Node}
- */
- this.node = node;
- /**
- * The target node type. Multiple types can be defined by separating them with a `|` sign.
- *
- * @type {string}
- */
- this.convertTo = convertTo;
- }
- /**
- * This method is overwritten since the implementation tries to infer the best
- * matching type from the {@link ConvertNode#convertTo} property.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {string} The node type.
- */
- getNodeType( builder ) {
- const requestType = this.node.getNodeType( builder );
- let convertTo = null;
- for ( const overloadingType of this.convertTo.split( '|' ) ) {
- if ( convertTo === null || builder.getTypeLength( requestType ) === builder.getTypeLength( overloadingType ) ) {
- convertTo = overloadingType;
- }
- }
- return convertTo;
- }
- serialize( data ) {
- super.serialize( data );
- data.convertTo = this.convertTo;
- }
- deserialize( data ) {
- super.deserialize( data );
- this.convertTo = data.convertTo;
- }
- generate( builder, output ) {
- const node = this.node;
- const type = this.getNodeType( builder );
- const snippet = node.build( builder, type );
- return builder.format( snippet, type, output );
- }
- }
- /**
- * This module uses cache management to create temporary variables
- * if the node is used more than once to prevent duplicate calculations.
- *
- * The class acts as a base class for many other nodes types.
- *
- * @augments Node
- */
- class TempNode extends Node {
- static get type() {
- return 'TempNode';
- }
- /**
- * Constructs a temp node.
- *
- * @param {?string} nodeType - The node type.
- */
- constructor( nodeType = null ) {
- super( nodeType );
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isTempNode = true;
- }
- /**
- * Whether this node is used more than once in context of other nodes.
- *
- * @param {NodeBuilder} builder - The node builder.
- * @return {boolean} A flag that indicates if there is more than one dependency to other nodes.
- */
- hasDependencies( builder ) {
- return builder.getDataFromNode( this ).usageCount > 1;
- }
- build( builder, output ) {
- const buildStage = builder.getBuildStage();
- if ( buildStage === 'generate' ) {
- const type = builder.getVectorType( this.getNodeType( builder, output ) );
- const nodeData = builder.getDataFromNode( this );
- if ( nodeData.propertyName !== undefined ) {
- return builder.format( nodeData.propertyName, type, output );
- } else if ( type !== 'void' && output !== 'void' && this.hasDependencies( builder ) ) {
- const snippet = super.build( builder, type );
- const nodeVar = builder.getVarFromNode( this, null, type );
- const propertyName = builder.getPropertyName( nodeVar );
- builder.addLineFlowCode( `${ propertyName } = ${ snippet }`, this );
- nodeData.snippet = snippet;
- nodeData.propertyName = propertyName;
- return builder.format( nodeData.propertyName, type, output );
- }
- }
- return super.build( builder, output );
- }
- }
- /**
- * This module is part of the TSL core and usually not used in app level code.
- * It represents a join operation during the shader generation process.
- * For example in can compose/join two single floats into a `vec2` type.
- *
- * @augments TempNode
- */
- class JoinNode extends TempNode {
- static get type() {
- return 'JoinNode';
- }
- /**
- * Constructs a new join node.
- *
- * @param {Array<Node>} nodes - An array of nodes that should be joined.
- * @param {?string} [nodeType=null] - The node type.
- */
- constructor( nodes = [], nodeType = null ) {
- super( nodeType );
- /**
- * An array of nodes that should be joined.
- *
- * @type {Array<Node>}
- */
- this.nodes = nodes;
- }
- /**
- * This method is overwritten since the node type must be inferred from the
- * joined data length if not explicitly defined.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {string} The node type.
- */
- getNodeType( builder ) {
- if ( this.nodeType !== null ) {
- return builder.getVectorType( this.nodeType );
- }
- return builder.getTypeFromLength( this.nodes.reduce( ( count, cur ) => count + builder.getTypeLength( cur.getNodeType( builder ) ), 0 ) );
- }
- generate( builder, output ) {
- const type = this.getNodeType( builder );
- const maxLength = builder.getTypeLength( type );
- const nodes = this.nodes;
- const primitiveType = builder.getComponentType( type );
- const snippetValues = [];
- let length = 0;
- for ( const input of nodes ) {
- if ( length >= maxLength ) {
- error( `TSL: Length of parameters exceeds maximum length of function '${ type }()' type.` );
- break;
- }
- let inputType = input.getNodeType( builder );
- let inputTypeLength = builder.getTypeLength( inputType );
- let inputSnippet;
- if ( length + inputTypeLength > maxLength ) {
- error( `TSL: Length of '${ type }()' data exceeds maximum length of output type.` );
- inputTypeLength = maxLength - length;
- inputType = builder.getTypeFromLength( inputTypeLength );
- }
- length += inputTypeLength;
- inputSnippet = input.build( builder, inputType );
- const inputPrimitiveType = builder.getComponentType( inputType );
- if ( inputPrimitiveType !== primitiveType ) {
- const targetType = builder.getTypeFromLength( inputTypeLength, primitiveType );
- inputSnippet = builder.format( inputSnippet, inputType, targetType );
- }
- snippetValues.push( inputSnippet );
- }
- const snippet = `${ builder.getType( type ) }( ${ snippetValues.join( ', ' ) } )`;
- return builder.format( snippet, type, output );
- }
- }
- const _stringVectorComponents = vectorComponents.join( '' );
- /**
- * This module is part of the TSL core and usually not used in app level code.
- * `SplitNode` represents a property access operation which means it is
- * used to implement any `.xyzw`, `.rgba` and `stpq` usage on node objects.
- * For example:
- * ```js
- * const redValue = color.r;
- * ```
- *
- * @augments Node
- */
- class SplitNode extends Node {
- static get type() {
- return 'SplitNode';
- }
- /**
- * Constructs a new split node.
- *
- * @param {Node} node - The node that should be accessed.
- * @param {string} [components='x'] - The components that should be accessed.
- */
- constructor( node, components = 'x' ) {
- super();
- /**
- * The node that should be accessed.
- *
- * @type {Node}
- */
- this.node = node;
- /**
- * The components that should be accessed.
- *
- * @type {string}
- */
- this.components = components;
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isSplitNode = true;
- }
- /**
- * Returns the vector length which is computed based on the requested components.
- *
- * @return {number} The vector length.
- */
- getVectorLength() {
- let vectorLength = this.components.length;
- for ( const c of this.components ) {
- vectorLength = Math.max( vectorComponents.indexOf( c ) + 1, vectorLength );
- }
- return vectorLength;
- }
- /**
- * Returns the component type of the node's type.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {string} The component type.
- */
- getComponentType( builder ) {
- return builder.getComponentType( this.node.getNodeType( builder ) );
- }
- /**
- * This method is overwritten since the node type is inferred from requested components.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {string} The node type.
- */
- getNodeType( builder ) {
- return builder.getTypeFromLength( this.components.length, this.getComponentType( builder ) );
- }
- /**
- * Returns the scope of the node.
- *
- * @return {Node} The scope of the node.
- */
- getScope() {
- return this.node.getScope();
- }
- generate( builder, output ) {
- const node = this.node;
- const nodeTypeLength = builder.getTypeLength( node.getNodeType( builder ) );
- let snippet = null;
- if ( nodeTypeLength > 1 ) {
- let type = null;
- const componentsLength = this.getVectorLength();
- if ( componentsLength >= nodeTypeLength ) {
- // needed expand the input node
- type = builder.getTypeFromLength( this.getVectorLength(), this.getComponentType( builder ) );
- }
- const nodeSnippet = node.build( builder, type );
- if ( this.components.length === nodeTypeLength && this.components === _stringVectorComponents.slice( 0, this.components.length ) ) {
- // unnecessary swizzle
- snippet = builder.format( nodeSnippet, type, output );
- } else {
- snippet = builder.format( `${nodeSnippet}.${this.components}`, this.getNodeType( builder ), output );
- }
- } else {
- // ignore .components if .node returns float/integer
- snippet = node.build( builder, output );
- }
- return snippet;
- }
- serialize( data ) {
- super.serialize( data );
- data.components = this.components;
- }
- deserialize( data ) {
- super.deserialize( data );
- this.components = data.components;
- }
- }
- /**
- * This module is part of the TSL core and usually not used in app level code.
- * `SetNode` represents a set operation which means it is used to implement any
- * `setXYZW()`, `setRGBA()` and `setSTPQ()` method invocations on node objects.
- * For example:
- * ```js
- * materialLine.colorNode = color( 0, 0, 0 ).setR( float( 1 ) );
- * ```
- *
- * @augments TempNode
- */
- class SetNode extends TempNode {
- static get type() {
- return 'SetNode';
- }
- /**
- * Constructs a new set node.
- *
- * @param {Node} sourceNode - The node that should be updated.
- * @param {string} components - The components that should be updated.
- * @param {Node} targetNode - The value node.
- */
- constructor( sourceNode, components, targetNode ) {
- super();
- /**
- * The node that should be updated.
- *
- * @type {Node}
- */
- this.sourceNode = sourceNode;
- /**
- * The components that should be updated.
- *
- * @type {string}
- */
- this.components = components;
- /**
- * The value node.
- *
- * @type {Node}
- */
- this.targetNode = targetNode;
- }
- /**
- * This method is overwritten since the node type is inferred from {@link SetNode#sourceNode}.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {string} The node type.
- */
- getNodeType( builder ) {
- return this.sourceNode.getNodeType( builder );
- }
- generate( builder ) {
- const { sourceNode, components, targetNode } = this;
- const sourceType = this.getNodeType( builder );
- const componentType = builder.getComponentType( targetNode.getNodeType( builder ) );
- const targetType = builder.getTypeFromLength( components.length, componentType );
- const targetSnippet = targetNode.build( builder, targetType );
- const sourceSnippet = sourceNode.build( builder, sourceType );
- const length = builder.getTypeLength( sourceType );
- const snippetValues = [];
- for ( let i = 0; i < length; i ++ ) {
- const component = vectorComponents[ i ];
- if ( component === components[ 0 ] ) {
- snippetValues.push( targetSnippet );
- i += components.length - 1;
- } else {
- snippetValues.push( sourceSnippet + '.' + component );
- }
- }
- return `${ builder.getType( sourceType ) }( ${ snippetValues.join( ', ' ) } )`;
- }
- }
- /**
- * This module is part of the TSL core and usually not used in app level code.
- * It represents a flip operation during the shader generation process
- * meaning it flips normalized values with the following formula:
- * ```
- * x = 1 - x;
- * ```
- * `FlipNode` is internally used to implement any `flipXYZW()`, `flipRGBA()` and
- * `flipSTPQ()` method invocations on node objects. For example:
- * ```js
- * uvNode = uvNode.flipY();
- * ```
- *
- * @augments TempNode
- */
- class FlipNode extends TempNode {
- static get type() {
- return 'FlipNode';
- }
- /**
- * Constructs a new flip node.
- *
- * @param {Node} sourceNode - The node which component(s) should be flipped.
- * @param {string} components - The components that should be flipped e.g. `'x'` or `'xy'`.
- */
- constructor( sourceNode, components ) {
- super();
- /**
- * The node which component(s) should be flipped.
- *
- * @type {Node}
- */
- this.sourceNode = sourceNode;
- /**
- * The components that should be flipped e.g. `'x'` or `'xy'`.
- *
- * @type {string}
- */
- this.components = components;
- }
- /**
- * This method is overwritten since the node type is inferred from the source node.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {string} The node type.
- */
- getNodeType( builder ) {
- return this.sourceNode.getNodeType( builder );
- }
- generate( builder ) {
- const { components, sourceNode } = this;
- const sourceType = this.getNodeType( builder );
- const sourceSnippet = sourceNode.build( builder );
- const sourceCache = builder.getVarFromNode( this );
- const sourceProperty = builder.getPropertyName( sourceCache );
- builder.addLineFlowCode( sourceProperty + ' = ' + sourceSnippet, this );
- const length = builder.getTypeLength( sourceType );
- const snippetValues = [];
- let componentIndex = 0;
- for ( let i = 0; i < length; i ++ ) {
- const component = vectorComponents[ i ];
- if ( component === components[ componentIndex ] ) {
- snippetValues.push( '1.0 - ' + ( sourceProperty + '.' + component ) );
- componentIndex ++;
- } else {
- snippetValues.push( sourceProperty + '.' + component );
- }
- }
- return `${ builder.getType( sourceType ) }( ${ snippetValues.join( ', ' ) } )`;
- }
- }
- /**
- * Base class for representing data input nodes.
- *
- * @augments Node
- */
- class InputNode extends Node {
- static get type() {
- return 'InputNode';
- }
- /**
- * Constructs a new input node.
- *
- * @param {any} value - The value of this node. This can be any JS primitive, functions, array buffers or even three.js objects (vector, matrices, colors).
- * @param {?string} nodeType - The node type. If no explicit type is defined, the node tries to derive the type from its value.
- */
- constructor( value, nodeType = null ) {
- super( nodeType );
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isInputNode = true;
- /**
- * The value of this node. This can be any JS primitive, functions, array buffers or even three.js objects (vector, matrices, colors).
- *
- * @type {any}
- */
- this.value = value;
- /**
- * The precision of the value in the shader.
- *
- * @type {?('low'|'medium'|'high')}
- * @default null
- */
- this.precision = null;
- }
- getNodeType( /*builder*/ ) {
- if ( this.nodeType === null ) {
- return getValueType( this.value );
- }
- return this.nodeType;
- }
- /**
- * Returns the input type of the node which is by default the node type. Derived modules
- * might overwrite this method and use a fixed type or compute one analytically.
- *
- * A typical example for different input and node types are textures. The input type of a
- * normal RGBA texture is `texture` whereas its node type is `vec4`.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {string} The input type.
- */
- getInputType( builder ) {
- return this.getNodeType( builder );
- }
- /**
- * Sets the precision to the given value. The method can be
- * overwritten in derived classes if the final precision must be computed
- * analytically.
- *
- * @param {('low'|'medium'|'high')} precision - The precision of the input value in the shader.
- * @return {InputNode} A reference to this node.
- */
- setPrecision( precision ) {
- this.precision = precision;
- return this;
- }
- serialize( data ) {
- super.serialize( data );
- data.value = this.value;
- if ( this.value && this.value.toArray ) data.value = this.value.toArray();
- data.valueType = getValueType( this.value );
- data.nodeType = this.nodeType;
- if ( data.valueType === 'ArrayBuffer' ) data.value = arrayBufferToBase64( data.value );
- data.precision = this.precision;
- }
- deserialize( data ) {
- super.deserialize( data );
- this.nodeType = data.nodeType;
- this.value = Array.isArray( data.value ) ? getValueFromType( data.valueType, ...data.value ) : data.value;
- this.precision = data.precision || null;
- if ( this.value && this.value.fromArray ) this.value = this.value.fromArray( data.value );
- }
- generate( /*builder, output*/ ) {
- warn( 'Abstract function.' );
- }
- }
- const _regNum = /float|u?int/;
- /**
- * Class for representing a constant value in the shader.
- *
- * @augments InputNode
- */
- class ConstNode extends InputNode {
- static get type() {
- return 'ConstNode';
- }
- /**
- * Constructs a new input node.
- *
- * @param {any} value - The value of this node. Usually a JS primitive or three.js object (vector, matrix, color).
- * @param {?string} nodeType - The node type. If no explicit type is defined, the node tries to derive the type from its value.
- */
- constructor( value, nodeType = null ) {
- super( value, nodeType );
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isConstNode = true;
- }
- /**
- * Generates the shader string of the value with the current node builder.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {string} The generated value as a shader string.
- */
- generateConst( builder ) {
- return builder.generateConst( this.getNodeType( builder ), this.value );
- }
- generate( builder, output ) {
- const type = this.getNodeType( builder );
- if ( _regNum.test( type ) && _regNum.test( output ) ) {
- return builder.generateConst( output, this.value );
- }
- return builder.format( this.generateConst( builder ), type, output );
- }
- }
- /**
- * Base class for representing member access on an object-like
- * node data structures.
- *
- * @augments Node
- */
- class MemberNode extends Node {
- static get type() {
- return 'MemberNode';
- }
- /**
- * Constructs a member node.
- *
- * @param {Node} structNode - The struct node.
- * @param {string} property - The property name.
- */
- constructor( structNode, property ) {
- super();
- /**
- * The struct node.
- *
- * @type {Node}
- */
- this.structNode = structNode;
- /**
- * The property name.
- *
- * @type {Node}
- */
- this.property = property;
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isMemberNode = true;
- }
- hasMember( builder ) {
- if ( this.structNode.isMemberNode ) {
- if ( this.structNode.hasMember( builder ) === false ) {
- return false;
- }
- }
- return this.structNode.getMemberType( builder, this.property ) !== 'void';
- }
- getNodeType( builder ) {
- if ( this.hasMember( builder ) === false ) {
- // default type if member does not exist
- return 'float';
- }
- return this.structNode.getMemberType( builder, this.property );
- }
- getMemberType( builder, name ) {
- if ( this.hasMember( builder ) === false ) {
- // default type if member does not exist
- return 'float';
- }
- const type = this.getNodeType( builder );
- const struct = builder.getStructTypeNode( type );
- return struct.getMemberType( builder, name );
- }
- generate( builder ) {
- if ( this.hasMember( builder ) === false ) {
- warn( `TSL: Member "${ this.property }" does not exist in struct.` );
- const type = this.getNodeType( builder );
- return builder.generateConst( type );
- }
- const propertyName = this.structNode.build( builder );
- return propertyName + '.' + this.property;
- }
- }
- let currentStack = null;
- const NodeElements = new Map();
- // Extend Node Class for TSL using prototype
- function addMethodChaining( name, nodeElement ) {
- if ( NodeElements.has( name ) ) {
- warn( `TSL: Redefinition of method chaining '${ name }'.` );
- return;
- }
- if ( typeof nodeElement !== 'function' ) throw new Error( `THREE.TSL: Node element ${ name } is not a function` );
- NodeElements.set( name, nodeElement );
- if ( name !== 'assign' ) {
- // Changing Node prototype to add method chaining
- Node.prototype[ name ] = function ( ...params ) {
- //if ( name === 'toVarIntent' ) return this;
- return this.isStackNode ? this.addToStack( nodeElement( ...params ) ) : nodeElement( this, ...params );
- };
- // Adding assign method chaining
- Node.prototype[ name + 'Assign' ] = function ( ...params ) {
- return this.isStackNode ? this.assign( params[ 0 ], nodeElement( ...params ) ) : this.assign( nodeElement( this, ...params ) );
- };
- }
- }
- const parseSwizzle = ( props ) => props.replace( /r|s/g, 'x' ).replace( /g|t/g, 'y' ).replace( /b|p/g, 'z' ).replace( /a|q/g, 'w' );
- const parseSwizzleAndSort = ( props ) => parseSwizzle( props ).split( '' ).sort().join( '' );
- Node.prototype.assign = function ( ...params ) {
- if ( this.isStackNode !== true ) {
- if ( currentStack !== null ) {
- currentStack.assign( this, ...params );
- } else {
- error( 'TSL: No stack defined for assign operation. Make sure the assign is inside a Fn().' );
- }
- return this;
- } else {
- const nodeElement = NodeElements.get( 'assign' );
- return this.addToStack( nodeElement( ...params ) );
- }
- };
- Node.prototype.toVarIntent = function () {
- return this;
- };
- Node.prototype.get = function ( value ) {
- return new MemberNode( this, value );
- };
- // Cache prototype for TSL
- const proto = {};
- // Set swizzle properties for xyzw, rgba, and stpq.
- function setProtoSwizzle( property, altA, altB ) {
- // swizzle properties
- proto[ property ] = proto[ altA ] = proto[ altB ] = {
- get() {
- this._cache = this._cache || {};
- //
- let split = this._cache[ property ];
- if ( split === undefined ) {
- split = new SplitNode( this, property );
- this._cache[ property ] = split;
- }
- return split;
- },
- set( value ) {
- this[ property ].assign( nodeObject( value ) );
- }
- };
- // set properties ( swizzle ) and sort to xyzw sequence
- const propUpper = property.toUpperCase();
- const altAUpper = altA.toUpperCase();
- const altBUpper = altB.toUpperCase();
- // Set methods for swizzle properties
- Node.prototype[ 'set' + propUpper ] = Node.prototype[ 'set' + altAUpper ] = Node.prototype[ 'set' + altBUpper ] = function ( value ) {
- const swizzle = parseSwizzleAndSort( property );
- return new SetNode( this, swizzle, nodeObject( value ) );
- };
- // Set methods for flip properties
- Node.prototype[ 'flip' + propUpper ] = Node.prototype[ 'flip' + altAUpper ] = Node.prototype[ 'flip' + altBUpper ] = function () {
- const swizzle = parseSwizzleAndSort( property );
- return new FlipNode( this, swizzle );
- };
- }
- const swizzleA = [ 'x', 'y', 'z', 'w' ];
- const swizzleB = [ 'r', 'g', 'b', 'a' ];
- const swizzleC = [ 's', 't', 'p', 'q' ];
- for ( let a = 0; a < 4; a ++ ) {
- let prop = swizzleA[ a ];
- let altA = swizzleB[ a ];
- let altB = swizzleC[ a ];
- setProtoSwizzle( prop, altA, altB );
- for ( let b = 0; b < 4; b ++ ) {
- prop = swizzleA[ a ] + swizzleA[ b ];
- altA = swizzleB[ a ] + swizzleB[ b ];
- altB = swizzleC[ a ] + swizzleC[ b ];
- setProtoSwizzle( prop, altA, altB );
- for ( let c = 0; c < 4; c ++ ) {
- prop = swizzleA[ a ] + swizzleA[ b ] + swizzleA[ c ];
- altA = swizzleB[ a ] + swizzleB[ b ] + swizzleB[ c ];
- altB = swizzleC[ a ] + swizzleC[ b ] + swizzleC[ c ];
- setProtoSwizzle( prop, altA, altB );
- for ( let d = 0; d < 4; d ++ ) {
- prop = swizzleA[ a ] + swizzleA[ b ] + swizzleA[ c ] + swizzleA[ d ];
- altA = swizzleB[ a ] + swizzleB[ b ] + swizzleB[ c ] + swizzleB[ d ];
- altB = swizzleC[ a ] + swizzleC[ b ] + swizzleC[ c ] + swizzleC[ d ];
- setProtoSwizzle( prop, altA, altB );
- }
- }
- }
- }
- // Set/get static properties for array elements (0-31).
- for ( let i = 0; i < 32; i ++ ) {
- proto[ i ] = {
- get() {
- this._cache = this._cache || {};
- //
- let element = this._cache[ i ];
- if ( element === undefined ) {
- element = new ArrayElementNode( this, new ConstNode( i, 'uint' ) );
- this._cache[ i ] = element;
- }
- return element;
- },
- set( value ) {
- this[ i ].assign( nodeObject( value ) );
- }
- };
- }
- /*
- // Set properties for width, height, and depth.
- function setProtoProperty( property, target ) {
- proto[ property ] = {
- get() {
- this._cache = this._cache || {};
- //
- let split = this._cache[ target ];
- if ( split === undefined ) {
- split = new SplitNode( this, target );
- this._cache[ target ] = split;
- }
- return split;
- },
- set( value ) {
- this[ target ].assign( nodeObject( value ) );
- }
- };
- }
- setProtoProperty( 'width', 'x' );
- setProtoProperty( 'height', 'y' );
- setProtoProperty( 'depth', 'z' );
- */
- Object.defineProperties( Node.prototype, proto );
- // --- FINISH ---
- const nodeBuilderFunctionsCacheMap = new WeakMap();
- const ShaderNodeObject = function ( obj, altType = null ) {
- const type = getValueType( obj );
- if ( type === 'node' ) {
- return obj;
- } else if ( ( altType === null && ( type === 'float' || type === 'boolean' ) ) || ( type && type !== 'shader' && type !== 'string' ) ) {
- return nodeObject( getConstNode( obj, altType ) );
- } else if ( type === 'shader' ) {
- return obj.isFn ? obj : Fn( obj );
- }
- return obj;
- };
- const ShaderNodeObjects = function ( objects, altType = null ) {
- for ( const name in objects ) {
- objects[ name ] = nodeObject( objects[ name ], altType );
- }
- return objects;
- };
- const ShaderNodeArray = function ( array, altType = null ) {
- const len = array.length;
- for ( let i = 0; i < len; i ++ ) {
- array[ i ] = nodeObject( array[ i ], altType );
- }
- return array;
- };
- const ShaderNodeProxy = function ( NodeClass, scope = null, factor = null, settings = null ) {
- function assignNode( node ) {
- if ( settings !== null ) {
- node = nodeObject( Object.assign( node, settings ) );
- if ( settings.intent === true ) {
- node = node.toVarIntent();
- }
- } else {
- node = nodeObject( node );
- }
- return node;
- }
- let fn, name = scope, minParams, maxParams;
- function verifyParamsLimit( params ) {
- let tslName;
- if ( name ) tslName = /[a-z]/i.test( name ) ? name + '()' : name;
- else tslName = NodeClass.type;
- if ( minParams !== undefined && params.length < minParams ) {
- error( `TSL: "${ tslName }" parameter length is less than minimum required.` );
- return params.concat( new Array( minParams - params.length ).fill( 0 ) );
- } else if ( maxParams !== undefined && params.length > maxParams ) {
- error( `TSL: "${ tslName }" parameter length exceeds limit.` );
- return params.slice( 0, maxParams );
- }
- return params;
- }
- if ( scope === null ) {
- fn = ( ...params ) => {
- return assignNode( new NodeClass( ...nodeArray( verifyParamsLimit( params ) ) ) );
- };
- } else if ( factor !== null ) {
- factor = nodeObject( factor );
- fn = ( ...params ) => {
- return assignNode( new NodeClass( scope, ...nodeArray( verifyParamsLimit( params ) ), factor ) );
- };
- } else {
- fn = ( ...params ) => {
- return assignNode( new NodeClass( scope, ...nodeArray( verifyParamsLimit( params ) ) ) );
- };
- }
- fn.setParameterLength = ( ...params ) => {
- if ( params.length === 1 ) minParams = maxParams = params[ 0 ];
- else if ( params.length === 2 ) [ minParams, maxParams ] = params;
- return fn;
- };
- fn.setName = ( value ) => {
- name = value;
- return fn;
- };
- return fn;
- };
- const ShaderNodeImmutable = function ( NodeClass, ...params ) {
- return nodeObject( new NodeClass( ...nodeArray( params ) ) );
- };
- class ShaderCallNodeInternal extends Node {
- constructor( shaderNode, rawInputs ) {
- super();
- this.shaderNode = shaderNode;
- this.rawInputs = rawInputs;
- this.isShaderCallNodeInternal = true;
- }
- getNodeType( builder ) {
- return this.shaderNode.nodeType || this.getOutputNode( builder ).getNodeType( builder );
- }
- getElementType( builder ) {
- return this.getOutputNode( builder ).getElementType( builder );
- }
- getMemberType( builder, name ) {
- return this.getOutputNode( builder ).getMemberType( builder, name );
- }
- call( builder ) {
- const { shaderNode, rawInputs } = this;
- const properties = builder.getNodeProperties( shaderNode );
- const subBuild = builder.getClosestSubBuild( shaderNode.subBuilds ) || '';
- const subBuildProperty = subBuild || 'default';
- if ( properties[ subBuildProperty ] ) {
- return properties[ subBuildProperty ];
- }
- //
- const previousSubBuildFn = builder.subBuildFn;
- const previousFnCall = builder.fnCall;
- builder.subBuildFn = subBuild;
- builder.fnCall = this;
- let result = null;
- if ( shaderNode.layout ) {
- let functionNodesCacheMap = nodeBuilderFunctionsCacheMap.get( builder.constructor );
- if ( functionNodesCacheMap === undefined ) {
- functionNodesCacheMap = new WeakMap();
- nodeBuilderFunctionsCacheMap.set( builder.constructor, functionNodesCacheMap );
- }
- let functionNode = functionNodesCacheMap.get( shaderNode );
- if ( functionNode === undefined ) {
- functionNode = nodeObject( builder.buildFunctionNode( shaderNode ) );
- functionNodesCacheMap.set( shaderNode, functionNode );
- }
- builder.addInclude( functionNode );
- //
- const inputs = rawInputs ? getLayoutParameters( rawInputs ) : null;
- result = nodeObject( functionNode.call( inputs ) );
- } else {
- const secureNodeBuilder = new Proxy( builder, {
- get: ( target, property, receiver ) => {
- let value;
- if ( Symbol.iterator === property ) {
- value = function* () {
- yield undefined;
- };
- } else {
- value = Reflect.get( target, property, receiver );
- }
- return value;
- }
- } );
- //
- const inputs = rawInputs ? getProxyParameters( rawInputs ) : null;
- const hasParameters = Array.isArray( rawInputs ) ? rawInputs.length > 0 : rawInputs !== null;
- const jsFunc = shaderNode.jsFunc;
- const outputNode = hasParameters || jsFunc.length > 1 ? jsFunc( inputs, secureNodeBuilder ) : jsFunc( secureNodeBuilder );
- result = nodeObject( outputNode );
- }
- builder.subBuildFn = previousSubBuildFn;
- builder.fnCall = previousFnCall;
- if ( shaderNode.once ) {
- properties[ subBuildProperty ] = result;
- }
- return result;
- }
- setupOutput( builder ) {
- builder.addStack();
- builder.stack.outputNode = this.call( builder );
- return builder.removeStack();
- }
- getOutputNode( builder ) {
- const properties = builder.getNodeProperties( this );
- const subBuildOutput = builder.getSubBuildOutput( this );
- properties[ subBuildOutput ] = properties[ subBuildOutput ] || this.setupOutput( builder );
- properties[ subBuildOutput ].subBuild = builder.getClosestSubBuild( this );
- return properties[ subBuildOutput ];
- }
- build( builder, output = null ) {
- let result = null;
- const buildStage = builder.getBuildStage();
- const properties = builder.getNodeProperties( this );
- const subBuildOutput = builder.getSubBuildOutput( this );
- const outputNode = this.getOutputNode( builder );
- const previousFnCall = builder.fnCall;
- builder.fnCall = this;
- if ( buildStage === 'setup' ) {
- const subBuildInitialized = builder.getSubBuildProperty( 'initialized', this );
- if ( properties[ subBuildInitialized ] !== true ) {
- properties[ subBuildInitialized ] = true;
- properties[ subBuildOutput ] = this.getOutputNode( builder );
- properties[ subBuildOutput ].build( builder );
- // If the shaderNode has subBuilds, add them to the chaining nodes
- // so they can be built later in the build process.
- if ( this.shaderNode.subBuilds ) {
- for ( const node of builder.chaining ) {
- const nodeData = builder.getDataFromNode( node, 'any' );
- nodeData.subBuilds = nodeData.subBuilds || new Set();
- for ( const subBuild of this.shaderNode.subBuilds ) {
- nodeData.subBuilds.add( subBuild );
- }
- //builder.getDataFromNode( node ).subBuilds = nodeData.subBuilds;
- }
- }
- }
- result = properties[ subBuildOutput ];
- } else if ( buildStage === 'analyze' ) {
- outputNode.build( builder, output );
- } else if ( buildStage === 'generate' ) {
- result = outputNode.build( builder, output ) || '';
- }
- builder.fnCall = previousFnCall;
- return result;
- }
- }
- function getLayoutParameters( params ) {
- let output;
- nodeObjects( params );
- const isArrayAsParameter = params[ 0 ] && ( params[ 0 ].isNode || Object.getPrototypeOf( params[ 0 ] ) !== Object.prototype );
- if ( isArrayAsParameter ) {
- output = [ ...params ];
- } else {
- output = params[ 0 ];
- }
- return output;
- }
- function getProxyParameters( params ) {
- let index = 0;
- nodeObjects( params );
- return new Proxy( params, {
- get: ( target, property, receiver ) => {
- let value;
- if ( property === 'length' ) {
- value = params.length;
- return value;
- }
- if ( Symbol.iterator === property ) {
- value = function* () {
- for ( const inputNode of params ) {
- yield nodeObject( inputNode );
- }
- };
- } else {
- if ( params.length > 0 ) {
- if ( Object.getPrototypeOf( params[ 0 ] ) === Object.prototype ) {
- const objectTarget = params[ 0 ];
- if ( objectTarget[ property ] === undefined ) {
- value = objectTarget[ index ++ ];
- } else {
- value = Reflect.get( objectTarget, property, receiver );
- }
- } else if ( params[ 0 ] instanceof Node ) {
- if ( params[ property ] === undefined ) {
- value = params[ index ++ ];
- } else {
- value = Reflect.get( params, property, receiver );
- }
- }
- } else {
- value = Reflect.get( target, property, receiver );
- }
- value = nodeObject( value );
- }
- return value;
- }
- } );
- }
- class ShaderNodeInternal extends Node {
- constructor( jsFunc, nodeType ) {
- super( nodeType );
- this.jsFunc = jsFunc;
- this.layout = null;
- this.global = true;
- this.once = false;
- }
- setLayout( layout ) {
- this.layout = layout;
- return this;
- }
- getLayout() {
- return this.layout;
- }
- call( rawInputs = null ) {
- return new ShaderCallNodeInternal( this, rawInputs );
- }
- setup() {
- return this.call();
- }
- }
- const bools = [ false, true ];
- const uints = [ 0, 1, 2, 3 ];
- const ints = [ -1, -2 ];
- const floats = [ 0.5, 1.5, 1 / 3, 1e-6, 1e6, Math.PI, Math.PI * 2, 1 / Math.PI, 2 / Math.PI, 1 / ( Math.PI * 2 ), Math.PI / 2 ];
- const boolsCacheMap = new Map();
- for ( const bool of bools ) boolsCacheMap.set( bool, new ConstNode( bool ) );
- const uintsCacheMap = new Map();
- for ( const uint of uints ) uintsCacheMap.set( uint, new ConstNode( uint, 'uint' ) );
- const intsCacheMap = new Map( [ ...uintsCacheMap ].map( el => new ConstNode( el.value, 'int' ) ) );
- for ( const int of ints ) intsCacheMap.set( int, new ConstNode( int, 'int' ) );
- const floatsCacheMap = new Map( [ ...intsCacheMap ].map( el => new ConstNode( el.value ) ) );
- for ( const float of floats ) floatsCacheMap.set( float, new ConstNode( float ) );
- for ( const float of floats ) floatsCacheMap.set( - float, new ConstNode( - float ) );
- const cacheMaps = { bool: boolsCacheMap, uint: uintsCacheMap, ints: intsCacheMap, float: floatsCacheMap };
- const constNodesCacheMap = new Map( [ ...boolsCacheMap, ...floatsCacheMap ] );
- const getConstNode = ( value, type ) => {
- if ( constNodesCacheMap.has( value ) ) {
- return constNodesCacheMap.get( value );
- } else if ( value.isNode === true ) {
- return value;
- } else {
- return new ConstNode( value, type );
- }
- };
- const ConvertType = function ( type, cacheMap = null ) {
- return ( ...params ) => {
- for ( const param of params ) {
- if ( param === undefined ) {
- error( `TSL: Invalid parameter for the type "${ type }".` );
- return nodeObject( new ConstNode( 0, type ) );
- }
- }
- if ( params.length === 0 || ( ! [ 'bool', 'float', 'int', 'uint' ].includes( type ) && params.every( param => {
- const paramType = typeof param;
- return paramType !== 'object' && paramType !== 'function';
- } ) ) ) {
- params = [ getValueFromType( type, ...params ) ];
- }
- if ( params.length === 1 && cacheMap !== null && cacheMap.has( params[ 0 ] ) ) {
- return nodeObjectIntent( cacheMap.get( params[ 0 ] ) );
- }
- if ( params.length === 1 ) {
- const node = getConstNode( params[ 0 ], type );
- if ( node.nodeType === type ) return nodeObjectIntent( node );
- return nodeObjectIntent( new ConvertNode( node, type ) );
- }
- const nodes = params.map( param => getConstNode( param ) );
- return nodeObjectIntent( new JoinNode( nodes, type ) );
- };
- };
- // exports
- const defined = ( v ) => typeof v === 'object' && v !== null ? v.value : v; // TODO: remove boolean conversion and defined function
- // utils
- const getConstNodeType = ( value ) => ( value !== undefined && value !== null ) ? ( value.nodeType || value.convertTo || ( typeof value === 'string' ? value : null ) ) : null;
- // shader node base
- function ShaderNode( jsFunc, nodeType ) {
- return new ShaderNodeInternal( jsFunc, nodeType );
- }
- const nodeObject = ( val, altType = null ) => /* new */ ShaderNodeObject( val, altType );
- const nodeObjectIntent = ( val, altType = null ) => /* new */ nodeObject( val, altType ).toVarIntent();
- const nodeObjects = ( val, altType = null ) => new ShaderNodeObjects( val, altType );
- const nodeArray = ( val, altType = null ) => new ShaderNodeArray( val, altType );
- const nodeProxy = ( NodeClass, scope = null, factor = null, settings = null ) => new ShaderNodeProxy( NodeClass, scope, factor, settings );
- const nodeImmutable = ( NodeClass, ...params ) => new ShaderNodeImmutable( NodeClass, ...params );
- const nodeProxyIntent = ( NodeClass, scope = null, factor = null, settings = {} ) => new ShaderNodeProxy( NodeClass, scope, factor, { ...settings, intent: true } );
- let fnId = 0;
- class FnNode extends Node {
- constructor( jsFunc, layout = null ) {
- super();
- let nodeType = null;
- if ( layout !== null ) {
- if ( typeof layout === 'object' ) {
- nodeType = layout.return;
- } else {
- if ( typeof layout === 'string' ) {
- nodeType = layout;
- } else {
- error( 'TSL: Invalid layout type.' );
- }
- layout = null;
- }
- }
- this.shaderNode = new ShaderNode( jsFunc, nodeType );
- if ( layout !== null ) {
- this.setLayout( layout );
- }
- this.isFn = true;
- }
- setLayout( layout ) {
- const nodeType = this.shaderNode.nodeType;
- if ( typeof layout.inputs !== 'object' ) {
- const fullLayout = {
- name: 'fn' + fnId ++,
- type: nodeType,
- inputs: []
- };
- for ( const name in layout ) {
- if ( name === 'return' ) continue;
- fullLayout.inputs.push( {
- name: name,
- type: layout[ name ]
- } );
- }
- layout = fullLayout;
- }
- this.shaderNode.setLayout( layout );
- return this;
- }
- getNodeType( builder ) {
- return this.shaderNode.getNodeType( builder ) || 'float';
- }
- call( ...params ) {
- const fnCall = this.shaderNode.call( params );
- if ( this.shaderNode.nodeType === 'void' ) fnCall.toStack();
- return fnCall.toVarIntent();
- }
- once( subBuilds = null ) {
- this.shaderNode.once = true;
- this.shaderNode.subBuilds = subBuilds;
- return this;
- }
- generate( builder ) {
- const type = this.getNodeType( builder );
- error( 'TSL: "Fn()" was declared but not invoked. Try calling it like "Fn()( ...params )".' );
- return builder.generateConst( type );
- }
- }
- function Fn( jsFunc, layout = null ) {
- const instance = new FnNode( jsFunc, layout );
- return new Proxy( () => {}, {
- apply( target, thisArg, params ) {
- return instance.call( ...params );
- },
- get( target, prop, receiver ) {
- return Reflect.get( instance, prop, receiver );
- },
- set( target, prop, value, receiver ) {
- return Reflect.set( instance, prop, value, receiver );
- }
- } );
- }
- //
- const setCurrentStack = ( stack ) => {
- currentStack = stack;
- };
- const getCurrentStack = () => currentStack;
- /**
- * Represent a conditional node using if/else statements.
- *
- * ```js
- * If( condition, function )
- * .ElseIf( condition, function )
- * .Else( function )
- * ```
- * @tsl
- * @function
- * @param {...any} params - The parameters for the conditional node.
- * @returns {StackNode} The conditional node.
- */
- const If = ( ...params ) => currentStack.If( ...params );
- /**
- * Represent a conditional node using switch/case statements.
- *
- * ```js
- * Switch( value )
- * .Case( 1, function )
- * .Case( 2, 3, 4, function )
- * .Default( function )
- * ```
- * @tsl
- * @function
- * @param {...any} params - The parameters for the conditional node.
- * @returns {StackNode} The conditional node.
- */
- const Switch = ( ...params ) => currentStack.Switch( ...params );
- /**
- * Add the given node to the current stack.
- *
- * @param {Node} node - The node to add.
- * @returns {Node} The node that was added to the stack.
- */
- function Stack( node ) {
- if ( currentStack ) currentStack.addToStack( node );
- return node;
- }
- addMethodChaining( 'toStack', Stack );
- // types
- const color = new ConvertType( 'color' );
- const float = new ConvertType( 'float', cacheMaps.float );
- const int = new ConvertType( 'int', cacheMaps.ints );
- const uint = new ConvertType( 'uint', cacheMaps.uint );
- const bool = new ConvertType( 'bool', cacheMaps.bool );
- const vec2 = new ConvertType( 'vec2' );
- const ivec2 = new ConvertType( 'ivec2' );
- const uvec2 = new ConvertType( 'uvec2' );
- const bvec2 = new ConvertType( 'bvec2' );
- const vec3 = new ConvertType( 'vec3' );
- const ivec3 = new ConvertType( 'ivec3' );
- const uvec3 = new ConvertType( 'uvec3' );
- const bvec3 = new ConvertType( 'bvec3' );
- const vec4 = new ConvertType( 'vec4' );
- const ivec4 = new ConvertType( 'ivec4' );
- const uvec4 = new ConvertType( 'uvec4' );
- const bvec4 = new ConvertType( 'bvec4' );
- const mat2 = new ConvertType( 'mat2' );
- const mat3 = new ConvertType( 'mat3' );
- const mat4 = new ConvertType( 'mat4' );
- const string = ( value = '' ) => nodeObject( new ConstNode( value, 'string' ) );
- const arrayBuffer = ( value ) => nodeObject( new ConstNode( value, 'ArrayBuffer' ) );
- addMethodChaining( 'toColor', color );
- addMethodChaining( 'toFloat', float );
- addMethodChaining( 'toInt', int );
- addMethodChaining( 'toUint', uint );
- addMethodChaining( 'toBool', bool );
- addMethodChaining( 'toVec2', vec2 );
- addMethodChaining( 'toIVec2', ivec2 );
- addMethodChaining( 'toUVec2', uvec2 );
- addMethodChaining( 'toBVec2', bvec2 );
- addMethodChaining( 'toVec3', vec3 );
- addMethodChaining( 'toIVec3', ivec3 );
- addMethodChaining( 'toUVec3', uvec3 );
- addMethodChaining( 'toBVec3', bvec3 );
- addMethodChaining( 'toVec4', vec4 );
- addMethodChaining( 'toIVec4', ivec4 );
- addMethodChaining( 'toUVec4', uvec4 );
- addMethodChaining( 'toBVec4', bvec4 );
- addMethodChaining( 'toMat2', mat2 );
- addMethodChaining( 'toMat3', mat3 );
- addMethodChaining( 'toMat4', mat4 );
- // basic nodes
- const element = /*@__PURE__*/ nodeProxy( ArrayElementNode ).setParameterLength( 2 );
- const convert = ( node, types ) => nodeObject( new ConvertNode( nodeObject( node ), types ) );
- const split = ( node, channels ) => nodeObject( new SplitNode( nodeObject( node ), channels ) );
- addMethodChaining( 'element', element );
- addMethodChaining( 'convert', convert );
- // deprecated
- /**
- * @tsl
- * @function
- * @deprecated since r176. Use {@link Stack} instead.
- *
- * @param {Node} node - The node to add.
- * @returns {Function}
- */
- const append = ( node ) => { // @deprecated, r176
- warn( 'TSL: append() has been renamed to Stack().' );
- return Stack( node );
- };
- addMethodChaining( 'append', ( node ) => { // @deprecated, r176
- warn( 'TSL: .append() has been renamed to .toStack().' );
- return Stack( node );
- } );
- /**
- * This class represents a shader property. It can be used
- * to explicitly define a property and assign a value to it.
- *
- * ```js
- * const threshold = property( 'float', 'threshold' ).assign( THRESHOLD );
- *```
- * `PropertyNode` is used by the engine to predefined common material properties
- * for TSL code.
- *
- * @augments Node
- */
- class PropertyNode extends Node {
- static get type() {
- return 'PropertyNode';
- }
- /**
- * Constructs a new property node.
- *
- * @param {string} nodeType - The type of the node.
- * @param {?string} [name=null] - The name of the property in the shader.
- * @param {boolean} [varying=false] - Whether this property is a varying or not.
- */
- constructor( nodeType, name = null, varying = false ) {
- super( nodeType );
- /**
- * The name of the property in the shader. If no name is defined,
- * the node system auto-generates one.
- *
- * @type {?string}
- * @default null
- */
- this.name = name;
- /**
- * Whether this property is a varying or not.
- *
- * @type {boolean}
- * @default false
- */
- this.varying = varying;
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isPropertyNode = true;
- /**
- * This flag is used for global cache.
- *
- * @type {boolean}
- * @default true
- */
- this.global = true;
- }
- customCacheKey() {
- return hashString( this.type + ':' + ( this.name || '' ) + ':' + ( this.varying ? '1' : '0' ) );
- }
- getHash( builder ) {
- return this.name || super.getHash( builder );
- }
- generate( builder ) {
- let nodeVar;
- if ( this.varying === true ) {
- nodeVar = builder.getVaryingFromNode( this, this.name );
- nodeVar.needsInterpolation = true;
- } else {
- nodeVar = builder.getVarFromNode( this, this.name );
- }
- return builder.getPropertyName( nodeVar );
- }
- }
- /**
- * TSL function for creating a property node.
- *
- * @tsl
- * @function
- * @param {string} type - The type of the node.
- * @param {?string} [name=null] - The name of the property in the shader.
- * @returns {PropertyNode}
- */
- const property = ( type, name ) => nodeObject( new PropertyNode( type, name ) );
- /**
- * TSL function for creating a varying property node.
- *
- * @tsl
- * @function
- * @param {string} type - The type of the node.
- * @param {?string} [name=null] - The name of the varying in the shader.
- * @returns {PropertyNode}
- */
- const varyingProperty = ( type, name ) => nodeObject( new PropertyNode( type, name, true ) );
- /**
- * TSL object that represents the shader variable `DiffuseColor`.
- *
- * @tsl
- * @type {PropertyNode<vec4>}
- */
- const diffuseColor = /*@__PURE__*/ nodeImmutable( PropertyNode, 'vec4', 'DiffuseColor' );
- /**
- * TSL object that represents the shader variable `DiffuseContribution`.
- *
- * @tsl
- * @type {PropertyNode<vec3>}
- */
- const diffuseContribution = /*@__PURE__*/ nodeImmutable( PropertyNode, 'vec3', 'DiffuseContribution' );
- /**
- * TSL object that represents the shader variable `EmissiveColor`.
- *
- * @tsl
- * @type {PropertyNode<vec3>}
- */
- const emissive = /*@__PURE__*/ nodeImmutable( PropertyNode, 'vec3', 'EmissiveColor' );
- /**
- * TSL object that represents the shader variable `Roughness`.
- *
- * @tsl
- * @type {PropertyNode<float>}
- */
- const roughness = /*@__PURE__*/ nodeImmutable( PropertyNode, 'float', 'Roughness' );
- /**
- * TSL object that represents the shader variable `Metalness`.
- *
- * @tsl
- * @type {PropertyNode<float>}
- */
- const metalness = /*@__PURE__*/ nodeImmutable( PropertyNode, 'float', 'Metalness' );
- /**
- * TSL object that represents the shader variable `Clearcoat`.
- *
- * @tsl
- * @type {PropertyNode<float>}
- */
- const clearcoat = /*@__PURE__*/ nodeImmutable( PropertyNode, 'float', 'Clearcoat' );
- /**
- * TSL object that represents the shader variable `ClearcoatRoughness`.
- *
- * @tsl
- * @type {PropertyNode<float>}
- */
- const clearcoatRoughness = /*@__PURE__*/ nodeImmutable( PropertyNode, 'float', 'ClearcoatRoughness' );
- /**
- * TSL object that represents the shader variable `Sheen`.
- *
- * @tsl
- * @type {PropertyNode<vec3>}
- */
- const sheen = /*@__PURE__*/ nodeImmutable( PropertyNode, 'vec3', 'Sheen' );
- /**
- * TSL object that represents the shader variable `SheenRoughness`.
- *
- * @tsl
- * @type {PropertyNode<float>}
- */
- const sheenRoughness = /*@__PURE__*/ nodeImmutable( PropertyNode, 'float', 'SheenRoughness' );
- /**
- * TSL object that represents the shader variable `Iridescence`.
- *
- * @tsl
- * @type {PropertyNode<float>}
- */
- const iridescence = /*@__PURE__*/ nodeImmutable( PropertyNode, 'float', 'Iridescence' );
- /**
- * TSL object that represents the shader variable `IridescenceIOR`.
- *
- * @tsl
- * @type {PropertyNode<float>}
- */
- const iridescenceIOR = /*@__PURE__*/ nodeImmutable( PropertyNode, 'float', 'IridescenceIOR' );
- /**
- * TSL object that represents the shader variable `IridescenceThickness`.
- *
- * @tsl
- * @type {PropertyNode<float>}
- */
- const iridescenceThickness = /*@__PURE__*/ nodeImmutable( PropertyNode, 'float', 'IridescenceThickness' );
- /**
- * TSL object that represents the shader variable `AlphaT`.
- *
- * @tsl
- * @type {PropertyNode<float>}
- */
- const alphaT = /*@__PURE__*/ nodeImmutable( PropertyNode, 'float', 'AlphaT' );
- /**
- * TSL object that represents the shader variable `Anisotropy`.
- *
- * @tsl
- * @type {PropertyNode<float>}
- */
- const anisotropy = /*@__PURE__*/ nodeImmutable( PropertyNode, 'float', 'Anisotropy' );
- /**
- * TSL object that represents the shader variable `AnisotropyT`.
- *
- * @tsl
- * @type {PropertyNode<vec3>}
- */
- const anisotropyT = /*@__PURE__*/ nodeImmutable( PropertyNode, 'vec3', 'AnisotropyT' );
- /**
- * TSL object that represents the shader variable `AnisotropyB`.
- *
- * @tsl
- * @type {PropertyNode<vec3>}
- */
- const anisotropyB = /*@__PURE__*/ nodeImmutable( PropertyNode, 'vec3', 'AnisotropyB' );
- /**
- * TSL object that represents the shader variable `SpecularColor`.
- *
- * @tsl
- * @type {PropertyNode<color>}
- */
- const specularColor = /*@__PURE__*/ nodeImmutable( PropertyNode, 'color', 'SpecularColor' );
- /**
- * TSL object that represents the shader variable `SpecularColorBlended`.
- *
- * @tsl
- * @type {PropertyNode<color>}
- */
- const specularColorBlended = /*@__PURE__*/ nodeImmutable( PropertyNode, 'color', 'SpecularColorBlended' );
- /**
- * TSL object that represents the shader variable `SpecularF90`.
- *
- * @tsl
- * @type {PropertyNode<float>}
- */
- const specularF90 = /*@__PURE__*/ nodeImmutable( PropertyNode, 'float', 'SpecularF90' );
- /**
- * TSL object that represents the shader variable `Shininess`.
- *
- * @tsl
- * @type {PropertyNode<float>}
- */
- const shininess = /*@__PURE__*/ nodeImmutable( PropertyNode, 'float', 'Shininess' );
- /**
- * TSL object that represents the shader variable `Output`.
- *
- * @tsl
- * @type {PropertyNode<vec4>}
- */
- const output = /*@__PURE__*/ nodeImmutable( PropertyNode, 'vec4', 'Output' );
- /**
- * TSL object that represents the shader variable `dashSize`.
- *
- * @tsl
- * @type {PropertyNode<float>}
- */
- const dashSize = /*@__PURE__*/ nodeImmutable( PropertyNode, 'float', 'dashSize' );
- /**
- * TSL object that represents the shader variable `gapSize`.
- *
- * @tsl
- * @type {PropertyNode<float>}
- */
- const gapSize = /*@__PURE__*/ nodeImmutable( PropertyNode, 'float', 'gapSize' );
- /**
- * TSL object that represents the shader variable `pointWidth`.
- *
- * @tsl
- * @type {PropertyNode<float>}
- */
- const pointWidth = /*@__PURE__*/ nodeImmutable( PropertyNode, 'float', 'pointWidth' );
- /**
- * TSL object that represents the shader variable `IOR`.
- *
- * @tsl
- * @type {PropertyNode<float>}
- */
- const ior = /*@__PURE__*/ nodeImmutable( PropertyNode, 'float', 'IOR' );
- /**
- * TSL object that represents the shader variable `Transmission`.
- *
- * @tsl
- * @type {PropertyNode<float>}
- */
- const transmission = /*@__PURE__*/ nodeImmutable( PropertyNode, 'float', 'Transmission' );
- /**
- * TSL object that represents the shader variable `Thickness`.
- *
- * @tsl
- * @type {PropertyNode<float>}
- */
- const thickness = /*@__PURE__*/ nodeImmutable( PropertyNode, 'float', 'Thickness' );
- /**
- * TSL object that represents the shader variable `AttenuationDistance`.
- *
- * @tsl
- * @type {PropertyNode<float>}
- */
- const attenuationDistance = /*@__PURE__*/ nodeImmutable( PropertyNode, 'float', 'AttenuationDistance' );
- /**
- * TSL object that represents the shader variable `AttenuationColor`.
- *
- * @tsl
- * @type {PropertyNode<color>}
- */
- const attenuationColor = /*@__PURE__*/ nodeImmutable( PropertyNode, 'color', 'AttenuationColor' );
- /**
- * TSL object that represents the shader variable `Dispersion`.
- *
- * @tsl
- * @type {PropertyNode<float>}
- */
- const dispersion = /*@__PURE__*/ nodeImmutable( PropertyNode, 'float', 'Dispersion' );
- /**
- * This node can be used to group single instances of {@link UniformNode}
- * and manage them as a uniform buffer.
- *
- * In most cases, the predefined nodes `objectGroup`, `renderGroup` and `frameGroup`
- * will be used when defining the {@link UniformNode#groupNode} property.
- *
- * - `objectGroup`: Uniform buffer per object.
- * - `renderGroup`: Shared uniform buffer, updated once per render call.
- * - `frameGroup`: Shared uniform buffer, updated once per frame.
- *
- * @augments Node
- */
- class UniformGroupNode extends Node {
- static get type() {
- return 'UniformGroupNode';
- }
- /**
- * Constructs a new uniform group node.
- *
- * @param {string} name - The name of the uniform group node.
- * @param {boolean} [shared=false] - Whether this uniform group node is shared or not.
- * @param {number} [order=1] - Influences the internal sorting.
- */
- constructor( name, shared = false, order = 1 ) {
- super( 'string' );
- /**
- * The name of the uniform group node.
- *
- * @type {string}
- */
- this.name = name;
- /**
- * Whether this uniform group node is shared or not.
- *
- * @type {boolean}
- * @default false
- */
- this.shared = shared;
- /**
- * Influences the internal sorting.
- * TODO: Add details when this property should be changed.
- *
- * @type {number}
- * @default 1
- */
- this.order = order;
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isUniformGroup = true;
- }
- serialize( data ) {
- super.serialize( data );
- data.name = this.name;
- data.version = this.version;
- data.shared = this.shared;
- }
- deserialize( data ) {
- super.deserialize( data );
- this.name = data.name;
- this.version = data.version;
- this.shared = data.shared;
- }
- }
- /**
- * TSL function for creating a uniform group node with the given name.
- *
- * @tsl
- * @function
- * @param {string} name - The name of the uniform group node.
- * @returns {UniformGroupNode}
- */
- const uniformGroup = ( name ) => new UniformGroupNode( name );
- /**
- * TSL function for creating a shared uniform group node with the given name and order.
- *
- * @tsl
- * @function
- * @param {string} name - The name of the uniform group node.
- * @param {number} [order=0] - Influences the internal sorting.
- * @returns {UniformGroupNode}
- */
- const sharedUniformGroup = ( name, order = 0 ) => new UniformGroupNode( name, true, order );
- /**
- * TSL object that represents a shared uniform group node which is updated once per frame.
- *
- * @tsl
- * @type {UniformGroupNode}
- */
- const frameGroup = /*@__PURE__*/ sharedUniformGroup( 'frame' );
- /**
- * TSL object that represents a shared uniform group node which is updated once per render.
- *
- * @tsl
- * @type {UniformGroupNode}
- */
- const renderGroup = /*@__PURE__*/ sharedUniformGroup( 'render' );
- /**
- * TSL object that represents a uniform group node which is updated once per object.
- *
- * @tsl
- * @type {UniformGroupNode}
- */
- const objectGroup = /*@__PURE__*/ uniformGroup( 'object' );
- /**
- * Class for representing a uniform.
- *
- * @augments InputNode
- */
- class UniformNode extends InputNode {
- static get type() {
- return 'UniformNode';
- }
- /**
- * Constructs a new uniform node.
- *
- * @param {any} value - The value of this node. Usually a JS primitive or three.js object (vector, matrix, color, texture).
- * @param {?string} nodeType - The node type. If no explicit type is defined, the node tries to derive the type from its value.
- */
- constructor( value, nodeType = null ) {
- super( value, nodeType );
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isUniformNode = true;
- /**
- * The name or label of the uniform.
- *
- * @type {string}
- * @default ''
- */
- this.name = '';
- /**
- * The uniform group of this uniform. By default, uniforms are
- * managed per object but they might belong to a shared group
- * which is updated per frame or render call.
- *
- * @type {UniformGroupNode}
- */
- this.groupNode = objectGroup;
- }
- /**
- * Sets the {@link UniformNode#name} property.
- *
- * @param {string} name - The name of the uniform.
- * @return {UniformNode} A reference to this node.
- */
- setName( name ) {
- this.name = name;
- return this;
- }
- /**
- * Sets the {@link UniformNode#name} property.
- *
- * @deprecated
- * @param {string} name - The name of the uniform.
- * @return {UniformNode} A reference to this node.
- */
- label( name ) {
- warn( 'TSL: "label()" has been deprecated. Use "setName()" instead.' ); // @deprecated r179
- return this.setName( name );
- }
- /**
- * Sets the {@link UniformNode#groupNode} property.
- *
- * @param {UniformGroupNode} group - The uniform group.
- * @return {UniformNode} A reference to this node.
- */
- setGroup( group ) {
- this.groupNode = group;
- return this;
- }
- /**
- * Returns the {@link UniformNode#groupNode}.
- *
- * @return {UniformGroupNode} The uniform group.
- */
- getGroup() {
- return this.groupNode;
- }
- /**
- * By default, this method returns the result of {@link Node#getHash} but derived
- * classes might overwrite this method with a different implementation.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {string} The uniform hash.
- */
- getUniformHash( builder ) {
- return this.getHash( builder );
- }
- onUpdate( callback, updateType ) {
- callback = callback.bind( this );
- return super.onUpdate( ( frame ) => {
- const value = callback( frame, this );
- if ( value !== undefined ) {
- this.value = value;
- }
- }, updateType );
- }
- getInputType( builder ) {
- let type = super.getInputType( builder );
- if ( type === 'bool' ) {
- type = 'uint';
- }
- return type;
- }
- generate( builder, output ) {
- const type = this.getNodeType( builder );
- const hash = this.getUniformHash( builder );
- let sharedNode = builder.getNodeFromHash( hash );
- if ( sharedNode === undefined ) {
- builder.setHashNode( this, hash );
- sharedNode = this;
- }
- const sharedNodeType = sharedNode.getInputType( builder );
- const nodeUniform = builder.getUniformFromNode( sharedNode, sharedNodeType, builder.shaderStage, this.name || builder.context.nodeName );
- const uniformName = builder.getPropertyName( nodeUniform );
- if ( builder.context.nodeName !== undefined ) delete builder.context.nodeName;
- //
- let snippet = uniformName;
- if ( type === 'bool' ) {
- // cache to variable
- const nodeData = builder.getDataFromNode( this );
- let propertyName = nodeData.propertyName;
- if ( propertyName === undefined ) {
- const nodeVar = builder.getVarFromNode( this, null, 'bool' );
- propertyName = builder.getPropertyName( nodeVar );
- nodeData.propertyName = propertyName;
- snippet = builder.format( uniformName, sharedNodeType, type );
- builder.addLineFlowCode( `${ propertyName } = ${ snippet }`, this );
- }
- snippet = propertyName;
- }
- return builder.format( snippet, type, output );
- }
- }
- /**
- * TSL function for creating a uniform node.
- *
- * @tsl
- * @function
- * @param {any|string} value - The value of this uniform or your type. Usually a JS primitive or three.js object (vector, matrix, color, texture).
- * @param {string} [type] - The node type. If no explicit type is defined, the node tries to derive the type from its value.
- * @returns {UniformNode}
- */
- const uniform = ( value, type ) => {
- const nodeType = getConstNodeType( type || value );
- if ( nodeType === value ) {
- // if the value is a type but no having a value
- value = getValueFromType( nodeType );
- }
- if ( value && value.isNode === true ) {
- let v = value.value;
- value.traverse( n => {
- if ( n.isConstNode === true ) {
- v = n.value;
- }
- } );
- value = v;
- }
- return nodeObject( new UniformNode( value, nodeType ) );
- };
- /**
- * ArrayNode represents a collection of nodes, typically created using the {@link array} function.
- * ```js
- * const colors = array( [
- * vec3( 1, 0, 0 ),
- * vec3( 0, 1, 0 ),
- * vec3( 0, 0, 1 )
- * ] );
- *
- * const redColor = tintColors.element( 0 );
- * ```
- *
- * @augments TempNode
- */
- class ArrayNode extends TempNode {
- static get type() {
- return 'ArrayNode';
- }
- /**
- * Constructs a new array node.
- *
- * @param {?string} nodeType - The data type of the elements.
- * @param {number} count - Size of the array.
- * @param {?Array<Node>} [values=null] - Array default values.
- */
- constructor( nodeType, count, values = null ) {
- super( nodeType );
- /**
- * Array size.
- *
- * @type {number}
- */
- this.count = count;
- /**
- * Array default values.
- *
- * @type {?Array<Node>}
- */
- this.values = values;
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isArrayNode = true;
- }
- /**
- * Returns the number of elements in the node array.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {number} The number of elements in the node array.
- */
- getArrayCount( /*builder*/ ) {
- return this.count;
- }
- /**
- * Returns the node's type.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {string} The type of the node.
- */
- getNodeType( builder ) {
- if ( this.nodeType === null ) {
- this.nodeType = this.values[ 0 ].getNodeType( builder );
- }
- return this.nodeType;
- }
- /**
- * Returns the node's type.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {string} The type of the node.
- */
- getElementType( builder ) {
- return this.getNodeType( builder );
- }
- /**
- * This method builds the output node and returns the resulting array as a shader string.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {string} The generated shader string.
- */
- generate( builder ) {
- const type = this.getNodeType( builder );
- return builder.generateArray( type, this.count, this.values );
- }
- }
- /**
- * TSL function for creating an array node.
- *
- * @tsl
- * @function
- * @param {string|Array<Node>} nodeTypeOrValues - A string representing the element type (e.g., 'vec3')
- * or an array containing the default values (e.g., [ vec3() ]).
- * @param {?number} [count] - Size of the array.
- * @returns {ArrayNode}
- */
- const array = ( ...params ) => {
- let node;
- if ( params.length === 1 ) {
- const values = params[ 0 ];
- node = new ArrayNode( null, values.length, values );
- } else {
- const nodeType = params[ 0 ];
- const count = params[ 1 ];
- node = new ArrayNode( nodeType, count );
- }
- return nodeObject( node );
- };
- addMethodChaining( 'toArray', ( node, count ) => array( Array( count ).fill( node ) ) );
- /**
- * These node represents an assign operation. Meaning a node is assigned
- * to another node.
- *
- * @augments TempNode
- */
- class AssignNode extends TempNode {
- static get type() {
- return 'AssignNode';
- }
- /**
- * Constructs a new assign node.
- *
- * @param {Node} targetNode - The target node.
- * @param {Node} sourceNode - The source type.
- */
- constructor( targetNode, sourceNode ) {
- super();
- /**
- * The target node.
- *
- * @type {Node}
- */
- this.targetNode = targetNode;
- /**
- * The source node.
- *
- * @type {Node}
- */
- this.sourceNode = sourceNode;
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isAssignNode = true;
- }
- /**
- * Whether this node is used more than once in context of other nodes. This method
- * is overwritten since it always returns `false` (assigns are unique).
- *
- * @return {boolean} A flag that indicates if there is more than one dependency to other nodes. Always `false`.
- */
- hasDependencies() {
- return false;
- }
- getNodeType( builder, output ) {
- return output !== 'void' ? this.targetNode.getNodeType( builder ) : 'void';
- }
- /**
- * Whether a split is required when assigning source to target. This can happen when the component length of
- * target and source data type does not match.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {boolean} Whether a split is required when assigning source to target.
- */
- needsSplitAssign( builder ) {
- const { targetNode } = this;
- if ( builder.isAvailable( 'swizzleAssign' ) === false && targetNode.isSplitNode && targetNode.components.length > 1 ) {
- const targetLength = builder.getTypeLength( targetNode.node.getNodeType( builder ) );
- const assignDifferentVector = vectorComponents.join( '' ).slice( 0, targetLength ) !== targetNode.components;
- return assignDifferentVector;
- }
- return false;
- }
- setup( builder ) {
- const { targetNode, sourceNode } = this;
- const scope = targetNode.getScope();
- const scopeData = builder.getDataFromNode( scope );
- scopeData.assign = true;
- const properties = builder.getNodeProperties( this );
- properties.sourceNode = sourceNode;
- properties.targetNode = targetNode.context( { assign: true } );
- }
- generate( builder, output ) {
- const { targetNode, sourceNode } = builder.getNodeProperties( this );
- const needsSplitAssign = this.needsSplitAssign( builder );
- const target = targetNode.build( builder );
- const targetType = targetNode.getNodeType( builder );
- const source = sourceNode.build( builder, targetType );
- const sourceType = sourceNode.getNodeType( builder );
- const nodeData = builder.getDataFromNode( this );
- //
- let snippet;
- if ( nodeData.initialized === true ) {
- if ( output !== 'void' ) {
- snippet = target;
- }
- } else if ( needsSplitAssign ) {
- const sourceVar = builder.getVarFromNode( this, null, targetType );
- const sourceProperty = builder.getPropertyName( sourceVar );
- builder.addLineFlowCode( `${ sourceProperty } = ${ source }`, this );
- const splitNode = targetNode.node;
- const splitTargetNode = splitNode.node.context( { assign: true } );
- const targetRoot = splitTargetNode.build( builder );
- for ( let i = 0; i < splitNode.components.length; i ++ ) {
- const component = splitNode.components[ i ];
- builder.addLineFlowCode( `${ targetRoot }.${ component } = ${ sourceProperty }[ ${ i } ]`, this );
- }
- if ( output !== 'void' ) {
- snippet = target;
- }
- } else {
- snippet = `${ target } = ${ source }`;
- if ( output === 'void' || sourceType === 'void' ) {
- builder.addLineFlowCode( snippet, this );
- if ( output !== 'void' ) {
- snippet = target;
- }
- }
- }
- nodeData.initialized = true;
- return builder.format( snippet, targetType, output );
- }
- }
- /**
- * TSL function for creating an assign node.
- *
- * @tsl
- * @function
- * @param {Node} targetNode - The target node.
- * @param {Node} sourceNode - The source type.
- * @returns {AssignNode}
- */
- const assign = /*@__PURE__*/ nodeProxy( AssignNode ).setParameterLength( 2 );
- addMethodChaining( 'assign', assign );
- /**
- * This module represents the call of a {@link FunctionNode}. Developers are usually not confronted
- * with this module since they use the predefined TSL syntax `wgslFn` and `glslFn` which encapsulate
- * this logic.
- *
- * @augments TempNode
- */
- class FunctionCallNode extends TempNode {
- static get type() {
- return 'FunctionCallNode';
- }
- /**
- * Constructs a new function call node.
- *
- * @param {?FunctionNode} functionNode - The function node.
- * @param {Object<string, Node>} [parameters={}] - The parameters for the function call.
- */
- constructor( functionNode = null, parameters = {} ) {
- super();
- /**
- * The function node.
- *
- * @type {?FunctionNode}
- * @default null
- */
- this.functionNode = functionNode;
- /**
- * The parameters of the function call.
- *
- * @type {Object<string, Node>}
- * @default {}
- */
- this.parameters = parameters;
- }
- /**
- * Sets the parameters of the function call node.
- *
- * @param {Object<string, Node>} parameters - The parameters to set.
- * @return {FunctionCallNode} A reference to this node.
- */
- setParameters( parameters ) {
- this.parameters = parameters;
- return this;
- }
- /**
- * Returns the parameters of the function call node.
- *
- * @return {Object<string, Node>} The parameters of this node.
- */
- getParameters() {
- return this.parameters;
- }
- /**
- * Returns the type of this function call node.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @returns {string} The type of this node.
- */
- getNodeType( builder ) {
- return this.functionNode.getNodeType( builder );
- }
- /**
- * Returns the function node of this function call node.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @param {string} [name] - The name of the member.
- * @returns {string} The type of the member.
- */
- getMemberType( builder, name ) {
- return this.functionNode.getMemberType( builder, name );
- }
- generate( builder ) {
- const params = [];
- const functionNode = this.functionNode;
- const inputs = functionNode.getInputs( builder );
- const parameters = this.parameters;
- const generateInput = ( node, inputNode ) => {
- const type = inputNode.type;
- const pointer = type === 'pointer';
- let output;
- if ( pointer ) output = '&' + node.build( builder );
- else output = node.build( builder, type );
- return output;
- };
- if ( Array.isArray( parameters ) ) {
- if ( parameters.length > inputs.length ) {
- error( 'TSL: The number of provided parameters exceeds the expected number of inputs in \'Fn()\'.' );
- parameters.length = inputs.length;
- } else if ( parameters.length < inputs.length ) {
- error( 'TSL: The number of provided parameters is less than the expected number of inputs in \'Fn()\'.' );
- while ( parameters.length < inputs.length ) {
- parameters.push( float( 0 ) );
- }
- }
- for ( let i = 0; i < parameters.length; i ++ ) {
- params.push( generateInput( parameters[ i ], inputs[ i ] ) );
- }
- } else {
- for ( const inputNode of inputs ) {
- const node = parameters[ inputNode.name ];
- if ( node !== undefined ) {
- params.push( generateInput( node, inputNode ) );
- } else {
- error( `TSL: Input '${ inputNode.name }' not found in \'Fn()\'.` );
- params.push( generateInput( float( 0 ), inputNode ) );
- }
- }
- }
- const functionName = functionNode.build( builder, 'property' );
- return `${ functionName }( ${ params.join( ', ' ) } )`;
- }
- }
- const call = ( func, ...params ) => {
- params = params.length > 1 || ( params[ 0 ] && params[ 0 ].isNode === true ) ? nodeArray( params ) : nodeObjects( params[ 0 ] );
- return new FunctionCallNode( nodeObject( func ), params );
- };
- addMethodChaining( 'call', call );
- const _vectorOperators = {
- '==': 'equal',
- '!=': 'notEqual',
- '<': 'lessThan',
- '>': 'greaterThan',
- '<=': 'lessThanEqual',
- '>=': 'greaterThanEqual',
- '%': 'mod'
- };
- /**
- * This node represents basic mathematical and logical operations like addition,
- * subtraction or comparisons (e.g. `equal()`).
- *
- * @augments TempNode
- */
- class OperatorNode extends TempNode {
- static get type() {
- return 'OperatorNode';
- }
- /**
- * Constructs a new operator node.
- *
- * @param {string} op - The operator.
- * @param {Node} aNode - The first input.
- * @param {Node} bNode - The second input.
- * @param {...Node} params - Additional input parameters.
- */
- constructor( op, aNode, bNode, ...params ) {
- super();
- if ( params.length > 0 ) {
- let finalOp = new OperatorNode( op, aNode, bNode );
- for ( let i = 0; i < params.length - 1; i ++ ) {
- finalOp = new OperatorNode( op, finalOp, params[ i ] );
- }
- aNode = finalOp;
- bNode = params[ params.length - 1 ];
- }
- /**
- * The operator.
- *
- * @type {string}
- */
- this.op = op;
- /**
- * The first input.
- *
- * @type {Node}
- */
- this.aNode = aNode;
- /**
- * The second input.
- *
- * @type {Node}
- */
- this.bNode = bNode;
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isOperatorNode = true;
- }
- /**
- * Returns the operator method name.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @param {string} output - The output type.
- * @returns {string} The operator method name.
- */
- getOperatorMethod( builder, output ) {
- return builder.getMethod( _vectorOperators[ this.op ], output );
- }
- /**
- * This method is overwritten since the node type is inferred from the operator
- * and the input node types.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @param {?string} [output=null] - The output type.
- * @return {string} The node type.
- */
- getNodeType( builder, output = null ) {
- const op = this.op;
- const aNode = this.aNode;
- const bNode = this.bNode;
- const typeA = aNode.getNodeType( builder );
- const typeB = bNode ? bNode.getNodeType( builder ) : null;
- if ( typeA === 'void' || typeB === 'void' ) {
- return output || 'void';
- } else if ( op === '%' ) {
- return typeA;
- } else if ( op === '~' || op === '&' || op === '|' || op === '^' || op === '>>' || op === '<<' ) {
- return builder.getIntegerType( typeA );
- } else if ( op === '!' || op === '&&' || op === '||' || op === '^^' ) {
- return 'bool';
- } else if ( op === '==' || op === '!=' || op === '<' || op === '>' || op === '<=' || op === '>=' ) {
- const typeLength = Math.max( builder.getTypeLength( typeA ), builder.getTypeLength( typeB ) );
- return typeLength > 1 ? `bvec${ typeLength }` : 'bool';
- } else {
- // Handle matrix operations
- if ( builder.isMatrix( typeA ) ) {
- if ( typeB === 'float' ) {
- return typeA; // matrix * scalar = matrix
- } else if ( builder.isVector( typeB ) ) {
- return builder.getVectorFromMatrix( typeA ); // matrix * vector
- } else if ( builder.isMatrix( typeB ) ) {
- return typeA; // matrix * matrix
- }
- } else if ( builder.isMatrix( typeB ) ) {
- if ( typeA === 'float' ) {
- return typeB; // scalar * matrix = matrix
- } else if ( builder.isVector( typeA ) ) {
- return builder.getVectorFromMatrix( typeB ); // vector * matrix
- }
- }
- // Handle non-matrix cases
- if ( builder.getTypeLength( typeB ) > builder.getTypeLength( typeA ) ) {
- // anytype x anytype: use the greater length vector
- return typeB;
- }
- return typeA;
- }
- }
- generate( builder, output ) {
- const op = this.op;
- const { aNode, bNode } = this;
- const type = this.getNodeType( builder, output );
- let typeA = null;
- let typeB = null;
- if ( type !== 'void' ) {
- typeA = aNode.getNodeType( builder );
- typeB = bNode ? bNode.getNodeType( builder ) : null;
- if ( op === '<' || op === '>' || op === '<=' || op === '>=' || op === '==' || op === '!=' ) {
- if ( builder.isVector( typeA ) ) {
- typeB = typeA;
- } else if ( builder.isVector( typeB ) ) {
- typeA = typeB;
- } else if ( typeA !== typeB ) {
- typeA = typeB = 'float';
- }
- } else if ( op === '>>' || op === '<<' ) {
- typeA = type;
- typeB = builder.changeComponentType( typeB, 'uint' );
- } else if ( op === '%' ) {
- typeA = type;
- typeB = builder.isInteger( typeA ) && builder.isInteger( typeB ) ? typeB : typeA;
- } else if ( builder.isMatrix( typeA ) ) {
- if ( typeB === 'float' ) {
- // Keep matrix type for typeA, but ensure typeB stays float
- typeB = 'float';
- } else if ( builder.isVector( typeB ) ) {
- // matrix x vector
- typeB = builder.getVectorFromMatrix( typeA );
- } else if ( builder.isMatrix( typeB ) ) ; else {
- typeA = typeB = type;
- }
- } else if ( builder.isMatrix( typeB ) ) {
- if ( typeA === 'float' ) {
- // Keep matrix type for typeB, but ensure typeA stays float
- typeA = 'float';
- } else if ( builder.isVector( typeA ) ) {
- // vector x matrix
- typeA = builder.getVectorFromMatrix( typeB );
- } else {
- typeA = typeB = type;
- }
- } else {
- // anytype x anytype
- typeA = typeB = type;
- }
- } else {
- typeA = typeB = type;
- }
- const a = aNode.build( builder, typeA );
- const b = bNode ? bNode.build( builder, typeB ) : null;
- const fnOpSnippet = builder.getFunctionOperator( op );
- if ( output !== 'void' ) {
- const isGLSL = builder.renderer.coordinateSystem === WebGLCoordinateSystem;
- if ( op === '==' || op === '!=' || op === '<' || op === '>' || op === '<=' || op === '>=' ) {
- if ( isGLSL ) {
- if ( builder.isVector( typeA ) ) {
- return builder.format( `${ this.getOperatorMethod( builder, output ) }( ${ a }, ${ b } )`, type, output );
- } else {
- return builder.format( `( ${ a } ${ op } ${ b } )`, type, output );
- }
- } else {
- // WGSL
- return builder.format( `( ${ a } ${ op } ${ b } )`, type, output );
- }
- } else if ( op === '%' ) {
- if ( builder.isInteger( typeB ) ) {
- return builder.format( `( ${ a } % ${ b } )`, type, output );
- } else {
- return builder.format( `${ this.getOperatorMethod( builder, type ) }( ${ a }, ${ b } )`, type, output );
- }
- } else if ( op === '!' || op === '~' ) {
- return builder.format( `(${op}${a})`, typeA, output );
- } else if ( fnOpSnippet ) {
- return builder.format( `${ fnOpSnippet }( ${ a }, ${ b } )`, type, output );
- } else {
- // Handle matrix operations
- if ( builder.isMatrix( typeA ) && typeB === 'float' ) {
- return builder.format( `( ${ b } ${ op } ${ a } )`, type, output );
- } else if ( typeA === 'float' && builder.isMatrix( typeB ) ) {
- return builder.format( `${ a } ${ op } ${ b }`, type, output );
- } else {
- let snippet = `( ${ a } ${ op } ${ b } )`;
- if ( ! isGLSL && type === 'bool' && builder.isVector( typeA ) && builder.isVector( typeB ) ) {
- snippet = `all${ snippet }`;
- }
- return builder.format( snippet, type, output );
- }
- }
- } else if ( typeA !== 'void' ) {
- if ( fnOpSnippet ) {
- return builder.format( `${ fnOpSnippet }( ${ a }, ${ b } )`, type, output );
- } else {
- if ( builder.isMatrix( typeA ) && typeB === 'float' ) {
- return builder.format( `${ b } ${ op } ${ a }`, type, output );
- } else {
- return builder.format( `${ a } ${ op } ${ b }`, type, output );
- }
- }
- }
- }
- serialize( data ) {
- super.serialize( data );
- data.op = this.op;
- }
- deserialize( data ) {
- super.deserialize( data );
- this.op = data.op;
- }
- }
- /**
- * Returns the addition of two or more value.
- *
- * @tsl
- * @function
- * @param {Node} a - The first input.
- * @param {Node} b - The second input.
- * @param {...Node} params - Additional input parameters.
- * @returns {OperatorNode}
- */
- const add = /*@__PURE__*/ nodeProxyIntent( OperatorNode, '+' ).setParameterLength( 2, Infinity ).setName( 'add' );
- /**
- * Returns the subtraction of two or more value.
- *
- * @tsl
- * @function
- * @param {Node} a - The first input.
- * @param {Node} b - The second input.
- * @param {...Node} params - Additional input parameters.
- * @returns {OperatorNode}
- */
- const sub = /*@__PURE__*/ nodeProxyIntent( OperatorNode, '-' ).setParameterLength( 2, Infinity ).setName( 'sub' );
- /**
- * Returns the multiplication of two or more value.
- *
- * @tsl
- * @function
- * @param {Node} a - The first input.
- * @param {Node} b - The second input.
- * @param {...Node} params - Additional input parameters.
- * @returns {OperatorNode}
- */
- const mul = /*@__PURE__*/ nodeProxyIntent( OperatorNode, '*' ).setParameterLength( 2, Infinity ).setName( 'mul' );
- /**
- * Returns the division of two or more value.
- *
- * @tsl
- * @function
- * @param {Node} a - The first input.
- * @param {Node} b - The second input.
- * @param {...Node} params - Additional input parameters.
- * @returns {OperatorNode}
- */
- const div = /*@__PURE__*/ nodeProxyIntent( OperatorNode, '/' ).setParameterLength( 2, Infinity ).setName( 'div' );
- /**
- * Computes the remainder of dividing the first node by the second one.
- *
- * @tsl
- * @function
- * @param {Node} a - The first input.
- * @param {Node} b - The second input.
- * @returns {OperatorNode}
- */
- const mod = /*@__PURE__*/ nodeProxyIntent( OperatorNode, '%' ).setParameterLength( 2 ).setName( 'mod' );
- /**
- * Checks if two nodes are equal.
- *
- * @tsl
- * @function
- * @param {Node} a - The first input.
- * @param {Node} b - The second input.
- * @returns {OperatorNode}
- */
- const equal = /*@__PURE__*/ nodeProxyIntent( OperatorNode, '==' ).setParameterLength( 2 ).setName( 'equal' );
- /**
- * Checks if two nodes are not equal.
- *
- * @tsl
- * @function
- * @param {Node} a - The first input.
- * @param {Node} b - The second input.
- * @returns {OperatorNode}
- */
- const notEqual = /*@__PURE__*/ nodeProxyIntent( OperatorNode, '!=' ).setParameterLength( 2 ).setName( 'notEqual' );
- /**
- * Checks if the first node is less than the second.
- *
- * @tsl
- * @function
- * @param {Node} a - The first input.
- * @param {Node} b - The second input.
- * @returns {OperatorNode}
- */
- const lessThan = /*@__PURE__*/ nodeProxyIntent( OperatorNode, '<' ).setParameterLength( 2 ).setName( 'lessThan' );
- /**
- * Checks if the first node is greater than the second.
- *
- * @tsl
- * @function
- * @param {Node} a - The first input.
- * @param {Node} b - The second input.
- * @returns {OperatorNode}
- */
- const greaterThan = /*@__PURE__*/ nodeProxyIntent( OperatorNode, '>' ).setParameterLength( 2 ).setName( 'greaterThan' );
- /**
- * Checks if the first node is less than or equal to the second.
- *
- * @tsl
- * @function
- * @param {Node} a - The first input.
- * @param {Node} b - The second input.
- * @returns {OperatorNode}
- */
- const lessThanEqual = /*@__PURE__*/ nodeProxyIntent( OperatorNode, '<=' ).setParameterLength( 2 ).setName( 'lessThanEqual' );
- /**
- * Checks if the first node is greater than or equal to the second.
- *
- * @tsl
- * @function
- * @param {Node} a - The first input.
- * @param {Node} b - The second input.
- * @returns {OperatorNode}
- */
- const greaterThanEqual = /*@__PURE__*/ nodeProxyIntent( OperatorNode, '>=' ).setParameterLength( 2 ).setName( 'greaterThanEqual' );
- /**
- * Performs a logical AND operation on multiple nodes.
- *
- * @tsl
- * @function
- * @param {...Node} nodes - The input nodes to be combined using AND.
- * @returns {OperatorNode}
- */
- const and = /*@__PURE__*/ nodeProxyIntent( OperatorNode, '&&' ).setParameterLength( 2, Infinity ).setName( 'and' );
- /**
- * Performs a logical OR operation on multiple nodes.
- *
- * @tsl
- * @function
- * @param {...Node} nodes - The input nodes to be combined using OR.
- * @returns {OperatorNode}
- */
- const or = /*@__PURE__*/ nodeProxyIntent( OperatorNode, '||' ).setParameterLength( 2, Infinity ).setName( 'or' );
- /**
- * Performs logical NOT on a node.
- *
- * @tsl
- * @function
- * @param {Node} value - The value.
- * @returns {OperatorNode}
- */
- const not = /*@__PURE__*/ nodeProxyIntent( OperatorNode, '!' ).setParameterLength( 1 ).setName( 'not' );
- /**
- * Performs logical XOR on two nodes.
- *
- * @tsl
- * @function
- * @param {Node} a - The first input.
- * @param {Node} b - The second input.
- * @returns {OperatorNode}
- */
- const xor = /*@__PURE__*/ nodeProxyIntent( OperatorNode, '^^' ).setParameterLength( 2 ).setName( 'xor' );
- /**
- * Performs bitwise AND on two nodes.
- *
- * @tsl
- * @function
- * @param {Node} a - The first input.
- * @param {Node} b - The second input.
- * @returns {OperatorNode}
- */
- const bitAnd = /*@__PURE__*/ nodeProxyIntent( OperatorNode, '&' ).setParameterLength( 2 ).setName( 'bitAnd' );
- /**
- * Performs bitwise NOT on a node.
- *
- * @tsl
- * @function
- * @param {Node} a - The first input.
- * @param {Node} b - The second input.
- * @returns {OperatorNode}
- */
- const bitNot = /*@__PURE__*/ nodeProxyIntent( OperatorNode, '~' ).setParameterLength( 1 ).setName( 'bitNot' );
- /**
- * Performs bitwise OR on two nodes.
- *
- * @tsl
- * @function
- * @param {Node} a - The first input.
- * @param {Node} b - The second input.
- * @returns {OperatorNode}
- */
- const bitOr = /*@__PURE__*/ nodeProxyIntent( OperatorNode, '|' ).setParameterLength( 2 ).setName( 'bitOr' );
- /**
- * Performs bitwise XOR on two nodes.
- *
- * @tsl
- * @function
- * @param {Node} a - The first input.
- * @param {Node} b - The second input.
- * @returns {OperatorNode}
- */
- const bitXor = /*@__PURE__*/ nodeProxyIntent( OperatorNode, '^' ).setParameterLength( 2 ).setName( 'bitXor' );
- /**
- * Shifts a node to the left.
- *
- * @tsl
- * @function
- * @param {Node} a - The node to shift.
- * @param {Node} b - The value to shift.
- * @returns {OperatorNode}
- */
- const shiftLeft = /*@__PURE__*/ nodeProxyIntent( OperatorNode, '<<' ).setParameterLength( 2 ).setName( 'shiftLeft' );
- /**
- * Shifts a node to the right.
- *
- * @tsl
- * @function
- * @param {Node} a - The node to shift.
- * @param {Node} b - The value to shift.
- * @returns {OperatorNode}
- */
- const shiftRight = /*@__PURE__*/ nodeProxyIntent( OperatorNode, '>>' ).setParameterLength( 2 ).setName( 'shiftRight' );
- /**
- * Increments a node by 1.
- *
- * @tsl
- * @function
- * @param {Node} a - The node to increment.
- * @returns {OperatorNode}
- */
- const incrementBefore = Fn( ( [ a ] ) => {
- a.addAssign( 1 );
- return a;
- } );
- /**
- * Decrements a node by 1.
- *
- * @tsl
- * @function
- * @param {Node} a - The node to decrement.
- * @returns {OperatorNode}
- */
- const decrementBefore = Fn( ( [ a ] ) => {
- a.subAssign( 1 );
- return a;
- } );
- /**
- * Increments a node by 1 and returns the previous value.
- *
- * @tsl
- * @function
- * @param {Node} a - The node to increment.
- * @returns {OperatorNode}
- */
- const increment = /*@__PURE__*/ Fn( ( [ a ] ) => {
- const temp = int( a ).toConst();
- a.addAssign( 1 );
- return temp;
- } );
- /**
- * Decrements a node by 1 and returns the previous value.
- *
- * @tsl
- * @function
- * @param {Node} a - The node to decrement.
- * @returns {OperatorNode}
- */
- const decrement = /*@__PURE__*/ Fn( ( [ a ] ) => {
- const temp = int( a ).toConst();
- a.subAssign( 1 );
- return temp;
- } );
- addMethodChaining( 'add', add );
- addMethodChaining( 'sub', sub );
- addMethodChaining( 'mul', mul );
- addMethodChaining( 'div', div );
- addMethodChaining( 'mod', mod );
- addMethodChaining( 'equal', equal );
- addMethodChaining( 'notEqual', notEqual );
- addMethodChaining( 'lessThan', lessThan );
- addMethodChaining( 'greaterThan', greaterThan );
- addMethodChaining( 'lessThanEqual', lessThanEqual );
- addMethodChaining( 'greaterThanEqual', greaterThanEqual );
- addMethodChaining( 'and', and );
- addMethodChaining( 'or', or );
- addMethodChaining( 'not', not );
- addMethodChaining( 'xor', xor );
- addMethodChaining( 'bitAnd', bitAnd );
- addMethodChaining( 'bitNot', bitNot );
- addMethodChaining( 'bitOr', bitOr );
- addMethodChaining( 'bitXor', bitXor );
- addMethodChaining( 'shiftLeft', shiftLeft );
- addMethodChaining( 'shiftRight', shiftRight );
- addMethodChaining( 'incrementBefore', incrementBefore );
- addMethodChaining( 'decrementBefore', decrementBefore );
- addMethodChaining( 'increment', increment );
- addMethodChaining( 'decrement', decrement );
- /**
- * @tsl
- * @function
- * @deprecated since r175. Use {@link mod} instead.
- *
- * @param {Node} a - The first input.
- * @param {Node} b - The second input.
- * @returns {OperatorNode}
- */
- const modInt = ( a, b ) => { // @deprecated, r175
- warn( 'TSL: "modInt()" is deprecated. Use "mod( int( ... ) )" instead.' );
- return mod( int( a ), int( b ) );
- };
- addMethodChaining( 'modInt', modInt );
- /**
- * This node represents a variety of mathematical methods available in shaders.
- * They are divided into three categories:
- *
- * - Methods with one input like `sin`, `cos` or `normalize`.
- * - Methods with two inputs like `dot`, `cross` or `pow`.
- * - Methods with three inputs like `mix`, `clamp` or `smoothstep`.
- *
- * @augments TempNode
- */
- class MathNode extends TempNode {
- static get type() {
- return 'MathNode';
- }
- /**
- * Constructs a new math node.
- *
- * @param {string} method - The method name.
- * @param {Node} aNode - The first input.
- * @param {?Node} [bNode=null] - The second input.
- * @param {?Node} [cNode=null] - The third input.
- */
- constructor( method, aNode, bNode = null, cNode = null ) {
- super();
- // Allow the max() and min() functions to take an arbitrary number of arguments.
- if ( ( method === MathNode.MAX || method === MathNode.MIN ) && arguments.length > 3 ) {
- let finalOp = new MathNode( method, aNode, bNode );
- for ( let i = 2; i < arguments.length - 1; i ++ ) {
- finalOp = new MathNode( method, finalOp, arguments[ i ] );
- }
- aNode = finalOp;
- bNode = arguments[ arguments.length - 1 ];
- cNode = null;
- }
- /**
- * The method name.
- *
- * @type {string}
- */
- this.method = method;
- /**
- * The first input.
- *
- * @type {Node}
- */
- this.aNode = aNode;
- /**
- * The second input.
- *
- * @type {?Node}
- * @default null
- */
- this.bNode = bNode;
- /**
- * The third input.
- *
- * @type {?Node}
- * @default null
- */
- this.cNode = cNode;
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isMathNode = true;
- }
- /**
- * The input type is inferred from the node types of the input nodes.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {string} The input type.
- */
- getInputType( builder ) {
- const aType = this.aNode.getNodeType( builder );
- const bType = this.bNode ? this.bNode.getNodeType( builder ) : null;
- const cType = this.cNode ? this.cNode.getNodeType( builder ) : null;
- const aLen = builder.isMatrix( aType ) ? 0 : builder.getTypeLength( aType );
- const bLen = builder.isMatrix( bType ) ? 0 : builder.getTypeLength( bType );
- const cLen = builder.isMatrix( cType ) ? 0 : builder.getTypeLength( cType );
- if ( aLen > bLen && aLen > cLen ) {
- return aType;
- } else if ( bLen > cLen ) {
- return bType;
- } else if ( cLen > aLen ) {
- return cType;
- }
- return aType;
- }
- /**
- * The selected method as well as the input type determine the node type of this node.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {string} The node type.
- */
- getNodeType( builder ) {
- const method = this.method;
- if ( method === MathNode.LENGTH || method === MathNode.DISTANCE || method === MathNode.DOT ) {
- return 'float';
- } else if ( method === MathNode.CROSS ) {
- return 'vec3';
- } else if ( method === MathNode.ALL || method === MathNode.ANY ) {
- return 'bool';
- } else if ( method === MathNode.EQUALS ) {
- return builder.changeComponentType( this.aNode.getNodeType( builder ), 'bool' );
- } else {
- return this.getInputType( builder );
- }
- }
- setup( builder ) {
- const { aNode, bNode, method } = this;
- let outputNode = null;
- if ( method === MathNode.ONE_MINUS ) {
- outputNode = sub( 1.0, aNode );
- } else if ( method === MathNode.RECIPROCAL ) {
- outputNode = div( 1.0, aNode );
- } else if ( method === MathNode.DIFFERENCE ) {
- outputNode = abs( sub( aNode, bNode ) );
- } else if ( method === MathNode.TRANSFORM_DIRECTION ) {
- // dir can be either a direction vector or a normal vector
- // upper-left 3x3 of matrix is assumed to be orthogonal
- let tA = aNode;
- let tB = bNode;
- if ( builder.isMatrix( tA.getNodeType( builder ) ) ) {
- tB = vec4( vec3( tB ), 0.0 );
- } else {
- tA = vec4( vec3( tA ), 0.0 );
- }
- const mulNode = mul( tA, tB ).xyz;
- outputNode = normalize( mulNode );
- }
- if ( outputNode !== null ) {
- return outputNode;
- } else {
- return super.setup( builder );
- }
- }
- generate( builder, output ) {
- const properties = builder.getNodeProperties( this );
- if ( properties.outputNode ) {
- return super.generate( builder, output );
- }
- let method = this.method;
- const type = this.getNodeType( builder );
- const inputType = this.getInputType( builder );
- const a = this.aNode;
- const b = this.bNode;
- const c = this.cNode;
- const coordinateSystem = builder.renderer.coordinateSystem;
- if ( method === MathNode.NEGATE ) {
- return builder.format( '( - ' + a.build( builder, inputType ) + ' )', type, output );
- } else {
- const params = [];
- if ( method === MathNode.CROSS ) {
- params.push(
- a.build( builder, type ),
- b.build( builder, type )
- );
- } else if ( coordinateSystem === WebGLCoordinateSystem && method === MathNode.STEP ) {
- params.push(
- a.build( builder, builder.getTypeLength( a.getNodeType( builder ) ) === 1 ? 'float' : inputType ),
- b.build( builder, inputType )
- );
- } else if ( coordinateSystem === WebGLCoordinateSystem && ( method === MathNode.MIN || method === MathNode.MAX ) ) {
- params.push(
- a.build( builder, inputType ),
- b.build( builder, builder.getTypeLength( b.getNodeType( builder ) ) === 1 ? 'float' : inputType )
- );
- } else if ( method === MathNode.REFRACT ) {
- params.push(
- a.build( builder, inputType ),
- b.build( builder, inputType ),
- c.build( builder, 'float' )
- );
- } else if ( method === MathNode.MIX ) {
- params.push(
- a.build( builder, inputType ),
- b.build( builder, inputType ),
- c.build( builder, builder.getTypeLength( c.getNodeType( builder ) ) === 1 ? 'float' : inputType )
- );
- } else {
- if ( coordinateSystem === WebGPUCoordinateSystem && method === MathNode.ATAN && b !== null ) {
- method = 'atan2';
- }
- if ( builder.shaderStage !== 'fragment' && ( method === MathNode.DFDX || method === MathNode.DFDY ) ) {
- warn( `TSL: '${ method }' is not supported in the ${ builder.shaderStage } stage.` );
- method = '/*' + method + '*/';
- }
- params.push( a.build( builder, inputType ) );
- if ( b !== null ) params.push( b.build( builder, inputType ) );
- if ( c !== null ) params.push( c.build( builder, inputType ) );
- }
- return builder.format( `${ builder.getMethod( method, type ) }( ${params.join( ', ' )} )`, type, output );
- }
- }
- serialize( data ) {
- super.serialize( data );
- data.method = this.method;
- }
- deserialize( data ) {
- super.deserialize( data );
- this.method = data.method;
- }
- }
- // 1 input
- MathNode.ALL = 'all';
- MathNode.ANY = 'any';
- MathNode.RADIANS = 'radians';
- MathNode.DEGREES = 'degrees';
- MathNode.EXP = 'exp';
- MathNode.EXP2 = 'exp2';
- MathNode.LOG = 'log';
- MathNode.LOG2 = 'log2';
- MathNode.SQRT = 'sqrt';
- MathNode.INVERSE_SQRT = 'inversesqrt';
- MathNode.FLOOR = 'floor';
- MathNode.CEIL = 'ceil';
- MathNode.NORMALIZE = 'normalize';
- MathNode.FRACT = 'fract';
- MathNode.SIN = 'sin';
- MathNode.COS = 'cos';
- MathNode.TAN = 'tan';
- MathNode.ASIN = 'asin';
- MathNode.ACOS = 'acos';
- MathNode.ATAN = 'atan';
- MathNode.ABS = 'abs';
- MathNode.SIGN = 'sign';
- MathNode.LENGTH = 'length';
- MathNode.NEGATE = 'negate';
- MathNode.ONE_MINUS = 'oneMinus';
- MathNode.DFDX = 'dFdx';
- MathNode.DFDY = 'dFdy';
- MathNode.ROUND = 'round';
- MathNode.RECIPROCAL = 'reciprocal';
- MathNode.TRUNC = 'trunc';
- MathNode.FWIDTH = 'fwidth';
- MathNode.TRANSPOSE = 'transpose';
- MathNode.DETERMINANT = 'determinant';
- MathNode.INVERSE = 'inverse';
- // 2 inputs
- MathNode.EQUALS = 'equals';
- MathNode.MIN = 'min';
- MathNode.MAX = 'max';
- MathNode.STEP = 'step';
- MathNode.REFLECT = 'reflect';
- MathNode.DISTANCE = 'distance';
- MathNode.DIFFERENCE = 'difference';
- MathNode.DOT = 'dot';
- MathNode.CROSS = 'cross';
- MathNode.POW = 'pow';
- MathNode.TRANSFORM_DIRECTION = 'transformDirection';
- // 3 inputs
- MathNode.MIX = 'mix';
- MathNode.CLAMP = 'clamp';
- MathNode.REFRACT = 'refract';
- MathNode.SMOOTHSTEP = 'smoothstep';
- MathNode.FACEFORWARD = 'faceforward';
- // 1 inputs
- /**
- * A small value used to handle floating-point precision errors.
- *
- * @tsl
- * @type {Node<float>}
- */
- const EPSILON = /*@__PURE__*/ float( 1e-6 );
- /**
- * Represents infinity.
- *
- * @tsl
- * @type {Node<float>}
- */
- const INFINITY = /*@__PURE__*/ float( 1e6 );
- /**
- * Represents PI.
- *
- * @tsl
- * @type {Node<float>}
- */
- const PI = /*@__PURE__*/ float( Math.PI );
- /**
- * Represents PI * 2. Please use the non-deprecated version `TWO_PI`.
- *
- * @tsl
- * @deprecated
- * @type {Node<float>}
- */
- const PI2 = /*@__PURE__*/ float( Math.PI * 2 ); // @deprecated r181
- /**
- * Represents PI * 2.
- *
- * @tsl
- * @type {Node<float>}
- */
- const TWO_PI = /*@__PURE__*/ float( Math.PI * 2 );
- /**
- * Represents PI / 2.
- *
- * @tsl
- * @type {Node<float>}
- */
- const HALF_PI = /*@__PURE__*/ float( Math.PI * 0.5 );
- /**
- * Returns `true` if all components of `x` are `true`.
- *
- * @tsl
- * @function
- * @param {Node | number} x - The parameter.
- * @returns {Node<bool>}
- */
- const all = /*@__PURE__*/ nodeProxyIntent( MathNode, MathNode.ALL ).setParameterLength( 1 );
- /**
- * Returns `true` if any components of `x` are `true`.
- *
- * @tsl
- * @function
- * @param {Node | number} x - The parameter.
- * @returns {Node<bool>}
- */
- const any = /*@__PURE__*/ nodeProxyIntent( MathNode, MathNode.ANY ).setParameterLength( 1 );
- /**
- * Converts a quantity in degrees to radians.
- *
- * @tsl
- * @function
- * @param {Node | number} x - The input in degrees.
- * @returns {Node}
- */
- const radians = /*@__PURE__*/ nodeProxyIntent( MathNode, MathNode.RADIANS ).setParameterLength( 1 );
- /**
- * Convert a quantity in radians to degrees.
- *
- * @tsl
- * @function
- * @param {Node | number} x - The input in radians.
- * @returns {Node}
- */
- const degrees = /*@__PURE__*/ nodeProxyIntent( MathNode, MathNode.DEGREES ).setParameterLength( 1 );
- /**
- * Returns the natural exponentiation of the parameter.
- *
- * @tsl
- * @function
- * @param {Node | number} x - The parameter.
- * @returns {Node}
- */
- const exp = /*@__PURE__*/ nodeProxyIntent( MathNode, MathNode.EXP ).setParameterLength( 1 );
- /**
- * Returns 2 raised to the power of the parameter.
- *
- * @tsl
- * @function
- * @param {Node | number} x - The parameter.
- * @returns {Node}
- */
- const exp2 = /*@__PURE__*/ nodeProxyIntent( MathNode, MathNode.EXP2 ).setParameterLength( 1 );
- /**
- * Returns the natural logarithm of the parameter.
- *
- * @tsl
- * @function
- * @param {Node | number} x - The parameter.
- * @returns {Node}
- */
- const log = /*@__PURE__*/ nodeProxyIntent( MathNode, MathNode.LOG ).setParameterLength( 1 );
- /**
- * Returns the base 2 logarithm of the parameter.
- *
- * @tsl
- * @function
- * @param {Node | number} x - The parameter.
- * @returns {Node}
- */
- const log2 = /*@__PURE__*/ nodeProxyIntent( MathNode, MathNode.LOG2 ).setParameterLength( 1 );
- /**
- * Returns the square root of the parameter.
- *
- * @tsl
- * @function
- * @param {Node | number} x - The parameter.
- * @returns {Node}
- */
- const sqrt = /*@__PURE__*/ nodeProxyIntent( MathNode, MathNode.SQRT ).setParameterLength( 1 );
- /**
- * Returns the inverse of the square root of the parameter.
- *
- * @tsl
- * @function
- * @param {Node | number} x - The parameter.
- * @returns {Node}
- */
- const inverseSqrt = /*@__PURE__*/ nodeProxyIntent( MathNode, MathNode.INVERSE_SQRT ).setParameterLength( 1 );
- /**
- * Finds the nearest integer less than or equal to the parameter.
- *
- * @tsl
- * @function
- * @param {Node | number} x - The parameter.
- * @returns {Node}
- */
- const floor = /*@__PURE__*/ nodeProxyIntent( MathNode, MathNode.FLOOR ).setParameterLength( 1 );
- /**
- * Finds the nearest integer that is greater than or equal to the parameter.
- *
- * @tsl
- * @function
- * @param {Node | number} x - The parameter.
- * @returns {Node}
- */
- const ceil = /*@__PURE__*/ nodeProxyIntent( MathNode, MathNode.CEIL ).setParameterLength( 1 );
- /**
- * Calculates the unit vector in the same direction as the original vector.
- *
- * @tsl
- * @function
- * @param {Node} x - The input vector.
- * @returns {Node}
- */
- const normalize = /*@__PURE__*/ nodeProxyIntent( MathNode, MathNode.NORMALIZE ).setParameterLength( 1 );
- /**
- * Computes the fractional part of the parameter.
- *
- * @tsl
- * @function
- * @param {Node | number} x - The parameter.
- * @returns {Node}
- */
- const fract = /*@__PURE__*/ nodeProxyIntent( MathNode, MathNode.FRACT ).setParameterLength( 1 );
- /**
- * Returns the sine of the parameter.
- *
- * @tsl
- * @function
- * @param {Node | number} x - The parameter.
- * @returns {Node}
- */
- const sin = /*@__PURE__*/ nodeProxyIntent( MathNode, MathNode.SIN ).setParameterLength( 1 );
- /**
- * Returns the cosine of the parameter.
- *
- * @tsl
- * @function
- * @param {Node | number} x - The parameter.
- * @returns {Node}
- */
- const cos = /*@__PURE__*/ nodeProxyIntent( MathNode, MathNode.COS ).setParameterLength( 1 );
- /**
- * Returns the tangent of the parameter.
- *
- * @tsl
- * @function
- * @param {Node | number} x - The parameter.
- * @returns {Node}
- */
- const tan = /*@__PURE__*/ nodeProxyIntent( MathNode, MathNode.TAN ).setParameterLength( 1 );
- /**
- * Returns the arcsine of the parameter.
- *
- * @tsl
- * @function
- * @param {Node | number} x - The parameter.
- * @returns {Node}
- */
- const asin = /*@__PURE__*/ nodeProxyIntent( MathNode, MathNode.ASIN ).setParameterLength( 1 );
- /**
- * Returns the arccosine of the parameter.
- *
- * @tsl
- * @function
- * @param {Node | number} x - The parameter.
- * @returns {Node}
- */
- const acos = /*@__PURE__*/ nodeProxyIntent( MathNode, MathNode.ACOS ).setParameterLength( 1 );
- /**
- * Returns the arc-tangent of the parameter.
- * If two parameters are provided, the result is `atan2(y/x)`.
- *
- * @tsl
- * @function
- * @param {Node | number} y - The y parameter.
- * @param {?(Node | number)} x - The x parameter.
- * @returns {Node}
- */
- const atan = /*@__PURE__*/ nodeProxyIntent( MathNode, MathNode.ATAN ).setParameterLength( 1, 2 );
- /**
- * Returns the absolute value of the parameter.
- *
- * @tsl
- * @function
- * @param {Node | number} x - The parameter.
- * @returns {Node}
- */
- const abs = /*@__PURE__*/ nodeProxyIntent( MathNode, MathNode.ABS ).setParameterLength( 1 );
- /**
- * Extracts the sign of the parameter.
- *
- * @tsl
- * @function
- * @param {Node | number} x - The parameter.
- * @returns {Node}
- */
- const sign = /*@__PURE__*/ nodeProxyIntent( MathNode, MathNode.SIGN ).setParameterLength( 1 );
- /**
- * Calculates the length of a vector.
- *
- * @tsl
- * @function
- * @param {Node} x - The parameter.
- * @returns {Node<float>}
- */
- const length = /*@__PURE__*/ nodeProxyIntent( MathNode, MathNode.LENGTH ).setParameterLength( 1 );
- /**
- * Negates the value of the parameter (-x).
- *
- * @tsl
- * @function
- * @param {Node | number} x - The parameter.
- * @returns {Node}
- */
- const negate = /*@__PURE__*/ nodeProxyIntent( MathNode, MathNode.NEGATE ).setParameterLength( 1 );
- /**
- * Return `1` minus the parameter.
- *
- * @tsl
- * @function
- * @param {Node | number} x - The parameter.
- * @returns {Node}
- */
- const oneMinus = /*@__PURE__*/ nodeProxyIntent( MathNode, MathNode.ONE_MINUS ).setParameterLength( 1 );
- /**
- * Returns the partial derivative of the parameter with respect to x.
- *
- * @tsl
- * @function
- * @param {Node | number} x - The parameter.
- * @returns {Node}
- */
- const dFdx = /*@__PURE__*/ nodeProxyIntent( MathNode, MathNode.DFDX ).setParameterLength( 1 );
- /**
- * Returns the partial derivative of the parameter with respect to y.
- *
- * @tsl
- * @function
- * @param {Node | number} x - The parameter.
- * @returns {Node}
- */
- const dFdy = /*@__PURE__*/ nodeProxyIntent( MathNode, MathNode.DFDY ).setParameterLength( 1 );
- /**
- * Rounds the parameter to the nearest integer.
- *
- * @tsl
- * @function
- * @param {Node | number} x - The parameter.
- * @returns {Node}
- */
- const round = /*@__PURE__*/ nodeProxyIntent( MathNode, MathNode.ROUND ).setParameterLength( 1 );
- /**
- * Returns the reciprocal of the parameter `(1/x)`.
- *
- * @tsl
- * @function
- * @param {Node | number} x - The parameter.
- * @returns {Node}
- */
- const reciprocal = /*@__PURE__*/ nodeProxyIntent( MathNode, MathNode.RECIPROCAL ).setParameterLength( 1 );
- /**
- * Truncates the parameter, removing the fractional part.
- *
- * @tsl
- * @function
- * @param {Node | number} x - The parameter.
- * @returns {Node}
- */
- const trunc = /*@__PURE__*/ nodeProxyIntent( MathNode, MathNode.TRUNC ).setParameterLength( 1 );
- /**
- * Returns the sum of the absolute derivatives in x and y.
- *
- * @tsl
- * @function
- * @param {Node | number} x - The parameter.
- * @returns {Node}
- */
- const fwidth = /*@__PURE__*/ nodeProxyIntent( MathNode, MathNode.FWIDTH ).setParameterLength( 1 );
- /**
- * Returns the transpose of a matrix.
- *
- * @tsl
- * @function
- * @param {Node<mat2|mat3|mat4>} x - The parameter.
- * @returns {Node}
- */
- const transpose = /*@__PURE__*/ nodeProxyIntent( MathNode, MathNode.TRANSPOSE ).setParameterLength( 1 );
- /**
- * Returns the determinant of a matrix.
- *
- * @tsl
- * @function
- * @param {Node<mat2|mat3|mat4>} x - The parameter.
- * @returns {Node<float>}
- */
- const determinant = /*@__PURE__*/ nodeProxyIntent( MathNode, MathNode.DETERMINANT ).setParameterLength( 1 );
- /**
- * Returns the inverse of a matrix.
- *
- * @tsl
- * @function
- * @param {Node<mat2|mat3|mat4>} x - The parameter.
- * @returns {Node<mat2|mat3|mat4>}
- */
- const inverse = /*@__PURE__*/ nodeProxyIntent( MathNode, MathNode.INVERSE ).setParameterLength( 1 );
- // 2 inputs
- /**
- * Returns `true` if `x` equals `y`.
- *
- * @tsl
- * @function
- * @param {Node | number} x - The first parameter.
- * @param {Node | number} y - The second parameter.
- * @deprecated since r175. Use {@link equal} instead.
- * @returns {Node<bool>}
- */
- const equals = ( x, y ) => { // @deprecated, r172
- warn( 'TSL: "equals" is deprecated. Use "equal" inside a vector instead, like: "bvec*( equal( ... ) )"' );
- return equal( x, y );
- };
- /**
- * Returns the least of the given values.
- *
- * @tsl
- * @function
- * @param {...(Node | number)} values - The values to compare.
- * @returns {Node}
- */
- const min$1 = /*@__PURE__*/ nodeProxyIntent( MathNode, MathNode.MIN ).setParameterLength( 2, Infinity );
- /**
- * Returns the greatest of the given values.
- *
- * @tsl
- * @function
- * @param {...(Node | number)} values - The values to compare.
- * @returns {Node}
- */
- const max$1 = /*@__PURE__*/ nodeProxyIntent( MathNode, MathNode.MAX ).setParameterLength( 2, Infinity );
- /**
- * Generate a step function by comparing two values.
- *
- * @tsl
- * @function
- * @param {Node | number} x - The y parameter.
- * @param {Node | number} y - The x parameter.
- * @returns {Node}
- */
- const step = /*@__PURE__*/ nodeProxyIntent( MathNode, MathNode.STEP ).setParameterLength( 2 );
- /**
- * Calculates the reflection direction for an incident vector.
- *
- * @tsl
- * @function
- * @param {Node<vec2|vec3|vec4>} I - The incident vector.
- * @param {Node<vec2|vec3|vec4>} N - The normal vector.
- * @returns {Node<vec2|vec3|vec4>}
- */
- const reflect = /*@__PURE__*/ nodeProxyIntent( MathNode, MathNode.REFLECT ).setParameterLength( 2 );
- /**
- * Calculates the distance between two points.
- *
- * @tsl
- * @function
- * @param {Node<vec2|vec3|vec4>} x - The first point.
- * @param {Node<vec2|vec3|vec4>} y - The second point.
- * @returns {Node<float>}
- */
- const distance = /*@__PURE__*/ nodeProxyIntent( MathNode, MathNode.DISTANCE ).setParameterLength( 2 );
- /**
- * Calculates the absolute difference between two values.
- *
- * @tsl
- * @function
- * @param {Node | number} x - The first parameter.
- * @param {Node | number} y - The second parameter.
- * @returns {Node}
- */
- const difference = /*@__PURE__*/ nodeProxyIntent( MathNode, MathNode.DIFFERENCE ).setParameterLength( 2 );
- /**
- * Calculates the dot product of two vectors.
- *
- * @tsl
- * @function
- * @param {Node<vec2|vec3|vec4>} x - The first vector.
- * @param {Node<vec2|vec3|vec4>} y - The second vector.
- * @returns {Node<float>}
- */
- const dot = /*@__PURE__*/ nodeProxyIntent( MathNode, MathNode.DOT ).setParameterLength( 2 );
- /**
- * Calculates the cross product of two vectors.
- *
- * @tsl
- * @function
- * @param {Node<vec2|vec3>} x - The first vector.
- * @param {Node<vec2|vec3>} y - The second vector.
- * @returns {Node<float|vec3>}
- */
- const cross = /*@__PURE__*/ nodeProxyIntent( MathNode, MathNode.CROSS ).setParameterLength( 2 );
- /**
- * Return the value of the first parameter raised to the power of the second one.
- *
- * @tsl
- * @function
- * @param {Node | number} x - The first parameter.
- * @param {Node | number} y - The second parameter.
- * @returns {Node}
- */
- const pow = /*@__PURE__*/ nodeProxyIntent( MathNode, MathNode.POW ).setParameterLength( 2 );
- /**
- * Returns the square of the parameter.
- *
- * @tsl
- * @function
- * @param {Node | number} x - The first parameter.
- * @returns {Node}
- */
- const pow2 = ( x ) => mul( x, x );
- /**
- * Returns the cube of the parameter.
- *
- * @tsl
- * @function
- * @param {Node | number} x - The first parameter.
- * @returns {Node}
- */
- const pow3 = ( x ) => mul( x, x, x );
- /**
- * Returns the fourth power of the parameter.
- *
- * @tsl
- * @function
- * @param {Node | number} x - The first parameter.
- * @returns {Node}
- */
- const pow4 = ( x ) => mul( x, x, x, x );
- /**
- * Transforms the direction of a vector by a matrix and then normalizes the result.
- *
- * @tsl
- * @function
- * @param {Node<vec2|vec3|vec4>} direction - The direction vector.
- * @param {Node<mat2|mat3|mat4>} matrix - The transformation matrix.
- * @returns {Node}
- */
- const transformDirection = /*@__PURE__*/ nodeProxyIntent( MathNode, MathNode.TRANSFORM_DIRECTION ).setParameterLength( 2 );
- /**
- * Returns the cube root of a number.
- *
- * @tsl
- * @function
- * @param {Node | number} a - The first parameter.
- * @returns {Node}
- */
- const cbrt = ( a ) => mul( sign( a ), pow( abs( a ), 1.0 / 3.0 ) );
- /**
- * Calculate the squared length of a vector.
- *
- * @tsl
- * @function
- * @param {Node<vec2|vec3|vec4>} a - The vector.
- * @returns {Node<float>}
- */
- const lengthSq = ( a ) => dot( a, a );
- /**
- * Linearly interpolates between two values.
- *
- * @tsl
- * @function
- * @param {Node | number} a - The first parameter.
- * @param {Node | number} b - The second parameter.
- * @param {Node | number} t - The interpolation value.
- * @returns {Node}
- */
- const mix = /*@__PURE__*/ nodeProxyIntent( MathNode, MathNode.MIX ).setParameterLength( 3 );
- /**
- * Constrains a value to lie between two further values.
- *
- * @tsl
- * @function
- * @param {Node | number} value - The value to constrain.
- * @param {Node | number} [low=0] - The lower bound.
- * @param {Node | number} [high=1] - The upper bound.
- * @returns {Node}
- */
- const clamp = ( value, low = 0, high = 1 ) => nodeObject( new MathNode( MathNode.CLAMP, nodeObject( value ), nodeObject( low ), nodeObject( high ) ) );
- /**
- * Constrains a value between `0` and `1`.
- *
- * @tsl
- * @function
- * @param {Node | number} value - The value to constrain.
- * @returns {Node}
- */
- const saturate = ( value ) => clamp( value );
- /**
- * Calculates the refraction direction for an incident vector.
- *
- * @tsl
- * @function
- * @param {Node<vec2|vec3|vec4>} I - The incident vector.
- * @param {Node<vec2|vec3|vec4>} N - The normal vector.
- * @param {Node<float>} eta - The ratio of indices of refraction.
- * @returns {Node<vec2|vec3|vec4>}
- */
- const refract = /*@__PURE__*/ nodeProxyIntent( MathNode, MathNode.REFRACT ).setParameterLength( 3 );
- /**
- * Performs a Hermite interpolation between two values.
- *
- * @tsl
- * @function
- * @param {Node | number} low - The value of the lower edge of the Hermite function.
- * @param {Node | number} high - The value of the upper edge of the Hermite function.
- * @param {Node | number} x - The source value for interpolation.
- * @returns {Node}
- */
- const smoothstep = /*@__PURE__*/ nodeProxyIntent( MathNode, MathNode.SMOOTHSTEP ).setParameterLength( 3 );
- /**
- * Returns a vector pointing in the same direction as another.
- *
- * @tsl
- * @function
- * @param {Node<vec2|vec3|vec4>} N - The vector to orient.
- * @param {Node<vec2|vec3|vec4>} I - The incident vector.
- * @param {Node<vec2|vec3|vec4>} Nref - The reference vector.
- * @returns {Node<vec2|vec3|vec4>}
- */
- const faceForward = /*@__PURE__*/ nodeProxyIntent( MathNode, MathNode.FACEFORWARD ).setParameterLength( 3 );
- /**
- * Returns a random value for the given uv.
- *
- * @tsl
- * @function
- * @param {Node<vec2>} uv - The uv node.
- * @returns {Node<float>}
- */
- const rand = /*@__PURE__*/ Fn( ( [ uv ] ) => {
- const a = 12.9898, b = 78.233, c = 43758.5453;
- const dt = dot( uv.xy, vec2( a, b ) ), sn = mod( dt, PI );
- return fract( sin( sn ).mul( c ) );
- } );
- /**
- * Alias for `mix()` with a different parameter order.
- *
- * @tsl
- * @function
- * @param {Node | number} t - The interpolation value.
- * @param {Node | number} e1 - The first parameter.
- * @param {Node | number} e2 - The second parameter.
- * @returns {Node}
- */
- const mixElement = ( t, e1, e2 ) => mix( e1, e2, t );
- /**
- * Alias for `smoothstep()` with a different parameter order.
- *
- * @tsl
- * @function
- * @param {Node | number} x - The source value for interpolation.
- * @param {Node | number} low - The value of the lower edge of the Hermite function.
- * @param {Node | number} high - The value of the upper edge of the Hermite function.
- * @returns {Node}
- */
- const smoothstepElement = ( x, low, high ) => smoothstep( low, high, x );
- /**
- * Alias for `step()` with a different parameter order.
- *
- * @tsl
- * @function
- * @param {Node | number} x - The source value for interpolation.
- * @param {Node | number} edge - The edge value.
- * @returns {Node}
- */
- const stepElement = ( x, edge ) => step( edge, x );
- /**
- * Returns the arc-tangent of the quotient of its parameters.
- *
- * @tsl
- * @function
- * @deprecated since r172. Use {@link atan} instead.
- *
- * @param {Node | number} y - The y parameter.
- * @param {Node | number} x - The x parameter.
- * @returns {Node}
- */
- const atan2 = ( y, x ) => { // @deprecated, r172
- warn( 'TSL: "atan2" is overloaded. Use "atan" instead.' );
- return atan( y, x );
- };
- // GLSL alias function
- const faceforward = faceForward;
- const inversesqrt = inverseSqrt;
- // Method chaining
- addMethodChaining( 'all', all );
- addMethodChaining( 'any', any );
- addMethodChaining( 'equals', equals );
- addMethodChaining( 'radians', radians );
- addMethodChaining( 'degrees', degrees );
- addMethodChaining( 'exp', exp );
- addMethodChaining( 'exp2', exp2 );
- addMethodChaining( 'log', log );
- addMethodChaining( 'log2', log2 );
- addMethodChaining( 'sqrt', sqrt );
- addMethodChaining( 'inverseSqrt', inverseSqrt );
- addMethodChaining( 'floor', floor );
- addMethodChaining( 'ceil', ceil );
- addMethodChaining( 'normalize', normalize );
- addMethodChaining( 'fract', fract );
- addMethodChaining( 'sin', sin );
- addMethodChaining( 'cos', cos );
- addMethodChaining( 'tan', tan );
- addMethodChaining( 'asin', asin );
- addMethodChaining( 'acos', acos );
- addMethodChaining( 'atan', atan );
- addMethodChaining( 'abs', abs );
- addMethodChaining( 'sign', sign );
- addMethodChaining( 'length', length );
- addMethodChaining( 'lengthSq', lengthSq );
- addMethodChaining( 'negate', negate );
- addMethodChaining( 'oneMinus', oneMinus );
- addMethodChaining( 'dFdx', dFdx );
- addMethodChaining( 'dFdy', dFdy );
- addMethodChaining( 'round', round );
- addMethodChaining( 'reciprocal', reciprocal );
- addMethodChaining( 'trunc', trunc );
- addMethodChaining( 'fwidth', fwidth );
- addMethodChaining( 'atan2', atan2 );
- addMethodChaining( 'min', min$1 );
- addMethodChaining( 'max', max$1 );
- addMethodChaining( 'step', stepElement );
- addMethodChaining( 'reflect', reflect );
- addMethodChaining( 'distance', distance );
- addMethodChaining( 'dot', dot );
- addMethodChaining( 'cross', cross );
- addMethodChaining( 'pow', pow );
- addMethodChaining( 'pow2', pow2 );
- addMethodChaining( 'pow3', pow3 );
- addMethodChaining( 'pow4', pow4 );
- addMethodChaining( 'transformDirection', transformDirection );
- addMethodChaining( 'mix', mixElement );
- addMethodChaining( 'clamp', clamp );
- addMethodChaining( 'refract', refract );
- addMethodChaining( 'smoothstep', smoothstepElement );
- addMethodChaining( 'faceForward', faceForward );
- addMethodChaining( 'difference', difference );
- addMethodChaining( 'saturate', saturate );
- addMethodChaining( 'cbrt', cbrt );
- addMethodChaining( 'transpose', transpose );
- addMethodChaining( 'determinant', determinant );
- addMethodChaining( 'inverse', inverse );
- addMethodChaining( 'rand', rand );
- /**
- * Represents a logical `if/else` statement. Can be used as an alternative
- * to the `If()`/`Else()` syntax.
- *
- * The corresponding TSL `select()` looks like so:
- * ```js
- * velocity = position.greaterThanEqual( limit ).select( velocity.negate(), velocity );
- * ```
- * The `select()` method is called in a chaining fashion on a condition. The parameter nodes of `select()`
- * determine the outcome of the entire statement.
- *
- * @augments Node
- */
- class ConditionalNode extends Node {
- static get type() {
- return 'ConditionalNode';
- }
- /**
- * Constructs a new conditional node.
- *
- * @param {Node} condNode - The node that defines the condition.
- * @param {Node} ifNode - The node that is evaluate when the condition ends up `true`.
- * @param {?Node} [elseNode=null] - The node that is evaluate when the condition ends up `false`.
- */
- constructor( condNode, ifNode, elseNode = null ) {
- super();
- /**
- * The node that defines the condition.
- *
- * @type {Node}
- */
- this.condNode = condNode;
- /**
- * The node that is evaluate when the condition ends up `true`.
- *
- * @type {Node}
- */
- this.ifNode = ifNode;
- /**
- * The node that is evaluate when the condition ends up `false`.
- *
- * @type {?Node}
- * @default null
- */
- this.elseNode = elseNode;
- }
- /**
- * This method is overwritten since the node type is inferred from the if/else
- * nodes.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {string} The node type.
- */
- getNodeType( builder ) {
- const { ifNode, elseNode } = builder.getNodeProperties( this );
- if ( ifNode === undefined ) {
- // fallback setup
- builder.flowBuildStage( this, 'setup' );
- return this.getNodeType( builder );
- }
- const ifType = ifNode.getNodeType( builder );
- if ( elseNode !== null ) {
- const elseType = elseNode.getNodeType( builder );
- if ( builder.getTypeLength( elseType ) > builder.getTypeLength( ifType ) ) {
- return elseType;
- }
- }
- return ifType;
- }
- setup( builder ) {
- const condNode = this.condNode;
- const ifNode = this.ifNode.isolate();
- const elseNode = this.elseNode ? this.elseNode.isolate() : null;
- //
- const currentNodeBlock = builder.context.nodeBlock;
- builder.getDataFromNode( ifNode ).parentNodeBlock = currentNodeBlock;
- if ( elseNode !== null ) builder.getDataFromNode( elseNode ).parentNodeBlock = currentNodeBlock;
- //
- const isUniformFlow = builder.context.uniformFlow;
- const properties = builder.getNodeProperties( this );
- properties.condNode = condNode;
- properties.ifNode = isUniformFlow ? ifNode : ifNode.context( { nodeBlock: ifNode } );
- properties.elseNode = elseNode ? ( isUniformFlow ? elseNode : elseNode.context( { nodeBlock: elseNode } ) ) : null;
- }
- generate( builder, output ) {
- const type = this.getNodeType( builder );
- const nodeData = builder.getDataFromNode( this );
- if ( nodeData.nodeProperty !== undefined ) {
- return nodeData.nodeProperty;
- }
- const { condNode, ifNode, elseNode } = builder.getNodeProperties( this );
- const functionNode = builder.currentFunctionNode;
- const needsOutput = output !== 'void';
- const nodeProperty = needsOutput ? property( type ).build( builder ) : '';
- nodeData.nodeProperty = nodeProperty;
- const nodeSnippet = condNode.build( builder, 'bool' );
- const isUniformFlow = builder.context.uniformFlow;
- if ( isUniformFlow && elseNode !== null ) {
- const ifSnippet = ifNode.build( builder, type );
- const elseSnippet = elseNode.build( builder, type );
- const mathSnippet = builder.getTernary( nodeSnippet, ifSnippet, elseSnippet );
- // TODO: If node property already exists return something else
- return builder.format( mathSnippet, type, output );
- }
- builder.addFlowCode( `\n${ builder.tab }if ( ${ nodeSnippet } ) {\n\n` ).addFlowTab();
- let ifSnippet = ifNode.build( builder, type );
- if ( ifSnippet ) {
- if ( needsOutput ) {
- ifSnippet = nodeProperty + ' = ' + ifSnippet + ';';
- } else {
- ifSnippet = 'return ' + ifSnippet + ';';
- if ( functionNode === null ) {
- warn( 'TSL: Return statement used in an inline \'Fn()\'. Define a layout struct to allow return values.' );
- ifSnippet = '// ' + ifSnippet;
- }
- }
- }
- builder.removeFlowTab().addFlowCode( builder.tab + '\t' + ifSnippet + '\n\n' + builder.tab + '}' );
- if ( elseNode !== null ) {
- builder.addFlowCode( ' else {\n\n' ).addFlowTab();
- let elseSnippet = elseNode.build( builder, type );
- if ( elseSnippet ) {
- if ( needsOutput ) {
- elseSnippet = nodeProperty + ' = ' + elseSnippet + ';';
- } else {
- elseSnippet = 'return ' + elseSnippet + ';';
- if ( functionNode === null ) {
- warn( 'TSL: Return statement used in an inline \'Fn()\'. Define a layout struct to allow return values.' );
- elseSnippet = '// ' + elseSnippet;
- }
- }
- }
- builder.removeFlowTab().addFlowCode( builder.tab + '\t' + elseSnippet + '\n\n' + builder.tab + '}\n\n' );
- } else {
- builder.addFlowCode( '\n\n' );
- }
- return builder.format( nodeProperty, type, output );
- }
- }
- /**
- * TSL function for creating a conditional node.
- *
- * @tsl
- * @function
- * @param {Node} condNode - The node that defines the condition.
- * @param {Node} ifNode - The node that is evaluate when the condition ends up `true`.
- * @param {?Node} [elseNode=null] - The node that is evaluate when the condition ends up `false`.
- * @returns {ConditionalNode}
- */
- const select = /*@__PURE__*/ nodeProxy( ConditionalNode ).setParameterLength( 2, 3 );
- addMethodChaining( 'select', select );
- /**
- * This node can be used as a context management component for another node.
- * {@link NodeBuilder} performs its node building process in a specific context and
- * this node allows the modify the context. A typical use case is to overwrite `getUV()` e.g.:
- *
- * ```js
- *node.context( { getUV: () => customCoord } );
- *\// or
- *material.contextNode = context( { getUV: () => customCoord } );
- *\// or
- *renderer.contextNode = context( { getUV: () => customCoord } );
- *\// or
- *scenePass.contextNode = context( { getUV: () => customCoord } );
- *```
- * @augments Node
- */
- class ContextNode extends Node {
- static get type() {
- return 'ContextNode';
- }
- /**
- * Constructs a new context node.
- *
- * @param {Node} node - The node whose context should be modified.
- * @param {Object} [value={}] - The modified context data.
- */
- constructor( node = null, value = {} ) {
- super();
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isContextNode = true;
- /**
- * The node whose context should be modified.
- *
- * @type {Node}
- */
- this.node = node;
- /**
- * The modified context data.
- *
- * @type {Object}
- * @default {}
- */
- this.value = value;
- }
- /**
- * This method is overwritten to ensure it returns the reference to {@link ContextNode#node}.
- *
- * @return {Node} A reference to {@link ContextNode#node}.
- */
- getScope() {
- return this.node.getScope();
- }
- /**
- * This method is overwritten to ensure it returns the type of {@link ContextNode#node}.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {string} The node type.
- */
- getNodeType( builder ) {
- return this.node.getNodeType( builder );
- }
- /**
- * Gathers the context data from all parent context nodes.
- *
- * @return {Object} The gathered context data.
- */
- getFlowContextData() {
- const children = [];
- this.traverse( ( node ) => {
- if ( node.isContextNode === true ) {
- children.push( node.value );
- }
- } );
- return Object.assign( {}, ...children );
- }
- /**
- * This method is overwritten to ensure it returns the member type of {@link ContextNode#node}.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @param {string} name - The member name.
- * @returns {string} The member type.
- */
- getMemberType( builder, name ) {
- return this.node.getMemberType( builder, name );
- }
- analyze( builder ) {
- const previousContext = builder.addContext( this.value );
- this.node.build( builder );
- builder.setContext( previousContext );
- }
- setup( builder ) {
- const previousContext = builder.addContext( this.value );
- this.node.build( builder );
- builder.setContext( previousContext );
- }
- generate( builder, output ) {
- const previousContext = builder.addContext( this.value );
- const snippet = this.node.build( builder, output );
- builder.setContext( previousContext );
- return snippet;
- }
- }
- /**
- * TSL function for creating a context node.
- *
- * @tsl
- * @function
- * @param {Node|Object} [nodeOrValue={}] - The node whose context should be modified or the modified context data.
- * @param {Object} [value={}] - The modified context data.
- * @returns {ContextNode}
- */
- const context = ( nodeOrValue = null, value = {} ) => {
- let node = nodeOrValue;
- if ( node === null || node.isNode !== true ) {
- value = node || value;
- node = null;
- }
- return new ContextNode( node, value );
- };
- /**
- * TSL function for defining a uniformFlow context value for a given node.
- *
- * @tsl
- * @function
- * @param {Node} node - The node whose dependencies should all execute within a uniform control-flow path.
- * @returns {ContextNode}
- */
- const uniformFlow = ( node ) => context( node, { uniformFlow: true } );
- /**
- * TSL function for defining a name for the context value for a given node.
- *
- * @tsl
- * @function
- * @param {Node} node - The node whose context should be modified.
- * @param {string} name - The name to set.
- * @returns {ContextNode}
- */
- const setName = ( node, name ) => context( node, { nodeName: name } );
- /**
- * TSL function for defining a built-in shadow context for a given node.
- *
- * @tsl
- * @function
- * @param {ShadowNode} shadowNode - The shadow node representing the light's shadow.
- * @param {Light} light - The light associated with the shadow.
- * @param {Node} [node=null] - The node whose context should be modified.
- * @returns {ContextNode}
- */
- function builtinShadowContext( shadowNode, light, node = null ) {
- return context( node, {
- getShadow: ( { light: shadowLight, shadowColorNode } ) => {
- if ( light === shadowLight ) {
- return shadowColorNode.mul( shadowNode );
- }
- return shadowColorNode;
- }
- } );
- }
- /**
- * TSL function for defining a built-in ambient occlusion context for a given node.
- *
- * @tsl
- * @function
- * @param {Node} aoNode - The ambient occlusion value node to apply.
- * @param {Node} [node=null] - The node whose context should be modified.
- * @returns {ContextNode}
- */
- function builtinAOContext( aoNode, node = null ) {
- return context( node, {
- getAO: ( inputNode, { material } ) => {
- if ( material.transparent === true ) return inputNode;
- return inputNode !== null ? inputNode.mul( aoNode ) : aoNode;
- }
- } );
- }
- /**
- * TSL function for defining a label context value for a given node.
- *
- * @tsl
- * @function
- * @deprecated
- * @param {Node} node - The node whose context should be modified.
- * @param {string} name - The name/label to set.
- * @returns {ContextNode}
- */
- function label( node, name ) {
- warn( 'TSL: "label()" has been deprecated. Use "setName()" instead.' ); // @deprecated r179
- return setName( node, name );
- }
- addMethodChaining( 'context', context );
- addMethodChaining( 'label', label );
- addMethodChaining( 'uniformFlow', uniformFlow );
- addMethodChaining( 'setName', setName );
- addMethodChaining( 'builtinShadowContext', ( node, shadowNode, light ) => builtinShadowContext( shadowNode, light, node ) );
- addMethodChaining( 'builtinAOContext', ( node, aoValue ) => builtinAOContext( aoValue, node ) );
- /**
- * Class for representing shader variables as nodes. Variables are created from
- * existing nodes like the following:
- *
- * ```js
- * const depth = sampleDepth( uvNode ).toVar( 'depth' );
- * ```
- *
- * @augments Node
- */
- class VarNode extends Node {
- static get type() {
- return 'VarNode';
- }
- /**
- * Constructs a new variable node.
- *
- * @param {Node} node - The node for which a variable should be created.
- * @param {?string} [name=null] - The name of the variable in the shader.
- * @param {boolean} [readOnly=false] - The read-only flag.
- */
- constructor( node, name = null, readOnly = false ) {
- super();
- /**
- * The node for which a variable should be created.
- *
- * @type {Node}
- */
- this.node = node;
- /**
- * The name of the variable in the shader. If no name is defined,
- * the node system auto-generates one.
- *
- * @type {?string}
- * @default null
- */
- this.name = name;
- /**
- * `VarNode` sets this property to `true` by default.
- *
- * @type {boolean}
- * @default true
- */
- this.global = true;
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isVarNode = true;
- /**
- *
- * The read-only flag.
- *
- * @type {boolean}
- * @default false
- */
- this.readOnly = readOnly;
- /**
- *
- * Add this flag to the node system to indicate that this node require parents.
- *
- * @type {boolean}
- * @default true
- */
- this.parents = true;
- /**
- * This flag is used to indicate that this node is used for intent.
- *
- * @type {boolean}
- * @default false
- */
- this.intent = false;
- }
- /**
- * Sets the intent flag for this node.
- *
- * This flag is used to indicate that this node is used for intent
- * and should not be built directly. Instead, it is used to indicate that
- * the node should be treated as a variable intent.
- *
- * It's useful for assigning variables without needing creating a new variable node.
- *
- * @param {boolean} value - The value to set for the intent flag.
- * @returns {VarNode} This node.
- */
- setIntent( value ) {
- this.intent = value;
- return this;
- }
- /**
- * Checks if this node is used for intent.
- *
- * @param {NodeBuilder} builder - The node builder.
- * @returns {boolean} Whether this node is used for intent.
- */
- isIntent( builder ) {
- const data = builder.getDataFromNode( this );
- if ( data.forceDeclaration === true ) return false;
- return this.intent;
- }
- /**
- * Returns the intent flag of this node.
- *
- * @return {boolean} The intent flag.
- */
- getIntent() {
- return this.intent;
- }
- getMemberType( builder, name ) {
- return this.node.getMemberType( builder, name );
- }
- getElementType( builder ) {
- return this.node.getElementType( builder );
- }
- getNodeType( builder ) {
- return this.node.getNodeType( builder );
- }
- getArrayCount( builder ) {
- return this.node.getArrayCount( builder );
- }
- isAssign( builder ) {
- const data = builder.getDataFromNode( this );
- return data.assign;
- }
- build( ...params ) {
- const builder = params[ 0 ];
- if ( this._hasStack( builder ) === false && builder.buildStage === 'setup' ) {
- if ( builder.context.nodeLoop || builder.context.nodeBlock ) {
- let addBefore = false;
- if ( this.node.isShaderCallNodeInternal && this.node.shaderNode.getLayout() === null ) {
- if ( builder.fnCall && builder.fnCall.shaderNode ) {
- const shaderNodeData = builder.getDataFromNode( this.node.shaderNode );
- if ( shaderNodeData.hasLoop ) {
- const data = builder.getDataFromNode( this );
- data.forceDeclaration = true;
- addBefore = true;
- }
- }
- }
- const baseStack = builder.getBaseStack();
- if ( addBefore ) {
- baseStack.addToStackBefore( this );
- } else {
- baseStack.addToStack( this );
- }
- }
- }
- if ( this.isIntent( builder ) ) {
- if ( this.isAssign( builder ) !== true ) {
- return this.node.build( ...params );
- }
- }
- return super.build( ...params );
- }
- generate( builder ) {
- const { node, name, readOnly } = this;
- const { renderer } = builder;
- const isWebGPUBackend = renderer.backend.isWebGPUBackend === true;
- let isDeterministic = false;
- let shouldTreatAsReadOnly = false;
- if ( readOnly ) {
- isDeterministic = builder.isDeterministic( node );
- shouldTreatAsReadOnly = isWebGPUBackend ? readOnly : isDeterministic;
- }
- const nodeType = this.getNodeType( builder );
- if ( nodeType == 'void' ) {
- if ( this.isIntent( builder ) !== true ) {
- error( 'TSL: ".toVar()" can not be used with void type.' );
- }
- const snippet = node.build( builder );
- return snippet;
- }
- const vectorType = builder.getVectorType( nodeType );
- const snippet = node.build( builder, vectorType );
- const nodeVar = builder.getVarFromNode( this, name, vectorType, undefined, shouldTreatAsReadOnly );
- const propertyName = builder.getPropertyName( nodeVar );
- let declarationPrefix = propertyName;
- if ( shouldTreatAsReadOnly ) {
- if ( isWebGPUBackend ) {
- declarationPrefix = isDeterministic
- ? `const ${ propertyName }`
- : `let ${ propertyName }`;
- } else {
- const count = node.getArrayCount( builder );
- declarationPrefix = `const ${ builder.getVar( nodeVar.type, propertyName, count ) }`;
- }
- }
- builder.addLineFlowCode( `${ declarationPrefix } = ${ snippet }`, this );
- return propertyName;
- }
- _hasStack( builder ) {
- const nodeData = builder.getDataFromNode( this );
- return nodeData.stack !== undefined;
- }
- }
- /**
- * TSL function for creating a var node.
- *
- * @tsl
- * @function
- * @param {Node} node - The node for which a variable should be created.
- * @param {?string} name - The name of the variable in the shader.
- * @returns {VarNode}
- */
- const createVar = /*@__PURE__*/ nodeProxy( VarNode );
- /**
- * TSL function for creating a var node.
- *
- * @tsl
- * @function
- * @param {Node} node - The node for which a variable should be created.
- * @param {?string} name - The name of the variable in the shader.
- * @returns {VarNode}
- */
- const Var = ( node, name = null ) => createVar( node, name ).toStack();
- /**
- * TSL function for creating a const node.
- *
- * @tsl
- * @function
- * @param {Node} node - The node for which a constant should be created.
- * @param {?string} name - The name of the constant in the shader.
- * @returns {VarNode}
- */
- const Const = ( node, name = null ) => createVar( node, name, true ).toStack();
- //
- //
- /**
- * TSL function for creating a var intent node.
- *
- * @tsl
- * @function
- * @param {Node} node - The node for which a variable should be created.
- * @param {?string} name - The name of the variable in the shader.
- * @returns {VarNode}
- */
- const VarIntent = ( node ) => {
- return createVar( node ).setIntent( true ).toStack();
- };
- // Method chaining
- addMethodChaining( 'toVar', Var );
- addMethodChaining( 'toConst', Const );
- addMethodChaining( 'toVarIntent', VarIntent );
- /**
- * This node is used to build a sub-build in the node system.
- *
- * @augments Node
- * @param {Node} node - The node to be built in the sub-build.
- * @param {string} name - The name of the sub-build.
- * @param {?string} [nodeType=null] - The type of the node, if known.
- */
- class SubBuildNode extends Node {
- static get type() {
- return 'SubBuild';
- }
- constructor( node, name, nodeType = null ) {
- super( nodeType );
- /**
- * The node to be built in the sub-build.
- *
- * @type {Node}
- */
- this.node = node;
- /**
- * The name of the sub-build.
- *
- * @type {string}
- */
- this.name = name;
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isSubBuildNode = true;
- }
- getNodeType( builder ) {
- if ( this.nodeType !== null ) return this.nodeType;
- builder.addSubBuild( this.name );
- const nodeType = this.node.getNodeType( builder );
- builder.removeSubBuild();
- return nodeType;
- }
- build( builder, ...params ) {
- builder.addSubBuild( this.name );
- const data = this.node.build( builder, ...params );
- builder.removeSubBuild();
- return data;
- }
- }
- /**
- * Creates a new sub-build node.
- *
- * @tsl
- * @function
- * @param {Node} node - The node to be built in the sub-build.
- * @param {string} name - The name of the sub-build.
- * @param {?string} [type=null] - The type of the node, if known.
- * @returns {Node} A node object wrapping the SubBuildNode instance.
- */
- const subBuild = ( node, name, type = null ) => nodeObject( new SubBuildNode( nodeObject( node ), name, type ) );
- /**
- * Class for representing shader varyings as nodes. Varyings are create from
- * existing nodes like the following:
- *
- * ```js
- * const positionLocal = positionGeometry.toVarying( 'vPositionLocal' );
- * ```
- *
- * @augments Node
- */
- class VaryingNode extends Node {
- static get type() {
- return 'VaryingNode';
- }
- /**
- * Constructs a new varying node.
- *
- * @param {Node} node - The node for which a varying should be created.
- * @param {?string} name - The name of the varying in the shader.
- */
- constructor( node, name = null ) {
- super();
- /**
- * The node for which a varying should be created.
- *
- * @type {Node}
- */
- this.node = node;
- /**
- * The name of the varying in the shader. If no name is defined,
- * the node system auto-generates one.
- *
- * @type {?string}
- * @default null
- */
- this.name = name;
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isVaryingNode = true;
- /**
- * The interpolation type of the varying data.
- *
- * @type {?string}
- * @default null
- */
- this.interpolationType = null;
- /**
- * The interpolation sampling type of varying data.
- *
- * @type {?string}
- * @default null
- */
- this.interpolationSampling = null;
- /**
- * This flag is used for global cache.
- *
- * @type {boolean}
- * @default true
- */
- this.global = true;
- }
- /**
- * Defines the interpolation type of the varying.
- *
- * @param {string} type - The interpolation type.
- * @param {?string} sampling - The interpolation sampling type
- * @return {VaryingNode} A reference to this node.
- */
- setInterpolation( type, sampling = null ) {
- this.interpolationType = type;
- this.interpolationSampling = sampling;
- return this;
- }
- getHash( builder ) {
- return this.name || super.getHash( builder );
- }
- getNodeType( builder ) {
- // VaryingNode is auto type
- return this.node.getNodeType( builder );
- }
- /**
- * This method performs the setup of a varying node with the current node builder.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {NodeVarying} The node varying from the node builder.
- */
- setupVarying( builder ) {
- const properties = builder.getNodeProperties( this );
- let varying = properties.varying;
- if ( varying === undefined ) {
- const name = this.name;
- const type = this.getNodeType( builder );
- const interpolationType = this.interpolationType;
- const interpolationSampling = this.interpolationSampling;
- properties.varying = varying = builder.getVaryingFromNode( this, name, type, interpolationType, interpolationSampling );
- properties.node = subBuild( this.node, 'VERTEX' );
- }
- // this property can be used to check if the varying can be optimized for a variable
- varying.needsInterpolation || ( varying.needsInterpolation = ( builder.shaderStage === 'fragment' ) );
- return varying;
- }
- setup( builder ) {
- this.setupVarying( builder );
- builder.flowNodeFromShaderStage( NodeShaderStage.VERTEX, this.node );
- }
- analyze( builder ) {
- this.setupVarying( builder );
- builder.flowNodeFromShaderStage( NodeShaderStage.VERTEX, this.node );
- }
- generate( builder ) {
- const propertyKey = builder.getSubBuildProperty( 'property', builder.currentStack );
- const properties = builder.getNodeProperties( this );
- const varying = this.setupVarying( builder );
- if ( properties[ propertyKey ] === undefined ) {
- const type = this.getNodeType( builder );
- const propertyName = builder.getPropertyName( varying, NodeShaderStage.VERTEX );
- // force node run in vertex stage
- builder.flowNodeFromShaderStage( NodeShaderStage.VERTEX, properties.node, type, propertyName );
- properties[ propertyKey ] = propertyName;
- }
- return builder.getPropertyName( varying );
- }
- }
- /**
- * TSL function for creating a varying node.
- *
- * @tsl
- * @function
- * @param {Node} node - The node for which a varying should be created.
- * @param {?string} name - The name of the varying in the shader.
- * @returns {VaryingNode}
- */
- const varying = /*@__PURE__*/ nodeProxy( VaryingNode ).setParameterLength( 1, 2 );
- /**
- * Computes a node in the vertex stage.
- *
- * @tsl
- * @function
- * @param {Node} node - The node which should be executed in the vertex stage.
- * @returns {VaryingNode}
- */
- const vertexStage = ( node ) => varying( node );
- addMethodChaining( 'toVarying', varying );
- addMethodChaining( 'toVertexStage', vertexStage );
- // Deprecated
- addMethodChaining( 'varying', ( ...params ) => { // @deprecated, r173
- warn( 'TSL: .varying() has been renamed to .toVarying().' );
- return varying( ...params );
- } );
- addMethodChaining( 'vertexStage', ( ...params ) => { // @deprecated, r173
- warn( 'TSL: .vertexStage() has been renamed to .toVertexStage().' );
- return varying( ...params );
- } );
- /**
- * Converts the given color value from sRGB to linear-sRGB color space.
- *
- * @tsl
- * @function
- * @param {Node<vec3>} color - The sRGB color.
- * @return {Node<vec3>} The linear-sRGB color.
- */
- const sRGBTransferEOTF = /*@__PURE__*/ Fn( ( [ color ] ) => {
- const a = color.mul( 0.9478672986 ).add( 0.0521327014 ).pow( 2.4 );
- const b = color.mul( 0.0773993808 );
- const factor = color.lessThanEqual( 0.04045 );
- const rgbResult = mix( a, b, factor );
- return rgbResult;
- } ).setLayout( {
- name: 'sRGBTransferEOTF',
- type: 'vec3',
- inputs: [
- { name: 'color', type: 'vec3' }
- ]
- } );
- /**
- * Converts the given color value from linear-sRGB to sRGB color space.
- *
- * @tsl
- * @function
- * @param {Node<vec3>} color - The linear-sRGB color.
- * @return {Node<vec3>} The sRGB color.
- */
- const sRGBTransferOETF = /*@__PURE__*/ Fn( ( [ color ] ) => {
- const a = color.pow( 0.41666 ).mul( 1.055 ).sub( 0.055 );
- const b = color.mul( 12.92 );
- const factor = color.lessThanEqual( 0.0031308 );
- const rgbResult = mix( a, b, factor );
- return rgbResult;
- } ).setLayout( {
- name: 'sRGBTransferOETF',
- type: 'vec3',
- inputs: [
- { name: 'color', type: 'vec3' }
- ]
- } );
- const WORKING_COLOR_SPACE = 'WorkingColorSpace';
- const OUTPUT_COLOR_SPACE = 'OutputColorSpace';
- /**
- * This node represents a color space conversion. Meaning it converts
- * a color value from a source to a target color space.
- *
- * @augments TempNode
- */
- class ColorSpaceNode extends TempNode {
- static get type() {
- return 'ColorSpaceNode';
- }
- /**
- * Constructs a new color space node.
- *
- * @param {Node} colorNode - Represents the color to convert.
- * @param {string} source - The source color space.
- * @param {string} target - The target color space.
- */
- constructor( colorNode, source, target ) {
- super( 'vec4' );
- /**
- * Represents the color to convert.
- *
- * @type {Node}
- */
- this.colorNode = colorNode;
- /**
- * The source color space.
- *
- * @type {string}
- */
- this.source = source;
- /**
- * The target color space.
- *
- * @type {string}
- */
- this.target = target;
- }
- /**
- * This method resolves the constants `WORKING_COLOR_SPACE` and
- * `OUTPUT_COLOR_SPACE` based on the current configuration of the
- * color management and renderer.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @param {string} colorSpace - The color space to resolve.
- * @return {string} The resolved color space.
- */
- resolveColorSpace( builder, colorSpace ) {
- if ( colorSpace === WORKING_COLOR_SPACE ) {
- return ColorManagement.workingColorSpace;
- } else if ( colorSpace === OUTPUT_COLOR_SPACE ) {
- return builder.context.outputColorSpace || builder.renderer.outputColorSpace;
- }
- return colorSpace;
- }
- setup( builder ) {
- const { colorNode } = this;
- const source = this.resolveColorSpace( builder, this.source );
- const target = this.resolveColorSpace( builder, this.target );
- let outputNode = colorNode;
- if ( ColorManagement.enabled === false || source === target || ! source || ! target ) {
- return outputNode;
- }
- if ( ColorManagement.getTransfer( source ) === SRGBTransfer ) {
- outputNode = vec4( sRGBTransferEOTF( outputNode.rgb ), outputNode.a );
- }
- if ( ColorManagement.getPrimaries( source ) !== ColorManagement.getPrimaries( target ) ) {
- outputNode = vec4(
- mat3( ColorManagement._getMatrix( new Matrix3(), source, target ) ).mul( outputNode.rgb ),
- outputNode.a
- );
- }
- if ( ColorManagement.getTransfer( target ) === SRGBTransfer ) {
- outputNode = vec4( sRGBTransferOETF( outputNode.rgb ), outputNode.a );
- }
- return outputNode;
- }
- }
- /**
- * TSL function for converting a given color node from the current working color space to the given color space.
- *
- * @tsl
- * @function
- * @param {Node} node - Represents the node to convert.
- * @param {string} targetColorSpace - The target color space.
- * @returns {ColorSpaceNode}
- */
- const workingToColorSpace = ( node, targetColorSpace ) => nodeObject( new ColorSpaceNode( nodeObject( node ), WORKING_COLOR_SPACE, targetColorSpace ) );
- /**
- * TSL function for converting a given color node from the given color space to the current working color space.
- *
- * @tsl
- * @function
- * @param {Node} node - Represents the node to convert.
- * @param {string} sourceColorSpace - The source color space.
- * @returns {ColorSpaceNode}
- */
- const colorSpaceToWorking = ( node, sourceColorSpace ) => nodeObject( new ColorSpaceNode( nodeObject( node ), sourceColorSpace, WORKING_COLOR_SPACE ) );
- /**
- * TSL function for converting a given color node from one color space to another one.
- *
- * @tsl
- * @function
- * @param {Node} node - Represents the node to convert.
- * @param {string} sourceColorSpace - The source color space.
- * @param {string} targetColorSpace - The target color space.
- * @returns {ColorSpaceNode}
- */
- const convertColorSpace = ( node, sourceColorSpace, targetColorSpace ) => nodeObject( new ColorSpaceNode( nodeObject( node ), sourceColorSpace, targetColorSpace ) );
- addMethodChaining( 'workingToColorSpace', workingToColorSpace );
- addMethodChaining( 'colorSpaceToWorking', colorSpaceToWorking );
- // TODO: Avoid duplicated code and use only ReferenceBaseNode or ReferenceNode
- /**
- * This class is only relevant if the referenced property is array-like.
- * In this case, `ReferenceElementNode` allows to refer to a specific
- * element inside the data structure via an index.
- *
- * @augments ArrayElementNode
- */
- let ReferenceElementNode$1 = class ReferenceElementNode extends ArrayElementNode {
- static get type() {
- return 'ReferenceElementNode';
- }
- /**
- * Constructs a new reference element node.
- *
- * @param {ReferenceBaseNode} referenceNode - The reference node.
- * @param {Node} indexNode - The index node that defines the element access.
- */
- constructor( referenceNode, indexNode ) {
- super( referenceNode, indexNode );
- /**
- * Similar to {@link ReferenceBaseNode#reference}, an additional
- * property references to the current node.
- *
- * @type {?ReferenceBaseNode}
- * @default null
- */
- this.referenceNode = referenceNode;
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isReferenceElementNode = true;
- }
- /**
- * This method is overwritten since the node type is inferred from
- * the uniform type of the reference node.
- *
- * @return {string} The node type.
- */
- getNodeType() {
- return this.referenceNode.uniformType;
- }
- generate( builder ) {
- const snippet = super.generate( builder );
- const arrayType = this.referenceNode.getNodeType();
- const elementType = this.getNodeType();
- return builder.format( snippet, arrayType, elementType );
- }
- };
- /**
- * Base class for nodes which establishes a reference to a property of another object.
- * In this way, the value of the node is automatically linked to the value of
- * referenced object. Reference nodes internally represent the linked value
- * as a uniform.
- *
- * @augments Node
- */
- class ReferenceBaseNode extends Node {
- static get type() {
- return 'ReferenceBaseNode';
- }
- /**
- * Constructs a new reference base node.
- *
- * @param {string} property - The name of the property the node refers to.
- * @param {string} uniformType - The uniform type that should be used to represent the property value.
- * @param {?Object} [object=null] - The object the property belongs to.
- * @param {?number} [count=null] - When the linked property is an array-like, this parameter defines its length.
- */
- constructor( property, uniformType, object = null, count = null ) {
- super();
- /**
- * The name of the property the node refers to.
- *
- * @type {string}
- */
- this.property = property;
- /**
- * The uniform type that should be used to represent the property value.
- *
- * @type {string}
- */
- this.uniformType = uniformType;
- /**
- * The object the property belongs to.
- *
- * @type {?Object}
- * @default null
- */
- this.object = object;
- /**
- * When the linked property is an array, this parameter defines its length.
- *
- * @type {?number}
- * @default null
- */
- this.count = count;
- /**
- * The property name might have dots so nested properties can be referred.
- * The hierarchy of the names is stored inside this array.
- *
- * @type {Array<string>}
- */
- this.properties = property.split( '.' );
- /**
- * Points to the current referred object. This property exists next to {@link ReferenceNode#object}
- * since the final reference might be updated from calling code.
- *
- * @type {?Object}
- * @default null
- */
- this.reference = object;
- /**
- * The uniform node that holds the value of the reference node.
- *
- * @type {UniformNode}
- * @default null
- */
- this.node = null;
- /**
- * The uniform group of the internal uniform.
- *
- * @type {UniformGroupNode}
- * @default null
- */
- this.group = null;
- /**
- * Overwritten since reference nodes are updated per object.
- *
- * @type {string}
- * @default 'object'
- */
- this.updateType = NodeUpdateType.OBJECT;
- }
- /**
- * Sets the uniform group for this reference node.
- *
- * @param {UniformGroupNode} group - The uniform group to set.
- * @return {ReferenceBaseNode} A reference to this node.
- */
- setGroup( group ) {
- this.group = group;
- return this;
- }
- /**
- * When the referred property is array-like, this method can be used
- * to access elements via an index node.
- *
- * @param {IndexNode} indexNode - indexNode.
- * @return {ReferenceElementNode} A reference to an element.
- */
- element( indexNode ) {
- return nodeObject( new ReferenceElementNode$1( this, nodeObject( indexNode ) ) );
- }
- /**
- * Sets the node type which automatically defines the internal
- * uniform type.
- *
- * @param {string} uniformType - The type to set.
- */
- setNodeType( uniformType ) {
- const node = uniform( null, uniformType );
- if ( this.group !== null ) {
- node.setGroup( this.group );
- }
- this.node = node;
- }
- /**
- * This method is overwritten since the node type is inferred from
- * the type of the reference node.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {string} The node type.
- */
- getNodeType( builder ) {
- if ( this.node === null ) {
- this.updateReference( builder );
- this.updateValue();
- }
- return this.node.getNodeType( builder );
- }
- /**
- * Returns the property value from the given referred object.
- *
- * @param {Object} [object=this.reference] - The object to retrieve the property value from.
- * @return {any} The value.
- */
- getValueFromReference( object = this.reference ) {
- const { properties } = this;
- let value = object[ properties[ 0 ] ];
- for ( let i = 1; i < properties.length; i ++ ) {
- value = value[ properties[ i ] ];
- }
- return value;
- }
- /**
- * Allows to update the reference based on the given state. The state is only
- * evaluated {@link ReferenceBaseNode#object} is not set.
- *
- * @param {(NodeFrame|NodeBuilder)} state - The current state.
- * @return {Object} The updated reference.
- */
- updateReference( state ) {
- this.reference = this.object !== null ? this.object : state.object;
- return this.reference;
- }
- /**
- * The output of the reference node is the internal uniform node.
- *
- * @return {UniformNode} The output node.
- */
- setup() {
- this.updateValue();
- return this.node;
- }
- /**
- * Overwritten to update the internal uniform value.
- *
- * @param {NodeFrame} frame - A reference to the current node frame.
- */
- update( /*frame*/ ) {
- this.updateValue();
- }
- /**
- * Retrieves the value from the referred object property and uses it
- * to updated the internal uniform.
- */
- updateValue() {
- if ( this.node === null ) this.setNodeType( this.uniformType );
- const value = this.getValueFromReference();
- if ( Array.isArray( value ) ) {
- this.node.array = value;
- } else {
- this.node.value = value;
- }
- }
- }
- /**
- * TSL function for creating a reference base node.
- *
- * @tsl
- * @function
- * @param {string} name - The name of the property the node refers to.
- * @param {string} type - The uniform type that should be used to represent the property value.
- * @param {Object} object - The object the property belongs to.
- * @returns {ReferenceBaseNode}
- */
- const reference$1 = ( name, type, object ) => nodeObject( new ReferenceBaseNode( name, type, object ) );
- /**
- * This node is a special type of reference node which is intended
- * for linking renderer properties with node values.
- * ```js
- * const exposureNode = rendererReference( 'toneMappingExposure', 'float', renderer );
- * ```
- * When changing `renderer.toneMappingExposure`, the node value of `exposureNode` will
- * automatically be updated.
- *
- * @augments ReferenceBaseNode
- */
- class RendererReferenceNode extends ReferenceBaseNode {
- static get type() {
- return 'RendererReferenceNode';
- }
- /**
- * Constructs a new renderer reference node.
- *
- * @param {string} property - The name of the property the node refers to.
- * @param {string} inputType - The uniform type that should be used to represent the property value.
- * @param {?Renderer} [renderer=null] - The renderer the property belongs to. When no renderer is set,
- * the node refers to the renderer of the current state.
- */
- constructor( property, inputType, renderer = null ) {
- super( property, inputType, renderer );
- /**
- * The renderer the property belongs to. When no renderer is set,
- * the node refers to the renderer of the current state.
- *
- * @type {?Renderer}
- * @default null
- */
- this.renderer = renderer;
- this.setGroup( renderGroup );
- }
- /**
- * Updates the reference based on the given state. The state is only evaluated
- * {@link RendererReferenceNode#renderer} is not set.
- *
- * @param {(NodeFrame|NodeBuilder)} state - The current state.
- * @return {Object} The updated reference.
- */
- updateReference( state ) {
- this.reference = this.renderer !== null ? this.renderer : state.renderer;
- return this.reference;
- }
- }
- /**
- * TSL function for creating a renderer reference node.
- *
- * @tsl
- * @function
- * @param {string} name - The name of the property the node refers to.
- * @param {string} type - The uniform type that should be used to represent the property value.
- * @param {?Renderer} [renderer=null] - The renderer the property belongs to. When no renderer is set,
- * the node refers to the renderer of the current state.
- * @returns {RendererReferenceNode}
- */
- const rendererReference = ( name, type, renderer = null ) => nodeObject( new RendererReferenceNode( name, type, renderer ) );
- /**
- * This node represents a tone mapping operation.
- *
- * @augments TempNode
- */
- class ToneMappingNode extends TempNode {
- static get type() {
- return 'ToneMappingNode';
- }
- /**
- * Constructs a new tone mapping node.
- *
- * @param {number} toneMapping - The tone mapping type.
- * @param {Node} exposureNode - The tone mapping exposure.
- * @param {Node} [colorNode=null] - The color node to process.
- */
- constructor( toneMapping, exposureNode = toneMappingExposure, colorNode = null ) {
- super( 'vec3' );
- /**
- * The tone mapping type.
- *
- * @private
- * @type {number}
- */
- this._toneMapping = toneMapping;
- /**
- * The tone mapping exposure.
- *
- * @type {Node}
- * @default null
- */
- this.exposureNode = exposureNode;
- /**
- * Represents the color to process.
- *
- * @type {?Node}
- * @default null
- */
- this.colorNode = colorNode;
- }
- /**
- * Overwrites the default `customCacheKey()` implementation by including the tone
- * mapping type into the cache key.
- *
- * @return {number} The hash.
- */
- customCacheKey() {
- return hash$1( this._toneMapping );
- }
- /**
- * Sets the tone mapping type.
- *
- * @param {number} value - The tone mapping type.
- * @return {ToneMappingNode} A reference to this node.
- */
- setToneMapping( value ) {
- this._toneMapping = value;
- return this;
- }
- /**
- * Gets the tone mapping type.
- *
- * @returns {number} The tone mapping type.
- */
- getToneMapping() {
- return this._toneMapping;
- }
- setup( builder ) {
- const colorNode = this.colorNode || builder.context.color;
- const toneMapping = this._toneMapping;
- if ( toneMapping === NoToneMapping ) return colorNode;
- let outputNode = null;
- const toneMappingFn = builder.renderer.library.getToneMappingFunction( toneMapping );
- if ( toneMappingFn !== null ) {
- outputNode = vec4( toneMappingFn( colorNode.rgb, this.exposureNode ), colorNode.a );
- } else {
- error( 'ToneMappingNode: Unsupported Tone Mapping configuration.', toneMapping );
- outputNode = colorNode;
- }
- return outputNode;
- }
- }
- /**
- * TSL function for creating a tone mapping node.
- *
- * @tsl
- * @function
- * @param {number} mapping - The tone mapping type.
- * @param {Node<float> | number} exposure - The tone mapping exposure.
- * @param {Node<vec3> | Color} color - The color node to process.
- * @returns {ToneMappingNode<vec3>}
- */
- const toneMapping = ( mapping, exposure, color ) => nodeObject( new ToneMappingNode( mapping, nodeObject( exposure ), nodeObject( color ) ) );
- /**
- * TSL object that represents the global tone mapping exposure of the renderer.
- *
- * @tsl
- * @type {RendererReferenceNode<vec3>}
- */
- const toneMappingExposure = /*@__PURE__*/ rendererReference( 'toneMappingExposure', 'float' );
- addMethodChaining( 'toneMapping', ( color, mapping, exposure ) => toneMapping( mapping, exposure, color ) );
- /**
- * Internal buffer attribute library.
- *
- * @private
- * @type {WeakMap<TypedArray, InterleavedBuffer>}
- */
- const _bufferLib = new WeakMap();
- /**
- * Internal method for retrieving or creating interleaved buffers.
- *
- * @private
- * @param {TypedArray} value - The attribute data.
- * @param {number} itemSize - The attribute item size.
- * @returns {InterleavedBuffer} The interleaved buffer.
- */
- function _getBufferAttribute( value, itemSize ) {
- let buffer = _bufferLib.get( value );
- if ( buffer === undefined ) {
- buffer = new InterleavedBuffer( value, itemSize );
- _bufferLib.set( value, buffer );
- }
- return buffer;
- }
- /**
- * In earlier `three.js` versions it was only possible to define attribute data
- * on geometry level. With `BufferAttributeNode`, it is also possible to do this
- * on the node level.
- * ```js
- * const geometry = new THREE.PlaneGeometry();
- * const positionAttribute = geometry.getAttribute( 'position' );
- *
- * const colors = [];
- * for ( let i = 0; i < position.count; i ++ ) {
- * colors.push( 1, 0, 0 );
- * }
- *
- * material.colorNode = bufferAttribute( new THREE.Float32BufferAttribute( colors, 3 ) );
- * ```
- * This new approach is especially interesting when geometry data are generated via
- * compute shaders. The below line converts a storage buffer into an attribute node.
- * ```js
- * material.positionNode = positionBuffer.toAttribute();
- * ```
- * @augments InputNode
- */
- class BufferAttributeNode extends InputNode {
- static get type() {
- return 'BufferAttributeNode';
- }
- /**
- * Constructs a new buffer attribute node.
- *
- * @param {BufferAttribute|InterleavedBuffer|TypedArray} value - The attribute data.
- * @param {?string} [bufferType=null] - The buffer type (e.g. `'vec3'`).
- * @param {number} [bufferStride=0] - The buffer stride.
- * @param {number} [bufferOffset=0] - The buffer offset.
- */
- constructor( value, bufferType = null, bufferStride = 0, bufferOffset = 0 ) {
- super( value, bufferType );
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isBufferNode = true;
- /**
- * The buffer type (e.g. `'vec3'`).
- *
- * @type {?string}
- * @default null
- */
- this.bufferType = bufferType;
- /**
- * The buffer stride.
- *
- * @type {number}
- * @default 0
- */
- this.bufferStride = bufferStride;
- /**
- * The buffer offset.
- *
- * @type {number}
- * @default 0
- */
- this.bufferOffset = bufferOffset;
- /**
- * The usage property. Set this to `THREE.DynamicDrawUsage` via `.setUsage()`,
- * if you are planning to update the attribute data per frame.
- *
- * @type {number}
- * @default StaticDrawUsage
- */
- this.usage = StaticDrawUsage;
- /**
- * Whether the attribute is instanced or not.
- *
- * @type {boolean}
- * @default false
- */
- this.instanced = false;
- /**
- * A reference to the buffer attribute.
- *
- * @type {?BufferAttribute}
- * @default null
- */
- this.attribute = null;
- /**
- * `BufferAttributeNode` sets this property to `true` by default.
- *
- * @type {boolean}
- * @default true
- */
- this.global = true;
- if ( value && value.isBufferAttribute === true && value.itemSize <= 4 ) {
- this.attribute = value;
- this.usage = value.usage;
- this.instanced = value.isInstancedBufferAttribute;
- }
- }
- /**
- * This method is overwritten since the attribute data might be shared
- * and thus the hash should be shared as well.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {string} The hash.
- */
- getHash( builder ) {
- if ( this.bufferStride === 0 && this.bufferOffset === 0 ) {
- let bufferData = builder.globalCache.getData( this.value );
- if ( bufferData === undefined ) {
- bufferData = {
- node: this
- };
- builder.globalCache.setData( this.value, bufferData );
- }
- return bufferData.node.uuid;
- }
- return this.uuid;
- }
- /**
- * This method is overwritten since the node type is inferred from
- * the buffer attribute.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {string} The node type.
- */
- getNodeType( builder ) {
- if ( this.bufferType === null ) {
- this.bufferType = builder.getTypeFromAttribute( this.attribute );
- }
- return this.bufferType;
- }
- /**
- * Depending on which value was passed to the node, `setup()` behaves
- * differently. If no instance of `BufferAttribute` was passed, the method
- * creates an internal attribute and configures it respectively.
- *
- * @param {NodeBuilder} builder - The current node builder.
- */
- setup( builder ) {
- if ( this.attribute !== null ) return;
- //
- const type = this.getNodeType( builder );
- const itemSize = builder.getTypeLength( type );
- const value = this.value;
- const stride = this.bufferStride || itemSize;
- const offset = this.bufferOffset;
- let buffer;
- if ( value.isInterleavedBuffer === true ) {
- buffer = value;
- } else if ( value.isBufferAttribute === true ) {
- buffer = _getBufferAttribute( value.array, stride );
- } else {
- buffer = _getBufferAttribute( value, stride );
- }
- const bufferAttribute = new InterleavedBufferAttribute( buffer, itemSize, offset );
- buffer.setUsage( this.usage );
- this.attribute = bufferAttribute;
- this.attribute.isInstancedBufferAttribute = this.instanced; // @TODO: Add a possible: InstancedInterleavedBufferAttribute
- }
- /**
- * Generates the code snippet of the buffer attribute node.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {string} The generated code snippet.
- */
- generate( builder ) {
- const nodeType = this.getNodeType( builder );
- const nodeAttribute = builder.getBufferAttributeFromNode( this, nodeType );
- const propertyName = builder.getPropertyName( nodeAttribute );
- let output = null;
- if ( builder.shaderStage === 'vertex' || builder.shaderStage === 'compute' ) {
- this.name = propertyName;
- output = propertyName;
- } else {
- const nodeVarying = varying( this );
- output = nodeVarying.build( builder, nodeType );
- }
- return output;
- }
- /**
- * Overwrites the default implementation to return a fixed value `'bufferAttribute'`.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {string} The input type.
- */
- getInputType( /*builder*/ ) {
- return 'bufferAttribute';
- }
- /**
- * Sets the `usage` property to the given value.
- *
- * @param {number} value - The usage to set.
- * @return {BufferAttributeNode} A reference to this node.
- */
- setUsage( value ) {
- this.usage = value;
- if ( this.attribute && this.attribute.isBufferAttribute === true ) {
- this.attribute.usage = value;
- }
- return this;
- }
- /**
- * Sets the `instanced` property to the given value.
- *
- * @param {boolean} value - The value to set.
- * @return {BufferAttributeNode} A reference to this node.
- */
- setInstanced( value ) {
- this.instanced = value;
- return this;
- }
- }
- /**
- * Internal method for creating buffer attribute nodes.
- *
- * @private
- * @param {BufferAttribute|InterleavedBuffer|TypedArray} array - The attribute data.
- * @param {?string} [type=null] - The buffer type (e.g. `'vec3'`).
- * @param {number} [stride=0] - The buffer stride.
- * @param {number} [offset=0] - The buffer offset.
- * @param {number} [usage=StaticDrawUsage] - The buffer usage.
- * @param {boolean} [instanced=false] - Whether the buffer is instanced.
- * @returns {BufferAttributeNode|Node} The buffer attribute node.
- */
- function createBufferAttribute( array, type = null, stride = 0, offset = 0, usage = StaticDrawUsage, instanced = false ) {
- if ( type === 'mat3' || ( type === null && array.itemSize === 9 ) ) {
- return mat3(
- new BufferAttributeNode( array, 'vec3', 9, 0 ).setUsage( usage ).setInstanced( instanced ),
- new BufferAttributeNode( array, 'vec3', 9, 3 ).setUsage( usage ).setInstanced( instanced ),
- new BufferAttributeNode( array, 'vec3', 9, 6 ).setUsage( usage ).setInstanced( instanced )
- );
- } else if ( type === 'mat4' || ( type === null && array.itemSize === 16 ) ) {
- return mat4(
- new BufferAttributeNode( array, 'vec4', 16, 0 ).setUsage( usage ).setInstanced( instanced ),
- new BufferAttributeNode( array, 'vec4', 16, 4 ).setUsage( usage ).setInstanced( instanced ),
- new BufferAttributeNode( array, 'vec4', 16, 8 ).setUsage( usage ).setInstanced( instanced ),
- new BufferAttributeNode( array, 'vec4', 16, 12 ).setUsage( usage ).setInstanced( instanced )
- );
- }
- return new BufferAttributeNode( array, type, stride, offset );
- }
- /**
- * TSL function for creating a buffer attribute node.
- *
- * @tsl
- * @function
- * @param {BufferAttribute|InterleavedBuffer|TypedArray} array - The attribute data.
- * @param {?string} [type=null] - The buffer type (e.g. `'vec3'`).
- * @param {number} [stride=0] - The buffer stride.
- * @param {number} [offset=0] - The buffer offset.
- * @returns {BufferAttributeNode|Node}
- */
- const bufferAttribute = ( array, type = null, stride = 0, offset = 0 ) => createBufferAttribute( array, type, stride, offset );
- /**
- * TSL function for creating a buffer attribute node but with dynamic draw usage.
- * Use this function if attribute data are updated per frame.
- *
- * @tsl
- * @function
- * @param {BufferAttribute|InterleavedBuffer|TypedArray} array - The attribute data.
- * @param {?string} [type=null] - The buffer type (e.g. `'vec3'`).
- * @param {number} [stride=0] - The buffer stride.
- * @param {number} [offset=0] - The buffer offset.
- * @returns {BufferAttributeNode|Node}
- */
- const dynamicBufferAttribute = ( array, type = null, stride = 0, offset = 0 ) => createBufferAttribute( array, type, stride, offset, DynamicDrawUsage );
- /**
- * TSL function for creating a buffer attribute node but with enabled instancing
- *
- * @tsl
- * @function
- * @param {BufferAttribute|InterleavedBuffer|TypedArray} array - The attribute data.
- * @param {?string} [type=null] - The buffer type (e.g. `'vec3'`).
- * @param {number} [stride=0] - The buffer stride.
- * @param {number} [offset=0] - The buffer offset.
- * @returns {BufferAttributeNode|Node}
- */
- const instancedBufferAttribute = ( array, type = null, stride = 0, offset = 0 ) => createBufferAttribute( array, type, stride, offset, StaticDrawUsage, true );
- /**
- * TSL function for creating a buffer attribute node but with dynamic draw usage and enabled instancing
- *
- * @tsl
- * @function
- * @param {BufferAttribute|InterleavedBuffer|TypedArray} array - The attribute data.
- * @param {?string} [type=null] - The buffer type (e.g. `'vec3'`).
- * @param {number} [stride=0] - The buffer stride.
- * @param {number} [offset=0] - The buffer offset.
- * @returns {BufferAttributeNode|Node}
- */
- const instancedDynamicBufferAttribute = ( array, type = null, stride = 0, offset = 0 ) => createBufferAttribute( array, type, stride, offset, DynamicDrawUsage, true );
- addMethodChaining( 'toAttribute', ( bufferNode ) => bufferAttribute( bufferNode.value ) );
- /**
- * TODO
- *
- * @augments Node
- */
- class ComputeNode extends Node {
- static get type() {
- return 'ComputeNode';
- }
- /**
- * Constructs a new compute node.
- *
- * @param {Node} computeNode - TODO
- * @param {Array<number>} workgroupSize - TODO.
- */
- constructor( computeNode, workgroupSize ) {
- super( 'void' );
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isComputeNode = true;
- /**
- * TODO
- *
- * @type {Node}
- */
- this.computeNode = computeNode;
- /**
- * TODO
- *
- * @type {Array<number>}
- * @default [ 64 ]
- */
- this.workgroupSize = workgroupSize;
- /**
- * TODO
- *
- * @type {number|Array<number>}
- */
- this.count = null;
- /**
- * TODO
- *
- * @type {number}
- */
- this.version = 1;
- /**
- * The name or label of the uniform.
- *
- * @type {string}
- * @default ''
- */
- this.name = '';
- /**
- * The `updateBeforeType` is set to `NodeUpdateType.OBJECT` since {@link ComputeNode#updateBefore}
- * is executed once per object by default.
- *
- * @type {string}
- * @default 'object'
- */
- this.updateBeforeType = NodeUpdateType.OBJECT;
- /**
- * TODO
- *
- * @type {?Function}
- */
- this.onInitFunction = null;
- }
- /**
- * TODO
- *
- * @param {number|Array<number>} count - Array with [ x, y, z ] values for dispatch or a single number for the count
- * @return {ComputeNode}
- */
- setCount( count ) {
- this.count = count;
- return this;
- }
- /**
- * TODO
- *
- * @return {number|Array<number>}
- */
- getCount() {
- return this.count;
- }
- /**
- * Executes the `dispose` event for this node.
- */
- dispose() {
- this.dispatchEvent( { type: 'dispose' } );
- }
- /**
- * Sets the {@link ComputeNode#name} property.
- *
- * @param {string} name - The name of the uniform.
- * @return {ComputeNode} A reference to this node.
- */
- setName( name ) {
- this.name = name;
- return this;
- }
- /**
- * Sets the {@link ComputeNode#name} property.
- *
- * @deprecated
- * @param {string} name - The name of the uniform.
- * @return {ComputeNode} A reference to this node.
- */
- label( name ) {
- warn( 'TSL: "label()" has been deprecated. Use "setName()" instead.' ); // @deprecated r179
- return this.setName( name );
- }
- /**
- * TODO
- *
- * @param {Function} callback - TODO.
- * @return {ComputeNode} A reference to this node.
- */
- onInit( callback ) {
- this.onInitFunction = callback;
- return this;
- }
- /**
- * The method execute the compute for this node.
- *
- * @param {NodeFrame} frame - A reference to the current node frame.
- */
- updateBefore( { renderer } ) {
- renderer.compute( this );
- }
- setup( builder ) {
- const result = this.computeNode.build( builder );
- if ( result ) {
- const properties = builder.getNodeProperties( this );
- properties.outputComputeNode = result.outputNode;
- result.outputNode = null;
- }
- return result;
- }
- generate( builder, output ) {
- const { shaderStage } = builder;
- if ( shaderStage === 'compute' ) {
- const snippet = this.computeNode.build( builder, 'void' );
- if ( snippet !== '' ) {
- builder.addLineFlowCode( snippet, this );
- }
- } else {
- const properties = builder.getNodeProperties( this );
- const outputComputeNode = properties.outputComputeNode;
- if ( outputComputeNode ) {
- return outputComputeNode.build( builder, output );
- }
- }
- }
- }
- /**
- * TSL function for creating a compute kernel node.
- *
- * @tsl
- * @function
- * @param {Node} node - TODO
- * @param {Array<number>} [workgroupSize=[64]] - TODO.
- * @returns {AtomicFunctionNode}
- */
- const computeKernel = ( node, workgroupSize = [ 64 ] ) => {
- if ( workgroupSize.length === 0 || workgroupSize.length > 3 ) {
- error( 'TSL: compute() workgroupSize must have 1, 2, or 3 elements' );
- }
- for ( let i = 0; i < workgroupSize.length; i ++ ) {
- const val = workgroupSize[ i ];
- if ( typeof val !== 'number' || val <= 0 || ! Number.isInteger( val ) ) {
- error( `TSL: compute() workgroupSize element at index [ ${ i } ] must be a positive integer` );
- }
- }
- // Implicit fill-up to [ x, y, z ] with 1s, just like WGSL treats @workgroup_size when fewer dimensions are specified
- while ( workgroupSize.length < 3 ) workgroupSize.push( 1 );
- //
- return nodeObject( new ComputeNode( nodeObject( node ), workgroupSize ) );
- };
- /**
- * TSL function for creating a compute node.
- *
- * @tsl
- * @function
- * @param {Node} node - TODO
- * @param {number|Array<number>} count - TODO.
- * @param {Array<number>} [workgroupSize=[64]] - TODO.
- * @returns {AtomicFunctionNode}
- */
- const compute = ( node, count, workgroupSize ) => computeKernel( node, workgroupSize ).setCount( count );
- addMethodChaining( 'compute', compute );
- addMethodChaining( 'computeKernel', computeKernel );
- /**
- * This node can be used as a cache management component for another node.
- * Caching is in general used by default in {@link NodeBuilder} but this node
- * allows the usage of a shared parent cache during the build process.
- *
- * @augments Node
- */
- class IsolateNode extends Node {
- static get type() {
- return 'IsolateNode';
- }
- /**
- * Constructs a new cache node.
- *
- * @param {Node} node - The node that should be cached.
- * @param {boolean} [parent=true] - Whether this node refers to a shared parent cache or not.
- */
- constructor( node, parent = true ) {
- super();
- /**
- * The node that should be cached.
- *
- * @type {Node}
- */
- this.node = node;
- /**
- * Whether this node refers to a shared parent cache or not.
- *
- * @type {boolean}
- * @default true
- */
- this.parent = parent;
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isIsolateNode = true;
- }
- getNodeType( builder ) {
- const previousCache = builder.getCache();
- const cache = builder.getCacheFromNode( this, this.parent );
- builder.setCache( cache );
- const nodeType = this.node.getNodeType( builder );
- builder.setCache( previousCache );
- return nodeType;
- }
- build( builder, ...params ) {
- const previousCache = builder.getCache();
- const cache = builder.getCacheFromNode( this, this.parent );
- builder.setCache( cache );
- const data = this.node.build( builder, ...params );
- builder.setCache( previousCache );
- return data;
- }
- setParent( parent ) {
- this.parent = parent;
- return this;
- }
- getParent() {
- return this.parent;
- }
- }
- /**
- * TSL function for creating a cache node.
- *
- * @tsl
- * @function
- * @param {Node} node - The node that should be cached.
- * @returns {IsolateNode}
- */
- const isolate = ( node ) => new IsolateNode( nodeObject( node ) );
- /**
- * TSL function for creating a cache node.
- *
- * @tsl
- * @function
- * @deprecated
- * @param {Node} node - The node that should be cached.
- * @param {boolean} [parent=true] - Whether this node refers to a shared parent cache or not.
- * @returns {IsolateNode}
- */
- function cache( node, parent = true ) {
- warn( 'TSL: "cache()" has been deprecated. Use "isolate()" instead.' ); // @deprecated r181
- return isolate( node ).setParent( parent );
- }
- addMethodChaining( 'cache', cache );
- addMethodChaining( 'isolate', isolate );
- /**
- * The class generates the code of a given node but returns another node in the output.
- * This can be used to call a method or node that does not return a value, i.e.
- * type `void` on an input where returning a value is required. Example:
- *
- * ```js
- * material.colorNode = myColor.bypass( runVoidFn() )
- *```
- *
- * @augments Node
- */
- class BypassNode extends Node {
- static get type() {
- return 'BypassNode';
- }
- /**
- * Constructs a new bypass node.
- *
- * @param {Node} outputNode - The output node.
- * @param {Node} callNode - The call node.
- */
- constructor( outputNode, callNode ) {
- super();
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isBypassNode = true;
- /**
- * The output node.
- *
- * @type {Node}
- */
- this.outputNode = outputNode;
- /**
- * The call node.
- *
- * @type {Node}
- */
- this.callNode = callNode;
- }
- getNodeType( builder ) {
- return this.outputNode.getNodeType( builder );
- }
- generate( builder ) {
- const snippet = this.callNode.build( builder, 'void' );
- if ( snippet !== '' ) {
- builder.addLineFlowCode( snippet, this );
- }
- return this.outputNode.build( builder );
- }
- }
- /**
- * TSL function for creating a bypass node.
- *
- * @tsl
- * @function
- * @param {Node} outputNode - The output node.
- * @param {Node} callNode - The call node.
- * @returns {BypassNode}
- */
- const bypass = /*@__PURE__*/ nodeProxy( BypassNode ).setParameterLength( 2 );
- addMethodChaining( 'bypass', bypass );
- /**
- * This node allows to remap a node value from one range into another. E.g a value of
- * `0.4` in the range `[ 0.3, 0.5 ]` should be remapped into the normalized range `[ 0, 1 ]`.
- * `RemapNode` takes care of that and converts the original value of `0.4` to `0.5`.
- *
- * @augments Node
- */
- class RemapNode extends Node {
- static get type() {
- return 'RemapNode';
- }
- /**
- * Constructs a new remap node.
- *
- * @param {Node} node - The node that should be remapped.
- * @param {Node} inLowNode - The source or current lower bound of the range.
- * @param {Node} inHighNode - The source or current upper bound of the range.
- * @param {Node} [outLowNode=float(0)] - The target lower bound of the range.
- * @param {Node} [outHighNode=float(1)] - The target upper bound of the range.
- */
- constructor( node, inLowNode, inHighNode, outLowNode = float( 0 ), outHighNode = float( 1 ) ) {
- super();
- /**
- * The node that should be remapped.
- *
- * @type {Node}
- */
- this.node = node;
- /**
- * The source or current lower bound of the range.
- *
- * @type {Node}
- */
- this.inLowNode = inLowNode;
- /**
- * The source or current upper bound of the range.
- *
- * @type {Node}
- */
- this.inHighNode = inHighNode;
- /**
- * The target lower bound of the range.
- *
- * @type {Node}
- * @default float(0)
- */
- this.outLowNode = outLowNode;
- /**
- * The target upper bound of the range.
- *
- * @type {Node}
- * @default float(1)
- */
- this.outHighNode = outHighNode;
- /**
- * Whether the node value should be clamped before
- * remapping it to the target range.
- *
- * @type {boolean}
- * @default true
- */
- this.doClamp = true;
- }
- setup() {
- const { node, inLowNode, inHighNode, outLowNode, outHighNode, doClamp } = this;
- let t = node.sub( inLowNode ).div( inHighNode.sub( inLowNode ) );
- if ( doClamp === true ) t = t.clamp();
- return t.mul( outHighNode.sub( outLowNode ) ).add( outLowNode );
- }
- }
- /**
- * TSL function for creating a remap node.
- *
- * @tsl
- * @function
- * @param {Node} node - The node that should be remapped.
- * @param {Node} inLowNode - The source or current lower bound of the range.
- * @param {Node} inHighNode - The source or current upper bound of the range.
- * @param {?Node} [outLowNode=float(0)] - The target lower bound of the range.
- * @param {?Node} [outHighNode=float(1)] - The target upper bound of the range.
- * @returns {RemapNode}
- */
- const remap = /*@__PURE__*/ nodeProxy( RemapNode, null, null, { doClamp: false } ).setParameterLength( 3, 5 );
- /**
- * TSL function for creating a remap node, but with enabled clamping.
- *
- * @tsl
- * @function
- * @param {Node} node - The node that should be remapped.
- * @param {Node} inLowNode - The source or current lower bound of the range.
- * @param {Node} inHighNode - The source or current upper bound of the range.
- * @param {?Node} [outLowNode=float(0)] - The target lower bound of the range.
- * @param {?Node} [outHighNode=float(1)] - The target upper bound of the range.
- * @returns {RemapNode}
- */
- const remapClamp = /*@__PURE__*/ nodeProxy( RemapNode ).setParameterLength( 3, 5 );
- addMethodChaining( 'remap', remap );
- addMethodChaining( 'remapClamp', remapClamp );
- /**
- * This class can be used to implement basic expressions in shader code.
- * Basic examples for that are `return`, `continue` or `discard` statements.
- *
- * @augments Node
- */
- class ExpressionNode extends Node {
- static get type() {
- return 'ExpressionNode';
- }
- /**
- * Constructs a new expression node.
- *
- * @param {string} [snippet=''] - The native code snippet.
- * @param {string} [nodeType='void'] - The node type.
- */
- constructor( snippet = '', nodeType = 'void' ) {
- super( nodeType );
- /**
- * The native code snippet.
- *
- * @type {string}
- * @default ''
- */
- this.snippet = snippet;
- }
- generate( builder, output ) {
- const type = this.getNodeType( builder );
- const snippet = this.snippet;
- if ( type === 'void' ) {
- builder.addLineFlowCode( snippet, this );
- } else {
- return builder.format( snippet, type, output );
- }
- }
- }
- /**
- * TSL function for creating an expression node.
- *
- * @tsl
- * @function
- * @param {string} [snippet] - The native code snippet.
- * @param {?string} [nodeType='void'] - The node type.
- * @returns {ExpressionNode}
- */
- const expression = /*@__PURE__*/ nodeProxy( ExpressionNode ).setParameterLength( 1, 2 );
- /**
- * Represents a `discard` shader operation in TSL.
- *
- * @tsl
- * @function
- * @param {?ConditionalNode} conditional - An optional conditional node. It allows to decide whether the discard should be executed or not.
- * @return {Node} The `discard` expression.
- */
- const Discard = ( conditional ) => ( conditional ? select( conditional, expression( 'discard' ) ) : expression( 'discard' ) ).toStack();
- /**
- * Represents a `return` shader operation in TSL.
- *
- * @tsl
- * @function
- * @return {ExpressionNode} The `return` expression.
- */
- const Return = () => expression( 'return' ).toStack();
- addMethodChaining( 'discard', Discard );
- /**
- * Normally, tone mapping and color conversion happens automatically
- * before outputting pixel too the default (screen) framebuffer. In certain
- * post processing setups this happens to late because certain effects
- * require e.g. sRGB input. For such scenarios, `RenderOutputNode` can be used
- * to apply tone mapping and color space conversion at an arbitrary point
- * in the effect chain.
- *
- * When applying tone mapping and color space conversion manually with this node,
- * you have to set {@link PostProcessing#outputColorTransform} to `false`.
- *
- * ```js
- * const postProcessing = new PostProcessing( renderer );
- * postProcessing.outputColorTransform = false;
- *
- * const scenePass = pass( scene, camera );
- * const outputPass = renderOutput( scenePass );
- *
- * postProcessing.outputNode = outputPass;
- * ```
- *
- * @augments TempNode
- */
- class RenderOutputNode extends TempNode {
- static get type() {
- return 'RenderOutputNode';
- }
- /**
- * Constructs a new render output node.
- *
- * @param {Node} colorNode - The color node to process.
- * @param {?number} toneMapping - The tone mapping type.
- * @param {?string} outputColorSpace - The output color space.
- */
- constructor( colorNode, toneMapping, outputColorSpace ) {
- super( 'vec4' );
- /**
- * The color node to process.
- *
- * @type {Node}
- */
- this.colorNode = colorNode;
- /**
- * The tone mapping type.
- *
- * @private
- * @type {?number}
- */
- this._toneMapping = toneMapping;
- /**
- * The output color space.
- *
- * @type {?string}
- */
- this.outputColorSpace = outputColorSpace;
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isRenderOutputNode = true;
- }
- /**
- * Sets the tone mapping type.
- *
- * @param {number} value - The tone mapping type.
- * @return {ToneMappingNode} A reference to this node.
- */
- setToneMapping( value ) {
- this._toneMapping = value;
- return this;
- }
- /**
- * Gets the tone mapping type.
- *
- * @returns {number} The tone mapping type.
- */
- getToneMapping() {
- return this._toneMapping;
- }
- setup( { context } ) {
- let outputNode = this.colorNode || context.color;
- // tone mapping
- const toneMapping = ( this._toneMapping !== null ? this._toneMapping : context.toneMapping ) || NoToneMapping;
- const outputColorSpace = ( this.outputColorSpace !== null ? this.outputColorSpace : context.outputColorSpace ) || NoColorSpace;
- if ( toneMapping !== NoToneMapping ) {
- outputNode = outputNode.toneMapping( toneMapping );
- }
- // working to output color space
- if ( outputColorSpace !== NoColorSpace && outputColorSpace !== ColorManagement.workingColorSpace ) {
- outputNode = outputNode.workingToColorSpace( outputColorSpace );
- }
- return outputNode;
- }
- }
- /**
- * TSL function for creating a posterize node.
- *
- * @tsl
- * @function
- * @param {Node} color - The color node to process.
- * @param {?number} [toneMapping=null] - The tone mapping type.
- * @param {?string} [outputColorSpace=null] - The output color space.
- * @returns {RenderOutputNode}
- */
- const renderOutput = ( color, toneMapping = null, outputColorSpace = null ) => nodeObject( new RenderOutputNode( nodeObject( color ), toneMapping, outputColorSpace ) );
- addMethodChaining( 'renderOutput', renderOutput );
- class DebugNode extends TempNode {
- static get type() {
- return 'DebugNode';
- }
- constructor( node, callback = null ) {
- super();
- this.node = node;
- this.callback = callback;
- }
- getNodeType( builder ) {
- return this.node.getNodeType( builder );
- }
- setup( builder ) {
- return this.node.build( builder );
- }
- analyze( builder ) {
- return this.node.build( builder );
- }
- generate( builder ) {
- const callback = this.callback;
- const snippet = this.node.build( builder );
- const title = '--- TSL debug - ' + builder.shaderStage + ' shader ---';
- const border = '-'.repeat( title.length );
- let code = '';
- code += '// #' + title + '#\n';
- code += builder.flow.code.replace( /^\t/mg, '' ) + '\n';
- code += '/* ... */ ' + snippet + ' /* ... */\n';
- code += '// #' + border + '#\n';
- if ( callback !== null ) {
- callback( builder, code );
- } else {
- log$1( code );
- }
- return snippet;
- }
- }
- /**
- * TSL function for creating a debug node.
- *
- * @tsl
- * @function
- * @param {Node} node - The node to debug.
- * @param {?Function} [callback=null] - Optional callback function to handle the debug output.
- * @returns {DebugNode}
- */
- const debug = ( node, callback = null ) => nodeObject( new DebugNode( nodeObject( node ), callback ) ).toStack();
- addMethodChaining( 'debug', debug );
- /**
- * InspectorBase is the base class for all inspectors.
- *
- * @class InspectorBase
- */
- class InspectorBase {
- /**
- * Creates a new InspectorBase.
- */
- constructor() {
- /**
- * The renderer associated with this inspector.
- *
- * @type {WebGLRenderer}
- * @private
- */
- this._renderer = null;
- /**
- * The current frame being processed.
- *
- * @type {Object}
- */
- this.currentFrame = null;
- }
- /**
- * Returns the node frame for the current renderer.
- *
- * @return {Object} The node frame.
- */
- get nodeFrame() {
- return this._renderer._nodes.nodeFrame;
- }
- /**
- * Sets the renderer for this inspector.
- *
- * @param {WebGLRenderer} renderer - The renderer to associate with this inspector.
- * @return {InspectorBase} This inspector instance.
- */
- setRenderer( renderer ) {
- this._renderer = renderer;
- return this;
- }
- /**
- * Returns the renderer associated with this inspector.
- *
- * @return {WebGLRenderer} The associated renderer.
- */
- getRenderer() {
- return this._renderer;
- }
- /**
- * Initializes the inspector.
- */
- init() { }
- /**
- * Called when a frame begins.
- */
- begin() { }
- /**
- * Called when a frame ends.
- */
- finish() { }
- /**
- * Inspects a node.
- *
- * @param {Node} node - The node to inspect.
- */
- inspect( /*node*/ ) { }
- /**
- * When a compute operation is performed.
- *
- * @param {ComputeNode} computeNode - The compute node being executed.
- * @param {number|Array<number>} dispatchSizeOrCount - The dispatch size or count.
- */
- computeAsync( /*computeNode, dispatchSizeOrCount*/ ) { }
- /**
- * Called when a compute operation begins.
- *
- * @param {string} uid - A unique identifier for the render context.
- * @param {ComputeNode} computeNode - The compute node being executed.
- */
- beginCompute( /*uid, computeNode*/ ) { }
- /**
- * Called when a compute operation ends.
- *
- * @param {string} uid - A unique identifier for the render context.
- * @param {ComputeNode} computeNode - The compute node being executed.
- */
- finishCompute( /*uid*/ ) { }
- /**
- * Called when a render operation begins.
- *
- * @param {string} uid - A unique identifier for the render context.
- * @param {Scene} scene - The scene being rendered.
- * @param {Camera} camera - The camera being used for rendering.
- * @param {?WebGLRenderTarget} renderTarget - The render target, if any.
- */
- beginRender( /*uid, scene, camera, renderTarget*/ ) { }
- /**
- * Called when an animation loop ends.
- *
- * @param {string} uid - A unique identifier for the render context.
- */
- finishRender( /*uid*/ ) { }
- /**
- * Called when a texture copy operation is performed.
- *
- * @param {Texture} srcTexture - The source texture.
- * @param {Texture} dstTexture - The destination texture.
- */
- copyTextureToTexture( /*srcTexture, dstTexture*/ ) { }
- /**
- * Called when a framebuffer copy operation is performed.
- *
- * @param {Texture} framebufferTexture - The texture associated with the framebuffer.
- */
- copyFramebufferToTexture( /*framebufferTexture*/ ) { }
- }
- /**
- * InspectorNode is a wrapper node that allows inspection of node values during rendering.
- * It can be used to debug or analyze node outputs in the rendering pipeline.
- *
- * @augments Node
- */
- class InspectorNode extends Node {
- /**
- * Returns the type of the node.
- *
- * @returns {string}
- */
- static get type() {
- return 'InspectorNode';
- }
- /**
- * Creates an InspectorNode.
- *
- * @param {Node} node - The node to inspect.
- * @param {string} [name=''] - Optional name for the inspector node.
- * @param {Function|null} [callback=null] - Optional callback to modify the node during setup.
- */
- constructor( node, name = '', callback = null ) {
- super();
- this.node = node;
- this.name = name;
- this.callback = callback;
- this.updateType = NodeUpdateType.FRAME;
- this.isInspectorNode = true;
- }
- /**
- * Returns the name of the inspector node.
- *
- * @returns {string}
- */
- getName() {
- return this.name || this.node.name;
- }
- /**
- * Updates the inspector node, allowing inspection of the wrapped node.
- *
- * @param {NodeFrame} frame - A reference to the current node frame.
- */
- update( frame ) {
- frame.renderer.inspector.inspect( this );
- }
- /**
- * Returns the type of the wrapped node.
- *
- * @param {NodeBuilder} builder - The node builder.
- * @returns {string}
- */
- getNodeType( builder ) {
- return this.node.getNodeType( builder );
- }
- /**
- * Sets up the inspector node.
- *
- * @param {NodeBuilder} builder - The node builder.
- * @returns {Node} The setup node.
- */
- setup( builder ) {
- let node = this.node;
- if ( builder.context.inspector === true && this.callback !== null ) {
- node = this.callback( node );
- }
- if ( builder.renderer.backend.isWebGPUBackend !== true && builder.renderer.inspector.constructor !== InspectorBase ) {
- warnOnce( 'TSL: ".toInspector()" is only available with WebGPU.' );
- }
- return node;
- }
- }
- /**
- * Creates an inspector node to wrap around a given node for inspection purposes.
- *
- * @tsl
- * @param {Node} node - The node to inspect.
- * @param {string} [name=''] - Optional name for the inspector node.
- * @param {Function|null} [callback=null] - Optional callback to modify the node during setup.
- * @returns {Node} The inspector node.
- */
- function inspector( node, name = '', callback = null ) {
- node = nodeObject( node );
- return node.before( new InspectorNode( node, name, callback ) );
- }
- addMethodChaining( 'toInspector', inspector );
- function addNodeElement( name/*, nodeElement*/ ) {
- warn( 'TSL: AddNodeElement has been removed in favor of tree-shaking. Trying add', name );
- }
- /**
- * Base class for representing shader attributes as nodes.
- *
- * @augments Node
- */
- class AttributeNode extends Node {
- static get type() {
- return 'AttributeNode';
- }
- /**
- * Constructs a new attribute node.
- *
- * @param {string} attributeName - The name of the attribute.
- * @param {?string} nodeType - The node type.
- */
- constructor( attributeName, nodeType = null ) {
- super( nodeType );
- /**
- * `AttributeNode` sets this property to `true` by default.
- *
- * @type {boolean}
- * @default true
- */
- this.global = true;
- this._attributeName = attributeName;
- }
- getHash( builder ) {
- return this.getAttributeName( builder );
- }
- getNodeType( builder ) {
- let nodeType = this.nodeType;
- if ( nodeType === null ) {
- const attributeName = this.getAttributeName( builder );
- if ( builder.hasGeometryAttribute( attributeName ) ) {
- const attribute = builder.geometry.getAttribute( attributeName );
- nodeType = builder.getTypeFromAttribute( attribute );
- } else {
- nodeType = 'float';
- }
- }
- return nodeType;
- }
- /**
- * Sets the attribute name to the given value. The method can be
- * overwritten in derived classes if the final name must be computed
- * analytically.
- *
- * @param {string} attributeName - The name of the attribute.
- * @return {AttributeNode} A reference to this node.
- */
- setAttributeName( attributeName ) {
- this._attributeName = attributeName;
- return this;
- }
- /**
- * Returns the attribute name of this node. The method can be
- * overwritten in derived classes if the final name must be computed
- * analytically.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {string} The attribute name.
- */
- getAttributeName( /*builder*/ ) {
- return this._attributeName;
- }
- generate( builder ) {
- const attributeName = this.getAttributeName( builder );
- const nodeType = this.getNodeType( builder );
- const geometryAttribute = builder.hasGeometryAttribute( attributeName );
- if ( geometryAttribute === true ) {
- const attribute = builder.geometry.getAttribute( attributeName );
- const attributeType = builder.getTypeFromAttribute( attribute );
- const nodeAttribute = builder.getAttribute( attributeName, attributeType );
- if ( builder.shaderStage === 'vertex' ) {
- return builder.format( nodeAttribute.name, attributeType, nodeType );
- } else {
- const nodeVarying = varying( this );
- return nodeVarying.build( builder, nodeType );
- }
- } else {
- warn( `AttributeNode: Vertex attribute "${ attributeName }" not found on geometry.` );
- return builder.generateConst( nodeType );
- }
- }
- serialize( data ) {
- super.serialize( data );
- data.global = this.global;
- data._attributeName = this._attributeName;
- }
- deserialize( data ) {
- super.deserialize( data );
- this.global = data.global;
- this._attributeName = data._attributeName;
- }
- }
- /**
- * TSL function for creating an attribute node.
- *
- * @tsl
- * @function
- * @param {string} name - The name of the attribute.
- * @param {?string} [nodeType=null] - The node type.
- * @returns {AttributeNode}
- */
- const attribute = ( name, nodeType = null ) => nodeObject( new AttributeNode( name, nodeType ) );
- /**
- * TSL function for creating an uv attribute node with the given index.
- *
- * @tsl
- * @function
- * @param {number} [index=0] - The uv index.
- * @return {AttributeNode<vec2>} The uv attribute node.
- */
- const uv$1 = ( index = 0 ) => attribute( 'uv' + ( index > 0 ? index : '' ), 'vec2' );
- /**
- * A node that represents the dimensions of a texture. The texture size is
- * retrieved in the shader via built-in shader functions like `textureDimensions()`
- * or `textureSize()`.
- *
- * @augments Node
- */
- class TextureSizeNode extends Node {
- static get type() {
- return 'TextureSizeNode';
- }
- /**
- * Constructs a new texture size node.
- *
- * @param {TextureNode} textureNode - A texture node which size should be retrieved.
- * @param {?Node<int>} [levelNode=null] - A level node which defines the requested mip.
- */
- constructor( textureNode, levelNode = null ) {
- super( 'uvec2' );
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isTextureSizeNode = true;
- /**
- * A texture node which size should be retrieved.
- *
- * @type {TextureNode}
- */
- this.textureNode = textureNode;
- /**
- * A level node which defines the requested mip.
- *
- * @type {Node<int>}
- * @default null
- */
- this.levelNode = levelNode;
- }
- generate( builder, output ) {
- const textureProperty = this.textureNode.build( builder, 'property' );
- const level = this.levelNode === null ? '0' : this.levelNode.build( builder, 'int' );
- return builder.format( `${ builder.getMethod( 'textureDimensions' ) }( ${ textureProperty }, ${ level } )`, this.getNodeType( builder ), output );
- }
- }
- /**
- * TSL function for creating a texture size node.
- *
- * @tsl
- * @function
- * @param {TextureNode} textureNode - A texture node which size should be retrieved.
- * @param {?Node<int>} [levelNode=null] - A level node which defines the requested mip.
- * @returns {TextureSizeNode}
- */
- const textureSize = /*@__PURE__*/ nodeProxy( TextureSizeNode ).setParameterLength( 1, 2 );
- /**
- * A special type of uniform node that computes the
- * maximum mipmap level for a given texture node.
- *
- * ```js
- * const level = maxMipLevel( textureNode );
- * ```
- *
- * @augments UniformNode
- */
- class MaxMipLevelNode extends UniformNode {
- static get type() {
- return 'MaxMipLevelNode';
- }
- /**
- * Constructs a new max mip level node.
- *
- * @param {TextureNode} textureNode - The texture node to compute the max mip level for.
- */
- constructor( textureNode ) {
- super( 0 );
- /**
- * The texture node to compute the max mip level for.
- *
- * @private
- * @type {TextureNode}
- */
- this._textureNode = textureNode;
- /**
- * The `updateType` is set to `NodeUpdateType.FRAME` since the node updates
- * the texture once per frame in its {@link MaxMipLevelNode#update} method.
- *
- * @type {string}
- * @default 'frame'
- */
- this.updateType = NodeUpdateType.FRAME;
- }
- /**
- * The texture node to compute the max mip level for.
- *
- * @readonly
- * @type {TextureNode}
- */
- get textureNode() {
- return this._textureNode;
- }
- /**
- * The texture.
- *
- * @readonly
- * @type {Texture}
- */
- get texture() {
- return this._textureNode.value;
- }
- update() {
- const texture = this.texture;
- const images = texture.images;
- const image = ( images && images.length > 0 ) ? ( ( images[ 0 ] && images[ 0 ].image ) || images[ 0 ] ) : texture.image;
- if ( image && image.width !== undefined ) {
- const { width, height } = image;
- this.value = Math.log2( Math.max( width, height ) );
- }
- }
- }
- /**
- * TSL function for creating a max mip level node.
- *
- * @tsl
- * @function
- * @param {TextureNode} textureNode - The texture node to compute the max mip level for.
- * @returns {MaxMipLevelNode}
- */
- const maxMipLevel = /*@__PURE__*/ nodeProxy( MaxMipLevelNode ).setParameterLength( 1 );
- const EmptyTexture$1 = /*@__PURE__*/ new Texture();
- /**
- * This type of uniform node represents a 2D texture.
- *
- * @augments UniformNode
- */
- class TextureNode extends UniformNode {
- static get type() {
- return 'TextureNode';
- }
- /**
- * Constructs a new texture node.
- *
- * @param {Texture} [value=EmptyTexture] - The texture.
- * @param {?Node<vec2|vec3>} [uvNode=null] - The uv node.
- * @param {?Node<int>} [levelNode=null] - The level node.
- * @param {?Node<float>} [biasNode=null] - The bias node.
- */
- constructor( value = EmptyTexture$1, uvNode = null, levelNode = null, biasNode = null ) {
- super( value );
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isTextureNode = true;
- /**
- * Represents the texture coordinates.
- *
- * @type {?Node<vec2|vec3>}
- * @default null
- */
- this.uvNode = uvNode;
- /**
- * Represents the mip level that should be selected.
- *
- * @type {?Node<int>}
- * @default null
- */
- this.levelNode = levelNode;
- /**
- * Represents the bias to be applied during level-of-detail computation.
- *
- * @type {?Node<float>}
- * @default null
- */
- this.biasNode = biasNode;
- /**
- * Represents a reference value a texture sample is compared to.
- *
- * @type {?Node<float>}
- * @default null
- */
- this.compareNode = null;
- /**
- * When using texture arrays, the depth node defines the layer to select.
- *
- * @type {?Node<int>}
- * @default null
- */
- this.depthNode = null;
- /**
- * When defined, a texture is sampled using explicit gradients.
- *
- * @type {?Array<Node<vec2>>}
- * @default null
- */
- this.gradNode = null;
- /**
- * Represents the optional texel offset applied to the unnormalized texture
- * coordinate before sampling the texture.
- *
- * @type {?Node<ivec2|ivec3>}
- * @default null
- */
- this.offsetNode = null;
- /**
- * Whether texture values should be sampled or fetched.
- *
- * @type {boolean}
- * @default true
- */
- this.sampler = true;
- /**
- * Whether the uv transformation matrix should be
- * automatically updated or not. Use `setUpdateMatrix()`
- * if you want to change the value of the property.
- *
- * @type {boolean}
- * @default false
- */
- this.updateMatrix = false;
- /**
- * By default the `update()` method is not executed. Depending on
- * whether a uv transformation matrix and/or flipY is applied, `update()`
- * is executed per object.
- *
- * @type {string}
- * @default 'none'
- */
- this.updateType = NodeUpdateType.NONE;
- /**
- * The reference node.
- *
- * @type {?Node}
- * @default null
- */
- this.referenceNode = null;
- /**
- * The texture value is stored in a private property.
- *
- * @private
- * @type {Texture}
- */
- this._value = value;
- /**
- * The uniform node that represents the uv transformation matrix.
- *
- * @private
- * @type {?UniformNode<mat3>}
- * @default null
- */
- this._matrixUniform = null;
- /**
- * The uniform node that represents the y-flip. Only required for WebGL.
- *
- * @private
- * @type {?UniformNode<bool>}
- * @default null
- */
- this._flipYUniform = null;
- this.setUpdateMatrix( uvNode === null );
- }
- set value( value ) {
- if ( this.referenceNode ) {
- this.referenceNode.value = value;
- } else {
- this._value = value;
- }
- }
- /**
- * The texture value.
- *
- * @type {Texture}
- */
- get value() {
- return this.referenceNode ? this.referenceNode.value : this._value;
- }
- /**
- * Overwritten since the uniform hash is defined by the texture's UUID.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {string} The uniform hash.
- */
- getUniformHash( /*builder*/ ) {
- return this.value.uuid;
- }
- /**
- * Overwritten since the node type is inferred from the texture type.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {string} The node type.
- */
- getNodeType( /*builder*/ ) {
- if ( this.value.isDepthTexture === true ) return 'float';
- if ( this.value.type === UnsignedIntType ) {
- return 'uvec4';
- } else if ( this.value.type === IntType ) {
- return 'ivec4';
- }
- return 'vec4';
- }
- /**
- * Overwrites the default implementation to return a fixed value `'texture'`.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {string} The input type.
- */
- getInputType( /*builder*/ ) {
- return 'texture';
- }
- /**
- * Returns a default uvs based on the current texture's channel.
- *
- * @return {AttributeNode<vec2>} The default uvs.
- */
- getDefaultUV() {
- return uv$1( this.value.channel );
- }
- /**
- * Overwritten to always return the texture reference of the node.
- *
- * @param {any} state - This method can be invocated in different contexts so `state` can refer to any object type.
- * @return {Texture} The texture reference.
- */
- updateReference( /*state*/ ) {
- return this.value;
- }
- /**
- * Transforms the given uv node with the texture transformation matrix.
- *
- * @param {Node} uvNode - The uv node to transform.
- * @return {Node} The transformed uv node.
- */
- getTransformedUV( uvNode ) {
- if ( this._matrixUniform === null ) this._matrixUniform = uniform( this.value.matrix );
- return this._matrixUniform.mul( vec3( uvNode, 1 ) ).xy;
- }
- /**
- * Defines whether the uv transformation matrix should automatically be updated or not.
- *
- * @param {boolean} value - The update toggle.
- * @return {TextureNode} A reference to this node.
- */
- setUpdateMatrix( value ) {
- this.updateMatrix = value;
- return this;
- }
- /**
- * Setups the uv node. Depending on the backend as well as texture's image and type, it might be necessary
- * to modify the uv node for correct sampling.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @param {Node} uvNode - The uv node to setup.
- * @return {Node} The updated uv node.
- */
- setupUV( builder, uvNode ) {
- if ( builder.isFlipY() ) {
- if ( this._flipYUniform === null ) this._flipYUniform = uniform( false );
- uvNode = uvNode.toVar();
- if ( this.sampler ) {
- uvNode = this._flipYUniform.select( uvNode.flipY(), uvNode );
- } else {
- uvNode = this._flipYUniform.select( uvNode.setY( int( textureSize( this, this.levelNode ).y ).sub( uvNode.y ).sub( 1 ) ), uvNode );
- }
- }
- return uvNode;
- }
- /**
- * Setups texture node by preparing the internal nodes for code generation.
- *
- * @param {NodeBuilder} builder - The current node builder.
- */
- setup( builder ) {
- const properties = builder.getNodeProperties( this );
- properties.referenceNode = this.referenceNode;
- //
- const texture = this.value;
- if ( ! texture || texture.isTexture !== true ) {
- throw new Error( 'THREE.TSL: `texture( value )` function expects a valid instance of THREE.Texture().' );
- }
- //
- const uvNode = Fn( () => {
- let uvNode = this.uvNode;
- if ( ( uvNode === null || builder.context.forceUVContext === true ) && builder.context.getUV ) {
- uvNode = builder.context.getUV( this, builder );
- }
- if ( ! uvNode ) uvNode = this.getDefaultUV();
- if ( this.updateMatrix === true ) {
- uvNode = this.getTransformedUV( uvNode );
- }
- uvNode = this.setupUV( builder, uvNode );
- //
- this.updateType = ( this._matrixUniform !== null || this._flipYUniform !== null ) ? NodeUpdateType.OBJECT : NodeUpdateType.NONE;
- //
- return uvNode;
- } )();
- //
- let levelNode = this.levelNode;
- if ( levelNode === null && builder.context.getTextureLevel ) {
- levelNode = builder.context.getTextureLevel( this );
- }
- //
- properties.uvNode = uvNode;
- properties.levelNode = levelNode;
- properties.biasNode = this.biasNode;
- properties.compareNode = this.compareNode;
- properties.gradNode = this.gradNode;
- properties.depthNode = this.depthNode;
- properties.offsetNode = this.offsetNode;
- }
- /**
- * Generates the uv code snippet.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @param {Node} uvNode - The uv node to generate code for.
- * @return {string} The generated code snippet.
- */
- generateUV( builder, uvNode ) {
- return uvNode.build( builder, this.sampler === true ? 'vec2' : 'ivec2' );
- }
- /**
- * Generates the offset code snippet.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @param {Node} offsetNode - The offset node to generate code for.
- * @return {string} The generated code snippet.
- */
- generateOffset( builder, offsetNode ) {
- return offsetNode.build( builder, 'ivec2' );
- }
- /**
- * Generates the snippet for the texture sampling.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @param {string} textureProperty - The texture property.
- * @param {string} uvSnippet - The uv snippet.
- * @param {?string} levelSnippet - The level snippet.
- * @param {?string} biasSnippet - The bias snippet.
- * @param {?string} depthSnippet - The depth snippet.
- * @param {?string} compareSnippet - The compare snippet.
- * @param {?Array<string>} gradSnippet - The grad snippet.
- * @param {?string} offsetSnippet - The offset snippet.
- * @return {string} The generated code snippet.
- */
- generateSnippet( builder, textureProperty, uvSnippet, levelSnippet, biasSnippet, depthSnippet, compareSnippet, gradSnippet, offsetSnippet ) {
- const texture = this.value;
- let snippet;
- if ( biasSnippet ) {
- snippet = builder.generateTextureBias( texture, textureProperty, uvSnippet, biasSnippet, depthSnippet, offsetSnippet );
- } else if ( gradSnippet ) {
- snippet = builder.generateTextureGrad( texture, textureProperty, uvSnippet, gradSnippet, depthSnippet, offsetSnippet );
- } else if ( compareSnippet ) {
- snippet = builder.generateTextureCompare( texture, textureProperty, uvSnippet, compareSnippet, depthSnippet, offsetSnippet );
- } else if ( this.sampler === false ) {
- snippet = builder.generateTextureLoad( texture, textureProperty, uvSnippet, levelSnippet, depthSnippet, offsetSnippet );
- } else if ( levelSnippet ) {
- snippet = builder.generateTextureLevel( texture, textureProperty, uvSnippet, levelSnippet, depthSnippet, offsetSnippet );
- } else {
- snippet = builder.generateTexture( texture, textureProperty, uvSnippet, depthSnippet, offsetSnippet );
- }
- return snippet;
- }
- /**
- * Generates the code snippet of the texture node.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @param {string} output - The current output.
- * @return {string} The generated code snippet.
- */
- generate( builder, output ) {
- const texture = this.value;
- const properties = builder.getNodeProperties( this );
- const textureProperty = super.generate( builder, 'property' );
- if ( /^sampler/.test( output ) ) {
- return textureProperty + '_sampler';
- } else if ( builder.isReference( output ) ) {
- return textureProperty;
- } else {
- const nodeData = builder.getDataFromNode( this );
- let propertyName = nodeData.propertyName;
- if ( propertyName === undefined ) {
- const { uvNode, levelNode, biasNode, compareNode, depthNode, gradNode, offsetNode } = properties;
- const uvSnippet = this.generateUV( builder, uvNode );
- const levelSnippet = levelNode ? levelNode.build( builder, 'float' ) : null;
- const biasSnippet = biasNode ? biasNode.build( builder, 'float' ) : null;
- const depthSnippet = depthNode ? depthNode.build( builder, 'int' ) : null;
- const compareSnippet = compareNode ? compareNode.build( builder, 'float' ) : null;
- const gradSnippet = gradNode ? [ gradNode[ 0 ].build( builder, 'vec2' ), gradNode[ 1 ].build( builder, 'vec2' ) ] : null;
- const offsetSnippet = offsetNode ? this.generateOffset( builder, offsetNode ) : null;
- const nodeVar = builder.getVarFromNode( this );
- propertyName = builder.getPropertyName( nodeVar );
- const snippet = this.generateSnippet( builder, textureProperty, uvSnippet, levelSnippet, biasSnippet, depthSnippet, compareSnippet, gradSnippet, offsetSnippet );
- builder.addLineFlowCode( `${propertyName} = ${snippet}`, this );
- nodeData.snippet = snippet;
- nodeData.propertyName = propertyName;
- }
- let snippet = propertyName;
- const nodeType = this.getNodeType( builder );
- if ( builder.needsToWorkingColorSpace( texture ) ) {
- snippet = colorSpaceToWorking( expression( snippet, nodeType ), texture.colorSpace ).setup( builder ).build( builder, nodeType );
- }
- return builder.format( snippet, nodeType, output );
- }
- }
- /**
- * Sets the sampler value.
- *
- * @param {boolean} value - The sampler value to set.
- * @return {TextureNode} A reference to this texture node.
- */
- setSampler( value ) {
- this.sampler = value;
- return this;
- }
- /**
- * Returns the sampler value.
- *
- * @return {boolean} The sampler value.
- */
- getSampler() {
- return this.sampler;
- }
- // @TODO: Move to TSL
- /**
- * @function
- * @deprecated since r172. Use {@link TextureNode#sample} instead.
- *
- * @param {Node} uvNode - The uv node.
- * @return {TextureNode} A texture node representing the texture sample.
- */
- uv( uvNode ) { // @deprecated, r172
- warn( 'TextureNode: .uv() has been renamed. Use .sample() instead.' );
- return this.sample( uvNode );
- }
- /**
- * Samples the texture with the given uv node.
- *
- * @param {Node} uvNode - The uv node.
- * @return {TextureNode} A texture node representing the texture sample.
- */
- sample( uvNode ) {
- const textureNode = this.clone();
- textureNode.uvNode = nodeObject( uvNode );
- textureNode.referenceNode = this.getBase();
- return nodeObject( textureNode );
- }
- /**
- * TSL function for creating a texture node that fetches/loads texels without interpolation.
- *
- * @param {Node<uvec2>} uvNode - The uv node.
- * @returns {TextureNode} A texture node representing the texture load.
- */
- load( uvNode ) {
- return this.sample( uvNode ).setSampler( false );
- }
- /**
- * Samples a blurred version of the texture by defining an internal bias.
- *
- * @param {Node<float>} amountNode - How blurred the texture should be.
- * @return {TextureNode} A texture node representing the texture sample.
- */
- blur( amountNode ) {
- const textureNode = this.clone();
- textureNode.biasNode = nodeObject( amountNode ).mul( maxMipLevel( textureNode ) );
- textureNode.referenceNode = this.getBase();
- const map = textureNode.value;
- if ( textureNode.generateMipmaps === false && ( map && map.generateMipmaps === false || map.minFilter === NearestFilter || map.magFilter === NearestFilter ) ) {
- warn( 'TSL: texture().blur() requires mipmaps and sampling. Use .generateMipmaps=true and .minFilter/.magFilter=THREE.LinearFilter in the Texture.' );
- textureNode.biasNode = null;
- }
- return nodeObject( textureNode );
- }
- /**
- * Samples a specific mip of the texture.
- *
- * @param {Node<int>} levelNode - The mip level to sample.
- * @return {TextureNode} A texture node representing the texture sample.
- */
- level( levelNode ) {
- const textureNode = this.clone();
- textureNode.levelNode = nodeObject( levelNode );
- textureNode.referenceNode = this.getBase();
- return nodeObject( textureNode );
- }
- /**
- * Returns the texture size of the requested level.
- *
- * @param {Node<int>} levelNode - The level to compute the size for.
- * @return {TextureSizeNode} The texture size.
- */
- size( levelNode ) {
- return textureSize( this, levelNode );
- }
- /**
- * Samples the texture with the given bias.
- *
- * @param {Node<float>} biasNode - The bias node.
- * @return {TextureNode} A texture node representing the texture sample.
- */
- bias( biasNode ) {
- const textureNode = this.clone();
- textureNode.biasNode = nodeObject( biasNode );
- textureNode.referenceNode = this.getBase();
- return nodeObject( textureNode );
- }
- /**
- * Returns the base texture of this node.
- * @return {TextureNode} The base texture node.
- */
- getBase() {
- return this.referenceNode ? this.referenceNode.getBase() : this;
- }
- /**
- * Samples the texture by executing a compare operation.
- *
- * @param {Node<float>} compareNode - The node that defines the compare value.
- * @return {TextureNode} A texture node representing the texture sample.
- */
- compare( compareNode ) {
- const textureNode = this.clone();
- textureNode.compareNode = nodeObject( compareNode );
- textureNode.referenceNode = this.getBase();
- return nodeObject( textureNode );
- }
- /**
- * Samples the texture using an explicit gradient.
- *
- * @param {Node<vec2>} gradNodeX - The gradX node.
- * @param {Node<vec2>} gradNodeY - The gradY node.
- * @return {TextureNode} A texture node representing the texture sample.
- */
- grad( gradNodeX, gradNodeY ) {
- const textureNode = this.clone();
- textureNode.gradNode = [ nodeObject( gradNodeX ), nodeObject( gradNodeY ) ];
- textureNode.referenceNode = this.getBase();
- return nodeObject( textureNode );
- }
- /**
- * Samples the texture by defining a depth node.
- *
- * @param {Node<int>} depthNode - The depth node.
- * @return {TextureNode} A texture node representing the texture sample.
- */
- depth( depthNode ) {
- const textureNode = this.clone();
- textureNode.depthNode = nodeObject( depthNode );
- textureNode.referenceNode = this.getBase();
- return nodeObject( textureNode );
- }
- /**
- * Samples the texture by defining an offset node.
- *
- * @param {Node<ivec2>} offsetNode - The offset node.
- * @return {TextureNode} A texture node representing the texture sample.
- */
- offset( offsetNode ) {
- const textureNode = this.clone();
- textureNode.offsetNode = nodeObject( offsetNode );
- textureNode.referenceNode = this.getBase();
- return nodeObject( textureNode );
- }
- // --
- serialize( data ) {
- super.serialize( data );
- data.value = this.value.toJSON( data.meta ).uuid;
- data.sampler = this.sampler;
- data.updateMatrix = this.updateMatrix;
- data.updateType = this.updateType;
- }
- deserialize( data ) {
- super.deserialize( data );
- this.value = data.meta.textures[ data.value ];
- this.sampler = data.sampler;
- this.updateMatrix = data.updateMatrix;
- this.updateType = data.updateType;
- }
- /**
- * The update is used to implement the update of the uv transformation matrix.
- */
- update() {
- const texture = this.value;
- const matrixUniform = this._matrixUniform;
- if ( matrixUniform !== null ) matrixUniform.value = texture.matrix;
- if ( texture.matrixAutoUpdate === true ) {
- texture.updateMatrix();
- }
- //
- const flipYUniform = this._flipYUniform;
- if ( flipYUniform !== null ) {
- flipYUniform.value = ( ( texture.image instanceof ImageBitmap && texture.flipY === true ) || texture.isRenderTargetTexture === true || texture.isFramebufferTexture === true || texture.isDepthTexture === true );
- }
- }
- /**
- * Clones the texture node.
- *
- * @return {TextureNode} The cloned texture node.
- */
- clone() {
- const newNode = new this.constructor( this.value, this.uvNode, this.levelNode, this.biasNode );
- newNode.sampler = this.sampler;
- newNode.depthNode = this.depthNode;
- newNode.compareNode = this.compareNode;
- newNode.gradNode = this.gradNode;
- newNode.offsetNode = this.offsetNode;
- return newNode;
- }
- }
- /**
- * TSL function for creating a texture node.
- *
- * @tsl
- * @function
- * @param {?Texture} value - The texture.
- * @param {?Node<vec2|vec3>} [uvNode=null] - The uv node.
- * @param {?Node<int>} [levelNode=null] - The level node.
- * @param {?Node<float>} [biasNode=null] - The bias node.
- * @returns {TextureNode}
- */
- const textureBase = /*@__PURE__*/ nodeProxy( TextureNode ).setParameterLength( 1, 4 ).setName( 'texture' );
- /**
- * TSL function for creating a texture node or sample a texture node already existing.
- *
- * @tsl
- * @function
- * @param {?(Texture|TextureNode)} [value=EmptyTexture] - The texture.
- * @param {?Node<vec2|vec3>} [uvNode=null] - The uv node.
- * @param {?Node<int>} [levelNode=null] - The level node.
- * @param {?Node<float>} [biasNode=null] - The bias node.
- * @returns {TextureNode}
- */
- const texture = ( value = EmptyTexture$1, uvNode = null, levelNode = null, biasNode = null ) => {
- let textureNode;
- if ( value && value.isTextureNode === true ) {
- textureNode = nodeObject( value.clone() );
- textureNode.referenceNode = value.getBase(); // Ensure the reference is set to the original node
- if ( uvNode !== null ) textureNode.uvNode = nodeObject( uvNode );
- if ( levelNode !== null ) textureNode.levelNode = nodeObject( levelNode );
- if ( biasNode !== null ) textureNode.biasNode = nodeObject( biasNode );
- } else {
- textureNode = textureBase( value, uvNode, levelNode, biasNode );
- }
- return textureNode;
- };
- /**
- * TSL function for creating a uniform texture node.
- *
- * @tsl
- * @function
- * @param {?Texture} value - The texture.
- * @returns {TextureNode}
- */
- const uniformTexture = ( value = EmptyTexture$1 ) => texture( value );
- /**
- * TSL function for creating a texture node that fetches/loads texels without interpolation.
- *
- * @tsl
- * @function
- * @param {?(Texture|TextureNode)} [value=EmptyTexture] - The texture.
- * @param {?Node<vec2|vec3>} [uvNode=null] - The uv node.
- * @param {?Node<int>} [levelNode=null] - The level node.
- * @param {?Node<float>} [biasNode=null] - The bias node.
- * @returns {TextureNode}
- */
- const textureLoad = ( ...params ) => texture( ...params ).setSampler( false );
- const textureLevel = ( value, uv, level ) => texture( value, uv ).level( level );
- /**
- * Converts a texture or texture node to a sampler.
- *
- * @tsl
- * @function
- * @param {TextureNode|Texture} value - The texture or texture node to convert.
- * @returns {Node}
- */
- const sampler = ( value ) => ( value.isNode === true ? value : texture( value ) ).convert( 'sampler' );
- /**
- * Converts a texture or texture node to a sampler comparison.
- *
- * @tsl
- * @function
- * @param {TextureNode|Texture} value - The texture or texture node to convert.
- * @returns {Node}
- */
- const samplerComparison = ( value ) => ( value.isNode === true ? value : texture( value ) ).convert( 'samplerComparison' );
- /**
- * A special type of uniform node which represents array-like data
- * as uniform buffers. The access usually happens via `element()`
- * which returns an instance of {@link ArrayElementNode}. For example:
- *
- * ```js
- * const bufferNode = buffer( array, 'mat4', count );
- * const matrixNode = bufferNode.element( index ); // access a matrix from the buffer
- * ```
- * In general, it is recommended to use the more managed {@link UniformArrayNode}
- * since it handles more input types and automatically cares about buffer paddings.
- *
- * @augments UniformNode
- */
- class BufferNode extends UniformNode {
- static get type() {
- return 'BufferNode';
- }
- /**
- * Constructs a new buffer node.
- *
- * @param {Array<number>} value - Array-like buffer data.
- * @param {string} bufferType - The data type of the buffer.
- * @param {number} [bufferCount=0] - The count of buffer elements.
- */
- constructor( value, bufferType, bufferCount = 0 ) {
- super( value, bufferType );
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isBufferNode = true;
- /**
- * The data type of the buffer.
- *
- * @type {string}
- */
- this.bufferType = bufferType;
- /**
- * The uniform node that holds the value of the reference node.
- *
- * @type {number}
- * @default 0
- */
- this.bufferCount = bufferCount;
- /**
- * An array of update ranges.
- *
- * @type {Array<{start: number, count: number}>}
- */
- this.updateRanges = [];
- }
- /**
- * Adds a range of data in the data array to be updated on the GPU.
- *
- * @param {number} start - Position at which to start update.
- * @param {number} count - The number of components to update.
- */
- addUpdateRange( start, count ) {
- this.updateRanges.push( { start, count } );
- }
- /**
- * Clears the update ranges.
- */
- clearUpdateRanges() {
- this.updateRanges.length = 0;
- }
- /**
- * The data type of the buffer elements.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {string} The element type.
- */
- getElementType( builder ) {
- return this.getNodeType( builder );
- }
- /**
- * Overwrites the default implementation to return a fixed value `'buffer'`.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {string} The input type.
- */
- getInputType( /*builder*/ ) {
- return 'buffer';
- }
- }
- /**
- * TSL function for creating a buffer node.
- *
- * @tsl
- * @function
- * @param {Array<number>} value - Array-like buffer data.
- * @param {string} type - The data type of a buffer element.
- * @param {number} count - The count of buffer elements.
- * @returns {BufferNode}
- */
- const buffer = ( value, type, count ) => nodeObject( new BufferNode( value, type, count ) );
- /**
- * Represents the element access on uniform array nodes.
- *
- * @augments ArrayElementNode
- */
- class UniformArrayElementNode extends ArrayElementNode {
- static get type() {
- return 'UniformArrayElementNode';
- }
- /**
- * Constructs a new buffer node.
- *
- * @param {UniformArrayNode} uniformArrayNode - The uniform array node to access.
- * @param {IndexNode} indexNode - The index data that define the position of the accessed element in the array.
- */
- constructor( uniformArrayNode, indexNode ) {
- super( uniformArrayNode, indexNode );
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isArrayBufferElementNode = true;
- }
- generate( builder ) {
- const snippet = super.generate( builder );
- const type = this.getNodeType();
- const paddedType = this.node.getPaddedType();
- return builder.format( snippet, paddedType, type );
- }
- }
- /**
- * Similar to {@link BufferNode} this module represents array-like data as
- * uniform buffers. Unlike {@link BufferNode}, it can handle more common
- * data types in the array (e.g `three.js` primitives) and automatically
- * manage buffer padding. It should be the first choice when working with
- * uniforms buffers.
- * ```js
- * const tintColors = uniformArray( [
- * new Color( 1, 0, 0 ),
- * new Color( 0, 1, 0 ),
- * new Color( 0, 0, 1 )
- * ], 'color' );
- *
- * const redColor = tintColors.element( 0 );
- *
- * @augments BufferNode
- */
- class UniformArrayNode extends BufferNode {
- static get type() {
- return 'UniformArrayNode';
- }
- /**
- * Constructs a new uniform array node.
- *
- * @param {Array<any>} value - Array holding the buffer data.
- * @param {?string} [elementType=null] - The data type of a buffer element.
- */
- constructor( value, elementType = null ) {
- super( null );
- /**
- * Array holding the buffer data. Unlike {@link BufferNode}, the array can
- * hold number primitives as well as three.js objects like vectors, matrices
- * or colors.
- *
- * @type {Array<any>}
- */
- this.array = value;
- /**
- * The data type of an array element.
- *
- * @type {string}
- */
- this.elementType = elementType === null ? getValueType( value[ 0 ] ) : elementType;
- /**
- * The padded type. Uniform buffers must conform to a certain buffer layout
- * so a separate type is computed to ensure correct buffer size.
- *
- * @type {string}
- */
- this.paddedType = this.getPaddedType();
- /**
- * Overwritten since uniform array nodes are updated per render.
- *
- * @type {string}
- * @default 'render'
- */
- this.updateType = NodeUpdateType.RENDER;
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isArrayBufferNode = true;
- }
- /**
- * This method is overwritten since the node type is inferred from the
- * {@link UniformArrayNode#paddedType}.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {string} The node type.
- */
- getNodeType( /*builder*/ ) {
- return this.paddedType;
- }
- /**
- * The data type of the array elements.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {string} The element type.
- */
- getElementType() {
- return this.elementType;
- }
- /**
- * Returns the padded type based on the element type.
- *
- * @return {string} The padded type.
- */
- getPaddedType() {
- const elementType = this.elementType;
- let paddedType = 'vec4';
- if ( elementType === 'mat2' ) {
- paddedType = 'mat2';
- } else if ( /mat/.test( elementType ) === true ) {
- paddedType = 'mat4';
- } else if ( elementType.charAt( 0 ) === 'i' ) {
- paddedType = 'ivec4';
- } else if ( elementType.charAt( 0 ) === 'u' ) {
- paddedType = 'uvec4';
- }
- return paddedType;
- }
- /**
- * The update makes sure to correctly transfer the data from the (complex) objects
- * in the array to the internal, correctly padded value buffer.
- *
- * @param {NodeFrame} frame - A reference to the current node frame.
- */
- update( /*frame*/ ) {
- const { array, value } = this;
- const elementType = this.elementType;
- if ( elementType === 'float' || elementType === 'int' || elementType === 'uint' ) {
- for ( let i = 0; i < array.length; i ++ ) {
- const index = i * 4;
- value[ index ] = array[ i ];
- }
- } else if ( elementType === 'color' ) {
- for ( let i = 0; i < array.length; i ++ ) {
- const index = i * 4;
- const vector = array[ i ];
- value[ index ] = vector.r;
- value[ index + 1 ] = vector.g;
- value[ index + 2 ] = vector.b || 0;
- //value[ index + 3 ] = vector.a || 0;
- }
- } else if ( elementType === 'mat2' ) {
- for ( let i = 0; i < array.length; i ++ ) {
- const index = i * 4;
- const matrix = array[ i ];
- value[ index ] = matrix.elements[ 0 ];
- value[ index + 1 ] = matrix.elements[ 1 ];
- value[ index + 2 ] = matrix.elements[ 2 ];
- value[ index + 3 ] = matrix.elements[ 3 ];
- }
- } else if ( elementType === 'mat3' ) {
- for ( let i = 0; i < array.length; i ++ ) {
- const index = i * 16;
- const matrix = array[ i ];
- value[ index ] = matrix.elements[ 0 ];
- value[ index + 1 ] = matrix.elements[ 1 ];
- value[ index + 2 ] = matrix.elements[ 2 ];
- value[ index + 4 ] = matrix.elements[ 3 ];
- value[ index + 5 ] = matrix.elements[ 4 ];
- value[ index + 6 ] = matrix.elements[ 5 ];
- value[ index + 8 ] = matrix.elements[ 6 ];
- value[ index + 9 ] = matrix.elements[ 7 ];
- value[ index + 10 ] = matrix.elements[ 8 ];
- value[ index + 15 ] = 1;
- }
- } else if ( elementType === 'mat4' ) {
- for ( let i = 0; i < array.length; i ++ ) {
- const index = i * 16;
- const matrix = array[ i ];
- for ( let i = 0; i < matrix.elements.length; i ++ ) {
- value[ index + i ] = matrix.elements[ i ];
- }
- }
- } else {
- for ( let i = 0; i < array.length; i ++ ) {
- const index = i * 4;
- const vector = array[ i ];
- value[ index ] = vector.x;
- value[ index + 1 ] = vector.y;
- value[ index + 2 ] = vector.z || 0;
- value[ index + 3 ] = vector.w || 0;
- }
- }
- }
- /**
- * Implement the value buffer creation based on the array data.
- *
- * @param {NodeBuilder} builder - A reference to the current node builder.
- * @return {null}
- */
- setup( builder ) {
- const length = this.array.length;
- const elementType = this.elementType;
- let arrayType = Float32Array;
- const paddedType = this.paddedType;
- const paddedElementLength = builder.getTypeLength( paddedType );
- if ( elementType.charAt( 0 ) === 'i' ) arrayType = Int32Array;
- if ( elementType.charAt( 0 ) === 'u' ) arrayType = Uint32Array;
- this.value = new arrayType( length * paddedElementLength );
- this.bufferCount = length;
- this.bufferType = paddedType;
- return super.setup( builder );
- }
- /**
- * Overwrites the default `element()` method to provide element access
- * based on {@link UniformArrayNode}.
- *
- * @param {IndexNode} indexNode - The index node.
- * @return {UniformArrayElementNode}
- */
- element( indexNode ) {
- return nodeObject( new UniformArrayElementNode( this, nodeObject( indexNode ) ) );
- }
- }
- /**
- * TSL function for creating an uniform array node.
- *
- * @tsl
- * @function
- * @param {Array<any>} values - Array-like data.
- * @param {?string} [nodeType] - The data type of the array elements.
- * @returns {UniformArrayNode}
- */
- const uniformArray = ( values, nodeType ) => nodeObject( new UniformArrayNode( values, nodeType ) );
- /**
- * The node allows to set values for built-in shader variables. That is
- * required for features like hardware-accelerated vertex clipping.
- *
- * @augments Node
- */
- class BuiltinNode extends Node {
- /**
- * Constructs a new builtin node.
- *
- * @param {string} name - The name of the built-in shader variable.
- */
- constructor( name ) {
- super( 'float' );
- /**
- * The name of the built-in shader variable.
- *
- * @type {string}
- */
- this.name = name;
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isBuiltinNode = true;
- }
- /**
- * Generates the code snippet of the builtin node.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {string} The generated code snippet.
- */
- generate( /* builder */ ) {
- return this.name;
- }
- }
- /**
- * TSL function for creating a builtin node.
- *
- * @tsl
- * @function
- * @param {string} name - The name of the built-in shader variable.
- * @returns {BuiltinNode}
- */
- const builtin = nodeProxy( BuiltinNode ).setParameterLength( 1 );
- let _screenSizeVec, _viewportVec;
- /**
- * This node provides a collection of screen related metrics.
- * Depending on {@link ScreenNode#scope}, the nodes can represent
- * resolution or viewport data as well as fragment or uv coordinates.
- *
- * @augments Node
- */
- class ScreenNode extends Node {
- static get type() {
- return 'ScreenNode';
- }
- /**
- * Constructs a new screen node.
- *
- * @param {('coordinate'|'viewport'|'size'|'uv'|'dpr')} scope - The node's scope.
- */
- constructor( scope ) {
- super();
- /**
- * The node represents different metric depending on which scope is selected.
- *
- * - `ScreenNode.COORDINATE`: Window-relative coordinates of the current fragment according to WebGPU standards.
- * - `ScreenNode.VIEWPORT`: The current viewport defined as a four-dimensional vector.
- * - `ScreenNode.SIZE`: The dimensions of the current bound framebuffer.
- * - `ScreenNode.UV`: Normalized coordinates.
- * - `ScreenNode.DPR`: Device pixel ratio.
- *
- * @type {('coordinate'|'viewport'|'size'|'uv'|'dpr')}
- */
- this.scope = scope;
- /**
- * This output node.
- *
- * @private
- * @type {?Node}
- * @default null
- */
- this._output = null;
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isViewportNode = true;
- }
- /**
- * This method is overwritten since the node type depends on the selected scope.
- *
- * @return {('float'|'vec2'|'vec4')} The node type.
- */
- getNodeType() {
- if ( this.scope === ScreenNode.DPR ) return 'float';
- if ( this.scope === ScreenNode.VIEWPORT ) return 'vec4';
- else return 'vec2';
- }
- /**
- * This method is overwritten since the node's update type depends on the selected scope.
- *
- * @return {NodeUpdateType} The update type.
- */
- getUpdateType() {
- let updateType = NodeUpdateType.NONE;
- if ( this.scope === ScreenNode.SIZE || this.scope === ScreenNode.VIEWPORT || this.scope === ScreenNode.DPR ) {
- updateType = NodeUpdateType.RENDER;
- }
- this.updateType = updateType;
- return updateType;
- }
- /**
- * `ScreenNode` implements {@link Node#update} to retrieve viewport and size information
- * from the current renderer.
- *
- * @param {NodeFrame} frame - A reference to the current node frame.
- */
- update( { renderer } ) {
- const renderTarget = renderer.getRenderTarget();
- if ( this.scope === ScreenNode.VIEWPORT ) {
- if ( renderTarget !== null ) {
- _viewportVec.copy( renderTarget.viewport );
- } else {
- renderer.getViewport( _viewportVec );
- _viewportVec.multiplyScalar( renderer.getPixelRatio() );
- }
- } else if ( this.scope === ScreenNode.DPR ) {
- this._output.value = renderer.getPixelRatio();
- } else {
- if ( renderTarget !== null ) {
- _screenSizeVec.width = renderTarget.width;
- _screenSizeVec.height = renderTarget.height;
- } else {
- renderer.getDrawingBufferSize( _screenSizeVec );
- }
- }
- }
- setup( /*builder*/ ) {
- const scope = this.scope;
- let output = null;
- if ( scope === ScreenNode.SIZE ) {
- output = uniform( _screenSizeVec || ( _screenSizeVec = new Vector2() ) );
- } else if ( scope === ScreenNode.VIEWPORT ) {
- output = uniform( _viewportVec || ( _viewportVec = new Vector4() ) );
- } else if ( scope === ScreenNode.DPR ) {
- output = uniform( 1 );
- } else {
- output = vec2( screenCoordinate.div( screenSize ) );
- }
- this._output = output;
- return output;
- }
- generate( builder ) {
- if ( this.scope === ScreenNode.COORDINATE ) {
- let coord = builder.getFragCoord();
- if ( builder.isFlipY() ) {
- // follow webgpu standards
- const size = builder.getNodeProperties( screenSize ).outputNode.build( builder );
- coord = `${ builder.getType( 'vec2' ) }( ${ coord }.x, ${ size }.y - ${ coord }.y )`;
- }
- return coord;
- }
- return super.generate( builder );
- }
- }
- ScreenNode.COORDINATE = 'coordinate';
- ScreenNode.VIEWPORT = 'viewport';
- ScreenNode.SIZE = 'size';
- ScreenNode.UV = 'uv';
- ScreenNode.DPR = 'dpr';
- // Screen
- /**
- * TSL object that represents the current DPR.
- *
- * @tsl
- * @type {ScreenNode<float>}
- */
- const screenDPR = /*@__PURE__*/ nodeImmutable( ScreenNode, ScreenNode.DPR );
- /**
- * TSL object that represents normalized screen coordinates, unitless in `[0, 1]`.
- *
- * @tsl
- * @type {ScreenNode<vec2>}
- */
- const screenUV = /*@__PURE__*/ nodeImmutable( ScreenNode, ScreenNode.UV );
- /**
- * TSL object that represents the screen resolution in physical pixel units.
- *
- * @tsl
- * @type {ScreenNode<vec2>}
- */
- const screenSize = /*@__PURE__*/ nodeImmutable( ScreenNode, ScreenNode.SIZE );
- /**
- * TSL object that represents the current `x`/`y` pixel position on the screen in physical pixel units.
- *
- * @tsl
- * @type {ScreenNode<vec2>}
- */
- const screenCoordinate = /*@__PURE__*/ nodeImmutable( ScreenNode, ScreenNode.COORDINATE );
- // Viewport
- /**
- * TSL object that represents the viewport rectangle as `x`, `y`, `width` and `height` in physical pixel units.
- *
- * @tsl
- * @type {ScreenNode<vec4>}
- */
- const viewport = /*@__PURE__*/ nodeImmutable( ScreenNode, ScreenNode.VIEWPORT );
- /**
- * TSL object that represents the viewport resolution in physical pixel units.
- *
- * @tsl
- * @type {ScreenNode<vec2>}
- */
- const viewportSize = viewport.zw;
- /**
- * TSL object that represents the current `x`/`y` pixel position on the viewport in physical pixel units.
- *
- * @tsl
- * @type {ScreenNode<vec2>}
- */
- const viewportCoordinate = /*@__PURE__*/ screenCoordinate.sub( viewport.xy );
- /**
- * TSL object that represents normalized viewport coordinates, unitless in `[0, 1]`.
- *
- * @tsl
- * @type {ScreenNode<vec2>}
- */
- const viewportUV = /*@__PURE__*/ viewportCoordinate.div( viewportSize );
- // Deprecated
- /**
- * @deprecated since r169. Use {@link screenSize} instead.
- */
- const viewportResolution = /*@__PURE__*/ ( Fn( () => { // @deprecated, r169
- warn( 'TSL: "viewportResolution" is deprecated. Use "screenSize" instead.' );
- return screenSize;
- }, 'vec2' ).once() )();
- /**
- * TSL object that represents the current `index` value of the camera if used ArrayCamera.
- *
- * @tsl
- * @type {UniformNode<uint>}
- */
- const cameraIndex = /*@__PURE__*/ uniform( 0, 'uint' ).setName( 'u_cameraIndex' ).setGroup( sharedUniformGroup( 'cameraIndex' ) ).toVarying( 'v_cameraIndex' );
- /**
- * TSL object that represents the `near` value of the camera used for the current render.
- *
- * @tsl
- * @type {UniformNode<float>}
- */
- const cameraNear = /*@__PURE__*/ uniform( 'float' ).setName( 'cameraNear' ).setGroup( renderGroup ).onRenderUpdate( ( { camera } ) => camera.near );
- /**
- * TSL object that represents the `far` value of the camera used for the current render.
- *
- * @tsl
- * @type {UniformNode<float>}
- */
- const cameraFar = /*@__PURE__*/ uniform( 'float' ).setName( 'cameraFar' ).setGroup( renderGroup ).onRenderUpdate( ( { camera } ) => camera.far );
- /**
- * TSL object that represents the projection matrix of the camera used for the current render.
- *
- * @tsl
- * @type {UniformNode<mat4>}
- */
- const cameraProjectionMatrix = /*@__PURE__*/ ( Fn( ( { camera } ) => {
- let cameraProjectionMatrix;
- if ( camera.isArrayCamera && camera.cameras.length > 0 ) {
- const matrices = [];
- for ( const subCamera of camera.cameras ) {
- matrices.push( subCamera.projectionMatrix );
- }
- const cameraProjectionMatrices = uniformArray( matrices ).setGroup( renderGroup ).setName( 'cameraProjectionMatrices' );
- cameraProjectionMatrix = cameraProjectionMatrices.element( camera.isMultiViewCamera ? builtin( 'gl_ViewID_OVR' ) : cameraIndex ).toConst( 'cameraProjectionMatrix' );
- } else {
- cameraProjectionMatrix = uniform( 'mat4' ).setName( 'cameraProjectionMatrix' ).setGroup( renderGroup ).onRenderUpdate( ( { camera } ) => camera.projectionMatrix );
- }
- return cameraProjectionMatrix;
- } ).once() )();
- /**
- * TSL object that represents the inverse projection matrix of the camera used for the current render.
- *
- * @tsl
- * @type {UniformNode<mat4>}
- */
- const cameraProjectionMatrixInverse = /*@__PURE__*/ ( Fn( ( { camera } ) => {
- let cameraProjectionMatrixInverse;
- if ( camera.isArrayCamera && camera.cameras.length > 0 ) {
- const matrices = [];
- for ( const subCamera of camera.cameras ) {
- matrices.push( subCamera.projectionMatrixInverse );
- }
- const cameraProjectionMatricesInverse = uniformArray( matrices ).setGroup( renderGroup ).setName( 'cameraProjectionMatricesInverse' );
- cameraProjectionMatrixInverse = cameraProjectionMatricesInverse.element( camera.isMultiViewCamera ? builtin( 'gl_ViewID_OVR' ) : cameraIndex ).toConst( 'cameraProjectionMatrixInverse' );
- } else {
- cameraProjectionMatrixInverse = uniform( 'mat4' ).setName( 'cameraProjectionMatrixInverse' ).setGroup( renderGroup ).onRenderUpdate( ( { camera } ) => camera.projectionMatrixInverse );
- }
- return cameraProjectionMatrixInverse;
- } ).once() )();
- /**
- * TSL object that represents the view matrix of the camera used for the current render.
- *
- * @tsl
- * @type {UniformNode<mat4>}
- */
- const cameraViewMatrix = /*@__PURE__*/ ( Fn( ( { camera } ) => {
- let cameraViewMatrix;
- if ( camera.isArrayCamera && camera.cameras.length > 0 ) {
- const matrices = [];
- for ( const subCamera of camera.cameras ) {
- matrices.push( subCamera.matrixWorldInverse );
- }
- const cameraViewMatrices = uniformArray( matrices ).setGroup( renderGroup ).setName( 'cameraViewMatrices' );
- cameraViewMatrix = cameraViewMatrices.element( camera.isMultiViewCamera ? builtin( 'gl_ViewID_OVR' ) : cameraIndex ).toConst( 'cameraViewMatrix' );
- } else {
- cameraViewMatrix = uniform( 'mat4' ).setName( 'cameraViewMatrix' ).setGroup( renderGroup ).onRenderUpdate( ( { camera } ) => camera.matrixWorldInverse );
- }
- return cameraViewMatrix;
- } ).once() )();
- /**
- * TSL object that represents the world matrix of the camera used for the current render.
- *
- * @tsl
- * @type {UniformNode<mat4>}
- */
- const cameraWorldMatrix = /*@__PURE__*/ ( Fn( ( { camera } ) => {
- let cameraWorldMatrix;
- if ( camera.isArrayCamera && camera.cameras.length > 0 ) {
- const matrices = [];
- for ( const subCamera of camera.cameras ) {
- matrices.push( subCamera.matrixWorld );
- }
- const cameraWorldMatrices = uniformArray( matrices ).setGroup( renderGroup ).setName( 'cameraWorldMatrices' );
- cameraWorldMatrix = cameraWorldMatrices.element( camera.isMultiViewCamera ? builtin( 'gl_ViewID_OVR' ) : cameraIndex ).toConst( 'cameraWorldMatrix' );
- } else {
- cameraWorldMatrix = uniform( 'mat4' ).setName( 'cameraWorldMatrix' ).setGroup( renderGroup ).onRenderUpdate( ( { camera } ) => camera.matrixWorld );
- }
- return cameraWorldMatrix;
- } ).once() )();
- /**
- * TSL object that represents the normal matrix of the camera used for the current render.
- *
- * @tsl
- * @type {UniformNode<mat3>}
- */
- const cameraNormalMatrix = /*@__PURE__*/ ( Fn( ( { camera } ) => {
- let cameraNormalMatrix;
- if ( camera.isArrayCamera && camera.cameras.length > 0 ) {
- const matrices = [];
- for ( const subCamera of camera.cameras ) {
- matrices.push( subCamera.normalMatrix );
- }
- const cameraNormalMatrices = uniformArray( matrices ).setGroup( renderGroup ).setName( 'cameraNormalMatrices' );
- cameraNormalMatrix = cameraNormalMatrices.element( camera.isMultiViewCamera ? builtin( 'gl_ViewID_OVR' ) : cameraIndex ).toConst( 'cameraNormalMatrix' );
- } else {
- cameraNormalMatrix = uniform( 'mat3' ).setName( 'cameraNormalMatrix' ).setGroup( renderGroup ).onRenderUpdate( ( { camera } ) => camera.normalMatrix );
- }
- return cameraNormalMatrix;
- } ).once() )();
- /**
- * TSL object that represents the position in world space of the camera used for the current render.
- *
- * @tsl
- * @type {UniformNode<vec3>}
- */
- const cameraPosition = /*@__PURE__*/ ( Fn( ( { camera } ) => {
- let cameraPosition;
- if ( camera.isArrayCamera && camera.cameras.length > 0 ) {
- const positions = [];
- for ( let i = 0, l = camera.cameras.length; i < l; i ++ ) {
- positions.push( new Vector3() );
- }
- const cameraPositions = uniformArray( positions ).setGroup( renderGroup ).setName( 'cameraPositions' ).onRenderUpdate( ( { camera }, self ) => {
- const subCameras = camera.cameras;
- const array = self.array;
- for ( let i = 0, l = subCameras.length; i < l; i ++ ) {
- array[ i ].setFromMatrixPosition( subCameras[ i ].matrixWorld );
- }
- } );
- cameraPosition = cameraPositions.element( camera.isMultiViewCamera ? builtin( 'gl_ViewID_OVR' ) : cameraIndex ).toConst( 'cameraPosition' );
- } else {
- cameraPosition = uniform( new Vector3() ).setName( 'cameraPosition' ).setGroup( renderGroup ).onRenderUpdate( ( { camera }, self ) => self.value.setFromMatrixPosition( camera.matrixWorld ) );
- }
- return cameraPosition;
- } ).once() )();
- /**
- * TSL object that represents the viewport of the camera used for the current render.
- *
- * @tsl
- * @type {UniformNode<vec4>}
- */
- const cameraViewport = /*@__PURE__*/ ( Fn( ( { camera } ) => {
- let cameraViewport;
- if ( camera.isArrayCamera && camera.cameras.length > 0 ) {
- const viewports = [];
- for ( const subCamera of camera.cameras ) {
- viewports.push( subCamera.viewport );
- }
- const cameraViewports = uniformArray( viewports, 'vec4' ).setGroup( renderGroup ).setName( 'cameraViewports' );
- cameraViewport = cameraViewports.element( cameraIndex ).toConst( 'cameraViewport' );
- } else {
- // Fallback for single camera
- cameraViewport = vec4( 0, 0, screenSize.x, screenSize.y ).toConst( 'cameraViewport' );
- }
- return cameraViewport;
- } ).once() )();
- const _sphere = /*@__PURE__*/ new Sphere();
- /**
- * This node can be used to access transformation related metrics of 3D objects.
- * Depending on the selected scope, a different metric is represented as a uniform
- * in the shader. The following scopes are supported:
- *
- * - `POSITION`: The object's position in world space.
- * - `VIEW_POSITION`: The object's position in view/camera space.
- * - `DIRECTION`: The object's direction in world space.
- * - `SCALE`: The object's scale in world space.
- * - `WORLD_MATRIX`: The object's matrix in world space.
- *
- * @augments Node
- */
- class Object3DNode extends Node {
- static get type() {
- return 'Object3DNode';
- }
- /**
- * Constructs a new object 3D node.
- *
- * @param {('position'|'viewPosition'|'direction'|'scale'|'worldMatrix')} scope - The node represents a different type of transformation depending on the scope.
- * @param {?Object3D} [object3d=null] - The 3D object.
- */
- constructor( scope, object3d = null ) {
- super();
- /**
- * The node reports a different type of transformation depending on the scope.
- *
- * @type {('position'|'viewPosition'|'direction'|'scale'|'worldMatrix')}
- */
- this.scope = scope;
- /**
- * The 3D object.
- *
- * @type {?Object3D}
- * @default null
- */
- this.object3d = object3d;
- /**
- * Overwritten since this type of node is updated per object.
- *
- * @type {string}
- * @default 'object'
- */
- this.updateType = NodeUpdateType.OBJECT;
- /**
- * Holds the value of the node as a uniform.
- *
- * @type {UniformNode}
- */
- this.uniformNode = new UniformNode( null );
- }
- /**
- * Overwritten since the node type is inferred from the scope.
- *
- * @return {('mat4'|'vec3'|'float')} The node type.
- */
- getNodeType() {
- const scope = this.scope;
- if ( scope === Object3DNode.WORLD_MATRIX ) {
- return 'mat4';
- } else if ( scope === Object3DNode.POSITION || scope === Object3DNode.VIEW_POSITION || scope === Object3DNode.DIRECTION || scope === Object3DNode.SCALE ) {
- return 'vec3';
- } else if ( scope === Object3DNode.RADIUS ) {
- return 'float';
- }
- }
- /**
- * Updates the uniform value depending on the scope.
- *
- * @param {NodeFrame} frame - The current node frame.
- */
- update( frame ) {
- const object = this.object3d;
- const uniformNode = this.uniformNode;
- const scope = this.scope;
- if ( scope === Object3DNode.WORLD_MATRIX ) {
- uniformNode.value = object.matrixWorld;
- } else if ( scope === Object3DNode.POSITION ) {
- uniformNode.value = uniformNode.value || new Vector3();
- uniformNode.value.setFromMatrixPosition( object.matrixWorld );
- } else if ( scope === Object3DNode.SCALE ) {
- uniformNode.value = uniformNode.value || new Vector3();
- uniformNode.value.setFromMatrixScale( object.matrixWorld );
- } else if ( scope === Object3DNode.DIRECTION ) {
- uniformNode.value = uniformNode.value || new Vector3();
- object.getWorldDirection( uniformNode.value );
- } else if ( scope === Object3DNode.VIEW_POSITION ) {
- const camera = frame.camera;
- uniformNode.value = uniformNode.value || new Vector3();
- uniformNode.value.setFromMatrixPosition( object.matrixWorld );
- uniformNode.value.applyMatrix4( camera.matrixWorldInverse );
- } else if ( scope === Object3DNode.RADIUS ) {
- const geometry = frame.object.geometry;
- if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere();
- _sphere.copy( geometry.boundingSphere ).applyMatrix4( object.matrixWorld );
- uniformNode.value = _sphere.radius;
- }
- }
- /**
- * Generates the code snippet of the uniform node. The node type of the uniform
- * node also depends on the selected scope.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {string} The generated code snippet.
- */
- generate( builder ) {
- const scope = this.scope;
- if ( scope === Object3DNode.WORLD_MATRIX ) {
- this.uniformNode.nodeType = 'mat4';
- } else if ( scope === Object3DNode.POSITION || scope === Object3DNode.VIEW_POSITION || scope === Object3DNode.DIRECTION || scope === Object3DNode.SCALE ) {
- this.uniformNode.nodeType = 'vec3';
- } else if ( scope === Object3DNode.RADIUS ) {
- this.uniformNode.nodeType = 'float';
- }
- return this.uniformNode.build( builder );
- }
- serialize( data ) {
- super.serialize( data );
- data.scope = this.scope;
- }
- deserialize( data ) {
- super.deserialize( data );
- this.scope = data.scope;
- }
- }
- Object3DNode.WORLD_MATRIX = 'worldMatrix';
- Object3DNode.POSITION = 'position';
- Object3DNode.SCALE = 'scale';
- Object3DNode.VIEW_POSITION = 'viewPosition';
- Object3DNode.DIRECTION = 'direction';
- Object3DNode.RADIUS = 'radius';
- /**
- * TSL function for creating an object 3D node that represents the object's direction in world space.
- *
- * @tsl
- * @function
- * @param {?Object3D} [object3d] - The 3D object.
- * @returns {Object3DNode<vec3>}
- */
- const objectDirection = /*@__PURE__*/ nodeProxy( Object3DNode, Object3DNode.DIRECTION ).setParameterLength( 1 );
- /**
- * TSL function for creating an object 3D node that represents the object's world matrix.
- *
- * @tsl
- * @function
- * @param {?Object3D} [object3d] - The 3D object.
- * @returns {Object3DNode<mat4>}
- */
- const objectWorldMatrix = /*@__PURE__*/ nodeProxy( Object3DNode, Object3DNode.WORLD_MATRIX ).setParameterLength( 1 );
- /**
- * TSL function for creating an object 3D node that represents the object's position in world space.
- *
- * @tsl
- * @function
- * @param {?Object3D} [object3d] - The 3D object.
- * @returns {Object3DNode<vec3>}
- */
- const objectPosition = /*@__PURE__*/ nodeProxy( Object3DNode, Object3DNode.POSITION ).setParameterLength( 1 );
- /**
- * TSL function for creating an object 3D node that represents the object's scale in world space.
- *
- * @tsl
- * @function
- * @param {?Object3D} [object3d] - The 3D object.
- * @returns {Object3DNode<vec3>}
- */
- const objectScale = /*@__PURE__*/ nodeProxy( Object3DNode, Object3DNode.SCALE ).setParameterLength( 1 );
- /**
- * TSL function for creating an object 3D node that represents the object's position in view/camera space.
- *
- * @tsl
- * @function
- * @param {?Object3D} [object3d] - The 3D object.
- * @returns {Object3DNode<vec3>}
- */
- const objectViewPosition = /*@__PURE__*/ nodeProxy( Object3DNode, Object3DNode.VIEW_POSITION ).setParameterLength( 1 );
- /**
- * TSL function for creating an object 3D node that represents the object's radius.
- *
- * @tsl
- * @function
- * @param {?Object3D} [object3d] - The 3D object.
- * @returns {Object3DNode<float>}
- */
- const objectRadius = /*@__PURE__*/ nodeProxy( Object3DNode, Object3DNode.RADIUS ).setParameterLength( 1 );
- /**
- * This type of node is a specialized version of `Object3DNode`
- * with larger set of model related metrics. Unlike `Object3DNode`,
- * `ModelNode` extracts the reference to the 3D object from the
- * current node frame state.
- *
- * @augments Object3DNode
- */
- class ModelNode extends Object3DNode {
- static get type() {
- return 'ModelNode';
- }
- /**
- * Constructs a new object model node.
- *
- * @param {('position'|'viewPosition'|'direction'|'scale'|'worldMatrix')} scope - The node represents a different type of transformation depending on the scope.
- */
- constructor( scope ) {
- super( scope );
- }
- /**
- * Extracts the model reference from the frame state and then
- * updates the uniform value depending on the scope.
- *
- * @param {NodeFrame} frame - The current node frame.
- */
- update( frame ) {
- this.object3d = frame.object;
- super.update( frame );
- }
- }
- /**
- * TSL object that represents the object's direction in world space.
- *
- * @tsl
- * @type {ModelNode<vec3>}
- */
- const modelDirection = /*@__PURE__*/ nodeImmutable( ModelNode, ModelNode.DIRECTION );
- /**
- * TSL object that represents the object's world matrix.
- *
- * @tsl
- * @type {ModelNode<mat4>}
- */
- const modelWorldMatrix = /*@__PURE__*/ nodeImmutable( ModelNode, ModelNode.WORLD_MATRIX );
- /**
- * TSL object that represents the object's position in world space.
- *
- * @tsl
- * @type {ModelNode<vec3>}
- */
- const modelPosition = /*@__PURE__*/ nodeImmutable( ModelNode, ModelNode.POSITION );
- /**
- * TSL object that represents the object's scale in world space.
- *
- * @tsl
- * @type {ModelNode<vec3>}
- */
- const modelScale = /*@__PURE__*/ nodeImmutable( ModelNode, ModelNode.SCALE );
- /**
- * TSL object that represents the object's position in view/camera space.
- *
- * @tsl
- * @type {ModelNode<vec3>}
- */
- const modelViewPosition = /*@__PURE__*/ nodeImmutable( ModelNode, ModelNode.VIEW_POSITION );
- /**
- * TSL object that represents the object's radius.
- *
- * @tsl
- * @type {ModelNode<float>}
- */
- const modelRadius = /*@__PURE__*/ nodeImmutable( ModelNode, ModelNode.RADIUS );
- /**
- * TSL object that represents the object's normal matrix.
- *
- * @tsl
- * @type {UniformNode<mat3>}
- */
- const modelNormalMatrix = /*@__PURE__*/ uniform( new Matrix3() ).onObjectUpdate( ( { object }, self ) => self.value.getNormalMatrix( object.matrixWorld ) );
- /**
- * TSL object that represents the object's inverse world matrix.
- *
- * @tsl
- * @type {UniformNode<mat4>}
- */
- const modelWorldMatrixInverse = /*@__PURE__*/ uniform( new Matrix4() ).onObjectUpdate( ( { object }, self ) => self.value.copy( object.matrixWorld ).invert() );
- /**
- * TSL object that represents the object's model view matrix.
- *
- * @tsl
- * @type {Node<mat4>}
- */
- const modelViewMatrix = /*@__PURE__*/ ( Fn( ( builder ) => {
- return builder.context.modelViewMatrix || mediumpModelViewMatrix;
- } ).once() )().toVar( 'modelViewMatrix' );
- // GPU Precision
- /**
- * TSL object that represents the object's model view in `mediump` precision.
- *
- * @tsl
- * @type {Node<mat4>}
- */
- const mediumpModelViewMatrix = /*@__PURE__*/ cameraViewMatrix.mul( modelWorldMatrix );
- // CPU Precision
- /**
- * TSL object that represents the object's model view in `highp` precision
- * which is achieved by computing the matrix in JS and not in the shader.
- *
- * @tsl
- * @type {Node<mat4>}
- */
- const highpModelViewMatrix = /*@__PURE__*/ ( Fn( ( builder ) => {
- builder.context.isHighPrecisionModelViewMatrix = true;
- return uniform( 'mat4' ).onObjectUpdate( ( { object, camera } ) => {
- return object.modelViewMatrix.multiplyMatrices( camera.matrixWorldInverse, object.matrixWorld );
- } );
- } ).once() )().toVar( 'highpModelViewMatrix' );
- /**
- * TSL object that represents the object's model normal view in `highp` precision
- * which is achieved by computing the matrix in JS and not in the shader.
- *
- * @tsl
- * @type {Node<mat3>}
- */
- const highpModelNormalViewMatrix = /*@__PURE__*/ ( Fn( ( builder ) => {
- const isHighPrecisionModelViewMatrix = builder.context.isHighPrecisionModelViewMatrix;
- return uniform( 'mat3' ).onObjectUpdate( ( { object, camera } ) => {
- if ( isHighPrecisionModelViewMatrix !== true ) {
- object.modelViewMatrix.multiplyMatrices( camera.matrixWorldInverse, object.matrixWorld );
- }
- return object.normalMatrix.getNormalMatrix( object.modelViewMatrix );
- } );
- } ).once() )().toVar( 'highpModelNormalViewMatrix' );
- /**
- * TSL object that represents the position attribute of the current rendered object.
- *
- * @tsl
- * @type {AttributeNode<vec3>}
- */
- const positionGeometry = /*@__PURE__*/ attribute( 'position', 'vec3' );
- /**
- * TSL object that represents the vertex position in local space of the current rendered object.
- *
- * @tsl
- * @type {AttributeNode<vec3>}
- */
- const positionLocal = /*@__PURE__*/ positionGeometry.toVarying( 'positionLocal' );
- /**
- * TSL object that represents the previous vertex position in local space of the current rendered object.
- * Used in context of {@link VelocityNode} for rendering motion vectors.
- *
- * @tsl
- * @type {AttributeNode<vec3>}
- */
- const positionPrevious = /*@__PURE__*/ positionGeometry.toVarying( 'positionPrevious' );
- /**
- * TSL object that represents the vertex position in world space of the current rendered object.
- *
- * @tsl
- * @type {VaryingNode<vec3>}
- */
- const positionWorld = /*@__PURE__*/ ( Fn( ( builder ) => {
- return modelWorldMatrix.mul( positionLocal ).xyz.toVarying( builder.getSubBuildProperty( 'v_positionWorld' ) );
- }, 'vec3' ).once( [ 'POSITION' ] ) )();
- /**
- * TSL object that represents the position world direction of the current rendered object.
- *
- * @tsl
- * @type {Node<vec3>}
- */
- const positionWorldDirection = /*@__PURE__*/ ( Fn( () => {
- const vertexPWD = positionLocal.transformDirection( modelWorldMatrix ).toVarying( 'v_positionWorldDirection' );
- return vertexPWD.normalize().toVar( 'positionWorldDirection' );
- }, 'vec3' ).once( [ 'POSITION' ] ) )();
- /**
- * TSL object that represents the vertex position in view space of the current rendered object.
- *
- * @tsl
- * @type {VaryingNode<vec3>}
- */
- const positionView = /*@__PURE__*/ ( Fn( ( builder ) => {
- return builder.context.setupPositionView().toVarying( 'v_positionView' );
- }, 'vec3' ).once( [ 'POSITION' ] ) )();
- /**
- * TSL object that represents the position view direction of the current rendered object.
- *
- * @tsl
- * @type {VaryingNode<vec3>}
- */
- const positionViewDirection = /*@__PURE__*/ ( Fn( ( builder ) => {
- let output;
- if ( builder.camera.isOrthographicCamera ) {
- output = vec3( 0, 0, 1 );
- } else {
- output = positionView.negate().toVarying( 'v_positionViewDirection' ).normalize();
- }
- return output.toVar( 'positionViewDirection' );
- }, 'vec3' ).once( [ 'POSITION' ] ) )();
- /**
- * This node can be used to evaluate whether a primitive is front or back facing.
- *
- * @augments Node
- */
- class FrontFacingNode extends Node {
- static get type() {
- return 'FrontFacingNode';
- }
- /**
- * Constructs a new front facing node.
- */
- constructor() {
- super( 'bool' );
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isFrontFacingNode = true;
- }
- generate( builder ) {
- if ( builder.shaderStage !== 'fragment' ) return 'true';
- //
- const { material } = builder;
- if ( material.side === BackSide ) {
- return 'false';
- }
- return builder.getFrontFacing();
- }
- }
- /**
- * TSL object that represents whether a primitive is front or back facing
- *
- * @tsl
- * @type {FrontFacingNode<bool>}
- */
- const frontFacing = /*@__PURE__*/ nodeImmutable( FrontFacingNode );
- /**
- * TSL object that represents the front facing status as a number instead of a bool.
- * `1` means front facing, `-1` means back facing.
- *
- * @tsl
- * @type {Node<float>}
- */
- const faceDirection = /*@__PURE__*/ float( frontFacing ).mul( 2.0 ).sub( 1.0 );
- /**
- * Converts a direction vector to a face direction vector based on the material's side.
- *
- * If the material is set to `BackSide`, the direction is inverted.
- * If the material is set to `DoubleSide`, the direction is multiplied by `faceDirection`.
- *
- * @tsl
- * @param {Node<vec3>} direction - The direction vector to convert.
- * @returns {Node<vec3>} The converted direction vector.
- */
- const directionToFaceDirection = /*@__PURE__*/ Fn( ( [ direction ], { material } ) => {
- const side = material.side;
- if ( side === BackSide ) {
- direction = direction.mul( -1 );
- } else if ( side === DoubleSide ) {
- direction = direction.mul( faceDirection );
- }
- return direction;
- } );
- /**
- * TSL object that represents the normal attribute of the current rendered object in local space.
- *
- * @tsl
- * @type {Node<vec3>}
- */
- const normalGeometry = /*@__PURE__*/ attribute( 'normal', 'vec3' );
- /**
- * TSL object that represents the vertex normal of the current rendered object in local space.
- *
- * @tsl
- * @type {Node<vec3>}
- */
- const normalLocal = /*@__PURE__*/ ( Fn( ( builder ) => {
- if ( builder.geometry.hasAttribute( 'normal' ) === false ) {
- warn( 'TSL: Vertex attribute "normal" not found on geometry.' );
- return vec3( 0, 1, 0 );
- }
- return normalGeometry;
- }, 'vec3' ).once() )().toVar( 'normalLocal' );
- /**
- * TSL object that represents the flat vertex normal of the current rendered object in view space.
- *
- * @tsl
- * @type {Node<vec3>}
- */
- const normalFlat = /*@__PURE__*/ positionView.dFdx().cross( positionView.dFdy() ).normalize().toVar( 'normalFlat' );
- /**
- * TSL object that represents the vertex normal of the current rendered object in view space.
- *
- * @tsl
- * @type {Node<vec3>}
- */
- const normalViewGeometry = /*@__PURE__*/ ( Fn( ( builder ) => {
- let node;
- if ( builder.material.flatShading === true ) {
- node = normalFlat;
- } else {
- node = transformNormalToView( normalLocal ).toVarying( 'v_normalViewGeometry' ).normalize();
- }
- return node;
- }, 'vec3' ).once() )().toVar( 'normalViewGeometry' );
- /**
- * TSL object that represents the vertex normal of the current rendered object in world space.
- *
- * @tsl
- * @type {Node<vec3>}
- */
- const normalWorldGeometry = /*@__PURE__*/ ( Fn( ( builder ) => {
- let normal = normalViewGeometry.transformDirection( cameraViewMatrix );
- if ( builder.material.flatShading !== true ) {
- normal = normal.toVarying( 'v_normalWorldGeometry' );
- }
- return normal.normalize().toVar( 'normalWorldGeometry' );
- }, 'vec3' ).once() )();
- /**
- * TSL object that represents the vertex normal of the current rendered object in view space.
- *
- * @tsl
- * @type {Node<vec3>}
- */
- const normalView = /*@__PURE__*/ ( Fn( ( { subBuildFn, material, context } ) => {
- let node;
- if ( subBuildFn === 'NORMAL' || subBuildFn === 'VERTEX' ) {
- node = normalViewGeometry;
- if ( material.flatShading !== true ) {
- node = directionToFaceDirection( node );
- }
- } else {
- // Use getUV context to avoid side effects from nodes overwriting getUV in the context (e.g. EnvironmentNode)
- node = context.setupNormal().context( { getUV: null } );
- }
- return node;
- }, 'vec3' ).once( [ 'NORMAL', 'VERTEX' ] ) )().toVar( 'normalView' );
- /**
- * TSL object that represents the vertex normal of the current rendered object in world space.
- *
- * @tsl
- * @type {Node<vec3>}
- */
- const normalWorld = /*@__PURE__*/ normalView.transformDirection( cameraViewMatrix ).toVar( 'normalWorld' );
- /**
- * TSL object that represents the clearcoat vertex normal of the current rendered object in view space.
- *
- * @tsl
- * @type {Node<vec3>}
- */
- const clearcoatNormalView = /*@__PURE__*/ ( Fn( ( { subBuildFn, context } ) => {
- let node;
- if ( subBuildFn === 'NORMAL' || subBuildFn === 'VERTEX' ) {
- node = normalView;
- } else {
- // Use getUV context to avoid side effects from nodes overwriting getUV in the context (e.g. EnvironmentNode)
- node = context.setupClearcoatNormal().context( { getUV: null } );
- }
- return node;
- }, 'vec3' ).once( [ 'NORMAL', 'VERTEX' ] ) )().toVar( 'clearcoatNormalView' );
- /**
- * Transforms the normal with the given matrix.
- *
- * @tsl
- * @function
- * @param {Node<vec3>} normal - The normal.
- * @param {Node<mat3>} [matrix=modelWorldMatrix] - The matrix.
- * @return {Node<vec3>} The transformed normal.
- */
- const transformNormal = /*@__PURE__*/ Fn( ( [ normal, matrix = modelWorldMatrix ] ) => {
- const m = mat3( matrix );
- const transformedNormal = normal.div( vec3( m[ 0 ].dot( m[ 0 ] ), m[ 1 ].dot( m[ 1 ] ), m[ 2 ].dot( m[ 2 ] ) ) );
- return m.mul( transformedNormal ).xyz;
- } );
- /**
- * Transforms the given normal from local to view space.
- *
- * @tsl
- * @function
- * @param {Node<vec3>} normal - The normal.
- * @param {NodeBuilder} builder - The current node builder.
- * @return {Node<vec3>} The transformed normal.
- */
- const transformNormalToView = /*@__PURE__*/ Fn( ( [ normal ], builder ) => {
- const modelNormalViewMatrix = builder.context.modelNormalViewMatrix;
- if ( modelNormalViewMatrix ) {
- return modelNormalViewMatrix.transformDirection( normal );
- }
- //
- const transformedNormal = modelNormalMatrix.mul( normal );
- return cameraViewMatrix.transformDirection( transformedNormal );
- } );
- // Deprecated
- /**
- * TSL object that represents the transformed vertex normal of the current rendered object in view space.
- *
- * @tsl
- * @type {Node<vec3>}
- * @deprecated since r178. Use `normalView` instead.
- */
- const transformedNormalView = ( Fn( () => { // @deprecated, r177
- warn( 'TSL: "transformedNormalView" is deprecated. Use "normalView" instead.' );
- return normalView;
- } ).once( [ 'NORMAL', 'VERTEX' ] ) )();
- /**
- * TSL object that represents the transformed vertex normal of the current rendered object in world space.
- *
- * @tsl
- * @type {Node<vec3>}
- * @deprecated since r178. Use `normalWorld` instead.
- */
- const transformedNormalWorld = ( Fn( () => { // @deprecated, r177
- warn( 'TSL: "transformedNormalWorld" is deprecated. Use "normalWorld" instead.' );
- return normalWorld;
- } ).once( [ 'NORMAL', 'VERTEX' ] ) )();
- /**
- * TSL object that represents the transformed clearcoat vertex normal of the current rendered object in view space.
- *
- * @tsl
- * @type {Node<vec3>}
- * @deprecated since r178. Use `clearcoatNormalView` instead.
- */
- const transformedClearcoatNormalView = ( Fn( () => { // @deprecated, r177
- warn( 'TSL: "transformedClearcoatNormalView" is deprecated. Use "clearcoatNormalView" instead.' );
- return clearcoatNormalView;
- } ).once( [ 'NORMAL', 'VERTEX' ] ) )();
- const _e1$1 = /*@__PURE__*/ new Euler();
- const _m1$1 = /*@__PURE__*/ new Matrix4();
- /**
- * TSL object that represents the refraction ratio of the material used for rendering the current object.
- *
- * @tsl
- * @type {UniformNode<float>}
- */
- const materialRefractionRatio = /*@__PURE__*/ uniform( 0 ).onReference( ( { material } ) => material ).onObjectUpdate( ( { material } ) => material.refractionRatio );
- /**
- * TSL object that represents the intensity of environment maps of PBR materials.
- * When `material.envMap` is set, the value is `material.envMapIntensity` otherwise `scene.environmentIntensity`.
- *
- * @tsl
- * @type {Node<float>}
- */
- const materialEnvIntensity = /*@__PURE__*/ uniform( 1 ).onReference( ( { material } ) => material ).onObjectUpdate( function ( { material, scene } ) {
- return material.envMap ? material.envMapIntensity : scene.environmentIntensity;
- } );
- /**
- * TSL object that represents the rotation of environment maps.
- * When `material.envMap` is set, the value is `material.envMapRotation`. `scene.environmentRotation` controls the
- * rotation of `scene.environment` instead.
- *
- * @tsl
- * @type {Node<mat4>}
- */
- const materialEnvRotation = /*@__PURE__*/ uniform( new Matrix4() ).onReference( function ( frame ) {
- return frame.material;
- } ).onObjectUpdate( function ( { material, scene } ) {
- const rotation = ( scene.environment !== null && material.envMap === null ) ? scene.environmentRotation : material.envMapRotation;
- if ( rotation ) {
- _e1$1.copy( rotation );
- _m1$1.makeRotationFromEuler( _e1$1 );
- } else {
- _m1$1.identity();
- }
- return _m1$1;
- } );
- /**
- * The reflect vector in view space.
- *
- * @tsl
- * @type {Node<vec3>}
- */
- const reflectView = /*@__PURE__*/ positionViewDirection.negate().reflect( normalView );
- /**
- * The refract vector in view space.
- *
- * @tsl
- * @type {Node<vec3>}
- */
- const refractView = /*@__PURE__*/ positionViewDirection.negate().refract( normalView, materialRefractionRatio );
- /**
- * Used for sampling cube maps when using cube reflection mapping.
- *
- * @tsl
- * @type {Node<vec3>}
- */
- const reflectVector = /*@__PURE__*/ reflectView.transformDirection( cameraViewMatrix ).toVar( 'reflectVector' );
- /**
- * Used for sampling cube maps when using cube refraction mapping.
- *
- * @tsl
- * @type {Node<vec3>}
- */
- const refractVector = /*@__PURE__*/ refractView.transformDirection( cameraViewMatrix ).toVar( 'reflectVector' );
- const EmptyTexture = /*@__PURE__*/ new CubeTexture();
- /**
- * This type of uniform node represents a cube texture.
- *
- * @augments TextureNode
- */
- class CubeTextureNode extends TextureNode {
- static get type() {
- return 'CubeTextureNode';
- }
- /**
- * Constructs a new cube texture node.
- *
- * @param {CubeTexture} value - The cube texture.
- * @param {?Node<vec3>} [uvNode=null] - The uv node.
- * @param {?Node<int>} [levelNode=null] - The level node.
- * @param {?Node<float>} [biasNode=null] - The bias node.
- */
- constructor( value, uvNode = null, levelNode = null, biasNode = null ) {
- super( value, uvNode, levelNode, biasNode );
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isCubeTextureNode = true;
- }
- /**
- * Overwrites the default implementation to return the appropriate cube texture type.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {string} The input type.
- */
- getInputType( /*builder*/ ) {
- if ( this.value.isDepthTexture === true ) {
- return 'cubeDepthTexture';
- }
- return 'cubeTexture';
- }
- /**
- * Returns a default uvs based on the mapping type of the cube texture.
- *
- * @return {Node<vec3>} The default uv attribute.
- */
- getDefaultUV() {
- const texture = this.value;
- if ( texture.mapping === CubeReflectionMapping ) {
- return reflectVector;
- } else if ( texture.mapping === CubeRefractionMapping ) {
- return refractVector;
- } else {
- error( 'CubeTextureNode: Mapping "%s" not supported.', texture.mapping );
- return vec3( 0, 0, 0 );
- }
- }
- /**
- * Overwritten with an empty implementation since the `updateMatrix` flag is ignored
- * for cube textures. The uv transformation matrix is not applied to cube textures.
- *
- * @param {boolean} value - The update toggle.
- */
- setUpdateMatrix( /*updateMatrix*/ ) { } // Ignore .updateMatrix for CubeTextureNode
- /**
- * Setups the uv node. Depending on the backend as well as the texture type, it might be necessary
- * to modify the uv node for correct sampling.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @param {Node} uvNode - The uv node to setup.
- * @return {Node} The updated uv node.
- */
- setupUV( builder, uvNode ) {
- const texture = this.value;
- // Depth textures (shadow maps) - no environment rotation, Y flip for WebGPU
- if ( texture.isDepthTexture === true ) {
- if ( builder.renderer.coordinateSystem === WebGPUCoordinateSystem ) {
- return vec3( uvNode.x, uvNode.y.negate(), uvNode.z );
- }
- return uvNode;
- }
- if ( builder.renderer.coordinateSystem === WebGPUCoordinateSystem || ! texture.isRenderTargetTexture ) {
- uvNode = vec3( uvNode.x.negate(), uvNode.yz );
- }
- return materialEnvRotation.mul( uvNode );
- }
- /**
- * Generates the uv code snippet.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @param {Node} cubeUV - The uv node to generate code for.
- * @return {string} The generated code snippet.
- */
- generateUV( builder, cubeUV ) {
- return cubeUV.build( builder, this.sampler === true ? 'vec3' : 'ivec3' );
- }
- }
- /**
- * TSL function for creating a cube texture node.
- *
- * @tsl
- * @function
- * @param {CubeTexture} value - The cube texture.
- * @param {?Node<vec3>} [uvNode=null] - The uv node.
- * @param {?Node<int>} [levelNode=null] - The level node.
- * @param {?Node<float>} [biasNode=null] - The bias node.
- * @returns {CubeTextureNode}
- */
- const cubeTextureBase = /*@__PURE__*/ nodeProxy( CubeTextureNode ).setParameterLength( 1, 4 ).setName( 'cubeTexture' );
- /**
- * TSL function for creating a cube texture uniform node.
- *
- * @tsl
- * @function
- * @param {?(CubeTexture|CubeTextureNode)} [value=EmptyTexture] - The cube texture.
- * @param {?Node<vec3>} [uvNode=null] - The uv node.
- * @param {?Node<int>} [levelNode=null] - The level node.
- * @param {?Node<float>} [biasNode=null] - The bias node.
- * @returns {CubeTextureNode}
- */
- const cubeTexture = ( value = EmptyTexture, uvNode = null, levelNode = null, biasNode = null ) => {
- let textureNode;
- if ( value && value.isCubeTextureNode === true ) {
- textureNode = nodeObject( value.clone() );
- textureNode.referenceNode = value; // Ensure the reference is set to the original node
- if ( uvNode !== null ) textureNode.uvNode = nodeObject( uvNode );
- if ( levelNode !== null ) textureNode.levelNode = nodeObject( levelNode );
- if ( biasNode !== null ) textureNode.biasNode = nodeObject( biasNode );
- } else {
- textureNode = cubeTextureBase( value, uvNode, levelNode, biasNode );
- }
- return textureNode;
- };
- /**
- * TSL function for creating a uniform cube texture node.
- *
- * @tsl
- * @function
- * @param {?CubeTexture} [value=EmptyTexture] - The cube texture.
- * @returns {CubeTextureNode}
- */
- const uniformCubeTexture = ( value = EmptyTexture ) => cubeTextureBase( value );
- // TODO: Avoid duplicated code and use only ReferenceBaseNode or ReferenceNode
- /**
- * This class is only relevant if the referenced property is array-like.
- * In this case, `ReferenceElementNode` allows to refer to a specific
- * element inside the data structure via an index.
- *
- * @augments ArrayElementNode
- */
- class ReferenceElementNode extends ArrayElementNode {
- static get type() {
- return 'ReferenceElementNode';
- }
- /**
- * Constructs a new reference element node.
- *
- * @param {?ReferenceNode} referenceNode - The reference node.
- * @param {Node} indexNode - The index node that defines the element access.
- */
- constructor( referenceNode, indexNode ) {
- super( referenceNode, indexNode );
- /**
- * Similar to {@link ReferenceNode#reference}, an additional
- * property references to the current node.
- *
- * @type {?ReferenceNode}
- * @default null
- */
- this.referenceNode = referenceNode;
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isReferenceElementNode = true;
- }
- /**
- * This method is overwritten since the node type is inferred from
- * the uniform type of the reference node.
- *
- * @return {string} The node type.
- */
- getNodeType() {
- return this.referenceNode.uniformType;
- }
- generate( builder ) {
- const snippet = super.generate( builder );
- const arrayType = this.referenceNode.getNodeType();
- const elementType = this.getNodeType();
- return builder.format( snippet, arrayType, elementType );
- }
- }
- /**
- * This type of node establishes a reference to a property of another object.
- * In this way, the value of the node is automatically linked to the value of
- * referenced object. Reference nodes internally represent the linked value
- * as a uniform.
- *
- * @augments Node
- */
- class ReferenceNode extends Node {
- static get type() {
- return 'ReferenceNode';
- }
- /**
- * Constructs a new reference node.
- *
- * @param {string} property - The name of the property the node refers to.
- * @param {string} uniformType - The uniform type that should be used to represent the property value.
- * @param {?Object} [object=null] - The object the property belongs to.
- * @param {?number} [count=null] - When the linked property is an array-like, this parameter defines its length.
- */
- constructor( property, uniformType, object = null, count = null ) {
- super();
- /**
- * The name of the property the node refers to.
- *
- * @type {string}
- */
- this.property = property;
- /**
- * The uniform type that should be used to represent the property value.
- *
- * @type {string}
- */
- this.uniformType = uniformType;
- /**
- * The object the property belongs to.
- *
- * @type {?Object}
- * @default null
- */
- this.object = object;
- /**
- * When the linked property is an array, this parameter defines its length.
- *
- * @type {?number}
- * @default null
- */
- this.count = count;
- /**
- * The property name might have dots so nested properties can be referred.
- * The hierarchy of the names is stored inside this array.
- *
- * @type {Array<string>}
- */
- this.properties = property.split( '.' );
- /**
- * Points to the current referred object. This property exists next to {@link ReferenceNode#object}
- * since the final reference might be updated from calling code.
- *
- * @type {?Object}
- * @default null
- */
- this.reference = object;
- /**
- * The uniform node that holds the value of the reference node.
- *
- * @type {UniformNode}
- * @default null
- */
- this.node = null;
- /**
- * The uniform group of the internal uniform.
- *
- * @type {UniformGroupNode}
- * @default null
- */
- this.group = null;
- /**
- * An optional label of the internal uniform node.
- *
- * @type {?string}
- * @default null
- */
- this.name = null;
- /**
- * Overwritten since reference nodes are updated per object.
- *
- * @type {string}
- * @default 'object'
- */
- this.updateType = NodeUpdateType.OBJECT;
- }
- /**
- * When the referred property is array-like, this method can be used
- * to access elements via an index node.
- *
- * @param {IndexNode} indexNode - indexNode.
- * @return {ReferenceElementNode} A reference to an element.
- */
- element( indexNode ) {
- return nodeObject( new ReferenceElementNode( this, nodeObject( indexNode ) ) );
- }
- /**
- * Sets the uniform group for this reference node.
- *
- * @param {UniformGroupNode} group - The uniform group to set.
- * @return {ReferenceNode} A reference to this node.
- */
- setGroup( group ) {
- this.group = group;
- return this;
- }
- /**
- * Sets the name for the internal uniform.
- *
- * @param {string} name - The label to set.
- * @return {ReferenceNode} A reference to this node.
- */
- setName( name ) {
- this.name = name;
- return this;
- }
- /**
- * Sets the label for the internal uniform.
- *
- * @deprecated
- * @param {string} name - The label to set.
- * @return {ReferenceNode} A reference to this node.
- */
- label( name ) {
- warn( 'TSL: "label()" has been deprecated. Use "setName()" instead.' ); // @deprecated r179
- return this.setName( name );
- }
- /**
- * Sets the node type which automatically defines the internal
- * uniform type.
- *
- * @param {string} uniformType - The type to set.
- */
- setNodeType( uniformType ) {
- let node = null;
- if ( this.count !== null ) {
- node = buffer( null, uniformType, this.count );
- } else if ( Array.isArray( this.getValueFromReference() ) ) {
- node = uniformArray( null, uniformType );
- } else if ( uniformType === 'texture' ) {
- node = texture( null );
- } else if ( uniformType === 'cubeTexture' ) {
- node = cubeTexture( null );
- } else {
- node = uniform( null, uniformType );
- }
- if ( this.group !== null ) {
- node.setGroup( this.group );
- }
- if ( this.name !== null ) node.setName( this.name );
- this.node = node;
- }
- /**
- * This method is overwritten since the node type is inferred from
- * the type of the reference node.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {string} The node type.
- */
- getNodeType( builder ) {
- if ( this.node === null ) {
- this.updateReference( builder );
- this.updateValue();
- }
- return this.node.getNodeType( builder );
- }
- /**
- * Returns the property value from the given referred object.
- *
- * @param {Object} [object=this.reference] - The object to retrieve the property value from.
- * @return {any} The value.
- */
- getValueFromReference( object = this.reference ) {
- const { properties } = this;
- let value = object[ properties[ 0 ] ];
- for ( let i = 1; i < properties.length; i ++ ) {
- value = value[ properties[ i ] ];
- }
- return value;
- }
- /**
- * Allows to update the reference based on the given state. The state is only
- * evaluated {@link ReferenceNode#object} is not set.
- *
- * @param {(NodeFrame|NodeBuilder)} state - The current state.
- * @return {Object} The updated reference.
- */
- updateReference( state ) {
- this.reference = this.object !== null ? this.object : state.object;
- return this.reference;
- }
- /**
- * The output of the reference node is the internal uniform node.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {UniformNode} The output node.
- */
- setup( /* builder */ ) {
- this.updateValue();
- return this.node;
- }
- /**
- * Overwritten to update the internal uniform value.
- *
- * @param {NodeFrame} frame - A reference to the current node frame.
- */
- update( /*frame*/ ) {
- this.updateValue();
- }
- /**
- * Retrieves the value from the referred object property and uses it
- * to updated the internal uniform.
- */
- updateValue() {
- if ( this.node === null ) this.setNodeType( this.uniformType );
- const value = this.getValueFromReference();
- if ( Array.isArray( value ) ) {
- this.node.array = value;
- } else {
- this.node.value = value;
- }
- }
- }
- /**
- * TSL function for creating a reference node.
- *
- * @tsl
- * @function
- * @param {string} name - The name of the property the node refers to.
- * @param {string} type - The uniform type that should be used to represent the property value.
- * @param {?Object} [object] - The object the property belongs to.
- * @returns {ReferenceNode}
- */
- const reference = ( name, type, object ) => nodeObject( new ReferenceNode( name, type, object ) );
- /**
- * TSL function for creating a reference node. Use this function if you want need a reference
- * to an array-like property that should be represented as a uniform buffer.
- *
- * @tsl
- * @function
- * @param {string} name - The name of the property the node refers to.
- * @param {string} type - The uniform type that should be used to represent the property value.
- * @param {number} count - The number of value inside the array-like object.
- * @param {Object} object - An array-like object the property belongs to.
- * @returns {ReferenceNode}
- */
- const referenceBuffer = ( name, type, count, object ) => nodeObject( new ReferenceNode( name, type, object, count ) );
- /**
- * This node is a special type of reference node which is intended
- * for linking material properties with node values.
- * ```js
- * const opacityNode = materialReference( 'opacity', 'float', material );
- * ```
- * When changing `material.opacity`, the node value of `opacityNode` will
- * automatically be updated.
- *
- * @augments ReferenceNode
- */
- class MaterialReferenceNode extends ReferenceNode {
- static get type() {
- return 'MaterialReferenceNode';
- }
- /**
- * Constructs a new material reference node.
- *
- * @param {string} property - The name of the property the node refers to.
- * @param {string} inputType - The uniform type that should be used to represent the property value.
- * @param {?Material} [material=null] - The material the property belongs to. When no material is set,
- * the node refers to the material of the current rendered object.
- */
- constructor( property, inputType, material = null ) {
- super( property, inputType, material );
- /**
- * The material the property belongs to. When no material is set,
- * the node refers to the material of the current rendered object.
- *
- * @type {?Material}
- * @default null
- */
- this.material = material;
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isMaterialReferenceNode = true;
- }
- /**
- * Updates the reference based on the given state. The state is only evaluated
- * {@link MaterialReferenceNode#material} is not set.
- *
- * @param {(NodeFrame|NodeBuilder)} state - The current state.
- * @return {Object} The updated reference.
- */
- updateReference( state ) {
- this.reference = this.material !== null ? this.material : state.material;
- return this.reference;
- }
- }
- /**
- * TSL function for creating a material reference node.
- *
- * @tsl
- * @function
- * @param {string} name - The name of the property the node refers to.
- * @param {string} type - The uniform type that should be used to represent the property value.
- * @param {?Material} [material=null] - The material the property belongs to.
- * When no material is set, the node refers to the material of the current rendered object.
- * @returns {MaterialReferenceNode}
- */
- const materialReference = ( name, type, material = null ) => nodeObject( new MaterialReferenceNode( name, type, material ) );
- // Normal Mapping Without Precomputed Tangents
- // http://www.thetenthplanet.de/archives/1180
- const uv = uv$1();
- const q0 = positionView.dFdx();
- const q1 = positionView.dFdy();
- const st0 = uv.dFdx();
- const st1 = uv.dFdy();
- const N = normalView;
- const q1perp = q1.cross( N );
- const q0perp = N.cross( q0 );
- const T = q1perp.mul( st0.x ).add( q0perp.mul( st1.x ) );
- const B = q1perp.mul( st0.y ).add( q0perp.mul( st1.y ) );
- const det = T.dot( T ).max( B.dot( B ) );
- const scale$1 = det.equal( 0.0 ).select( 0.0, det.inverseSqrt() );
- /**
- * Tangent vector in view space, computed dynamically from geometry and UV derivatives.
- * Useful for normal mapping without precomputed tangents.
- *
- * Reference: http://www.thetenthplanet.de/archives/1180
- *
- * @tsl
- * @type {Node<vec3>}
- */
- const tangentViewFrame = /*@__PURE__*/ T.mul( scale$1 ).toVar( 'tangentViewFrame' );
- /**
- * Bitangent vector in view space, computed dynamically from geometry and UV derivatives.
- * Complements the tangentViewFrame for constructing the tangent space basis.
- *
- * Reference: http://www.thetenthplanet.de/archives/1180
- *
- * @tsl
- * @type {Node<vec3>}
- */
- const bitangentViewFrame = /*@__PURE__*/ B.mul( scale$1 ).toVar( 'bitangentViewFrame' );
- /**
- * TSL object that represents the tangent attribute of the current rendered object.
- *
- * @tsl
- * @type {Node<vec4>}
- */
- const tangentGeometry = /*@__PURE__*/ attribute( 'tangent', 'vec4' );
- /**
- * TSL object that represents the vertex tangent in local space of the current rendered object.
- *
- * @tsl
- * @type {Node<vec3>}
- */
- const tangentLocal = /*@__PURE__*/ tangentGeometry.xyz.toVar( 'tangentLocal' );
- /**
- * TSL object that represents the vertex tangent in view space of the current rendered object.
- *
- * @tsl
- * @type {Node<vec3>}
- */
- const tangentView = /*@__PURE__*/ ( Fn( ( { subBuildFn, geometry, material } ) => {
- let node;
- if ( subBuildFn === 'VERTEX' || geometry.hasAttribute( 'tangent' ) ) {
- node = modelViewMatrix.mul( vec4( tangentLocal, 0 ) ).xyz.toVarying( 'v_tangentView' ).normalize();
- } else {
- node = tangentViewFrame;
- }
- if ( material.flatShading !== true ) {
- node = directionToFaceDirection( node );
- }
- return node;
- }, 'vec3' ).once( [ 'NORMAL', 'VERTEX' ] ) )().toVar( 'tangentView' );
- /**
- * TSL object that represents the vertex tangent in world space of the current rendered object.
- *
- * @tsl
- * @type {Node<vec3>}
- */
- const tangentWorld = /*@__PURE__*/ tangentView.transformDirection( cameraViewMatrix ).toVarying( 'v_tangentWorld' ).normalize().toVar( 'tangentWorld' );
- /**
- * Returns the bitangent node and assigns it to a varying if the material is not flat shaded.
- *
- * @tsl
- * @private
- * @param {Node<vec3>} crossNormalTangent - The cross product of the normal and tangent vectors.
- * @param {string} varyingName - The name of the varying to assign the bitangent to.
- * @returns {Node<vec3>} The bitangent node.
- */
- const getBitangent = /*@__PURE__*/ Fn( ( [ crossNormalTangent, varyingName ], { subBuildFn, material } ) => {
- let bitangent = crossNormalTangent.mul( tangentGeometry.w ).xyz;
- if ( subBuildFn === 'NORMAL' && material.flatShading !== true ) {
- bitangent = bitangent.toVarying( varyingName );
- }
- return bitangent;
- } ).once( [ 'NORMAL' ] );
- /**
- * TSL object that represents the bitangent attribute of the current rendered object.
- *
- * @tsl
- * @type {Node<vec3>}
- */
- const bitangentGeometry = /*@__PURE__*/ getBitangent( normalGeometry.cross( tangentGeometry ), 'v_bitangentGeometry' ).normalize().toVar( 'bitangentGeometry' );
- /**
- * TSL object that represents the vertex bitangent in local space of the current rendered object.
- *
- * @tsl
- * @type {Node<vec3>}
- */
- const bitangentLocal = /*@__PURE__*/ getBitangent( normalLocal.cross( tangentLocal ), 'v_bitangentLocal' ).normalize().toVar( 'bitangentLocal' );
- /**
- * TSL object that represents the vertex bitangent in view space of the current rendered object.
- *
- * @tsl
- * @type {Node<vec3>}
- */
- const bitangentView = /*@__PURE__*/ ( Fn( ( { subBuildFn, geometry, material } ) => {
- let node;
- if ( subBuildFn === 'VERTEX' || geometry.hasAttribute( 'tangent' ) ) {
- node = getBitangent( normalView.cross( tangentView ), 'v_bitangentView' ).normalize();
- } else {
- node = bitangentViewFrame;
- }
- if ( material.flatShading !== true ) {
- node = directionToFaceDirection( node );
- }
- return node;
- }, 'vec3' ).once( [ 'NORMAL', 'VERTEX' ] ) )().toVar( 'bitangentView' );
- /**
- * TSL object that represents the vertex bitangent in world space of the current rendered object.
- *
- * @tsl
- * @type {Node<vec3>}
- */
- const bitangentWorld = /*@__PURE__*/ getBitangent( normalWorld.cross( tangentWorld ), 'v_bitangentWorld' ).normalize().toVar( 'bitangentWorld' );
- /**
- * TSL object that represents the TBN matrix in view space.
- *
- * @tsl
- * @type {Node<mat3>}
- */
- const TBNViewMatrix = /*@__PURE__*/ mat3( tangentView, bitangentView, normalView ).toVar( 'TBNViewMatrix' );
- /**
- * TSL object that represents the parallax direction.
- *
- * @tsl
- * @type {Node<mat3>}
- */
- const parallaxDirection = /*@__PURE__*/ positionViewDirection.mul( TBNViewMatrix )/*.normalize()*/;
- /**
- * TSL function for computing parallax uv coordinates.
- *
- * @tsl
- * @function
- * @param {Node<vec2>} uv - A uv node.
- * @param {Node<vec2>} scale - A scale node.
- * @returns {Node<vec2>} Parallax uv coordinates.
- */
- const parallaxUV = ( uv, scale ) => uv.sub( parallaxDirection.mul( scale ) );
- /**
- * TSL function for computing bent normals.
- *
- * @tsl
- * @function
- * @returns {Node<vec3>} Bent normals.
- */
- const bentNormalView = /*@__PURE__*/ ( Fn( () => {
- // https://google.github.io/filament/Filament.md.html#lighting/imagebasedlights/anisotropy
- let bentNormal = anisotropyB.cross( positionViewDirection );
- bentNormal = bentNormal.cross( anisotropyB ).normalize();
- bentNormal = mix( bentNormal, normalView, anisotropy.mul( roughness.oneMinus() ).oneMinus().pow2().pow2() ).normalize();
- return bentNormal;
- } ).once() )();
- /**
- * Packs a direction vector into a color value.
- *
- * @tsl
- * @function
- * @param {Node<vec3>} node - The direction to pack.
- * @return {Node<vec3>} The color.
- */
- const directionToColor = ( node ) => nodeObject( node ).mul( 0.5 ).add( 0.5 );
- /**
- * Unpacks a color value into a direction vector.
- *
- * @tsl
- * @function
- * @param {Node<vec3>} node - The color to unpack.
- * @return {Node<vec3>} The direction.
- */
- const colorToDirection = ( node ) => nodeObject( node ).mul( 2.0 ).sub( 1 );
- /**
- * Unpacks a tangent space normal, reconstructing the Z component by projecting the X,Y coordinates onto the hemisphere.
- * The X,Y coordinates are expected to be in the [-1, 1] range.
- *
- * @tsl
- * @function
- * @param {Node<vec2>} xy - The X,Y coordinates of the normal.
- * @return {Node<vec3>} The resulting normal.
- */
- const unpackNormal = ( xy ) => vec3( xy, sqrt( saturate( float( 1.0 ).sub( dot( xy, xy ) ) ) ) );
- /**
- * This class can be used for applying normals maps to materials.
- *
- * ```js
- * material.normalNode = normalMap( texture( normalTex ) );
- * ```
- *
- * @augments TempNode
- */
- class NormalMapNode extends TempNode {
- static get type() {
- return 'NormalMapNode';
- }
- /**
- * Constructs a new normal map node.
- *
- * @param {Node<vec3>} node - Represents the normal map data.
- * @param {?Node<vec2>} [scaleNode=null] - Controls the intensity of the effect.
- */
- constructor( node, scaleNode = null ) {
- super( 'vec3' );
- /**
- * Represents the normal map data.
- *
- * @type {Node<vec3>}
- */
- this.node = node;
- /**
- * Controls the intensity of the effect.
- *
- * @type {?Node<vec2>}
- * @default null
- */
- this.scaleNode = scaleNode;
- /**
- * The normal map type.
- *
- * @type {(TangentSpaceNormalMap|ObjectSpaceNormalMap)}
- * @default TangentSpaceNormalMap
- */
- this.normalMapType = TangentSpaceNormalMap;
- /**
- * Controls how to unpack the sampled normal map values.
- *
- * @type {string}
- * @default NoNormalPacking
- */
- this.unpackNormalMode = NoNormalPacking;
- }
- setup( { material } ) {
- const { normalMapType, scaleNode, unpackNormalMode } = this;
- let normalMap = this.node.mul( 2.0 ).sub( 1.0 );
- if ( normalMapType === TangentSpaceNormalMap ) {
- if ( unpackNormalMode === NormalRGPacking ) {
- normalMap = unpackNormal( normalMap.xy );
- } else if ( unpackNormalMode === NormalGAPacking ) {
- normalMap = unpackNormal( normalMap.yw );
- } else if ( unpackNormalMode !== NoNormalPacking ) {
- console.error( `THREE.NodeMaterial: Unexpected unpack normal mode: ${ unpackNormalMode }` );
- }
- } else {
- if ( unpackNormalMode !== NoNormalPacking ) {
- console.error( `THREE.NodeMaterial: Normal map type '${ normalMapType }' is not compatible with unpack normal mode '${ unpackNormalMode }'` );
- }
- }
- if ( scaleNode !== null ) {
- let scale = scaleNode;
- if ( material.flatShading === true ) {
- scale = directionToFaceDirection( scale );
- }
- normalMap = vec3( normalMap.xy.mul( scale ), normalMap.z );
- }
- let output = null;
- if ( normalMapType === ObjectSpaceNormalMap ) {
- output = transformNormalToView( normalMap );
- } else if ( normalMapType === TangentSpaceNormalMap ) {
- output = TBNViewMatrix.mul( normalMap ).normalize();
- } else {
- error( `NodeMaterial: Unsupported normal map type: ${ normalMapType }` );
- output = normalView; // Fallback to default normal view
- }
- return output;
- }
- }
- /**
- * TSL function for creating a normal map node.
- *
- * @tsl
- * @function
- * @param {Node<vec3>} node - Represents the normal map data.
- * @param {?Node<vec2>} [scaleNode=null] - Controls the intensity of the effect.
- * @returns {NormalMapNode}
- */
- const normalMap = /*@__PURE__*/ nodeProxy( NormalMapNode ).setParameterLength( 1, 2 );
- // Bump Mapping Unparametrized Surfaces on the GPU by Morten S. Mikkelsen
- // https://mmikk.github.io/papers3d/mm_sfgrad_bump.pdf
- const dHdxy_fwd = Fn( ( { textureNode, bumpScale } ) => {
- // It's used to preserve the same TextureNode instance
- const sampleTexture = ( callback ) => textureNode.isolate().context( { getUV: ( texNode ) => callback( texNode.uvNode || uv$1() ), forceUVContext: true } );
- const Hll = float( sampleTexture( ( uvNode ) => uvNode ) );
- return vec2(
- float( sampleTexture( ( uvNode ) => uvNode.add( uvNode.dFdx() ) ) ).sub( Hll ),
- float( sampleTexture( ( uvNode ) => uvNode.add( uvNode.dFdy() ) ) ).sub( Hll )
- ).mul( bumpScale );
- } );
- // Evaluate the derivative of the height w.r.t. screen-space using forward differencing (listing 2)
- const perturbNormalArb = Fn( ( inputs ) => {
- const { surf_pos, surf_norm, dHdxy } = inputs;
- // normalize is done to ensure that the bump map looks the same regardless of the texture's scale
- const vSigmaX = surf_pos.dFdx().normalize();
- const vSigmaY = surf_pos.dFdy().normalize();
- const vN = surf_norm; // normalized
- const R1 = vSigmaY.cross( vN );
- const R2 = vN.cross( vSigmaX );
- const fDet = vSigmaX.dot( R1 ).mul( faceDirection );
- const vGrad = fDet.sign().mul( dHdxy.x.mul( R1 ).add( dHdxy.y.mul( R2 ) ) );
- return fDet.abs().mul( surf_norm ).sub( vGrad ).normalize();
- } );
- /**
- * This class can be used for applying bump maps to materials.
- *
- * ```js
- * material.normalNode = bumpMap( texture( bumpTex ) );
- * ```
- *
- * @augments TempNode
- */
- class BumpMapNode extends TempNode {
- static get type() {
- return 'BumpMapNode';
- }
- /**
- * Constructs a new bump map node.
- *
- * @param {Node<float>} textureNode - Represents the bump map data.
- * @param {?Node<float>} [scaleNode=null] - Controls the intensity of the bump effect.
- */
- constructor( textureNode, scaleNode = null ) {
- super( 'vec3' );
- /**
- * Represents the bump map data.
- *
- * @type {Node<float>}
- */
- this.textureNode = textureNode;
- /**
- * Controls the intensity of the bump effect.
- *
- * @type {?Node<float>}
- * @default null
- */
- this.scaleNode = scaleNode;
- }
- setup() {
- const bumpScale = this.scaleNode !== null ? this.scaleNode : 1;
- const dHdxy = dHdxy_fwd( { textureNode: this.textureNode, bumpScale } );
- return perturbNormalArb( {
- surf_pos: positionView,
- surf_norm: normalView,
- dHdxy
- } );
- }
- }
- /**
- * TSL function for creating a bump map node.
- *
- * @tsl
- * @function
- * @param {Node<float>} textureNode - Represents the bump map data.
- * @param {?Node<float>} [scaleNode=null] - Controls the intensity of the bump effect.
- * @returns {BumpMapNode}
- */
- const bumpMap = /*@__PURE__*/ nodeProxy( BumpMapNode ).setParameterLength( 1, 2 );
- const _propertyCache = new Map();
- /**
- * This class should simplify the node access to material properties.
- * It internal uses reference nodes to make sure changes to material
- * properties are automatically reflected to predefined TSL objects
- * like e.g. `materialColor`.
- *
- * @augments Node
- */
- class MaterialNode extends Node {
- static get type() {
- return 'MaterialNode';
- }
- /**
- * Constructs a new material node.
- *
- * @param {string} scope - The scope defines what kind of material property is referred by the node.
- */
- constructor( scope ) {
- super();
- /**
- * The scope defines what material property is referred by the node.
- *
- * @type {string}
- */
- this.scope = scope;
- }
- /**
- * Returns a cached reference node for the given property and type.
- *
- * @param {string} property - The name of the material property.
- * @param {string} type - The uniform type of the property.
- * @return {MaterialReferenceNode} A material reference node representing the property access.
- */
- getCache( property, type ) {
- let node = _propertyCache.get( property );
- if ( node === undefined ) {
- node = materialReference( property, type );
- _propertyCache.set( property, node );
- }
- return node;
- }
- /**
- * Returns a float-typed material reference node for the given property name.
- *
- * @param {string} property - The name of the material property.
- * @return {MaterialReferenceNode<float>} A material reference node representing the property access.
- */
- getFloat( property ) {
- return this.getCache( property, 'float' );
- }
- /**
- * Returns a color-typed material reference node for the given property name.
- *
- * @param {string} property - The name of the material property.
- * @return {MaterialReferenceNode<color>} A material reference node representing the property access.
- */
- getColor( property ) {
- return this.getCache( property, 'color' );
- }
- /**
- * Returns a texture-typed material reference node for the given property name.
- *
- * @param {string} property - The name of the material property.
- * @return {MaterialReferenceNode} A material reference node representing the property access.
- */
- getTexture( property ) {
- return this.getCache( property === 'map' ? 'map' : property + 'Map', 'texture' );
- }
- /**
- * The node setup is done depending on the selected scope. Multiple material properties
- * might be grouped into a single node composition if they logically belong together.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {Node} The node representing the selected scope.
- */
- setup( builder ) {
- const material = builder.context.material;
- const scope = this.scope;
- let node = null;
- if ( scope === MaterialNode.COLOR ) {
- const colorNode = material.color !== undefined ? this.getColor( scope ) : vec3();
- if ( material.map && material.map.isTexture === true ) {
- node = colorNode.mul( this.getTexture( 'map' ) );
- } else {
- node = colorNode;
- }
- } else if ( scope === MaterialNode.OPACITY ) {
- const opacityNode = this.getFloat( scope );
- if ( material.alphaMap && material.alphaMap.isTexture === true ) {
- node = opacityNode.mul( this.getTexture( 'alpha' ) );
- } else {
- node = opacityNode;
- }
- } else if ( scope === MaterialNode.SPECULAR_STRENGTH ) {
- if ( material.specularMap && material.specularMap.isTexture === true ) {
- node = this.getTexture( 'specular' ).r;
- } else {
- node = float( 1 );
- }
- } else if ( scope === MaterialNode.SPECULAR_INTENSITY ) {
- const specularIntensityNode = this.getFloat( scope );
- if ( material.specularIntensityMap && material.specularIntensityMap.isTexture === true ) {
- node = specularIntensityNode.mul( this.getTexture( scope ).a );
- } else {
- node = specularIntensityNode;
- }
- } else if ( scope === MaterialNode.SPECULAR_COLOR ) {
- const specularColorNode = this.getColor( scope );
- if ( material.specularColorMap && material.specularColorMap.isTexture === true ) {
- node = specularColorNode.mul( this.getTexture( scope ).rgb );
- } else {
- node = specularColorNode;
- }
- } else if ( scope === MaterialNode.ROUGHNESS ) { // TODO: cleanup similar branches
- const roughnessNode = this.getFloat( scope );
- if ( material.roughnessMap && material.roughnessMap.isTexture === true ) {
- node = roughnessNode.mul( this.getTexture( scope ).g );
- } else {
- node = roughnessNode;
- }
- } else if ( scope === MaterialNode.METALNESS ) {
- const metalnessNode = this.getFloat( scope );
- if ( material.metalnessMap && material.metalnessMap.isTexture === true ) {
- node = metalnessNode.mul( this.getTexture( scope ).b );
- } else {
- node = metalnessNode;
- }
- } else if ( scope === MaterialNode.EMISSIVE ) {
- const emissiveIntensityNode = this.getFloat( 'emissiveIntensity' );
- const emissiveNode = this.getColor( scope ).mul( emissiveIntensityNode );
- if ( material.emissiveMap && material.emissiveMap.isTexture === true ) {
- node = emissiveNode.mul( this.getTexture( scope ) );
- } else {
- node = emissiveNode;
- }
- } else if ( scope === MaterialNode.NORMAL ) {
- if ( material.normalMap ) {
- node = normalMap( this.getTexture( 'normal' ), this.getCache( 'normalScale', 'vec2' ) );
- node.normalMapType = material.normalMapType;
- if ( material.normalMap.format == RGFormat || material.normalMap.format == RED_GREEN_RGTC2_Format || material.normalMap.format == RG11_EAC_Format ) {
- node.unpackNormalMode = NormalRGPacking;
- }
- } else if ( material.bumpMap ) {
- node = bumpMap( this.getTexture( 'bump' ).r, this.getFloat( 'bumpScale' ) );
- } else {
- node = normalView;
- }
- } else if ( scope === MaterialNode.CLEARCOAT ) {
- const clearcoatNode = this.getFloat( scope );
- if ( material.clearcoatMap && material.clearcoatMap.isTexture === true ) {
- node = clearcoatNode.mul( this.getTexture( scope ).r );
- } else {
- node = clearcoatNode;
- }
- } else if ( scope === MaterialNode.CLEARCOAT_ROUGHNESS ) {
- const clearcoatRoughnessNode = this.getFloat( scope );
- if ( material.clearcoatRoughnessMap && material.clearcoatRoughnessMap.isTexture === true ) {
- node = clearcoatRoughnessNode.mul( this.getTexture( scope ).r );
- } else {
- node = clearcoatRoughnessNode;
- }
- } else if ( scope === MaterialNode.CLEARCOAT_NORMAL ) {
- if ( material.clearcoatNormalMap ) {
- node = normalMap( this.getTexture( scope ), this.getCache( scope + 'Scale', 'vec2' ) );
- } else {
- node = normalView;
- }
- } else if ( scope === MaterialNode.SHEEN ) {
- const sheenNode = this.getColor( 'sheenColor' ).mul( this.getFloat( 'sheen' ) ); // Move this mul() to CPU
- if ( material.sheenColorMap && material.sheenColorMap.isTexture === true ) {
- node = sheenNode.mul( this.getTexture( 'sheenColor' ).rgb );
- } else {
- node = sheenNode;
- }
- } else if ( scope === MaterialNode.SHEEN_ROUGHNESS ) {
- const sheenRoughnessNode = this.getFloat( scope );
- if ( material.sheenRoughnessMap && material.sheenRoughnessMap.isTexture === true ) {
- node = sheenRoughnessNode.mul( this.getTexture( scope ).a );
- } else {
- node = sheenRoughnessNode;
- }
- node = node.clamp( 0.0001, 1.0 );
- } else if ( scope === MaterialNode.ANISOTROPY ) {
- if ( material.anisotropyMap && material.anisotropyMap.isTexture === true ) {
- const anisotropyPolar = this.getTexture( scope );
- const anisotropyMat = mat2( materialAnisotropyVector.x, materialAnisotropyVector.y, materialAnisotropyVector.y.negate(), materialAnisotropyVector.x );
- node = anisotropyMat.mul( anisotropyPolar.rg.mul( 2.0 ).sub( vec2( 1.0 ) ).normalize().mul( anisotropyPolar.b ) );
- } else {
- node = materialAnisotropyVector;
- }
- } else if ( scope === MaterialNode.IRIDESCENCE_THICKNESS ) {
- const iridescenceThicknessMaximum = reference( '1', 'float', material.iridescenceThicknessRange );
- if ( material.iridescenceThicknessMap ) {
- const iridescenceThicknessMinimum = reference( '0', 'float', material.iridescenceThicknessRange );
- node = iridescenceThicknessMaximum.sub( iridescenceThicknessMinimum ).mul( this.getTexture( scope ).g ).add( iridescenceThicknessMinimum );
- } else {
- node = iridescenceThicknessMaximum;
- }
- } else if ( scope === MaterialNode.TRANSMISSION ) {
- const transmissionNode = this.getFloat( scope );
- if ( material.transmissionMap ) {
- node = transmissionNode.mul( this.getTexture( scope ).r );
- } else {
- node = transmissionNode;
- }
- } else if ( scope === MaterialNode.THICKNESS ) {
- const thicknessNode = this.getFloat( scope );
- if ( material.thicknessMap ) {
- node = thicknessNode.mul( this.getTexture( scope ).g );
- } else {
- node = thicknessNode;
- }
- } else if ( scope === MaterialNode.IOR ) {
- node = this.getFloat( scope );
- } else if ( scope === MaterialNode.LIGHT_MAP ) {
- node = this.getTexture( scope ).rgb.mul( this.getFloat( 'lightMapIntensity' ) );
- } else if ( scope === MaterialNode.AO ) {
- node = this.getTexture( scope ).r.sub( 1.0 ).mul( this.getFloat( 'aoMapIntensity' ) ).add( 1.0 );
- } else if ( scope === MaterialNode.LINE_DASH_OFFSET ) {
- node = ( material.dashOffset ) ? this.getFloat( scope ) : float( 0 );
- } else {
- const outputType = this.getNodeType( builder );
- node = this.getCache( scope, outputType );
- }
- return node;
- }
- }
- MaterialNode.ALPHA_TEST = 'alphaTest';
- MaterialNode.COLOR = 'color';
- MaterialNode.OPACITY = 'opacity';
- MaterialNode.SHININESS = 'shininess';
- MaterialNode.SPECULAR = 'specular';
- MaterialNode.SPECULAR_STRENGTH = 'specularStrength';
- MaterialNode.SPECULAR_INTENSITY = 'specularIntensity';
- MaterialNode.SPECULAR_COLOR = 'specularColor';
- MaterialNode.REFLECTIVITY = 'reflectivity';
- MaterialNode.ROUGHNESS = 'roughness';
- MaterialNode.METALNESS = 'metalness';
- MaterialNode.NORMAL = 'normal';
- MaterialNode.CLEARCOAT = 'clearcoat';
- MaterialNode.CLEARCOAT_ROUGHNESS = 'clearcoatRoughness';
- MaterialNode.CLEARCOAT_NORMAL = 'clearcoatNormal';
- MaterialNode.EMISSIVE = 'emissive';
- MaterialNode.ROTATION = 'rotation';
- MaterialNode.SHEEN = 'sheen';
- MaterialNode.SHEEN_ROUGHNESS = 'sheenRoughness';
- MaterialNode.ANISOTROPY = 'anisotropy';
- MaterialNode.IRIDESCENCE = 'iridescence';
- MaterialNode.IRIDESCENCE_IOR = 'iridescenceIOR';
- MaterialNode.IRIDESCENCE_THICKNESS = 'iridescenceThickness';
- MaterialNode.IOR = 'ior';
- MaterialNode.TRANSMISSION = 'transmission';
- MaterialNode.THICKNESS = 'thickness';
- MaterialNode.ATTENUATION_DISTANCE = 'attenuationDistance';
- MaterialNode.ATTENUATION_COLOR = 'attenuationColor';
- MaterialNode.LINE_SCALE = 'scale';
- MaterialNode.LINE_DASH_SIZE = 'dashSize';
- MaterialNode.LINE_GAP_SIZE = 'gapSize';
- MaterialNode.LINE_WIDTH = 'linewidth';
- MaterialNode.LINE_DASH_OFFSET = 'dashOffset';
- MaterialNode.POINT_SIZE = 'size';
- MaterialNode.DISPERSION = 'dispersion';
- MaterialNode.LIGHT_MAP = 'light';
- MaterialNode.AO = 'ao';
- /**
- * TSL object that represents alpha test of the current material.
- *
- * @tsl
- * @type {Node<float>}
- */
- const materialAlphaTest = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.ALPHA_TEST );
- /**
- * TSL object that represents the diffuse color of the current material.
- * The value is composed via `color` * `map`.
- *
- * @tsl
- * @type {Node<vec3>}
- */
- const materialColor = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.COLOR );
- /**
- * TSL object that represents the shininess of the current material.
- *
- * @tsl
- * @type {Node<float>}
- */
- const materialShininess = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.SHININESS );
- /**
- * TSL object that represents the emissive color of the current material.
- * The value is composed via `emissive` * `emissiveIntensity` * `emissiveMap`.
- *
- * @tsl
- * @type {Node<vec3>}
- */
- const materialEmissive = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.EMISSIVE );
- /**
- * TSL object that represents the opacity of the current material.
- * The value is composed via `opacity` * `alphaMap`.
- *
- * @tsl
- * @type {Node<float>}
- */
- const materialOpacity = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.OPACITY );
- /**
- * TSL object that represents the specular of the current material.
- *
- * @tsl
- * @type {Node<vec3>}
- */
- const materialSpecular = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.SPECULAR );
- /**
- * TSL object that represents the specular intensity of the current material.
- * The value is composed via `specularIntensity` * `specularMap.a`.
- *
- * @tsl
- * @type {Node<float>}
- */
- const materialSpecularIntensity = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.SPECULAR_INTENSITY );
- /**
- * TSL object that represents the specular color of the current material.
- * The value is composed via `specularColor` * `specularMap.rgb`.
- *
- * @tsl
- * @type {Node<vec3>}
- */
- const materialSpecularColor = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.SPECULAR_COLOR );
- /**
- * TSL object that represents the specular strength of the current material.
- * The value is composed via `specularMap.r`.
- *
- * @tsl
- * @type {Node<float>}
- */
- const materialSpecularStrength = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.SPECULAR_STRENGTH );
- /**
- * TSL object that represents the reflectivity of the current material.
- *
- * @tsl
- * @type {Node<float>}
- */
- const materialReflectivity = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.REFLECTIVITY );
- /**
- * TSL object that represents the roughness of the current material.
- * The value is composed via `roughness` * `roughnessMap.g`.
- *
- * @tsl
- * @type {Node<float>}
- */
- const materialRoughness = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.ROUGHNESS );
- /**
- * TSL object that represents the metalness of the current material.
- * The value is composed via `metalness` * `metalnessMap.b`.
- *
- * @tsl
- * @type {Node<float>}
- */
- const materialMetalness = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.METALNESS );
- /**
- * TSL object that represents the normal of the current material.
- * The value will be either `normalMap` * `normalScale`, `bumpMap` * `bumpScale` or `normalView`.
- *
- * @tsl
- * @type {Node<vec3>}
- */
- const materialNormal = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.NORMAL );
- /**
- * TSL object that represents the clearcoat of the current material.
- * The value is composed via `clearcoat` * `clearcoatMap.r`
- *
- * @tsl
- * @type {Node<float>}
- */
- const materialClearcoat = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.CLEARCOAT );
- /**
- * TSL object that represents the clearcoat roughness of the current material.
- * The value is composed via `clearcoatRoughness` * `clearcoatRoughnessMap.r`.
- *
- * @tsl
- * @type {Node<float>}
- */
- const materialClearcoatRoughness = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.CLEARCOAT_ROUGHNESS );
- /**
- * TSL object that represents the clearcoat normal of the current material.
- * The value will be either `clearcoatNormalMap` or `normalView`.
- *
- * @tsl
- * @type {Node<vec3>}
- */
- const materialClearcoatNormal = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.CLEARCOAT_NORMAL );
- /**
- * TSL object that represents the rotation of the current sprite material.
- *
- * @tsl
- * @type {Node<float>}
- */
- const materialRotation = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.ROTATION );
- /**
- * TSL object that represents the sheen color of the current material.
- * The value is composed via `sheen` * `sheenColor` * `sheenColorMap`.
- *
- * @tsl
- * @type {Node<vec3>}
- */
- const materialSheen = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.SHEEN );
- /**
- * TSL object that represents the sheen roughness of the current material.
- * The value is composed via `sheenRoughness` * `sheenRoughnessMap.a`.
- *
- * @tsl
- * @type {Node<float>}
- */
- const materialSheenRoughness = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.SHEEN_ROUGHNESS );
- /**
- * TSL object that represents the anisotropy of the current material.
- *
- * @tsl
- * @type {Node<vec2>}
- */
- const materialAnisotropy = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.ANISOTROPY );
- /**
- * TSL object that represents the iridescence of the current material.
- *
- * @tsl
- * @type {Node<float>}
- */
- const materialIridescence = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.IRIDESCENCE );
- /**
- * TSL object that represents the iridescence IOR of the current material.
- *
- * @tsl
- * @type {Node<float>}
- */
- const materialIridescenceIOR = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.IRIDESCENCE_IOR );
- /**
- * TSL object that represents the iridescence thickness of the current material.
- *
- * @tsl
- * @type {Node<float>}
- */
- const materialIridescenceThickness = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.IRIDESCENCE_THICKNESS );
- /**
- * TSL object that represents the transmission of the current material.
- * The value is composed via `transmission` * `transmissionMap.r`.
- *
- * @tsl
- * @type {Node<float>}
- */
- const materialTransmission = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.TRANSMISSION );
- /**
- * TSL object that represents the thickness of the current material.
- * The value is composed via `thickness` * `thicknessMap.g`.
- *
- * @tsl
- * @type {Node<float>}
- */
- const materialThickness = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.THICKNESS );
- /**
- * TSL object that represents the IOR of the current material.
- *
- * @tsl
- * @type {Node<float>}
- */
- const materialIOR = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.IOR );
- /**
- * TSL object that represents the attenuation distance of the current material.
- *
- * @tsl
- * @type {Node<float>}
- */
- const materialAttenuationDistance = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.ATTENUATION_DISTANCE );
- /**
- * TSL object that represents the attenuation color of the current material.
- *
- * @tsl
- * @type {Node<vec3>}
- */
- const materialAttenuationColor = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.ATTENUATION_COLOR );
- /**
- * TSL object that represents the scale of the current dashed line material.
- *
- * @tsl
- * @type {Node<float>}
- */
- const materialLineScale = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.LINE_SCALE );
- /**
- * TSL object that represents the dash size of the current dashed line material.
- *
- * @tsl
- * @type {Node<float>}
- */
- const materialLineDashSize = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.LINE_DASH_SIZE );
- /**
- * TSL object that represents the gap size of the current dashed line material.
- *
- * @tsl
- * @type {Node<float>}
- */
- const materialLineGapSize = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.LINE_GAP_SIZE );
- /**
- * TSL object that represents the line width of the current line material.
- *
- * @tsl
- * @type {Node<float>}
- */
- const materialLineWidth = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.LINE_WIDTH );
- /**
- * TSL object that represents the dash offset of the current line material.
- *
- * @tsl
- * @type {Node<float>}
- */
- const materialLineDashOffset = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.LINE_DASH_OFFSET );
- /**
- * TSL object that represents the point size of the current points material.
- *
- * @tsl
- * @type {Node<float>}
- */
- const materialPointSize = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.POINT_SIZE );
- /**
- * TSL object that represents the dispersion of the current material.
- *
- * @tsl
- * @type {Node<float>}
- */
- const materialDispersion = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.DISPERSION );
- /**
- * TSL object that represents the light map of the current material.
- * The value is composed via `lightMapIntensity` * `lightMap.rgb`.
- *
- * @tsl
- * @type {Node<vec3>}
- */
- const materialLightMap = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.LIGHT_MAP );
- /**
- * TSL object that represents the ambient occlusion map of the current material.
- * The value is composed via `aoMap.r` - 1 * `aoMapIntensity` + 1.
- *
- * @tsl
- * @type {Node<float>}
- */
- const materialAO = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.AO );
- /**
- * TSL object that represents the anisotropy vector of the current material.
- *
- * @tsl
- * @type {Node<vec2>}
- */
- const materialAnisotropyVector = /*@__PURE__*/ uniform( new Vector2() ).onReference( function ( frame ) {
- return frame.material;
- } ).onRenderUpdate( function ( { material } ) {
- this.value.set( material.anisotropy * Math.cos( material.anisotropyRotation ), material.anisotropy * Math.sin( material.anisotropyRotation ) );
- } );
- /**
- * TSL object that represents the position in clip space after the model-view-projection transform of the current rendered object.
- *
- * @tsl
- * @type {VaryingNode<vec4>}
- */
- const modelViewProjection = /*@__PURE__*/ ( Fn( ( builder ) => {
- return builder.context.setupModelViewProjection();
- }, 'vec4' ).once() )().toVarying( 'v_modelViewProjection' );
- /**
- * This class enables element access on instances of {@link StorageBufferNode}.
- * In most cases, it is indirectly used when accessing elements with the
- * {@link StorageBufferNode#element} method.
- *
- * ```js
- * const position = positionStorage.element( instanceIndex );
- * ```
- *
- * @augments ArrayElementNode
- */
- class StorageArrayElementNode extends ArrayElementNode {
- static get type() {
- return 'StorageArrayElementNode';
- }
- /**
- * Constructs storage buffer element node.
- *
- * @param {StorageBufferNode} storageBufferNode - The storage buffer node.
- * @param {Node} indexNode - The index node that defines the element access.
- */
- constructor( storageBufferNode, indexNode ) {
- super( storageBufferNode, indexNode );
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isStorageArrayElementNode = true;
- }
- /**
- * The storage buffer node.
- *
- * @param {Node} value
- * @type {StorageBufferNode}
- */
- set storageBufferNode( value ) {
- this.node = value;
- }
- get storageBufferNode() {
- return this.node;
- }
- getMemberType( builder, name ) {
- const structTypeNode = this.storageBufferNode.structTypeNode;
- if ( structTypeNode ) {
- return structTypeNode.getMemberType( builder, name );
- }
- return 'void';
- }
- setup( builder ) {
- if ( builder.isAvailable( 'storageBuffer' ) === false ) {
- if ( this.node.isPBO === true ) {
- builder.setupPBO( this.node );
- }
- }
- return super.setup( builder );
- }
- generate( builder, output ) {
- let snippet;
- const isAssignContext = builder.context.assign;
- //
- if ( builder.isAvailable( 'storageBuffer' ) === false ) {
- if ( this.node.isPBO === true && isAssignContext !== true && ( this.node.value.isInstancedBufferAttribute || builder.shaderStage !== 'compute' ) ) {
- snippet = builder.generatePBO( this );
- } else {
- snippet = this.node.build( builder );
- }
- } else {
- snippet = super.generate( builder );
- }
- if ( isAssignContext !== true ) {
- const type = this.getNodeType( builder );
- snippet = builder.format( snippet, type, output );
- }
- return snippet;
- }
- }
- /**
- * TSL function for creating a storage element node.
- *
- * @tsl
- * @function
- * @param {StorageBufferNode} storageBufferNode - The storage buffer node.
- * @param {Node} indexNode - The index node that defines the element access.
- * @returns {StorageArrayElementNode}
- */
- const storageElement = /*@__PURE__*/ nodeProxy( StorageArrayElementNode ).setParameterLength( 2 );
- /**
- * This node is used in context of compute shaders and allows to define a
- * storage buffer for data. A typical workflow is to create instances of
- * this node with the convenience functions `attributeArray()` or `instancedArray()`,
- * setup up a compute shader that writes into the buffers and then convert
- * the storage buffers to attribute nodes for rendering.
- *
- * ```js
- * const positionBuffer = instancedArray( particleCount, 'vec3' ); // the storage buffer node
- *
- * const computeInit = Fn( () => { // the compute shader
- *
- * const position = positionBuffer.element( instanceIndex );
- *
- * // compute position data
- *
- * position.x = 1;
- * position.y = 1;
- * position.z = 1;
- *
- * } )().compute( particleCount );
- *
- * const particleMaterial = new THREE.SpriteNodeMaterial();
- * particleMaterial.positionNode = positionBuffer.toAttribute();
- *
- * renderer.computeAsync( computeInit );
- *
- * ```
- *
- * @augments BufferNode
- */
- class StorageBufferNode extends BufferNode {
- static get type() {
- return 'StorageBufferNode';
- }
- /**
- * Constructs a new storage buffer node.
- *
- * @param {StorageBufferAttribute|StorageInstancedBufferAttribute|BufferAttribute} value - The buffer data.
- * @param {?(string|Struct)} [bufferType=null] - The buffer type (e.g. `'vec3'`).
- * @param {number} [bufferCount=0] - The buffer count.
- */
- constructor( value, bufferType = null, bufferCount = 0 ) {
- let nodeType, structTypeNode = null;
- if ( bufferType && bufferType.isStruct ) {
- nodeType = 'struct';
- structTypeNode = bufferType.layout;
- if ( value.isStorageBufferAttribute || value.isStorageInstancedBufferAttribute ) {
- bufferCount = value.count;
- }
- } else if ( bufferType === null && ( value.isStorageBufferAttribute || value.isStorageInstancedBufferAttribute ) ) {
- nodeType = getTypeFromLength( value.itemSize );
- bufferCount = value.count;
- } else {
- nodeType = bufferType;
- }
- super( value, nodeType, bufferCount );
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isStorageBufferNode = true;
- /**
- * The buffer struct type.
- *
- * @type {?StructTypeNode}
- * @default null
- */
- this.structTypeNode = structTypeNode;
- /**
- * The access type of the texture node.
- *
- * @type {string}
- * @default 'readWrite'
- */
- this.access = NodeAccess.READ_WRITE;
- /**
- * Whether the node is atomic or not.
- *
- * @type {boolean}
- * @default false
- */
- this.isAtomic = false;
- /**
- * Whether the node represents a PBO or not.
- * Only relevant for WebGL.
- *
- * @type {boolean}
- * @default false
- */
- this.isPBO = false;
- /**
- * A reference to the internal buffer attribute node.
- *
- * @private
- * @type {?BufferAttributeNode}
- * @default null
- */
- this._attribute = null;
- /**
- * A reference to the internal varying node.
- *
- * @private
- * @type {?VaryingNode}
- * @default null
- */
- this._varying = null;
- /**
- * `StorageBufferNode` sets this property to `true` by default.
- *
- * @type {boolean}
- * @default true
- */
- this.global = true;
- if ( value.isStorageBufferAttribute !== true && value.isStorageInstancedBufferAttribute !== true ) {
- // TODO: Improve it, possibly adding a new property to the BufferAttribute to identify it as a storage buffer read-only attribute in Renderer
- if ( value.isInstancedBufferAttribute ) value.isStorageInstancedBufferAttribute = true;
- else value.isStorageBufferAttribute = true;
- }
- }
- /**
- * This method is overwritten since the buffer data might be shared
- * and thus the hash should be shared as well.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {string} The hash.
- */
- getHash( builder ) {
- if ( this.bufferCount === 0 ) {
- let bufferData = builder.globalCache.getData( this.value );
- if ( bufferData === undefined ) {
- bufferData = {
- node: this
- };
- builder.globalCache.setData( this.value, bufferData );
- }
- return bufferData.node.uuid;
- }
- return this.uuid;
- }
- /**
- * Overwrites the default implementation to return a fixed value `'indirectStorageBuffer'` or `'storageBuffer'`.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {string} The input type.
- */
- getInputType( /*builder*/ ) {
- return this.value.isIndirectStorageBufferAttribute ? 'indirectStorageBuffer' : 'storageBuffer';
- }
- /**
- * Enables element access with the given index node.
- *
- * @param {IndexNode} indexNode - The index node.
- * @return {StorageArrayElementNode} A node representing the element access.
- */
- element( indexNode ) {
- return storageElement( this, indexNode );
- }
- /**
- * Defines whether this node is a PBO or not. Only relevant for WebGL.
- *
- * @param {boolean} value - The value so set.
- * @return {StorageBufferNode} A reference to this node.
- */
- setPBO( value ) {
- this.isPBO = value;
- return this;
- }
- /**
- * Returns the `isPBO` value.
- *
- * @return {boolean} Whether the node represents a PBO or not.
- */
- getPBO() {
- return this.isPBO;
- }
- /**
- * Defines the node access.
- *
- * @param {string} value - The node access.
- * @return {StorageBufferNode} A reference to this node.
- */
- setAccess( value ) {
- this.access = value;
- return this;
- }
- /**
- * Convenience method for configuring a read-only node access.
- *
- * @return {StorageBufferNode} A reference to this node.
- */
- toReadOnly() {
- return this.setAccess( NodeAccess.READ_ONLY );
- }
- /**
- * Defines whether the node is atomic or not.
- *
- * @param {boolean} value - The atomic flag.
- * @return {StorageBufferNode} A reference to this node.
- */
- setAtomic( value ) {
- this.isAtomic = value;
- return this;
- }
- /**
- * Convenience method for making this node atomic.
- *
- * @return {StorageBufferNode} A reference to this node.
- */
- toAtomic() {
- return this.setAtomic( true );
- }
- /**
- * Returns attribute data for this storage buffer node.
- *
- * @return {{attribute: BufferAttributeNode, varying: VaryingNode}} The attribute data.
- */
- getAttributeData() {
- if ( this._attribute === null ) {
- this._attribute = bufferAttribute( this.value );
- this._varying = varying( this._attribute );
- }
- return {
- attribute: this._attribute,
- varying: this._varying
- };
- }
- /**
- * This method is overwritten since the node type from the availability of storage buffers
- * and the attribute data.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {string} The node type.
- */
- getNodeType( builder ) {
- if ( this.structTypeNode !== null ) {
- return this.structTypeNode.getNodeType( builder );
- }
- if ( builder.isAvailable( 'storageBuffer' ) || builder.isAvailable( 'indirectStorageBuffer' ) ) {
- return super.getNodeType( builder );
- }
- const { attribute } = this.getAttributeData();
- return attribute.getNodeType( builder );
- }
- /**
- * Returns the type of a member of the struct.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @param {string} name - The name of the member.
- * @return {string} The type of the member.
- */
- getMemberType( builder, name ) {
- if ( this.structTypeNode !== null ) {
- return this.structTypeNode.getMemberType( builder, name );
- }
- return 'void';
- }
- /**
- * Generates the code snippet of the storage buffer node.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {string} The generated code snippet.
- */
- generate( builder ) {
- if ( this.structTypeNode !== null ) this.structTypeNode.build( builder );
- if ( builder.isAvailable( 'storageBuffer' ) || builder.isAvailable( 'indirectStorageBuffer' ) ) {
- return super.generate( builder );
- }
- const { attribute, varying } = this.getAttributeData();
- const output = varying.build( builder );
- builder.registerTransform( output, attribute );
- return output;
- }
- }
- /**
- * TSL function for creating a storage buffer node.
- *
- * @tsl
- * @function
- * @param {StorageBufferAttribute|StorageInstancedBufferAttribute|BufferAttribute} value - The buffer data.
- * @param {?(string|Struct)} [type=null] - The buffer type (e.g. `'vec3'`).
- * @param {number} [count=0] - The buffer count.
- * @returns {StorageBufferNode}
- */
- const storage = ( value, type = null, count = 0 ) => nodeObject( new StorageBufferNode( value, type, count ) );
- /**
- * @tsl
- * @function
- * @deprecated since r171. Use `storage().setPBO( true )` instead.
- *
- * @param {StorageBufferAttribute|StorageInstancedBufferAttribute|BufferAttribute} value - The buffer data.
- * @param {?string} type - The buffer type (e.g. `'vec3'`).
- * @param {number} count - The buffer count.
- * @returns {StorageBufferNode}
- */
- const storageObject = ( value, type, count ) => { // @deprecated, r171
- warn( 'TSL: "storageObject()" is deprecated. Use "storage().setPBO( true )" instead.' );
- return storage( value, type, count ).setPBO( true );
- };
- /**
- * This class represents shader indices of different types. The following predefined node
- * objects cover frequent use cases:
- *
- * - `vertexIndex`: The index of a vertex within a mesh.
- * - `instanceIndex`: The index of either a mesh instance or an invocation of a compute shader.
- * - `drawIndex`: The index of a draw call.
- * - `invocationLocalIndex`: The index of a compute invocation within the scope of a workgroup load.
- * - `invocationSubgroupIndex`: The index of a compute invocation within the scope of a subgroup.
- * - `subgroupIndex`: The index of a compute invocation's subgroup within its workgroup.
- *
- * @augments Node
- */
- class IndexNode extends Node {
- static get type() {
- return 'IndexNode';
- }
- /**
- * Constructs a new index node.
- *
- * @param {('vertex'|'instance'|'subgroup'|'invocationLocal'|'invocationGlobal'|'invocationSubgroup'|'draw')} scope - The scope of the index node.
- */
- constructor( scope ) {
- super( 'uint' );
- /**
- * The scope of the index node.
- *
- * @type {string}
- */
- this.scope = scope;
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isIndexNode = true;
- }
- generate( builder ) {
- const nodeType = this.getNodeType( builder );
- const scope = this.scope;
- let propertyName;
- if ( scope === IndexNode.VERTEX ) {
- propertyName = builder.getVertexIndex();
- } else if ( scope === IndexNode.INSTANCE ) {
- propertyName = builder.getInstanceIndex();
- } else if ( scope === IndexNode.DRAW ) {
- propertyName = builder.getDrawIndex();
- } else if ( scope === IndexNode.INVOCATION_LOCAL ) {
- propertyName = builder.getInvocationLocalIndex();
- } else if ( scope === IndexNode.INVOCATION_SUBGROUP ) {
- propertyName = builder.getInvocationSubgroupIndex();
- } else if ( scope === IndexNode.SUBGROUP ) {
- propertyName = builder.getSubgroupIndex();
- } else {
- throw new Error( 'THREE.IndexNode: Unknown scope: ' + scope );
- }
- let output;
- if ( builder.shaderStage === 'vertex' || builder.shaderStage === 'compute' ) {
- output = propertyName;
- } else {
- const nodeVarying = varying( this );
- output = nodeVarying.build( builder, nodeType );
- }
- return output;
- }
- }
- IndexNode.VERTEX = 'vertex';
- IndexNode.INSTANCE = 'instance';
- IndexNode.SUBGROUP = 'subgroup';
- IndexNode.INVOCATION_LOCAL = 'invocationLocal';
- IndexNode.INVOCATION_SUBGROUP = 'invocationSubgroup';
- IndexNode.DRAW = 'draw';
- /**
- * TSL object that represents the index of a vertex within a mesh.
- *
- * @tsl
- * @type {IndexNode}
- */
- const vertexIndex = /*@__PURE__*/ nodeImmutable( IndexNode, IndexNode.VERTEX );
- /**
- * TSL object that represents the index of either a mesh instance or an invocation of a compute shader.
- *
- * @tsl
- * @type {IndexNode}
- */
- const instanceIndex = /*@__PURE__*/ nodeImmutable( IndexNode, IndexNode.INSTANCE );
- /**
- * TSL object that represents the index of the subgroup the current compute invocation belongs to.
- *
- * @tsl
- * @type {IndexNode}
- */
- const subgroupIndex = /*@__PURE__*/ nodeImmutable( IndexNode, IndexNode.SUBGROUP );
- /**
- * TSL object that represents the index of a compute invocation within the scope of a subgroup.
- *
- * @tsl
- * @type {IndexNode}
- */
- const invocationSubgroupIndex = /*@__PURE__*/ nodeImmutable( IndexNode, IndexNode.INVOCATION_SUBGROUP );
- /**
- * TSL object that represents the index of a compute invocation within the scope of a workgroup load.
- *
- * @tsl
- * @type {IndexNode}
- */
- const invocationLocalIndex = /*@__PURE__*/ nodeImmutable( IndexNode, IndexNode.INVOCATION_LOCAL );
- /**
- * TSL object that represents the index of a draw call.
- *
- * @tsl
- * @type {IndexNode}
- */
- const drawIndex = /*@__PURE__*/ nodeImmutable( IndexNode, IndexNode.DRAW );
- /**
- * This node implements the vertex shader logic which is required
- * when rendering 3D objects via instancing. The code makes sure
- * vertex positions, normals and colors can be modified via instanced
- * data.
- *
- * @augments Node
- */
- class InstanceNode extends Node {
- static get type() {
- return 'InstanceNode';
- }
- /**
- * Constructs a new instance node.
- *
- * @param {number} count - The number of instances.
- * @param {InstancedBufferAttribute|StorageInstancedBufferAttribute} instanceMatrix - Instanced buffer attribute representing the instance transformations.
- * @param {?InstancedBufferAttribute|StorageInstancedBufferAttribute} instanceColor - Instanced buffer attribute representing the instance colors.
- */
- constructor( count, instanceMatrix, instanceColor = null ) {
- super( 'void' );
- /**
- * The number of instances.
- *
- * @type {number}
- */
- this.count = count;
- /**
- * Instanced buffer attribute representing the transformation of instances.
- *
- * @type {InstancedBufferAttribute}
- */
- this.instanceMatrix = instanceMatrix;
- /**
- * Instanced buffer attribute representing the color of instances.
- *
- * @type {InstancedBufferAttribute}
- */
- this.instanceColor = instanceColor;
- /**
- * The node that represents the instance matrix data.
- *
- * @type {?Node}
- */
- this.instanceMatrixNode = null;
- /**
- * The node that represents the instance color data.
- *
- * @type {?Node}
- * @default null
- */
- this.instanceColorNode = null;
- /**
- * The update type is set to `frame` since an update
- * of instanced buffer data must be checked per frame.
- *
- * @type {string}
- * @default 'frame'
- */
- this.updateType = NodeUpdateType.FRAME;
- /**
- * A reference to a buffer that is used by `instanceMatrixNode`.
- *
- * @type {?InstancedInterleavedBuffer}
- */
- this.buffer = null;
- /**
- * A reference to a buffer that is used by `instanceColorNode`.
- *
- * @type {?InstancedBufferAttribute}
- */
- this.bufferColor = null;
- }
- /**
- * Tracks whether the matrix data is provided via a storage buffer.
- *
- * @type {boolean}
- */
- get isStorageMatrix() {
- const { instanceMatrix } = this;
- return instanceMatrix && instanceMatrix.isStorageInstancedBufferAttribute === true;
- }
- /**
- * Tracks whether the color data is provided via a storage buffer.
- *
- * @type {boolean}
- */
- get isStorageColor() {
- const { instanceColor } = this;
- return instanceColor && instanceColor.isStorageInstancedBufferAttribute === true;
- }
- /**
- * Setups the internal buffers and nodes and assigns the transformed vertex data
- * to predefined node variables for accumulation. That follows the same patterns
- * like with morph and skinning nodes.
- *
- * @param {NodeBuilder} builder - The current node builder.
- */
- setup( builder ) {
- const { instanceMatrix, instanceColor, isStorageMatrix, isStorageColor } = this;
- const { count } = instanceMatrix;
- let { instanceMatrixNode, instanceColorNode } = this;
- if ( instanceMatrixNode === null ) {
- if ( isStorageMatrix ) {
- instanceMatrixNode = storage( instanceMatrix, 'mat4', Math.max( count, 1 ) ).element( instanceIndex );
- } else {
- // Both backends have ~64kb UBO limit; fallback to attributes above 1000 matrices.
- if ( count <= 1000 ) {
- instanceMatrixNode = buffer( instanceMatrix.array, 'mat4', Math.max( count, 1 ) ).element( instanceIndex );
- } else {
- const interleaved = new InstancedInterleavedBuffer( instanceMatrix.array, 16, 1 );
- this.buffer = interleaved;
- const bufferFn = instanceMatrix.usage === DynamicDrawUsage ? instancedDynamicBufferAttribute : instancedBufferAttribute;
- const instanceBuffers = [
- bufferFn( interleaved, 'vec4', 16, 0 ),
- bufferFn( interleaved, 'vec4', 16, 4 ),
- bufferFn( interleaved, 'vec4', 16, 8 ),
- bufferFn( interleaved, 'vec4', 16, 12 )
- ];
- instanceMatrixNode = mat4( ...instanceBuffers );
- }
- }
- this.instanceMatrixNode = instanceMatrixNode;
- }
- if ( instanceColor && instanceColorNode === null ) {
- if ( isStorageColor ) {
- instanceColorNode = storage( instanceColor, 'vec3', Math.max( instanceColor.count, 1 ) ).element( instanceIndex );
- } else {
- const bufferAttribute = new InstancedBufferAttribute( instanceColor.array, 3 );
- const bufferFn = instanceColor.usage === DynamicDrawUsage ? instancedDynamicBufferAttribute : instancedBufferAttribute;
- this.bufferColor = bufferAttribute;
- instanceColorNode = vec3( bufferFn( bufferAttribute, 'vec3', 3, 0 ) );
- }
- this.instanceColorNode = instanceColorNode;
- }
- // POSITION
- const instancePosition = instanceMatrixNode.mul( positionLocal ).xyz;
- positionLocal.assign( instancePosition );
- // NORMAL
- if ( builder.hasGeometryAttribute( 'normal' ) ) {
- const instanceNormal = transformNormal( normalLocal, instanceMatrixNode );
- // ASSIGNS
- normalLocal.assign( instanceNormal );
- }
- // COLOR
- if ( this.instanceColorNode !== null ) {
- varyingProperty( 'vec3', 'vInstanceColor' ).assign( this.instanceColorNode );
- }
- }
- /**
- * Checks if the internal buffers require an update.
- *
- * @param {NodeFrame} frame - The current node frame.
- */
- update( /*frame*/ ) {
- if ( this.buffer !== null && this.isStorageMatrix !== true ) {
- this.buffer.clearUpdateRanges();
- this.buffer.updateRanges.push( ... this.instanceMatrix.updateRanges );
- // update version if necessary
- if ( this.instanceMatrix.usage !== DynamicDrawUsage && this.instanceMatrix.version !== this.buffer.version ) {
- this.buffer.version = this.instanceMatrix.version;
- }
- }
- if ( this.instanceColor && this.bufferColor !== null && this.isStorageColor !== true ) {
- this.bufferColor.clearUpdateRanges();
- this.bufferColor.updateRanges.push( ... this.instanceColor.updateRanges );
- if ( this.instanceColor.usage !== DynamicDrawUsage && this.instanceColor.version !== this.bufferColor.version ) {
- this.bufferColor.version = this.instanceColor.version;
- }
- }
- }
- }
- /**
- * TSL function for creating an instance node.
- *
- * @tsl
- * @function
- * @param {number} count - The number of instances.
- * @param {InstancedBufferAttribute|StorageInstancedBufferAttribute} instanceMatrix - Instanced buffer attribute representing the instance transformations.
- * @param {?InstancedBufferAttribute|StorageInstancedBufferAttribute} instanceColor - Instanced buffer attribute representing the instance colors.
- * @returns {InstanceNode}
- */
- const instance = /*@__PURE__*/ nodeProxy( InstanceNode ).setParameterLength( 2, 3 );
- /**
- * This is a special version of `InstanceNode` which requires the usage of {@link InstancedMesh}.
- * It allows an easier setup of the instance node.
- *
- * @augments InstanceNode
- */
- class InstancedMeshNode extends InstanceNode {
- static get type() {
- return 'InstancedMeshNode';
- }
- /**
- * Constructs a new instanced mesh node.
- *
- * @param {InstancedMesh} instancedMesh - The instanced mesh.
- */
- constructor( instancedMesh ) {
- const { count, instanceMatrix, instanceColor } = instancedMesh;
- super( count, instanceMatrix, instanceColor );
- /**
- * A reference to the instanced mesh.
- *
- * @type {InstancedMesh}
- */
- this.instancedMesh = instancedMesh;
- }
- }
- /**
- * TSL function for creating an instanced mesh node.
- *
- * @tsl
- * @function
- * @param {InstancedMesh} instancedMesh - The instancedMesh.
- * @returns {InstancedMeshNode}
- */
- const instancedMesh = /*@__PURE__*/ nodeProxy( InstancedMeshNode ).setParameterLength( 1 );
- /**
- * This node implements the vertex shader logic which is required
- * when rendering 3D objects via batching. `BatchNode` must be used
- * with instances of {@link BatchedMesh}.
- *
- * @augments Node
- */
- class BatchNode extends Node {
- static get type() {
- return 'BatchNode';
- }
- /**
- * Constructs a new batch node.
- *
- * @param {BatchedMesh} batchMesh - A reference to batched mesh.
- */
- constructor( batchMesh ) {
- super( 'void' );
- /**
- * A reference to batched mesh.
- *
- * @type {BatchedMesh}
- */
- this.batchMesh = batchMesh;
- /**
- * The batching index node.
- *
- * @type {?IndexNode}
- * @default null
- */
- this.batchingIdNode = null;
- }
- /**
- * Setups the internal buffers and nodes and assigns the transformed vertex data
- * to predefined node variables for accumulation. That follows the same patterns
- * like with morph and skinning nodes.
- *
- * @param {NodeBuilder} builder - The current node builder.
- */
- setup( builder ) {
- if ( this.batchingIdNode === null ) {
- if ( builder.getDrawIndex() === null ) {
- this.batchingIdNode = instanceIndex;
- } else {
- this.batchingIdNode = drawIndex;
- }
- }
- const getIndirectIndex = Fn( ( [ id ] ) => {
- const size = int( textureSize( textureLoad( this.batchMesh._indirectTexture ), 0 ).x ).toConst();
- const x = int( id ).mod( size ).toConst();
- const y = int( id ).div( size ).toConst();
- return textureLoad( this.batchMesh._indirectTexture, ivec2( x, y ) ).x;
- } ).setLayout( {
- name: 'getIndirectIndex',
- type: 'uint',
- inputs: [
- { name: 'id', type: 'int' }
- ]
- } );
- const indirectId = getIndirectIndex( int( this.batchingIdNode ) );
- const matricesTexture = this.batchMesh._matricesTexture;
- const size = int( textureSize( textureLoad( matricesTexture ), 0 ).x ).toConst();
- const j = float( indirectId ).mul( 4 ).toInt().toConst();
- const x = j.mod( size ).toConst();
- const y = j.div( size ).toConst();
- const batchingMatrix = mat4(
- textureLoad( matricesTexture, ivec2( x, y ) ),
- textureLoad( matricesTexture, ivec2( x.add( 1 ), y ) ),
- textureLoad( matricesTexture, ivec2( x.add( 2 ), y ) ),
- textureLoad( matricesTexture, ivec2( x.add( 3 ), y ) )
- );
- const colorsTexture = this.batchMesh._colorsTexture;
- if ( colorsTexture !== null ) {
- const getBatchingColor = Fn( ( [ id ] ) => {
- const size = int( textureSize( textureLoad( colorsTexture ), 0 ).x ).toConst();
- const j = id;
- const x = j.mod( size ).toConst();
- const y = j.div( size ).toConst();
- return textureLoad( colorsTexture, ivec2( x, y ) ).rgb;
- } ).setLayout( {
- name: 'getBatchingColor',
- type: 'vec3',
- inputs: [
- { name: 'id', type: 'int' }
- ]
- } );
- const color = getBatchingColor( indirectId );
- varyingProperty( 'vec3', 'vBatchColor' ).assign( color );
- }
- const bm = mat3( batchingMatrix );
- positionLocal.assign( batchingMatrix.mul( positionLocal ) );
- const transformedNormal = normalLocal.div( vec3( bm[ 0 ].dot( bm[ 0 ] ), bm[ 1 ].dot( bm[ 1 ] ), bm[ 2 ].dot( bm[ 2 ] ) ) );
- const batchingNormal = bm.mul( transformedNormal ).xyz;
- normalLocal.assign( batchingNormal );
- if ( builder.hasGeometryAttribute( 'tangent' ) ) {
- tangentLocal.mulAssign( bm );
- }
- }
- }
- /**
- * TSL function for creating a batch node.
- *
- * @tsl
- * @function
- * @param {BatchedMesh} batchMesh - A reference to batched mesh.
- * @returns {BatchNode}
- */
- const batch = /*@__PURE__*/ nodeProxy( BatchNode ).setParameterLength( 1 );
- const _frameId = new WeakMap();
- /**
- * This node implements the vertex transformation shader logic which is required
- * for skinning/skeletal animation.
- *
- * @augments Node
- */
- class SkinningNode extends Node {
- static get type() {
- return 'SkinningNode';
- }
- /**
- * Constructs a new skinning node.
- *
- * @param {SkinnedMesh} skinnedMesh - The skinned mesh.
- */
- constructor( skinnedMesh ) {
- super( 'void' );
- /**
- * The skinned mesh.
- *
- * @type {SkinnedMesh}
- */
- this.skinnedMesh = skinnedMesh;
- /**
- * The update type overwritten since skinning nodes are updated per object.
- *
- * @type {string}
- */
- this.updateType = NodeUpdateType.OBJECT;
- //
- /**
- * The skin index attribute.
- *
- * @type {AttributeNode}
- */
- this.skinIndexNode = attribute( 'skinIndex', 'uvec4' );
- /**
- * The skin weight attribute.
- *
- * @type {AttributeNode}
- */
- this.skinWeightNode = attribute( 'skinWeight', 'vec4' );
- /**
- * The bind matrix node.
- *
- * @type {Node<mat4>}
- */
- this.bindMatrixNode = reference( 'bindMatrix', 'mat4' );
- /**
- * The bind matrix inverse node.
- *
- * @type {Node<mat4>}
- */
- this.bindMatrixInverseNode = reference( 'bindMatrixInverse', 'mat4' );
- /**
- * The bind matrices as a uniform buffer node.
- *
- * @type {Node}
- */
- this.boneMatricesNode = referenceBuffer( 'skeleton.boneMatrices', 'mat4', skinnedMesh.skeleton.bones.length );
- /**
- * The current vertex position in local space.
- *
- * @type {Node<vec3>}
- */
- this.positionNode = positionLocal;
- /**
- * The result of vertex position in local space.
- *
- * @type {Node<vec3>}
- */
- this.toPositionNode = positionLocal;
- /**
- * The previous bind matrices as a uniform buffer node.
- * Required for computing motion vectors.
- *
- * @type {?Node}
- * @default null
- */
- this.previousBoneMatricesNode = null;
- }
- /**
- * Transforms the given vertex position via skinning.
- *
- * @param {Node} [boneMatrices=this.boneMatricesNode] - The bone matrices
- * @param {Node<vec3>} [position=this.positionNode] - The vertex position in local space.
- * @return {Node<vec3>} The transformed vertex position.
- */
- getSkinnedPosition( boneMatrices = this.boneMatricesNode, position = this.positionNode ) {
- const { skinIndexNode, skinWeightNode, bindMatrixNode, bindMatrixInverseNode } = this;
- const boneMatX = boneMatrices.element( skinIndexNode.x );
- const boneMatY = boneMatrices.element( skinIndexNode.y );
- const boneMatZ = boneMatrices.element( skinIndexNode.z );
- const boneMatW = boneMatrices.element( skinIndexNode.w );
- // POSITION
- const skinVertex = bindMatrixNode.mul( position );
- const skinned = add(
- boneMatX.mul( skinWeightNode.x ).mul( skinVertex ),
- boneMatY.mul( skinWeightNode.y ).mul( skinVertex ),
- boneMatZ.mul( skinWeightNode.z ).mul( skinVertex ),
- boneMatW.mul( skinWeightNode.w ).mul( skinVertex )
- );
- return bindMatrixInverseNode.mul( skinned ).xyz;
- }
- /**
- * Transforms the given vertex normal via skinning.
- *
- * @param {Node} [boneMatrices=this.boneMatricesNode] - The bone matrices
- * @param {Node<vec3>} [normal=normalLocal] - The vertex normal in local space.
- * @return {Node<vec3>} The transformed vertex normal.
- */
- getSkinnedNormal( boneMatrices = this.boneMatricesNode, normal = normalLocal ) {
- const { skinIndexNode, skinWeightNode, bindMatrixNode, bindMatrixInverseNode } = this;
- const boneMatX = boneMatrices.element( skinIndexNode.x );
- const boneMatY = boneMatrices.element( skinIndexNode.y );
- const boneMatZ = boneMatrices.element( skinIndexNode.z );
- const boneMatW = boneMatrices.element( skinIndexNode.w );
- // NORMAL
- let skinMatrix = add(
- skinWeightNode.x.mul( boneMatX ),
- skinWeightNode.y.mul( boneMatY ),
- skinWeightNode.z.mul( boneMatZ ),
- skinWeightNode.w.mul( boneMatW )
- );
- skinMatrix = bindMatrixInverseNode.mul( skinMatrix ).mul( bindMatrixNode );
- return skinMatrix.transformDirection( normal ).xyz;
- }
- /**
- * Computes the transformed/skinned vertex position of the previous frame.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {Node<vec3>} The skinned position from the previous frame.
- */
- getPreviousSkinnedPosition( builder ) {
- const skinnedMesh = builder.object;
- if ( this.previousBoneMatricesNode === null ) {
- skinnedMesh.skeleton.previousBoneMatrices = new Float32Array( skinnedMesh.skeleton.boneMatrices );
- this.previousBoneMatricesNode = referenceBuffer( 'skeleton.previousBoneMatrices', 'mat4', skinnedMesh.skeleton.bones.length );
- }
- return this.getSkinnedPosition( this.previousBoneMatricesNode, positionPrevious );
- }
- /**
- * Returns `true` if bone matrices from the previous frame are required. Relevant
- * when computing motion vectors with {@link VelocityNode}.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {boolean} Whether bone matrices from the previous frame are required or not.
- */
- needsPreviousBoneMatrices( builder ) {
- const mrt = builder.renderer.getMRT();
- return ( mrt && mrt.has( 'velocity' ) ) || getDataFromObject( builder.object ).useVelocity === true;
- }
- /**
- * Setups the skinning node by assigning the transformed vertex data to predefined node variables.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {Node<vec3>} The transformed vertex position.
- */
- setup( builder ) {
- if ( this.needsPreviousBoneMatrices( builder ) ) {
- positionPrevious.assign( this.getPreviousSkinnedPosition( builder ) );
- }
- const skinPosition = this.getSkinnedPosition();
- if ( this.toPositionNode ) this.toPositionNode.assign( skinPosition );
- //
- if ( builder.hasGeometryAttribute( 'normal' ) ) {
- const skinNormal = this.getSkinnedNormal();
- normalLocal.assign( skinNormal );
- if ( builder.hasGeometryAttribute( 'tangent' ) ) {
- tangentLocal.assign( skinNormal );
- }
- }
- return skinPosition;
- }
- /**
- * Generates the code snippet of the skinning node.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @param {string} output - The current output.
- * @return {string} The generated code snippet.
- */
- generate( builder, output ) {
- if ( output !== 'void' ) {
- return super.generate( builder, output );
- }
- }
- /**
- * Updates the state of the skinned mesh by updating the skeleton once per frame.
- *
- * @param {NodeFrame} frame - The current node frame.
- */
- update( frame ) {
- const skeleton = frame.object && frame.object.skeleton ? frame.object.skeleton : this.skinnedMesh.skeleton;
- if ( _frameId.get( skeleton ) === frame.frameId ) return;
- _frameId.set( skeleton, frame.frameId );
- if ( this.previousBoneMatricesNode !== null ) {
- if ( skeleton.previousBoneMatrices === null ) {
- // cloned skeletons miss "previousBoneMatrices" in their first updated
- skeleton.previousBoneMatrices = new Float32Array( skeleton.boneMatrices );
- }
- skeleton.previousBoneMatrices.set( skeleton.boneMatrices );
- }
- skeleton.update();
- }
- }
- /**
- * TSL function for creating a skinning node.
- *
- * @tsl
- * @function
- * @param {SkinnedMesh} skinnedMesh - The skinned mesh.
- * @returns {SkinningNode}
- */
- const skinning = ( skinnedMesh ) => nodeObject( new SkinningNode( skinnedMesh ) );
- /**
- * TSL function for computing skinning.
- *
- * @tsl
- * @function
- * @param {SkinnedMesh} skinnedMesh - The skinned mesh.
- * @param {Node<vec3>} [toPosition=null] - The target position.
- * @returns {SkinningNode}
- */
- const computeSkinning = ( skinnedMesh, toPosition = null ) => {
- const node = new SkinningNode( skinnedMesh );
- node.positionNode = storage( new InstancedBufferAttribute( skinnedMesh.geometry.getAttribute( 'position' ).array, 3 ), 'vec3' ).setPBO( true ).toReadOnly().element( instanceIndex ).toVar();
- node.skinIndexNode = storage( new InstancedBufferAttribute( new Uint32Array( skinnedMesh.geometry.getAttribute( 'skinIndex' ).array ), 4 ), 'uvec4' ).setPBO( true ).toReadOnly().element( instanceIndex ).toVar();
- node.skinWeightNode = storage( new InstancedBufferAttribute( skinnedMesh.geometry.getAttribute( 'skinWeight' ).array, 4 ), 'vec4' ).setPBO( true ).toReadOnly().element( instanceIndex ).toVar();
- node.bindMatrixNode = uniform( skinnedMesh.bindMatrix, 'mat4' );
- node.bindMatrixInverseNode = uniform( skinnedMesh.bindMatrixInverse, 'mat4' );
- node.boneMatricesNode = buffer( skinnedMesh.skeleton.boneMatrices, 'mat4', skinnedMesh.skeleton.bones.length );
- node.toPositionNode = toPosition;
- return nodeObject( node );
- };
- /**
- * This module offers a variety of ways to implement loops in TSL. In it's basic form it's:
- * ```js
- * Loop( count, ( { i } ) => {
- *
- * } );
- * ```
- * However, it is also possible to define a start and end ranges, data types and loop conditions:
- * ```js
- * Loop( { start: int( 0 ), end: int( 10 ), type: 'int', condition: '<' }, ( { i } ) => {
- *
- * } );
- *```
- * Nested loops can be defined in a compacted form:
- * ```js
- * Loop( 10, 5, ( { i, j } ) => {
- *
- * } );
- * ```
- * Loops that should run backwards can be defined like so:
- * ```js
- * Loop( { start: 10 }, () => {} );
- * ```
- * It is possible to execute with boolean values, similar to the `while` syntax.
- * ```js
- * const value = float( 0 ).toVar();
- *
- * Loop( value.lessThan( 10 ), () => {
- *
- * value.addAssign( 1 );
- *
- * } );
- * ```
- * The module also provides `Break()` and `Continue()` TSL expression for loop control.
- * @augments Node
- */
- class LoopNode extends Node {
- static get type() {
- return 'LoopNode';
- }
- /**
- * Constructs a new loop node.
- *
- * @param {Array<any>} params - Depending on the loop type, array holds different parameterization values for the loop.
- */
- constructor( params = [] ) {
- super( 'void' );
- this.params = params;
- }
- /**
- * Returns a loop variable name based on an index. The pattern is
- * `0` = `i`, `1`= `j`, `2`= `k` and so on.
- *
- * @param {number} index - The index.
- * @return {string} The loop variable name.
- */
- getVarName( index ) {
- return String.fromCharCode( 'i'.charCodeAt( 0 ) + index );
- }
- /**
- * Returns properties about this node.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {Object} The node properties.
- */
- getProperties( builder ) {
- const properties = builder.getNodeProperties( this );
- if ( properties.stackNode !== undefined ) return properties;
- //
- const inputs = {};
- for ( let i = 0, l = this.params.length - 1; i < l; i ++ ) {
- const param = this.params[ i ];
- const name = ( param.isNode !== true && param.name ) || this.getVarName( i );
- const type = ( param.isNode !== true && param.type ) || 'int';
- inputs[ name ] = expression( name, type );
- }
- const stack = builder.addStack();
- const fnCall = this.params[ this.params.length - 1 ]( inputs );
- properties.returnsNode = fnCall.context( { nodeLoop: fnCall } );
- properties.stackNode = stack;
- const baseParam = this.params[ 0 ];
- if ( baseParam.isNode !== true && typeof baseParam.update === 'function' ) {
- const fnUpdateCall = Fn( this.params[ 0 ].update )( inputs );
- properties.updateNode = fnUpdateCall.context( { nodeLoop: fnUpdateCall } );
- }
- builder.removeStack();
- return properties;
- }
- setup( builder ) {
- // setup properties
- this.getProperties( builder );
- if ( builder.fnCall ) {
- const shaderNodeData = builder.getDataFromNode( builder.fnCall.shaderNode );
- shaderNodeData.hasLoop = true;
- }
- }
- generate( builder ) {
- const properties = this.getProperties( builder );
- const params = this.params;
- const stackNode = properties.stackNode;
- for ( let i = 0, l = params.length - 1; i < l; i ++ ) {
- const param = params[ i ];
- let isWhile = false, start = null, end = null, name = null, type = null, condition = null, update = null;
- if ( param.isNode ) {
- if ( param.getNodeType( builder ) === 'bool' ) {
- isWhile = true;
- type = 'bool';
- end = param.build( builder, type );
- } else {
- type = 'int';
- name = this.getVarName( i );
- start = '0';
- end = param.build( builder, type );
- condition = '<';
- }
- } else {
- type = param.type || 'int';
- name = param.name || this.getVarName( i );
- start = param.start;
- end = param.end;
- condition = param.condition;
- update = param.update;
- if ( typeof start === 'number' ) start = builder.generateConst( type, start );
- else if ( start && start.isNode ) start = start.build( builder, type );
- if ( typeof end === 'number' ) end = builder.generateConst( type, end );
- else if ( end && end.isNode ) end = end.build( builder, type );
- if ( start !== undefined && end === undefined ) {
- start = start + ' - 1';
- end = '0';
- condition = '>=';
- } else if ( end !== undefined && start === undefined ) {
- start = '0';
- condition = '<';
- }
- if ( condition === undefined ) {
- if ( Number( start ) > Number( end ) ) {
- condition = '>=';
- } else {
- condition = '<';
- }
- }
- }
- let loopSnippet;
- if ( isWhile ) {
- loopSnippet = `while ( ${ end } )`;
- } else {
- const internalParam = { start, end};
- //
- const startSnippet = internalParam.start;
- const endSnippet = internalParam.end;
- let updateSnippet;
- const deltaOperator = () => condition.includes( '<' ) ? '+=' : '-=';
- if ( update !== undefined && update !== null ) {
- switch ( typeof update ) {
- case 'function':
- const flow = builder.flowStagesNode( properties.updateNode, 'void' );
- const snippet = flow.code.replace( /\t|;/g, '' );
- updateSnippet = snippet;
- break;
- case 'number':
- updateSnippet = name + ' ' + deltaOperator() + ' ' + builder.generateConst( type, update );
- break;
- case 'string':
- updateSnippet = name + ' ' + update;
- break;
- default:
- if ( update.isNode ) {
- updateSnippet = name + ' ' + deltaOperator() + ' ' + update.build( builder );
- } else {
- error( 'TSL: \'Loop( { update: ... } )\' is not a function, string or number.' );
- updateSnippet = 'break /* invalid update */';
- }
- }
- } else {
- if ( type === 'int' || type === 'uint' ) {
- update = condition.includes( '<' ) ? '++' : '--';
- } else {
- update = deltaOperator() + ' 1.';
- }
- updateSnippet = name + ' ' + update;
- }
- const declarationSnippet = builder.getVar( type, name ) + ' = ' + startSnippet;
- const conditionalSnippet = name + ' ' + condition + ' ' + endSnippet;
- loopSnippet = `for ( ${ declarationSnippet }; ${ conditionalSnippet }; ${ updateSnippet } )`;
- }
- builder.addFlowCode( ( i === 0 ? '\n' : '' ) + builder.tab + loopSnippet + ' {\n\n' ).addFlowTab();
- }
- const stackSnippet = stackNode.build( builder, 'void' );
- properties.returnsNode.build( builder, 'void' );
- builder.removeFlowTab().addFlowCode( '\n' + builder.tab + stackSnippet );
- for ( let i = 0, l = this.params.length - 1; i < l; i ++ ) {
- builder.addFlowCode( ( i === 0 ? '' : builder.tab ) + '}\n\n' ).removeFlowTab();
- }
- builder.addFlowTab();
- }
- }
- /**
- * TSL function for creating a loop node.
- *
- * @tsl
- * @function
- * @param {...any} params - A list of parameters.
- * @returns {LoopNode}
- */
- const Loop = ( ...params ) => new LoopNode( nodeArray( params, 'int' ) ).toStack();
- /**
- * TSL function for creating a `Continue()` expression.
- *
- * @tsl
- * @function
- * @returns {ExpressionNode}
- */
- const Continue = () => expression( 'continue' ).toStack();
- /**
- * TSL function for creating a `Break()` expression.
- *
- * @tsl
- * @function
- * @returns {ExpressionNode}
- */
- const Break = () => expression( 'break' ).toStack();
- const _morphTextures = /*@__PURE__*/ new WeakMap();
- const _morphVec4 = /*@__PURE__*/ new Vector4();
- const getMorph = /*@__PURE__*/ Fn( ( { bufferMap, influence, stride, width, depth, offset } ) => {
- const texelIndex = int( vertexIndex ).mul( stride ).add( offset );
- const y = texelIndex.div( width );
- const x = texelIndex.sub( y.mul( width ) );
- const bufferAttrib = textureLoad( bufferMap, ivec2( x, y ) ).depth( depth ).xyz;
- return bufferAttrib.mul( influence );
- } );
- function getEntry( geometry ) {
- const hasMorphPosition = geometry.morphAttributes.position !== undefined;
- const hasMorphNormals = geometry.morphAttributes.normal !== undefined;
- const hasMorphColors = geometry.morphAttributes.color !== undefined;
- // instead of using attributes, the WebGL 2 code path encodes morph targets
- // into an array of data textures. Each layer represents a single morph target.
- const morphAttribute = geometry.morphAttributes.position || geometry.morphAttributes.normal || geometry.morphAttributes.color;
- const morphTargetsCount = ( morphAttribute !== undefined ) ? morphAttribute.length : 0;
- let entry = _morphTextures.get( geometry );
- if ( entry === undefined || entry.count !== morphTargetsCount ) {
- if ( entry !== undefined ) entry.texture.dispose();
- const morphTargets = geometry.morphAttributes.position || [];
- const morphNormals = geometry.morphAttributes.normal || [];
- const morphColors = geometry.morphAttributes.color || [];
- let vertexDataCount = 0;
- if ( hasMorphPosition === true ) vertexDataCount = 1;
- if ( hasMorphNormals === true ) vertexDataCount = 2;
- if ( hasMorphColors === true ) vertexDataCount = 3;
- let width = geometry.attributes.position.count * vertexDataCount;
- let height = 1;
- const maxTextureSize = 4096; // @TODO: Use 'capabilities.maxTextureSize'
- if ( width > maxTextureSize ) {
- height = Math.ceil( width / maxTextureSize );
- width = maxTextureSize;
- }
- const buffer = new Float32Array( width * height * 4 * morphTargetsCount );
- const bufferTexture = new DataArrayTexture( buffer, width, height, morphTargetsCount );
- bufferTexture.type = FloatType;
- bufferTexture.needsUpdate = true;
- // fill buffer
- const vertexDataStride = vertexDataCount * 4;
- for ( let i = 0; i < morphTargetsCount; i ++ ) {
- const morphTarget = morphTargets[ i ];
- const morphNormal = morphNormals[ i ];
- const morphColor = morphColors[ i ];
- const offset = width * height * 4 * i;
- for ( let j = 0; j < morphTarget.count; j ++ ) {
- const stride = j * vertexDataStride;
- if ( hasMorphPosition === true ) {
- _morphVec4.fromBufferAttribute( morphTarget, j );
- buffer[ offset + stride + 0 ] = _morphVec4.x;
- buffer[ offset + stride + 1 ] = _morphVec4.y;
- buffer[ offset + stride + 2 ] = _morphVec4.z;
- buffer[ offset + stride + 3 ] = 0;
- }
- if ( hasMorphNormals === true ) {
- _morphVec4.fromBufferAttribute( morphNormal, j );
- buffer[ offset + stride + 4 ] = _morphVec4.x;
- buffer[ offset + stride + 5 ] = _morphVec4.y;
- buffer[ offset + stride + 6 ] = _morphVec4.z;
- buffer[ offset + stride + 7 ] = 0;
- }
- if ( hasMorphColors === true ) {
- _morphVec4.fromBufferAttribute( morphColor, j );
- buffer[ offset + stride + 8 ] = _morphVec4.x;
- buffer[ offset + stride + 9 ] = _morphVec4.y;
- buffer[ offset + stride + 10 ] = _morphVec4.z;
- buffer[ offset + stride + 11 ] = ( morphColor.itemSize === 4 ) ? _morphVec4.w : 1;
- }
- }
- }
- entry = {
- count: morphTargetsCount,
- texture: bufferTexture,
- stride: vertexDataCount,
- size: new Vector2( width, height )
- };
- _morphTextures.set( geometry, entry );
- function disposeTexture() {
- bufferTexture.dispose();
- _morphTextures.delete( geometry );
- geometry.removeEventListener( 'dispose', disposeTexture );
- }
- geometry.addEventListener( 'dispose', disposeTexture );
- }
- return entry;
- }
- /**
- * This node implements the vertex transformation shader logic which is required
- * for morph target animation.
- *
- * @augments Node
- */
- class MorphNode extends Node {
- static get type() {
- return 'MorphNode';
- }
- /**
- * Constructs a new morph node.
- *
- * @param {Mesh} mesh - The mesh holding the morph targets.
- */
- constructor( mesh ) {
- super( 'void' );
- /**
- * The mesh holding the morph targets.
- *
- * @type {Mesh}
- */
- this.mesh = mesh;
- /**
- * A uniform node which represents the morph base influence value.
- *
- * @type {UniformNode<float>}
- */
- this.morphBaseInfluence = uniform( 1 );
- /**
- * The update type overwritten since morph nodes are updated per object.
- *
- * @type {string}
- */
- this.updateType = NodeUpdateType.OBJECT;
- }
- /**
- * Setups the morph node by assigning the transformed vertex data to predefined node variables.
- *
- * @param {NodeBuilder} builder - The current node builder.
- */
- setup( builder ) {
- const { geometry } = builder;
- const hasMorphPosition = geometry.morphAttributes.position !== undefined;
- const hasMorphNormals = geometry.hasAttribute( 'normal' ) && geometry.morphAttributes.normal !== undefined;
- const morphAttribute = geometry.morphAttributes.position || geometry.morphAttributes.normal || geometry.morphAttributes.color;
- const morphTargetsCount = ( morphAttribute !== undefined ) ? morphAttribute.length : 0;
- // nodes
- const { texture: bufferMap, stride, size } = getEntry( geometry );
- if ( hasMorphPosition === true ) positionLocal.mulAssign( this.morphBaseInfluence );
- if ( hasMorphNormals === true ) normalLocal.mulAssign( this.morphBaseInfluence );
- const width = int( size.width );
- Loop( morphTargetsCount, ( { i } ) => {
- const influence = float( 0 ).toVar();
- if ( this.mesh.count > 1 && ( this.mesh.morphTexture !== null && this.mesh.morphTexture !== undefined ) ) {
- influence.assign( textureLoad( this.mesh.morphTexture, ivec2( int( i ).add( 1 ), int( instanceIndex ) ) ).r );
- } else {
- influence.assign( reference( 'morphTargetInfluences', 'float' ).element( i ).toVar() );
- }
- If( influence.notEqual( 0 ), () => {
- if ( hasMorphPosition === true ) {
- positionLocal.addAssign( getMorph( {
- bufferMap,
- influence,
- stride,
- width,
- depth: i,
- offset: int( 0 )
- } ) );
- }
- if ( hasMorphNormals === true ) {
- normalLocal.addAssign( getMorph( {
- bufferMap,
- influence,
- stride,
- width,
- depth: i,
- offset: int( 1 )
- } ) );
- }
- } );
- } );
- }
- /**
- * Updates the state of the morphed mesh by updating the base influence.
- *
- * @param {NodeFrame} frame - The current node frame.
- */
- update( /*frame*/ ) {
- const morphBaseInfluence = this.morphBaseInfluence;
- if ( this.mesh.geometry.morphTargetsRelative ) {
- morphBaseInfluence.value = 1;
- } else {
- morphBaseInfluence.value = 1 - this.mesh.morphTargetInfluences.reduce( ( a, b ) => a + b, 0 );
- }
- }
- }
- /**
- * TSL function for creating a morph node.
- *
- * @tsl
- * @function
- * @param {Mesh} mesh - The mesh holding the morph targets.
- * @returns {MorphNode}
- */
- const morphReference = /*@__PURE__*/ nodeProxy( MorphNode ).setParameterLength( 1 );
- /**
- * Base class for lighting nodes.
- *
- * @augments Node
- */
- class LightingNode extends Node {
- static get type() {
- return 'LightingNode';
- }
- /**
- * Constructs a new lighting node.
- */
- constructor() {
- super( 'vec3' );
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isLightingNode = true;
- }
- }
- /**
- * A generic class that can be used by nodes which contribute
- * ambient occlusion to the scene. E.g. an ambient occlusion map
- * node can be used as input for this module. Used in {@link NodeMaterial}.
- *
- * @augments LightingNode
- */
- class AONode extends LightingNode {
- static get type() {
- return 'AONode';
- }
- /**
- * Constructs a new AO node.
- *
- * @param {?Node<float>} [aoNode=null] - The ambient occlusion node.
- */
- constructor( aoNode = null ) {
- super();
- /**
- * The ambient occlusion node.
- *
- * @type {?Node<float>}
- * @default null
- */
- this.aoNode = aoNode;
- }
- setup( builder ) {
- builder.context.ambientOcclusion.mulAssign( this.aoNode );
- }
- }
- /**
- * `LightingContextNode` represents an extension of the {@link ContextNode} module
- * by adding lighting specific context data. It represents the runtime context of
- * {@link LightsNode}.
- *
- * @augments ContextNode
- */
- class LightingContextNode extends ContextNode {
- static get type() {
- return 'LightingContextNode';
- }
- /**
- * Constructs a new lighting context node.
- *
- * @param {LightsNode} lightsNode - The lights node.
- * @param {?LightingModel} [lightingModel=null] - The current lighting model.
- * @param {?Node<vec3>} [backdropNode=null] - A backdrop node.
- * @param {?Node<float>} [backdropAlphaNode=null] - A backdrop alpha node.
- */
- constructor( lightsNode, lightingModel = null, backdropNode = null, backdropAlphaNode = null ) {
- super( lightsNode );
- /**
- * The current lighting model.
- *
- * @type {?LightingModel}
- * @default null
- */
- this.lightingModel = lightingModel;
- /**
- * A backdrop node.
- *
- * @type {?Node<vec3>}
- * @default null
- */
- this.backdropNode = backdropNode;
- /**
- * A backdrop alpha node.
- *
- * @type {?Node<float>}
- * @default null
- */
- this.backdropAlphaNode = backdropAlphaNode;
- this._value = null;
- }
- /**
- * Returns a lighting context object.
- *
- * @return {{
- * radiance: Node<vec3>,
- * irradiance: Node<vec3>,
- * iblIrradiance: Node<vec3>,
- * ambientOcclusion: Node<float>,
- * reflectedLight: {directDiffuse: Node<vec3>, directSpecular: Node<vec3>, indirectDiffuse: Node<vec3>, indirectSpecular: Node<vec3>},
- * backdrop: Node<vec3>,
- * backdropAlpha: Node<float>
- * }} The lighting context object.
- */
- getContext() {
- const { backdropNode, backdropAlphaNode } = this;
- const directDiffuse = vec3().toVar( 'directDiffuse' ),
- directSpecular = vec3().toVar( 'directSpecular' ),
- indirectDiffuse = vec3().toVar( 'indirectDiffuse' ),
- indirectSpecular = vec3().toVar( 'indirectSpecular' );
- const reflectedLight = {
- directDiffuse,
- directSpecular,
- indirectDiffuse,
- indirectSpecular
- };
- const context = {
- radiance: vec3().toVar( 'radiance' ),
- irradiance: vec3().toVar( 'irradiance' ),
- iblIrradiance: vec3().toVar( 'iblIrradiance' ),
- ambientOcclusion: float( 1 ).toVar( 'ambientOcclusion' ),
- reflectedLight,
- backdrop: backdropNode,
- backdropAlpha: backdropAlphaNode
- };
- return context;
- }
- setup( builder ) {
- this.value = this._value || ( this._value = this.getContext() );
- this.value.lightingModel = this.lightingModel || builder.context.lightingModel;
- return super.setup( builder );
- }
- }
- const lightingContext = /*@__PURE__*/ nodeProxy( LightingContextNode );
- /**
- * A generic class that can be used by nodes which contribute
- * irradiance to the scene. E.g. a light map node can be used
- * as input for this module. Used in {@link NodeMaterial}.
- *
- * @augments LightingNode
- */
- class IrradianceNode extends LightingNode {
- static get type() {
- return 'IrradianceNode';
- }
- /**
- * Constructs a new irradiance node.
- *
- * @param {Node<vec3>} node - A node contributing irradiance.
- */
- constructor( node ) {
- super();
- /**
- * A node contributing irradiance.
- *
- * @type {Node<vec3>}
- */
- this.node = node;
- }
- setup( builder ) {
- builder.context.irradiance.addAssign( this.node );
- }
- }
- const _size$5 = /*@__PURE__*/ new Vector2();
- /**
- * A special type of texture node which represents the data of the current viewport
- * as a texture. The module extracts data from the current bound framebuffer with
- * a copy operation so no extra render pass is required to produce the texture data
- * (which is good for performance). `ViewportTextureNode` can be used as an input for a
- * variety of effects like refractive or transmissive materials.
- *
- * @augments TextureNode
- */
- class ViewportTextureNode extends TextureNode {
- static get type() {
- return 'ViewportTextureNode';
- }
- /**
- * Constructs a new viewport texture node.
- *
- * @param {Node} [uvNode=screenUV] - The uv node.
- * @param {?Node} [levelNode=null] - The level node.
- * @param {?Texture} [framebufferTexture=null] - A framebuffer texture holding the viewport data. If not provided, a framebuffer texture is created automatically.
- */
- constructor( uvNode = screenUV, levelNode = null, framebufferTexture = null ) {
- let defaultFramebuffer = null;
- if ( framebufferTexture === null ) {
- defaultFramebuffer = new FramebufferTexture();
- defaultFramebuffer.minFilter = LinearMipmapLinearFilter;
- framebufferTexture = defaultFramebuffer;
- } else {
- defaultFramebuffer = framebufferTexture;
- }
- super( framebufferTexture, uvNode, levelNode );
- /**
- * Whether to generate mipmaps or not.
- *
- * @type {boolean}
- * @default false
- */
- this.generateMipmaps = false;
- /**
- * The reference framebuffer texture. This is used to store the framebuffer texture
- * for the current render target. If the render target changes, a new framebuffer texture
- * is created automatically.
- *
- * @type {FramebufferTexture}
- * @default null
- */
- this.defaultFramebuffer = defaultFramebuffer;
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isOutputTextureNode = true;
- /**
- * The `updateBeforeType` is set to `NodeUpdateType.FRAME` since the node renders the
- * scene once per frame in its {@link ViewportTextureNode#updateBefore} method.
- *
- * @type {string}
- * @default 'frame'
- */
- this.updateBeforeType = NodeUpdateType.FRAME;
- /**
- * The framebuffer texture for the current renderer context.
- *
- * @type {WeakMap<RenderTarget, FramebufferTexture>}
- * @private
- */
- this._cacheTextures = new WeakMap();
- }
- /**
- * This methods returns a texture for the given render target reference.
- *
- * To avoid rendering errors, `ViewportTextureNode` must use unique framebuffer textures
- * for different render contexts.
- *
- * @param {?RenderTarget} [reference=null] - The render target reference.
- * @return {Texture} The framebuffer texture.
- */
- getTextureForReference( reference = null ) {
- let defaultFramebuffer;
- let cacheTextures;
- if ( this.referenceNode ) {
- defaultFramebuffer = this.referenceNode.defaultFramebuffer;
- cacheTextures = this.referenceNode._cacheTextures;
- } else {
- defaultFramebuffer = this.defaultFramebuffer;
- cacheTextures = this._cacheTextures;
- }
- if ( reference === null ) {
- return defaultFramebuffer;
- }
- if ( cacheTextures.has( reference ) === false ) {
- const framebufferTexture = defaultFramebuffer.clone();
- cacheTextures.set( reference, framebufferTexture );
- }
- return cacheTextures.get( reference );
- }
- updateReference( frame ) {
- const renderTarget = frame.renderer.getRenderTarget();
- this.value = this.getTextureForReference( renderTarget );
- return this.value;
- }
- updateBefore( frame ) {
- const renderer = frame.renderer;
- const renderTarget = renderer.getRenderTarget();
- if ( renderTarget === null ) {
- renderer.getDrawingBufferSize( _size$5 );
- } else {
- _size$5.set( renderTarget.width, renderTarget.height );
- }
- //
- const framebufferTexture = this.getTextureForReference( renderTarget );
- if ( framebufferTexture.image.width !== _size$5.width || framebufferTexture.image.height !== _size$5.height ) {
- framebufferTexture.image.width = _size$5.width;
- framebufferTexture.image.height = _size$5.height;
- framebufferTexture.needsUpdate = true;
- }
- //
- const currentGenerateMipmaps = framebufferTexture.generateMipmaps;
- framebufferTexture.generateMipmaps = this.generateMipmaps;
- renderer.copyFramebufferToTexture( framebufferTexture );
- framebufferTexture.generateMipmaps = currentGenerateMipmaps;
- }
- clone() {
- const viewportTextureNode = new this.constructor( this.uvNode, this.levelNode, this.value );
- viewportTextureNode.generateMipmaps = this.generateMipmaps;
- return viewportTextureNode;
- }
- }
- /**
- * TSL function for creating a viewport texture node.
- *
- * @tsl
- * @function
- * @param {?Node} [uvNode=screenUV] - The uv node.
- * @param {?Node} [levelNode=null] - The level node.
- * @param {?Texture} [framebufferTexture=null] - A framebuffer texture holding the viewport data. If not provided, a framebuffer texture is created automatically.
- * @returns {ViewportTextureNode}
- */
- const viewportTexture = /*@__PURE__*/ nodeProxy( ViewportTextureNode ).setParameterLength( 0, 3 );
- /**
- * TSL function for creating a viewport texture node with enabled mipmap generation.
- *
- * @tsl
- * @function
- * @param {?Node} [uvNode=screenUV] - The uv node.
- * @param {?Node} [levelNode=null] - The level node.
- * @param {?Texture} [framebufferTexture=null] - A framebuffer texture holding the viewport data. If not provided, a framebuffer texture is created automatically.
- * @returns {ViewportTextureNode}
- */
- const viewportMipTexture = /*@__PURE__*/ nodeProxy( ViewportTextureNode, null, null, { generateMipmaps: true } ).setParameterLength( 0, 3 );
- let _sharedDepthbuffer = null;
- /**
- * Represents the depth of the current viewport as a texture. This module
- * can be used in combination with viewport texture to achieve effects
- * that require depth evaluation.
- *
- * @augments ViewportTextureNode
- */
- class ViewportDepthTextureNode extends ViewportTextureNode {
- static get type() {
- return 'ViewportDepthTextureNode';
- }
- /**
- * Constructs a new viewport depth texture node.
- *
- * @param {Node} [uvNode=screenUV] - The uv node.
- * @param {?Node} [levelNode=null] - The level node.
- */
- constructor( uvNode = screenUV, levelNode = null ) {
- if ( _sharedDepthbuffer === null ) {
- _sharedDepthbuffer = new DepthTexture();
- }
- super( uvNode, levelNode, _sharedDepthbuffer );
- }
- /**
- * Overwritten so the method always returns the unique shared
- * depth texture.
- *
- * @return {DepthTexture} The shared depth texture.
- */
- getTextureForReference() {
- return _sharedDepthbuffer;
- }
- }
- /**
- * TSL function for a viewport depth texture node.
- *
- * @tsl
- * @function
- * @param {?Node} [uvNode=screenUV] - The uv node.
- * @param {?Node} [levelNode=null] - The level node.
- * @returns {ViewportDepthTextureNode}
- */
- const viewportDepthTexture = /*@__PURE__*/ nodeProxy( ViewportDepthTextureNode ).setParameterLength( 0, 2 );
- /**
- * This node offers a collection of features in context of the depth logic in the fragment shader.
- * Depending on {@link ViewportDepthNode#scope}, it can be used to define a depth value for the current
- * fragment or for depth evaluation purposes.
- *
- * @augments Node
- */
- class ViewportDepthNode extends Node {
- static get type() {
- return 'ViewportDepthNode';
- }
- /**
- * Constructs a new viewport depth node.
- *
- * @param {('depth'|'depthBase'|'linearDepth')} scope - The node's scope.
- * @param {?Node} [valueNode=null] - The value node.
- */
- constructor( scope, valueNode = null ) {
- super( 'float' );
- /**
- * The node behaves differently depending on which scope is selected.
- *
- * - `ViewportDepthNode.DEPTH_BASE`: Allows to define a value for the current fragment's depth.
- * - `ViewportDepthNode.DEPTH`: Represents the depth value for the current fragment (`valueNode` is ignored).
- * - `ViewportDepthNode.LINEAR_DEPTH`: Represents the linear (orthographic) depth value of the current fragment.
- * If a `valueNode` is set, the scope can be used to convert perspective depth data to linear data.
- *
- * @type {('depth'|'depthBase'|'linearDepth')}
- */
- this.scope = scope;
- /**
- * Can be used to define a custom depth value.
- * The property is ignored in the `ViewportDepthNode.DEPTH` scope.
- *
- * @type {?Node}
- * @default null
- */
- this.valueNode = valueNode;
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isViewportDepthNode = true;
- }
- generate( builder ) {
- const { scope } = this;
- if ( scope === ViewportDepthNode.DEPTH_BASE ) {
- return builder.getFragDepth();
- }
- return super.generate( builder );
- }
- setup( { camera } ) {
- const { scope } = this;
- const value = this.valueNode;
- let node = null;
- if ( scope === ViewportDepthNode.DEPTH_BASE ) {
- if ( value !== null ) {
- node = depthBase().assign( value );
- }
- } else if ( scope === ViewportDepthNode.DEPTH ) {
- if ( camera.isPerspectiveCamera ) {
- node = viewZToPerspectiveDepth( positionView.z, cameraNear, cameraFar );
- } else {
- node = viewZToOrthographicDepth( positionView.z, cameraNear, cameraFar );
- }
- } else if ( scope === ViewportDepthNode.LINEAR_DEPTH ) {
- if ( value !== null ) {
- if ( camera.isPerspectiveCamera ) {
- const viewZ = perspectiveDepthToViewZ( value, cameraNear, cameraFar );
- node = viewZToOrthographicDepth( viewZ, cameraNear, cameraFar );
- } else {
- node = value;
- }
- } else {
- node = viewZToOrthographicDepth( positionView.z, cameraNear, cameraFar );
- }
- }
- return node;
- }
- }
- ViewportDepthNode.DEPTH_BASE = 'depthBase';
- ViewportDepthNode.DEPTH = 'depth';
- ViewportDepthNode.LINEAR_DEPTH = 'linearDepth';
- // NOTE: viewZ, the z-coordinate in camera space, is negative for points in front of the camera
- /**
- * TSL function for converting a viewZ value to an orthographic depth value.
- *
- * @tsl
- * @function
- * @param {Node<float>} viewZ - The viewZ node.
- * @param {Node<float>} near - The camera's near value.
- * @param {Node<float>} far - The camera's far value.
- * @returns {Node<float>}
- */
- const viewZToOrthographicDepth = ( viewZ, near, far ) => viewZ.add( near ).div( near.sub( far ) );
- /**
- * TSL function for converting an orthographic depth value to a viewZ value.
- *
- * @tsl
- * @function
- * @param {Node<float>} depth - The orthographic depth.
- * @param {Node<float>} near - The camera's near value.
- * @param {Node<float>} far - The camera's far value.
- * @returns {Node<float>}
- */
- const orthographicDepthToViewZ = ( depth, near, far ) => near.sub( far ).mul( depth ).sub( near );
- /**
- * TSL function for converting a viewZ value to a perspective depth value.
- *
- * Note: {link https://twitter.com/gonnavis/status/1377183786949959682}.
- *
- * @tsl
- * @function
- * @param {Node<float>} viewZ - The viewZ node.
- * @param {Node<float>} near - The camera's near value.
- * @param {Node<float>} far - The camera's far value.
- * @returns {Node<float>}
- */
- const viewZToPerspectiveDepth = ( viewZ, near, far ) => near.add( viewZ ).mul( far ).div( far.sub( near ).mul( viewZ ) );
- /**
- * TSL function for converting a perspective depth value to a viewZ value.
- *
- * @tsl
- * @function
- * @param {Node<float>} depth - The perspective depth.
- * @param {Node<float>} near - The camera's near value.
- * @param {Node<float>} far - The camera's far value.
- * @returns {Node<float>}
- */
- const perspectiveDepthToViewZ = ( depth, near, far ) => near.mul( far ).div( far.sub( near ).mul( depth ).sub( far ) );
- /**
- * TSL function for converting a viewZ value to a logarithmic depth value.
- *
- * @tsl
- * @function
- * @param {Node<float>} viewZ - The viewZ node.
- * @param {Node<float>} near - The camera's near value.
- * @param {Node<float>} far - The camera's far value.
- * @returns {Node<float>}
- */
- const viewZToLogarithmicDepth = ( viewZ, near, far ) => {
- // NOTE: viewZ must be negative--see explanation at the end of this comment block.
- // The final logarithmic depth formula used here is adapted from one described in an
- // article by Thatcher Ulrich (see http://tulrich.com/geekstuff/log_depth_buffer.txt),
- // which was an improvement upon an earlier formula one described in an
- // Outerra article (https://outerra.blogspot.com/2009/08/logarithmic-z-buffer.html).
- // Ulrich's formula is the following:
- // z = K * log( w / cameraNear ) / log( cameraFar / cameraNear )
- // where K = 2^k - 1, and k is the number of bits in the depth buffer.
- // The Outerra variant ignored the camera near plane (it assumed it was 0) and instead
- // opted for a "C-constant" for resolution adjustment of objects near the camera.
- // Outerra states: "Notice that the 'C' variant doesn’t use a near plane distance, it has it
- // set at 0" (quote from https://outerra.blogspot.com/2012/11/maximizing-depth-buffer-range-and.html).
- // Ulrich's variant has the benefit of constant relative precision over the whole near-far range.
- // It was debated here whether Outerra's "C-constant" or Ulrich's "near plane" variant should
- // be used, and ultimately Ulrich's "near plane" version was chosen.
- // Outerra eventually made another improvement to their original "C-constant" variant,
- // but it still does not incorporate the camera near plane (for this version,
- // see https://outerra.blogspot.com/2013/07/logarithmic-depth-buffer-optimizations.html).
- // Here we make 4 changes to Ulrich's formula:
- // 1. Clamp the camera near plane so we don't divide by 0.
- // 2. Use log2 instead of log to avoid an extra multiply (shaders implement log using log2).
- // 3. Assume K is 1 (K = maximum value in depth buffer; see Ulrich's formula above).
- // 4. To maintain consistency with the functions "viewZToOrthographicDepth" and "viewZToPerspectiveDepth",
- // we modify the formula here to use 'viewZ' instead of 'w'. The other functions expect a negative viewZ,
- // so we do the same here, hence the 'viewZ.negate()' call.
- // For visual representation of this depth curve, see https://www.desmos.com/calculator/uyqk0vex1u
- near = near.max( 1e-6 ).toVar();
- const numerator = log2( viewZ.negate().div( near ) );
- const denominator = log2( far.div( near ) );
- return numerator.div( denominator );
- };
- /**
- * TSL function for converting a logarithmic depth value to a viewZ value.
- *
- * @tsl
- * @function
- * @param {Node<float>} depth - The logarithmic depth.
- * @param {Node<float>} near - The camera's near value.
- * @param {Node<float>} far - The camera's far value.
- * @returns {Node<float>}
- */
- const logarithmicDepthToViewZ = ( depth, near, far ) => {
- // NOTE: we add a 'negate()' call to the return value here to maintain consistency with
- // the functions "orthographicDepthToViewZ" and "perspectiveDepthToViewZ" (they return
- // a negative viewZ).
- const exponent = depth.mul( log( far.div( near ) ) );
- return float( Math.E ).pow( exponent ).mul( near ).negate();
- };
- /**
- * TSL function for defining a value for the current fragment's depth.
- *
- * @tsl
- * @function
- * @param {Node<float>} value - The depth value to set.
- * @returns {ViewportDepthNode<float>}
- */
- const depthBase = /*@__PURE__*/ nodeProxy( ViewportDepthNode, ViewportDepthNode.DEPTH_BASE );
- /**
- * TSL object that represents the depth value for the current fragment.
- *
- * @tsl
- * @type {ViewportDepthNode}
- */
- const depth = /*@__PURE__*/ nodeImmutable( ViewportDepthNode, ViewportDepthNode.DEPTH );
- /**
- * TSL function for converting a perspective depth value to linear depth.
- *
- * @tsl
- * @function
- * @param {?Node<float>} [value=null] - The perspective depth. If `null` is provided, the current fragment's depth is used.
- * @returns {ViewportDepthNode<float>}
- */
- const linearDepth = /*@__PURE__*/ nodeProxy( ViewportDepthNode, ViewportDepthNode.LINEAR_DEPTH ).setParameterLength( 0, 1 );
- /**
- * TSL object that represents the linear (orthographic) depth value of the current fragment
- *
- * @tsl
- * @type {ViewportDepthNode}
- */
- const viewportLinearDepth = /*@__PURE__*/ linearDepth( viewportDepthTexture() );
- depth.assign = ( value ) => depthBase( value );
- /**
- * This node is used in {@link NodeMaterial} to setup the clipping
- * which can happen hardware-accelerated (if supported) and optionally
- * use alpha-to-coverage for anti-aliasing clipped edges.
- *
- * @augments Node
- */
- class ClippingNode extends Node {
- static get type() {
- return 'ClippingNode';
- }
- /**
- * Constructs a new clipping node.
- *
- * @param {('default'|'hardware'|'alphaToCoverage')} [scope='default'] - The node's scope. Similar to other nodes,
- * the selected scope influences the behavior of the node and what type of code is generated.
- */
- constructor( scope = ClippingNode.DEFAULT ) {
- super();
- /**
- * The node's scope. Similar to other nodes, the selected scope influences
- * the behavior of the node and what type of code is generated.
- *
- * @type {('default'|'hardware'|'alphaToCoverage')}
- */
- this.scope = scope;
- }
- /**
- * Setups the node depending on the selected scope.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {Node} The result node.
- */
- setup( builder ) {
- super.setup( builder );
- const clippingContext = builder.clippingContext;
- const { intersectionPlanes, unionPlanes } = clippingContext;
- this.hardwareClipping = builder.material.hardwareClipping;
- if ( this.scope === ClippingNode.ALPHA_TO_COVERAGE ) {
- return this.setupAlphaToCoverage( intersectionPlanes, unionPlanes );
- } else if ( this.scope === ClippingNode.HARDWARE ) {
- return this.setupHardwareClipping( unionPlanes, builder );
- } else {
- return this.setupDefault( intersectionPlanes, unionPlanes );
- }
- }
- /**
- * Setups alpha to coverage.
- *
- * @param {Array<Vector4>} intersectionPlanes - The intersection planes.
- * @param {Array<Vector4>} unionPlanes - The union planes.
- * @return {Node} The result node.
- */
- setupAlphaToCoverage( intersectionPlanes, unionPlanes ) {
- return Fn( () => {
- const distanceToPlane = float().toVar( 'distanceToPlane' );
- const distanceGradient = float().toVar( 'distanceToGradient' );
- const clipOpacity = float( 1 ).toVar( 'clipOpacity' );
- const numUnionPlanes = unionPlanes.length;
- if ( this.hardwareClipping === false && numUnionPlanes > 0 ) {
- const clippingPlanes = uniformArray( unionPlanes ).setGroup( renderGroup );
- Loop( numUnionPlanes, ( { i } ) => {
- const plane = clippingPlanes.element( i );
- distanceToPlane.assign( positionView.dot( plane.xyz ).negate().add( plane.w ) );
- distanceGradient.assign( distanceToPlane.fwidth().div( 2.0 ) );
- clipOpacity.mulAssign( smoothstep( distanceGradient.negate(), distanceGradient, distanceToPlane ) );
- } );
- }
- const numIntersectionPlanes = intersectionPlanes.length;
- if ( numIntersectionPlanes > 0 ) {
- const clippingPlanes = uniformArray( intersectionPlanes ).setGroup( renderGroup );
- const intersectionClipOpacity = float( 1 ).toVar( 'intersectionClipOpacity' );
- Loop( numIntersectionPlanes, ( { i } ) => {
- const plane = clippingPlanes.element( i );
- distanceToPlane.assign( positionView.dot( plane.xyz ).negate().add( plane.w ) );
- distanceGradient.assign( distanceToPlane.fwidth().div( 2.0 ) );
- intersectionClipOpacity.mulAssign( smoothstep( distanceGradient.negate(), distanceGradient, distanceToPlane ).oneMinus() );
- } );
- clipOpacity.mulAssign( intersectionClipOpacity.oneMinus() );
- }
- diffuseColor.a.mulAssign( clipOpacity );
- diffuseColor.a.equal( 0.0 ).discard();
- } )();
- }
- /**
- * Setups the default clipping.
- *
- * @param {Array<Vector4>} intersectionPlanes - The intersection planes.
- * @param {Array<Vector4>} unionPlanes - The union planes.
- * @return {Node} The result node.
- */
- setupDefault( intersectionPlanes, unionPlanes ) {
- return Fn( () => {
- const numUnionPlanes = unionPlanes.length;
- if ( this.hardwareClipping === false && numUnionPlanes > 0 ) {
- const clippingPlanes = uniformArray( unionPlanes ).setGroup( renderGroup );
- Loop( numUnionPlanes, ( { i } ) => {
- const plane = clippingPlanes.element( i );
- positionView.dot( plane.xyz ).greaterThan( plane.w ).discard();
- } );
- }
- const numIntersectionPlanes = intersectionPlanes.length;
- if ( numIntersectionPlanes > 0 ) {
- const clippingPlanes = uniformArray( intersectionPlanes ).setGroup( renderGroup );
- const clipped = bool( true ).toVar( 'clipped' );
- Loop( numIntersectionPlanes, ( { i } ) => {
- const plane = clippingPlanes.element( i );
- clipped.assign( positionView.dot( plane.xyz ).greaterThan( plane.w ).and( clipped ) );
- } );
- clipped.discard();
- }
- } )();
- }
- /**
- * Setups hardware clipping.
- *
- * @param {Array<Vector4>} unionPlanes - The union planes.
- * @param {NodeBuilder} builder - The current node builder.
- * @return {Node} The result node.
- */
- setupHardwareClipping( unionPlanes, builder ) {
- const numUnionPlanes = unionPlanes.length;
- builder.enableHardwareClipping( numUnionPlanes );
- return Fn( () => {
- const clippingPlanes = uniformArray( unionPlanes ).setGroup( renderGroup );
- const hw_clip_distances = builtin( builder.getClipDistance() );
- Loop( numUnionPlanes, ( { i } ) => {
- const plane = clippingPlanes.element( i );
- const distance = positionView.dot( plane.xyz ).sub( plane.w ).negate();
- hw_clip_distances.element( i ).assign( distance );
- } );
- } )();
- }
- }
- ClippingNode.ALPHA_TO_COVERAGE = 'alphaToCoverage';
- ClippingNode.DEFAULT = 'default';
- ClippingNode.HARDWARE = 'hardware';
- /**
- * TSL function for setting up the default clipping logic.
- *
- * @tsl
- * @function
- * @returns {ClippingNode}
- */
- const clipping = () => nodeObject( new ClippingNode() );
- /**
- * TSL function for setting up alpha to coverage.
- *
- * @tsl
- * @function
- * @returns {ClippingNode}
- */
- const clippingAlpha = () => nodeObject( new ClippingNode( ClippingNode.ALPHA_TO_COVERAGE ) );
- /**
- * TSL function for setting up hardware-based clipping.
- *
- * @tsl
- * @function
- * @returns {ClippingNode}
- */
- const hardwareClipping = () => nodeObject( new ClippingNode( ClippingNode.HARDWARE ) );
- // See: https://casual-effects.com/research/Wyman2017Hashed/index.html
- const ALPHA_HASH_SCALE = 0.05; // Derived from trials only, and may be changed.
- const hash2D = /*@__PURE__*/ Fn( ( [ value ] ) => {
- return fract( mul( 1.0e4, sin( mul( 17.0, value.x ).add( mul( 0.1, value.y ) ) ) ).mul( add( 0.1, abs( sin( mul( 13.0, value.y ).add( value.x ) ) ) ) ) );
- } );
- const hash3D = /*@__PURE__*/ Fn( ( [ value ] ) => {
- return hash2D( vec2( hash2D( value.xy ), value.z ) );
- } );
- const getAlphaHashThreshold = /*@__PURE__*/ Fn( ( [ position ] ) => {
- // Find the discretized derivatives of our coordinates
- const maxDeriv = max$1(
- length( dFdx( position.xyz ) ),
- length( dFdy( position.xyz ) )
- );
- const pixScale = float( 1 ).div( float( ALPHA_HASH_SCALE ).mul( maxDeriv ) ).toVar( 'pixScale' );
- // Find two nearest log-discretized noise scales
- const pixScales = vec2(
- exp2( floor( log2( pixScale ) ) ),
- exp2( ceil( log2( pixScale ) ) )
- );
- // Compute alpha thresholds at our two noise scales
- const alpha = vec2(
- hash3D( floor( pixScales.x.mul( position.xyz ) ) ),
- hash3D( floor( pixScales.y.mul( position.xyz ) ) ),
- );
- // Factor to interpolate lerp with
- const lerpFactor = fract( log2( pixScale ) );
- // Interpolate alpha threshold from noise at two scales
- const x = add( mul( lerpFactor.oneMinus(), alpha.x ), mul( lerpFactor, alpha.y ) );
- // Pass into CDF to compute uniformly distrib threshold
- const a = min$1( lerpFactor, lerpFactor.oneMinus() );
- const cases = vec3(
- x.mul( x ).div( mul( 2.0, a ).mul( sub( 1.0, a ) ) ),
- x.sub( mul( 0.5, a ) ).div( sub( 1.0, a ) ),
- sub( 1.0, sub( 1.0, x ).mul( sub( 1.0, x ) ).div( mul( 2.0, a ).mul( sub( 1.0, a ) ) ) ) );
- // Find our final, uniformly distributed alpha threshold (ατ)
- const threshold = x.lessThan( a.oneMinus() ).select( x.lessThan( a ).select( cases.x, cases.y ), cases.z );
- // Avoids ατ == 0. Could also do ατ =1-ατ
- return clamp( threshold, 1.0e-6, 1.0 );
- } ).setLayout( {
- name: 'getAlphaHashThreshold',
- type: 'float',
- inputs: [
- { name: 'position', type: 'vec3' }
- ]
- } );
- /**
- * An attribute node for representing vertex colors.
- *
- * @augments AttributeNode
- */
- class VertexColorNode extends AttributeNode {
- static get type() {
- return 'VertexColorNode';
- }
- /**
- * Constructs a new vertex color node.
- *
- * @param {number} index - The attribute index.
- */
- constructor( index ) {
- super( null, 'vec4' );
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isVertexColorNode = true;
- /**
- * The attribute index to enable more than one sets of vertex colors.
- *
- * @type {number}
- * @default 0
- */
- this.index = index;
- }
- /**
- * Overwrites the default implementation by honoring the attribute index.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {string} The attribute name.
- */
- getAttributeName( /*builder*/ ) {
- const index = this.index;
- return 'color' + ( index > 0 ? index : '' );
- }
- generate( builder ) {
- const attributeName = this.getAttributeName( builder );
- const geometryAttribute = builder.hasGeometryAttribute( attributeName );
- let result;
- if ( geometryAttribute === true ) {
- result = super.generate( builder );
- } else {
- // Vertex color fallback should be white
- result = builder.generateConst( this.nodeType, new Vector4( 1, 1, 1, 1 ) );
- }
- return result;
- }
- serialize( data ) {
- super.serialize( data );
- data.index = this.index;
- }
- deserialize( data ) {
- super.deserialize( data );
- this.index = data.index;
- }
- }
- /**
- * TSL function for creating a reference node.
- *
- * @tsl
- * @function
- * @param {number} [index=0] - The attribute index.
- * @returns {VertexColorNode}
- */
- const vertexColor = ( index = 0 ) => nodeObject( new VertexColorNode( index ) );
- /**
- * Represents a "Color Burn" blend mode.
- *
- * It's designed to darken the base layer's colors based on the color of the blend layer.
- * It significantly increases the contrast of the base layer, making the colors more vibrant and saturated.
- * The darker the color in the blend layer, the stronger the darkening and contrast effect on the base layer.
- *
- * @tsl
- * @function
- * @param {Node<vec3>} base - The base color.
- * @param {Node<vec3>} blend - The blend color. A white (#ffffff) blend color does not alter the base color.
- * @return {Node<vec3>} The result.
- */
- const blendBurn = /*@__PURE__*/ Fn( ( [ base, blend ] ) => {
- return min$1( 1.0, base.oneMinus().div( blend ) ).oneMinus();
- } ).setLayout( {
- name: 'blendBurn',
- type: 'vec3',
- inputs: [
- { name: 'base', type: 'vec3' },
- { name: 'blend', type: 'vec3' }
- ]
- } );
- /**
- * Represents a "Color Dodge" blend mode.
- *
- * It's designed to lighten the base layer's colors based on the color of the blend layer.
- * It significantly increases the brightness of the base layer, making the colors lighter and more vibrant.
- * The brighter the color in the blend layer, the stronger the lightening and contrast effect on the base layer.
- *
- * @tsl
- * @function
- * @param {Node<vec3>} base - The base color.
- * @param {Node<vec3>} blend - The blend color. A black (#000000) blend color does not alter the base color.
- * @return {Node<vec3>} The result.
- */
- const blendDodge = /*@__PURE__*/ Fn( ( [ base, blend ] ) => {
- return min$1( base.div( blend.oneMinus() ), 1.0 );
- } ).setLayout( {
- name: 'blendDodge',
- type: 'vec3',
- inputs: [
- { name: 'base', type: 'vec3' },
- { name: 'blend', type: 'vec3' }
- ]
- } );
- /**
- * Represents a "Screen" blend mode.
- *
- * Similar to `blendDodge()`, this mode also lightens the base layer's colors based on the color of the blend layer.
- * The "Screen" blend mode is better for general brightening whereas the "Dodge" results in more subtle and nuanced
- * effects.
- *
- * @tsl
- * @function
- * @param {Node<vec3>} base - The base color.
- * @param {Node<vec3>} blend - The blend color. A black (#000000) blend color does not alter the base color.
- * @return {Node<vec3>} The result.
- */
- const blendScreen = /*@__PURE__*/ Fn( ( [ base, blend ] ) => {
- return base.oneMinus().mul( blend.oneMinus() ).oneMinus();
- } ).setLayout( {
- name: 'blendScreen',
- type: 'vec3',
- inputs: [
- { name: 'base', type: 'vec3' },
- { name: 'blend', type: 'vec3' }
- ]
- } );
- /**
- * Represents a "Overlay" blend mode.
- *
- * It's designed to increase the contrast of the base layer based on the color of the blend layer.
- * It amplifies the existing colors and contrast in the base layer, making lighter areas lighter and darker areas darker.
- * The color of the blend layer significantly influences the resulting contrast and color shift in the base layer.
- *
- * @tsl
- * @function
- * @param {Node<vec3>} base - The base color.
- * @param {Node<vec3>} blend - The blend color
- * @return {Node<vec3>} The result.
- */
- const blendOverlay = /*@__PURE__*/ Fn( ( [ base, blend ] ) => {
- return mix( base.mul( 2.0 ).mul( blend ), base.oneMinus().mul( 2.0 ).mul( blend.oneMinus() ).oneMinus(), step( 0.5, base ) );
- } ).setLayout( {
- name: 'blendOverlay',
- type: 'vec3',
- inputs: [
- { name: 'base', type: 'vec3' },
- { name: 'blend', type: 'vec3' }
- ]
- } );
- /**
- * This function blends two color based on their alpha values by replicating the behavior of `THREE.NormalBlending`.
- * It assumes both input colors have non-premultiplied alpha.
- *
- * @tsl
- * @function
- * @param {Node<vec4>} base - The base color.
- * @param {Node<vec4>} blend - The blend color
- * @return {Node<vec4>} The result.
- */
- const blendColor = /*@__PURE__*/ Fn( ( [ base, blend ] ) => {
- const outAlpha = blend.a.add( base.a.mul( blend.a.oneMinus() ) );
- return vec4( blend.rgb.mul( blend.a ).add( base.rgb.mul( base.a ).mul( blend.a.oneMinus() ) ).div( outAlpha ), outAlpha );
- } ).setLayout( {
- name: 'blendColor',
- type: 'vec4',
- inputs: [
- { name: 'base', type: 'vec4' },
- { name: 'blend', type: 'vec4' }
- ]
- } );
- /**
- * Premultiplies the RGB channels of a color by its alpha channel.
- *
- * This function is useful for converting a non-premultiplied alpha color
- * into a premultiplied alpha format, where the RGB values are scaled
- * by the alpha value. Premultiplied alpha is often used in graphics
- * rendering for certain operations, such as compositing and image processing.
- *
- * @tsl
- * @function
- * @param {Node<vec4>} color - The input color with non-premultiplied alpha.
- * @return {Node<vec4>} The color with premultiplied alpha.
- */
- const premultiplyAlpha = /*@__PURE__*/ Fn( ( [ color ] ) => {
- return vec4( color.rgb.mul( color.a ), color.a );
- }, { color: 'vec4', return: 'vec4' } );
- /**
- * Unpremultiplies the RGB channels of a color by its alpha channel.
- *
- * This function is useful for converting a premultiplied alpha color
- * back into a non-premultiplied alpha format, where the RGB values are
- * divided by the alpha value. Unpremultiplied alpha is often used in graphics
- * rendering for certain operations, such as compositing and image processing.
- *
- * @tsl
- * @function
- * @param {Node<vec4>} color - The input color with premultiplied alpha.
- * @return {Node<vec4>} The color with non-premultiplied alpha.
- */
- const unpremultiplyAlpha = /*@__PURE__*/ Fn( ( [ color ] ) => {
- If( color.a.equal( 0.0 ), () => vec4( 0.0 ) );
- return vec4( color.rgb.div( color.a ), color.a );
- }, { color: 'vec4', return: 'vec4' } );
- // Deprecated
- /**
- * @tsl
- * @function
- * @deprecated since r171. Use {@link blendBurn} instead.
- *
- * @param {...any} params
- * @returns {Function}
- */
- const burn = ( ...params ) => { // @deprecated, r171
- warn( 'TSL: "burn" has been renamed. Use "blendBurn" instead.' );
- return blendBurn( params );
- };
- /**
- * @tsl
- * @function
- * @deprecated since r171. Use {@link blendDodge} instead.
- *
- * @param {...any} params
- * @returns {Function}
- */
- const dodge = ( ...params ) => { // @deprecated, r171
- warn( 'TSL: "dodge" has been renamed. Use "blendDodge" instead.' );
- return blendDodge( params );
- };
- /**
- * @tsl
- * @function
- * @deprecated since r171. Use {@link blendScreen} instead.
- *
- * @param {...any} params
- * @returns {Function}
- */
- const screen = ( ...params ) => { // @deprecated, r171
- warn( 'TSL: "screen" has been renamed. Use "blendScreen" instead.' );
- return blendScreen( params );
- };
- /**
- * @tsl
- * @function
- * @deprecated since r171. Use {@link blendOverlay} instead.
- *
- * @param {...any} params
- * @returns {Function}
- */
- const overlay = ( ...params ) => { // @deprecated, r171
- warn( 'TSL: "overlay" has been renamed. Use "blendOverlay" instead.' );
- return blendOverlay( params );
- };
- /**
- * Base class for all node materials.
- *
- * @augments Material
- */
- class NodeMaterial extends Material {
- static get type() {
- return 'NodeMaterial';
- }
- /**
- * Represents the type of the node material.
- *
- * @type {string}
- */
- get type() {
- return this.constructor.type;
- }
- set type( _value ) { /* */ }
- /**
- * Constructs a new node material.
- */
- constructor() {
- super();
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isNodeMaterial = true;
- /**
- * Whether this material is affected by fog or not.
- *
- * @type {boolean}
- * @default true
- */
- this.fog = true;
- /**
- * Whether this material is affected by lights or not.
- *
- * @type {boolean}
- * @default false
- */
- this.lights = false;
- /**
- * Whether this material uses hardware clipping or not.
- * This property is managed by the engine and should not be
- * modified by apps.
- *
- * @type {boolean}
- * @default false
- */
- this.hardwareClipping = false;
- /**
- * Node materials which set their `lights` property to `true`
- * are affected by all lights of the scene. Sometimes selective
- * lighting is wanted which means only _some_ lights in the scene
- * affect a material. This can be achieved by creating an instance
- * of {@link LightsNode} with a list of selective
- * lights and assign the node to this property.
- *
- * ```js
- * const customLightsNode = lights( [ light1, light2 ] );
- * material.lightsNode = customLightsNode;
- * ```
- *
- * @type {?LightsNode}
- * @default null
- */
- this.lightsNode = null;
- /**
- * The environment of node materials can be defined by an environment
- * map assigned to the `envMap` property or by `Scene.environment`
- * if the node material is a PBR material. This node property allows to overwrite
- * the default behavior and define the environment with a custom node.
- *
- * ```js
- * material.envNode = pmremTexture( renderTarget.texture );
- * ```
- *
- * @type {?Node<vec3>}
- * @default null
- */
- this.envNode = null;
- /**
- * The lighting of node materials might be influenced by ambient occlusion.
- * The default AO is inferred from an ambient occlusion map assigned to `aoMap`
- * and the respective `aoMapIntensity`. This node property allows to overwrite
- * the default and define the ambient occlusion with a custom node instead.
- *
- * If you don't want to overwrite the diffuse color but modify the existing
- * values instead, use {@link materialAO}.
- *
- * @type {?Node<float>}
- * @default null
- */
- this.aoNode = null;
- /**
- * The diffuse color of node materials is by default inferred from the
- * `color` and `map` properties. This node property allows to overwrite the default
- * and define the diffuse color with a node instead.
- *
- * ```js
- * material.colorNode = color( 0xff0000 ); // define red color
- * ```
- *
- * If you don't want to overwrite the diffuse color but modify the existing
- * values instead, use {@link materialColor}.
- *
- * ```js
- * material.colorNode = materialColor.mul( color( 0xff0000 ) ); // give diffuse colors a red tint
- * ```
- *
- * @type {?Node<vec3>}
- * @default null
- */
- this.colorNode = null;
- /**
- * The normals of node materials are by default inferred from the `normalMap`/`normalScale`
- * or `bumpMap`/`bumpScale` properties. This node property allows to overwrite the default
- * and define the normals with a node instead.
- *
- * If you don't want to overwrite the normals but modify the existing values instead,
- * use {@link materialNormal}.
- *
- * @type {?Node<vec3>}
- * @default null
- */
- this.normalNode = null;
- /**
- * The opacity of node materials is by default inferred from the `opacity`
- * and `alphaMap` properties. This node property allows to overwrite the default
- * and define the opacity with a node instead.
- *
- * If you don't want to overwrite the opacity but modify the existing
- * value instead, use {@link materialOpacity}.
- *
- * @type {?Node<float>}
- * @default null
- */
- this.opacityNode = null;
- /**
- * This node can be used to implement a variety of filter-like effects. The idea is
- * to store the current rendering into a texture e.g. via `viewportSharedTexture()`, use it
- * to create an arbitrary effect and then assign the node composition to this property.
- * Everything behind the object using this material will now be affected by a filter.
- *
- * ```js
- * const material = new NodeMaterial()
- * material.transparent = true;
- *
- * // everything behind the object will be monochromatic
- * material.backdropNode = saturation( viewportSharedTexture().rgb, 0 );
- * ```
- *
- * Backdrop computations are part of the lighting so only lit materials can use this property.
- *
- * @type {?Node<vec3>}
- * @default null
- */
- this.backdropNode = null;
- /**
- * This node allows to modulate the influence of `backdropNode` to the outgoing light.
- *
- * @type {?Node<float>}
- * @default null
- */
- this.backdropAlphaNode = null;
- /**
- * The alpha test of node materials is by default inferred from the `alphaTest`
- * property. This node property allows to overwrite the default and define the
- * alpha test with a node instead.
- *
- * If you don't want to overwrite the alpha test but modify the existing
- * value instead, use {@link materialAlphaTest}.
- *
- * @type {?Node<float>}
- * @default null
- */
- this.alphaTestNode = null;
- /**
- * Discards the fragment if the mask value is `false`.
- *
- * @type {?Node<bool>}
- * @default null
- */
- this.maskNode = null;
- /**
- * The local vertex positions are computed based on multiple factors like the
- * attribute data, morphing or skinning. This node property allows to overwrite
- * the default and define local vertex positions with nodes instead.
- *
- * If you don't want to overwrite the vertex positions but modify the existing
- * values instead, use {@link positionLocal}.
- *
- *```js
- * material.positionNode = positionLocal.add( displace );
- * ```
- *
- * @type {?Node<vec3>}
- * @default null
- */
- this.positionNode = null;
- /**
- * This node property is intended for logic which modifies geometry data once or per animation step.
- * Apps usually place such logic randomly in initialization routines or in the animation loop.
- * `geometryNode` is intended as a dedicated API so there is an intended spot where geometry modifications
- * can be implemented.
- *
- * The idea is to assign a `Fn` definition that holds the geometry modification logic. A typical example
- * would be a GPU based particle system that provides a node material for usage on app level. The particle
- * simulation would be implemented as compute shaders and managed inside a `Fn` function. This function is
- * eventually assigned to `geometryNode`.
- *
- * @type {?Function}
- * @default null
- */
- this.geometryNode = null;
- /**
- * Allows to overwrite depth values in the fragment shader.
- *
- * @type {?Node<float>}
- * @default null
- */
- this.depthNode = null;
- /**
- * Allows to overwrite the position used for shadow map rendering which
- * is by default {@link positionWorld}, the vertex position
- * in world space.
- *
- * @type {?Node<float>}
- * @default null
- */
- this.receivedShadowPositionNode = null;
- /**
- * Allows to overwrite the geometry position used for shadow map projection which
- * is by default {@link positionLocal}, the vertex position in local space.
- *
- * @type {?Node<float>}
- * @default null
- */
- this.castShadowPositionNode = null;
- /**
- * This node can be used to influence how an object using this node material
- * receive shadows.
- *
- * ```js
- * const totalShadows = float( 1 ).toVar();
- * material.receivedShadowNode = Fn( ( [ shadow ] ) => {
- * totalShadows.mulAssign( shadow );
- * //return float( 1 ); // bypass received shadows
- * return shadow.mix( color( 0xff0000 ), 1 ); // modify shadow color
- * } );
- *
- * @type {?(Function|FunctionNode<vec4>)}
- * @default null
- */
- this.receivedShadowNode = null;
- /**
- * This node can be used to influence how an object using this node material
- * casts shadows. To apply a color to shadows, you can simply do:
- *
- * ```js
- * material.castShadowNode = vec4( 1, 0, 0, 1 );
- * ```
- *
- * Which can be nice to fake colored shadows of semi-transparent objects. It
- * is also common to use the property with `Fn` function so checks are performed
- * per fragment.
- *
- * ```js
- * materialCustomShadow.castShadowNode = Fn( () => {
- * hash( vertexIndex ).greaterThan( 0.5 ).discard();
- * return materialColor;
- * } )();
- * ```
- *
- * @type {?Node<vec4>}
- * @default null
- */
- this.castShadowNode = null;
- /**
- * This node can be used to define the final output of the material.
- *
- * TODO: Explain the differences to `fragmentNode`.
- *
- * @type {?Node<vec4>}
- * @default null
- */
- this.outputNode = null;
- /**
- * MRT configuration is done on renderer or pass level. This node allows to
- * overwrite what values are written into MRT targets on material level. This
- * can be useful for implementing selective FX features that should only affect
- * specific objects.
- *
- * @type {?MRTNode}
- * @default null
- */
- this.mrtNode = null;
- /**
- * This node property can be used if you need complete freedom in implementing
- * the fragment shader. Assigning a node will replace the built-in material
- * logic used in the fragment stage.
- *
- * @type {?Node<vec4>}
- * @default null
- */
- this.fragmentNode = null;
- /**
- * This node property can be used if you need complete freedom in implementing
- * the vertex shader. Assigning a node will replace the built-in material logic
- * used in the vertex stage.
- *
- * @type {?Node<vec4>}
- * @default null
- */
- this.vertexNode = null;
- /**
- * This node can be used as a global context management component for this material.
- *
- * @type {?ContextNode}
- * @default null
- */
- this.contextNode = null;
- // Deprecated properties
- Object.defineProperty( this, 'shadowPositionNode', { // @deprecated, r176
- get: () => {
- return this.receivedShadowPositionNode;
- },
- set: ( value ) => {
- warn( 'NodeMaterial: ".shadowPositionNode" was renamed to ".receivedShadowPositionNode".' );
- this.receivedShadowPositionNode = value;
- }
- } );
- }
- /**
- * Returns an array of child nodes for this material.
- *
- * @private
- * @returns {Array<{property: string, childNode: Node}>}
- */
- _getNodeChildren() {
- const children = [];
- for ( const property of Object.getOwnPropertyNames( this ) ) {
- if ( property.startsWith( '_' ) === true ) continue;
- const object = this[ property ];
- if ( object && object.isNode === true ) {
- children.push( { property, childNode: object } );
- }
- }
- return children;
- }
- /**
- * Allows to define a custom cache key that influence the material key computation
- * for render objects.
- *
- * @return {string} The custom cache key.
- */
- customProgramCacheKey() {
- const values = [];
- for ( const { property, childNode } of this._getNodeChildren() ) {
- values.push( hashString( property.slice( 0, -4 ) ), childNode.getCacheKey() );
- }
- return this.type + hashArray( values );
- }
- /**
- * Builds this material with the given node builder.
- *
- * @param {NodeBuilder} builder - The current node builder.
- */
- build( builder ) {
- this.setup( builder );
- }
- /**
- * Setups a node material observer with the given builder.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {NodeMaterialObserver} The node material observer.
- */
- setupObserver( builder ) {
- return new NodeMaterialObserver( builder );
- }
- /**
- * Setups the vertex and fragment stage of this node material.
- *
- * @param {NodeBuilder} builder - The current node builder.
- */
- setup( builder ) {
- builder.context.setupNormal = () => subBuild( this.setupNormal( builder ), 'NORMAL', 'vec3' );
- builder.context.setupPositionView = () => this.setupPositionView( builder );
- builder.context.setupModelViewProjection = () => this.setupModelViewProjection( builder );
- const renderer = builder.renderer;
- const renderTarget = renderer.getRenderTarget();
- // < CONTEXT >
- if ( renderer.contextNode.isContextNode === true ) {
- builder.context = { ...builder.context, ...renderer.contextNode.getFlowContextData() };
- } else {
- error( 'NodeMaterial: "renderer.contextNode" must be an instance of `context()`.' );
- }
- if ( this.contextNode !== null ) {
- if ( this.contextNode.isContextNode === true ) {
- builder.context = { ...builder.context, ...this.contextNode.getFlowContextData() };
- } else {
- error( 'NodeMaterial: "material.contextNode" must be an instance of `context()`.' );
- }
- }
- // < VERTEX STAGE >
- builder.addStack();
- const mvp = subBuild( this.setupVertex( builder ), 'VERTEX' );
- const vertexNode = this.vertexNode || mvp;
- builder.stack.outputNode = vertexNode;
- this.setupHardwareClipping( builder );
- if ( this.geometryNode !== null ) {
- builder.stack.outputNode = builder.stack.outputNode.bypass( this.geometryNode );
- }
- builder.addFlow( 'vertex', builder.removeStack() );
- // < FRAGMENT STAGE >
- builder.addStack();
- let resultNode;
- const clippingNode = this.setupClipping( builder );
- if ( this.depthWrite === true || this.depthTest === true ) {
- // only write depth if depth buffer is configured
- if ( renderTarget !== null ) {
- if ( renderTarget.depthBuffer === true ) this.setupDepth( builder );
- } else {
- if ( renderer.depth === true ) this.setupDepth( builder );
- }
- }
- if ( this.fragmentNode === null ) {
- this.setupDiffuseColor( builder );
- this.setupVariants( builder );
- const outgoingLightNode = this.setupLighting( builder );
- if ( clippingNode !== null ) builder.stack.addToStack( clippingNode );
- // force unsigned floats - useful for RenderTargets
- const basicOutput = vec4( outgoingLightNode, diffuseColor.a ).max( 0 );
- resultNode = this.setupOutput( builder, basicOutput );
- // OUTPUT NODE
- output.assign( resultNode );
- //
- const isCustomOutput = this.outputNode !== null;
- if ( isCustomOutput ) resultNode = this.outputNode;
- //
- if ( builder.context.getOutput ) {
- resultNode = builder.context.getOutput( resultNode, builder );
- }
- // MRT
- if ( renderTarget !== null ) {
- const mrt = renderer.getMRT();
- const materialMRT = this.mrtNode;
- if ( mrt !== null ) {
- if ( isCustomOutput ) output.assign( resultNode );
- resultNode = mrt;
- if ( materialMRT !== null ) {
- resultNode = mrt.merge( materialMRT );
- }
- } else if ( materialMRT !== null ) {
- resultNode = materialMRT;
- }
- }
- } else {
- let fragmentNode = this.fragmentNode;
- if ( fragmentNode.isOutputStructNode !== true ) {
- fragmentNode = vec4( fragmentNode );
- }
- resultNode = this.setupOutput( builder, fragmentNode );
- }
- builder.stack.outputNode = resultNode;
- builder.addFlow( 'fragment', builder.removeStack() );
- // < OBSERVER >
- builder.observer = this.setupObserver( builder );
- }
- /**
- * Setups the clipping node.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {ClippingNode} The clipping node.
- */
- setupClipping( builder ) {
- if ( builder.clippingContext === null ) return null;
- const { unionPlanes, intersectionPlanes } = builder.clippingContext;
- let result = null;
- if ( unionPlanes.length > 0 || intersectionPlanes.length > 0 ) {
- const samples = builder.renderer.currentSamples;
- if ( this.alphaToCoverage && samples > 1 ) {
- // to be added to flow when the color/alpha value has been determined
- result = clippingAlpha();
- } else {
- builder.stack.addToStack( clipping() );
- }
- }
- return result;
- }
- /**
- * Setups the hardware clipping if available on the current device.
- *
- * @param {NodeBuilder} builder - The current node builder.
- */
- setupHardwareClipping( builder ) {
- this.hardwareClipping = false;
- if ( builder.clippingContext === null ) return;
- const candidateCount = builder.clippingContext.unionPlanes.length;
- // 8 planes supported by WebGL ANGLE_clip_cull_distance and WebGPU clip-distances
- if ( candidateCount > 0 && candidateCount <= 8 && builder.isAvailable( 'clipDistance' ) ) {
- builder.stack.addToStack( hardwareClipping() );
- this.hardwareClipping = true;
- }
- return;
- }
- /**
- * Setups the depth of this material.
- *
- * @param {NodeBuilder} builder - The current node builder.
- */
- setupDepth( builder ) {
- const { renderer, camera } = builder;
- // Depth
- let depthNode = this.depthNode;
- if ( depthNode === null ) {
- const mrt = renderer.getMRT();
- if ( mrt && mrt.has( 'depth' ) ) {
- depthNode = mrt.get( 'depth' );
- } else if ( renderer.logarithmicDepthBuffer === true ) {
- if ( camera.isPerspectiveCamera ) {
- depthNode = viewZToLogarithmicDepth( positionView.z, cameraNear, cameraFar );
- } else {
- depthNode = viewZToOrthographicDepth( positionView.z, cameraNear, cameraFar );
- }
- }
- }
- if ( depthNode !== null ) {
- depth.assign( depthNode ).toStack();
- }
- }
- /**
- * Setups the position node in view space. This method exists
- * so derived node materials can modify the implementation e.g. sprite materials.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {Node<vec3>} The position in view space.
- */
- setupPositionView( /*builder*/ ) {
- return modelViewMatrix.mul( positionLocal ).xyz;
- }
- /**
- * Setups the position in clip space.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {Node<vec4>} The position in view space.
- */
- setupModelViewProjection( /*builder*/ ) {
- return cameraProjectionMatrix.mul( positionView );
- }
- /**
- * Setups the logic for the vertex stage.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {Node<vec4>} The position in clip space.
- */
- setupVertex( builder ) {
- builder.addStack();
- this.setupPosition( builder );
- builder.context.vertex = builder.removeStack();
- return modelViewProjection;
- }
- /**
- * Setups the computation of the position in local space.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {Node<vec3>} The position in local space.
- */
- setupPosition( builder ) {
- const { object, geometry } = builder;
- if ( geometry.morphAttributes.position || geometry.morphAttributes.normal || geometry.morphAttributes.color ) {
- morphReference( object ).toStack();
- }
- if ( object.isSkinnedMesh === true ) {
- skinning( object ).toStack();
- }
- if ( this.displacementMap ) {
- const displacementMap = materialReference( 'displacementMap', 'texture' );
- const displacementScale = materialReference( 'displacementScale', 'float' );
- const displacementBias = materialReference( 'displacementBias', 'float' );
- positionLocal.addAssign( normalLocal.normalize().mul( ( displacementMap.x.mul( displacementScale ).add( displacementBias ) ) ) );
- }
- if ( object.isBatchedMesh ) {
- batch( object ).toStack();
- }
- if ( ( object.isInstancedMesh && object.instanceMatrix && object.instanceMatrix.isInstancedBufferAttribute === true ) ) {
- instancedMesh( object ).toStack();
- }
- if ( this.positionNode !== null ) {
- positionLocal.assign( subBuild( this.positionNode, 'POSITION', 'vec3' ) );
- }
- return positionLocal;
- }
- /**
- * Setups the computation of the material's diffuse color.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @param {BufferGeometry} geometry - The geometry.
- */
- setupDiffuseColor( builder ) {
- const { object, geometry } = builder;
- // MASK
- if ( this.maskNode !== null ) {
- // Discard if the mask is `false`
- bool( this.maskNode ).not().discard();
- }
- // COLOR
- let colorNode = this.colorNode ? vec4( this.colorNode ) : materialColor;
- // VERTEX COLORS
- if ( this.vertexColors === true && geometry.hasAttribute( 'color' ) ) {
- colorNode = colorNode.mul( vertexColor() );
- }
- // INSTANCED COLORS
- if ( object.instanceColor ) {
- const instanceColor = varyingProperty( 'vec3', 'vInstanceColor' );
- colorNode = instanceColor.mul( colorNode );
- }
- if ( object.isBatchedMesh && object._colorsTexture ) {
- const batchColor = varyingProperty( 'vec3', 'vBatchColor' );
- colorNode = batchColor.mul( colorNode );
- }
- // DIFFUSE COLOR
- diffuseColor.assign( colorNode );
- // OPACITY
- const opacityNode = this.opacityNode ? float( this.opacityNode ) : materialOpacity;
- diffuseColor.a.assign( diffuseColor.a.mul( opacityNode ) );
- // ALPHA TEST
- let alphaTestNode = null;
- if ( this.alphaTestNode !== null || this.alphaTest > 0 ) {
- alphaTestNode = this.alphaTestNode !== null ? float( this.alphaTestNode ) : materialAlphaTest;
- if ( this.alphaToCoverage === true ) {
- diffuseColor.a = smoothstep( alphaTestNode, alphaTestNode.add( fwidth( diffuseColor.a ) ), diffuseColor.a );
- diffuseColor.a.lessThanEqual( 0 ).discard();
- } else {
- diffuseColor.a.lessThanEqual( alphaTestNode ).discard();
- }
- }
- // ALPHA HASH
- if ( this.alphaHash === true ) {
- diffuseColor.a.lessThan( getAlphaHashThreshold( positionLocal ) ).discard();
- }
- // OPAQUE
- if ( builder.isOpaque() ) {
- diffuseColor.a.assign( 1.0 );
- }
- }
- /**
- * Abstract interface method that can be implemented by derived materials
- * to setup material-specific node variables.
- *
- * @abstract
- * @param {NodeBuilder} builder - The current node builder.
- */
- setupVariants( /*builder*/ ) {
- // Interface function.
- }
- /**
- * Setups the outgoing light node variable
- *
- * @return {Node<vec3>} The outgoing light node.
- */
- setupOutgoingLight() {
- return ( this.lights === true ) ? vec3( 0 ) : diffuseColor.rgb;
- }
- /**
- * Setups the normal node from the material.
- *
- * @return {Node<vec3>} The normal node.
- */
- setupNormal() {
- return this.normalNode ? vec3( this.normalNode ) : materialNormal;
- }
- /**
- * Setups the environment node from the material.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {Node<vec4>} The environment node.
- */
- setupEnvironment( /*builder*/ ) {
- let node = null;
- if ( this.envNode ) {
- node = this.envNode;
- } else if ( this.envMap ) {
- node = this.envMap.isCubeTexture ? materialReference( 'envMap', 'cubeTexture' ) : materialReference( 'envMap', 'texture' );
- }
- return node;
- }
- /**
- * Setups the light map node from the material.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {Node<vec3>} The light map node.
- */
- setupLightMap( builder ) {
- let node = null;
- if ( builder.material.lightMap ) {
- node = new IrradianceNode( materialLightMap );
- }
- return node;
- }
- /**
- * Setups the lights node based on the scene, environment and material.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {LightsNode} The lights node.
- */
- setupLights( builder ) {
- const materialLightsNode = [];
- //
- const envNode = this.setupEnvironment( builder );
- if ( envNode && envNode.isLightingNode ) {
- materialLightsNode.push( envNode );
- }
- const lightMapNode = this.setupLightMap( builder );
- if ( lightMapNode && lightMapNode.isLightingNode ) {
- materialLightsNode.push( lightMapNode );
- }
- let aoNode = this.aoNode;
- if ( aoNode === null && builder.material.aoMap ) {
- aoNode = materialAO;
- }
- if ( builder.context.getAO ) {
- aoNode = builder.context.getAO( aoNode, builder );
- }
- if ( aoNode ) {
- materialLightsNode.push( new AONode( aoNode ) );
- }
- let lightsN = this.lightsNode || builder.lightsNode;
- if ( materialLightsNode.length > 0 ) {
- lightsN = builder.renderer.lighting.createNode( [ ...lightsN.getLights(), ...materialLightsNode ] );
- }
- return lightsN;
- }
- /**
- * This method should be implemented by most derived materials
- * since it defines the material's lighting model.
- *
- * @abstract
- * @param {NodeBuilder} builder - The current node builder.
- * @return {LightingModel} The lighting model.
- */
- setupLightingModel( /*builder*/ ) {
- // Interface function.
- }
- /**
- * Setups the outgoing light node.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {Node<vec3>} The outgoing light node.
- */
- setupLighting( builder ) {
- const { material } = builder;
- const { backdropNode, backdropAlphaNode, emissiveNode } = this;
- // OUTGOING LIGHT
- const lights = this.lights === true || this.lightsNode !== null;
- const lightsNode = lights ? this.setupLights( builder ) : null;
- let outgoingLightNode = this.setupOutgoingLight( builder );
- if ( lightsNode && lightsNode.getScope().hasLights ) {
- const lightingModel = this.setupLightingModel( builder ) || null;
- outgoingLightNode = lightingContext( lightsNode, lightingModel, backdropNode, backdropAlphaNode );
- } else if ( backdropNode !== null ) {
- outgoingLightNode = vec3( backdropAlphaNode !== null ? mix( outgoingLightNode, backdropNode, backdropAlphaNode ) : backdropNode );
- }
- // EMISSIVE
- if ( ( emissiveNode && emissiveNode.isNode === true ) || ( material.emissive && material.emissive.isColor === true ) ) {
- emissive.assign( vec3( emissiveNode ? emissiveNode : materialEmissive ) );
- outgoingLightNode = outgoingLightNode.add( emissive );
- }
- return outgoingLightNode;
- }
- /**
- * Setup the fog.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @param {Node<vec4>} outputNode - The existing output node.
- * @return {Node<vec4>} The output node.
- */
- setupFog( builder, outputNode ) {
- const fogNode = builder.fogNode;
- if ( fogNode ) {
- output.assign( outputNode );
- outputNode = vec4( fogNode.toVar() );
- }
- return outputNode;
- }
- /**
- * Setups premultiplied alpha.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @param {Node<vec4>} outputNode - The existing output node.
- * @return {Node<vec4>} The output node.
- */
- setupPremultipliedAlpha( builder, outputNode ) {
- return premultiplyAlpha( outputNode );
- }
- /**
- * Setups the output node.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @param {Node<vec4>} outputNode - The existing output node.
- * @return {Node<vec4>} The output node.
- */
- setupOutput( builder, outputNode ) {
- // FOG
- if ( this.fog === true ) {
- outputNode = this.setupFog( builder, outputNode );
- }
- // PREMULTIPLIED ALPHA
- if ( this.premultipliedAlpha === true ) {
- outputNode = this.setupPremultipliedAlpha( builder, outputNode );
- }
- return outputNode;
- }
- /**
- * Most classic material types have a node pendant e.g. for `MeshBasicMaterial`
- * there is `MeshBasicNodeMaterial`. This utility method is intended for
- * defining all material properties of the classic type in the node type.
- *
- * @param {Material} material - The material to copy properties with their values to this node material.
- */
- setDefaultValues( material ) {
- // This approach is to reuse the native refreshUniforms*
- // and turn available the use of features like transmission and environment in core
- for ( const property in material ) {
- const value = material[ property ];
- if ( this[ property ] === undefined ) {
- this[ property ] = value;
- if ( value && value.clone ) this[ property ] = value.clone();
- }
- }
- const descriptors = Object.getOwnPropertyDescriptors( material.constructor.prototype );
- for ( const key in descriptors ) {
- if ( Object.getOwnPropertyDescriptor( this.constructor.prototype, key ) === undefined &&
- descriptors[ key ].get !== undefined ) {
- Object.defineProperty( this.constructor.prototype, key, descriptors[ key ] );
- }
- }
- }
- /**
- * Serializes this material to JSON.
- *
- * @param {?(Object|string)} meta - The meta information for serialization.
- * @return {Object} The serialized node.
- */
- toJSON( meta ) {
- const isRoot = ( meta === undefined || typeof meta === 'string' );
- if ( isRoot ) {
- meta = {
- textures: {},
- images: {},
- nodes: {}
- };
- }
- const data = Material.prototype.toJSON.call( this, meta );
- data.inputNodes = {};
- for ( const { property, childNode } of this._getNodeChildren() ) {
- data.inputNodes[ property ] = childNode.toJSON( meta ).uuid;
- }
- // TODO: Copied from Object3D.toJSON
- function extractFromCache( cache ) {
- const values = [];
- for ( const key in cache ) {
- const data = cache[ key ];
- delete data.metadata;
- values.push( data );
- }
- return values;
- }
- if ( isRoot ) {
- const textures = extractFromCache( meta.textures );
- const images = extractFromCache( meta.images );
- const nodes = extractFromCache( meta.nodes );
- if ( textures.length > 0 ) data.textures = textures;
- if ( images.length > 0 ) data.images = images;
- if ( nodes.length > 0 ) data.nodes = nodes;
- }
- return data;
- }
- /**
- * Copies the properties of the given node material to this instance.
- *
- * @param {NodeMaterial} source - The material to copy.
- * @return {NodeMaterial} A reference to this node material.
- */
- copy( source ) {
- this.lightsNode = source.lightsNode;
- this.envNode = source.envNode;
- this.aoNode = source.aoNode;
- this.colorNode = source.colorNode;
- this.normalNode = source.normalNode;
- this.opacityNode = source.opacityNode;
- this.backdropNode = source.backdropNode;
- this.backdropAlphaNode = source.backdropAlphaNode;
- this.alphaTestNode = source.alphaTestNode;
- this.maskNode = source.maskNode;
- this.positionNode = source.positionNode;
- this.geometryNode = source.geometryNode;
- this.depthNode = source.depthNode;
- this.receivedShadowPositionNode = source.receivedShadowPositionNode;
- this.castShadowPositionNode = source.castShadowPositionNode;
- this.receivedShadowNode = source.receivedShadowNode;
- this.castShadowNode = source.castShadowNode;
- this.outputNode = source.outputNode;
- this.mrtNode = source.mrtNode;
- this.fragmentNode = source.fragmentNode;
- this.vertexNode = source.vertexNode;
- this.contextNode = source.contextNode;
- return super.copy( source );
- }
- }
- const _defaultValues$d = /*@__PURE__*/ new LineBasicMaterial();
- /**
- * Node material version of {@link LineBasicMaterial}.
- *
- * @augments NodeMaterial
- */
- class LineBasicNodeMaterial extends NodeMaterial {
- static get type() {
- return 'LineBasicNodeMaterial';
- }
- /**
- * Constructs a new line basic node material.
- *
- * @param {Object} [parameters] - The configuration parameter.
- */
- constructor( parameters ) {
- super();
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isLineBasicNodeMaterial = true;
- this.setDefaultValues( _defaultValues$d );
- this.setValues( parameters );
- }
- }
- const _defaultValues$c = /*@__PURE__*/ new LineDashedMaterial();
- /**
- * Node material version of {@link LineDashedMaterial}.
- *
- * @augments NodeMaterial
- */
- class LineDashedNodeMaterial extends NodeMaterial {
- static get type() {
- return 'LineDashedNodeMaterial';
- }
- /**
- * Constructs a new line dashed node material.
- *
- * @param {Object} [parameters] - The configuration parameter.
- */
- constructor( parameters ) {
- super();
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isLineDashedNodeMaterial = true;
- this.setDefaultValues( _defaultValues$c );
- /**
- * The dash offset.
- *
- * @type {number}
- * @default 0
- */
- this.dashOffset = 0;
- /**
- * The offset of dash materials is by default inferred from the `dashOffset`
- * property. This node property allows to overwrite the default
- * and define the offset with a node instead.
- *
- * If you don't want to overwrite the offset but modify the existing
- * value instead, use {@link materialLineDashOffset}.
- *
- * @type {?Node<float>}
- * @default null
- */
- this.offsetNode = null;
- /**
- * The scale of dash materials is by default inferred from the `scale`
- * property. This node property allows to overwrite the default
- * and define the scale with a node instead.
- *
- * If you don't want to overwrite the scale but modify the existing
- * value instead, use {@link materialLineScale}.
- *
- * @type {?Node<float>}
- * @default null
- */
- this.dashScaleNode = null;
- /**
- * The dash size of dash materials is by default inferred from the `dashSize`
- * property. This node property allows to overwrite the default
- * and define the dash size with a node instead.
- *
- * If you don't want to overwrite the dash size but modify the existing
- * value instead, use {@link materialLineDashSize}.
- *
- * @type {?Node<float>}
- * @default null
- */
- this.dashSizeNode = null;
- /**
- * The gap size of dash materials is by default inferred from the `gapSize`
- * property. This node property allows to overwrite the default
- * and define the gap size with a node instead.
- *
- * If you don't want to overwrite the gap size but modify the existing
- * value instead, use {@link materialLineGapSize}.
- *
- * @type {?Node<float>}
- * @default null
- */
- this.gapSizeNode = null;
- this.setValues( parameters );
- }
- /**
- * Setups the dash specific node variables.
- *
- * @param {NodeBuilder} builder - The current node builder.
- */
- setupVariants( /* builder */ ) {
- const offsetNode = this.offsetNode ? float( this.offsetNode ) : materialLineDashOffset;
- const dashScaleNode = this.dashScaleNode ? float( this.dashScaleNode ) : materialLineScale;
- const dashSizeNode = this.dashSizeNode ? float( this.dashSizeNode ) : materialLineDashSize;
- const gapSizeNode = this.gapSizeNode ? float( this.gapSizeNode ) : materialLineGapSize;
- dashSize.assign( dashSizeNode );
- gapSize.assign( gapSizeNode );
- const vLineDistance = varying( attribute( 'lineDistance' ).mul( dashScaleNode ) );
- const vLineDistanceOffset = offsetNode ? vLineDistance.add( offsetNode ) : vLineDistance;
- vLineDistanceOffset.mod( dashSize.add( gapSize ) ).greaterThan( dashSize ).discard();
- }
- }
- let _sharedFramebuffer = null;
- /**
- * `ViewportTextureNode` creates an internal texture for each node instance. This module
- * shares a texture across all instances of `ViewportSharedTextureNode`. It should
- * be the first choice when using data of the default/screen framebuffer for performance reasons.
- *
- * @augments ViewportTextureNode
- */
- class ViewportSharedTextureNode extends ViewportTextureNode {
- static get type() {
- return 'ViewportSharedTextureNode';
- }
- /**
- * Constructs a new viewport shared texture node.
- *
- * @param {Node} [uvNode=screenUV] - The uv node.
- * @param {?Node} [levelNode=null] - The level node.
- */
- constructor( uvNode = screenUV, levelNode = null ) {
- if ( _sharedFramebuffer === null ) {
- _sharedFramebuffer = new FramebufferTexture();
- }
- super( uvNode, levelNode, _sharedFramebuffer );
- }
- /**
- * Overwritten so the method always returns the unique shared
- * framebuffer texture.
- *
- * @return {FramebufferTexture} The shared framebuffer texture.
- */
- getTextureForReference() {
- return _sharedFramebuffer;
- }
- updateReference() {
- return this;
- }
- }
- /**
- * TSL function for creating a shared viewport texture node.
- *
- * @tsl
- * @function
- * @param {?Node} [uvNode=screenUV] - The uv node.
- * @param {?Node} [levelNode=null] - The level node.
- * @returns {ViewportSharedTextureNode}
- */
- const viewportSharedTexture = /*@__PURE__*/ nodeProxy( ViewportSharedTextureNode ).setParameterLength( 0, 2 );
- const _defaultValues$b = /*@__PURE__*/ new LineDashedMaterial();
- /**
- * This node material can be used to render lines with a size larger than one
- * by representing them as instanced meshes.
- *
- * @augments NodeMaterial
- */
- class Line2NodeMaterial extends NodeMaterial {
- static get type() {
- return 'Line2NodeMaterial';
- }
- /**
- * Constructs a new node material for wide line rendering.
- *
- * @param {Object} [parameters={}] - The configuration parameter.
- */
- constructor( parameters = {} ) {
- super();
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isLine2NodeMaterial = true;
- this.setDefaultValues( _defaultValues$b );
- /**
- * Whether vertex colors should be used or not.
- *
- * @type {boolean}
- * @default false
- */
- this.useColor = parameters.vertexColors;
- /**
- * The dash offset.
- *
- * @type {number}
- * @default 0
- */
- this.dashOffset = 0;
- /**
- * Defines the lines color.
- *
- * @type {?Node<vec3>}
- * @default null
- */
- this.lineColorNode = null;
- /**
- * Defines the offset.
- *
- * @type {?Node<float>}
- * @default null
- */
- this.offsetNode = null;
- /**
- * Defines the dash scale.
- *
- * @type {?Node<float>}
- * @default null
- */
- this.dashScaleNode = null;
- /**
- * Defines the dash size.
- *
- * @type {?Node<float>}
- * @default null
- */
- this.dashSizeNode = null;
- /**
- * Defines the gap size.
- *
- * @type {?Node<float>}
- * @default null
- */
- this.gapSizeNode = null;
- /**
- * Blending is set to `NoBlending` since transparency
- * is not supported, yet.
- *
- * @type {number}
- * @default 0
- */
- this.blending = NoBlending;
- this._useDash = parameters.dashed;
- this._useAlphaToCoverage = true;
- this._useWorldUnits = false;
- this.setValues( parameters );
- }
- /**
- * Setups the vertex and fragment stage of this node material.
- *
- * @param {NodeBuilder} builder - The current node builder.
- */
- setup( builder ) {
- const { renderer } = builder;
- const useAlphaToCoverage = this._useAlphaToCoverage;
- const useColor = this.useColor;
- const useDash = this._useDash;
- const useWorldUnits = this._useWorldUnits;
- const trimSegment = Fn( ( { start, end } ) => {
- const a = cameraProjectionMatrix.element( 2 ).element( 2 ); // 3nd entry in 3th column
- const b = cameraProjectionMatrix.element( 3 ).element( 2 ); // 3nd entry in 4th column
- const nearEstimate = b.mul( -0.5 ).div( a );
- const alpha = nearEstimate.sub( start.z ).div( end.z.sub( start.z ) );
- return vec4( mix( start.xyz, end.xyz, alpha ), end.w );
- } ).setLayout( {
- name: 'trimSegment',
- type: 'vec4',
- inputs: [
- { name: 'start', type: 'vec4' },
- { name: 'end', type: 'vec4' }
- ]
- } );
- this.vertexNode = Fn( () => {
- const instanceStart = attribute( 'instanceStart' );
- const instanceEnd = attribute( 'instanceEnd' );
- // camera space
- const start = vec4( modelViewMatrix.mul( vec4( instanceStart, 1.0 ) ) ).toVar( 'start' );
- const end = vec4( modelViewMatrix.mul( vec4( instanceEnd, 1.0 ) ) ).toVar( 'end' );
- if ( useDash ) {
- const dashScaleNode = this.dashScaleNode ? float( this.dashScaleNode ) : materialLineScale;
- const offsetNode = this.offsetNode ? float( this.offsetNode ) : materialLineDashOffset;
- const instanceDistanceStart = attribute( 'instanceDistanceStart' );
- const instanceDistanceEnd = attribute( 'instanceDistanceEnd' );
- let lineDistance = positionGeometry.y.lessThan( 0.5 ).select( dashScaleNode.mul( instanceDistanceStart ), dashScaleNode.mul( instanceDistanceEnd ) );
- lineDistance = lineDistance.add( offsetNode );
- varyingProperty( 'float', 'lineDistance' ).assign( lineDistance );
- }
- if ( useWorldUnits ) {
- varyingProperty( 'vec3', 'worldStart' ).assign( start.xyz );
- varyingProperty( 'vec3', 'worldEnd' ).assign( end.xyz );
- }
- const aspect = viewport.z.div( viewport.w );
- // special case for perspective projection, and segments that terminate either in, or behind, the camera plane
- // clearly the gpu firmware has a way of addressing this issue when projecting into ndc space
- // but we need to perform ndc-space calculations in the shader, so we must address this issue directly
- // perhaps there is a more elegant solution -- WestLangley
- const perspective = cameraProjectionMatrix.element( 2 ).element( 3 ).equal( -1 ); // 4th entry in the 3rd column
- If( perspective, () => {
- If( start.z.lessThan( 0.0 ).and( end.z.greaterThan( 0.0 ) ), () => {
- end.assign( trimSegment( { start: start, end: end } ) );
- } ).ElseIf( end.z.lessThan( 0.0 ).and( start.z.greaterThanEqual( 0.0 ) ), () => {
- start.assign( trimSegment( { start: end, end: start } ) );
- } );
- } );
- // clip space
- const clipStart = cameraProjectionMatrix.mul( start );
- const clipEnd = cameraProjectionMatrix.mul( end );
- // ndc space
- const ndcStart = clipStart.xyz.div( clipStart.w );
- const ndcEnd = clipEnd.xyz.div( clipEnd.w );
- // direction
- const dir = ndcEnd.xy.sub( ndcStart.xy ).toVar();
- // account for clip-space aspect ratio
- dir.x.assign( dir.x.mul( aspect ) );
- dir.assign( dir.normalize() );
- const clip = vec4().toVar();
- if ( useWorldUnits ) {
- // get the offset direction as perpendicular to the view vector
- const worldDir = end.xyz.sub( start.xyz ).normalize();
- const tmpFwd = mix( start.xyz, end.xyz, 0.5 ).normalize();
- const worldUp = worldDir.cross( tmpFwd ).normalize();
- const worldFwd = worldDir.cross( worldUp );
- const worldPos = varyingProperty( 'vec4', 'worldPos' );
- worldPos.assign( positionGeometry.y.lessThan( 0.5 ).select( start, end ) );
- // height offset
- const hw = materialLineWidth.mul( 0.5 );
- worldPos.addAssign( vec4( positionGeometry.x.lessThan( 0.0 ).select( worldUp.mul( hw ), worldUp.mul( hw ).negate() ), 0 ) );
- // don't extend the line if we're rendering dashes because we
- // won't be rendering the endcaps
- if ( ! useDash ) {
- // cap extension
- worldPos.addAssign( vec4( positionGeometry.y.lessThan( 0.5 ).select( worldDir.mul( hw ).negate(), worldDir.mul( hw ) ), 0 ) );
- // add width to the box
- worldPos.addAssign( vec4( worldFwd.mul( hw ), 0 ) );
- // endcaps
- If( positionGeometry.y.greaterThan( 1.0 ).or( positionGeometry.y.lessThan( 0.0 ) ), () => {
- worldPos.subAssign( vec4( worldFwd.mul( 2.0 ).mul( hw ), 0 ) );
- } );
- }
- // project the worldpos
- clip.assign( cameraProjectionMatrix.mul( worldPos ) );
- // shift the depth of the projected points so the line
- // segments overlap neatly
- const clipPose = vec3().toVar();
- clipPose.assign( positionGeometry.y.lessThan( 0.5 ).select( ndcStart, ndcEnd ) );
- clip.z.assign( clipPose.z.mul( clip.w ) );
- } else {
- const offset = vec2( dir.y, dir.x.negate() ).toVar( 'offset' );
- // undo aspect ratio adjustment
- dir.x.assign( dir.x.div( aspect ) );
- offset.x.assign( offset.x.div( aspect ) );
- // sign flip
- offset.assign( positionGeometry.x.lessThan( 0.0 ).select( offset.negate(), offset ) );
- // endcaps
- If( positionGeometry.y.lessThan( 0.0 ), () => {
- offset.assign( offset.sub( dir ) );
- } ).ElseIf( positionGeometry.y.greaterThan( 1.0 ), () => {
- offset.assign( offset.add( dir ) );
- } );
- // adjust for linewidth
- offset.assign( offset.mul( materialLineWidth ) );
- // adjust for clip-space to screen-space conversion // maybe resolution should be based on viewport ...
- offset.assign( offset.div( viewport.w.div( screenDPR ) ) );
- // select end
- clip.assign( positionGeometry.y.lessThan( 0.5 ).select( clipStart, clipEnd ) );
- // back to clip space
- offset.assign( offset.mul( clip.w ) );
- clip.assign( clip.add( vec4( offset, 0, 0 ) ) );
- }
- return clip;
- } )();
- const closestLineToLine = Fn( ( { p1, p2, p3, p4 } ) => {
- const p13 = p1.sub( p3 );
- const p43 = p4.sub( p3 );
- const p21 = p2.sub( p1 );
- const d1343 = p13.dot( p43 );
- const d4321 = p43.dot( p21 );
- const d1321 = p13.dot( p21 );
- const d4343 = p43.dot( p43 );
- const d2121 = p21.dot( p21 );
- const denom = d2121.mul( d4343 ).sub( d4321.mul( d4321 ) );
- const numer = d1343.mul( d4321 ).sub( d1321.mul( d4343 ) );
- const mua = numer.div( denom ).clamp();
- const mub = d1343.add( d4321.mul( mua ) ).div( d4343 ).clamp();
- return vec2( mua, mub );
- } );
- this.colorNode = Fn( () => {
- const vUv = uv$1();
- if ( useDash ) {
- const dashSizeNode = this.dashSizeNode ? float( this.dashSizeNode ) : materialLineDashSize;
- const gapSizeNode = this.gapSizeNode ? float( this.gapSizeNode ) : materialLineGapSize;
- dashSize.assign( dashSizeNode );
- gapSize.assign( gapSizeNode );
- const vLineDistance = varyingProperty( 'float', 'lineDistance' );
- vUv.y.lessThan( -1 ).or( vUv.y.greaterThan( 1.0 ) ).discard(); // discard endcaps
- vLineDistance.mod( dashSize.add( gapSize ) ).greaterThan( dashSize ).discard(); // todo - FIX
- }
- const alpha = float( 1 ).toVar( 'alpha' );
- if ( useWorldUnits ) {
- const worldStart = varyingProperty( 'vec3', 'worldStart' );
- const worldEnd = varyingProperty( 'vec3', 'worldEnd' );
- // Find the closest points on the view ray and the line segment
- const rayEnd = varyingProperty( 'vec4', 'worldPos' ).xyz.normalize().mul( 1e5 );
- const lineDir = worldEnd.sub( worldStart );
- const params = closestLineToLine( { p1: worldStart, p2: worldEnd, p3: vec3( 0.0, 0.0, 0.0 ), p4: rayEnd } );
- const p1 = worldStart.add( lineDir.mul( params.x ) );
- const p2 = rayEnd.mul( params.y );
- const delta = p1.sub( p2 );
- const len = delta.length();
- const norm = len.div( materialLineWidth );
- if ( ! useDash ) {
- if ( useAlphaToCoverage && renderer.currentSamples > 0 ) {
- const dnorm = norm.fwidth();
- alpha.assign( smoothstep( dnorm.negate().add( 0.5 ), dnorm.add( 0.5 ), norm ).oneMinus() );
- } else {
- norm.greaterThan( 0.5 ).discard();
- }
- }
- } else {
- // round endcaps
- if ( useAlphaToCoverage && renderer.currentSamples > 0 ) {
- const a = vUv.x;
- const b = vUv.y.greaterThan( 0.0 ).select( vUv.y.sub( 1.0 ), vUv.y.add( 1.0 ) );
- const len2 = a.mul( a ).add( b.mul( b ) );
- const dlen = float( len2.fwidth() ).toVar( 'dlen' );
- If( vUv.y.abs().greaterThan( 1.0 ), () => {
- alpha.assign( smoothstep( dlen.oneMinus(), dlen.add( 1 ), len2 ).oneMinus() );
- } );
- } else {
- If( vUv.y.abs().greaterThan( 1.0 ), () => {
- const a = vUv.x;
- const b = vUv.y.greaterThan( 0.0 ).select( vUv.y.sub( 1.0 ), vUv.y.add( 1.0 ) );
- const len2 = a.mul( a ).add( b.mul( b ) );
- len2.greaterThan( 1.0 ).discard();
- } );
- }
- }
- let lineColorNode;
- if ( this.lineColorNode ) {
- lineColorNode = this.lineColorNode;
- } else {
- if ( useColor ) {
- const instanceColorStart = attribute( 'instanceColorStart' );
- const instanceColorEnd = attribute( 'instanceColorEnd' );
- const instanceColor = positionGeometry.y.lessThan( 0.5 ).select( instanceColorStart, instanceColorEnd );
- lineColorNode = instanceColor.mul( materialColor );
- } else {
- lineColorNode = materialColor;
- }
- }
- return vec4( lineColorNode, alpha );
- } )();
- if ( this.transparent ) {
- const opacityNode = this.opacityNode ? float( this.opacityNode ) : materialOpacity;
- this.outputNode = vec4( this.colorNode.rgb.mul( opacityNode ).add( viewportSharedTexture().rgb.mul( opacityNode.oneMinus() ) ), this.colorNode.a );
- }
- super.setup( builder );
- }
- /**
- * Whether the lines should sized in world units or not.
- * When set to `false` the unit is pixel.
- *
- * @type {boolean}
- * @default false
- */
- get worldUnits() {
- return this._useWorldUnits;
- }
- set worldUnits( value ) {
- if ( this._useWorldUnits !== value ) {
- this._useWorldUnits = value;
- this.needsUpdate = true;
- }
- }
- /**
- * Whether the lines should be dashed or not.
- *
- * @type {boolean}
- * @default false
- */
- get dashed() {
- return this._useDash;
- }
- set dashed( value ) {
- if ( this._useDash !== value ) {
- this._useDash = value;
- this.needsUpdate = true;
- }
- }
- /**
- * Whether alpha to coverage should be used or not.
- *
- * @type {boolean}
- * @default true
- */
- get alphaToCoverage() {
- return this._useAlphaToCoverage;
- }
- set alphaToCoverage( value ) {
- if ( this._useAlphaToCoverage !== value ) {
- this._useAlphaToCoverage = value;
- this.needsUpdate = true;
- }
- }
- }
- const _defaultValues$a = /*@__PURE__*/ new MeshNormalMaterial();
- /**
- * Node material version of {@link MeshNormalMaterial}.
- *
- * @augments NodeMaterial
- */
- class MeshNormalNodeMaterial extends NodeMaterial {
- static get type() {
- return 'MeshNormalNodeMaterial';
- }
- /**
- * Constructs a new mesh normal node material.
- *
- * @param {Object} [parameters] - The configuration parameter.
- */
- constructor( parameters ) {
- super();
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isMeshNormalNodeMaterial = true;
- this.setDefaultValues( _defaultValues$a );
- this.setValues( parameters );
- }
- /**
- * Overwrites the default implementation by computing the diffuse color
- * based on the normal data.
- */
- setupDiffuseColor() {
- const opacityNode = this.opacityNode ? float( this.opacityNode ) : materialOpacity;
- // By convention, a normal packed to RGB is in sRGB color space. Convert it to working color space.
- diffuseColor.assign( colorSpaceToWorking( vec4( directionToColor( normalView ), opacityNode ), SRGBColorSpace ) );
- }
- }
- /**
- * TSL function for creating an equirect uv node.
- *
- * Can be used to compute texture coordinates for projecting an
- * equirectangular texture onto a mesh for using it as the scene's
- * background.
- *
- * ```js
- * scene.backgroundNode = texture( equirectTexture, equirectUV() );
- * ```
- *
- * @tsl
- * @function
- * @param {?Node<vec3>} [dirNode=positionWorldDirection] - A direction vector for sampling which is by default `positionWorldDirection`.
- * @returns {Node<vec2>}
- */
- const equirectUV = /*@__PURE__*/ Fn( ( [ dir = positionWorldDirection ] ) => {
- const u = dir.z.atan( dir.x ).mul( 1 / ( Math.PI * 2 ) ).add( 0.5 );
- const v = dir.y.clamp( -1, 1.0 ).asin().mul( 1 / Math.PI ).add( 0.5 );
- return vec2( u, v );
- } );
- // @TODO: Consider rename WebGLCubeRenderTarget to just CubeRenderTarget
- /**
- * This class represents a cube render target. It is a special version
- * of `WebGLCubeRenderTarget` which is compatible with `WebGPURenderer`.
- *
- * @augments WebGLCubeRenderTarget
- */
- class CubeRenderTarget extends WebGLCubeRenderTarget {
- /**
- * Constructs a new cube render target.
- *
- * @param {number} [size=1] - The size of the render target.
- * @param {RenderTarget~Options} [options] - The configuration object.
- */
- constructor( size = 1, options = {} ) {
- super( size, options );
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isCubeRenderTarget = true;
- }
- /**
- * Converts the given equirectangular texture to a cube map.
- *
- * @param {Renderer} renderer - The renderer.
- * @param {Texture} texture - The equirectangular texture.
- * @return {CubeRenderTarget} A reference to this cube render target.
- */
- fromEquirectangularTexture( renderer, texture$1 ) {
- const currentMinFilter = texture$1.minFilter;
- const currentGenerateMipmaps = texture$1.generateMipmaps;
- texture$1.generateMipmaps = true;
- this.texture.type = texture$1.type;
- this.texture.colorSpace = texture$1.colorSpace;
- this.texture.generateMipmaps = texture$1.generateMipmaps;
- this.texture.minFilter = texture$1.minFilter;
- this.texture.magFilter = texture$1.magFilter;
- const geometry = new BoxGeometry( 5, 5, 5 );
- const uvNode = equirectUV( positionWorldDirection );
- const material = new NodeMaterial();
- material.colorNode = texture( texture$1, uvNode, 0 );
- material.side = BackSide;
- material.blending = NoBlending;
- const mesh = new Mesh( geometry, material );
- const scene = new Scene();
- scene.add( mesh );
- // Avoid blurred poles
- if ( texture$1.minFilter === LinearMipmapLinearFilter ) texture$1.minFilter = LinearFilter;
- const camera = new CubeCamera( 1, 10, this );
- const currentMRT = renderer.getMRT();
- renderer.setMRT( null );
- camera.update( renderer, scene );
- renderer.setMRT( currentMRT );
- texture$1.minFilter = currentMinFilter;
- texture$1.currentGenerateMipmaps = currentGenerateMipmaps;
- mesh.geometry.dispose();
- mesh.material.dispose();
- return this;
- }
- }
- const _cache$1 = new WeakMap();
- /**
- * This node can be used to automatically convert environment maps in the
- * equirectangular format into the cube map format.
- *
- * @augments TempNode
- */
- class CubeMapNode extends TempNode {
- static get type() {
- return 'CubeMapNode';
- }
- /**
- * Constructs a new cube map node.
- *
- * @param {Node} envNode - The node representing the environment map.
- */
- constructor( envNode ) {
- super( 'vec3' );
- /**
- * The node representing the environment map.
- *
- * @type {Node}
- */
- this.envNode = envNode;
- /**
- * A reference to the internal cube texture.
- *
- * @private
- * @type {?CubeTexture}
- * @default null
- */
- this._cubeTexture = null;
- /**
- * A reference to the internal cube texture node.
- *
- * @private
- * @type {CubeTextureNode}
- */
- this._cubeTextureNode = cubeTexture( null );
- const defaultTexture = new CubeTexture();
- defaultTexture.isRenderTargetTexture = true;
- /**
- * A default cube texture that acts as a placeholder.
- * It is used when the conversion from equirectangular to cube
- * map has not finished yet for a given texture.
- *
- * @private
- * @type {CubeTexture}
- */
- this._defaultTexture = defaultTexture;
- /**
- * The `updateBeforeType` is set to `NodeUpdateType.RENDER` since the node updates
- * the texture once per render in its {@link CubeMapNode#updateBefore} method.
- *
- * @type {string}
- * @default 'render'
- */
- this.updateBeforeType = NodeUpdateType.RENDER;
- }
- updateBefore( frame ) {
- const { renderer, material } = frame;
- const envNode = this.envNode;
- if ( envNode.isTextureNode || envNode.isMaterialReferenceNode ) {
- const texture = ( envNode.isTextureNode ) ? envNode.value : material[ envNode.property ];
- if ( texture && texture.isTexture ) {
- const mapping = texture.mapping;
- if ( mapping === EquirectangularReflectionMapping || mapping === EquirectangularRefractionMapping ) {
- // check for converted cubemap map
- if ( _cache$1.has( texture ) ) {
- const cubeMap = _cache$1.get( texture );
- mapTextureMapping( cubeMap, texture.mapping );
- this._cubeTexture = cubeMap;
- } else {
- // create cube map from equirectangular map
- const image = texture.image;
- if ( isEquirectangularMapReady$1( image ) ) {
- const renderTarget = new CubeRenderTarget( image.height );
- renderTarget.fromEquirectangularTexture( renderer, texture );
- mapTextureMapping( renderTarget.texture, texture.mapping );
- this._cubeTexture = renderTarget.texture;
- _cache$1.set( texture, renderTarget.texture );
- texture.addEventListener( 'dispose', onTextureDispose );
- } else {
- // default cube texture as fallback when equirectangular texture is not yet loaded
- this._cubeTexture = this._defaultTexture;
- }
- }
- //
- this._cubeTextureNode.value = this._cubeTexture;
- } else {
- // envNode already refers to a cube map
- this._cubeTextureNode = this.envNode;
- }
- }
- }
- }
- setup( builder ) {
- this.updateBefore( builder );
- return this._cubeTextureNode;
- }
- }
- /**
- * Returns true if the given equirectangular image has been fully loaded
- * and is ready for further processing.
- *
- * @private
- * @param {Image} image - The equirectangular image to check.
- * @return {boolean} Whether the image is ready or not.
- */
- function isEquirectangularMapReady$1( image ) {
- if ( image === null || image === undefined ) return false;
- return image.height > 0;
- }
- /**
- * This function is executed when `dispose()` is called on the equirectangular
- * texture. In this case, the generated cube map with its render target
- * is deleted as well.
- *
- * @private
- * @param {Object} event - The event object.
- */
- function onTextureDispose( event ) {
- const texture = event.target;
- texture.removeEventListener( 'dispose', onTextureDispose );
- const renderTarget = _cache$1.get( texture );
- if ( renderTarget !== undefined ) {
- _cache$1.delete( texture );
- renderTarget.dispose();
- }
- }
- /**
- * This function makes sure the generated cube map uses the correct
- * texture mapping that corresponds to the equirectangular original.
- *
- * @private
- * @param {Texture} texture - The cube texture.
- * @param {number} mapping - The original texture mapping.
- */
- function mapTextureMapping( texture, mapping ) {
- if ( mapping === EquirectangularReflectionMapping ) {
- texture.mapping = CubeReflectionMapping;
- } else if ( mapping === EquirectangularRefractionMapping ) {
- texture.mapping = CubeRefractionMapping;
- }
- }
- /**
- * TSL function for creating a cube map node.
- *
- * @tsl
- * @function
- * @param {Node} envNode - The node representing the environment map.
- * @returns {CubeMapNode}
- */
- const cubeMapNode = /*@__PURE__*/ nodeProxy( CubeMapNode ).setParameterLength( 1 );
- /**
- * Represents a basic model for Image-based lighting (IBL). The environment
- * is defined via environment maps in the equirectangular or cube map format.
- * `BasicEnvironmentNode` is intended for non-PBR materials like {@link MeshBasicNodeMaterial}
- * or {@link MeshPhongNodeMaterial}.
- *
- * @augments LightingNode
- */
- class BasicEnvironmentNode extends LightingNode {
- static get type() {
- return 'BasicEnvironmentNode';
- }
- /**
- * Constructs a new basic environment node.
- *
- * @param {Node} [envNode=null] - A node representing the environment.
- */
- constructor( envNode = null ) {
- super();
- /**
- * A node representing the environment.
- *
- * @type {Node}
- * @default null
- */
- this.envNode = envNode;
- }
- setup( builder ) {
- // environment property is used in the finish() method of BasicLightingModel
- builder.context.environment = cubeMapNode( this.envNode );
- }
- }
- /**
- * A specific version of {@link IrradianceNode} that is only relevant
- * for {@link MeshBasicNodeMaterial}. Since the material is unlit, it
- * requires a special scaling factor for the light map.
- *
- * @augments LightingNode
- */
- class BasicLightMapNode extends LightingNode {
- static get type() {
- return 'BasicLightMapNode';
- }
- /**
- * Constructs a new basic light map node.
- *
- * @param {?Node<vec3>} [lightMapNode=null] - The light map node.
- */
- constructor( lightMapNode = null ) {
- super();
- /**
- * The light map node.
- *
- * @type {?Node<vec3>}
- */
- this.lightMapNode = lightMapNode;
- }
- setup( builder ) {
- // irradianceLightMap property is used in the indirectDiffuse() method of BasicLightingModel
- const RECIPROCAL_PI = float( 1 / Math.PI );
- builder.context.irradianceLightMap = this.lightMapNode.mul( RECIPROCAL_PI );
- }
- }
- /**
- * Abstract class for implementing lighting models. The module defines
- * multiple methods that concrete lighting models can implement. These
- * methods are executed at different points during the light evaluation
- * process.
- */
- class LightingModel {
- /**
- * This method is intended for setting up lighting model and context data
- * which are later used in the evaluation process.
- *
- * @abstract
- * @param {NodeBuilder} builder - The current node builder.
- */
- start( builder ) {
- // lights ( direct )
- builder.lightsNode.setupLights( builder, builder.lightsNode.getLightNodes( builder ) );
- // indirect
- this.indirect( builder );
- }
- /**
- * This method is intended for executing final tasks like final updates
- * to the outgoing light.
- *
- * @abstract
- * @param {NodeBuilder} builder - The current node builder.
- */
- finish( /*builder*/ ) { }
- /**
- * This method is intended for implementing the direct light term and
- * executed during the build process of directional, point and spot light nodes.
- *
- * @abstract
- * @param {Object} lightData - The light data.
- * @param {NodeBuilder} builder - The current node builder.
- */
- direct( /*lightData, builder*/ ) { }
- /**
- * This method is intended for implementing the direct light term for
- * rect area light nodes.
- *
- * @abstract
- * @param {Object} lightData - The light data.
- * @param {NodeBuilder} builder - The current node builder.
- */
- directRectArea( /*lightData, builder*/ ) {}
- /**
- * This method is intended for implementing the indirect light term.
- *
- * @abstract
- * @param {NodeBuilder} builder - The current node builder.
- */
- indirect( /*builder*/ ) { }
- /**
- * This method is intended for implementing the ambient occlusion term.
- * Unlike other methods, this method must be called manually by the lighting
- * model in its indirect term.
- *
- * @abstract
- * @param {NodeBuilder} builder - The current node builder.
- */
- ambientOcclusion( /*input, stack, builder*/ ) { }
- }
- /**
- * Represents the lighting model for unlit materials. The only light contribution
- * is baked indirect lighting modulated with ambient occlusion and the material's
- * diffuse color. Environment mapping is supported. Used in {@link MeshBasicNodeMaterial}.
- *
- * @augments LightingModel
- */
- class BasicLightingModel extends LightingModel {
- /**
- * Constructs a new basic lighting model.
- */
- constructor() {
- super();
- }
- /**
- * Implements the baked indirect lighting with its modulation.
- *
- * @param {NodeBuilder} builder - The current node builder.
- */
- indirect( { context } ) {
- const ambientOcclusion = context.ambientOcclusion;
- const reflectedLight = context.reflectedLight;
- const irradianceLightMap = context.irradianceLightMap;
- reflectedLight.indirectDiffuse.assign( vec4( 0.0 ) );
- // accumulation (baked indirect lighting only)
- if ( irradianceLightMap ) {
- reflectedLight.indirectDiffuse.addAssign( irradianceLightMap );
- } else {
- reflectedLight.indirectDiffuse.addAssign( vec4( 1.0, 1.0, 1.0, 0.0 ) );
- }
- // modulation
- reflectedLight.indirectDiffuse.mulAssign( ambientOcclusion );
- reflectedLight.indirectDiffuse.mulAssign( diffuseColor.rgb );
- }
- /**
- * Implements the environment mapping.
- *
- * @param {NodeBuilder} builder - The current node builder.
- */
- finish( builder ) {
- const { material, context } = builder;
- const outgoingLight = context.outgoingLight;
- const envNode = builder.context.environment;
- if ( envNode ) {
- switch ( material.combine ) {
- case MultiplyOperation:
- outgoingLight.rgb.assign( mix( outgoingLight.rgb, outgoingLight.rgb.mul( envNode.rgb ), materialSpecularStrength.mul( materialReflectivity ) ) );
- break;
- case MixOperation:
- outgoingLight.rgb.assign( mix( outgoingLight.rgb, envNode.rgb, materialSpecularStrength.mul( materialReflectivity ) ) );
- break;
- case AddOperation:
- outgoingLight.rgb.addAssign( envNode.rgb.mul( materialSpecularStrength.mul( materialReflectivity ) ) );
- break;
- default:
- warn( 'BasicLightingModel: Unsupported .combine value:', material.combine );
- break;
- }
- }
- }
- }
- const _defaultValues$9 = /*@__PURE__*/ new MeshBasicMaterial();
- /**
- * Node material version of {@link MeshBasicMaterial}.
- *
- * @augments NodeMaterial
- */
- class MeshBasicNodeMaterial extends NodeMaterial {
- static get type() {
- return 'MeshBasicNodeMaterial';
- }
- /**
- * Constructs a new mesh basic node material.
- *
- * @param {Object} [parameters] - The configuration parameter.
- */
- constructor( parameters ) {
- super();
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isMeshBasicNodeMaterial = true;
- /**
- * Although the basic material is by definition unlit, we set
- * this property to `true` since we use a lighting model to compute
- * the outgoing light of the fragment shader.
- *
- * @type {boolean}
- * @default true
- */
- this.lights = true;
- this.setDefaultValues( _defaultValues$9 );
- this.setValues( parameters );
- }
- /**
- * Basic materials are not affected by normal and bump maps so we
- * return by default {@link normalViewGeometry}.
- *
- * @return {Node<vec3>} The normal node.
- */
- setupNormal() {
- return directionToFaceDirection( normalViewGeometry ); // see #28839
- }
- /**
- * Overwritten since this type of material uses {@link BasicEnvironmentNode}
- * to implement the default environment mapping.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {?BasicEnvironmentNode<vec3>} The environment node.
- */
- setupEnvironment( builder ) {
- const envNode = super.setupEnvironment( builder );
- return envNode ? new BasicEnvironmentNode( envNode ) : null;
- }
- /**
- * This method must be overwritten since light maps are evaluated
- * with a special scaling factor for basic materials.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {?BasicLightMapNode<vec3>} The light map node.
- */
- setupLightMap( builder ) {
- let node = null;
- if ( builder.material.lightMap ) {
- node = new BasicLightMapNode( materialLightMap );
- }
- return node;
- }
- /**
- * The material overwrites this method because `lights` is set to `true` but
- * we still want to return the diffuse color as the outgoing light.
- *
- * @return {Node<vec3>} The outgoing light node.
- */
- setupOutgoingLight() {
- return diffuseColor.rgb;
- }
- /**
- * Setups the lighting model.
- *
- * @return {BasicLightingModel} The lighting model.
- */
- setupLightingModel() {
- return new BasicLightingModel();
- }
- }
- const F_Schlick = /*@__PURE__*/ Fn( ( { f0, f90, dotVH } ) => {
- // Original approximation by Christophe Schlick '94
- // float fresnel = pow( 1.0 - dotVH, 5.0 );
- // Optimized variant (presented by Epic at SIGGRAPH '13)
- // https://cdn2.unrealengine.com/Resources/files/2013SiggraphPresentationsNotes-26915738.pdf
- const fresnel = dotVH.mul( -5.55473 ).sub( 6.98316 ).mul( dotVH ).exp2();
- return f0.mul( fresnel.oneMinus() ).add( f90.mul( fresnel ) );
- } ); // validated
- const BRDF_Lambert = /*@__PURE__*/ Fn( ( inputs ) => {
- return inputs.diffuseColor.mul( 1 / Math.PI ); // punctual light
- } ); // validated
- const G_BlinnPhong_Implicit = () => float( 0.25 );
- const D_BlinnPhong = /*@__PURE__*/ Fn( ( { dotNH } ) => {
- return shininess.mul( float( 0.5 ) ).add( 1.0 ).mul( float( 1 / Math.PI ) ).mul( dotNH.pow( shininess ) );
- } );
- const BRDF_BlinnPhong = /*@__PURE__*/ Fn( ( { lightDirection } ) => {
- const halfDir = lightDirection.add( positionViewDirection ).normalize();
- const dotNH = normalView.dot( halfDir ).clamp();
- const dotVH = positionViewDirection.dot( halfDir ).clamp();
- const F = F_Schlick( { f0: specularColor, f90: 1.0, dotVH } );
- const G = G_BlinnPhong_Implicit();
- const D = D_BlinnPhong( { dotNH } );
- return F.mul( G ).mul( D );
- } );
- /**
- * Represents the lighting model for a phong material. Used in {@link MeshPhongNodeMaterial}.
- *
- * @augments BasicLightingModel
- */
- class PhongLightingModel extends BasicLightingModel {
- /**
- * Constructs a new phong lighting model.
- *
- * @param {boolean} [specular=true] - Whether specular is supported or not.
- */
- constructor( specular = true ) {
- super();
- /**
- * Whether specular is supported or not. Set this to `false` if you are
- * looking for a Lambert-like material meaning a material for non-shiny
- * surfaces, without specular highlights.
- *
- * @type {boolean}
- * @default true
- */
- this.specular = specular;
- }
- /**
- * Implements the direct lighting. The specular portion is optional an can be controlled
- * with the {@link PhongLightingModel#specular} flag.
- *
- * @param {Object} lightData - The light data.
- */
- direct( { lightDirection, lightColor, reflectedLight } ) {
- const dotNL = normalView.dot( lightDirection ).clamp();
- const irradiance = dotNL.mul( lightColor );
- reflectedLight.directDiffuse.addAssign( irradiance.mul( BRDF_Lambert( { diffuseColor: diffuseColor.rgb } ) ) );
- if ( this.specular === true ) {
- reflectedLight.directSpecular.addAssign( irradiance.mul( BRDF_BlinnPhong( { lightDirection } ) ).mul( materialSpecularStrength ) );
- }
- }
- /**
- * Implements the indirect lighting.
- *
- * @param {NodeBuilder} builder - The current node builder.
- */
- indirect( builder ) {
- const { ambientOcclusion, irradiance, reflectedLight } = builder.context;
- reflectedLight.indirectDiffuse.addAssign( irradiance.mul( BRDF_Lambert( { diffuseColor } ) ) );
- reflectedLight.indirectDiffuse.mulAssign( ambientOcclusion );
- }
- }
- const _defaultValues$8 = /*@__PURE__*/ new MeshLambertMaterial();
- /**
- * Node material version of {@link MeshLambertMaterial}.
- *
- * @augments NodeMaterial
- */
- class MeshLambertNodeMaterial extends NodeMaterial {
- static get type() {
- return 'MeshLambertNodeMaterial';
- }
- /**
- * Constructs a new mesh lambert node material.
- *
- * @param {Object} [parameters] - The configuration parameter.
- */
- constructor( parameters ) {
- super();
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isMeshLambertNodeMaterial = true;
- /**
- * Set to `true` because lambert materials react on lights.
- *
- * @type {boolean}
- * @default true
- */
- this.lights = true;
- this.setDefaultValues( _defaultValues$8 );
- this.setValues( parameters );
- }
- /**
- * Overwritten since this type of material uses {@link BasicEnvironmentNode}
- * to implement the default environment mapping.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {?BasicEnvironmentNode<vec3>} The environment node.
- */
- setupEnvironment( builder ) {
- const envNode = super.setupEnvironment( builder );
- return envNode ? new BasicEnvironmentNode( envNode ) : null;
- }
- /**
- * Setups the lighting model.
- *
- * @return {PhongLightingModel} The lighting model.
- */
- setupLightingModel( /*builder*/ ) {
- return new PhongLightingModel( false ); // ( specular ) -> force lambert
- }
- }
- const _defaultValues$7 = /*@__PURE__*/ new MeshPhongMaterial();
- /**
- * Node material version of {@link MeshPhongMaterial}.
- *
- * @augments NodeMaterial
- */
- class MeshPhongNodeMaterial extends NodeMaterial {
- static get type() {
- return 'MeshPhongNodeMaterial';
- }
- /**
- * Constructs a new mesh lambert node material.
- *
- * @param {Object} [parameters] - The configuration parameter.
- */
- constructor( parameters ) {
- super();
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isMeshPhongNodeMaterial = true;
- /**
- * Set to `true` because phong materials react on lights.
- *
- * @type {boolean}
- * @default true
- */
- this.lights = true;
- /**
- * The shininess of phong materials is by default inferred from the `shininess`
- * property. This node property allows to overwrite the default
- * and define the shininess with a node instead.
- *
- * If you don't want to overwrite the shininess but modify the existing
- * value instead, use {@link materialShininess}.
- *
- * @type {?Node<float>}
- * @default null
- */
- this.shininessNode = null;
- /**
- * The specular color of phong materials is by default inferred from the
- * `specular` property. This node property allows to overwrite the default
- * and define the specular color with a node instead.
- *
- * If you don't want to overwrite the specular color but modify the existing
- * value instead, use {@link materialSpecular}.
- *
- * @type {?Node<vec3>}
- * @default null
- */
- this.specularNode = null;
- this.setDefaultValues( _defaultValues$7 );
- this.setValues( parameters );
- }
- /**
- * Overwritten since this type of material uses {@link BasicEnvironmentNode}
- * to implement the default environment mapping.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {?BasicEnvironmentNode<vec3>} The environment node.
- */
- setupEnvironment( builder ) {
- const envNode = super.setupEnvironment( builder );
- return envNode ? new BasicEnvironmentNode( envNode ) : null;
- }
- /**
- * Setups the lighting model.
- *
- * @return {PhongLightingModel} The lighting model.
- */
- setupLightingModel( /*builder*/ ) {
- return new PhongLightingModel();
- }
- /**
- * Setups the phong specific node variables.
- *
- * @param {NodeBuilder} builder - The current node builder.
- */
- setupVariants( /*builder*/ ) {
- // SHININESS
- const shininessNode = ( this.shininessNode ? float( this.shininessNode ) : materialShininess ).max( 1e-4 ); // to prevent pow( 0.0, 0.0 )
- shininess.assign( shininessNode );
- // SPECULAR COLOR
- const specularNode = this.specularNode || materialSpecular;
- specularColor.assign( specularNode );
- }
- copy( source ) {
- this.shininessNode = source.shininessNode;
- this.specularNode = source.specularNode;
- return super.copy( source );
- }
- }
- const getGeometryRoughness = /*@__PURE__*/ Fn( ( builder ) => {
- if ( builder.geometry.hasAttribute( 'normal' ) === false ) {
- return float( 0 );
- }
- const dxy = normalViewGeometry.dFdx().abs().max( normalViewGeometry.dFdy().abs() );
- const geometryRoughness = dxy.x.max( dxy.y ).max( dxy.z );
- return geometryRoughness;
- } );
- const getRoughness = /*@__PURE__*/ Fn( ( inputs ) => {
- const { roughness } = inputs;
- const geometryRoughness = getGeometryRoughness();
- let roughnessFactor = roughness.max( 0.0525 ); // 0.0525 corresponds to the base mip of a 256 cubemap.
- roughnessFactor = roughnessFactor.add( geometryRoughness );
- roughnessFactor = roughnessFactor.min( 1.0 );
- return roughnessFactor;
- } );
- // Moving Frostbite to Physically Based Rendering 3.0 - page 12, listing 2
- // https://seblagarde.files.wordpress.com/2015/07/course_notes_moving_frostbite_to_pbr_v32.pdf
- const V_GGX_SmithCorrelated = /*@__PURE__*/ Fn( ( { alpha, dotNL, dotNV } ) => {
- const a2 = alpha.pow2();
- const gv = dotNL.mul( a2.add( a2.oneMinus().mul( dotNV.pow2() ) ).sqrt() );
- const gl = dotNV.mul( a2.add( a2.oneMinus().mul( dotNL.pow2() ) ).sqrt() );
- return div( 0.5, gv.add( gl ).max( EPSILON ) );
- } ).setLayout( {
- name: 'V_GGX_SmithCorrelated',
- type: 'float',
- inputs: [
- { name: 'alpha', type: 'float' },
- { name: 'dotNL', type: 'float' },
- { name: 'dotNV', type: 'float' }
- ]
- } ); // validated
- // https://google.github.io/filament/Filament.md.html#materialsystem/anisotropicmodel/anisotropicspecularbrdf
- const V_GGX_SmithCorrelated_Anisotropic = /*@__PURE__*/ Fn( ( { alphaT, alphaB, dotTV, dotBV, dotTL, dotBL, dotNV, dotNL } ) => {
- const gv = dotNL.mul( vec3( alphaT.mul( dotTV ), alphaB.mul( dotBV ), dotNV ).length() );
- const gl = dotNV.mul( vec3( alphaT.mul( dotTL ), alphaB.mul( dotBL ), dotNL ).length() );
- const v = div( 0.5, gv.add( gl ) );
- return v;
- } ).setLayout( {
- name: 'V_GGX_SmithCorrelated_Anisotropic',
- type: 'float',
- inputs: [
- { name: 'alphaT', type: 'float', qualifier: 'in' },
- { name: 'alphaB', type: 'float', qualifier: 'in' },
- { name: 'dotTV', type: 'float', qualifier: 'in' },
- { name: 'dotBV', type: 'float', qualifier: 'in' },
- { name: 'dotTL', type: 'float', qualifier: 'in' },
- { name: 'dotBL', type: 'float', qualifier: 'in' },
- { name: 'dotNV', type: 'float', qualifier: 'in' },
- { name: 'dotNL', type: 'float', qualifier: 'in' }
- ]
- } );
- // Microfacet Models for Refraction through Rough Surfaces - equation (33)
- // http://graphicrants.blogspot.com/2013/08/specular-brdf-reference.html
- // alpha is "roughness squared" in Disney’s reparameterization
- const D_GGX = /*@__PURE__*/ Fn( ( { alpha, dotNH } ) => {
- const a2 = alpha.pow2();
- const denom = dotNH.pow2().mul( a2.oneMinus() ).oneMinus(); // avoid alpha = 0 with dotNH = 1
- return a2.div( denom.pow2() ).mul( 1 / Math.PI );
- } ).setLayout( {
- name: 'D_GGX',
- type: 'float',
- inputs: [
- { name: 'alpha', type: 'float' },
- { name: 'dotNH', type: 'float' }
- ]
- } ); // validated
- const RECIPROCAL_PI = /*@__PURE__*/ float( 1 / Math.PI );
- // https://google.github.io/filament/Filament.md.html#materialsystem/anisotropicmodel/anisotropicspecularbrdf
- const D_GGX_Anisotropic = /*@__PURE__*/ Fn( ( { alphaT, alphaB, dotNH, dotTH, dotBH } ) => {
- const a2 = alphaT.mul( alphaB );
- const v = vec3( alphaB.mul( dotTH ), alphaT.mul( dotBH ), a2.mul( dotNH ) );
- const v2 = v.dot( v );
- const w2 = a2.div( v2 );
- return RECIPROCAL_PI.mul( a2.mul( w2.pow2() ) );
- } ).setLayout( {
- name: 'D_GGX_Anisotropic',
- type: 'float',
- inputs: [
- { name: 'alphaT', type: 'float', qualifier: 'in' },
- { name: 'alphaB', type: 'float', qualifier: 'in' },
- { name: 'dotNH', type: 'float', qualifier: 'in' },
- { name: 'dotTH', type: 'float', qualifier: 'in' },
- { name: 'dotBH', type: 'float', qualifier: 'in' }
- ]
- } );
- // GGX Distribution, Schlick Fresnel, GGX_SmithCorrelated Visibility
- const BRDF_GGX = /*@__PURE__*/ Fn( ( { lightDirection, f0, f90, roughness, f, normalView: normalView$1 = normalView, USE_IRIDESCENCE, USE_ANISOTROPY } ) => {
- const alpha = roughness.pow2(); // UE4's roughness
- const halfDir = lightDirection.add( positionViewDirection ).normalize();
- const dotNL = normalView$1.dot( lightDirection ).clamp();
- const dotNV = normalView$1.dot( positionViewDirection ).clamp(); // @ TODO: Move to core dotNV
- const dotNH = normalView$1.dot( halfDir ).clamp();
- const dotVH = positionViewDirection.dot( halfDir ).clamp();
- let F = F_Schlick( { f0, f90, dotVH } );
- let V, D;
- if ( defined( USE_IRIDESCENCE ) ) {
- F = iridescence.mix( F, f );
- }
- if ( defined( USE_ANISOTROPY ) ) {
- const dotTL = anisotropyT.dot( lightDirection );
- const dotTV = anisotropyT.dot( positionViewDirection );
- const dotTH = anisotropyT.dot( halfDir );
- const dotBL = anisotropyB.dot( lightDirection );
- const dotBV = anisotropyB.dot( positionViewDirection );
- const dotBH = anisotropyB.dot( halfDir );
- V = V_GGX_SmithCorrelated_Anisotropic( { alphaT, alphaB: alpha, dotTV, dotBV, dotTL, dotBL, dotNV, dotNL } );
- D = D_GGX_Anisotropic( { alphaT, alphaB: alpha, dotNH, dotTH, dotBH } );
- } else {
- V = V_GGX_SmithCorrelated( { alpha, dotNL, dotNV } );
- D = D_GGX( { alpha, dotNH } );
- }
- return F.mul( V ).mul( D );
- } ); // validated
- /**
- * Precomputed DFG LUT for Image-Based Lighting
- * Resolution: 16x16
- * Samples: 4096 per texel
- * Format: RG16F (2 half floats per texel: scale, bias)
- */
- const DATA = new Uint16Array( [
- 0x30b5, 0x3ad1, 0x314c, 0x3a4d, 0x33d2, 0x391c, 0x35ef, 0x3828, 0x37f3, 0x36a6, 0x38d1, 0x3539, 0x3979, 0x3410, 0x39f8, 0x3252, 0x3a53, 0x30f0, 0x3a94, 0x2fc9, 0x3abf, 0x2e35, 0x3ada, 0x2d05, 0x3ae8, 0x2c1f, 0x3aed, 0x2ae0, 0x3aea, 0x29d1, 0x3ae1, 0x28ff,
- 0x3638, 0x38e4, 0x364a, 0x38ce, 0x3699, 0x385e, 0x374e, 0x372c, 0x3839, 0x35a4, 0x38dc, 0x3462, 0x396e, 0x32c4, 0x39de, 0x3134, 0x3a2b, 0x3003, 0x3a59, 0x2e3a, 0x3a6d, 0x2ce1, 0x3a6e, 0x2bba, 0x3a5f, 0x2a33, 0x3a49, 0x290a, 0x3a2d, 0x2826, 0x3a0a, 0x26e8,
- 0x3894, 0x36d7, 0x3897, 0x36c9, 0x38a3, 0x3675, 0x38bc, 0x35ac, 0x38ee, 0x349c, 0x393e, 0x3332, 0x3997, 0x3186, 0x39e2, 0x3038, 0x3a13, 0x2e75, 0x3a29, 0x2cf5, 0x3a2d, 0x2bac, 0x3a21, 0x29ff, 0x3a04, 0x28bc, 0x39dc, 0x2790, 0x39ad, 0x261a, 0x3978, 0x24fa,
- 0x39ac, 0x34a8, 0x39ac, 0x34a3, 0x39ae, 0x3480, 0x39ae, 0x3423, 0x39b1, 0x330e, 0x39c2, 0x31a9, 0x39e0, 0x3063, 0x39fc, 0x2eb5, 0x3a0c, 0x2d1d, 0x3a14, 0x2bcf, 0x3a07, 0x29ff, 0x39e9, 0x28a3, 0x39be, 0x273c, 0x3989, 0x25b3, 0x394a, 0x2488, 0x3907, 0x2345,
- 0x3a77, 0x3223, 0x3a76, 0x321f, 0x3a73, 0x3204, 0x3a6a, 0x31b3, 0x3a58, 0x3114, 0x3a45, 0x303b, 0x3a34, 0x2eb6, 0x3a26, 0x2d31, 0x3a1e, 0x2bef, 0x3a0b, 0x2a0d, 0x39ec, 0x28a1, 0x39c0, 0x271b, 0x3987, 0x2580, 0x3944, 0x2449, 0x38fa, 0x22bd, 0x38ac, 0x2155,
- 0x3b07, 0x2fca, 0x3b06, 0x2fca, 0x3b00, 0x2fb8, 0x3af4, 0x2f7c, 0x3adb, 0x2eea, 0x3ab4, 0x2e00, 0x3a85, 0x2cec, 0x3a5e, 0x2bc5, 0x3a36, 0x2a00, 0x3a0d, 0x2899, 0x39dc, 0x2707, 0x39a0, 0x2562, 0x395a, 0x2424, 0x390b, 0x2268, 0x38b7, 0x20fd, 0x385f, 0x1fd1,
- 0x3b69, 0x2cb9, 0x3b68, 0x2cbb, 0x3b62, 0x2cbb, 0x3b56, 0x2cae, 0x3b3b, 0x2c78, 0x3b0d, 0x2c0a, 0x3acf, 0x2ae3, 0x3a92, 0x2998, 0x3a54, 0x2867, 0x3a17, 0x26d0, 0x39d3, 0x253c, 0x3989, 0x2402, 0x3935, 0x2226, 0x38dc, 0x20bd, 0x387d, 0x1f54, 0x381d, 0x1db3,
- 0x3ba9, 0x296b, 0x3ba8, 0x296f, 0x3ba3, 0x297b, 0x3b98, 0x2987, 0x3b7f, 0x2976, 0x3b4e, 0x2927, 0x3b0e, 0x2895, 0x3ac2, 0x27b7, 0x3a73, 0x263b, 0x3a23, 0x24e7, 0x39d0, 0x239b, 0x3976, 0x21d9, 0x3917, 0x207e, 0x38b2, 0x1ee7, 0x384b, 0x1d53, 0x37c7, 0x1c1e,
- 0x3bd2, 0x25cb, 0x3bd1, 0x25d3, 0x3bcd, 0x25f0, 0x3bc2, 0x261f, 0x3bad, 0x2645, 0x3b7d, 0x262d, 0x3b3e, 0x25c4, 0x3aec, 0x250f, 0x3a93, 0x243a, 0x3a32, 0x22ce, 0x39d0, 0x215b, 0x3969, 0x202a, 0x38fe, 0x1e6e, 0x388f, 0x1cf1, 0x381f, 0x1b9b, 0x3762, 0x19dd,
- 0x3be9, 0x21ab, 0x3be9, 0x21b7, 0x3be5, 0x21e5, 0x3bdd, 0x2241, 0x3bc9, 0x22a7, 0x3ba0, 0x22ec, 0x3b62, 0x22cd, 0x3b0f, 0x2247, 0x3aae, 0x2175, 0x3a44, 0x2088, 0x39d4, 0x1f49, 0x3960, 0x1dbe, 0x38e9, 0x1c77, 0x3870, 0x1ae8, 0x37f1, 0x1953, 0x3708, 0x181b,
- 0x3bf6, 0x1cea, 0x3bf6, 0x1cfb, 0x3bf3, 0x1d38, 0x3bec, 0x1dbd, 0x3bda, 0x1e7c, 0x3bb7, 0x1f25, 0x3b7d, 0x1f79, 0x3b2c, 0x1f4c, 0x3ac6, 0x1ea6, 0x3a55, 0x1dbb, 0x39da, 0x1cbd, 0x395a, 0x1b9d, 0x38d8, 0x1a00, 0x3855, 0x18ac, 0x37ab, 0x173c, 0x36b7, 0x1598,
- 0x3bfc, 0x1736, 0x3bfc, 0x1759, 0x3bf9, 0x17e7, 0x3bf4, 0x1896, 0x3be4, 0x1997, 0x3bc6, 0x1aa8, 0x3b91, 0x1b84, 0x3b43, 0x1bd2, 0x3ade, 0x1b8a, 0x3a65, 0x1acd, 0x39e2, 0x19d3, 0x3957, 0x18cd, 0x38ca, 0x17b3, 0x383e, 0x1613, 0x376d, 0x14bf, 0x366f, 0x135e,
- 0x3bff, 0x101b, 0x3bff, 0x1039, 0x3bfc, 0x10c8, 0x3bf9, 0x1226, 0x3bea, 0x1428, 0x3bcf, 0x1584, 0x3b9f, 0x16c5, 0x3b54, 0x179a, 0x3af0, 0x17ce, 0x3a76, 0x1771, 0x39ea, 0x16a4, 0x3956, 0x15a7, 0x38bf, 0x14a7, 0x3829, 0x1379, 0x3735, 0x11ea, 0x362d, 0x10a1,
- 0x3c00, 0x061b, 0x3c00, 0x066a, 0x3bfe, 0x081c, 0x3bfa, 0x0a4c, 0x3bed, 0x0d16, 0x3bd5, 0x0fb3, 0x3ba9, 0x114d, 0x3b63, 0x127c, 0x3b01, 0x132f, 0x3a85, 0x1344, 0x39f4, 0x12d2, 0x3957, 0x120d, 0x38b5, 0x1122, 0x3817, 0x103c, 0x3703, 0x0ed3, 0x35f0, 0x0d6d,
- 0x3c00, 0x007a, 0x3c00, 0x0089, 0x3bfe, 0x011d, 0x3bfb, 0x027c, 0x3bf0, 0x04fa, 0x3bda, 0x0881, 0x3bb1, 0x0acd, 0x3b6f, 0x0c97, 0x3b10, 0x0d7b, 0x3a93, 0x0df1, 0x39fe, 0x0def, 0x3959, 0x0d8a, 0x38af, 0x0ce9, 0x3808, 0x0c31, 0x36d5, 0x0af0, 0x35b9, 0x09a3,
- 0x3c00, 0x0000, 0x3c00, 0x0001, 0x3bff, 0x0015, 0x3bfb, 0x0059, 0x3bf2, 0x00fd, 0x3bdd, 0x01df, 0x3bb7, 0x031c, 0x3b79, 0x047c, 0x3b1d, 0x05d4, 0x3aa0, 0x06d5, 0x3a08, 0x075a, 0x395d, 0x075e, 0x38aa, 0x06f7, 0x37f4, 0x0648, 0x36ac, 0x0576, 0x3586, 0x049f
- ] );
- let lut = null;
- const DFGLUT = /*@__PURE__*/ Fn( ( { roughness, dotNV } ) => {
- if ( lut === null ) {
- lut = new DataTexture( DATA, 16, 16, RGFormat, HalfFloatType );
- lut.name = 'DFG_LUT';
- lut.minFilter = LinearFilter;
- lut.magFilter = LinearFilter;
- lut.wrapS = ClampToEdgeWrapping;
- lut.wrapT = ClampToEdgeWrapping;
- lut.generateMipmaps = false;
- lut.needsUpdate = true;
- }
- const uv = vec2( roughness, dotNV );
- return texture( lut, uv ).rg;
- } );
- // GGX BRDF with multi-scattering energy compensation for direct lighting
- // This provides more accurate energy conservation, especially for rough materials
- // Based on "Practical Multiple Scattering Compensation for Microfacet Models"
- // https://blog.selfshadow.com/publications/turquin/ms_comp_final.pdf
- const BRDF_GGX_Multiscatter = /*@__PURE__*/ Fn( ( { lightDirection, f0, f90, roughness: _roughness, f, USE_IRIDESCENCE, USE_ANISOTROPY } ) => {
- // Single-scattering BRDF (standard GGX)
- const singleScatter = BRDF_GGX( { lightDirection, f0, f90, roughness: _roughness, f, USE_IRIDESCENCE, USE_ANISOTROPY } );
- // Multi-scattering compensation
- const dotNL = normalView.dot( lightDirection ).clamp();
- const dotNV = normalView.dot( positionViewDirection ).clamp();
- // Precomputed DFG values for view and light directions
- const dfgV = DFGLUT( { roughness: _roughness, dotNV } );
- const dfgL = DFGLUT( { roughness: _roughness, dotNV: dotNL } );
- // Single-scattering energy for view and light
- const FssEss_V = f0.mul( dfgV.x ).add( f90.mul( dfgV.y ) );
- const FssEss_L = f0.mul( dfgL.x ).add( f90.mul( dfgL.y ) );
- const Ess_V = dfgV.x.add( dfgV.y );
- const Ess_L = dfgL.x.add( dfgL.y );
- // Energy lost to multiple scattering
- const Ems_V = float( 1.0 ).sub( Ess_V );
- const Ems_L = float( 1.0 ).sub( Ess_L );
- // Average Fresnel reflectance
- const Favg = f0.add( f0.oneMinus().mul( 0.047619 ) ); // 1/21
- // Multiple scattering contribution
- // Uses geometric mean of view and light contributions for better energy distribution
- const Fms = FssEss_V.mul( FssEss_L ).mul( Favg ).div( float( 1.0 ).sub( Ems_V.mul( Ems_L ).mul( Favg ).mul( Favg ) ).add( EPSILON ) );
- // Energy compensation factor
- const compensationFactor = Ems_V.mul( Ems_L );
- const multiScatter = Fms.mul( compensationFactor );
- return singleScatter.add( multiScatter );
- } );
- const EnvironmentBRDF = /*@__PURE__*/ Fn( ( inputs ) => {
- const { dotNV, specularColor, specularF90, roughness } = inputs;
- const fab = DFGLUT( { dotNV, roughness } );
- return specularColor.mul( fab.x ).add( specularF90.mul( fab.y ) );
- } );
- const Schlick_to_F0 = /*@__PURE__*/ Fn( ( { f, f90, dotVH } ) => {
- const x = dotVH.oneMinus().saturate();
- const x2 = x.mul( x );
- const x5 = x.mul( x2, x2 ).clamp( 0, .9999 );
- return f.sub( vec3( f90 ).mul( x5 ) ).div( x5.oneMinus() );
- } ).setLayout( {
- name: 'Schlick_to_F0',
- type: 'vec3',
- inputs: [
- { name: 'f', type: 'vec3' },
- { name: 'f90', type: 'float' },
- { name: 'dotVH', type: 'float' }
- ]
- } );
- // https://github.com/google/filament/blob/master/shaders/src/brdf.fs
- const D_Charlie = /*@__PURE__*/ Fn( ( { roughness, dotNH } ) => {
- const alpha = roughness.pow2();
- // Estevez and Kulla 2017, "Production Friendly Microfacet Sheen BRDF"
- const invAlpha = float( 1.0 ).div( alpha );
- const cos2h = dotNH.pow2();
- const sin2h = cos2h.oneMinus().max( 0.0078125 ); // 2^(-14/2), so sin2h^2 > 0 in fp16
- return float( 2.0 ).add( invAlpha ).mul( sin2h.pow( invAlpha.mul( 0.5 ) ) ).div( 2.0 * Math.PI );
- } ).setLayout( {
- name: 'D_Charlie',
- type: 'float',
- inputs: [
- { name: 'roughness', type: 'float' },
- { name: 'dotNH', type: 'float' }
- ]
- } );
- // https://github.com/google/filament/blob/master/shaders/src/brdf.fs
- const V_Neubelt = /*@__PURE__*/ Fn( ( { dotNV, dotNL } ) => {
- // Neubelt and Pettineo 2013, "Crafting a Next-gen Material Pipeline for The Order: 1886"
- return float( 1.0 ).div( float( 4.0 ).mul( dotNL.add( dotNV ).sub( dotNL.mul( dotNV ) ) ) );
- } ).setLayout( {
- name: 'V_Neubelt',
- type: 'float',
- inputs: [
- { name: 'dotNV', type: 'float' },
- { name: 'dotNL', type: 'float' }
- ]
- } );
- const BRDF_Sheen = /*@__PURE__*/ Fn( ( { lightDirection } ) => {
- const halfDir = lightDirection.add( positionViewDirection ).normalize();
- const dotNL = normalView.dot( lightDirection ).clamp();
- const dotNV = normalView.dot( positionViewDirection ).clamp();
- const dotNH = normalView.dot( halfDir ).clamp();
- const D = D_Charlie( { roughness: sheenRoughness, dotNH } );
- const V = V_Neubelt( { dotNV, dotNL } );
- return sheen.mul( D ).mul( V );
- } );
- // Rect Area Light
- // Real-Time Polygonal-Light Shading with Linearly Transformed Cosines
- // by Eric Heitz, Jonathan Dupuy, Stephen Hill and David Neubelt
- // code: https://github.com/selfshadow/ltc_code/
- const LTC_Uv = /*@__PURE__*/ Fn( ( { N, V, roughness } ) => {
- const LUT_SIZE = 64.0;
- const LUT_SCALE = ( LUT_SIZE - 1.0 ) / LUT_SIZE;
- const LUT_BIAS = 0.5 / LUT_SIZE;
- const dotNV = N.dot( V ).saturate();
- // texture parameterized by sqrt( GGX alpha ) and sqrt( 1 - cos( theta ) )
- const uv = vec2( roughness, dotNV.oneMinus().sqrt() );
- uv.assign( uv.mul( LUT_SCALE ).add( LUT_BIAS ) );
- return uv;
- } ).setLayout( {
- name: 'LTC_Uv',
- type: 'vec2',
- inputs: [
- { name: 'N', type: 'vec3' },
- { name: 'V', type: 'vec3' },
- { name: 'roughness', type: 'float' }
- ]
- } );
- const LTC_ClippedSphereFormFactor = /*@__PURE__*/ Fn( ( { f } ) => {
- // Real-Time Area Lighting: a Journey from Research to Production (p.102)
- // An approximation of the form factor of a horizon-clipped rectangle.
- const l = f.length();
- return max$1( l.mul( l ).add( f.z ).div( l.add( 1.0 ) ), 0 );
- } ).setLayout( {
- name: 'LTC_ClippedSphereFormFactor',
- type: 'float',
- inputs: [
- { name: 'f', type: 'vec3' }
- ]
- } );
- const LTC_EdgeVectorFormFactor = /*@__PURE__*/ Fn( ( { v1, v2 } ) => {
- const x = v1.dot( v2 );
- const y = x.abs().toVar();
- // rational polynomial approximation to theta / sin( theta ) / 2PI
- const a = y.mul( 0.0145206 ).add( 0.4965155 ).mul( y ).add( 0.8543985 ).toVar();
- const b = y.add( 4.1616724 ).mul( y ).add( 3.4175940 ).toVar();
- const v = a.div( b );
- const theta_sintheta = x.greaterThan( 0.0 ).select( v, max$1( x.mul( x ).oneMinus(), 1e-7 ).inverseSqrt().mul( 0.5 ).sub( v ) );
- return v1.cross( v2 ).mul( theta_sintheta );
- } ).setLayout( {
- name: 'LTC_EdgeVectorFormFactor',
- type: 'vec3',
- inputs: [
- { name: 'v1', type: 'vec3' },
- { name: 'v2', type: 'vec3' }
- ]
- } );
- const LTC_Evaluate = /*@__PURE__*/ Fn( ( { N, V, P, mInv, p0, p1, p2, p3 } ) => {
- // bail if point is on back side of plane of light
- // assumes ccw winding order of light vertices
- const v1 = p1.sub( p0 ).toVar();
- const v2 = p3.sub( p0 ).toVar();
- const lightNormal = v1.cross( v2 );
- const result = vec3().toVar();
- If( lightNormal.dot( P.sub( p0 ) ).greaterThanEqual( 0.0 ), () => {
- // construct orthonormal basis around N
- const T1 = V.sub( N.mul( V.dot( N ) ) ).normalize();
- const T2 = N.cross( T1 ).negate(); // negated from paper; possibly due to a different handedness of world coordinate system
- // compute transform
- const mat = mInv.mul( mat3( T1, T2, N ).transpose() ).toVar();
- // transform rect
- // & project rect onto sphere
- const coords0 = mat.mul( p0.sub( P ) ).normalize().toVar();
- const coords1 = mat.mul( p1.sub( P ) ).normalize().toVar();
- const coords2 = mat.mul( p2.sub( P ) ).normalize().toVar();
- const coords3 = mat.mul( p3.sub( P ) ).normalize().toVar();
- // calculate vector form factor
- const vectorFormFactor = vec3( 0 ).toVar();
- vectorFormFactor.addAssign( LTC_EdgeVectorFormFactor( { v1: coords0, v2: coords1 } ) );
- vectorFormFactor.addAssign( LTC_EdgeVectorFormFactor( { v1: coords1, v2: coords2 } ) );
- vectorFormFactor.addAssign( LTC_EdgeVectorFormFactor( { v1: coords2, v2: coords3 } ) );
- vectorFormFactor.addAssign( LTC_EdgeVectorFormFactor( { v1: coords3, v2: coords0 } ) );
- // adjust for horizon clipping
- result.assign( vec3( LTC_ClippedSphereFormFactor( { f: vectorFormFactor } ) ) );
- } );
- return result;
- } ).setLayout( {
- name: 'LTC_Evaluate',
- type: 'vec3',
- inputs: [
- { name: 'N', type: 'vec3' },
- { name: 'V', type: 'vec3' },
- { name: 'P', type: 'vec3' },
- { name: 'mInv', type: 'mat3' },
- { name: 'p0', type: 'vec3' },
- { name: 'p1', type: 'vec3' },
- { name: 'p2', type: 'vec3' },
- { name: 'p3', type: 'vec3' }
- ]
- } );
- const LTC_Evaluate_Volume = /*@__PURE__*/ Fn( ( { P, p0, p1, p2, p3 } ) => {
- // bail if point is on back side of plane of light
- // assumes ccw winding order of light vertices
- const v1 = p1.sub( p0 ).toVar();
- const v2 = p3.sub( p0 ).toVar();
- const lightNormal = v1.cross( v2 );
- const result = vec3().toVar();
- If( lightNormal.dot( P.sub( p0 ) ).greaterThanEqual( 0.0 ), () => {
- // transform rect
- // & project rect onto sphere
- const coords0 = p0.sub( P ).normalize().toVar();
- const coords1 = p1.sub( P ).normalize().toVar();
- const coords2 = p2.sub( P ).normalize().toVar();
- const coords3 = p3.sub( P ).normalize().toVar();
- // calculate vector form factor
- const vectorFormFactor = vec3( 0 ).toVar();
- vectorFormFactor.addAssign( LTC_EdgeVectorFormFactor( { v1: coords0, v2: coords1 } ) );
- vectorFormFactor.addAssign( LTC_EdgeVectorFormFactor( { v1: coords1, v2: coords2 } ) );
- vectorFormFactor.addAssign( LTC_EdgeVectorFormFactor( { v1: coords2, v2: coords3 } ) );
- vectorFormFactor.addAssign( LTC_EdgeVectorFormFactor( { v1: coords3, v2: coords0 } ) );
- // adjust for horizon clipping
- result.assign( vec3( LTC_ClippedSphereFormFactor( { f: vectorFormFactor.abs() } ) ) );
- } );
- return result;
- } ).setLayout( {
- name: 'LTC_Evaluate',
- type: 'vec3',
- inputs: [
- { name: 'P', type: 'vec3' },
- { name: 'p0', type: 'vec3' },
- { name: 'p1', type: 'vec3' },
- { name: 'p2', type: 'vec3' },
- { name: 'p3', type: 'vec3' }
- ]
- } );
- // Mipped Bicubic Texture Filtering by N8
- // https://www.shadertoy.com/view/Dl2SDW
- const bC = 1.0 / 6.0;
- const w0 = ( a ) => mul( bC, mul( a, mul( a, a.negate().add( 3.0 ) ).sub( 3.0 ) ).add( 1.0 ) );
- const w1 = ( a ) => mul( bC, mul( a, mul( a, mul( 3.0, a ).sub( 6.0 ) ) ).add( 4.0 ) );
- const w2 = ( a ) => mul( bC, mul( a, mul( a, mul( -3, a ).add( 3.0 ) ).add( 3.0 ) ).add( 1.0 ) );
- const w3 = ( a ) => mul( bC, pow( a, 3 ) );
- const g0 = ( a ) => w0( a ).add( w1( a ) );
- const g1 = ( a ) => w2( a ).add( w3( a ) );
- // h0 and h1 are the two offset functions
- const h0 = ( a ) => add( -1, w1( a ).div( w0( a ).add( w1( a ) ) ) );
- const h1 = ( a ) => add( 1.0, w3( a ).div( w2( a ).add( w3( a ) ) ) );
- const bicubic = ( textureNode, texelSize, lod ) => {
- const uv = textureNode.uvNode;
- const uvScaled = mul( uv, texelSize.zw ).add( 0.5 );
- const iuv = floor( uvScaled );
- const fuv = fract( uvScaled );
- const g0x = g0( fuv.x );
- const g1x = g1( fuv.x );
- const h0x = h0( fuv.x );
- const h1x = h1( fuv.x );
- const h0y = h0( fuv.y );
- const h1y = h1( fuv.y );
- const p0 = vec2( iuv.x.add( h0x ), iuv.y.add( h0y ) ).sub( 0.5 ).mul( texelSize.xy );
- const p1 = vec2( iuv.x.add( h1x ), iuv.y.add( h0y ) ).sub( 0.5 ).mul( texelSize.xy );
- const p2 = vec2( iuv.x.add( h0x ), iuv.y.add( h1y ) ).sub( 0.5 ).mul( texelSize.xy );
- const p3 = vec2( iuv.x.add( h1x ), iuv.y.add( h1y ) ).sub( 0.5 ).mul( texelSize.xy );
- const a = g0( fuv.y ).mul( add( g0x.mul( textureNode.sample( p0 ).level( lod ) ), g1x.mul( textureNode.sample( p1 ).level( lod ) ) ) );
- const b = g1( fuv.y ).mul( add( g0x.mul( textureNode.sample( p2 ).level( lod ) ), g1x.mul( textureNode.sample( p3 ).level( lod ) ) ) );
- return a.add( b );
- };
- /**
- * Applies mipped bicubic texture filtering to the given texture node.
- *
- * @tsl
- * @function
- * @param {TextureNode} textureNode - The texture node that should be filtered.
- * @param {Node<float>} lodNode - Defines the LOD to sample from.
- * @return {Node} The filtered texture sample.
- */
- const textureBicubicLevel = /*@__PURE__*/ Fn( ( [ textureNode, lodNode ] ) => {
- const fLodSize = vec2( textureNode.size( int( lodNode ) ) );
- const cLodSize = vec2( textureNode.size( int( lodNode.add( 1.0 ) ) ) );
- const fLodSizeInv = div( 1.0, fLodSize );
- const cLodSizeInv = div( 1.0, cLodSize );
- const fSample = bicubic( textureNode, vec4( fLodSizeInv, fLodSize ), floor( lodNode ) );
- const cSample = bicubic( textureNode, vec4( cLodSizeInv, cLodSize ), ceil( lodNode ) );
- return fract( lodNode ).mix( fSample, cSample );
- } );
- /**
- * Applies mipped bicubic texture filtering to the given texture node.
- *
- * @tsl
- * @function
- * @param {TextureNode} textureNode - The texture node that should be filtered.
- * @param {Node<float>} [strength] - Defines the strength of the bicubic filtering.
- * @return {Node} The filtered texture sample.
- */
- const textureBicubic = /*@__PURE__*/ Fn( ( [ textureNode, strength ] ) => {
- const lod = strength.mul( maxMipLevel( textureNode ) );
- return textureBicubicLevel( textureNode, lod );
- } );
- //
- // Transmission
- //
- const getVolumeTransmissionRay = /*@__PURE__*/ Fn( ( [ n, v, thickness, ior, modelMatrix ] ) => {
- // Direction of refracted light.
- const refractionVector = vec3( refract( v.negate(), normalize( n ), div( 1.0, ior ) ) );
- // Compute rotation-independent scaling of the model matrix.
- const modelScale = vec3(
- length( modelMatrix[ 0 ].xyz ),
- length( modelMatrix[ 1 ].xyz ),
- length( modelMatrix[ 2 ].xyz )
- );
- // The thickness is specified in local space.
- return normalize( refractionVector ).mul( thickness.mul( modelScale ) );
- } ).setLayout( {
- name: 'getVolumeTransmissionRay',
- type: 'vec3',
- inputs: [
- { name: 'n', type: 'vec3' },
- { name: 'v', type: 'vec3' },
- { name: 'thickness', type: 'float' },
- { name: 'ior', type: 'float' },
- { name: 'modelMatrix', type: 'mat4' }
- ]
- } );
- const applyIorToRoughness = /*@__PURE__*/ Fn( ( [ roughness, ior ] ) => {
- // Scale roughness with IOR so that an IOR of 1.0 results in no microfacet refraction and
- // an IOR of 1.5 results in the default amount of microfacet refraction.
- return roughness.mul( clamp( ior.mul( 2.0 ).sub( 2.0 ), 0.0, 1.0 ) );
- } ).setLayout( {
- name: 'applyIorToRoughness',
- type: 'float',
- inputs: [
- { name: 'roughness', type: 'float' },
- { name: 'ior', type: 'float' }
- ]
- } );
- const viewportBackSideTexture = /*@__PURE__*/ viewportMipTexture();
- const viewportFrontSideTexture = /*@__PURE__*/ viewportMipTexture();
- const getTransmissionSample = /*@__PURE__*/ Fn( ( [ fragCoord, roughness, ior ], { material } ) => {
- const vTexture = material.side === BackSide ? viewportBackSideTexture : viewportFrontSideTexture;
- const transmissionSample = vTexture.sample( fragCoord );
- //const transmissionSample = viewportMipTexture( fragCoord );
- const lod = log2( screenSize.x ).mul( applyIorToRoughness( roughness, ior ) );
- return textureBicubicLevel( transmissionSample, lod );
- } );
- const volumeAttenuation = /*@__PURE__*/ Fn( ( [ transmissionDistance, attenuationColor, attenuationDistance ] ) => {
- If( attenuationDistance.notEqual( 0 ), () => {
- // Compute light attenuation using Beer's law.
- const attenuationCoefficient = log( attenuationColor ).negate().div( attenuationDistance );
- const transmittance = exp( attenuationCoefficient.negate().mul( transmissionDistance ) );
- return transmittance;
- } );
- // Attenuation distance is +∞, i.e. the transmitted color is not attenuated at all.
- return vec3( 1.0 );
- } ).setLayout( {
- name: 'volumeAttenuation',
- type: 'vec3',
- inputs: [
- { name: 'transmissionDistance', type: 'float' },
- { name: 'attenuationColor', type: 'vec3' },
- { name: 'attenuationDistance', type: 'float' }
- ]
- } );
- const getIBLVolumeRefraction = /*@__PURE__*/ Fn( ( [ n, v, roughness, diffuseColor, specularColor, specularF90, position, modelMatrix, viewMatrix, projMatrix, ior, thickness, attenuationColor, attenuationDistance, dispersion ] ) => {
- let transmittedLight, transmittance;
- if ( dispersion ) {
- transmittedLight = vec4().toVar();
- transmittance = vec3().toVar();
- const halfSpread = ior.sub( 1.0 ).mul( dispersion.mul( 0.025 ) );
- const iors = vec3( ior.sub( halfSpread ), ior, ior.add( halfSpread ) );
- Loop( { start: 0, end: 3 }, ( { i } ) => {
- const ior = iors.element( i );
- const transmissionRay = getVolumeTransmissionRay( n, v, thickness, ior, modelMatrix );
- const refractedRayExit = position.add( transmissionRay );
- // Project refracted vector on the framebuffer, while mapping to normalized device coordinates.
- const ndcPos = projMatrix.mul( viewMatrix.mul( vec4( refractedRayExit, 1.0 ) ) );
- const refractionCoords = vec2( ndcPos.xy.div( ndcPos.w ) ).toVar();
- refractionCoords.addAssign( 1.0 );
- refractionCoords.divAssign( 2.0 );
- refractionCoords.assign( vec2( refractionCoords.x, refractionCoords.y.oneMinus() ) ); // webgpu
- // Sample framebuffer to get pixel the refracted ray hits.
- const transmissionSample = getTransmissionSample( refractionCoords, roughness, ior );
- transmittedLight.element( i ).assign( transmissionSample.element( i ) );
- transmittedLight.a.addAssign( transmissionSample.a );
- transmittance.element( i ).assign( diffuseColor.element( i ).mul( volumeAttenuation( length( transmissionRay ), attenuationColor, attenuationDistance ).element( i ) ) );
- } );
- transmittedLight.a.divAssign( 3.0 );
- } else {
- const transmissionRay = getVolumeTransmissionRay( n, v, thickness, ior, modelMatrix );
- const refractedRayExit = position.add( transmissionRay );
- // Project refracted vector on the framebuffer, while mapping to normalized device coordinates.
- const ndcPos = projMatrix.mul( viewMatrix.mul( vec4( refractedRayExit, 1.0 ) ) );
- const refractionCoords = vec2( ndcPos.xy.div( ndcPos.w ) ).toVar();
- refractionCoords.addAssign( 1.0 );
- refractionCoords.divAssign( 2.0 );
- refractionCoords.assign( vec2( refractionCoords.x, refractionCoords.y.oneMinus() ) ); // webgpu
- // Sample framebuffer to get pixel the refracted ray hits.
- transmittedLight = getTransmissionSample( refractionCoords, roughness, ior );
- transmittance = diffuseColor.mul( volumeAttenuation( length( transmissionRay ), attenuationColor, attenuationDistance ) );
- }
- const attenuatedColor = transmittance.rgb.mul( transmittedLight.rgb );
- const dotNV = n.dot( v ).clamp();
- // Get the specular component.
- const F = vec3( EnvironmentBRDF( { // n, v, specularColor, specularF90, roughness
- dotNV,
- specularColor,
- specularF90,
- roughness
- } ) );
- // As less light is transmitted, the opacity should be increased. This simple approximation does a decent job
- // of modulating a CSS background, and has no effect when the buffer is opaque, due to a solid object or clear color.
- const transmittanceFactor = transmittance.r.add( transmittance.g, transmittance.b ).div( 3.0 );
- return vec4( F.oneMinus().mul( attenuatedColor ), transmittedLight.a.oneMinus().mul( transmittanceFactor ).oneMinus() );
- } );
- //
- // Iridescence
- //
- // XYZ to linear-sRGB color space
- const XYZ_TO_REC709 = /*@__PURE__*/ mat3(
- 3.2404542, -0.969266, 0.0556434,
- -1.5371385, 1.8760108, -0.2040259,
- -0.4985314, 0.0415560, 1.0572252
- );
- // Assume air interface for top
- // Note: We don't handle the case fresnel0 == 1
- const Fresnel0ToIor = ( fresnel0 ) => {
- const sqrtF0 = fresnel0.sqrt();
- return vec3( 1.0 ).add( sqrtF0 ).div( vec3( 1.0 ).sub( sqrtF0 ) );
- };
- // ior is a value between 1.0 and 3.0. 1.0 is air interface
- const IorToFresnel0 = ( transmittedIor, incidentIor ) => {
- return transmittedIor.sub( incidentIor ).div( transmittedIor.add( incidentIor ) ).pow2();
- };
- // Fresnel equations for dielectric/dielectric interfaces.
- // Ref: https://belcour.github.io/blog/research/2017/05/01/brdf-thin-film.html
- // Evaluation XYZ sensitivity curves in Fourier space
- const evalSensitivity = ( OPD, shift ) => {
- const phase = OPD.mul( 2.0 * Math.PI * 1.0e-9 );
- const val = vec3( 5.4856e-13, 4.4201e-13, 5.2481e-13 );
- const pos = vec3( 1.6810e+06, 1.7953e+06, 2.2084e+06 );
- const VAR = vec3( 4.3278e+09, 9.3046e+09, 6.6121e+09 );
- const x = float( 9.7470e-14 * Math.sqrt( 2.0 * Math.PI * 4.5282e+09 ) ).mul( phase.mul( 2.2399e+06 ).add( shift.x ).cos() ).mul( phase.pow2().mul( -45282e5 ).exp() );
- let xyz = val.mul( VAR.mul( 2.0 * Math.PI ).sqrt() ).mul( pos.mul( phase ).add( shift ).cos() ).mul( phase.pow2().negate().mul( VAR ).exp() );
- xyz = vec3( xyz.x.add( x ), xyz.y, xyz.z ).div( 1.0685e-7 );
- const rgb = XYZ_TO_REC709.mul( xyz );
- return rgb;
- };
- const evalIridescence = /*@__PURE__*/ Fn( ( { outsideIOR, eta2, cosTheta1, thinFilmThickness, baseF0 } ) => {
- // Force iridescenceIOR -> outsideIOR when thinFilmThickness -> 0.0
- const iridescenceIOR = mix( outsideIOR, eta2, smoothstep( 0.0, 0.03, thinFilmThickness ) );
- // Evaluate the cosTheta on the base layer (Snell law)
- const sinTheta2Sq = outsideIOR.div( iridescenceIOR ).pow2().mul( cosTheta1.pow2().oneMinus() );
- // Handle TIR:
- const cosTheta2Sq = sinTheta2Sq.oneMinus();
- If( cosTheta2Sq.lessThan( 0 ), () => {
- return vec3( 1.0 );
- } );
- const cosTheta2 = cosTheta2Sq.sqrt();
- // First interface
- const R0 = IorToFresnel0( iridescenceIOR, outsideIOR );
- const R12 = F_Schlick( { f0: R0, f90: 1.0, dotVH: cosTheta1 } );
- //const R21 = R12;
- const T121 = R12.oneMinus();
- const phi12 = iridescenceIOR.lessThan( outsideIOR ).select( Math.PI, 0.0 );
- const phi21 = float( Math.PI ).sub( phi12 );
- // Second interface
- const baseIOR = Fresnel0ToIor( baseF0.clamp( 0.0, 0.9999 ) ); // guard against 1.0
- const R1 = IorToFresnel0( baseIOR, iridescenceIOR.toVec3() );
- const R23 = F_Schlick( { f0: R1, f90: 1.0, dotVH: cosTheta2 } );
- const phi23 = vec3(
- baseIOR.x.lessThan( iridescenceIOR ).select( Math.PI, 0.0 ),
- baseIOR.y.lessThan( iridescenceIOR ).select( Math.PI, 0.0 ),
- baseIOR.z.lessThan( iridescenceIOR ).select( Math.PI, 0.0 )
- );
- // Phase shift
- const OPD = iridescenceIOR.mul( thinFilmThickness, cosTheta2, 2.0 );
- const phi = vec3( phi21 ).add( phi23 );
- // Compound terms
- const R123 = R12.mul( R23 ).clamp( 1e-5, 0.9999 );
- const r123 = R123.sqrt();
- const Rs = T121.pow2().mul( R23 ).div( vec3( 1.0 ).sub( R123 ) );
- // Reflectance term for m = 0 (DC term amplitude)
- const C0 = R12.add( Rs );
- const I = C0.toVar();
- // Reflectance term for m > 0 (pairs of diracs)
- const Cm = Rs.sub( T121 ).toVar();
- Loop( { start: 1, end: 2, condition: '<=', name: 'm' }, ( { m } ) => {
- Cm.mulAssign( r123 );
- const Sm = evalSensitivity( float( m ).mul( OPD ), float( m ).mul( phi ) ).mul( 2.0 );
- I.addAssign( Cm.mul( Sm ) );
- } );
- // Since out of gamut colors might be produced, negative color values are clamped to 0.
- return I.max( vec3( 0.0 ) );
- } ).setLayout( {
- name: 'evalIridescence',
- type: 'vec3',
- inputs: [
- { name: 'outsideIOR', type: 'float' },
- { name: 'eta2', type: 'float' },
- { name: 'cosTheta1', type: 'float' },
- { name: 'thinFilmThickness', type: 'float' },
- { name: 'baseF0', type: 'vec3' }
- ]
- } );
- //
- // Sheen
- //
- // This is a curve-fit approximation to the "Charlie sheen" BRDF integrated over the hemisphere from
- // Estevez and Kulla 2017, "Production Friendly Microfacet Sheen BRDF".
- const IBLSheenBRDF = /*@__PURE__*/ Fn( ( { normal, viewDir, roughness } ) => {
- const dotNV = normal.dot( viewDir ).saturate();
- const r2 = roughness.mul( roughness );
- const rInv = roughness.add( 0.1 ).reciprocal();
- const a = float( -1.9362 ).add( roughness.mul( 1.0678 ) ).add( r2.mul( 0.4573 ) ).sub( rInv.mul( 0.8469 ) );
- const b = float( -0.6014 ).add( roughness.mul( 0.5538 ) ).sub( r2.mul( 0.4670 ) ).sub( rInv.mul( 0.1255 ) );
- const DG = a.mul( dotNV ).add( b ).exp();
- return DG.saturate();
- } );
- const clearcoatF0 = vec3( 0.04 );
- const clearcoatF90 = float( 1 );
- /**
- * Represents the lighting model for a PBR material.
- *
- * @augments LightingModel
- */
- class PhysicalLightingModel extends LightingModel {
- /**
- * Constructs a new physical lighting model.
- *
- * @param {boolean} [clearcoat=false] - Whether clearcoat is supported or not.
- * @param {boolean} [sheen=false] - Whether sheen is supported or not.
- * @param {boolean} [iridescence=false] - Whether iridescence is supported or not.
- * @param {boolean} [anisotropy=false] - Whether anisotropy is supported or not.
- * @param {boolean} [transmission=false] - Whether transmission is supported or not.
- * @param {boolean} [dispersion=false] - Whether dispersion is supported or not.
- */
- constructor( clearcoat = false, sheen = false, iridescence = false, anisotropy = false, transmission = false, dispersion = false ) {
- super();
- /**
- * Whether clearcoat is supported or not.
- *
- * @type {boolean}
- * @default false
- */
- this.clearcoat = clearcoat;
- /**
- * Whether sheen is supported or not.
- *
- * @type {boolean}
- * @default false
- */
- this.sheen = sheen;
- /**
- * Whether iridescence is supported or not.
- *
- * @type {boolean}
- * @default false
- */
- this.iridescence = iridescence;
- /**
- * Whether anisotropy is supported or not.
- *
- * @type {boolean}
- * @default false
- */
- this.anisotropy = anisotropy;
- /**
- * Whether transmission is supported or not.
- *
- * @type {boolean}
- * @default false
- */
- this.transmission = transmission;
- /**
- * Whether dispersion is supported or not.
- *
- * @type {boolean}
- * @default false
- */
- this.dispersion = dispersion;
- /**
- * The clear coat radiance.
- *
- * @type {?Node}
- * @default null
- */
- this.clearcoatRadiance = null;
- /**
- * The clear coat specular direct.
- *
- * @type {?Node}
- * @default null
- */
- this.clearcoatSpecularDirect = null;
- /**
- * The clear coat specular indirect.
- *
- * @type {?Node}
- * @default null
- */
- this.clearcoatSpecularIndirect = null;
- /**
- * The sheen specular direct.
- *
- * @type {?Node}
- * @default null
- */
- this.sheenSpecularDirect = null;
- /**
- * The sheen specular indirect.
- *
- * @type {?Node}
- * @default null
- */
- this.sheenSpecularIndirect = null;
- /**
- * The iridescence Fresnel.
- *
- * @type {?Node}
- * @default null
- */
- this.iridescenceFresnel = null;
- /**
- * The iridescence F0.
- *
- * @type {?Node}
- * @default null
- */
- this.iridescenceF0 = null;
- /**
- * The iridescence F0 dielectric.
- *
- * @type {?Node}
- * @default null
- */
- this.iridescenceF0Dielectric = null;
- /**
- * The iridescence F0 metallic.
- *
- * @type {?Node}
- * @default null
- */
- this.iridescenceF0Metallic = null;
- }
- /**
- * Depending on what features are requested, the method prepares certain node variables
- * which are later used for lighting computations.
- *
- * @param {NodeBuilder} builder - The current node builder.
- */
- start( builder ) {
- if ( this.clearcoat === true ) {
- this.clearcoatRadiance = vec3().toVar( 'clearcoatRadiance' );
- this.clearcoatSpecularDirect = vec3().toVar( 'clearcoatSpecularDirect' );
- this.clearcoatSpecularIndirect = vec3().toVar( 'clearcoatSpecularIndirect' );
- }
- if ( this.sheen === true ) {
- this.sheenSpecularDirect = vec3().toVar( 'sheenSpecularDirect' );
- this.sheenSpecularIndirect = vec3().toVar( 'sheenSpecularIndirect' );
- }
- if ( this.iridescence === true ) {
- const dotNVi = normalView.dot( positionViewDirection ).clamp();
- const iridescenceFresnelDielectric = evalIridescence( {
- outsideIOR: float( 1.0 ),
- eta2: iridescenceIOR,
- cosTheta1: dotNVi,
- thinFilmThickness: iridescenceThickness,
- baseF0: specularColor
- } );
- const iridescenceFresnelMetallic = evalIridescence( {
- outsideIOR: float( 1.0 ),
- eta2: iridescenceIOR,
- cosTheta1: dotNVi,
- thinFilmThickness: iridescenceThickness,
- baseF0: diffuseColor.rgb
- } );
- this.iridescenceFresnel = mix( iridescenceFresnelDielectric, iridescenceFresnelMetallic, metalness );
- this.iridescenceF0Dielectric = Schlick_to_F0( { f: iridescenceFresnelDielectric, f90: 1.0, dotVH: dotNVi } );
- this.iridescenceF0Metallic = Schlick_to_F0( { f: iridescenceFresnelMetallic, f90: 1.0, dotVH: dotNVi } );
- this.iridescenceF0 = mix( this.iridescenceF0Dielectric, this.iridescenceF0Metallic, metalness );
- }
- if ( this.transmission === true ) {
- const position = positionWorld;
- const v = cameraPosition.sub( positionWorld ).normalize(); // TODO: Create Node for this, same issue in MaterialX
- const n = normalWorld;
- const context = builder.context;
- context.backdrop = getIBLVolumeRefraction(
- n,
- v,
- roughness,
- diffuseContribution,
- specularColorBlended,
- specularF90, // specularF90
- position, // positionWorld
- modelWorldMatrix, // modelMatrix
- cameraViewMatrix, // viewMatrix
- cameraProjectionMatrix, // projMatrix
- ior,
- thickness,
- attenuationColor,
- attenuationDistance,
- this.dispersion ? dispersion : null
- );
- context.backdropAlpha = transmission;
- diffuseColor.a.mulAssign( mix( 1, context.backdrop.a, transmission ) );
- }
- super.start( builder );
- }
- // Fdez-Agüera's "Multiple-Scattering Microfacet Model for Real-Time Image Based Lighting"
- // Approximates multi-scattering in order to preserve energy.
- // http://www.jcgt.org/published/0008/01/03/
- computeMultiscattering( singleScatter, multiScatter, specularF90, f0, iridescenceF0 = null ) {
- const dotNV = normalView.dot( positionViewDirection ).clamp(); // @ TODO: Move to core dotNV
- const fab = DFGLUT( { roughness, dotNV } );
- const Fr = iridescenceF0 ? iridescence.mix( f0, iridescenceF0 ) : f0;
- const FssEss = Fr.mul( fab.x ).add( specularF90.mul( fab.y ) );
- const Ess = fab.x.add( fab.y );
- const Ems = Ess.oneMinus();
- const Favg = Fr.add( Fr.oneMinus().mul( 0.047619 ) ); // 1/21
- const Fms = FssEss.mul( Favg ).div( Ems.mul( Favg ).oneMinus() );
- singleScatter.addAssign( FssEss );
- multiScatter.addAssign( Fms.mul( Ems ) );
- }
- /**
- * Implements the direct light.
- *
- * @param {Object} lightData - The light data.
- * @param {NodeBuilder} builder - The current node builder.
- */
- direct( { lightDirection, lightColor, reflectedLight }, /* builder */ ) {
- const dotNL = normalView.dot( lightDirection ).clamp();
- const irradiance = dotNL.mul( lightColor ).toVar();
- if ( this.sheen === true ) {
- this.sheenSpecularDirect.addAssign( irradiance.mul( BRDF_Sheen( { lightDirection } ) ) );
- const sheenAlbedoV = IBLSheenBRDF( { normal: normalView, viewDir: positionViewDirection, roughness: sheenRoughness } );
- const sheenAlbedoL = IBLSheenBRDF( { normal: normalView, viewDir: lightDirection, roughness: sheenRoughness } );
- const sheenEnergyComp = sheen.r.max( sheen.g ).max( sheen.b ).mul( sheenAlbedoV.max( sheenAlbedoL ) ).oneMinus();
- irradiance.mulAssign( sheenEnergyComp );
- }
- if ( this.clearcoat === true ) {
- const dotNLcc = clearcoatNormalView.dot( lightDirection ).clamp();
- const ccIrradiance = dotNLcc.mul( lightColor );
- this.clearcoatSpecularDirect.addAssign( ccIrradiance.mul( BRDF_GGX( { lightDirection, f0: clearcoatF0, f90: clearcoatF90, roughness: clearcoatRoughness, normalView: clearcoatNormalView } ) ) );
- }
- reflectedLight.directDiffuse.addAssign( irradiance.mul( BRDF_Lambert( { diffuseColor: diffuseContribution } ) ) );
- reflectedLight.directSpecular.addAssign( irradiance.mul( BRDF_GGX_Multiscatter( { lightDirection, f0: specularColorBlended, f90: 1, roughness, f: this.iridescenceFresnel, USE_IRIDESCENCE: this.iridescence, USE_ANISOTROPY: this.anisotropy } ) ) );
- }
- /**
- * This method is intended for implementing the direct light term for
- * rect area light nodes.
- *
- * @param {Object} input - The input data.
- * @param {NodeBuilder} builder - The current node builder.
- */
- directRectArea( { lightColor, lightPosition, halfWidth, halfHeight, reflectedLight, ltc_1, ltc_2 }, /* builder */ ) {
- const p0 = lightPosition.add( halfWidth ).sub( halfHeight ); // counterclockwise; light shines in local neg z direction
- const p1 = lightPosition.sub( halfWidth ).sub( halfHeight );
- const p2 = lightPosition.sub( halfWidth ).add( halfHeight );
- const p3 = lightPosition.add( halfWidth ).add( halfHeight );
- const N = normalView;
- const V = positionViewDirection;
- const P = positionView.toVar();
- const uv = LTC_Uv( { N, V, roughness } );
- const t1 = ltc_1.sample( uv ).toVar();
- const t2 = ltc_2.sample( uv ).toVar();
- const mInv = mat3(
- vec3( t1.x, 0, t1.y ),
- vec3( 0, 1, 0 ),
- vec3( t1.z, 0, t1.w )
- ).toVar();
- // LTC Fresnel Approximation by Stephen Hill
- // http://blog.selfshadow.com/publications/s2016-advances/s2016_ltc_fresnel.pdf
- const fresnel = specularColorBlended.mul( t2.x ).add( specularColorBlended.oneMinus().mul( t2.y ) ).toVar();
- reflectedLight.directSpecular.addAssign( lightColor.mul( fresnel ).mul( LTC_Evaluate( { N, V, P, mInv, p0, p1, p2, p3 } ) ) );
- reflectedLight.directDiffuse.addAssign( lightColor.mul( diffuseContribution ).mul( LTC_Evaluate( { N, V, P, mInv: mat3( 1, 0, 0, 0, 1, 0, 0, 0, 1 ), p0, p1, p2, p3 } ) ) );
- }
- /**
- * Implements the indirect lighting.
- *
- * @param {NodeBuilder} builder - The current node builder.
- */
- indirect( builder ) {
- this.indirectDiffuse( builder );
- this.indirectSpecular( builder );
- this.ambientOcclusion( builder );
- }
- /**
- * Implements the indirect diffuse term.
- *
- * @param {NodeBuilder} builder - The current node builder.
- */
- indirectDiffuse( builder ) {
- const { irradiance, reflectedLight } = builder.context;
- const diffuse = irradiance.mul( BRDF_Lambert( { diffuseColor: diffuseContribution } ) ).toVar();
- if ( this.sheen === true ) {
- const sheenAlbedo = IBLSheenBRDF( { normal: normalView, viewDir: positionViewDirection, roughness: sheenRoughness } );
- const sheenEnergyComp = sheen.r.max( sheen.g ).max( sheen.b ).mul( sheenAlbedo ).oneMinus();
- diffuse.mulAssign( sheenEnergyComp );
- }
- reflectedLight.indirectDiffuse.addAssign( diffuse );
- }
- /**
- * Implements the indirect specular term.
- *
- * @param {NodeBuilder} builder - The current node builder.
- */
- indirectSpecular( builder ) {
- const { radiance, iblIrradiance, reflectedLight } = builder.context;
- if ( this.sheen === true ) {
- this.sheenSpecularIndirect.addAssign( iblIrradiance.mul(
- sheen,
- IBLSheenBRDF( {
- normal: normalView,
- viewDir: positionViewDirection,
- roughness: sheenRoughness
- } )
- ) );
- }
- if ( this.clearcoat === true ) {
- const dotNVcc = clearcoatNormalView.dot( positionViewDirection ).clamp();
- const clearcoatEnv = EnvironmentBRDF( {
- dotNV: dotNVcc,
- specularColor: clearcoatF0,
- specularF90: clearcoatF90,
- roughness: clearcoatRoughness
- } );
- this.clearcoatSpecularIndirect.addAssign( this.clearcoatRadiance.mul( clearcoatEnv ) );
- }
- // Both indirect specular and indirect diffuse light accumulate here
- // Compute multiscattering separately for dielectric and metallic, then mix
- const singleScatteringDielectric = vec3().toVar( 'singleScatteringDielectric' );
- const multiScatteringDielectric = vec3().toVar( 'multiScatteringDielectric' );
- const singleScatteringMetallic = vec3().toVar( 'singleScatteringMetallic' );
- const multiScatteringMetallic = vec3().toVar( 'multiScatteringMetallic' );
- this.computeMultiscattering( singleScatteringDielectric, multiScatteringDielectric, specularF90, specularColor, this.iridescenceF0Dielectric );
- this.computeMultiscattering( singleScatteringMetallic, multiScatteringMetallic, specularF90, diffuseColor.rgb, this.iridescenceF0Metallic );
- // Mix based on metalness
- const singleScattering = mix( singleScatteringDielectric, singleScatteringMetallic, metalness );
- const multiScattering = mix( multiScatteringDielectric, multiScatteringMetallic, metalness );
- // Diffuse energy conservation uses dielectric path
- const totalScatteringDielectric = singleScatteringDielectric.add( multiScatteringDielectric );
- const diffuse = diffuseContribution.mul( totalScatteringDielectric.oneMinus() );
- const cosineWeightedIrradiance = iblIrradiance.mul( 1 / Math.PI );
- const indirectSpecular = radiance.mul( singleScattering ).add( multiScattering.mul( cosineWeightedIrradiance ) ).toVar();
- const indirectDiffuse = diffuse.mul( cosineWeightedIrradiance ).toVar();
- if ( this.sheen === true ) {
- const sheenAlbedo = IBLSheenBRDF( { normal: normalView, viewDir: positionViewDirection, roughness: sheenRoughness } );
- const sheenEnergyComp = sheen.r.max( sheen.g ).max( sheen.b ).mul( sheenAlbedo ).oneMinus();
- indirectSpecular.mulAssign( sheenEnergyComp );
- indirectDiffuse.mulAssign( sheenEnergyComp );
- }
- reflectedLight.indirectSpecular.addAssign( indirectSpecular );
- reflectedLight.indirectDiffuse.addAssign( indirectDiffuse );
- }
- /**
- * Implements the ambient occlusion term.
- *
- * @param {NodeBuilder} builder - The current node builder.
- */
- ambientOcclusion( builder ) {
- const { ambientOcclusion, reflectedLight } = builder.context;
- const dotNV = normalView.dot( positionViewDirection ).clamp(); // @ TODO: Move to core dotNV
- const aoNV = dotNV.add( ambientOcclusion );
- const aoExp = roughness.mul( -16 ).oneMinus().negate().exp2();
- const aoNode = ambientOcclusion.sub( aoNV.pow( aoExp ).oneMinus() ).clamp();
- if ( this.clearcoat === true ) {
- this.clearcoatSpecularIndirect.mulAssign( ambientOcclusion );
- }
- if ( this.sheen === true ) {
- this.sheenSpecularIndirect.mulAssign( ambientOcclusion );
- }
- reflectedLight.indirectDiffuse.mulAssign( ambientOcclusion );
- reflectedLight.indirectSpecular.mulAssign( aoNode );
- }
- /**
- * Used for final lighting accumulations depending on the requested features.
- *
- * @param {NodeBuilder} builder - The current node builder.
- */
- finish( { context } ) {
- const { outgoingLight } = context;
- if ( this.clearcoat === true ) {
- const dotNVcc = clearcoatNormalView.dot( positionViewDirection ).clamp();
- const Fcc = F_Schlick( {
- dotVH: dotNVcc,
- f0: clearcoatF0,
- f90: clearcoatF90
- } );
- const clearcoatLight = outgoingLight.mul( clearcoat.mul( Fcc ).oneMinus() ).add( this.clearcoatSpecularDirect.add( this.clearcoatSpecularIndirect ).mul( clearcoat ) );
- outgoingLight.assign( clearcoatLight );
- }
- if ( this.sheen === true ) {
- const sheenLight = outgoingLight.add( this.sheenSpecularDirect, this.sheenSpecularIndirect.mul( 1.0 / Math.PI ) );
- outgoingLight.assign( sheenLight );
- }
- }
- }
- // These defines must match with PMREMGenerator
- const cubeUV_r0 = /*@__PURE__*/ float( 1.0 );
- const cubeUV_m0 = /*@__PURE__*/ float( -2 );
- const cubeUV_r1 = /*@__PURE__*/ float( 0.8 );
- const cubeUV_m1 = /*@__PURE__*/ float( -1 );
- const cubeUV_r4 = /*@__PURE__*/ float( 0.4 );
- const cubeUV_m4 = /*@__PURE__*/ float( 2.0 );
- const cubeUV_r5 = /*@__PURE__*/ float( 0.305 );
- const cubeUV_m5 = /*@__PURE__*/ float( 3.0 );
- const cubeUV_r6 = /*@__PURE__*/ float( 0.21 );
- const cubeUV_m6 = /*@__PURE__*/ float( 4.0 );
- const cubeUV_minMipLevel = /*@__PURE__*/ float( 4.0 );
- const cubeUV_minTileSize = /*@__PURE__*/ float( 16.0 );
- // These shader functions convert between the UV coordinates of a single face of
- // a cubemap, the 0-5 integer index of a cube face, and the direction vector for
- // sampling a textureCube (not generally normalized ).
- const getFace = /*@__PURE__*/ Fn( ( [ direction ] ) => {
- const absDirection = vec3( abs( direction ) ).toVar();
- const face = float( -1 ).toVar();
- If( absDirection.x.greaterThan( absDirection.z ), () => {
- If( absDirection.x.greaterThan( absDirection.y ), () => {
- face.assign( select( direction.x.greaterThan( 0.0 ), 0.0, 3.0 ) );
- } ).Else( () => {
- face.assign( select( direction.y.greaterThan( 0.0 ), 1.0, 4.0 ) );
- } );
- } ).Else( () => {
- If( absDirection.z.greaterThan( absDirection.y ), () => {
- face.assign( select( direction.z.greaterThan( 0.0 ), 2.0, 5.0 ) );
- } ).Else( () => {
- face.assign( select( direction.y.greaterThan( 0.0 ), 1.0, 4.0 ) );
- } );
- } );
- return face;
- } ).setLayout( {
- name: 'getFace',
- type: 'float',
- inputs: [
- { name: 'direction', type: 'vec3' }
- ]
- } );
- // RH coordinate system; PMREM face-indexing convention
- const getUV = /*@__PURE__*/ Fn( ( [ direction, face ] ) => {
- const uv = vec2().toVar();
- If( face.equal( 0.0 ), () => {
- uv.assign( vec2( direction.z, direction.y ).div( abs( direction.x ) ) ); // pos x
- } ).ElseIf( face.equal( 1.0 ), () => {
- uv.assign( vec2( direction.x.negate(), direction.z.negate() ).div( abs( direction.y ) ) ); // pos y
- } ).ElseIf( face.equal( 2.0 ), () => {
- uv.assign( vec2( direction.x.negate(), direction.y ).div( abs( direction.z ) ) ); // pos z
- } ).ElseIf( face.equal( 3.0 ), () => {
- uv.assign( vec2( direction.z.negate(), direction.y ).div( abs( direction.x ) ) ); // neg x
- } ).ElseIf( face.equal( 4.0 ), () => {
- uv.assign( vec2( direction.x.negate(), direction.z ).div( abs( direction.y ) ) ); // neg y
- } ).Else( () => {
- uv.assign( vec2( direction.x, direction.y ).div( abs( direction.z ) ) ); // neg z
- } );
- return mul( 0.5, uv.add( 1.0 ) );
- } ).setLayout( {
- name: 'getUV',
- type: 'vec2',
- inputs: [
- { name: 'direction', type: 'vec3' },
- { name: 'face', type: 'float' }
- ]
- } );
- const roughnessToMip = /*@__PURE__*/ Fn( ( [ roughness ] ) => {
- const mip = float( 0.0 ).toVar();
- If( roughness.greaterThanEqual( cubeUV_r1 ), () => {
- mip.assign( cubeUV_r0.sub( roughness ).mul( cubeUV_m1.sub( cubeUV_m0 ) ).div( cubeUV_r0.sub( cubeUV_r1 ) ).add( cubeUV_m0 ) );
- } ).ElseIf( roughness.greaterThanEqual( cubeUV_r4 ), () => {
- mip.assign( cubeUV_r1.sub( roughness ).mul( cubeUV_m4.sub( cubeUV_m1 ) ).div( cubeUV_r1.sub( cubeUV_r4 ) ).add( cubeUV_m1 ) );
- } ).ElseIf( roughness.greaterThanEqual( cubeUV_r5 ), () => {
- mip.assign( cubeUV_r4.sub( roughness ).mul( cubeUV_m5.sub( cubeUV_m4 ) ).div( cubeUV_r4.sub( cubeUV_r5 ) ).add( cubeUV_m4 ) );
- } ).ElseIf( roughness.greaterThanEqual( cubeUV_r6 ), () => {
- mip.assign( cubeUV_r5.sub( roughness ).mul( cubeUV_m6.sub( cubeUV_m5 ) ).div( cubeUV_r5.sub( cubeUV_r6 ) ).add( cubeUV_m5 ) );
- } ).Else( () => {
- mip.assign( float( -2 ).mul( log2( mul( 1.16, roughness ) ) ) ); // 1.16 = 1.79^0.25
- } );
- return mip;
- } ).setLayout( {
- name: 'roughnessToMip',
- type: 'float',
- inputs: [
- { name: 'roughness', type: 'float' }
- ]
- } );
- // RH coordinate system; PMREM face-indexing convention
- const getDirection = /*@__PURE__*/ Fn( ( [ uv_immutable, face ] ) => {
- const uv = uv_immutable.toVar();
- uv.assign( mul( 2.0, uv ).sub( 1.0 ) );
- const direction = vec3( uv, 1.0 ).toVar();
- If( face.equal( 0.0 ), () => {
- direction.assign( direction.zyx ); // ( 1, v, u ) pos x
- } ).ElseIf( face.equal( 1.0 ), () => {
- direction.assign( direction.xzy );
- direction.xz.mulAssign( -1 ); // ( -u, 1, -v ) pos y
- } ).ElseIf( face.equal( 2.0 ), () => {
- direction.x.mulAssign( -1 ); // ( -u, v, 1 ) pos z
- } ).ElseIf( face.equal( 3.0 ), () => {
- direction.assign( direction.zyx );
- direction.xz.mulAssign( -1 ); // ( -1, v, -u ) neg x
- } ).ElseIf( face.equal( 4.0 ), () => {
- direction.assign( direction.xzy );
- direction.xy.mulAssign( -1 ); // ( -u, -1, v ) neg y
- } ).ElseIf( face.equal( 5.0 ), () => {
- direction.z.mulAssign( -1 ); // ( u, v, -1 ) neg zS
- } );
- return direction;
- } ).setLayout( {
- name: 'getDirection',
- type: 'vec3',
- inputs: [
- { name: 'uv', type: 'vec2' },
- { name: 'face', type: 'float' }
- ]
- } );
- //
- const textureCubeUV = /*@__PURE__*/ Fn( ( [ envMap, sampleDir_immutable, roughness_immutable, CUBEUV_TEXEL_WIDTH, CUBEUV_TEXEL_HEIGHT, CUBEUV_MAX_MIP ] ) => {
- const roughness = float( roughness_immutable );
- const sampleDir = vec3( sampleDir_immutable );
- const mip = clamp( roughnessToMip( roughness ), cubeUV_m0, CUBEUV_MAX_MIP );
- const mipF = fract( mip );
- const mipInt = floor( mip );
- const color0 = vec3( bilinearCubeUV( envMap, sampleDir, mipInt, CUBEUV_TEXEL_WIDTH, CUBEUV_TEXEL_HEIGHT, CUBEUV_MAX_MIP ) ).toVar();
- If( mipF.notEqual( 0.0 ), () => {
- const color1 = vec3( bilinearCubeUV( envMap, sampleDir, mipInt.add( 1.0 ), CUBEUV_TEXEL_WIDTH, CUBEUV_TEXEL_HEIGHT, CUBEUV_MAX_MIP ) ).toVar();
- color0.assign( mix( color0, color1, mipF ) );
- } );
- return color0;
- } );
- const bilinearCubeUV = /*@__PURE__*/ Fn( ( [ envMap, direction_immutable, mipInt_immutable, CUBEUV_TEXEL_WIDTH, CUBEUV_TEXEL_HEIGHT, CUBEUV_MAX_MIP ] ) => {
- const mipInt = float( mipInt_immutable ).toVar();
- const direction = vec3( direction_immutable );
- const face = float( getFace( direction ) ).toVar();
- const filterInt = float( max$1( cubeUV_minMipLevel.sub( mipInt ), 0.0 ) ).toVar();
- mipInt.assign( max$1( mipInt, cubeUV_minMipLevel ) );
- const faceSize = float( exp2( mipInt ) ).toVar();
- const uv = vec2( getUV( direction, face ).mul( faceSize.sub( 2.0 ) ).add( 1.0 ) ).toVar();
- If( face.greaterThan( 2.0 ), () => {
- uv.y.addAssign( faceSize );
- face.subAssign( 3.0 );
- } );
- uv.x.addAssign( face.mul( faceSize ) );
- uv.x.addAssign( filterInt.mul( mul( 3.0, cubeUV_minTileSize ) ) );
- uv.y.addAssign( mul( 4.0, exp2( CUBEUV_MAX_MIP ).sub( faceSize ) ) );
- uv.x.mulAssign( CUBEUV_TEXEL_WIDTH );
- uv.y.mulAssign( CUBEUV_TEXEL_HEIGHT );
- return envMap.sample( uv ).grad( vec2(), vec2() ); // disable anisotropic filtering
- } );
- const getSample = /*@__PURE__*/ Fn( ( { envMap, mipInt, outputDirection, theta, axis, CUBEUV_TEXEL_WIDTH, CUBEUV_TEXEL_HEIGHT, CUBEUV_MAX_MIP } ) => {
- const cosTheta = cos( theta );
- // Rodrigues' axis-angle rotation
- const sampleDirection = outputDirection.mul( cosTheta )
- .add( axis.cross( outputDirection ).mul( sin( theta ) ) )
- .add( axis.mul( axis.dot( outputDirection ).mul( cosTheta.oneMinus() ) ) );
- return bilinearCubeUV( envMap, sampleDirection, mipInt, CUBEUV_TEXEL_WIDTH, CUBEUV_TEXEL_HEIGHT, CUBEUV_MAX_MIP );
- } );
- const blur = /*@__PURE__*/ Fn( ( { n, latitudinal, poleAxis, outputDirection, weights, samples, dTheta, mipInt, envMap, CUBEUV_TEXEL_WIDTH, CUBEUV_TEXEL_HEIGHT, CUBEUV_MAX_MIP } ) => {
- const axis = vec3( select( latitudinal, poleAxis, cross( poleAxis, outputDirection ) ) ).toVar();
- If( axis.equal( vec3( 0.0 ) ), () => {
- axis.assign( vec3( outputDirection.z, 0.0, outputDirection.x.negate() ) );
- } );
- axis.assign( normalize( axis ) );
- const gl_FragColor = vec3().toVar();
- gl_FragColor.addAssign( weights.element( 0 ).mul( getSample( { theta: 0.0, axis, outputDirection, mipInt, envMap, CUBEUV_TEXEL_WIDTH, CUBEUV_TEXEL_HEIGHT, CUBEUV_MAX_MIP } ) ) );
- Loop( { start: int( 1 ), end: n }, ( { i } ) => {
- If( i.greaterThanEqual( samples ), () => {
- Break();
- } );
- const theta = float( dTheta.mul( float( i ) ) ).toVar();
- gl_FragColor.addAssign( weights.element( i ).mul( getSample( { theta: theta.mul( -1 ), axis, outputDirection, mipInt, envMap, CUBEUV_TEXEL_WIDTH, CUBEUV_TEXEL_HEIGHT, CUBEUV_MAX_MIP } ) ) );
- gl_FragColor.addAssign( weights.element( i ).mul( getSample( { theta, axis, outputDirection, mipInt, envMap, CUBEUV_TEXEL_WIDTH, CUBEUV_TEXEL_HEIGHT, CUBEUV_MAX_MIP } ) ) );
- } );
- return vec4( gl_FragColor, 1 );
- } );
- // GGX VNDF importance sampling functions
- // Van der Corput radical inverse for generating quasi-random sequences
- const radicalInverse_VdC = /*@__PURE__*/ Fn( ( [ bits_immutable ] ) => {
- const bits = uint( bits_immutable ).toVar();
- bits.assign( bits.shiftLeft( uint( 16 ) ).bitOr( bits.shiftRight( uint( 16 ) ) ) );
- bits.assign( bits.bitAnd( uint( 0x55555555 ) ).shiftLeft( uint( 1 ) ).bitOr( bits.bitAnd( uint( 0xAAAAAAAA ) ).shiftRight( uint( 1 ) ) ) );
- bits.assign( bits.bitAnd( uint( 0x33333333 ) ).shiftLeft( uint( 2 ) ).bitOr( bits.bitAnd( uint( 0xCCCCCCCC ) ).shiftRight( uint( 2 ) ) ) );
- bits.assign( bits.bitAnd( uint( 0x0F0F0F0F ) ).shiftLeft( uint( 4 ) ).bitOr( bits.bitAnd( uint( 0xF0F0F0F0 ) ).shiftRight( uint( 4 ) ) ) );
- bits.assign( bits.bitAnd( uint( 0x00FF00FF ) ).shiftLeft( uint( 8 ) ).bitOr( bits.bitAnd( uint( 0xFF00FF00 ) ).shiftRight( uint( 8 ) ) ) );
- return float( bits ).mul( 2.3283064365386963e-10 ); // / 0x100000000
- } );
- // Hammersley sequence for quasi-Monte Carlo sampling
- const hammersley = /*@__PURE__*/ Fn( ( [ i, N ] ) => {
- return vec2( float( i ).div( float( N ) ), radicalInverse_VdC( i ) );
- } );
- // GGX VNDF importance sampling (Eric Heitz 2018)
- // "Sampling the GGX Distribution of Visible Normals"
- // https://jcgt.org/published/0007/04/01/
- const importanceSampleGGX_VNDF = /*@__PURE__*/ Fn( ( [ Xi, V_immutable, roughness_immutable ] ) => {
- const V = vec3( V_immutable ).toVar();
- const roughness = float( roughness_immutable );
- const alpha = roughness.mul( roughness ).toVar();
- // Section 3.2: Transform view direction to hemisphere configuration
- const Vh = normalize( vec3( alpha.mul( V.x ), alpha.mul( V.y ), V.z ) ).toVar();
- // Section 4.1: Orthonormal basis
- const lensq = Vh.x.mul( Vh.x ).add( Vh.y.mul( Vh.y ) );
- const T1 = select( lensq.greaterThan( 0.0 ), vec3( Vh.y.negate(), Vh.x, 0.0 ).div( sqrt( lensq ) ), vec3( 1.0, 0.0, 0.0 ) ).toVar();
- const T2 = cross( Vh, T1 ).toVar();
- // Section 4.2: Parameterization of projected area
- const r = sqrt( Xi.x );
- const phi = mul( 2.0, 3.14159265359 ).mul( Xi.y );
- const t1 = r.mul( cos( phi ) ).toVar();
- const t2 = r.mul( sin( phi ) ).toVar();
- const s = mul( 0.5, Vh.z.add( 1.0 ) );
- t2.assign( s.oneMinus().mul( sqrt( t1.mul( t1 ).oneMinus() ) ).add( s.mul( t2 ) ) );
- // Section 4.3: Reprojection onto hemisphere
- const Nh = T1.mul( t1 ).add( T2.mul( t2 ) ).add( Vh.mul( sqrt( max$1( 0.0, t1.mul( t1 ).add( t2.mul( t2 ) ).oneMinus() ) ) ) );
- // Section 3.4: Transform back to ellipsoid configuration
- return normalize( vec3( alpha.mul( Nh.x ), alpha.mul( Nh.y ), max$1( 0.0, Nh.z ) ) );
- } );
- // GGX convolution using VNDF importance sampling
- const ggxConvolution = /*@__PURE__*/ Fn( ( { roughness, mipInt, envMap, N_immutable, GGX_SAMPLES, CUBEUV_TEXEL_WIDTH, CUBEUV_TEXEL_HEIGHT, CUBEUV_MAX_MIP } ) => {
- const N = vec3( N_immutable ).toVar();
- const prefilteredColor = vec3( 0.0 ).toVar();
- const totalWeight = float( 0.0 ).toVar();
- // For very low roughness, just sample the environment directly
- If( roughness.lessThan( 0.001 ), () => {
- prefilteredColor.assign( bilinearCubeUV( envMap, N, mipInt, CUBEUV_TEXEL_WIDTH, CUBEUV_TEXEL_HEIGHT, CUBEUV_MAX_MIP ) );
- } ).Else( () => {
- // Tangent space basis for VNDF sampling
- const up = select( abs( N.z ).lessThan( 0.999 ), vec3( 0.0, 0.0, 1.0 ), vec3( 1.0, 0.0, 0.0 ) );
- const tangent = normalize( cross( up, N ) ).toVar();
- const bitangent = cross( N, tangent ).toVar();
- Loop( { start: uint( 0 ), end: GGX_SAMPLES }, ( { i } ) => {
- const Xi = hammersley( i, GGX_SAMPLES );
- // For PMREM, V = N, so in tangent space V is always (0, 0, 1)
- const H_tangent = importanceSampleGGX_VNDF( Xi, vec3( 0.0, 0.0, 1.0 ), roughness );
- // Transform H back to world space
- const H = normalize( tangent.mul( H_tangent.x ).add( bitangent.mul( H_tangent.y ) ).add( N.mul( H_tangent.z ) ) );
- const L = normalize( H.mul( dot( N, H ).mul( 2.0 ) ).sub( N ) );
- const NdotL = max$1( dot( N, L ), 0.0 );
- If( NdotL.greaterThan( 0.0 ), () => {
- // Sample environment at fixed mip level
- // VNDF importance sampling handles the distribution filtering
- const sampleColor = bilinearCubeUV( envMap, L, mipInt, CUBEUV_TEXEL_WIDTH, CUBEUV_TEXEL_HEIGHT, CUBEUV_MAX_MIP );
- // Weight by NdotL for the split-sum approximation
- // VNDF PDF naturally accounts for the visible microfacet distribution
- prefilteredColor.addAssign( sampleColor.mul( NdotL ) );
- totalWeight.addAssign( NdotL );
- } );
- } );
- If( totalWeight.greaterThan( 0.0 ), () => {
- prefilteredColor.assign( prefilteredColor.div( totalWeight ) );
- } );
- } );
- return vec4( prefilteredColor, 1.0 );
- } );
- const LOD_MIN = 4;
- // The standard deviations (radians) associated with the extra mips.
- // Used for scene blur in fromScene() method.
- const EXTRA_LOD_SIGMA = [ 0.125, 0.215, 0.35, 0.446, 0.526, 0.582 ];
- // The maximum length of the blur for loop. Smaller sigmas will use fewer
- // samples and exit early, but not recompile the shader.
- // Used for scene blur in fromScene() method.
- const MAX_SAMPLES = 20;
- // GGX VNDF importance sampling configuration
- const GGX_SAMPLES = 512;
- const _flatCamera = /*@__PURE__*/ new OrthographicCamera( -1, 1, 1, -1, 0, 1 );
- const _cubeCamera = /*@__PURE__*/ new PerspectiveCamera( 90, 1 );
- const _clearColor$2 = /*@__PURE__*/ new Color();
- let _oldTarget = null;
- let _oldActiveCubeFace = 0;
- let _oldActiveMipmapLevel = 0;
- const _origin = /*@__PURE__*/ new Vector3();
- // maps blur materials to their uniforms dictionary
- const _uniformsMap = new WeakMap();
- // WebGPU Face indices
- const _faceLib = [
- 3, 1, 5,
- 0, 4, 2
- ];
- const _direction = /*@__PURE__*/ getDirection( uv$1(), attribute( 'faceIndex' ) ).normalize();
- const _outputDirection = /*@__PURE__*/ vec3( _direction.x, _direction.y, _direction.z );
- /**
- * This class generates a Prefiltered, Mipmapped Radiance Environment Map
- * (PMREM) from a cubeMap environment texture. This allows different levels of
- * blur to be quickly accessed based on material roughness. It is packed into a
- * special CubeUV format that allows us to perform custom interpolation so that
- * we can support nonlinear formats such as RGBE. Unlike a traditional mipmap
- * chain, it only goes down to the LOD_MIN level (above), and then creates extra
- * even more filtered 'mips' at the same LOD_MIN resolution, associated with
- * higher roughness levels. In this way we maintain resolution to smoothly
- * interpolate diffuse lighting while limiting sampling computation.
- *
- * The prefiltering uses GGX VNDF (Visible Normal Distribution Function)
- * importance sampling based on "Sampling the GGX Distribution of Visible Normals"
- * (Heitz, 2018) to generate environment maps that accurately match the GGX BRDF
- * used in material rendering for physically-based image-based lighting.
- */
- class PMREMGenerator {
- /**
- * Constructs a new PMREM generator.
- *
- * @param {Renderer} renderer - The renderer.
- */
- constructor( renderer ) {
- this._renderer = renderer;
- this._pingPongRenderTarget = null;
- this._lodMax = 0;
- this._cubeSize = 0;
- this._sizeLods = [];
- this._sigmas = [];
- this._lodMeshes = [];
- this._blurMaterial = null;
- this._ggxMaterial = null;
- this._cubemapMaterial = null;
- this._equirectMaterial = null;
- this._backgroundBox = null;
- }
- get _hasInitialized() {
- return this._renderer.hasInitialized();
- }
- /**
- * Generates a PMREM from a supplied Scene, which can be faster than using an
- * image if networking bandwidth is low. Optional sigma specifies a blur radius
- * in radians to be applied to the scene before PMREM generation. Optional near
- * and far planes ensure the scene is rendered in its entirety.
- *
- * @param {Scene} scene - The scene to be captured.
- * @param {number} [sigma=0] - The blur radius in radians.
- * @param {number} [near=0.1] - The near plane distance.
- * @param {number} [far=100] - The far plane distance.
- * @param {Object} [options={}] - The configuration options.
- * @param {number} [options.size=256] - The texture size of the PMREM.
- * @param {Vector3} [options.renderTarget=origin] - The position of the internal cube camera that renders the scene.
- * @param {?RenderTarget} [options.renderTarget=null] - The render target to use.
- * @return {RenderTarget} The resulting PMREM.
- * @see {@link PMREMGenerator#fromScene}
- */
- fromScene( scene, sigma = 0, near = 0.1, far = 100, options = {} ) {
- const {
- size = 256,
- position = _origin,
- renderTarget = null,
- } = options;
- this._setSize( size );
- if ( this._hasInitialized === false ) {
- warn( 'PMREMGenerator: ".fromScene()" called before the backend is initialized. Try using "await renderer.init()" instead.' );
- const cubeUVRenderTarget = renderTarget || this._allocateTarget();
- options.renderTarget = cubeUVRenderTarget;
- this.fromSceneAsync( scene, sigma, near, far, options );
- return cubeUVRenderTarget;
- }
- _oldTarget = this._renderer.getRenderTarget();
- _oldActiveCubeFace = this._renderer.getActiveCubeFace();
- _oldActiveMipmapLevel = this._renderer.getActiveMipmapLevel();
- const cubeUVRenderTarget = renderTarget || this._allocateTarget();
- cubeUVRenderTarget.depthBuffer = true;
- this._init( cubeUVRenderTarget );
- this._sceneToCubeUV( scene, near, far, cubeUVRenderTarget, position );
- if ( sigma > 0 ) {
- this._blur( cubeUVRenderTarget, 0, 0, sigma );
- }
- this._applyPMREM( cubeUVRenderTarget );
- this._cleanup( cubeUVRenderTarget );
- return cubeUVRenderTarget;
- }
- /**
- * Generates a PMREM from a supplied Scene, which can be faster than using an
- * image if networking bandwidth is low. Optional sigma specifies a blur radius
- * in radians to be applied to the scene before PMREM generation. Optional near
- * and far planes ensure the scene is rendered in its entirety (the cubeCamera
- * is placed at the origin).
- *
- * @deprecated
- * @param {Scene} scene - The scene to be captured.
- * @param {number} [sigma=0] - The blur radius in radians.
- * @param {number} [near=0.1] - The near plane distance.
- * @param {number} [far=100] - The far plane distance.
- * @param {Object} [options={}] - The configuration options.
- * @param {number} [options.size=256] - The texture size of the PMREM.
- * @param {Vector3} [options.position=origin] - The position of the internal cube camera that renders the scene.
- * @param {?RenderTarget} [options.renderTarget=null] - The render target to use.
- * @return {Promise<RenderTarget>} A Promise that resolve with the PMREM when the generation has been finished.
- * @see {@link PMREMGenerator#fromScene}
- */
- async fromSceneAsync( scene, sigma = 0, near = 0.1, far = 100, options = {} ) {
- warnOnce( 'PMREMGenerator: ".fromSceneAsync()" is deprecated. Use "await renderer.init()" instead.' ); // @deprecated r181
- await this._renderer.init();
- return this.fromScene( scene, sigma, near, far, options );
- }
- /**
- * Generates a PMREM from an equirectangular texture, which can be either LDR
- * or HDR. The ideal input image size is 1k (1024 x 512),
- * as this matches best with the 256 x 256 cubemap output.
- *
- * @param {Texture} equirectangular - The equirectangular texture to be converted.
- * @param {?RenderTarget} [renderTarget=null] - The render target to use.
- * @return {RenderTarget} The resulting PMREM.
- * @see {@link PMREMGenerator#fromEquirectangularAsync}
- */
- fromEquirectangular( equirectangular, renderTarget = null ) {
- if ( this._hasInitialized === false ) {
- warn( 'PMREMGenerator: .fromEquirectangular() called before the backend is initialized. Try using "await renderer.init()" instead.' );
- this._setSizeFromTexture( equirectangular );
- const cubeUVRenderTarget = renderTarget || this._allocateTarget();
- this.fromEquirectangularAsync( equirectangular, cubeUVRenderTarget );
- return cubeUVRenderTarget;
- }
- return this._fromTexture( equirectangular, renderTarget );
- }
- /**
- * Generates a PMREM from an equirectangular texture, which can be either LDR
- * or HDR. The ideal input image size is 1k (1024 x 512),
- * as this matches best with the 256 x 256 cubemap output.
- *
- * @deprecated
- * @param {Texture} equirectangular - The equirectangular texture to be converted.
- * @param {?RenderTarget} [renderTarget=null] - The render target to use.
- * @return {Promise<RenderTarget>} The resulting PMREM.
- * @see {@link PMREMGenerator#fromEquirectangular}
- */
- async fromEquirectangularAsync( equirectangular, renderTarget = null ) {
- warnOnce( 'PMREMGenerator: ".fromEquirectangularAsync()" is deprecated. Use "await renderer.init()" instead.' ); // @deprecated r181
- await this._renderer.init();
- return this._fromTexture( equirectangular, renderTarget );
- }
- /**
- * Generates a PMREM from an cubemap texture, which can be either LDR
- * or HDR. The ideal input cube size is 256 x 256,
- * as this matches best with the 256 x 256 cubemap output.
- *
- * @param {Texture} cubemap - The cubemap texture to be converted.
- * @param {?RenderTarget} [renderTarget=null] - The render target to use.
- * @return {RenderTarget} The resulting PMREM.
- * @see {@link PMREMGenerator#fromCubemapAsync}
- */
- fromCubemap( cubemap, renderTarget = null ) {
- if ( this._hasInitialized === false ) {
- warn( 'PMREMGenerator: .fromCubemap() called before the backend is initialized. Try using .fromCubemapAsync() instead.' );
- this._setSizeFromTexture( cubemap );
- const cubeUVRenderTarget = renderTarget || this._allocateTarget();
- this.fromCubemapAsync( cubemap, renderTarget );
- return cubeUVRenderTarget;
- }
- return this._fromTexture( cubemap, renderTarget );
- }
- /**
- * Generates a PMREM from an cubemap texture, which can be either LDR
- * or HDR. The ideal input cube size is 256 x 256,
- * with the 256 x 256 cubemap output.
- *
- * @deprecated
- * @param {Texture} cubemap - The cubemap texture to be converted.
- * @param {?RenderTarget} [renderTarget=null] - The render target to use.
- * @return {Promise<RenderTarget>} The resulting PMREM.
- * @see {@link PMREMGenerator#fromCubemap}
- */
- async fromCubemapAsync( cubemap, renderTarget = null ) {
- warnOnce( 'PMREMGenerator: ".fromCubemapAsync()" is deprecated. Use "await renderer.init()" instead.' ); // @deprecated r181
- await this._renderer.init();
- return this._fromTexture( cubemap, renderTarget );
- }
- /**
- * Pre-compiles the cubemap shader. You can get faster start-up by invoking this method during
- * your texture's network fetch for increased concurrency.
- *
- * @returns {Promise}
- */
- async compileCubemapShader() {
- if ( this._cubemapMaterial === null ) {
- this._cubemapMaterial = _getCubemapMaterial();
- await this._compileMaterial( this._cubemapMaterial );
- }
- }
- /**
- * Pre-compiles the equirectangular shader. You can get faster start-up by invoking this method during
- * your texture's network fetch for increased concurrency.
- *
- * @returns {Promise}
- */
- async compileEquirectangularShader() {
- if ( this._equirectMaterial === null ) {
- this._equirectMaterial = _getEquirectMaterial();
- await this._compileMaterial( this._equirectMaterial );
- }
- }
- /**
- * Disposes of the PMREMGenerator's internal memory. Note that PMREMGenerator is a static class,
- * so you should not need more than one PMREMGenerator object. If you do, calling dispose() on
- * one of them will cause any others to also become unusable.
- */
- dispose() {
- this._dispose();
- if ( this._cubemapMaterial !== null ) this._cubemapMaterial.dispose();
- if ( this._equirectMaterial !== null ) this._equirectMaterial.dispose();
- if ( this._backgroundBox !== null ) {
- this._backgroundBox.geometry.dispose();
- this._backgroundBox.material.dispose();
- }
- }
- // private interface
- _setSizeFromTexture( texture ) {
- if ( texture.mapping === CubeReflectionMapping || texture.mapping === CubeRefractionMapping ) {
- this._setSize( texture.image.length === 0 ? 16 : ( texture.image[ 0 ].width || texture.image[ 0 ].image.width ) );
- } else { // Equirectangular
- this._setSize( texture.image.width / 4 );
- }
- }
- _setSize( cubeSize ) {
- this._lodMax = Math.floor( Math.log2( cubeSize ) );
- this._cubeSize = Math.pow( 2, this._lodMax );
- }
- _dispose() {
- if ( this._blurMaterial !== null ) this._blurMaterial.dispose();
- if ( this._ggxMaterial !== null ) this._ggxMaterial.dispose();
- if ( this._pingPongRenderTarget !== null ) this._pingPongRenderTarget.dispose();
- for ( let i = 0; i < this._lodMeshes.length; i ++ ) {
- this._lodMeshes[ i ].geometry.dispose();
- }
- }
- _cleanup( outputTarget ) {
- this._renderer.setRenderTarget( _oldTarget, _oldActiveCubeFace, _oldActiveMipmapLevel );
- outputTarget.scissorTest = false;
- _setViewport( outputTarget, 0, 0, outputTarget.width, outputTarget.height );
- }
- _fromTexture( texture, renderTarget ) {
- this._setSizeFromTexture( texture );
- _oldTarget = this._renderer.getRenderTarget();
- _oldActiveCubeFace = this._renderer.getActiveCubeFace();
- _oldActiveMipmapLevel = this._renderer.getActiveMipmapLevel();
- const cubeUVRenderTarget = renderTarget || this._allocateTarget();
- this._init( cubeUVRenderTarget );
- this._textureToCubeUV( texture, cubeUVRenderTarget );
- this._applyPMREM( cubeUVRenderTarget );
- this._cleanup( cubeUVRenderTarget );
- return cubeUVRenderTarget;
- }
- _allocateTarget() {
- const width = 3 * Math.max( this._cubeSize, 16 * 7 );
- const height = 4 * this._cubeSize;
- const cubeUVRenderTarget = _createRenderTarget( width, height );
- return cubeUVRenderTarget;
- }
- _init( renderTarget ) {
- if ( this._pingPongRenderTarget === null || this._pingPongRenderTarget.width !== renderTarget.width || this._pingPongRenderTarget.height !== renderTarget.height ) {
- if ( this._pingPongRenderTarget !== null ) {
- this._dispose();
- }
- this._pingPongRenderTarget = _createRenderTarget( renderTarget.width, renderTarget.height );
- const { _lodMax } = this;
- ( { lodMeshes: this._lodMeshes, sizeLods: this._sizeLods, sigmas: this._sigmas } = _createPlanes( _lodMax ) );
- this._blurMaterial = _getBlurShader( _lodMax, renderTarget.width, renderTarget.height );
- this._ggxMaterial = _getGGXShader( _lodMax, renderTarget.width, renderTarget.height );
- }
- }
- async _compileMaterial( material ) {
- const mesh = new Mesh( new BufferGeometry(), material );
- await this._renderer.compile( mesh, _flatCamera );
- }
- _sceneToCubeUV( scene, near, far, cubeUVRenderTarget, position ) {
- const cubeCamera = _cubeCamera;
- cubeCamera.near = near;
- cubeCamera.far = far;
- // px, py, pz, nx, ny, nz
- const upSign = [ 1, 1, 1, 1, -1, 1 ];
- const forwardSign = [ 1, -1, 1, -1, 1, -1 ];
- const renderer = this._renderer;
- const originalAutoClear = renderer.autoClear;
- renderer.getClearColor( _clearColor$2 );
- renderer.autoClear = false;
- if ( this._backgroundBox === null ) {
- this._backgroundBox = new Mesh(
- new BoxGeometry(),
- new MeshBasicMaterial( {
- name: 'PMREM.Background',
- side: BackSide,
- depthWrite: false,
- depthTest: false,
- } )
- );
- }
- const backgroundBox = this._backgroundBox;
- const backgroundMaterial = backgroundBox.material;
- let useSolidColor = false;
- const background = scene.background;
- if ( background ) {
- if ( background.isColor ) {
- backgroundMaterial.color.copy( background );
- scene.background = null;
- useSolidColor = true;
- }
- } else {
- backgroundMaterial.color.copy( _clearColor$2 );
- useSolidColor = true;
- }
- renderer.setRenderTarget( cubeUVRenderTarget );
- renderer.clear();
- if ( useSolidColor ) {
- renderer.render( backgroundBox, cubeCamera );
- }
- for ( let i = 0; i < 6; i ++ ) {
- const col = i % 3;
- if ( col === 0 ) {
- cubeCamera.up.set( 0, upSign[ i ], 0 );
- cubeCamera.position.set( position.x, position.y, position.z );
- cubeCamera.lookAt( position.x + forwardSign[ i ], position.y, position.z );
- } else if ( col === 1 ) {
- cubeCamera.up.set( 0, 0, upSign[ i ] );
- cubeCamera.position.set( position.x, position.y, position.z );
- cubeCamera.lookAt( position.x, position.y + forwardSign[ i ], position.z );
- } else {
- cubeCamera.up.set( 0, upSign[ i ], 0 );
- cubeCamera.position.set( position.x, position.y, position.z );
- cubeCamera.lookAt( position.x, position.y, position.z + forwardSign[ i ] );
- }
- const size = this._cubeSize;
- _setViewport( cubeUVRenderTarget, col * size, i > 2 ? size : 0, size, size );
- renderer.render( scene, cubeCamera );
- }
- renderer.autoClear = originalAutoClear;
- scene.background = background;
- }
- _textureToCubeUV( texture, cubeUVRenderTarget ) {
- const renderer = this._renderer;
- const isCubeTexture = ( texture.mapping === CubeReflectionMapping || texture.mapping === CubeRefractionMapping );
- if ( isCubeTexture ) {
- if ( this._cubemapMaterial === null ) {
- this._cubemapMaterial = _getCubemapMaterial( texture );
- }
- } else {
- if ( this._equirectMaterial === null ) {
- this._equirectMaterial = _getEquirectMaterial( texture );
- }
- }
- const material = isCubeTexture ? this._cubemapMaterial : this._equirectMaterial;
- material.fragmentNode.value = texture;
- const mesh = this._lodMeshes[ 0 ];
- mesh.material = material;
- const size = this._cubeSize;
- _setViewport( cubeUVRenderTarget, 0, 0, 3 * size, 2 * size );
- renderer.setRenderTarget( cubeUVRenderTarget );
- renderer.render( mesh, _flatCamera );
- }
- _applyPMREM( cubeUVRenderTarget ) {
- const renderer = this._renderer;
- const autoClear = renderer.autoClear;
- renderer.autoClear = false;
- const n = this._lodMeshes.length;
- // Use GGX VNDF importance sampling
- for ( let i = 1; i < n; i ++ ) {
- this._applyGGXFilter( cubeUVRenderTarget, i - 1, i );
- }
- renderer.autoClear = autoClear;
- }
- /**
- * Applies GGX VNDF importance sampling filter to generate a prefiltered environment map.
- * Uses Monte Carlo integration with VNDF importance sampling to accurately represent the
- * GGX BRDF for physically-based rendering. Reads from the previous LOD level and
- * applies incremental roughness filtering to avoid over-blurring.
- *
- * @private
- * @param {RenderTarget} cubeUVRenderTarget
- * @param {number} lodIn - Source LOD level to read from
- * @param {number} lodOut - Target LOD level to write to
- */
- _applyGGXFilter( cubeUVRenderTarget, lodIn, lodOut ) {
- const renderer = this._renderer;
- const pingPongRenderTarget = this._pingPongRenderTarget;
- const ggxMaterial = this._ggxMaterial;
- const ggxMesh = this._lodMeshes[ lodOut ];
- ggxMesh.material = ggxMaterial;
- const ggxUniforms = _uniformsMap.get( ggxMaterial );
- // Calculate incremental roughness between LOD levels
- const targetRoughness = lodOut / ( this._lodMeshes.length - 1 );
- const sourceRoughness = lodIn / ( this._lodMeshes.length - 1 );
- const incrementalRoughness = Math.sqrt( targetRoughness * targetRoughness - sourceRoughness * sourceRoughness );
- // Apply blur strength mapping for better quality across the roughness range
- const blurStrength = 0.0 + targetRoughness * 1.25;
- const adjustedRoughness = incrementalRoughness * blurStrength;
- // Calculate viewport position based on output LOD level
- const { _lodMax } = this;
- const outputSize = this._sizeLods[ lodOut ];
- const x = 3 * outputSize * ( lodOut > _lodMax - LOD_MIN ? lodOut - _lodMax + LOD_MIN : 0 );
- const y = 4 * ( this._cubeSize - outputSize );
- // Read from previous LOD with incremental roughness
- cubeUVRenderTarget.texture.frame = ( cubeUVRenderTarget.texture.frame || 0 ) + 1;
- ggxUniforms.envMap.value = cubeUVRenderTarget.texture;
- ggxUniforms.roughness.value = adjustedRoughness;
- ggxUniforms.mipInt.value = _lodMax - lodIn; // Sample from input LOD
- _setViewport( pingPongRenderTarget, x, y, 3 * outputSize, 2 * outputSize );
- renderer.setRenderTarget( pingPongRenderTarget );
- renderer.render( ggxMesh, _flatCamera );
- // Copy from pingPong back to cubeUV (simple direct copy)
- pingPongRenderTarget.texture.frame = ( pingPongRenderTarget.texture.frame || 0 ) + 1;
- ggxUniforms.envMap.value = pingPongRenderTarget.texture;
- ggxUniforms.roughness.value = 0.0; // Direct copy
- ggxUniforms.mipInt.value = _lodMax - lodOut; // Read from the level we just wrote
- _setViewport( cubeUVRenderTarget, x, y, 3 * outputSize, 2 * outputSize );
- renderer.setRenderTarget( cubeUVRenderTarget );
- renderer.render( ggxMesh, _flatCamera );
- }
- /**
- * This is a two-pass Gaussian blur for a cubemap. Normally this is done
- * vertically and horizontally, but this breaks down on a cube. Here we apply
- * the blur latitudinally (around the poles), and then longitudinally (towards
- * the poles) to approximate the orthogonally-separable blur. It is least
- * accurate at the poles, but still does a decent job.
- *
- * Used for initial scene blur in fromScene() method when sigma > 0.
- *
- * @private
- * @param {RenderTarget} cubeUVRenderTarget - The cubemap render target.
- * @param {number} lodIn - The input level-of-detail.
- * @param {number} lodOut - The output level-of-detail.
- * @param {number} sigma - The blur radius in radians.
- * @param {Vector3} [poleAxis] - The pole axis.
- */
- _blur( cubeUVRenderTarget, lodIn, lodOut, sigma, poleAxis ) {
- const pingPongRenderTarget = this._pingPongRenderTarget;
- this._halfBlur(
- cubeUVRenderTarget,
- pingPongRenderTarget,
- lodIn,
- lodOut,
- sigma,
- 'latitudinal',
- poleAxis );
- this._halfBlur(
- pingPongRenderTarget,
- cubeUVRenderTarget,
- lodOut,
- lodOut,
- sigma,
- 'longitudinal',
- poleAxis );
- }
- _halfBlur( targetIn, targetOut, lodIn, lodOut, sigmaRadians, direction, poleAxis ) {
- const renderer = this._renderer;
- const blurMaterial = this._blurMaterial;
- if ( direction !== 'latitudinal' && direction !== 'longitudinal' ) {
- error( 'blur direction must be either latitudinal or longitudinal!' );
- }
- // Number of standard deviations at which to cut off the discrete approximation.
- const STANDARD_DEVIATIONS = 3;
- const blurMesh = this._lodMeshes[ lodOut ];
- blurMesh.material = blurMaterial;
- const blurUniforms = _uniformsMap.get( blurMaterial );
- const pixels = this._sizeLods[ lodIn ] - 1;
- const radiansPerPixel = isFinite( sigmaRadians ) ? Math.PI / ( 2 * pixels ) : 2 * Math.PI / ( 2 * MAX_SAMPLES - 1 );
- const sigmaPixels = sigmaRadians / radiansPerPixel;
- const samples = isFinite( sigmaRadians ) ? 1 + Math.floor( STANDARD_DEVIATIONS * sigmaPixels ) : MAX_SAMPLES;
- if ( samples > MAX_SAMPLES ) {
- warn( `sigmaRadians, ${
- sigmaRadians}, is too large and will clip, as it requested ${
- samples} samples when the maximum is set to ${MAX_SAMPLES}` );
- }
- const weights = [];
- let sum = 0;
- for ( let i = 0; i < MAX_SAMPLES; ++ i ) {
- const x = i / sigmaPixels;
- const weight = Math.exp( - x * x / 2 );
- weights.push( weight );
- if ( i === 0 ) {
- sum += weight;
- } else if ( i < samples ) {
- sum += 2 * weight;
- }
- }
- for ( let i = 0; i < weights.length; i ++ ) {
- weights[ i ] = weights[ i ] / sum;
- }
- targetIn.texture.frame = ( targetIn.texture.frame || 0 ) + 1;
- blurUniforms.envMap.value = targetIn.texture;
- blurUniforms.samples.value = samples;
- blurUniforms.weights.array = weights;
- blurUniforms.latitudinal.value = direction === 'latitudinal' ? 1 : 0;
- if ( poleAxis ) {
- blurUniforms.poleAxis.value = poleAxis;
- }
- const { _lodMax } = this;
- blurUniforms.dTheta.value = radiansPerPixel;
- blurUniforms.mipInt.value = _lodMax - lodIn;
- const outputSize = this._sizeLods[ lodOut ];
- const x = 3 * outputSize * ( lodOut > _lodMax - LOD_MIN ? lodOut - _lodMax + LOD_MIN : 0 );
- const y = 4 * ( this._cubeSize - outputSize );
- _setViewport( targetOut, x, y, 3 * outputSize, 2 * outputSize );
- renderer.setRenderTarget( targetOut );
- renderer.render( blurMesh, _flatCamera );
- }
- }
- function _createPlanes( lodMax ) {
- const sizeLods = [];
- const sigmas = [];
- const lodMeshes = [];
- let lod = lodMax;
- const totalLods = lodMax - LOD_MIN + 1 + EXTRA_LOD_SIGMA.length;
- for ( let i = 0; i < totalLods; i ++ ) {
- const sizeLod = Math.pow( 2, lod );
- sizeLods.push( sizeLod );
- let sigma = 1.0 / sizeLod;
- if ( i > lodMax - LOD_MIN ) {
- sigma = EXTRA_LOD_SIGMA[ i - lodMax + LOD_MIN - 1 ];
- } else if ( i === 0 ) {
- sigma = 0;
- }
- sigmas.push( sigma );
- const texelSize = 1.0 / ( sizeLod - 2 );
- const min = - texelSize;
- const max = 1 + texelSize;
- const uv1 = [ min, min, max, min, max, max, min, min, max, max, min, max ];
- const cubeFaces = 6;
- const vertices = 6;
- const positionSize = 3;
- const uvSize = 2;
- const faceIndexSize = 1;
- const position = new Float32Array( positionSize * vertices * cubeFaces );
- const uv = new Float32Array( uvSize * vertices * cubeFaces );
- const faceIndex = new Float32Array( faceIndexSize * vertices * cubeFaces );
- for ( let face = 0; face < cubeFaces; face ++ ) {
- const x = ( face % 3 ) * 2 / 3 - 1;
- const y = face > 2 ? 0 : -1;
- const coordinates = [
- x, y, 0,
- x + 2 / 3, y, 0,
- x + 2 / 3, y + 1, 0,
- x, y, 0,
- x + 2 / 3, y + 1, 0,
- x, y + 1, 0
- ];
- const faceIdx = _faceLib[ face ];
- position.set( coordinates, positionSize * vertices * faceIdx );
- uv.set( uv1, uvSize * vertices * faceIdx );
- const fill = [ faceIdx, faceIdx, faceIdx, faceIdx, faceIdx, faceIdx ];
- faceIndex.set( fill, faceIndexSize * vertices * faceIdx );
- }
- const planes = new BufferGeometry();
- planes.setAttribute( 'position', new BufferAttribute( position, positionSize ) );
- planes.setAttribute( 'uv', new BufferAttribute( uv, uvSize ) );
- planes.setAttribute( 'faceIndex', new BufferAttribute( faceIndex, faceIndexSize ) );
- lodMeshes.push( new Mesh( planes, null ) );
- if ( lod > LOD_MIN ) {
- lod --;
- }
- }
- return { lodMeshes, sizeLods, sigmas };
- }
- function _createRenderTarget( width, height ) {
- const params = {
- magFilter: LinearFilter,
- minFilter: LinearFilter,
- generateMipmaps: false,
- type: HalfFloatType,
- format: RGBAFormat,
- colorSpace: LinearSRGBColorSpace,
- //depthBuffer: false
- };
- const cubeUVRenderTarget = new RenderTarget( width, height, params );
- cubeUVRenderTarget.texture.mapping = CubeUVReflectionMapping;
- cubeUVRenderTarget.texture.name = 'PMREM.cubeUv';
- cubeUVRenderTarget.texture.isPMREMTexture = true;
- cubeUVRenderTarget.scissorTest = true;
- return cubeUVRenderTarget;
- }
- function _setViewport( target, x, y, width, height ) {
- target.viewport.set( x, y, width, height );
- target.scissor.set( x, y, width, height );
- }
- function _getMaterial( type ) {
- const material = new NodeMaterial();
- material.depthTest = false;
- material.depthWrite = false;
- material.blending = NoBlending;
- material.name = `PMREM_${ type }`;
- return material;
- }
- function _getBlurShader( lodMax, width, height ) {
- const weights = uniformArray( new Array( MAX_SAMPLES ).fill( 0 ) );
- const poleAxis = uniform( new Vector3( 0, 1, 0 ) );
- const dTheta = uniform( 0 );
- const n = float( MAX_SAMPLES );
- const latitudinal = uniform( 0 ); // false, bool
- const samples = uniform( 1 ); // int
- const envMap = texture();
- const mipInt = uniform( 0 ); // int
- const CUBEUV_TEXEL_WIDTH = float( 1 / width );
- const CUBEUV_TEXEL_HEIGHT = float( 1 / height );
- const CUBEUV_MAX_MIP = float( lodMax );
- const materialUniforms = {
- n,
- latitudinal,
- weights,
- poleAxis,
- outputDirection: _outputDirection,
- dTheta,
- samples,
- envMap,
- mipInt,
- CUBEUV_TEXEL_WIDTH,
- CUBEUV_TEXEL_HEIGHT,
- CUBEUV_MAX_MIP
- };
- const material = _getMaterial( 'blur' );
- material.fragmentNode = blur( { ...materialUniforms, latitudinal: latitudinal.equal( 1 ) } );
- _uniformsMap.set( material, materialUniforms );
- return material;
- }
- function _getGGXShader( lodMax, width, height ) {
- const envMap = texture();
- const roughness = uniform( 0 );
- const mipInt = uniform( 0 );
- const CUBEUV_TEXEL_WIDTH = float( 1 / width );
- const CUBEUV_TEXEL_HEIGHT = float( 1 / height );
- const CUBEUV_MAX_MIP = float( lodMax );
- const materialUniforms = {
- envMap,
- roughness,
- mipInt,
- CUBEUV_TEXEL_WIDTH,
- CUBEUV_TEXEL_HEIGHT,
- CUBEUV_MAX_MIP
- };
- const material = _getMaterial( 'ggx' );
- material.fragmentNode = ggxConvolution( {
- ...materialUniforms,
- N_immutable: _outputDirection,
- GGX_SAMPLES: uint( GGX_SAMPLES )
- } );
- _uniformsMap.set( material, materialUniforms );
- return material;
- }
- function _getCubemapMaterial( envTexture ) {
- const material = _getMaterial( 'cubemap' );
- material.fragmentNode = cubeTexture( envTexture, _outputDirection );
- return material;
- }
- function _getEquirectMaterial( envTexture ) {
- const material = _getMaterial( 'equirect' );
- material.fragmentNode = texture( envTexture, equirectUV( _outputDirection ), 0 );
- return material;
- }
- const _cache = new WeakMap();
- /**
- * Generates the cubeUV size based on the given image height.
- *
- * @private
- * @param {number} imageHeight - The image height.
- * @return {{texelWidth: number,texelHeight: number, maxMip: number}} The result object.
- */
- function _generateCubeUVSize( imageHeight ) {
- const maxMip = Math.log2( imageHeight ) - 2;
- const texelHeight = 1.0 / imageHeight;
- const texelWidth = 1.0 / ( 3 * Math.max( Math.pow( 2, maxMip ), 7 * 16 ) );
- return { texelWidth, texelHeight, maxMip };
- }
- /**
- * Generates a PMREM from the given texture.
- *
- * @private
- * @param {Texture} texture - The texture to create the PMREM for.
- * @param {Renderer} renderer - The renderer.
- * @param {PMREMGenerator} generator - The PMREM generator.
- * @return {?Texture} The PMREM.
- */
- function _getPMREMFromTexture( texture, renderer, generator ) {
- const cache = _getCache( renderer );
- let cacheTexture = cache.get( texture );
- const pmremVersion = cacheTexture !== undefined ? cacheTexture.pmremVersion : -1;
- if ( pmremVersion !== texture.pmremVersion ) {
- const image = texture.image;
- if ( texture.isCubeTexture ) {
- if ( isCubeMapReady( image ) ) {
- cacheTexture = generator.fromCubemap( texture, cacheTexture );
- } else {
- return null;
- }
- } else {
- if ( isEquirectangularMapReady( image ) ) {
- cacheTexture = generator.fromEquirectangular( texture, cacheTexture );
- } else {
- return null;
- }
- }
- cacheTexture.pmremVersion = texture.pmremVersion;
- cache.set( texture, cacheTexture );
- }
- return cacheTexture.texture;
- }
- /**
- * Returns a cache that stores generated PMREMs for the respective textures.
- * A cache must be maintained per renderer since PMREMs are render target textures
- * which can't be shared across render contexts.
- *
- * @private
- * @param {Renderer} renderer - The renderer.
- * @return {WeakMap<Texture, Texture>} The PMREM cache.
- */
- function _getCache( renderer ) {
- let rendererCache = _cache.get( renderer );
- if ( rendererCache === undefined ) {
- rendererCache = new WeakMap();
- _cache.set( renderer, rendererCache );
- }
- return rendererCache;
- }
- /**
- * This node represents a PMREM which is a special type of preprocessed
- * environment map intended for PBR materials.
- *
- * ```js
- * const material = new MeshStandardNodeMaterial();
- * material.envNode = pmremTexture( envMap );
- * ```
- *
- * @augments TempNode
- */
- class PMREMNode extends TempNode {
- static get type() {
- return 'PMREMNode';
- }
- /**
- * Constructs a new function overloading node.
- *
- * @param {Texture} value - The input texture.
- * @param {Node<vec2>} [uvNode=null] - The uv node.
- * @param {Node<float>} [levelNode=null] - The level node.
- */
- constructor( value, uvNode = null, levelNode = null ) {
- super( 'vec3' );
- /**
- * Reference to the input texture.
- *
- * @private
- * @type {Texture}
- */
- this._value = value;
- /**
- * Reference to the generated PMREM.
- *
- * @private
- * @type {Texture | null}
- * @default null
- */
- this._pmrem = null;
- /**
- * The uv node.
- *
- * @type {Node<vec2>}
- */
- this.uvNode = uvNode;
- /**
- * The level node.
- *
- * @type {Node<float>}
- */
- this.levelNode = levelNode;
- /**
- * Reference to a PMREM generator.
- *
- * @private
- * @type {?PMREMGenerator}
- * @default null
- */
- this._generator = null;
- const defaultTexture = new Texture();
- defaultTexture.isRenderTargetTexture = true;
- /**
- * The texture node holding the generated PMREM.
- *
- * @private
- * @type {TextureNode}
- */
- this._texture = texture( defaultTexture );
- /**
- * A uniform representing the PMREM's width.
- *
- * @private
- * @type {UniformNode<float>}
- */
- this._width = uniform( 0 );
- /**
- * A uniform representing the PMREM's height.
- *
- * @private
- * @type {UniformNode<float>}
- */
- this._height = uniform( 0 );
- /**
- * A uniform representing the PMREM's max Mip.
- *
- * @private
- * @type {UniformNode<float>}
- */
- this._maxMip = uniform( 0 );
- /**
- * The `updateBeforeType` is set to `NodeUpdateType.RENDER`.
- *
- * @type {string}
- * @default 'render'
- */
- this.updateBeforeType = NodeUpdateType.RENDER;
- }
- set value( value ) {
- this._value = value;
- this._pmrem = null;
- }
- /**
- * The node's texture value.
- *
- * @type {Texture}
- */
- get value() {
- return this._value;
- }
- /**
- * Uses the given PMREM texture to update internal values.
- *
- * @param {Texture} texture - The PMREM texture.
- */
- updateFromTexture( texture ) {
- const cubeUVSize = _generateCubeUVSize( texture.image.height );
- this._texture.value = texture;
- this._width.value = cubeUVSize.texelWidth;
- this._height.value = cubeUVSize.texelHeight;
- this._maxMip.value = cubeUVSize.maxMip;
- }
- updateBefore( frame ) {
- let pmrem = this._pmrem;
- const pmremVersion = pmrem ? pmrem.pmremVersion : -1;
- const texture = this._value;
- if ( pmremVersion !== texture.pmremVersion ) {
- if ( texture.isPMREMTexture === true ) {
- pmrem = texture;
- } else {
- pmrem = _getPMREMFromTexture( texture, frame.renderer, this._generator );
- }
- if ( pmrem !== null ) {
- this._pmrem = pmrem;
- this.updateFromTexture( pmrem );
- }
- }
- }
- setup( builder ) {
- if ( this._generator === null ) {
- this._generator = new PMREMGenerator( builder.renderer );
- }
- this.updateBefore( builder );
- //
- let uvNode = this.uvNode;
- if ( uvNode === null && builder.context.getUV ) {
- uvNode = builder.context.getUV( this, builder );
- }
- //
- uvNode = materialEnvRotation.mul( vec3( uvNode.x, uvNode.y.negate(), uvNode.z ) );
- //
- let levelNode = this.levelNode;
- if ( levelNode === null && builder.context.getTextureLevel ) {
- levelNode = builder.context.getTextureLevel( this );
- }
- //
- return textureCubeUV( this._texture, uvNode, levelNode, this._width, this._height, this._maxMip );
- }
- dispose() {
- super.dispose();
- if ( this._generator !== null ) this._generator.dispose();
- }
- }
- /**
- * Returns `true` if the given cube map image has been fully loaded.
- *
- * @private
- * @param {?Array<(Image|Object)>} [image] - The cube map image.
- * @return {boolean} Whether the given cube map is ready or not.
- */
- function isCubeMapReady( image ) {
- if ( image === null || image === undefined ) return false;
- let count = 0;
- const length = 6;
- for ( let i = 0; i < length; i ++ ) {
- if ( image[ i ] !== undefined ) count ++;
- }
- return count === length;
- }
- /**
- * Returns `true` if the given equirectangular image has been fully loaded.
- *
- * @private
- * @param {(Image|Object)} image - The equirectangular image.
- * @return {boolean} Whether the given cube map is ready or not.
- */
- function isEquirectangularMapReady( image ) {
- if ( image === null || image === undefined ) return false;
- return image.height > 0;
- }
- /**
- * TSL function for creating a PMREM node.
- *
- * @tsl
- * @function
- * @param {Texture} value - The input texture.
- * @param {?Node<vec2>} [uvNode=null] - The uv node.
- * @param {?Node<float>} [levelNode=null] - The level node.
- * @returns {PMREMNode}
- */
- const pmremTexture = /*@__PURE__*/ nodeProxy( PMREMNode ).setParameterLength( 1, 3 );
- const _envNodeCache = new WeakMap();
- /**
- * Represents a physical model for Image-based lighting (IBL). The environment
- * is defined via environment maps in the equirectangular, cube map or cubeUV (PMREM) format.
- * `EnvironmentNode` is intended for PBR materials like {@link MeshStandardNodeMaterial}.
- *
- * @augments LightingNode
- */
- class EnvironmentNode extends LightingNode {
- static get type() {
- return 'EnvironmentNode';
- }
- /**
- * Constructs a new environment node.
- *
- * @param {Node} [envNode=null] - A node representing the environment.
- */
- constructor( envNode = null ) {
- super();
- /**
- * A node representing the environment.
- *
- * @type {?Node}
- * @default null
- */
- this.envNode = envNode;
- }
- setup( builder ) {
- const { material } = builder;
- let envNode = this.envNode;
- if ( envNode.isTextureNode || envNode.isMaterialReferenceNode ) {
- const value = ( envNode.isTextureNode ) ? envNode.value : material[ envNode.property ];
- let cacheEnvNode = _envNodeCache.get( value );
- if ( cacheEnvNode === undefined ) {
- cacheEnvNode = pmremTexture( value );
- _envNodeCache.set( value, cacheEnvNode );
- }
- envNode = cacheEnvNode;
- }
- //
- const useAnisotropy = material.useAnisotropy === true || material.anisotropy > 0;
- const radianceNormalView = useAnisotropy ? bentNormalView : normalView;
- const radiance = envNode.context( createRadianceContext( roughness, radianceNormalView ) ).mul( materialEnvIntensity );
- const irradiance = envNode.context( createIrradianceContext( normalWorld ) ).mul( Math.PI ).mul( materialEnvIntensity );
- const isolateRadiance = isolate( radiance );
- const isolateIrradiance = isolate( irradiance );
- //
- builder.context.radiance.addAssign( isolateRadiance );
- builder.context.iblIrradiance.addAssign( isolateIrradiance );
- //
- const clearcoatRadiance = builder.context.lightingModel.clearcoatRadiance;
- if ( clearcoatRadiance ) {
- const clearcoatRadianceContext = envNode.context( createRadianceContext( clearcoatRoughness, clearcoatNormalView ) ).mul( materialEnvIntensity );
- const isolateClearcoatRadiance = isolate( clearcoatRadianceContext );
- clearcoatRadiance.addAssign( isolateClearcoatRadiance );
- }
- }
- }
- const createRadianceContext = ( roughnessNode, normalViewNode ) => {
- let reflectVec = null;
- return {
- getUV: () => {
- if ( reflectVec === null ) {
- reflectVec = positionViewDirection.negate().reflect( normalViewNode );
- // Mixing the reflection with the normal is more accurate and keeps rough objects from gathering light from behind their tangent plane.
- reflectVec = pow4( roughnessNode ).mix( reflectVec, normalViewNode ).normalize();
- reflectVec = reflectVec.transformDirection( cameraViewMatrix );
- }
- return reflectVec;
- },
- getTextureLevel: () => {
- return roughnessNode;
- }
- };
- };
- const createIrradianceContext = ( normalWorldNode ) => {
- return {
- getUV: () => {
- return normalWorldNode;
- },
- getTextureLevel: () => {
- return float( 1.0 );
- }
- };
- };
- const _defaultValues$6 = /*@__PURE__*/ new MeshStandardMaterial();
- /**
- * Node material version of {@link MeshStandardMaterial}.
- *
- * @augments NodeMaterial
- */
- class MeshStandardNodeMaterial extends NodeMaterial {
- static get type() {
- return 'MeshStandardNodeMaterial';
- }
- /**
- * Constructs a new mesh standard node material.
- *
- * @param {Object} [parameters] - The configuration parameter.
- */
- constructor( parameters ) {
- super();
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isMeshStandardNodeMaterial = true;
- /**
- * Set to `true` because standard materials react on lights.
- *
- * @type {boolean}
- * @default true
- */
- this.lights = true;
- /**
- * The emissive color of standard materials is by default inferred from the `emissive`,
- * `emissiveIntensity` and `emissiveMap` properties. This node property allows to
- * overwrite the default and define the emissive color with a node instead.
- *
- * If you don't want to overwrite the emissive color but modify the existing
- * value instead, use {@link materialEmissive}.
- *
- * @type {?Node<vec3>}
- * @default null
- */
- this.emissiveNode = null;
- /**
- * The metalness of standard materials is by default inferred from the `metalness`,
- * and `metalnessMap` properties. This node property allows to
- * overwrite the default and define the metalness with a node instead.
- *
- * If you don't want to overwrite the metalness but modify the existing
- * value instead, use {@link materialMetalness}.
- *
- * @type {?Node<float>}
- * @default null
- */
- this.metalnessNode = null;
- /**
- * The roughness of standard materials is by default inferred from the `roughness`,
- * and `roughnessMap` properties. This node property allows to
- * overwrite the default and define the roughness with a node instead.
- *
- * If you don't want to overwrite the roughness but modify the existing
- * value instead, use {@link materialRoughness}.
- *
- * @type {?Node<float>}
- * @default null
- */
- this.roughnessNode = null;
- this.setDefaultValues( _defaultValues$6 );
- this.setValues( parameters );
- }
- /**
- * Overwritten since this type of material uses {@link EnvironmentNode}
- * to implement the PBR (PMREM based) environment mapping. Besides, the
- * method honors `Scene.environment`.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {?EnvironmentNode<vec3>} The environment node.
- */
- setupEnvironment( builder ) {
- let envNode = super.setupEnvironment( builder );
- if ( envNode === null && builder.environmentNode ) {
- envNode = builder.environmentNode;
- }
- return envNode ? new EnvironmentNode( envNode ) : null;
- }
- /**
- * Setups the lighting model.
- *
- * @return {PhysicalLightingModel} The lighting model.
- */
- setupLightingModel( /*builder*/ ) {
- return new PhysicalLightingModel();
- }
- /**
- * Setups the specular related node variables.
- */
- setupSpecular() {
- const specularColorNode = mix( vec3( 0.04 ), diffuseColor.rgb, metalness );
- specularColor.assign( vec3( 0.04 ) );
- specularColorBlended.assign( specularColorNode );
- specularF90.assign( 1.0 );
- }
- /**
- * Setups the standard specific node variables.
- *
- * @param {NodeBuilder} builder - The current node builder.
- */
- setupVariants() {
- // METALNESS
- const metalnessNode = this.metalnessNode ? float( this.metalnessNode ) : materialMetalness;
- metalness.assign( metalnessNode );
- // ROUGHNESS
- let roughnessNode = this.roughnessNode ? float( this.roughnessNode ) : materialRoughness;
- roughnessNode = getRoughness( { roughness: roughnessNode } );
- roughness.assign( roughnessNode );
- // SPECULAR COLOR
- this.setupSpecular();
- // DIFFUSE COLOR
- diffuseContribution.assign( diffuseColor.rgb.mul( metalnessNode.oneMinus() ) );
- }
- copy( source ) {
- this.emissiveNode = source.emissiveNode;
- this.metalnessNode = source.metalnessNode;
- this.roughnessNode = source.roughnessNode;
- return super.copy( source );
- }
- }
- const _defaultValues$5 = /*@__PURE__*/ new MeshPhysicalMaterial();
- /**
- * Node material version of {@link MeshPhysicalMaterial}.
- *
- * @augments MeshStandardNodeMaterial
- */
- class MeshPhysicalNodeMaterial extends MeshStandardNodeMaterial {
- static get type() {
- return 'MeshPhysicalNodeMaterial';
- }
- /**
- * Constructs a new mesh physical node material.
- *
- * @param {Object} [parameters] - The configuration parameter.
- */
- constructor( parameters ) {
- super();
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isMeshPhysicalNodeMaterial = true;
- /**
- * The clearcoat of physical materials is by default inferred from the `clearcoat`
- * and `clearcoatMap` properties. This node property allows to overwrite the default
- * and define the clearcoat with a node instead.
- *
- * If you don't want to overwrite the clearcoat but modify the existing
- * value instead, use {@link materialClearcoat}.
- *
- * @type {?Node<float>}
- * @default null
- */
- this.clearcoatNode = null;
- /**
- * The clearcoat roughness of physical materials is by default inferred from the `clearcoatRoughness`
- * and `clearcoatRoughnessMap` properties. This node property allows to overwrite the default
- * and define the clearcoat roughness with a node instead.
- *
- * If you don't want to overwrite the clearcoat roughness but modify the existing
- * value instead, use {@link materialClearcoatRoughness}.
- *
- * @type {?Node<float>}
- * @default null
- */
- this.clearcoatRoughnessNode = null;
- /**
- * The clearcoat normal of physical materials is by default inferred from the `clearcoatNormalMap`
- * property. This node property allows to overwrite the default
- * and define the clearcoat normal with a node instead.
- *
- * If you don't want to overwrite the clearcoat normal but modify the existing
- * value instead, use {@link materialClearcoatNormal}.
- *
- * @type {?Node<vec3>}
- * @default null
- */
- this.clearcoatNormalNode = null;
- /**
- * The sheen of physical materials is by default inferred from the `sheen`, `sheenColor`
- * and `sheenColorMap` properties. This node property allows to overwrite the default
- * and define the sheen with a node instead.
- *
- * If you don't want to overwrite the sheen but modify the existing
- * value instead, use {@link materialSheen}.
- *
- * @type {?Node<vec3>}
- * @default null
- */
- this.sheenNode = null;
- /**
- * The sheen roughness of physical materials is by default inferred from the `sheenRoughness` and
- * `sheenRoughnessMap` properties. This node property allows to overwrite the default
- * and define the sheen roughness with a node instead.
- *
- * If you don't want to overwrite the sheen roughness but modify the existing
- * value instead, use {@link materialSheenRoughness}.
- *
- * @type {?Node<float>}
- * @default null
- */
- this.sheenRoughnessNode = null;
- /**
- * The iridescence of physical materials is by default inferred from the `iridescence`
- * property. This node property allows to overwrite the default
- * and define the iridescence with a node instead.
- *
- * If you don't want to overwrite the iridescence but modify the existing
- * value instead, use {@link materialIridescence}.
- *
- * @type {?Node<float>}
- * @default null
- */
- this.iridescenceNode = null;
- /**
- * The iridescence IOR of physical materials is by default inferred from the `iridescenceIOR`
- * property. This node property allows to overwrite the default
- * and define the iridescence IOR with a node instead.
- *
- * If you don't want to overwrite the iridescence IOR but modify the existing
- * value instead, use {@link materialIridescenceIOR}.
- *
- * @type {?Node<float>}
- * @default null
- */
- this.iridescenceIORNode = null;
- /**
- * The iridescence thickness of physical materials is by default inferred from the `iridescenceThicknessRange`
- * and `iridescenceThicknessMap` properties. This node property allows to overwrite the default
- * and define the iridescence thickness with a node instead.
- *
- * If you don't want to overwrite the iridescence thickness but modify the existing
- * value instead, use {@link materialIridescenceThickness}.
- *
- * @type {?Node<float>}
- * @default null
- */
- this.iridescenceThicknessNode = null;
- /**
- * The specular intensity of physical materials is by default inferred from the `specularIntensity`
- * and `specularIntensityMap` properties. This node property allows to overwrite the default
- * and define the specular intensity with a node instead.
- *
- * If you don't want to overwrite the specular intensity but modify the existing
- * value instead, use {@link materialSpecularIntensity}.
- *
- * @type {?Node<float>}
- * @default null
- */
- this.specularIntensityNode = null;
- /**
- * The specular color of physical materials is by default inferred from the `specularColor`
- * and `specularColorMap` properties. This node property allows to overwrite the default
- * and define the specular color with a node instead.
- *
- * If you don't want to overwrite the specular color but modify the existing
- * value instead, use {@link materialSpecularColor}.
- *
- * @type {?Node<vec3>}
- * @default null
- */
- this.specularColorNode = null;
- /**
- * The ior of physical materials is by default inferred from the `ior`
- * property. This node property allows to overwrite the default
- * and define the ior with a node instead.
- *
- * If you don't want to overwrite the ior but modify the existing
- * value instead, use {@link materialIOR}.
- *
- * @type {?Node<float>}
- * @default null
- */
- this.iorNode = null;
- /**
- * The transmission of physical materials is by default inferred from the `transmission` and
- * `transmissionMap` properties. This node property allows to overwrite the default
- * and define the transmission with a node instead.
- *
- * If you don't want to overwrite the transmission but modify the existing
- * value instead, use {@link materialTransmission}.
- *
- * @type {?Node<float>}
- * @default null
- */
- this.transmissionNode = null;
- /**
- * The thickness of physical materials is by default inferred from the `thickness` and
- * `thicknessMap` properties. This node property allows to overwrite the default
- * and define the thickness with a node instead.
- *
- * If you don't want to overwrite the thickness but modify the existing
- * value instead, use {@link materialThickness}.
- *
- * @type {?Node<float>}
- * @default null
- */
- this.thicknessNode = null;
- /**
- * The attenuation distance of physical materials is by default inferred from the
- * `attenuationDistance` property. This node property allows to overwrite the default
- * and define the attenuation distance with a node instead.
- *
- * If you don't want to overwrite the attenuation distance but modify the existing
- * value instead, use {@link materialAttenuationDistance}.
- *
- * @type {?Node<float>}
- * @default null
- */
- this.attenuationDistanceNode = null;
- /**
- * The attenuation color of physical materials is by default inferred from the
- * `attenuationColor` property. This node property allows to overwrite the default
- * and define the attenuation color with a node instead.
- *
- * If you don't want to overwrite the attenuation color but modify the existing
- * value instead, use {@link materialAttenuationColor}.
- *
- * @type {?Node<vec3>}
- * @default null
- */
- this.attenuationColorNode = null;
- /**
- * The dispersion of physical materials is by default inferred from the
- * `dispersion` property. This node property allows to overwrite the default
- * and define the dispersion with a node instead.
- *
- * If you don't want to overwrite the dispersion but modify the existing
- * value instead, use {@link materialDispersion}.
- *
- * @type {?Node<float>}
- * @default null
- */
- this.dispersionNode = null;
- /**
- * The anisotropy of physical materials is by default inferred from the
- * `anisotropy` property. This node property allows to overwrite the default
- * and define the anisotropy with a node instead.
- *
- * If you don't want to overwrite the anisotropy but modify the existing
- * value instead, use {@link materialAnisotropy}.
- *
- * @type {?Node<float>}
- * @default null
- */
- this.anisotropyNode = null;
- this.setDefaultValues( _defaultValues$5 );
- this.setValues( parameters );
- }
- /**
- * Whether the lighting model should use clearcoat or not.
- *
- * @type {boolean}
- * @default true
- */
- get useClearcoat() {
- return this.clearcoat > 0 || this.clearcoatNode !== null;
- }
- /**
- * Whether the lighting model should use iridescence or not.
- *
- * @type {boolean}
- * @default true
- */
- get useIridescence() {
- return this.iridescence > 0 || this.iridescenceNode !== null;
- }
- /**
- * Whether the lighting model should use sheen or not.
- *
- * @type {boolean}
- * @default true
- */
- get useSheen() {
- return this.sheen > 0 || this.sheenNode !== null;
- }
- /**
- * Whether the lighting model should use anisotropy or not.
- *
- * @type {boolean}
- * @default true
- */
- get useAnisotropy() {
- return this.anisotropy > 0 || this.anisotropyNode !== null;
- }
- /**
- * Whether the lighting model should use transmission or not.
- *
- * @type {boolean}
- * @default true
- */
- get useTransmission() {
- return this.transmission > 0 || this.transmissionNode !== null;
- }
- /**
- * Whether the lighting model should use dispersion or not.
- *
- * @type {boolean}
- * @default true
- */
- get useDispersion() {
- return this.dispersion > 0 || this.dispersionNode !== null;
- }
- /**
- * Setups the specular related node variables.
- */
- setupSpecular() {
- const iorNode = this.iorNode ? float( this.iorNode ) : materialIOR;
- ior.assign( iorNode );
- specularColor.assign( min$1( pow2( ior.sub( 1.0 ).div( ior.add( 1.0 ) ) ).mul( materialSpecularColor ), vec3( 1.0 ) ).mul( materialSpecularIntensity ) );
- specularColorBlended.assign( mix( specularColor, diffuseColor.rgb, metalness ) );
- specularF90.assign( mix( materialSpecularIntensity, 1.0, metalness ) );
- }
- /**
- * Setups the lighting model.
- *
- * @return {PhysicalLightingModel} The lighting model.
- */
- setupLightingModel( /*builder*/ ) {
- return new PhysicalLightingModel( this.useClearcoat, this.useSheen, this.useIridescence, this.useAnisotropy, this.useTransmission, this.useDispersion );
- }
- /**
- * Setups the physical specific node variables.
- *
- * @param {NodeBuilder} builder - The current node builder.
- */
- setupVariants( builder ) {
- super.setupVariants( builder );
- // CLEARCOAT
- if ( this.useClearcoat ) {
- const clearcoatNode = this.clearcoatNode ? float( this.clearcoatNode ) : materialClearcoat;
- const clearcoatRoughnessNode = this.clearcoatRoughnessNode ? float( this.clearcoatRoughnessNode ) : materialClearcoatRoughness;
- clearcoat.assign( clearcoatNode );
- clearcoatRoughness.assign( getRoughness( { roughness: clearcoatRoughnessNode } ) );
- }
- // SHEEN
- if ( this.useSheen ) {
- const sheenNode = this.sheenNode ? vec3( this.sheenNode ) : materialSheen;
- const sheenRoughnessNode = this.sheenRoughnessNode ? float( this.sheenRoughnessNode ) : materialSheenRoughness;
- sheen.assign( sheenNode );
- sheenRoughness.assign( sheenRoughnessNode );
- }
- // IRIDESCENCE
- if ( this.useIridescence ) {
- const iridescenceNode = this.iridescenceNode ? float( this.iridescenceNode ) : materialIridescence;
- const iridescenceIORNode = this.iridescenceIORNode ? float( this.iridescenceIORNode ) : materialIridescenceIOR;
- const iridescenceThicknessNode = this.iridescenceThicknessNode ? float( this.iridescenceThicknessNode ) : materialIridescenceThickness;
- iridescence.assign( iridescenceNode );
- iridescenceIOR.assign( iridescenceIORNode );
- iridescenceThickness.assign( iridescenceThicknessNode );
- }
- // ANISOTROPY
- if ( this.useAnisotropy ) {
- const anisotropyV = ( this.anisotropyNode ? vec2( this.anisotropyNode ) : materialAnisotropy ).toVar();
- anisotropy.assign( anisotropyV.length() );
- If( anisotropy.equal( 0.0 ), () => {
- anisotropyV.assign( vec2( 1.0, 0.0 ) );
- } ).Else( () => {
- anisotropyV.divAssign( vec2( anisotropy ) );
- anisotropy.assign( anisotropy.saturate() );
- } );
- // Roughness along the anisotropy bitangent is the material roughness, while the tangent roughness increases with anisotropy.
- alphaT.assign( anisotropy.pow2().mix( roughness.pow2(), 1.0 ) );
- anisotropyT.assign( TBNViewMatrix[ 0 ].mul( anisotropyV.x ).add( TBNViewMatrix[ 1 ].mul( anisotropyV.y ) ) );
- anisotropyB.assign( TBNViewMatrix[ 1 ].mul( anisotropyV.x ).sub( TBNViewMatrix[ 0 ].mul( anisotropyV.y ) ) );
- }
- // TRANSMISSION
- if ( this.useTransmission ) {
- const transmissionNode = this.transmissionNode ? float( this.transmissionNode ) : materialTransmission;
- const thicknessNode = this.thicknessNode ? float( this.thicknessNode ) : materialThickness;
- const attenuationDistanceNode = this.attenuationDistanceNode ? float( this.attenuationDistanceNode ) : materialAttenuationDistance;
- const attenuationColorNode = this.attenuationColorNode ? vec3( this.attenuationColorNode ) : materialAttenuationColor;
- transmission.assign( transmissionNode );
- thickness.assign( thicknessNode );
- attenuationDistance.assign( attenuationDistanceNode );
- attenuationColor.assign( attenuationColorNode );
- if ( this.useDispersion ) {
- const dispersionNode = this.dispersionNode ? float( this.dispersionNode ) : materialDispersion;
- dispersion.assign( dispersionNode );
- }
- }
- }
- /**
- * Setups the clearcoat normal node.
- *
- * @return {Node<vec3>} The clearcoat normal.
- */
- setupClearcoatNormal() {
- return this.clearcoatNormalNode ? vec3( this.clearcoatNormalNode ) : materialClearcoatNormal;
- }
- setup( builder ) {
- builder.context.setupClearcoatNormal = () => subBuild( this.setupClearcoatNormal( builder ), 'NORMAL', 'vec3' );
- super.setup( builder );
- }
- copy( source ) {
- this.clearcoatNode = source.clearcoatNode;
- this.clearcoatRoughnessNode = source.clearcoatRoughnessNode;
- this.clearcoatNormalNode = source.clearcoatNormalNode;
- this.sheenNode = source.sheenNode;
- this.sheenRoughnessNode = source.sheenRoughnessNode;
- this.iridescenceNode = source.iridescenceNode;
- this.iridescenceIORNode = source.iridescenceIORNode;
- this.iridescenceThicknessNode = source.iridescenceThicknessNode;
- this.specularIntensityNode = source.specularIntensityNode;
- this.specularColorNode = source.specularColorNode;
- this.transmissionNode = source.transmissionNode;
- this.thicknessNode = source.thicknessNode;
- this.attenuationDistanceNode = source.attenuationDistanceNode;
- this.attenuationColorNode = source.attenuationColorNode;
- this.dispersionNode = source.dispersionNode;
- this.anisotropyNode = source.anisotropyNode;
- return super.copy( source );
- }
- }
- /**
- * Represents the lighting model for {@link MeshSSSNodeMaterial}.
- *
- * @augments PhysicalLightingModel
- */
- class SSSLightingModel extends PhysicalLightingModel {
- /**
- * Constructs a new physical lighting model.
- *
- * @param {boolean} [clearcoat=false] - Whether clearcoat is supported or not.
- * @param {boolean} [sheen=false] - Whether sheen is supported or not.
- * @param {boolean} [iridescence=false] - Whether iridescence is supported or not.
- * @param {boolean} [anisotropy=false] - Whether anisotropy is supported or not.
- * @param {boolean} [transmission=false] - Whether transmission is supported or not.
- * @param {boolean} [dispersion=false] - Whether dispersion is supported or not.
- * @param {boolean} [sss=false] - Whether SSS is supported or not.
- */
- constructor( clearcoat = false, sheen = false, iridescence = false, anisotropy = false, transmission = false, dispersion = false, sss = false ) {
- super( clearcoat, sheen, iridescence, anisotropy, transmission, dispersion );
- /**
- * Whether the lighting model should use SSS or not.
- *
- * @type {boolean}
- * @default false
- */
- this.useSSS = sss;
- }
- /**
- * Extends the default implementation with a SSS term.
- *
- * Reference: [Approximating Translucency for a Fast, Cheap and Convincing Subsurface Scattering Look](https://colinbarrebrisebois.com/2011/03/07/gdc-2011-approximating-translucency-for-a-fast-cheap-and-convincing-subsurface-scattering-look/)
- *
- * @param {Object} input - The input data.
- * @param {NodeBuilder} builder - The current node builder.
- */
- direct( { lightDirection, lightColor, reflectedLight }, builder ) {
- if ( this.useSSS === true ) {
- const material = builder.material;
- const { thicknessColorNode, thicknessDistortionNode, thicknessAmbientNode, thicknessAttenuationNode, thicknessPowerNode, thicknessScaleNode } = material;
- const scatteringHalf = lightDirection.add( normalView.mul( thicknessDistortionNode ) ).normalize();
- const scatteringDot = float( positionViewDirection.dot( scatteringHalf.negate() ).saturate().pow( thicknessPowerNode ).mul( thicknessScaleNode ) );
- const scatteringIllu = vec3( scatteringDot.add( thicknessAmbientNode ).mul( thicknessColorNode ) );
- reflectedLight.directDiffuse.addAssign( scatteringIllu.mul( thicknessAttenuationNode.mul( lightColor ) ) );
- }
- super.direct( { lightDirection, lightColor, reflectedLight }, builder );
- }
- }
- /**
- * This node material is an experimental extension of {@link MeshPhysicalNodeMaterial}
- * that implements a Subsurface scattering (SSS) term.
- *
- * @augments MeshPhysicalNodeMaterial
- */
- class MeshSSSNodeMaterial extends MeshPhysicalNodeMaterial {
- static get type() {
- return 'MeshSSSNodeMaterial';
- }
- /**
- * Constructs a new mesh SSS node material.
- *
- * @param {Object} [parameters] - The configuration parameter.
- */
- constructor( parameters ) {
- super( parameters );
- /**
- * Represents the thickness color.
- *
- * @type {?Node<vec3>}
- * @default null
- */
- this.thicknessColorNode = null;
- /**
- * Represents the distortion factor.
- *
- * @type {?Node<float>}
- */
- this.thicknessDistortionNode = float( 0.1 );
- /**
- * Represents the thickness ambient factor.
- *
- * @type {?Node<float>}
- */
- this.thicknessAmbientNode = float( 0.0 );
- /**
- * Represents the thickness attenuation.
- *
- * @type {?Node<float>}
- */
- this.thicknessAttenuationNode = float( .1 );
- /**
- * Represents the thickness power.
- *
- * @type {?Node<float>}
- */
- this.thicknessPowerNode = float( 2.0 );
- /**
- * Represents the thickness scale.
- *
- * @type {?Node<float>}
- */
- this.thicknessScaleNode = float( 10.0 );
- }
- /**
- * Whether the lighting model should use SSS or not.
- *
- * @type {boolean}
- * @default true
- */
- get useSSS() {
- return this.thicknessColorNode !== null;
- }
- /**
- * Setups the lighting model.
- *
- * @return {SSSLightingModel} The lighting model.
- */
- setupLightingModel( /*builder*/ ) {
- return new SSSLightingModel( this.useClearcoat, this.useSheen, this.useIridescence, this.useAnisotropy, this.useTransmission, this.useDispersion, this.useSSS );
- }
- copy( source ) {
- this.thicknessColorNode = source.thicknessColorNode;
- this.thicknessDistortionNode = source.thicknessDistortionNode;
- this.thicknessAmbientNode = source.thicknessAmbientNode;
- this.thicknessAttenuationNode = source.thicknessAttenuationNode;
- this.thicknessPowerNode = source.thicknessPowerNode;
- this.thicknessScaleNode = source.thicknessScaleNode;
- return super.copy( source );
- }
- }
- const getGradientIrradiance = /*@__PURE__*/ Fn( ( { normal, lightDirection, builder } ) => {
- // dotNL will be from -1.0 to 1.0
- const dotNL = normal.dot( lightDirection );
- const coord = vec2( dotNL.mul( 0.5 ).add( 0.5 ), 0.0 );
- if ( builder.material.gradientMap ) {
- const gradientMap = materialReference( 'gradientMap', 'texture' ).context( { getUV: () => coord } );
- return vec3( gradientMap.r );
- } else {
- const fw = coord.fwidth().mul( 0.5 );
- return mix( vec3( 0.7 ), vec3( 1.0 ), smoothstep( float( 0.7 ).sub( fw.x ), float( 0.7 ).add( fw.x ), coord.x ) );
- }
- } );
- /**
- * Represents the lighting model for a toon material. Used in {@link MeshToonNodeMaterial}.
- *
- * @augments LightingModel
- */
- class ToonLightingModel extends LightingModel {
- /**
- * Implements the direct lighting. Instead of using a conventional smooth irradiance, the irradiance is
- * reduced to a small number of discrete shades to create a comic-like, flat look.
- *
- * @param {Object} lightData - The light data.
- * @param {NodeBuilder} builder - The current node builder.
- */
- direct( { lightDirection, lightColor, reflectedLight }, builder ) {
- const irradiance = getGradientIrradiance( { normal: normalGeometry, lightDirection, builder } ).mul( lightColor );
- reflectedLight.directDiffuse.addAssign( irradiance.mul( BRDF_Lambert( { diffuseColor: diffuseColor.rgb } ) ) );
- }
- /**
- * Implements the indirect lighting.
- *
- * @param {NodeBuilder} builder - The current node builder.
- */
- indirect( builder ) {
- const { ambientOcclusion, irradiance, reflectedLight } = builder.context;
- reflectedLight.indirectDiffuse.addAssign( irradiance.mul( BRDF_Lambert( { diffuseColor } ) ) );
- reflectedLight.indirectDiffuse.mulAssign( ambientOcclusion );
- }
- }
- const _defaultValues$4 = /*@__PURE__*/ new MeshToonMaterial();
- /**
- * Node material version of {@link MeshToonMaterial}.
- *
- * @augments NodeMaterial
- */
- class MeshToonNodeMaterial extends NodeMaterial {
- static get type() {
- return 'MeshToonNodeMaterial';
- }
- /**
- * Constructs a new mesh toon node material.
- *
- * @param {Object} [parameters] - The configuration parameter.
- */
- constructor( parameters ) {
- super();
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isMeshToonNodeMaterial = true;
- /**
- * Set to `true` because toon materials react on lights.
- *
- * @type {boolean}
- * @default true
- */
- this.lights = true;
- this.setDefaultValues( _defaultValues$4 );
- this.setValues( parameters );
- }
- /**
- * Setups the lighting model.
- *
- * @return {ToonLightingModel} The lighting model.
- */
- setupLightingModel( /*builder*/ ) {
- return new ToonLightingModel();
- }
- }
- /**
- * TSL function for creating a matcap uv node.
- *
- * Can be used to compute texture coordinates for projecting a
- * matcap onto a mesh. Used by {@link MeshMatcapNodeMaterial}.
- *
- * @tsl
- * @function
- * @returns {Node<vec2>} The matcap UV coordinates.
- */
- const matcapUV = /*@__PURE__*/ Fn( () => {
- const x = vec3( positionViewDirection.z, 0, positionViewDirection.x.negate() ).normalize();
- const y = positionViewDirection.cross( x );
- return vec2( x.dot( normalView ), y.dot( normalView ) ).mul( 0.495 ).add( 0.5 ); // 0.495 to remove artifacts caused by undersized matcap disks
- } ).once( [ 'NORMAL', 'VERTEX' ] )().toVar( 'matcapUV' );
- const _defaultValues$3 = /*@__PURE__*/ new MeshMatcapMaterial();
- /**
- * Node material version of {@link MeshMatcapMaterial}.
- *
- * @augments NodeMaterial
- */
- class MeshMatcapNodeMaterial extends NodeMaterial {
- static get type() {
- return 'MeshMatcapNodeMaterial';
- }
- /**
- * Constructs a new mesh normal node material.
- *
- * @param {Object} [parameters] - The configuration parameter.
- */
- constructor( parameters ) {
- super();
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isMeshMatcapNodeMaterial = true;
- this.setDefaultValues( _defaultValues$3 );
- this.setValues( parameters );
- }
- /**
- * Setups the matcap specific node variables.
- *
- * @param {NodeBuilder} builder - The current node builder.
- */
- setupVariants( builder ) {
- const uv = matcapUV;
- let matcapColor;
- if ( builder.material.matcap ) {
- matcapColor = materialReference( 'matcap', 'texture' ).context( { getUV: () => uv } );
- } else {
- matcapColor = vec3( mix( 0.2, 0.8, uv.y ) ); // default if matcap is missing
- }
- diffuseColor.rgb.mulAssign( matcapColor.rgb );
- }
- }
- /**
- * Applies a rotation to the given position node.
- *
- * @augments TempNode
- */
- class RotateNode extends TempNode {
- static get type() {
- return 'RotateNode';
- }
- /**
- * Constructs a new rotate node.
- *
- * @param {Node} positionNode - The position node.
- * @param {Node} rotationNode - Represents the rotation that is applied to the position node. Depending
- * on whether the position data are 2D or 3D, the rotation is expressed a single float value or an Euler value.
- */
- constructor( positionNode, rotationNode ) {
- super();
- /**
- * The position node.
- *
- * @type {Node}
- */
- this.positionNode = positionNode;
- /**
- * Represents the rotation that is applied to the position node.
- * Depending on whether the position data are 2D or 3D, the rotation is expressed a single float value or an Euler value.
- *
- * @type {Node}
- */
- this.rotationNode = rotationNode;
- }
- /**
- * The type of the {@link RotateNode#positionNode} defines the node's type.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {string} The node's type.
- */
- getNodeType( builder ) {
- return this.positionNode.getNodeType( builder );
- }
- setup( builder ) {
- const { rotationNode, positionNode } = this;
- const nodeType = this.getNodeType( builder );
- if ( nodeType === 'vec2' ) {
- const cosAngle = rotationNode.cos();
- const sinAngle = rotationNode.sin();
- const rotationMatrix = mat2(
- cosAngle, sinAngle,
- sinAngle.negate(), cosAngle
- );
- return rotationMatrix.mul( positionNode );
- } else {
- const rotation = rotationNode;
- const rotationXMatrix = mat4( vec4( 1.0, 0.0, 0.0, 0.0 ), vec4( 0.0, cos( rotation.x ), sin( rotation.x ).negate(), 0.0 ), vec4( 0.0, sin( rotation.x ), cos( rotation.x ), 0.0 ), vec4( 0.0, 0.0, 0.0, 1.0 ) );
- const rotationYMatrix = mat4( vec4( cos( rotation.y ), 0.0, sin( rotation.y ), 0.0 ), vec4( 0.0, 1.0, 0.0, 0.0 ), vec4( sin( rotation.y ).negate(), 0.0, cos( rotation.y ), 0.0 ), vec4( 0.0, 0.0, 0.0, 1.0 ) );
- const rotationZMatrix = mat4( vec4( cos( rotation.z ), sin( rotation.z ).negate(), 0.0, 0.0 ), vec4( sin( rotation.z ), cos( rotation.z ), 0.0, 0.0 ), vec4( 0.0, 0.0, 1.0, 0.0 ), vec4( 0.0, 0.0, 0.0, 1.0 ) );
- return rotationXMatrix.mul( rotationYMatrix ).mul( rotationZMatrix ).mul( vec4( positionNode, 1.0 ) ).xyz;
- }
- }
- }
- /**
- * TSL function for creating a rotate node.
- *
- * @tsl
- * @function
- * @param {Node} positionNode - The position node.
- * @param {Node} rotationNode - Represents the rotation that is applied to the position node. Depending
- * on whether the position data are 2D or 3D, the rotation is expressed a single float value or an Euler value.
- * @returns {RotateNode}
- */
- const rotate = /*@__PURE__*/ nodeProxy( RotateNode ).setParameterLength( 2 );
- const _defaultValues$2 = /*@__PURE__*/ new SpriteMaterial();
- /**
- * Node material version of {@link SpriteMaterial}.
- *
- * @augments NodeMaterial
- */
- class SpriteNodeMaterial extends NodeMaterial {
- static get type() {
- return 'SpriteNodeMaterial';
- }
- /**
- * Constructs a new sprite node material.
- *
- * @param {Object} [parameters] - The configuration parameter.
- */
- constructor( parameters ) {
- super();
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isSpriteNodeMaterial = true;
- this._useSizeAttenuation = true;
- /**
- * This property makes it possible to define the position of the sprite with a
- * node. That can be useful when the material is used with instanced rendering
- * and node data are defined with an instanced attribute node:
- * ```js
- * const positionAttribute = new InstancedBufferAttribute( new Float32Array( positions ), 3 );
- * material.positionNode = instancedBufferAttribute( positionAttribute );
- * ```
- * Another possibility is to compute the instanced data with a compute shader:
- * ```js
- * const positionBuffer = instancedArray( particleCount, 'vec3' );
- * particleMaterial.positionNode = positionBuffer.toAttribute();
- * ```
- *
- * @type {?Node<vec2>}
- * @default null
- */
- this.positionNode = null;
- /**
- * The rotation of sprite materials is by default inferred from the `rotation`,
- * property. This node property allows to overwrite the default and define
- * the rotation with a node instead.
- *
- * If you don't want to overwrite the rotation but modify the existing
- * value instead, use {@link materialRotation}.
- *
- * @type {?Node<float>}
- * @default null
- */
- this.rotationNode = null;
- /**
- * This node property provides an additional way to scale sprites next to
- * `Object3D.scale`. The scale transformation based in `Object3D.scale`
- * is multiplied with the scale value of this node in the vertex shader.
- *
- * @type {?Node<vec2>}
- * @default null
- */
- this.scaleNode = null;
- /**
- * In Sprites, the transparent property is enabled by default.
- *
- * @type {boolean}
- * @default true
- */
- this.transparent = true;
- this.setDefaultValues( _defaultValues$2 );
- this.setValues( parameters );
- }
- /**
- * Setups the position node in view space. This method implements
- * the sprite specific vertex shader.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {Node<vec3>} The position in view space.
- */
- setupPositionView( builder ) {
- const { object, camera } = builder;
- const { positionNode, rotationNode, scaleNode, sizeAttenuation } = this;
- const mvPosition = modelViewMatrix.mul( vec3( positionNode || 0 ) );
- let scale = vec2( modelWorldMatrix[ 0 ].xyz.length(), modelWorldMatrix[ 1 ].xyz.length() );
- if ( scaleNode !== null ) {
- scale = scale.mul( vec2( scaleNode ) );
- }
- if ( camera.isPerspectiveCamera && sizeAttenuation === false ) {
- scale = scale.mul( mvPosition.z.negate() );
- }
- let alignedPosition = positionGeometry.xy;
- if ( object.center && object.center.isVector2 === true ) {
- const center = reference$1( 'center', 'vec2', object );
- alignedPosition = alignedPosition.sub( center.sub( 0.5 ) );
- }
- alignedPosition = alignedPosition.mul( scale );
- const rotation = float( rotationNode || materialRotation );
- const rotatedPosition = rotate( alignedPosition, rotation );
- return vec4( mvPosition.xy.add( rotatedPosition ), mvPosition.zw );
- }
- copy( source ) {
- this.positionNode = source.positionNode;
- this.rotationNode = source.rotationNode;
- this.scaleNode = source.scaleNode;
- return super.copy( source );
- }
- /**
- * Whether to use size attenuation or not.
- *
- * @type {boolean}
- * @default true
- */
- get sizeAttenuation() {
- return this._useSizeAttenuation;
- }
- set sizeAttenuation( value ) {
- if ( this._useSizeAttenuation !== value ) {
- this._useSizeAttenuation = value;
- this.needsUpdate = true;
- }
- }
- }
- const _defaultValues$1 = /*@__PURE__*/ new PointsMaterial();
- const _size$4 = /*@__PURE__*/ new Vector2();
- /**
- * Node material version of {@link PointsMaterial}.
- *
- * This material can be used in two ways:
- *
- * - By rendering point primitives with {@link Points}. Since WebGPU only supports point primitives
- * with a pixel size of `1`, it's not possible to define a size.
- *
- * ```js
- * const pointCloud = new THREE.Points( geometry, new THREE.PointsNodeMaterial() );
- * ```
- *
- * - By rendering point primitives with {@link Sprites}. In this case, size is honored,
- * see {@link PointsNodeMaterial#sizeNode}.
- *
- * ```js
- * const instancedPoints = new THREE.Sprite( new THREE.PointsNodeMaterial( { positionNode: instancedBufferAttribute( positionAttribute ) } ) );
- * ```
- *
- * @augments SpriteNodeMaterial
- */
- class PointsNodeMaterial extends SpriteNodeMaterial {
- static get type() {
- return 'PointsNodeMaterial';
- }
- /**
- * Constructs a new points node material.
- *
- * @param {Object} [parameters] - The configuration parameter.
- */
- constructor( parameters ) {
- super();
- /**
- * This node property provides an additional way to set the point size.
- *
- * Note that WebGPU only supports point primitives with 1 pixel size. Consequently,
- * this node has no effect when the material is used with {@link Points} and a WebGPU
- * backend. If an application wants to render points with a size larger than 1 pixel,
- * the material should be used with {@link Sprite} and instancing.
- *
- * @type {?Node<vec2>}
- * @default null
- */
- this.sizeNode = null;
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isPointsNodeMaterial = true;
- this.setDefaultValues( _defaultValues$1 );
- this.setValues( parameters );
- }
- setupPositionView() {
- const { positionNode } = this;
- return modelViewMatrix.mul( vec3( positionNode || positionLocal ) ).xyz;
- }
- setupVertexSprite( builder ) {
- const { material, camera } = builder;
- const { rotationNode, scaleNode, sizeNode, sizeAttenuation } = this;
- let mvp = super.setupVertex( builder );
- // skip further processing if the material is not a node material
- if ( material.isNodeMaterial !== true ) {
- return mvp;
- }
- // point size
- let pointSize = sizeNode !== null ? vec2( sizeNode ) : materialPointSize;
- pointSize = pointSize.mul( screenDPR );
- // size attenuation
- if ( camera.isPerspectiveCamera && sizeAttenuation === true ) {
- // follow WebGLRenderer's implementation, and scale by half the canvas height in logical units
- pointSize = pointSize.mul( scale.div( positionView.z.negate() ) );
- }
- // scale
- if ( scaleNode && scaleNode.isNode ) {
- pointSize = pointSize.mul( vec2( scaleNode ) );
- }
- // compute offset
- let offset = positionGeometry.xy;
- // apply rotation
- if ( rotationNode && rotationNode.isNode ) {
- const rotation = float( rotationNode );
- offset = rotate( offset, rotation );
- }
- // account for point size
- offset = offset.mul( pointSize );
- // scale by viewport size
- offset = offset.div( viewportSize.div( 2 ) );
- // compensate for the perspective divide
- offset = offset.mul( mvp.w );
- // add offset
- mvp = mvp.add( vec4( offset, 0, 0 ) );
- return mvp;
- }
- setupVertex( builder ) {
- if ( builder.object.isPoints ) {
- return super.setupVertex( builder );
- } else {
- return this.setupVertexSprite( builder );
- }
- }
- /**
- * Whether alpha to coverage should be used or not.
- *
- * @type {boolean}
- * @default true
- */
- get alphaToCoverage() {
- return this._useAlphaToCoverage;
- }
- set alphaToCoverage( value ) {
- if ( this._useAlphaToCoverage !== value ) {
- this._useAlphaToCoverage = value;
- this.needsUpdate = true;
- }
- }
- }
- const scale = /*@__PURE__*/ uniform( 1 ).onFrameUpdate( function ( { renderer } ) {
- const size = renderer.getSize( _size$4 ); // logical units
- this.value = 0.5 * size.y;
- } );
- /**
- * Represents lighting model for a shadow material. Used in {@link ShadowNodeMaterial}.
- *
- * @augments LightingModel
- */
- class ShadowMaskModel extends LightingModel {
- /**
- * Constructs a new shadow mask model.
- */
- constructor() {
- super();
- /**
- * The shadow mask node.
- *
- * @type {Node}
- */
- this.shadowNode = float( 1 ).toVar( 'shadowMask' );
- }
- /**
- * Only used to save the shadow mask.
- *
- * @param {Object} input - The input data.
- */
- direct( { lightNode } ) {
- if ( lightNode.shadowNode !== null ) {
- this.shadowNode.mulAssign( lightNode.shadowNode );
- }
- }
- /**
- * Uses the shadow mask to produce the final color.
- *
- * @param {NodeBuilder} builder - The current node builder.
- */
- finish( { context } ) {
- diffuseColor.a.mulAssign( this.shadowNode.oneMinus() );
- context.outgoingLight.rgb.assign( diffuseColor.rgb ); // TODO: Optimize LightsNode to avoid this assignment
- }
- }
- const _defaultValues = /*@__PURE__*/ new ShadowMaterial();
- /**
- * Node material version of {@link ShadowMaterial}.
- *
- * @augments NodeMaterial
- */
- class ShadowNodeMaterial extends NodeMaterial {
- static get type() {
- return 'ShadowNodeMaterial';
- }
- /**
- * Constructs a new shadow node material.
- *
- * @param {Object} [parameters] - The configuration parameter.
- */
- constructor( parameters ) {
- super();
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isShadowNodeMaterial = true;
- /**
- * Set to `true` because so it's possible to implement
- * the shadow mask effect.
- *
- * @type {boolean}
- * @default true
- */
- this.lights = true;
- /**
- * Overwritten since shadow materials are transparent
- * by default.
- *
- * @type {boolean}
- * @default true
- */
- this.transparent = true;
- this.setDefaultValues( _defaultValues );
- this.setValues( parameters );
- }
- /**
- * Setups the lighting model.
- *
- * @return {ShadowMaskModel} The lighting model.
- */
- setupLightingModel( /*builder*/ ) {
- return new ShadowMaskModel();
- }
- }
- const scatteringDensity = property( 'vec3' );
- const linearDepthRay = property( 'vec3' );
- const outgoingRayLight = property( 'vec3' );
- /**
- * VolumetricLightingModel class extends the LightingModel to implement volumetric lighting effects.
- * This model calculates the scattering and transmittance of light through a volumetric medium.
- * It dynamically adjusts the direction of the ray based on the camera and object positions.
- * The model supports custom scattering and depth nodes to enhance the lighting effects.
- *
- * @augments LightingModel
- */
- class VolumetricLightingModel extends LightingModel {
- constructor() {
- super();
- }
- start( builder ) {
- const { material } = builder;
- const startPos = property( 'vec3' );
- const endPos = property( 'vec3' );
- // This approach dynamically changes the direction of the ray,
- // prioritizing the ray from the camera to the object if it is inside the mesh, and from the object to the camera if it is far away.
- If( cameraPosition.sub( positionWorld ).length().greaterThan( modelRadius.mul( 2 ) ), () => {
- startPos.assign( cameraPosition );
- endPos.assign( positionWorld );
- } ).Else( () => {
- startPos.assign( positionWorld );
- endPos.assign( cameraPosition );
- } );
- //
- const viewVector = endPos.sub( startPos );
- const steps = uniform( 'int' ).onRenderUpdate( ( { material } ) => material.steps );
- const stepSize = viewVector.length().div( steps ).toVar();
- const rayDir = viewVector.normalize().toVar(); // TODO: toVar() should be automatic here ( in loop )
- const distTravelled = float( 0.0 ).toVar();
- const transmittance = vec3( 1 ).toVar();
- if ( material.offsetNode ) {
- // reduce banding
- distTravelled.addAssign( material.offsetNode.mul( stepSize ) );
- }
- Loop( steps, () => {
- const positionRay = startPos.add( rayDir.mul( distTravelled ) );
- const positionViewRay = cameraViewMatrix.mul( vec4( positionRay, 1 ) ).xyz;
- if ( material.depthNode !== null ) {
- linearDepthRay.assign( linearDepth( viewZToPerspectiveDepth( positionViewRay.z, cameraNear, cameraFar ) ) );
- builder.context.sceneDepthNode = linearDepth( material.depthNode ).toVar();
- }
- builder.context.positionWorld = positionRay;
- builder.context.shadowPositionWorld = positionRay;
- builder.context.positionView = positionViewRay;
- scatteringDensity.assign( 0 );
- let scatteringNode;
- if ( material.scatteringNode ) {
- scatteringNode = material.scatteringNode( {
- positionRay
- } );
- }
- super.start( builder );
- if ( scatteringNode ) {
- scatteringDensity.mulAssign( scatteringNode );
- }
- // beer's law
- const falloff = scatteringDensity.mul( .01 ).negate().mul( stepSize ).exp();
- transmittance.mulAssign( falloff );
- // move along the ray
- distTravelled.addAssign( stepSize );
- } );
- outgoingRayLight.addAssign( transmittance.saturate().oneMinus() );
- }
- scatteringLight( lightColor, builder ) {
- const sceneDepthNode = builder.context.sceneDepthNode;
- if ( sceneDepthNode ) {
- If( sceneDepthNode.greaterThanEqual( linearDepthRay ), () => {
- scatteringDensity.addAssign( lightColor );
- } );
- } else {
- scatteringDensity.addAssign( lightColor );
- }
- }
- direct( { lightNode, lightColor }, builder ) {
- // Ignore lights with infinite distance
- if ( lightNode.light.distance === undefined ) return;
- // TODO: We need a viewportOpaque*() ( output, depth ) to fit with modern rendering approaches
- const directLight = lightColor.xyz.toVar();
- directLight.mulAssign( lightNode.shadowNode ); // it no should be necessary if used in the same render pass
- this.scatteringLight( directLight, builder );
- }
- directRectArea( { lightColor, lightPosition, halfWidth, halfHeight }, builder ) {
- const p0 = lightPosition.add( halfWidth ).sub( halfHeight ); // counterclockwise; light shines in local neg z direction
- const p1 = lightPosition.sub( halfWidth ).sub( halfHeight );
- const p2 = lightPosition.sub( halfWidth ).add( halfHeight );
- const p3 = lightPosition.add( halfWidth ).add( halfHeight );
- const P = builder.context.positionView;
- const directLight = lightColor.xyz.mul( LTC_Evaluate_Volume( { P, p0, p1, p2, p3 } ) ).pow( 1.5 );
- this.scatteringLight( directLight, builder );
- }
- finish( builder ) {
- builder.context.outgoingLight.assign( outgoingRayLight );
- }
- }
- /**
- * Volume node material.
- *
- * @augments NodeMaterial
- */
- class VolumeNodeMaterial extends NodeMaterial {
- static get type() {
- return 'VolumeNodeMaterial';
- }
- /**
- * Constructs a new volume node material.
- *
- * @param {Object} [parameters] - The configuration parameter.
- */
- constructor( parameters ) {
- super();
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isVolumeNodeMaterial = true;
- /**
- * Number of steps used for raymarching.
- *
- * @type {number}
- * @default 25
- */
- this.steps = 25;
- /**
- * Offsets the distance a ray has been traveled through a volume.
- * Can be used to implement dithering to reduce banding.
- *
- * @type {Node<float>}
- * @default null
- */
- this.offsetNode = null;
- /**
- * Node used for scattering calculations.
- *
- * @type {Function|FunctionNode<vec4>}
- * @default null
- */
- this.scatteringNode = null;
- this.lights = true;
- this.transparent = true;
- this.side = BackSide;
- this.depthTest = false;
- this.depthWrite = false;
- this.setValues( parameters );
- }
- setupLightingModel() {
- return new VolumetricLightingModel();
- }
- }
- /**
- * This module manages the internal animation loop of the renderer.
- *
- * @private
- */
- class Animation {
- /**
- * Constructs a new animation loop management component.
- *
- * @param {Renderer} renderer - A reference to the main renderer.
- * @param {Nodes} nodes - Renderer component for managing nodes related logic.
- * @param {Info} info - Renderer component for managing metrics and monitoring data.
- */
- constructor( renderer, nodes, info ) {
- /**
- * A reference to the main renderer.
- *
- * @type {Renderer}
- */
- this.renderer = renderer;
- /**
- * Renderer component for managing nodes related logic.
- *
- * @type {Nodes}
- */
- this.nodes = nodes;
- /**
- * Renderer component for managing metrics and monitoring data.
- *
- * @type {Info}
- */
- this.info = info;
- /**
- * A reference to the context from `requestAnimationFrame()` can
- * be called (usually `window`).
- *
- * @type {?(Window|XRSession)}
- */
- this._context = typeof self !== 'undefined' ? self : null;
- /**
- * The user-defined animation loop.
- *
- * @type {?Function}
- * @default null
- */
- this._animationLoop = null;
- /**
- * The requestId which is returned from the `requestAnimationFrame()` call.
- * Can be used to cancel the stop the animation loop.
- *
- * @type {?number}
- * @default null
- */
- this._requestId = null;
- }
- /**
- * Starts the internal animation loop.
- */
- start() {
- const update = ( time, xrFrame ) => {
- this._requestId = this._context.requestAnimationFrame( update );
- if ( this.info.autoReset === true ) this.info.reset();
- this.nodes.nodeFrame.update();
- this.info.frame = this.nodes.nodeFrame.frameId;
- this.renderer._inspector.begin();
- if ( this._animationLoop !== null ) this._animationLoop( time, xrFrame );
- this.renderer._inspector.finish();
- };
- update();
- }
- /**
- * Stops the internal animation loop.
- */
- stop() {
- this._context.cancelAnimationFrame( this._requestId );
- this._requestId = null;
- }
- /**
- * Returns the user-level animation loop.
- *
- * @return {?Function} The animation loop.
- */
- getAnimationLoop() {
- return this._animationLoop;
- }
- /**
- * Defines the user-level animation loop.
- *
- * @param {?Function} callback - The animation loop.
- */
- setAnimationLoop( callback ) {
- this._animationLoop = callback;
- }
- /**
- * Returns the animation context.
- *
- * @return {Window|XRSession} The animation context.
- */
- getContext() {
- return this._context;
- }
- /**
- * Defines the context in which `requestAnimationFrame()` is executed.
- *
- * @param {Window|XRSession} context - The context to set.
- */
- setContext( context ) {
- this._context = context;
- }
- /**
- * Frees all internal resources and stops the animation loop.
- */
- dispose() {
- this.stop();
- }
- }
- /**
- * Data structure for the renderer. It allows defining values
- * with chained, hierarchical keys. Keys are meant to be
- * objects since the module internally works with Weak Maps
- * for performance reasons.
- *
- * @private
- */
- class ChainMap {
- /**
- * Constructs a new Chain Map.
- */
- constructor() {
- /**
- * A map of Weak Maps by their key length.
- *
- * @type {Object<number, WeakMap>}
- */
- this.weakMaps = {};
- }
- /**
- * Returns the Weak Map for the given keys.
- *
- * @param {Array<Object>} keys - List of keys.
- * @return {WeakMap} The weak map.
- */
- _getWeakMap( keys ) {
- const length = keys.length;
- let weakMap = this.weakMaps[ length ];
- if ( weakMap === undefined ) {
- weakMap = new WeakMap();
- this.weakMaps[ length ] = weakMap;
- }
- return weakMap;
- }
- /**
- * Returns the value for the given array of keys.
- *
- * @param {Array<Object>} keys - List of keys.
- * @return {any} The value. Returns `undefined` if no value was found.
- */
- get( keys ) {
- let map = this._getWeakMap( keys );
- for ( let i = 0; i < keys.length - 1; i ++ ) {
- map = map.get( keys[ i ] );
- if ( map === undefined ) return undefined;
- }
- return map.get( keys[ keys.length - 1 ] );
- }
- /**
- * Sets the value for the given keys.
- *
- * @param {Array<Object>} keys - List of keys.
- * @param {any} value - The value to set.
- * @return {ChainMap} A reference to this Chain Map.
- */
- set( keys, value ) {
- let map = this._getWeakMap( keys );
- for ( let i = 0; i < keys.length - 1; i ++ ) {
- const key = keys[ i ];
- if ( map.has( key ) === false ) map.set( key, new WeakMap() );
- map = map.get( key );
- }
- map.set( keys[ keys.length - 1 ], value );
- return this;
- }
- /**
- * Deletes a value for the given keys.
- *
- * @param {Array<Object>} keys - The keys.
- * @return {boolean} Returns `true` if the value has been removed successfully and `false` if the value has not be found.
- */
- delete( keys ) {
- let map = this._getWeakMap( keys );
- for ( let i = 0; i < keys.length - 1; i ++ ) {
- map = map.get( keys[ i ] );
- if ( map === undefined ) return false;
- }
- return map.delete( keys[ keys.length - 1 ] );
- }
- }
- let _id$a = 0;
- function getKeys( obj ) {
- const keys = Object.keys( obj );
- let proto = Object.getPrototypeOf( obj );
- while ( proto ) {
- const descriptors = Object.getOwnPropertyDescriptors( proto );
- for ( const key in descriptors ) {
- if ( descriptors[ key ] !== undefined ) {
- const descriptor = descriptors[ key ];
- if ( descriptor && typeof descriptor.get === 'function' ) {
- keys.push( key );
- }
- }
- }
- proto = Object.getPrototypeOf( proto );
- }
- return keys;
- }
- /**
- * A render object is the renderer's representation of single entity that gets drawn
- * with a draw command. There is no unique mapping of render objects to 3D objects in the
- * scene since render objects also depend from the used material, the current render context
- * and the current scene's lighting.
- *
- * In general, the basic process of the renderer is:
- *
- * - Analyze the 3D objects in the scene and generate render lists containing render items.
- * - Process the render lists by calling one or more render commands for each render item.
- * - For each render command, request a render object and perform the draw.
- *
- * The module provides an interface to get data required for the draw command like the actual
- * draw parameters or vertex buffers. It also holds a series of caching related methods since
- * creating render objects should only be done when necessary.
- *
- * @private
- */
- class RenderObject {
- /**
- * Constructs a new render object.
- *
- * @param {Nodes} nodes - Renderer component for managing nodes related logic.
- * @param {Geometries} geometries - Renderer component for managing geometries.
- * @param {Renderer} renderer - The renderer.
- * @param {Object3D} object - The 3D object.
- * @param {Material} material - The 3D object's material.
- * @param {Scene} scene - The scene the 3D object belongs to.
- * @param {Camera} camera - The camera the object should be rendered with.
- * @param {LightsNode} lightsNode - The lights node.
- * @param {RenderContext} renderContext - The render context.
- * @param {ClippingContext} clippingContext - The clipping context.
- */
- constructor( nodes, geometries, renderer, object, material, scene, camera, lightsNode, renderContext, clippingContext ) {
- this.id = _id$a ++;
- /**
- * Renderer component for managing nodes related logic.
- *
- * @type {Nodes}
- * @private
- */
- this._nodes = nodes;
- /**
- * Renderer component for managing geometries.
- *
- * @type {Geometries}
- * @private
- */
- this._geometries = geometries;
- /**
- * The renderer.
- *
- * @type {Renderer}
- */
- this.renderer = renderer;
- /**
- * The 3D object.
- *
- * @type {Object3D}
- */
- this.object = object;
- /**
- * The 3D object's material.
- *
- * @type {Material}
- */
- this.material = material;
- /**
- * The scene the 3D object belongs to.
- *
- * @type {Scene}
- */
- this.scene = scene;
- /**
- * The camera the 3D object should be rendered with.
- *
- * @type {Camera}
- */
- this.camera = camera;
- /**
- * The lights node.
- *
- * @type {LightsNode}
- */
- this.lightsNode = lightsNode;
- /**
- * The render context.
- *
- * @type {RenderContext}
- */
- this.context = renderContext;
- /**
- * The 3D object's geometry.
- *
- * @type {BufferGeometry}
- */
- this.geometry = object.geometry;
- /**
- * The render object's version.
- *
- * @type {number}
- */
- this.version = material.version;
- /**
- * The draw range of the geometry.
- *
- * @type {?Object}
- * @default null
- */
- this.drawRange = null;
- /**
- * An array holding the buffer attributes
- * of the render object. This entails attribute
- * definitions on geometry and node level.
- *
- * @type {?Array<BufferAttribute>}
- * @default null
- */
- this.attributes = null;
- /**
- * An object holding the version of the
- * attributes. The keys are the attribute names
- * and the values are the attribute versions.
- *
- * @type {?Object<string, number>}
- * @default null
- */
- this.attributesId = null;
- /**
- * A reference to a render pipeline the render
- * object is processed with.
- *
- * @type {RenderPipeline}
- * @default null
- */
- this.pipeline = null;
- /**
- * Only relevant for objects using
- * multiple materials. This represents a group entry
- * from the respective `BufferGeometry`.
- *
- * @type {?{start: number, count: number}}
- * @default null
- */
- this.group = null;
- /**
- * An array holding the vertex buffers which can
- * be buffer attributes but also interleaved buffers.
- *
- * @type {?Array<BufferAttribute|InterleavedBuffer>}
- * @default null
- */
- this.vertexBuffers = null;
- /**
- * The parameters for the draw command.
- *
- * @type {?Object}
- * @default null
- */
- this.drawParams = null;
- /**
- * If this render object is used inside a render bundle,
- * this property points to the respective bundle group.
- *
- * @type {?BundleGroup}
- * @default null
- */
- this.bundle = null;
- /**
- * The clipping context.
- *
- * @type {ClippingContext}
- */
- this.clippingContext = clippingContext;
- /**
- * The clipping context's cache key.
- *
- * @type {string}
- */
- this.clippingContextCacheKey = clippingContext !== null ? clippingContext.cacheKey : '';
- /**
- * The initial node cache key.
- *
- * @type {number}
- */
- this.initialNodesCacheKey = this.getDynamicCacheKey();
- /**
- * The initial cache key.
- *
- * @type {number}
- */
- this.initialCacheKey = this.getCacheKey();
- /**
- * The node builder state.
- *
- * @type {?NodeBuilderState}
- * @private
- * @default null
- */
- this._nodeBuilderState = null;
- /**
- * An array of bindings.
- *
- * @type {?Array<BindGroup>}
- * @private
- * @default null
- */
- this._bindings = null;
- /**
- * Reference to the node material observer.
- *
- * @type {?NodeMaterialObserver}
- * @private
- * @default null
- */
- this._monitor = null;
- /**
- * An event listener which is defined by `RenderObjects`. It performs
- * clean up tasks when `dispose()` on this render object.
- *
- * @method
- */
- this.onDispose = null;
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isRenderObject = true;
- /**
- * An event listener which is executed when `dispose()` is called on
- * the material of this render object.
- *
- * @method
- */
- this.onMaterialDispose = () => {
- this.dispose();
- };
- /**
- * An event listener which is executed when `dispose()` is called on
- * the geometry of this render object.
- *
- * @method
- */
- this.onGeometryDispose = () => {
- // clear geometry cache attributes
- this.attributes = null;
- this.attributesId = null;
- };
- this.material.addEventListener( 'dispose', this.onMaterialDispose );
- this.geometry.addEventListener( 'dispose', this.onGeometryDispose );
- }
- /**
- * Updates the clipping context.
- *
- * @param {ClippingContext} context - The clipping context to set.
- */
- updateClipping( context ) {
- this.clippingContext = context;
- }
- /**
- * Whether the clipping requires an update or not.
- *
- * @type {boolean}
- * @readonly
- */
- get clippingNeedsUpdate() {
- if ( this.clippingContext === null || this.clippingContext.cacheKey === this.clippingContextCacheKey ) return false;
- this.clippingContextCacheKey = this.clippingContext.cacheKey;
- return true;
- }
- /**
- * The number of clipping planes defined in context of hardware clipping.
- *
- * @type {number}
- * @readonly
- */
- get hardwareClippingPlanes() {
- return this.material.hardwareClipping === true ? this.clippingContext.unionClippingCount : 0;
- }
- /**
- * Returns the node builder state of this render object.
- *
- * @return {NodeBuilderState} The node builder state.
- */
- getNodeBuilderState() {
- return this._nodeBuilderState || ( this._nodeBuilderState = this._nodes.getForRender( this ) );
- }
- /**
- * Returns the node material observer of this render object.
- *
- * @return {NodeMaterialObserver} The node material observer.
- */
- getMonitor() {
- return this._monitor || ( this._monitor = this.getNodeBuilderState().observer );
- }
- /**
- * Returns an array of bind groups of this render object.
- *
- * @return {Array<BindGroup>} The bindings.
- */
- getBindings() {
- return this._bindings || ( this._bindings = this.getNodeBuilderState().createBindings() );
- }
- /**
- * Returns a binding group by group name of this render object.
- *
- * @param {string} name - The name of the binding group.
- * @return {?BindGroup} The bindings.
- */
- getBindingGroup( name ) {
- for ( const bindingGroup of this.getBindings() ) {
- if ( bindingGroup.name === name ) {
- return bindingGroup;
- }
- }
- }
- /**
- * Returns the index of the render object's geometry.
- *
- * @return {?BufferAttribute} The index. Returns `null` for non-indexed geometries.
- */
- getIndex() {
- return this._geometries.getIndex( this );
- }
- /**
- * Returns the indirect buffer attribute.
- *
- * @return {?BufferAttribute} The indirect attribute. `null` if no indirect drawing is used.
- */
- getIndirect() {
- return this._geometries.getIndirect( this );
- }
- /**
- * Returns the byte offset into the indirect attribute buffer.
- *
- * @return {number|Array<number>} The byte offset into the indirect attribute buffer.
- */
- getIndirectOffset() {
- return this._geometries.getIndirectOffset( this );
- }
- /**
- * Returns an array that acts as a key for identifying the render object in a chain map.
- *
- * @return {Array<Object>} An array with object references.
- */
- getChainArray() {
- return [ this.object, this.material, this.context, this.lightsNode ];
- }
- /**
- * This method is used when the geometry of a 3D object has been exchanged and the
- * respective render object now requires an update.
- *
- * @param {BufferGeometry} geometry - The geometry to set.
- */
- setGeometry( geometry ) {
- this.geometry = geometry;
- this.attributes = null;
- this.attributesId = null;
- }
- /**
- * Returns the buffer attributes of the render object. The returned array holds
- * attribute definitions on geometry and node level.
- *
- * @return {Array<BufferAttribute>} An array with buffer attributes.
- */
- getAttributes() {
- if ( this.attributes !== null ) return this.attributes;
- const nodeAttributes = this.getNodeBuilderState().nodeAttributes;
- const geometry = this.geometry;
- const attributes = [];
- const vertexBuffers = new Set();
- const attributesId = {};
- for ( const nodeAttribute of nodeAttributes ) {
- let attribute;
- if ( nodeAttribute.node && nodeAttribute.node.attribute ) {
- // node attribute
- attribute = nodeAttribute.node.attribute;
- } else {
- // geometry attribute
- attribute = geometry.getAttribute( nodeAttribute.name );
- attributesId[ nodeAttribute.name ] = attribute.version;
- }
- if ( attribute === undefined ) continue;
- attributes.push( attribute );
- const bufferAttribute = attribute.isInterleavedBufferAttribute ? attribute.data : attribute;
- vertexBuffers.add( bufferAttribute );
- }
- this.attributes = attributes;
- this.attributesId = attributesId;
- this.vertexBuffers = Array.from( vertexBuffers.values() );
- return attributes;
- }
- /**
- * Returns the vertex buffers of the render object.
- *
- * @return {Array<BufferAttribute|InterleavedBuffer>} An array with buffer attribute or interleaved buffers.
- */
- getVertexBuffers() {
- if ( this.vertexBuffers === null ) this.getAttributes();
- return this.vertexBuffers;
- }
- /**
- * Returns the draw parameters for the render object.
- *
- * @return {?{vertexCount: number, firstVertex: number, instanceCount: number, firstInstance: number}} The draw parameters.
- */
- getDrawParameters() {
- const { object, material, geometry, group, drawRange } = this;
- const drawParams = this.drawParams || ( this.drawParams = {
- vertexCount: 0,
- firstVertex: 0,
- instanceCount: 0,
- firstInstance: 0
- } );
- const index = this.getIndex();
- const hasIndex = ( index !== null );
- let instanceCount = 1;
- if ( geometry.isInstancedBufferGeometry === true ) {
- instanceCount = geometry.instanceCount;
- } else if ( object.count !== undefined ) {
- instanceCount = Math.max( 0, object.count );
- }
- if ( instanceCount === 0 ) return null;
- drawParams.instanceCount = instanceCount;
- if ( object.isBatchedMesh === true ) return drawParams;
- let rangeFactor = 1;
- if ( material.wireframe === true && ! object.isPoints && ! object.isLineSegments && ! object.isLine && ! object.isLineLoop ) {
- rangeFactor = 2;
- }
- let firstVertex = drawRange.start * rangeFactor;
- let lastVertex = ( drawRange.start + drawRange.count ) * rangeFactor;
- if ( group !== null ) {
- firstVertex = Math.max( firstVertex, group.start * rangeFactor );
- lastVertex = Math.min( lastVertex, ( group.start + group.count ) * rangeFactor );
- }
- const position = geometry.attributes.position;
- let itemCount = Infinity;
- if ( hasIndex ) {
- itemCount = index.count;
- } else if ( position !== undefined && position !== null ) {
- itemCount = position.count;
- }
- firstVertex = Math.max( firstVertex, 0 );
- lastVertex = Math.min( lastVertex, itemCount );
- const count = lastVertex - firstVertex;
- if ( count < 0 || count === Infinity ) return null;
- drawParams.vertexCount = count;
- drawParams.firstVertex = firstVertex;
- return drawParams;
- }
- /**
- * Returns the render object's geometry cache key.
- *
- * The geometry cache key is part of the material cache key.
- *
- * @return {string} The geometry cache key.
- */
- getGeometryCacheKey() {
- const { geometry } = this;
- let cacheKey = '';
- for ( const name of Object.keys( geometry.attributes ).sort() ) {
- const attribute = geometry.attributes[ name ];
- cacheKey += name + ',';
- if ( attribute.data ) cacheKey += attribute.data.stride + ',';
- if ( attribute.offset ) cacheKey += attribute.offset + ',';
- if ( attribute.itemSize ) cacheKey += attribute.itemSize + ',';
- if ( attribute.normalized ) cacheKey += 'n,';
- }
- // structural equality isn't sufficient for morph targets since the
- // data are maintained in textures. only if the targets are all equal
- // the texture and thus the instance of `MorphNode` can be shared.
- for ( const name of Object.keys( geometry.morphAttributes ).sort() ) {
- const targets = geometry.morphAttributes[ name ];
- cacheKey += 'morph-' + name + ',';
- for ( let i = 0, l = targets.length; i < l; i ++ ) {
- const attribute = targets[ i ];
- cacheKey += attribute.id + ',';
- }
- }
- if ( geometry.index ) {
- cacheKey += 'index,';
- }
- return cacheKey;
- }
- /**
- * Returns the render object's material cache key.
- *
- * The material cache key is part of the render object cache key.
- *
- * @return {number} The material cache key.
- */
- getMaterialCacheKey() {
- const { object, material, renderer } = this;
- let cacheKey = material.customProgramCacheKey();
- for ( const property of getKeys( material ) ) {
- if ( /^(is[A-Z]|_)|^(visible|version|uuid|name|opacity|userData)$/.test( property ) ) continue;
- const value = material[ property ];
- let valueKey;
- if ( value !== null ) {
- // some material values require a formatting
- const type = typeof value;
- if ( type === 'number' ) {
- valueKey = value !== 0 ? '1' : '0'; // Convert to on/off, important for clearcoat, transmission, etc
- } else if ( type === 'object' ) {
- valueKey = '{';
- if ( value.isTexture ) {
- valueKey += value.mapping;
- // WebGPU must honor the sampler data because they are part of the bindings
- if ( renderer.backend.isWebGPUBackend === true ) {
- valueKey += value.magFilter;
- valueKey += value.minFilter;
- valueKey += value.wrapS;
- valueKey += value.wrapT;
- valueKey += value.wrapR;
- }
- }
- valueKey += '}';
- } else {
- valueKey = String( value );
- }
- } else {
- valueKey = String( value );
- }
- cacheKey += /*property + ':' +*/ valueKey + ',';
- }
- cacheKey += this.clippingContextCacheKey + ',';
- if ( object.geometry ) {
- cacheKey += this.getGeometryCacheKey();
- }
- if ( object.skeleton ) {
- cacheKey += object.skeleton.bones.length + ',';
- }
- if ( object.isBatchedMesh ) {
- cacheKey += object._matricesTexture.uuid + ',';
- if ( object._colorsTexture !== null ) {
- cacheKey += object._colorsTexture.uuid + ',';
- }
- }
- if ( object.isInstancedMesh || object.count > 1 || Array.isArray( object.morphTargetInfluences ) ) {
- // TODO: https://github.com/mrdoob/three.js/pull/29066#issuecomment-2269400850
- cacheKey += object.uuid + ',';
- }
- cacheKey += this.context.id + ',';
- cacheKey += object.receiveShadow + ',';
- return hashString( cacheKey );
- }
- /**
- * Whether the geometry requires an update or not.
- *
- * @type {boolean}
- * @readonly
- */
- get needsGeometryUpdate() {
- if ( this.geometry.id !== this.object.geometry.id ) return true;
- if ( this.attributes !== null ) {
- const attributesId = this.attributesId;
- for ( const name in attributesId ) {
- const attribute = this.geometry.getAttribute( name );
- if ( attribute === undefined || attributesId[ name ] !== attribute.id ) {
- return true;
- }
- }
- }
- return false;
- }
- /**
- * Whether the render object requires an update or not.
- *
- * Note: There are two distinct places where render objects are checked for an update.
- *
- * 1. In `RenderObjects.get()` which is executed when the render object is request. This
- * method checks the `needsUpdate` flag and recreates the render object if necessary.
- * 2. In `Renderer._renderObjectDirect()` right after getting the render object via
- * `RenderObjects.get()`. The render object's NodeMaterialObserver is then used to detect
- * a need for a refresh due to material, geometry or object related value changes.
- *
- * TODO: Investigate if it's possible to merge both steps so there is only a single place
- * that performs the 'needsUpdate' check.
- *
- * @type {boolean}
- * @readonly
- */
- get needsUpdate() {
- return /*this.object.static !== true &&*/ ( this.initialNodesCacheKey !== this.getDynamicCacheKey() || this.clippingNeedsUpdate );
- }
- /**
- * Returns the dynamic cache key which represents a key that is computed per draw command.
- *
- * @return {number} The cache key.
- */
- getDynamicCacheKey() {
- let cacheKey = 0;
- // `Nodes.getCacheKey()` returns an environment cache key which is not relevant when
- // the renderer is inside a shadow pass.
- if ( this.material.isShadowPassMaterial !== true ) {
- cacheKey = this._nodes.getCacheKey( this.scene, this.lightsNode );
- }
- if ( this.camera.isArrayCamera ) {
- cacheKey = hash$1( cacheKey, this.camera.cameras.length );
- }
- if ( this.object.receiveShadow ) {
- cacheKey = hash$1( cacheKey, 1 );
- }
- cacheKey = hash$1( cacheKey, this.camera.id, this.renderer.contextNode.id, this.renderer.contextNode.version );
- return cacheKey;
- }
- /**
- * Returns the render object's cache key.
- *
- * @return {number} The cache key.
- */
- getCacheKey() {
- return this.getMaterialCacheKey() + this.getDynamicCacheKey();
- }
- /**
- * Frees internal resources.
- */
- dispose() {
- this.material.removeEventListener( 'dispose', this.onMaterialDispose );
- this.geometry.removeEventListener( 'dispose', this.onGeometryDispose );
- this.onDispose();
- }
- }
- const _chainKeys$5 = [];
- /**
- * This module manages the render objects of the renderer.
- *
- * @private
- */
- class RenderObjects {
- /**
- * Constructs a new render object management component.
- *
- * @param {Renderer} renderer - The renderer.
- * @param {Nodes} nodes - Renderer component for managing nodes related logic.
- * @param {Geometries} geometries - Renderer component for managing geometries.
- * @param {Pipelines} pipelines - Renderer component for managing pipelines.
- * @param {Bindings} bindings - Renderer component for managing bindings.
- * @param {Info} info - Renderer component for managing metrics and monitoring data.
- */
- constructor( renderer, nodes, geometries, pipelines, bindings, info ) {
- /**
- * The renderer.
- *
- * @type {Renderer}
- */
- this.renderer = renderer;
- /**
- * Renderer component for managing nodes related logic.
- *
- * @type {Nodes}
- */
- this.nodes = nodes;
- /**
- * Renderer component for managing geometries.
- *
- * @type {Geometries}
- */
- this.geometries = geometries;
- /**
- * Renderer component for managing pipelines.
- *
- * @type {Pipelines}
- */
- this.pipelines = pipelines;
- /**
- * Renderer component for managing bindings.
- *
- * @type {Bindings}
- */
- this.bindings = bindings;
- /**
- * Renderer component for managing metrics and monitoring data.
- *
- * @type {Info}
- */
- this.info = info;
- /**
- * A dictionary that manages render contexts in chain maps
- * for each pass ID.
- *
- * @type {Object<string,ChainMap>}
- */
- this.chainMaps = {};
- }
- /**
- * Returns a render object for the given object and state data.
- *
- * @param {Object3D} object - The 3D object.
- * @param {Material} material - The 3D object's material.
- * @param {Scene} scene - The scene the 3D object belongs to.
- * @param {Camera} camera - The camera the 3D object should be rendered with.
- * @param {LightsNode} lightsNode - The lights node.
- * @param {RenderContext} renderContext - The render context.
- * @param {ClippingContext} clippingContext - The clipping context.
- * @param {string} [passId] - An optional ID for identifying the pass.
- * @return {RenderObject} The render object.
- */
- get( object, material, scene, camera, lightsNode, renderContext, clippingContext, passId ) {
- const chainMap = this.getChainMap( passId );
- // reuse chainArray
- _chainKeys$5[ 0 ] = object;
- _chainKeys$5[ 1 ] = material;
- _chainKeys$5[ 2 ] = renderContext;
- _chainKeys$5[ 3 ] = lightsNode;
- let renderObject = chainMap.get( _chainKeys$5 );
- if ( renderObject === undefined ) {
- renderObject = this.createRenderObject( this.nodes, this.geometries, this.renderer, object, material, scene, camera, lightsNode, renderContext, clippingContext, passId );
- chainMap.set( _chainKeys$5, renderObject );
- } else {
- renderObject.updateClipping( clippingContext );
- if ( renderObject.needsGeometryUpdate ) {
- renderObject.setGeometry( object.geometry );
- }
- if ( renderObject.version !== material.version || renderObject.needsUpdate ) {
- if ( renderObject.initialCacheKey !== renderObject.getCacheKey() ) {
- renderObject.dispose();
- renderObject = this.get( object, material, scene, camera, lightsNode, renderContext, clippingContext, passId );
- } else {
- renderObject.version = material.version;
- }
- }
- }
- _chainKeys$5.length = 0;
- return renderObject;
- }
- /**
- * Returns a chain map for the given pass ID.
- *
- * @param {string} [passId='default'] - The pass ID.
- * @return {ChainMap} The chain map.
- */
- getChainMap( passId = 'default' ) {
- return this.chainMaps[ passId ] || ( this.chainMaps[ passId ] = new ChainMap() );
- }
- /**
- * Frees internal resources.
- */
- dispose() {
- this.chainMaps = {};
- }
- /**
- * Factory method for creating render objects with the given list of parameters.
- *
- * @param {Nodes} nodes - Renderer component for managing nodes related logic.
- * @param {Geometries} geometries - Renderer component for managing geometries.
- * @param {Renderer} renderer - The renderer.
- * @param {Object3D} object - The 3D object.
- * @param {Material} material - The object's material.
- * @param {Scene} scene - The scene the 3D object belongs to.
- * @param {Camera} camera - The camera the object should be rendered with.
- * @param {LightsNode} lightsNode - The lights node.
- * @param {RenderContext} renderContext - The render context.
- * @param {ClippingContext} clippingContext - The clipping context.
- * @param {string} [passId] - An optional ID for identifying the pass.
- * @return {RenderObject} The render object.
- */
- createRenderObject( nodes, geometries, renderer, object, material, scene, camera, lightsNode, renderContext, clippingContext, passId ) {
- const chainMap = this.getChainMap( passId );
- const renderObject = new RenderObject( nodes, geometries, renderer, object, material, scene, camera, lightsNode, renderContext, clippingContext );
- renderObject.onDispose = () => {
- this.pipelines.delete( renderObject );
- this.bindings.deleteForRender( renderObject );
- this.nodes.delete( renderObject );
- chainMap.delete( renderObject.getChainArray() );
- };
- return renderObject;
- }
- }
- /**
- * Data structure for the renderer. It is intended to manage
- * data of objects in dictionaries.
- *
- * @private
- */
- class DataMap {
- /**
- * Constructs a new data map.
- */
- constructor() {
- /**
- * `DataMap` internally uses a weak map
- * to manage its data.
- *
- * @type {WeakMap<Object, Object>}
- */
- this.data = new WeakMap();
- }
- /**
- * Returns the dictionary for the given object.
- *
- * @param {Object} object - The object.
- * @return {Object} The dictionary.
- */
- get( object ) {
- let map = this.data.get( object );
- if ( map === undefined ) {
- map = {};
- this.data.set( object, map );
- }
- return map;
- }
- /**
- * Deletes the dictionary for the given object.
- *
- * @param {Object} object - The object.
- * @return {?Object} The deleted dictionary.
- */
- delete( object ) {
- let map = null;
- if ( this.data.has( object ) ) {
- map = this.data.get( object );
- this.data.delete( object );
- }
- return map;
- }
- /**
- * Returns `true` if the given object has a dictionary defined.
- *
- * @param {Object} object - The object to test.
- * @return {boolean} Whether a dictionary is defined or not.
- */
- has( object ) {
- return this.data.has( object );
- }
- /**
- * Frees internal resources.
- */
- dispose() {
- this.data = new WeakMap();
- }
- }
- const AttributeType = {
- VERTEX: 1,
- INDEX: 2,
- STORAGE: 3,
- INDIRECT: 4
- };
- // size of a chunk in bytes (STD140 layout)
- const GPU_CHUNK_BYTES = 16;
- // @TODO: Move to src/constants.js
- const BlendColorFactor = 211;
- const OneMinusBlendColorFactor = 212;
- /**
- * This renderer module manages geometry attributes.
- *
- * @private
- * @augments DataMap
- */
- class Attributes extends DataMap {
- /**
- * Constructs a new attribute management component.
- *
- * @param {Backend} backend - The renderer's backend.
- */
- constructor( backend ) {
- super();
- /**
- * The renderer's backend.
- *
- * @type {Backend}
- */
- this.backend = backend;
- }
- /**
- * Deletes the data for the given attribute.
- *
- * @param {BufferAttribute} attribute - The attribute.
- * @return {?Object} The deleted attribute data.
- */
- delete( attribute ) {
- const attributeData = super.delete( attribute );
- if ( attributeData !== null ) {
- this.backend.destroyAttribute( attribute );
- }
- return attributeData;
- }
- /**
- * Updates the given attribute. This method creates attribute buffers
- * for new attributes and updates data for existing ones.
- *
- * @param {BufferAttribute} attribute - The attribute to update.
- * @param {number} type - The attribute type.
- */
- update( attribute, type ) {
- const data = this.get( attribute );
- if ( data.version === undefined ) {
- if ( type === AttributeType.VERTEX ) {
- this.backend.createAttribute( attribute );
- } else if ( type === AttributeType.INDEX ) {
- this.backend.createIndexAttribute( attribute );
- } else if ( type === AttributeType.STORAGE ) {
- this.backend.createStorageAttribute( attribute );
- } else if ( type === AttributeType.INDIRECT ) {
- this.backend.createIndirectStorageAttribute( attribute );
- }
- data.version = this._getBufferAttribute( attribute ).version;
- } else {
- const bufferAttribute = this._getBufferAttribute( attribute );
- if ( data.version < bufferAttribute.version || bufferAttribute.usage === DynamicDrawUsage ) {
- this.backend.updateAttribute( attribute );
- data.version = bufferAttribute.version;
- }
- }
- }
- /**
- * Utility method for handling interleaved buffer attributes correctly.
- * To process them, their `InterleavedBuffer` is returned.
- *
- * @param {BufferAttribute} attribute - The attribute.
- * @return {BufferAttribute|InterleavedBuffer}
- */
- _getBufferAttribute( attribute ) {
- if ( attribute.isInterleavedBufferAttribute ) attribute = attribute.data;
- return attribute;
- }
- }
- /**
- * Returns the wireframe version for the given geometry.
- *
- * @private
- * @function
- * @param {BufferGeometry} geometry - The geometry.
- * @return {number} The version.
- */
- function getWireframeVersion( geometry ) {
- return ( geometry.index !== null ) ? geometry.index.version : geometry.attributes.position.version;
- }
- /**
- * Returns a wireframe index attribute for the given geometry.
- *
- * @private
- * @function
- * @param {BufferGeometry} geometry - The geometry.
- * @return {BufferAttribute} The wireframe index attribute.
- */
- function getWireframeIndex( geometry ) {
- const indices = [];
- const geometryIndex = geometry.index;
- const geometryPosition = geometry.attributes.position;
- if ( geometryIndex !== null ) {
- const array = geometryIndex.array;
- for ( let i = 0, l = array.length; i < l; i += 3 ) {
- const a = array[ i + 0 ];
- const b = array[ i + 1 ];
- const c = array[ i + 2 ];
- indices.push( a, b, b, c, c, a );
- }
- } else {
- const array = geometryPosition.array;
- for ( let i = 0, l = ( array.length / 3 ) - 1; i < l; i += 3 ) {
- const a = i + 0;
- const b = i + 1;
- const c = i + 2;
- indices.push( a, b, b, c, c, a );
- }
- }
- const attribute = new ( arrayNeedsUint32( indices ) ? Uint32BufferAttribute : Uint16BufferAttribute )( indices, 1 );
- attribute.version = getWireframeVersion( geometry );
- return attribute;
- }
- /**
- * This renderer module manages geometries.
- *
- * @private
- * @augments DataMap
- */
- class Geometries extends DataMap {
- /**
- * Constructs a new geometry management component.
- *
- * @param {Attributes} attributes - Renderer component for managing attributes.
- * @param {Info} info - Renderer component for managing metrics and monitoring data.
- */
- constructor( attributes, info ) {
- super();
- /**
- * Renderer component for managing attributes.
- *
- * @type {Attributes}
- */
- this.attributes = attributes;
- /**
- * Renderer component for managing metrics and monitoring data.
- *
- * @type {Info}
- */
- this.info = info;
- /**
- * Weak Map for managing attributes for wireframe rendering.
- *
- * @type {WeakMap<BufferGeometry,BufferAttribute>}
- */
- this.wireframes = new WeakMap();
- /**
- * This Weak Map is used to make sure buffer attributes are
- * updated only once per render call.
- *
- * @type {WeakMap<BufferAttribute,number>}
- */
- this.attributeCall = new WeakMap();
- /**
- * Stores the event listeners attached to geometries.
- *
- * @private
- * @type {Map<BufferGeometry,Function>}
- */
- this._geometryDisposeListeners = new Map();
- }
- /**
- * Returns `true` if the given render object has an initialized geometry.
- *
- * @param {RenderObject} renderObject - The render object.
- * @return {boolean} Whether if the given render object has an initialized geometry or not.
- */
- has( renderObject ) {
- const geometry = renderObject.geometry;
- return super.has( geometry ) && this.get( geometry ).initialized === true;
- }
- /**
- * Prepares the geometry of the given render object for rendering.
- *
- * @param {RenderObject} renderObject - The render object.
- */
- updateForRender( renderObject ) {
- if ( this.has( renderObject ) === false ) this.initGeometry( renderObject );
- this.updateAttributes( renderObject );
- }
- /**
- * Initializes the geometry of the given render object.
- *
- * @param {RenderObject} renderObject - The render object.
- */
- initGeometry( renderObject ) {
- const geometry = renderObject.geometry;
- const geometryData = this.get( geometry );
- geometryData.initialized = true;
- this.info.memory.geometries ++;
- const onDispose = () => {
- this.info.memory.geometries --;
- const index = geometry.index;
- const geometryAttributes = renderObject.getAttributes();
- if ( index !== null ) {
- this.attributes.delete( index );
- }
- for ( const geometryAttribute of geometryAttributes ) {
- this.attributes.delete( geometryAttribute );
- }
- const wireframeAttribute = this.wireframes.get( geometry );
- if ( wireframeAttribute !== undefined ) {
- this.attributes.delete( wireframeAttribute );
- }
- geometry.removeEventListener( 'dispose', onDispose );
- this._geometryDisposeListeners.delete( geometry );
- };
- geometry.addEventListener( 'dispose', onDispose );
- // see #31798 why tracking separate remove listeners is required right now
- // TODO: Re-evaluate how onDispose() is managed in this component
- this._geometryDisposeListeners.set( geometry, onDispose );
- }
- /**
- * Updates the geometry attributes of the given render object.
- *
- * @param {RenderObject} renderObject - The render object.
- */
- updateAttributes( renderObject ) {
- // attributes
- const attributes = renderObject.getAttributes();
- for ( const attribute of attributes ) {
- if ( attribute.isStorageBufferAttribute || attribute.isStorageInstancedBufferAttribute ) {
- this.updateAttribute( attribute, AttributeType.STORAGE );
- } else {
- this.updateAttribute( attribute, AttributeType.VERTEX );
- }
- }
- // indexes
- const index = this.getIndex( renderObject );
- if ( index !== null ) {
- this.updateAttribute( index, AttributeType.INDEX );
- }
- // indirect
- const indirect = renderObject.geometry.indirect;
- if ( indirect !== null ) {
- this.updateAttribute( indirect, AttributeType.INDIRECT );
- }
- }
- /**
- * Updates the given attribute.
- *
- * @param {BufferAttribute} attribute - The attribute to update.
- * @param {number} type - The attribute type.
- */
- updateAttribute( attribute, type ) {
- const callId = this.info.render.calls;
- if ( ! attribute.isInterleavedBufferAttribute ) {
- if ( this.attributeCall.get( attribute ) !== callId ) {
- this.attributes.update( attribute, type );
- this.attributeCall.set( attribute, callId );
- }
- } else {
- if ( this.attributeCall.get( attribute ) === undefined ) {
- this.attributes.update( attribute, type );
- this.attributeCall.set( attribute, callId );
- } else if ( this.attributeCall.get( attribute.data ) !== callId ) {
- this.attributes.update( attribute, type );
- this.attributeCall.set( attribute.data, callId );
- this.attributeCall.set( attribute, callId );
- }
- }
- }
- /**
- * Returns the indirect buffer attribute of the given render object.
- *
- * @param {RenderObject} renderObject - The render object.
- * @return {?BufferAttribute} The indirect attribute. `null` if no indirect drawing is used.
- */
- getIndirect( renderObject ) {
- return renderObject.geometry.indirect;
- }
- /**
- * Returns the byte offset into the indirect attribute buffer of the given render object.
- *
- * @param {RenderObject} renderObject - The render object.
- * @return {number} The byte offset into the indirect attribute buffer.
- */
- getIndirectOffset( renderObject ) {
- return renderObject.geometry.indirectOffset;
- }
- /**
- * Returns the index of the given render object's geometry. This is implemented
- * in a method to return a wireframe index if necessary.
- *
- * @param {RenderObject} renderObject - The render object.
- * @return {?BufferAttribute} The index. Returns `null` for non-indexed geometries.
- */
- getIndex( renderObject ) {
- const { geometry, material } = renderObject;
- let index = geometry.index;
- if ( material.wireframe === true ) {
- const wireframes = this.wireframes;
- let wireframeAttribute = wireframes.get( geometry );
- if ( wireframeAttribute === undefined ) {
- wireframeAttribute = getWireframeIndex( geometry );
- wireframes.set( geometry, wireframeAttribute );
- } else if ( wireframeAttribute.version !== getWireframeVersion( geometry ) ) {
- this.attributes.delete( wireframeAttribute );
- wireframeAttribute = getWireframeIndex( geometry );
- wireframes.set( geometry, wireframeAttribute );
- }
- index = wireframeAttribute;
- }
- return index;
- }
- dispose() {
- for ( const [ geometry, onDispose ] of this._geometryDisposeListeners.entries() ) {
- geometry.removeEventListener( 'dispose', onDispose );
- }
- this._geometryDisposeListeners.clear();
- }
- }
- /**
- * This renderer module provides a series of statistical information
- * about the GPU memory and the rendering process. Useful for debugging
- * and monitoring.
- */
- class Info {
- /**
- * Constructs a new info component.
- */
- constructor() {
- /**
- * Whether frame related metrics should automatically
- * be resetted or not. This property should be set to `false`
- * by apps which manage their own animation loop. They must
- * then call `renderer.info.reset()` once per frame manually.
- *
- * @type {boolean}
- * @default true
- */
- this.autoReset = true;
- /**
- * The current frame ID. This ID is managed
- * by `NodeFrame`.
- *
- * @type {number}
- * @readonly
- * @default 0
- */
- this.frame = 0;
- /**
- * The number of render calls since the
- * app has been started.
- *
- * @type {number}
- * @readonly
- * @default 0
- */
- this.calls = 0;
- /**
- * Render related metrics.
- *
- * @type {Object}
- * @readonly
- * @property {number} calls - The number of render calls since the app has been started.
- * @property {number} frameCalls - The number of render calls of the current frame.
- * @property {number} drawCalls - The number of draw calls of the current frame.
- * @property {number} triangles - The number of rendered triangle primitives of the current frame.
- * @property {number} points - The number of rendered point primitives of the current frame.
- * @property {number} lines - The number of rendered line primitives of the current frame.
- * @property {number} timestamp - The timestamp of the frame.
- */
- this.render = {
- calls: 0,
- frameCalls: 0,
- drawCalls: 0,
- triangles: 0,
- points: 0,
- lines: 0,
- timestamp: 0,
- };
- /**
- * Compute related metrics.
- *
- * @type {Object}
- * @readonly
- * @property {number} calls - The number of compute calls since the app has been started.
- * @property {number} frameCalls - The number of compute calls of the current frame.
- * @property {number} timestamp - The timestamp of the frame when using `renderer.computeAsync()`.
- */
- this.compute = {
- calls: 0,
- frameCalls: 0,
- timestamp: 0
- };
- /**
- * Memory related metrics.
- *
- * @type {Object}
- * @readonly
- * @property {number} geometries - The number of active geometries.
- * @property {number} frameCalls - The number of active textures.
- */
- this.memory = {
- geometries: 0,
- textures: 0
- };
- }
- /**
- * This method should be executed per draw call and updates the corresponding metrics.
- *
- * @param {Object3D} object - The 3D object that is going to be rendered.
- * @param {number} count - The vertex or index count.
- * @param {number} instanceCount - The instance count.
- */
- update( object, count, instanceCount ) {
- this.render.drawCalls ++;
- if ( object.isMesh || object.isSprite ) {
- this.render.triangles += instanceCount * ( count / 3 );
- } else if ( object.isPoints ) {
- this.render.points += instanceCount * count;
- } else if ( object.isLineSegments ) {
- this.render.lines += instanceCount * ( count / 2 );
- } else if ( object.isLine ) {
- this.render.lines += instanceCount * ( count - 1 );
- } else {
- error( 'WebGPUInfo: Unknown object type.' );
- }
- }
- /**
- * Resets frame related metrics.
- */
- reset() {
- this.render.drawCalls = 0;
- this.render.frameCalls = 0;
- this.compute.frameCalls = 0;
- this.render.triangles = 0;
- this.render.points = 0;
- this.render.lines = 0;
- }
- /**
- * Performs a complete reset of the object.
- */
- dispose() {
- this.reset();
- this.calls = 0;
- this.render.calls = 0;
- this.compute.calls = 0;
- this.render.timestamp = 0;
- this.compute.timestamp = 0;
- this.memory.geometries = 0;
- this.memory.textures = 0;
- }
- }
- /**
- * Abstract class for representing pipelines.
- *
- * @private
- * @abstract
- */
- class Pipeline {
- /**
- * Constructs a new pipeline.
- *
- * @param {string} cacheKey - The pipeline's cache key.
- */
- constructor( cacheKey ) {
- /**
- * The pipeline's cache key.
- *
- * @type {string}
- */
- this.cacheKey = cacheKey;
- /**
- * How often the pipeline is currently in use.
- *
- * @type {number}
- * @default 0
- */
- this.usedTimes = 0;
- }
- }
- /**
- * Class for representing render pipelines.
- *
- * @private
- * @augments Pipeline
- */
- class RenderPipeline extends Pipeline {
- /**
- * Constructs a new render pipeline.
- *
- * @param {string} cacheKey - The pipeline's cache key.
- * @param {ProgrammableStage} vertexProgram - The pipeline's vertex shader.
- * @param {ProgrammableStage} fragmentProgram - The pipeline's fragment shader.
- */
- constructor( cacheKey, vertexProgram, fragmentProgram ) {
- super( cacheKey );
- /**
- * The pipeline's vertex shader.
- *
- * @type {ProgrammableStage}
- */
- this.vertexProgram = vertexProgram;
- /**
- * The pipeline's fragment shader.
- *
- * @type {ProgrammableStage}
- */
- this.fragmentProgram = fragmentProgram;
- }
- }
- /**
- * Class for representing compute pipelines.
- *
- * @private
- * @augments Pipeline
- */
- class ComputePipeline extends Pipeline {
- /**
- * Constructs a new render pipeline.
- *
- * @param {string} cacheKey - The pipeline's cache key.
- * @param {ProgrammableStage} computeProgram - The pipeline's compute shader.
- */
- constructor( cacheKey, computeProgram ) {
- super( cacheKey );
- /**
- * The pipeline's compute shader.
- *
- * @type {ProgrammableStage}
- */
- this.computeProgram = computeProgram;
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isComputePipeline = true;
- }
- }
- let _id$9 = 0;
- /**
- * Class for representing programmable stages which are vertex,
- * fragment or compute shaders. Unlike fixed-function states (like blending),
- * they represent the programmable part of a pipeline.
- *
- * @private
- */
- class ProgrammableStage {
- /**
- * Constructs a new programmable stage.
- *
- * @param {string} code - The shader code.
- * @param {('vertex'|'fragment'|'compute')} stage - The type of stage.
- * @param {string} name - The name of the shader.
- * @param {?Array<Object>} [transforms=null] - The transforms (only relevant for compute stages with WebGL 2 which uses Transform Feedback).
- * @param {?Array<Object>} [attributes=null] - The attributes (only relevant for compute stages with WebGL 2 which uses Transform Feedback).
- */
- constructor( code, stage, name, transforms = null, attributes = null ) {
- /**
- * The id of the programmable stage.
- *
- * @type {number}
- */
- this.id = _id$9 ++;
- /**
- * The shader code.
- *
- * @type {string}
- */
- this.code = code;
- /**
- * The type of stage.
- *
- * @type {string}
- */
- this.stage = stage;
- /**
- * The name of the stage.
- * This is used for debugging purposes.
- *
- * @type {string}
- */
- this.name = name;
- /**
- * The transforms (only relevant for compute stages with WebGL 2 which uses Transform Feedback).
- *
- * @type {?Array<Object>}
- */
- this.transforms = transforms;
- /**
- * The attributes (only relevant for compute stages with WebGL 2 which uses Transform Feedback).
- *
- * @type {?Array<Object>}
- */
- this.attributes = attributes;
- /**
- * How often the programmable stage is currently in use.
- *
- * @type {number}
- * @default 0
- */
- this.usedTimes = 0;
- }
- }
- /**
- * This renderer module manages the pipelines of the renderer.
- *
- * @private
- * @augments DataMap
- */
- class Pipelines extends DataMap {
- /**
- * Constructs a new pipeline management component.
- *
- * @param {Backend} backend - The renderer's backend.
- * @param {Nodes} nodes - Renderer component for managing nodes related logic.
- */
- constructor( backend, nodes ) {
- super();
- /**
- * The renderer's backend.
- *
- * @type {Backend}
- */
- this.backend = backend;
- /**
- * Renderer component for managing nodes related logic.
- *
- * @type {Nodes}
- */
- this.nodes = nodes;
- /**
- * A references to the bindings management component.
- * This reference will be set inside the `Bindings`
- * constructor.
- *
- * @type {?Bindings}
- * @default null
- */
- this.bindings = null;
- /**
- * Internal cache for maintaining pipelines.
- * The key of the map is a cache key, the value the pipeline.
- *
- * @type {Map<string,Pipeline>}
- */
- this.caches = new Map();
- /**
- * This dictionary maintains for each shader stage type (vertex,
- * fragment and compute) the programmable stage objects which
- * represent the actual shader code.
- *
- * @type {Object<string,Map<string, ProgrammableStage>>}
- */
- this.programs = {
- vertex: new Map(),
- fragment: new Map(),
- compute: new Map()
- };
- }
- /**
- * Returns a compute pipeline for the given compute node.
- *
- * @param {Node} computeNode - The compute node.
- * @param {Array<BindGroup>} bindings - The bindings.
- * @return {ComputePipeline} The compute pipeline.
- */
- getForCompute( computeNode, bindings ) {
- const { backend } = this;
- const data = this.get( computeNode );
- if ( this._needsComputeUpdate( computeNode ) ) {
- const previousPipeline = data.pipeline;
- if ( previousPipeline ) {
- previousPipeline.usedTimes --;
- previousPipeline.computeProgram.usedTimes --;
- }
- // get shader
- const nodeBuilderState = this.nodes.getForCompute( computeNode );
- // programmable stage
- let stageCompute = this.programs.compute.get( nodeBuilderState.computeShader );
- if ( stageCompute === undefined ) {
- if ( previousPipeline && previousPipeline.computeProgram.usedTimes === 0 ) this._releaseProgram( previousPipeline.computeProgram );
- stageCompute = new ProgrammableStage( nodeBuilderState.computeShader, 'compute', computeNode.name, nodeBuilderState.transforms, nodeBuilderState.nodeAttributes );
- this.programs.compute.set( nodeBuilderState.computeShader, stageCompute );
- backend.createProgram( stageCompute );
- }
- // determine compute pipeline
- const cacheKey = this._getComputeCacheKey( computeNode, stageCompute );
- let pipeline = this.caches.get( cacheKey );
- if ( pipeline === undefined ) {
- if ( previousPipeline && previousPipeline.usedTimes === 0 ) this._releasePipeline( previousPipeline );
- pipeline = this._getComputePipeline( computeNode, stageCompute, cacheKey, bindings );
- }
- // keep track of all used times
- pipeline.usedTimes ++;
- stageCompute.usedTimes ++;
- //
- data.version = computeNode.version;
- data.pipeline = pipeline;
- }
- return data.pipeline;
- }
- /**
- * Returns a render pipeline for the given render object.
- *
- * @param {RenderObject} renderObject - The render object.
- * @param {?Array<Promise>} [promises=null] - An array of compilation promises which is only relevant in context of `Renderer.compileAsync()`.
- * @return {RenderPipeline} The render pipeline.
- */
- getForRender( renderObject, promises = null ) {
- const { backend } = this;
- const data = this.get( renderObject );
- if ( this._needsRenderUpdate( renderObject ) ) {
- const previousPipeline = data.pipeline;
- if ( previousPipeline ) {
- previousPipeline.usedTimes --;
- previousPipeline.vertexProgram.usedTimes --;
- previousPipeline.fragmentProgram.usedTimes --;
- }
- // get shader
- const nodeBuilderState = renderObject.getNodeBuilderState();
- const name = renderObject.material ? renderObject.material.name : '';
- // programmable stages
- let stageVertex = this.programs.vertex.get( nodeBuilderState.vertexShader );
- if ( stageVertex === undefined ) {
- if ( previousPipeline && previousPipeline.vertexProgram.usedTimes === 0 ) this._releaseProgram( previousPipeline.vertexProgram );
- stageVertex = new ProgrammableStage( nodeBuilderState.vertexShader, 'vertex', name );
- this.programs.vertex.set( nodeBuilderState.vertexShader, stageVertex );
- backend.createProgram( stageVertex );
- }
- let stageFragment = this.programs.fragment.get( nodeBuilderState.fragmentShader );
- if ( stageFragment === undefined ) {
- if ( previousPipeline && previousPipeline.fragmentProgram.usedTimes === 0 ) this._releaseProgram( previousPipeline.fragmentProgram );
- stageFragment = new ProgrammableStage( nodeBuilderState.fragmentShader, 'fragment', name );
- this.programs.fragment.set( nodeBuilderState.fragmentShader, stageFragment );
- backend.createProgram( stageFragment );
- }
- // determine render pipeline
- const cacheKey = this._getRenderCacheKey( renderObject, stageVertex, stageFragment );
- let pipeline = this.caches.get( cacheKey );
- if ( pipeline === undefined ) {
- if ( previousPipeline && previousPipeline.usedTimes === 0 ) this._releasePipeline( previousPipeline );
- pipeline = this._getRenderPipeline( renderObject, stageVertex, stageFragment, cacheKey, promises );
- } else {
- renderObject.pipeline = pipeline;
- }
- // keep track of all used times
- pipeline.usedTimes ++;
- stageVertex.usedTimes ++;
- stageFragment.usedTimes ++;
- //
- data.pipeline = pipeline;
- }
- return data.pipeline;
- }
- /**
- * Deletes the pipeline for the given render object.
- *
- * @param {RenderObject} object - The render object.
- * @return {?Object} The deleted dictionary.
- */
- delete( object ) {
- const pipeline = this.get( object ).pipeline;
- if ( pipeline ) {
- // pipeline
- pipeline.usedTimes --;
- if ( pipeline.usedTimes === 0 ) this._releasePipeline( pipeline );
- // programs
- if ( pipeline.isComputePipeline ) {
- pipeline.computeProgram.usedTimes --;
- if ( pipeline.computeProgram.usedTimes === 0 ) this._releaseProgram( pipeline.computeProgram );
- } else {
- pipeline.fragmentProgram.usedTimes --;
- pipeline.vertexProgram.usedTimes --;
- if ( pipeline.vertexProgram.usedTimes === 0 ) this._releaseProgram( pipeline.vertexProgram );
- if ( pipeline.fragmentProgram.usedTimes === 0 ) this._releaseProgram( pipeline.fragmentProgram );
- }
- }
- return super.delete( object );
- }
- /**
- * Frees internal resources.
- */
- dispose() {
- super.dispose();
- this.caches = new Map();
- this.programs = {
- vertex: new Map(),
- fragment: new Map(),
- compute: new Map()
- };
- }
- /**
- * Updates the pipeline for the given render object.
- *
- * @param {RenderObject} renderObject - The render object.
- */
- updateForRender( renderObject ) {
- this.getForRender( renderObject );
- }
- /**
- * Returns a compute pipeline for the given parameters.
- *
- * @private
- * @param {Node} computeNode - The compute node.
- * @param {ProgrammableStage} stageCompute - The programmable stage representing the compute shader.
- * @param {string} cacheKey - The cache key.
- * @param {Array<BindGroup>} bindings - The bindings.
- * @return {ComputePipeline} The compute pipeline.
- */
- _getComputePipeline( computeNode, stageCompute, cacheKey, bindings ) {
- // check for existing pipeline
- cacheKey = cacheKey || this._getComputeCacheKey( computeNode, stageCompute );
- let pipeline = this.caches.get( cacheKey );
- if ( pipeline === undefined ) {
- pipeline = new ComputePipeline( cacheKey, stageCompute );
- this.caches.set( cacheKey, pipeline );
- this.backend.createComputePipeline( pipeline, bindings );
- }
- return pipeline;
- }
- /**
- * Returns a render pipeline for the given parameters.
- *
- * @private
- * @param {RenderObject} renderObject - The render object.
- * @param {ProgrammableStage} stageVertex - The programmable stage representing the vertex shader.
- * @param {ProgrammableStage} stageFragment - The programmable stage representing the fragment shader.
- * @param {string} cacheKey - The cache key.
- * @param {?Array<Promise>} promises - An array of compilation promises which is only relevant in context of `Renderer.compileAsync()`.
- * @return {ComputePipeline} The compute pipeline.
- */
- _getRenderPipeline( renderObject, stageVertex, stageFragment, cacheKey, promises ) {
- // check for existing pipeline
- cacheKey = cacheKey || this._getRenderCacheKey( renderObject, stageVertex, stageFragment );
- let pipeline = this.caches.get( cacheKey );
- if ( pipeline === undefined ) {
- pipeline = new RenderPipeline( cacheKey, stageVertex, stageFragment );
- this.caches.set( cacheKey, pipeline );
- renderObject.pipeline = pipeline;
- // The `promises` array is `null` by default and only set to an empty array when
- // `Renderer.compileAsync()` is used. The next call actually fills the array with
- // pending promises that resolve when the render pipelines are ready for rendering.
- this.backend.createRenderPipeline( renderObject, promises );
- }
- return pipeline;
- }
- /**
- * Computes a cache key representing a compute pipeline.
- *
- * @private
- * @param {Node} computeNode - The compute node.
- * @param {ProgrammableStage} stageCompute - The programmable stage representing the compute shader.
- * @return {string} The cache key.
- */
- _getComputeCacheKey( computeNode, stageCompute ) {
- return computeNode.id + ',' + stageCompute.id;
- }
- /**
- * Computes a cache key representing a render pipeline.
- *
- * @private
- * @param {RenderObject} renderObject - The render object.
- * @param {ProgrammableStage} stageVertex - The programmable stage representing the vertex shader.
- * @param {ProgrammableStage} stageFragment - The programmable stage representing the fragment shader.
- * @return {string} The cache key.
- */
- _getRenderCacheKey( renderObject, stageVertex, stageFragment ) {
- return stageVertex.id + ',' + stageFragment.id + ',' + this.backend.getRenderCacheKey( renderObject );
- }
- /**
- * Releases the given pipeline.
- *
- * @private
- * @param {Pipeline} pipeline - The pipeline to release.
- */
- _releasePipeline( pipeline ) {
- this.caches.delete( pipeline.cacheKey );
- }
- /**
- * Releases the shader program.
- *
- * @private
- * @param {Object} program - The shader program to release.
- */
- _releaseProgram( program ) {
- const code = program.code;
- const stage = program.stage;
- this.programs[ stage ].delete( code );
- }
- /**
- * Returns `true` if the compute pipeline for the given compute node requires an update.
- *
- * @private
- * @param {Node} computeNode - The compute node.
- * @return {boolean} Whether the compute pipeline for the given compute node requires an update or not.
- */
- _needsComputeUpdate( computeNode ) {
- const data = this.get( computeNode );
- return data.pipeline === undefined || data.version !== computeNode.version;
- }
- /**
- * Returns `true` if the render pipeline for the given render object requires an update.
- *
- * @private
- * @param {RenderObject} renderObject - The render object.
- * @return {boolean} Whether the render object for the given render object requires an update or not.
- */
- _needsRenderUpdate( renderObject ) {
- const data = this.get( renderObject );
- return data.pipeline === undefined || this.backend.needsRenderUpdate( renderObject );
- }
- }
- /**
- * This renderer module manages the bindings of the renderer.
- *
- * @private
- * @augments DataMap
- */
- class Bindings extends DataMap {
- /**
- * Constructs a new bindings management component.
- *
- * @param {Backend} backend - The renderer's backend.
- * @param {Nodes} nodes - Renderer component for managing nodes related logic.
- * @param {Textures} textures - Renderer component for managing textures.
- * @param {Attributes} attributes - Renderer component for managing attributes.
- * @param {Pipelines} pipelines - Renderer component for managing pipelines.
- * @param {Info} info - Renderer component for managing metrics and monitoring data.
- */
- constructor( backend, nodes, textures, attributes, pipelines, info ) {
- super();
- /**
- * The renderer's backend.
- *
- * @type {Backend}
- */
- this.backend = backend;
- /**
- * Renderer component for managing textures.
- *
- * @type {Textures}
- */
- this.textures = textures;
- /**
- * Renderer component for managing pipelines.
- *
- * @type {Pipelines}
- */
- this.pipelines = pipelines;
- /**
- * Renderer component for managing attributes.
- *
- * @type {Attributes}
- */
- this.attributes = attributes;
- /**
- * Renderer component for managing nodes related logic.
- *
- * @type {Nodes}
- */
- this.nodes = nodes;
- /**
- * Renderer component for managing metrics and monitoring data.
- *
- * @type {Info}
- */
- this.info = info;
- this.pipelines.bindings = this; // assign bindings to pipelines
- }
- /**
- * Returns the bind groups for the given render object.
- *
- * @param {RenderObject} renderObject - The render object.
- * @return {Array<BindGroup>} The bind groups.
- */
- getForRender( renderObject ) {
- const bindings = renderObject.getBindings();
- for ( const bindGroup of bindings ) {
- const groupData = this.get( bindGroup );
- if ( groupData.bindGroup === undefined ) {
- // each object defines an array of bindings (ubos, textures, samplers etc.)
- this._init( bindGroup );
- this.backend.createBindings( bindGroup, bindings, 0 );
- groupData.bindGroup = bindGroup;
- }
- }
- return bindings;
- }
- /**
- * Returns the bind groups for the given compute node.
- *
- * @param {Node} computeNode - The compute node.
- * @return {Array<BindGroup>} The bind groups.
- */
- getForCompute( computeNode ) {
- const bindings = this.nodes.getForCompute( computeNode ).bindings;
- for ( const bindGroup of bindings ) {
- const groupData = this.get( bindGroup );
- if ( groupData.bindGroup === undefined ) {
- this._init( bindGroup );
- this.backend.createBindings( bindGroup, bindings, 0 );
- groupData.bindGroup = bindGroup;
- }
- }
- return bindings;
- }
- /**
- * Updates the bindings for the given compute node.
- *
- * @param {Node} computeNode - The compute node.
- */
- updateForCompute( computeNode ) {
- this._updateBindings( this.getForCompute( computeNode ) );
- }
- /**
- * Updates the bindings for the given render object.
- *
- * @param {RenderObject} renderObject - The render object.
- */
- updateForRender( renderObject ) {
- this._updateBindings( this.getForRender( renderObject ) );
- }
- /**
- * Deletes the bindings for the given compute node.
- *
- * @param {Node} computeNode - The compute node.
- */
- deleteForCompute( computeNode ) {
- const bindings = this.nodes.getForCompute( computeNode ).bindings;
- for ( const bindGroup of bindings ) {
- this.backend.deleteBindGroupData( bindGroup );
- this.delete( bindGroup );
- }
- }
- /**
- * Deletes the bindings for the given renderObject node.
- *
- * @param {RenderObject} renderObject - The renderObject.
- */
- deleteForRender( renderObject ) {
- const bindings = renderObject.getBindings();
- for ( const bindGroup of bindings ) {
- this.backend.deleteBindGroupData( bindGroup );
- this.delete( bindGroup );
- }
- }
- /**
- * Updates the given array of bindings.
- *
- * @param {Array<BindGroup>} bindings - The bind groups.
- */
- _updateBindings( bindings ) {
- for ( const bindGroup of bindings ) {
- this._update( bindGroup, bindings );
- }
- }
- /**
- * Initializes the given bind group.
- *
- * @param {BindGroup} bindGroup - The bind group to initialize.
- */
- _init( bindGroup ) {
- for ( const binding of bindGroup.bindings ) {
- if ( binding.isSampledTexture ) {
- this.textures.updateTexture( binding.texture );
- } else if ( binding.isSampler ) {
- this.textures.updateSampler( binding.texture );
- } else if ( binding.isStorageBuffer ) {
- const attribute = binding.attribute;
- const attributeType = attribute.isIndirectStorageBufferAttribute ? AttributeType.INDIRECT : AttributeType.STORAGE;
- this.attributes.update( attribute, attributeType );
- }
- }
- }
- /**
- * Updates the given bind group.
- *
- * @param {BindGroup} bindGroup - The bind group to update.
- * @param {Array<BindGroup>} bindings - The bind groups.
- */
- _update( bindGroup, bindings ) {
- const { backend } = this;
- let needsBindingsUpdate = false;
- let cacheBindings = true;
- let cacheIndex = 0;
- let version = 0;
- // iterate over all bindings and check if buffer updates or a new binding group is required
- for ( const binding of bindGroup.bindings ) {
- const updatedGroup = this.nodes.updateGroup( binding );
- // every uniforms group is a uniform buffer. So if no update is required,
- // we move one with the next binding. Otherwise the next if block will update the group.
- if ( updatedGroup === false ) continue;
- //
- if ( binding.isStorageBuffer ) {
- const attribute = binding.attribute;
- const attributeType = attribute.isIndirectStorageBufferAttribute ? AttributeType.INDIRECT : AttributeType.STORAGE;
- this.attributes.update( attribute, attributeType );
- }
- if ( binding.isUniformBuffer ) {
- const updated = binding.update();
- if ( updated ) {
- backend.updateBinding( binding );
- }
- } else if ( binding.isSampledTexture ) {
- const updated = binding.update();
- // get the texture data after the update, to sync the texture reference from node
- const texture = binding.texture;
- const texturesTextureData = this.textures.get( texture );
- if ( updated ) {
- // version: update the texture data or create a new one
- this.textures.updateTexture( texture );
- // generation: update the bindings if a new texture has been created
- if ( binding.generation !== texturesTextureData.generation ) {
- binding.generation = texturesTextureData.generation;
- needsBindingsUpdate = true;
- cacheBindings = false;
- }
- }
- const textureData = backend.get( texture );
- if ( textureData.externalTexture !== undefined || texturesTextureData.isDefaultTexture ) {
- cacheBindings = false;
- } else {
- cacheIndex = cacheIndex * 10 + texture.id;
- version += texture.version;
- }
- if ( texture.isStorageTexture === true && texture.mipmapsAutoUpdate === true ) {
- const textureData = this.get( texture );
- if ( binding.store === true ) {
- textureData.needsMipmap = true;
- } else if ( this.textures.needsMipmaps( texture ) && textureData.needsMipmap === true ) {
- this.backend.generateMipmaps( texture );
- textureData.needsMipmap = false;
- }
- }
- } else if ( binding.isSampler ) {
- const updated = binding.update();
- if ( updated ) {
- const samplerKey = this.textures.updateSampler( binding.texture );
- if ( binding.samplerKey !== samplerKey ) {
- binding.samplerKey = samplerKey;
- needsBindingsUpdate = true;
- cacheBindings = false;
- }
- }
- }
- }
- if ( needsBindingsUpdate === true ) {
- this.backend.updateBindings( bindGroup, bindings, cacheBindings ? cacheIndex : 0, version );
- }
- }
- }
- /**
- * Default sorting function for opaque render items.
- *
- * @private
- * @function
- * @param {Object} a - The first render item.
- * @param {Object} b - The second render item.
- * @return {number} A numeric value which defines the sort order.
- */
- function painterSortStable( a, b ) {
- if ( a.groupOrder !== b.groupOrder ) {
- return a.groupOrder - b.groupOrder;
- } else if ( a.renderOrder !== b.renderOrder ) {
- return a.renderOrder - b.renderOrder;
- } else if ( a.z !== b.z ) {
- return a.z - b.z;
- } else {
- return a.id - b.id;
- }
- }
- /**
- * Default sorting function for transparent render items.
- *
- * @private
- * @function
- * @param {Object} a - The first render item.
- * @param {Object} b - The second render item.
- * @return {number} A numeric value which defines the sort order.
- */
- function reversePainterSortStable( a, b ) {
- if ( a.groupOrder !== b.groupOrder ) {
- return a.groupOrder - b.groupOrder;
- } else if ( a.renderOrder !== b.renderOrder ) {
- return a.renderOrder - b.renderOrder;
- } else if ( a.z !== b.z ) {
- return b.z - a.z;
- } else {
- return a.id - b.id;
- }
- }
- /**
- * Returns `true` if the given transparent material requires a double pass.
- *
- * @private
- * @function
- * @param {Material} material - The transparent material.
- * @return {boolean} Whether the given material requires a double pass or not.
- */
- function needsDoublePass( material ) {
- const hasTransmission = material.transmission > 0 || ( material.transmissionNode && material.transmissionNode.isNode );
- return hasTransmission && material.side === DoubleSide && material.forceSinglePass === false;
- }
- /**
- * When the renderer analyzes the scene at the beginning of a render call,
- * it stores 3D object for further processing in render lists. Depending on the
- * properties of a 3D objects (like their transformation or material state), the
- * objects are maintained in ordered lists for the actual rendering.
- *
- * Render lists are unique per scene and camera combination.
- *
- * @private
- * @augments Pipeline
- */
- class RenderList {
- /**
- * Constructs a render list.
- *
- * @param {Lighting} lighting - The lighting management component.
- * @param {Scene} scene - The scene.
- * @param {Camera} camera - The camera the scene is rendered with.
- */
- constructor( lighting, scene, camera ) {
- /**
- * 3D objects are transformed into render items and stored in this array.
- *
- * @type {Array<Object>}
- */
- this.renderItems = [];
- /**
- * The current render items index.
- *
- * @type {number}
- * @default 0
- */
- this.renderItemsIndex = 0;
- /**
- * A list with opaque render items.
- *
- * @type {Array<Object>}
- */
- this.opaque = [];
- /**
- * A list with transparent render items which require
- * double pass rendering (e.g. transmissive objects).
- *
- * @type {Array<Object>}
- */
- this.transparentDoublePass = [];
- /**
- * A list with transparent render items.
- *
- * @type {Array<Object>}
- */
- this.transparent = [];
- /**
- * A list with transparent render bundle data.
- *
- * @type {Array<Object>}
- */
- this.bundles = [];
- /**
- * The render list's lights node. This node is later
- * relevant for the actual analytical light nodes which
- * compute the scene's lighting in the shader.
- *
- * @type {LightsNode}
- */
- this.lightsNode = lighting.getNode( scene, camera );
- /**
- * The scene's lights stored in an array. This array
- * is used to setup the lights node.
- *
- * @type {Array<Light>}
- */
- this.lightsArray = [];
- /**
- * The scene.
- *
- * @type {Scene}
- */
- this.scene = scene;
- /**
- * The camera the scene is rendered with.
- *
- * @type {Camera}
- */
- this.camera = camera;
- /**
- * How many objects perform occlusion query tests.
- *
- * @type {number}
- * @default 0
- */
- this.occlusionQueryCount = 0;
- }
- /**
- * This method is called right at the beginning of a render call
- * before the scene is analyzed. It prepares the internal data
- * structures for the upcoming render lists generation.
- *
- * @return {RenderList} A reference to this render list.
- */
- begin() {
- this.renderItemsIndex = 0;
- this.opaque.length = 0;
- this.transparentDoublePass.length = 0;
- this.transparent.length = 0;
- this.bundles.length = 0;
- this.lightsArray.length = 0;
- this.occlusionQueryCount = 0;
- return this;
- }
- /**
- * Returns a render item for the giving render item state. The state is defined
- * by a series of object-related parameters.
- *
- * The method avoids object creation by holding render items and reusing them in
- * subsequent render calls (just with different property values).
- *
- * @param {Object3D} object - The 3D object.
- * @param {BufferGeometry} geometry - The 3D object's geometry.
- * @param {Material} material - The 3D object's material.
- * @param {number} groupOrder - The current group order.
- * @param {number} z - Th 3D object's depth value (z value in clip space).
- * @param {?number} group - {?Object} group - Only relevant for objects using multiple materials. This represents a group entry from the respective `BufferGeometry`.
- * @param {ClippingContext} clippingContext - The current clipping context.
- * @return {Object} The render item.
- */
- getNextRenderItem( object, geometry, material, groupOrder, z, group, clippingContext ) {
- let renderItem = this.renderItems[ this.renderItemsIndex ];
- if ( renderItem === undefined ) {
- renderItem = {
- id: object.id,
- object: object,
- geometry: geometry,
- material: material,
- groupOrder: groupOrder,
- renderOrder: object.renderOrder,
- z: z,
- group: group,
- clippingContext: clippingContext
- };
- this.renderItems[ this.renderItemsIndex ] = renderItem;
- } else {
- renderItem.id = object.id;
- renderItem.object = object;
- renderItem.geometry = geometry;
- renderItem.material = material;
- renderItem.groupOrder = groupOrder;
- renderItem.renderOrder = object.renderOrder;
- renderItem.z = z;
- renderItem.group = group;
- renderItem.clippingContext = clippingContext;
- }
- this.renderItemsIndex ++;
- return renderItem;
- }
- /**
- * Pushes the given object as a render item to the internal render lists.
- * The selected lists depend on the object properties.
- *
- * @param {Object3D} object - The 3D object.
- * @param {BufferGeometry} geometry - The 3D object's geometry.
- * @param {Material} material - The 3D object's material.
- * @param {number} groupOrder - The current group order.
- * @param {number} z - Th 3D object's depth value (z value in clip space).
- * @param {?number} group - {?Object} group - Only relevant for objects using multiple materials. This represents a group entry from the respective `BufferGeometry`.
- * @param {ClippingContext} clippingContext - The current clipping context.
- */
- push( object, geometry, material, groupOrder, z, group, clippingContext ) {
- const renderItem = this.getNextRenderItem( object, geometry, material, groupOrder, z, group, clippingContext );
- if ( object.occlusionTest === true ) this.occlusionQueryCount ++;
- if ( material.transparent === true || material.transmission > 0 ||
- ( material.transmissionNode && material.transmissionNode.isNode ) ||
- ( material.backdropNode && material.backdropNode.isNode ) ) {
- if ( needsDoublePass( material ) ) this.transparentDoublePass.push( renderItem );
- this.transparent.push( renderItem );
- } else {
- this.opaque.push( renderItem );
- }
- }
- /**
- * Inserts the given object as a render item at the start of the internal render lists.
- * The selected lists depend on the object properties.
- *
- * @param {Object3D} object - The 3D object.
- * @param {BufferGeometry} geometry - The 3D object's geometry.
- * @param {Material} material - The 3D object's material.
- * @param {number} groupOrder - The current group order.
- * @param {number} z - Th 3D object's depth value (z value in clip space).
- * @param {?number} group - {?Object} group - Only relevant for objects using multiple materials. This represents a group entry from the respective `BufferGeometry`.
- * @param {ClippingContext} clippingContext - The current clipping context.
- */
- unshift( object, geometry, material, groupOrder, z, group, clippingContext ) {
- const renderItem = this.getNextRenderItem( object, geometry, material, groupOrder, z, group, clippingContext );
- if ( material.transparent === true || material.transmission > 0 ||
- ( material.transmissionNode && material.transmissionNode.isNode ) ||
- ( material.backdropNode && material.backdropNode.isNode ) ) {
- if ( needsDoublePass( material ) ) this.transparentDoublePass.unshift( renderItem );
- this.transparent.unshift( renderItem );
- } else {
- this.opaque.unshift( renderItem );
- }
- }
- /**
- * Pushes render bundle group data into the render list.
- *
- * @param {Object} group - Bundle group data.
- */
- pushBundle( group ) {
- this.bundles.push( group );
- }
- /**
- * Pushes a light into the render list.
- *
- * @param {Light} light - The light.
- */
- pushLight( light ) {
- this.lightsArray.push( light );
- }
- /**
- * Sorts the internal render lists.
- *
- * @param {?function(any, any): number} customOpaqueSort - A custom sort function for opaque objects.
- * @param {?function(any, any): number} customTransparentSort - A custom sort function for transparent objects.
- */
- sort( customOpaqueSort, customTransparentSort ) {
- if ( this.opaque.length > 1 ) this.opaque.sort( customOpaqueSort || painterSortStable );
- if ( this.transparentDoublePass.length > 1 ) this.transparentDoublePass.sort( customTransparentSort || reversePainterSortStable );
- if ( this.transparent.length > 1 ) this.transparent.sort( customTransparentSort || reversePainterSortStable );
- }
- /**
- * This method performs finalizing tasks right after the render lists
- * have been generated.
- */
- finish() {
- // update lights
- this.lightsNode.setLights( this.lightsArray );
- // Clear references from inactive renderItems in the list
- for ( let i = this.renderItemsIndex, il = this.renderItems.length; i < il; i ++ ) {
- const renderItem = this.renderItems[ i ];
- if ( renderItem.id === null ) break;
- renderItem.id = null;
- renderItem.object = null;
- renderItem.geometry = null;
- renderItem.material = null;
- renderItem.groupOrder = null;
- renderItem.renderOrder = null;
- renderItem.z = null;
- renderItem.group = null;
- renderItem.clippingContext = null;
- }
- }
- }
- const _chainKeys$4 = [];
- /**
- * This renderer module manages the render lists which are unique
- * per scene and camera combination.
- *
- * @private
- */
- class RenderLists {
- /**
- * Constructs a render lists management component.
- *
- * @param {Lighting} lighting - The lighting management component.
- */
- constructor( lighting ) {
- /**
- * The lighting management component.
- *
- * @type {Lighting}
- */
- this.lighting = lighting;
- /**
- * The internal chain map which holds the render lists.
- *
- * @type {ChainMap}
- */
- this.lists = new ChainMap();
- }
- /**
- * Returns a render list for the given scene and camera.
- *
- * @param {Scene} scene - The scene.
- * @param {Camera} camera - The camera.
- * @return {RenderList} The render list.
- */
- get( scene, camera ) {
- const lists = this.lists;
- _chainKeys$4[ 0 ] = scene;
- _chainKeys$4[ 1 ] = camera;
- let list = lists.get( _chainKeys$4 );
- if ( list === undefined ) {
- list = new RenderList( this.lighting, scene, camera );
- lists.set( _chainKeys$4, list );
- }
- _chainKeys$4.length = 0;
- return list;
- }
- /**
- * Frees all internal resources.
- */
- dispose() {
- this.lists = new ChainMap();
- }
- }
- let _id$8 = 0;
- /**
- * Any render or compute command is executed in a specific context that defines
- * the state of the renderer and its backend. Typical examples for such context
- * data are the current clear values or data from the active framebuffer. This
- * module is used to represent these contexts as objects.
- *
- * @private
- */
- class RenderContext {
- /**
- * Constructs a new render context.
- */
- constructor() {
- /**
- * The context's ID.
- *
- * @type {number}
- */
- this.id = _id$8 ++;
- /**
- * Whether the current active framebuffer has a color attachment.
- *
- * @type {boolean}
- * @default true
- */
- this.color = true;
- /**
- * Whether the color attachment should be cleared or not.
- *
- * @type {boolean}
- * @default true
- */
- this.clearColor = true;
- /**
- * The clear color value.
- *
- * @type {Object}
- * @default true
- */
- this.clearColorValue = { r: 0, g: 0, b: 0, a: 1 };
- /**
- * Whether the current active framebuffer has a depth attachment.
- *
- * @type {boolean}
- * @default true
- */
- this.depth = true;
- /**
- * Whether the depth attachment should be cleared or not.
- *
- * @type {boolean}
- * @default true
- */
- this.clearDepth = true;
- /**
- * The clear depth value.
- *
- * @type {number}
- * @default 1
- */
- this.clearDepthValue = 1;
- /**
- * Whether the current active framebuffer has a stencil attachment.
- *
- * @type {boolean}
- * @default false
- */
- this.stencil = false;
- /**
- * Whether the stencil attachment should be cleared or not.
- *
- * @type {boolean}
- * @default true
- */
- this.clearStencil = true;
- /**
- * The clear stencil value.
- *
- * @type {number}
- * @default 1
- */
- this.clearStencilValue = 1;
- /**
- * By default the viewport encloses the entire framebuffer If a smaller
- * viewport is manually defined, this property is to `true` by the renderer.
- *
- * @type {boolean}
- * @default false
- */
- this.viewport = false;
- /**
- * The viewport value. This value is in physical pixels meaning it incorporates
- * the renderer's pixel ratio. The viewport property of render targets or
- * the renderer is in logical pixels.
- *
- * @type {Vector4}
- */
- this.viewportValue = new Vector4();
- /**
- * When the scissor test is active and scissor rectangle smaller than the
- * framebuffers dimensions, this property is to `true` by the renderer.
- *
- * @type {boolean}
- * @default false
- */
- this.scissor = false;
- /**
- * The scissor rectangle.
- *
- * @type {Vector4}
- */
- this.scissorValue = new Vector4();
- /**
- * The active render target.
- *
- * @type {?RenderTarget}
- * @default null
- */
- this.renderTarget = null;
- /**
- * The textures of the active render target.
- * `null` when no render target is set.
- *
- * @type {?Array<Texture>}
- * @default null
- */
- this.textures = null;
- /**
- * The depth texture of the active render target.
- * `null` when no render target is set.
- *
- * @type {?DepthTexture}
- * @default null
- */
- this.depthTexture = null;
- /**
- * The active cube face.
- *
- * @type {number}
- * @default 0
- */
- this.activeCubeFace = 0;
- /**
- * The active mipmap level.
- *
- * @type {number}
- * @default 0
- */
- this.activeMipmapLevel = 0;
- /**
- * The number of MSAA samples. This value is always `1` when
- * MSAA isn't used.
- *
- * @type {number}
- * @default 1
- */
- this.sampleCount = 1;
- /**
- * The active render target's width in physical pixels.
- *
- * @type {number}
- * @default 0
- */
- this.width = 0;
- /**
- * The active render target's height in physical pixels.
- *
- * @type {number}
- * @default 0
- */
- this.height = 0;
- /**
- * The occlusion query count.
- *
- * @type {number}
- * @default 0
- */
- this.occlusionQueryCount = 0;
- /**
- * The current clipping context.
- *
- * @type {?ClippingContext}
- * @default null
- */
- this.clippingContext = null;
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isRenderContext = true;
- }
- /**
- * Returns the cache key of this render context.
- *
- * @return {number} The cache key.
- */
- getCacheKey() {
- return getCacheKey( this );
- }
- }
- /**
- * Computes a cache key for the given render context. This key
- * should identify the render target state so it is possible to
- * configure the correct attachments in the respective backend.
- *
- * @param {RenderContext} renderContext - The render context.
- * @return {number} The cache key.
- */
- function getCacheKey( renderContext ) {
- const { textures, activeCubeFace, activeMipmapLevel } = renderContext;
- const values = [ activeCubeFace, activeMipmapLevel ];
- for ( const texture of textures ) {
- values.push( texture.id );
- }
- return hashArray( values );
- }
- const _chainKeys$3 = [];
- const _defaultScene = /*@__PURE__*/ new Scene();
- const _defaultCamera = /*@__PURE__*/ new Camera();
- /**
- * This module manages the render contexts of the renderer.
- *
- * @private
- */
- class RenderContexts {
- /**
- * Constructs a new render context management component.
- */
- constructor() {
- /**
- * A dictionary that manages render contexts in chain maps
- * for each attachment state.
- *
- * @type {Object<string,ChainMap>}
- */
- this.chainMaps = {};
- }
- /**
- * Returns a render context for the given scene, camera and render target.
- *
- * @param {Scene} scene - The scene.
- * @param {Camera} camera - The camera that is used to render the scene.
- * @param {?RenderTarget} [renderTarget=null] - The active render target.
- * @param {?MRT} [mrt=null] - The active multiple render target.
- * @return {RenderContext} The render context.
- */
- get( scene, camera, renderTarget = null, mrt = null ) {
- _chainKeys$3[ 0 ] = scene;
- _chainKeys$3[ 1 ] = camera;
- if ( mrt !== null ) {
- _chainKeys$3[ 2 ] = mrt;
- }
- let attachmentState;
- if ( renderTarget === null ) {
- attachmentState = 'default';
- } else {
- const format = renderTarget.texture.format;
- const count = renderTarget.textures.length;
- attachmentState = `${ count }:${ format }:${ renderTarget.samples }:${ renderTarget.depthBuffer }:${ renderTarget.stencilBuffer }`;
- }
- const chainMap = this._getChainMap( attachmentState );
- let renderState = chainMap.get( _chainKeys$3 );
- if ( renderState === undefined ) {
- renderState = new RenderContext();
- chainMap.set( _chainKeys$3, renderState );
- }
- _chainKeys$3.length = 0;
- if ( renderTarget !== null ) renderState.sampleCount = renderTarget.samples === 0 ? 1 : renderTarget.samples;
- return renderState;
- }
- /**
- * Returns a render context intended for clear operations.
- *
- * @param {?RenderTarget} [renderTarget=null] - The active render target.
- * @return {RenderContext} The render context.
- */
- getForClear( renderTarget = null ) {
- return this.get( _defaultScene, _defaultCamera, renderTarget );
- }
- /**
- * Returns a chain map for the given attachment state.
- *
- * @private
- * @param {string} attachmentState - The attachment state.
- * @return {ChainMap} The chain map.
- */
- _getChainMap( attachmentState ) {
- return this.chainMaps[ attachmentState ] || ( this.chainMaps[ attachmentState ] = new ChainMap() );
- }
- /**
- * Frees internal resources.
- */
- dispose() {
- this.chainMaps = {};
- }
- }
- const _size$3 = /*@__PURE__*/ new Vector3();
- /**
- * This module manages the textures of the renderer.
- *
- * @private
- * @augments DataMap
- */
- class Textures extends DataMap {
- /**
- * Constructs a new texture management component.
- *
- * @param {Renderer} renderer - The renderer.
- * @param {Backend} backend - The renderer's backend.
- * @param {Info} info - Renderer component for managing metrics and monitoring data.
- */
- constructor( renderer, backend, info ) {
- super();
- /**
- * The renderer.
- *
- * @type {Renderer}
- */
- this.renderer = renderer;
- /**
- * The backend.
- *
- * @type {Backend}
- */
- this.backend = backend;
- /**
- * Renderer component for managing metrics and monitoring data.
- *
- * @type {Info}
- */
- this.info = info;
- }
- /**
- * Updates the given render target. Based on the given render target configuration,
- * it updates the texture states representing the attachments of the framebuffer.
- *
- * @param {RenderTarget} renderTarget - The render target to update.
- * @param {number} [activeMipmapLevel=0] - The active mipmap level.
- */
- updateRenderTarget( renderTarget, activeMipmapLevel = 0 ) {
- const renderTargetData = this.get( renderTarget );
- const sampleCount = renderTarget.samples === 0 ? 1 : renderTarget.samples;
- const depthTextureMips = renderTargetData.depthTextureMips || ( renderTargetData.depthTextureMips = {} );
- const textures = renderTarget.textures;
- const size = this.getSize( textures[ 0 ] );
- const mipWidth = size.width >> activeMipmapLevel;
- const mipHeight = size.height >> activeMipmapLevel;
- let depthTexture = renderTarget.depthTexture || depthTextureMips[ activeMipmapLevel ];
- const useDepthTexture = renderTarget.depthBuffer === true || renderTarget.stencilBuffer === true;
- let textureNeedsUpdate = false;
- if ( depthTexture === undefined && useDepthTexture ) {
- depthTexture = new DepthTexture();
- depthTexture.format = renderTarget.stencilBuffer ? DepthStencilFormat : DepthFormat;
- depthTexture.type = renderTarget.stencilBuffer ? UnsignedInt248Type : UnsignedIntType; // FloatType
- depthTexture.image.width = mipWidth;
- depthTexture.image.height = mipHeight;
- depthTexture.image.depth = size.depth;
- depthTexture.renderTarget = renderTarget;
- depthTexture.isArrayTexture = renderTarget.multiview === true && size.depth > 1;
- depthTextureMips[ activeMipmapLevel ] = depthTexture;
- }
- if ( renderTargetData.width !== size.width || size.height !== renderTargetData.height ) {
- textureNeedsUpdate = true;
- if ( depthTexture ) {
- depthTexture.needsUpdate = true;
- depthTexture.image.width = mipWidth;
- depthTexture.image.height = mipHeight;
- depthTexture.image.depth = depthTexture.isArrayTexture ? depthTexture.image.depth : 1;
- }
- }
- renderTargetData.width = size.width;
- renderTargetData.height = size.height;
- renderTargetData.textures = textures;
- renderTargetData.depthTexture = depthTexture || null;
- renderTargetData.depth = renderTarget.depthBuffer;
- renderTargetData.stencil = renderTarget.stencilBuffer;
- renderTargetData.renderTarget = renderTarget;
- if ( renderTargetData.sampleCount !== sampleCount ) {
- textureNeedsUpdate = true;
- if ( depthTexture ) {
- depthTexture.needsUpdate = true;
- }
- renderTargetData.sampleCount = sampleCount;
- }
- //
- const options = { sampleCount };
- // XR render targets require no texture updates
- if ( renderTarget.isXRRenderTarget !== true ) {
- for ( let i = 0; i < textures.length; i ++ ) {
- const texture = textures[ i ];
- if ( textureNeedsUpdate ) texture.needsUpdate = true;
- this.updateTexture( texture, options );
- }
- if ( depthTexture ) {
- this.updateTexture( depthTexture, options );
- }
- }
- // dispose handler
- if ( renderTargetData.initialized !== true ) {
- renderTargetData.initialized = true;
- // dispose
- renderTargetData.onDispose = () => {
- this._destroyRenderTarget( renderTarget );
- };
- renderTarget.addEventListener( 'dispose', renderTargetData.onDispose );
- }
- }
- /**
- * Updates the given texture. Depending on the texture state, this method
- * triggers the upload of texture data to the GPU memory. If the texture data are
- * not yet ready for the upload, it uses default texture data for as a placeholder.
- *
- * @param {Texture} texture - The texture to update.
- * @param {Object} [options={}] - The options.
- */
- updateTexture( texture, options = {} ) {
- const textureData = this.get( texture );
- if ( textureData.initialized === true && textureData.version === texture.version ) return;
- const isRenderTarget = texture.isRenderTargetTexture || texture.isDepthTexture || texture.isFramebufferTexture;
- const backend = this.backend;
- if ( isRenderTarget && textureData.initialized === true ) {
- // it's an update
- backend.destroyTexture( texture );
- }
- //
- if ( texture.isFramebufferTexture ) {
- const renderTarget = this.renderer.getRenderTarget();
- if ( renderTarget ) {
- texture.type = renderTarget.texture.type;
- } else {
- texture.type = UnsignedByteType;
- }
- }
- //
- const { width, height, depth } = this.getSize( texture );
- options.width = width;
- options.height = height;
- options.depth = depth;
- options.needsMipmaps = this.needsMipmaps( texture );
- options.levels = options.needsMipmaps ? this.getMipLevels( texture, width, height ) : 1;
- // TODO: Uniformly handle mipmap definitions
- // Normal textures and compressed cube textures define base level + mips with their mipmap array
- // Uncompressed cube textures use their mipmap array only for mips (no base level)
- if ( texture.isCubeTexture && texture.mipmaps.length > 0 ) options.levels ++;
- //
- if ( isRenderTarget || texture.isStorageTexture === true || texture.isExternalTexture === true ) {
- backend.createTexture( texture, options );
- textureData.generation = texture.version;
- } else {
- if ( texture.version > 0 ) {
- const image = texture.image;
- if ( image === undefined ) {
- warn( 'Renderer: Texture marked for update but image is undefined.' );
- } else if ( image.complete === false ) {
- warn( 'Renderer: Texture marked for update but image is incomplete.' );
- } else {
- if ( texture.images ) {
- const images = [];
- for ( const image of texture.images ) {
- images.push( image );
- }
- options.images = images;
- } else {
- options.image = image;
- }
- if ( textureData.isDefaultTexture === undefined || textureData.isDefaultTexture === true ) {
- backend.createTexture( texture, options );
- textureData.isDefaultTexture = false;
- textureData.generation = texture.version;
- }
- if ( texture.source.dataReady === true ) backend.updateTexture( texture, options );
- const skipAutoGeneration = texture.isStorageTexture === true && texture.mipmapsAutoUpdate === false;
- if ( options.needsMipmaps && texture.mipmaps.length === 0 && ! skipAutoGeneration ) {
- backend.generateMipmaps( texture );
- }
- if ( texture.onUpdate ) texture.onUpdate( texture );
- }
- } else {
- // async update
- backend.createDefaultTexture( texture );
- textureData.isDefaultTexture = true;
- textureData.generation = texture.version;
- }
- }
- // dispose handler
- if ( textureData.initialized !== true ) {
- textureData.initialized = true;
- textureData.generation = texture.version;
- //
- this.info.memory.textures ++;
- //
- if ( texture.isVideoTexture && ColorManagement.enabled === true && ColorManagement.getTransfer( texture.colorSpace ) !== SRGBTransfer ) {
- warn( 'WebGPURenderer: Video textures must use a color space with a sRGB transfer function, e.g. SRGBColorSpace.' );
- }
- // dispose
- textureData.onDispose = () => {
- this._destroyTexture( texture );
- };
- texture.addEventListener( 'dispose', textureData.onDispose );
- }
- //
- textureData.version = texture.version;
- }
- /**
- * Updates the sampler for the given texture. This method has no effect
- * for the WebGL backend since it has no concept of samplers. Texture
- * parameters are configured with the `texParameter()` command for each
- * texture.
- *
- * In WebGPU, samplers are objects like textures and it's possible to share
- * them when the texture parameters match.
- *
- * @param {Texture} texture - The texture to update the sampler for.
- * @return {string} The current sampler key.
- */
- updateSampler( texture ) {
- return this.backend.updateSampler( texture );
- }
- /**
- * Computes the size of the given texture and writes the result
- * into the target vector. This vector is also returned by the
- * method.
- *
- * If no texture data are available for the compute yet, the method
- * returns default size values.
- *
- * @param {Texture} texture - The texture to compute the size for.
- * @param {Vector3} target - The target vector.
- * @return {Vector3} The target vector.
- */
- getSize( texture, target = _size$3 ) {
- let image = texture.images ? texture.images[ 0 ] : texture.image;
- if ( image ) {
- if ( image.image !== undefined ) image = image.image;
- if ( ( typeof HTMLVideoElement !== 'undefined' ) && ( image instanceof HTMLVideoElement ) ) {
- target.width = image.videoWidth || 1;
- target.height = image.videoHeight || 1;
- target.depth = 1;
- } else if ( ( typeof VideoFrame !== 'undefined' ) && ( image instanceof VideoFrame ) ) {
- target.width = image.displayWidth || 1;
- target.height = image.displayHeight || 1;
- target.depth = 1;
- } else {
- target.width = image.width || 1;
- target.height = image.height || 1;
- target.depth = texture.isCubeTexture ? 6 : ( image.depth || 1 );
- }
- } else {
- target.width = target.height = target.depth = 1;
- }
- return target;
- }
- /**
- * Computes the number of mipmap levels for the given texture.
- *
- * @param {Texture} texture - The texture.
- * @param {number} width - The texture's width.
- * @param {number} height - The texture's height.
- * @return {number} The number of mipmap levels.
- */
- getMipLevels( texture, width, height ) {
- let mipLevelCount;
- if ( texture.mipmaps.length > 0 ) {
- mipLevelCount = texture.mipmaps.length;
- } else {
- if ( texture.isCompressedTexture === true ) {
- // it is not possible to compute mipmaps for compressed textures. So
- // when no mipmaps are defined in "texture.mipmaps", force a texture
- // level of 1
- mipLevelCount = 1;
- } else {
- mipLevelCount = Math.floor( Math.log2( Math.max( width, height ) ) ) + 1;
- }
- }
- return mipLevelCount;
- }
- /**
- * Returns `true` if the given texture makes use of mipmapping.
- *
- * @param {Texture} texture - The texture.
- * @return {boolean} Whether mipmaps are required or not.
- */
- needsMipmaps( texture ) {
- return texture.generateMipmaps === true || texture.mipmaps.length > 0;
- }
- /**
- * Frees internal resources when the given render target isn't
- * required anymore.
- *
- * @param {RenderTarget} renderTarget - The render target to destroy.
- */
- _destroyRenderTarget( renderTarget ) {
- if ( this.has( renderTarget ) === true ) {
- const renderTargetData = this.get( renderTarget );
- const textures = renderTargetData.textures;
- const depthTexture = renderTargetData.depthTexture;
- //
- renderTarget.removeEventListener( 'dispose', renderTargetData.onDispose );
- //
- for ( let i = 0; i < textures.length; i ++ ) {
- this._destroyTexture( textures[ i ] );
- }
- if ( depthTexture ) {
- this._destroyTexture( depthTexture );
- }
- this.delete( renderTarget );
- this.backend.delete( renderTarget );
- }
- }
- /**
- * Frees internal resource when the given texture isn't
- * required anymore.
- *
- * @param {Texture} texture - The texture to destroy.
- */
- _destroyTexture( texture ) {
- if ( this.has( texture ) === true ) {
- const textureData = this.get( texture );
- //
- texture.removeEventListener( 'dispose', textureData.onDispose );
- // if a texture is not ready for use, it falls back to a default texture so it's possible
- // to use it for rendering. If a texture in this state is disposed, it's important to
- // not destroy/delete the underlying GPU texture object since it is cached and shared with
- // other textures.
- const isDefaultTexture = textureData.isDefaultTexture;
- this.backend.destroyTexture( texture, isDefaultTexture );
- this.delete( texture );
- this.info.memory.textures --;
- }
- }
- }
- /**
- * A four-component version of {@link Color} which is internally
- * used by the renderer to represents clear color with alpha as
- * one object.
- *
- * @private
- * @augments Color
- */
- class Color4 extends Color {
- /**
- * Constructs a new four-component color.
- * You can also pass a single THREE.Color, hex or
- * string argument to this constructor.
- *
- * @param {number|string} [r=1] - The red value.
- * @param {number} [g=1] - The green value.
- * @param {number} [b=1] - The blue value.
- * @param {number} [a=1] - The alpha value.
- */
- constructor( r, g, b, a = 1 ) {
- super( r, g, b );
- this.a = a;
- }
- /**
- * Overwrites the default to honor alpha.
- * You can also pass a single THREE.Color, hex or
- * string argument to this method.
- *
- * @param {number|string|Color} r - The red value.
- * @param {number} [g] - The green value.
- * @param {number} [b] - The blue value.
- * @param {number} [a=1] - The alpha value.
- * @return {Color4} A reference to this object.
- */
- set( r, g, b, a = 1 ) {
- this.a = a;
- return super.set( r, g, b );
- }
- /**
- * Overwrites the default to honor alpha.
- *
- * @param {Color4} color - The color to copy.
- * @return {Color4} A reference to this object.
- */
- copy( color ) {
- if ( color.a !== undefined ) this.a = color.a;
- return super.copy( color );
- }
- /**
- * Overwrites the default to honor alpha.
- *
- * @return {Color4} The cloned color.
- */
- clone() {
- return new this.constructor( this.r, this.g, this.b, this.a );
- }
- }
- /**
- * Special version of {@link PropertyNode} which is used for parameters.
- *
- * @augments PropertyNode
- */
- class ParameterNode extends PropertyNode {
- static get type() {
- return 'ParameterNode';
- }
- /**
- * Constructs a new parameter node.
- *
- * @param {string} nodeType - The type of the node.
- * @param {?string} [name=null] - The name of the parameter in the shader.
- */
- constructor( nodeType, name = null ) {
- super( nodeType, name );
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isParameterNode = true;
- }
- /**
- * Gets the type of a member variable in the parameter node.
- *
- * @param {NodeBuilder} builder - The node builder.
- * @param {string} name - The name of the member variable.
- * @returns {string}
- */
- getMemberType( builder, name ) {
- const type = this.getNodeType( builder );
- const struct = builder.getStructTypeNode( type );
- let memberType;
- if ( struct !== null ) {
- memberType = struct.getMemberType( builder, name );
- } else {
- error( `TSL: Member "${ name }" not found in struct "${ type }".` );
- memberType = 'float';
- }
- return memberType;
- }
- getHash() {
- return this.uuid;
- }
- generate() {
- return this.name;
- }
- }
- /**
- * TSL function for creating a parameter node.
- *
- * @tsl
- * @function
- * @param {string} type - The type of the node.
- * @param {?string} name - The name of the parameter in the shader.
- * @returns {ParameterNode}
- */
- const parameter = ( type, name ) => nodeObject( new ParameterNode( type, name ) );
- /**
- * Stack is a helper for Nodes that need to produce stack-based code instead of continuous flow.
- * They are usually needed in cases like `If`, `Else`.
- *
- * @augments Node
- */
- class StackNode extends Node {
- static get type() {
- return 'StackNode';
- }
- /**
- * Constructs a new stack node.
- *
- * @param {?StackNode} [parent=null] - The parent stack node.
- */
- constructor( parent = null ) {
- super();
- /**
- * List of nodes.
- *
- * @type {Array<Node>}
- */
- this.nodes = [];
- /**
- * The output node.
- *
- * @type {?Node}
- * @default null
- */
- this.outputNode = null;
- /**
- * The parent stack node.
- *
- * @type {?StackNode}
- * @default null
- */
- this.parent = parent;
- /**
- * The current conditional node.
- *
- * @private
- * @type {ConditionalNode}
- * @default null
- */
- this._currentCond = null;
- /**
- * The expression node. Only
- * relevant for Switch/Case.
- *
- * @private
- * @type {Node}
- * @default null
- */
- this._expressionNode = null;
- /**
- * The current node being processed.
- *
- * @private
- * @type {Node}
- * @default null
- */
- this._currentNode = null;
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isStackNode = true;
- }
- getElementType( builder ) {
- return this.hasOutput ? this.outputNode.getElementType( builder ) : 'void';
- }
- getNodeType( builder ) {
- return this.hasOutput ? this.outputNode.getNodeType( builder ) : 'void';
- }
- getMemberType( builder, name ) {
- return this.hasOutput ? this.outputNode.getMemberType( builder, name ) : 'void';
- }
- /**
- * Adds a node to this stack.
- *
- * @param {Node} node - The node to add.
- * @param {number} [index=this.nodes.length] - The index where the node should be added.
- * @return {StackNode} A reference to this stack node.
- */
- addToStack( node, index = this.nodes.length ) {
- if ( node.isNode !== true ) {
- error( 'TSL: Invalid node added to stack.' );
- return this;
- }
- this.nodes.splice( index, 0, node );
- return this;
- }
- /**
- * Adds a node to the stack before the current node.
- *
- * @param {Node} node - The node to add.
- * @return {StackNode} A reference to this stack node.
- */
- addToStackBefore( node ) {
- const index = this._currentNode ? this.nodes.indexOf( this._currentNode ) : 0;
- return this.addToStack( node, index );
- }
- /**
- * Represent an `if` statement in TSL.
- *
- * @param {Node} boolNode - Represents the condition.
- * @param {Function} method - TSL code which is executed if the condition evaluates to `true`.
- * @return {StackNode} A reference to this stack node.
- */
- If( boolNode, method ) {
- const methodNode = new ShaderNode( method );
- this._currentCond = select( boolNode, methodNode );
- return this.addToStack( this._currentCond );
- }
- /**
- * Represent an `elseif` statement in TSL.
- *
- * @param {Node} boolNode - Represents the condition.
- * @param {Function} method - TSL code which is executed if the condition evaluates to `true`.
- * @return {StackNode} A reference to this stack node.
- */
- ElseIf( boolNode, method ) {
- const methodNode = new ShaderNode( method );
- const ifNode = select( boolNode, methodNode );
- this._currentCond.elseNode = ifNode;
- this._currentCond = ifNode;
- return this;
- }
- /**
- * Represent an `else` statement in TSL.
- *
- * @param {Function} method - TSL code which is executed in the `else` case.
- * @return {StackNode} A reference to this stack node.
- */
- Else( method ) {
- this._currentCond.elseNode = new ShaderNode( method );
- return this;
- }
- /**
- * Represents a `switch` statement in TSL.
- *
- * @param {any} expression - Represents the expression.
- * @param {Function} method - TSL code which is executed if the condition evaluates to `true`.
- * @return {StackNode} A reference to this stack node.
- */
- Switch( expression ) {
- this._expressionNode = nodeObject( expression );
- return this;
- }
- /**
- * Represents a `case` statement in TSL. The TSL version accepts an arbitrary numbers of values.
- * The last parameter must be the callback method that should be executed in the `true` case.
- *
- * @param {...any} params - The values of the `Case()` statement as well as the callback method.
- * @return {StackNode} A reference to this stack node.
- */
- Case( ...params ) {
- const caseNodes = [];
- // extract case nodes from the parameter list
- if ( params.length >= 2 ) {
- for ( let i = 0; i < params.length - 1; i ++ ) {
- caseNodes.push( this._expressionNode.equal( nodeObject( params[ i ] ) ) );
- }
- } else {
- error( 'TSL: Invalid parameter length. Case() requires at least two parameters.' );
- }
- // extract method
- const method = params[ params.length - 1 ];
- const methodNode = new ShaderNode( method );
- // chain multiple cases when using Case( 1, 2, 3, () => {} )
- let caseNode = caseNodes[ 0 ];
- for ( let i = 1; i < caseNodes.length; i ++ ) {
- caseNode = caseNode.or( caseNodes[ i ] );
- }
- // build condition
- const condNode = select( caseNode, methodNode );
- if ( this._currentCond === null ) {
- this._currentCond = condNode;
- return this.addToStack( this._currentCond );
- } else {
- this._currentCond.elseNode = condNode;
- this._currentCond = condNode;
- return this;
- }
- }
- /**
- * Represents the default code block of a Switch/Case statement.
- *
- * @param {Function} method - TSL code which is executed in the `else` case.
- * @return {StackNode} A reference to this stack node.
- */
- Default( method ) {
- this.Else( method );
- return this;
- }
- setup( builder ) {
- const nodeProperties = builder.getNodeProperties( this );
- let index = 0;
- for ( const childNode of this.getChildren() ) {
- if ( childNode.isVarNode && childNode.isIntent( builder ) ) {
- if ( childNode.isAssign( builder ) !== true ) {
- continue;
- }
- }
- nodeProperties[ 'node' + index ++ ] = childNode;
- }
- // return a outputNode if exists or null
- return nodeProperties.outputNode || null;
- }
- get hasOutput() {
- return this.outputNode && this.outputNode.isNode;
- }
- build( builder, ...params ) {
- const previousStack = getCurrentStack();
- const buildStage = builder.buildStage;
- setCurrentStack( this );
- builder.setActiveStack( this );
- //
- const buildNode = ( node ) => {
- this._currentNode = node;
- if ( node.isVarNode && node.isIntent( builder ) ) {
- if ( node.isAssign( builder ) !== true ) {
- return;
- }
- }
- if ( buildStage === 'setup' ) {
- node.build( builder );
- } else if ( buildStage === 'analyze' ) {
- node.build( builder, this );
- } else if ( buildStage === 'generate' ) {
- const stages = builder.getDataFromNode( node, 'any' ).stages;
- const parents = stages && stages[ builder.shaderStage ];
- if ( node.isVarNode && parents && parents.length === 1 && parents[ 0 ] && parents[ 0 ].isStackNode ) {
- return; // skip var nodes that are only used in .toVarying()
- }
- node.build( builder, 'void' );
- }
- };
- //
- const nodes = [ ...this.nodes ];
- for ( const node of nodes ) {
- buildNode( node );
- }
- this._currentNode = null;
- const newNodes = this.nodes.filter( ( node ) => nodes.indexOf( node ) === -1 );
- for ( const node of newNodes ) {
- buildNode( node );
- }
- //
- let result;
- if ( this.hasOutput ) {
- result = this.outputNode.build( builder, ...params );
- } else {
- result = super.build( builder, ...params );
- }
- setCurrentStack( previousStack );
- builder.removeActiveStack( this );
- return result;
- }
- }
- /**
- * TSL function for creating a stack node.
- *
- * @tsl
- * @function
- * @param {?StackNode} [parent=null] - The parent stack node.
- * @returns {StackNode}
- */
- const stack = /*@__PURE__*/ nodeProxy( StackNode ).setParameterLength( 0, 1 );
- /**
- * Generates a layout for struct members.
- * This function takes an object representing struct members and returns an array of member layouts.
- * Each member layout includes the member's name, type, and whether it is atomic.
- *
- * @param {Object.<string, string|Object>} members - An object where keys are member names and values are either types (as strings) or objects with type and atomic properties.
- * @returns {Array.<{name: string, type: string, atomic: boolean}>} An array of member layouts.
- */
- function getMembersLayout( members ) {
- return Object.entries( members ).map( ( [ name, value ] ) => {
- if ( typeof value === 'string' ) {
- return { name, type: value, atomic: false };
- }
- return { name, type: value.type, atomic: value.atomic || false };
- } );
- }
- /**
- * Represents a struct type node in the node-based system.
- * This class is used to define and manage the layout and types of struct members.
- * It extends the base Node class and provides methods to get the length of the struct,
- * retrieve member types, and generate the struct type for a builder.
- *
- * @augments Node
- */
- class StructTypeNode extends Node {
- static get type() {
- return 'StructTypeNode';
- }
- /**
- * Creates an instance of StructTypeNode.
- *
- * @param {Object} membersLayout - The layout of the members for the struct.
- * @param {?string} [name=null] - The optional name of the struct.
- */
- constructor( membersLayout, name = null ) {
- super( 'struct' );
- /**
- * The layout of the members for the struct
- *
- * @type {Array.<{name: string, type: string, atomic: boolean}>}
- */
- this.membersLayout = getMembersLayout( membersLayout );
- /**
- * The name of the struct.
- *
- * @type {?string}
- * @default null
- */
- this.name = name;
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isStructLayoutNode = true;
- }
- /**
- * Returns the length of the struct.
- * The length is calculated by summing the lengths of the struct's members.
- *
- * @returns {number} The length of the struct.
- */
- getLength() {
- const BYTES_PER_ELEMENT = Float32Array.BYTES_PER_ELEMENT;
- let maxAlignment = 1; // maximum alignment value in this struct
- let offset = 0; // global buffer offset in 4 byte elements
- for ( const member of this.membersLayout ) {
- const type = member.type;
- const itemSize = getMemoryLengthFromType( type );
- const alignment = getAlignmentFromType( type ) / BYTES_PER_ELEMENT;
- maxAlignment = Math.max( maxAlignment, alignment );
- const chunkOffset = offset % maxAlignment; // offset in the current chunk of maxAlignment elements
- const overhang = chunkOffset % alignment; // distance from the last aligned offset
- if ( overhang !== 0 ) {
- offset += alignment - overhang; // move to next aligned offset
- }
- offset += itemSize;
- }
- return ( Math.ceil( offset / maxAlignment ) * maxAlignment ); // ensure length is a multiple of maxAlignment
- }
- getMemberType( builder, name ) {
- const member = this.membersLayout.find( m => m.name === name );
- return member ? member.type : 'void';
- }
- getNodeType( builder ) {
- const structType = builder.getStructTypeFromNode( this, this.membersLayout, this.name );
- return structType.name;
- }
- setup( builder ) {
- builder.getStructTypeFromNode( this, this.membersLayout, this.name );
- builder.addInclude( this );
- }
- generate( builder ) {
- return this.getNodeType( builder );
- }
- }
- /**
- * StructNode allows to create custom structures with multiple members.
- * This can also be used to define structures in attribute and uniform data.
- *
- * ```js
- * // Define a custom struct
- * const BoundingBox = struct( { min: 'vec3', max: 'vec3' } );
- *
- * // Create a new instance of the struct
- * const bb = BoundingBox( vec3( 0 ), vec3( 1 ) ); // style 1
- * const bb = BoundingBox( { min: vec3( 0 ), max: vec3( 1 ) } ); // style 2
- *
- * // Access the struct members
- * const min = bb.get( 'min' );
- *
- * // Assign a new value to a member
- * min.assign( vec3() );
- * ```
- * @augments Node
- */
- class StructNode extends Node {
- static get type() {
- return 'StructNode';
- }
- constructor( structTypeNode, values ) {
- super( 'vec3' );
- this.structTypeNode = structTypeNode;
- this.values = values;
- this.isStructNode = true;
- }
- getNodeType( builder ) {
- return this.structTypeNode.getNodeType( builder );
- }
- getMemberType( builder, name ) {
- return this.structTypeNode.getMemberType( builder, name );
- }
- generate( builder ) {
- const nodeVar = builder.getVarFromNode( this );
- const structType = nodeVar.type;
- const propertyName = builder.getPropertyName( nodeVar );
- builder.addLineFlowCode( `${ propertyName } = ${ builder.generateStruct( structType, this.structTypeNode.membersLayout, this.values ) }`, this );
- return nodeVar.name;
- }
- }
- /**
- * TSL function for creating a struct node.
- *
- * @tsl
- * @function
- * @param {Object} membersLayout - The layout of the struct members.
- * @param {?string} [name=null] - The name of the struct.
- * @returns {Function} The struct function.
- */
- const struct = ( membersLayout, name = null ) => {
- const structLayout = new StructTypeNode( membersLayout, name );
- const struct = ( ...params ) => {
- let values = null;
- if ( params.length > 0 ) {
- if ( params[ 0 ].isNode ) {
- values = {};
- const names = Object.keys( membersLayout );
- for ( let i = 0; i < params.length; i ++ ) {
- values[ names[ i ] ] = params[ i ];
- }
- } else {
- values = params[ 0 ];
- }
- }
- return nodeObject( new StructNode( structLayout, values ) );
- };
- struct.layout = structLayout;
- struct.isStruct = true;
- return struct;
- };
- /**
- * This node can be used to define multiple outputs in a shader programs.
- *
- * @augments Node
- */
- class OutputStructNode extends Node {
- static get type() {
- return 'OutputStructNode';
- }
- /**
- * Constructs a new output struct node. The constructor can be invoked with an
- * arbitrary number of nodes representing the members.
- *
- * @param {...Node} members - A parameter list of nodes.
- */
- constructor( ...members ) {
- super();
- /**
- * An array of nodes which defines the output.
- *
- * @type {Array<Node>}
- */
- this.members = members;
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isOutputStructNode = true;
- }
- getNodeType( builder ) {
- const properties = builder.getNodeProperties( this );
- if ( properties.membersLayout === undefined ) {
- const members = this.members;
- const membersLayout = [];
- for ( let i = 0; i < members.length; i ++ ) {
- const name = 'm' + i;
- const type = members[ i ].getNodeType( builder );
- membersLayout.push( { name, type, index: i } );
- }
- properties.membersLayout = membersLayout;
- properties.structType = builder.getOutputStructTypeFromNode( this, properties.membersLayout );
- }
- return properties.structType.name;
- }
- generate( builder ) {
- const propertyName = builder.getOutputStructName();
- const members = this.members;
- const structPrefix = propertyName !== '' ? propertyName + '.' : '';
- for ( let i = 0; i < members.length; i ++ ) {
- const snippet = members[ i ].build( builder );
- builder.addLineFlowCode( `${ structPrefix }m${ i } = ${ snippet }`, this );
- }
- return propertyName;
- }
- }
- /**
- * TSL function for creating an output struct node.
- *
- * @tsl
- * @function
- * @param {...Node} members - A parameter list of nodes.
- * @returns {OutputStructNode}
- */
- const outputStruct = /*@__PURE__*/ nodeProxy( OutputStructNode );
- /**
- * Returns the MRT texture index for the given name.
- *
- * @param {Array<Texture>} textures - The textures of a MRT-configured render target.
- * @param {string} name - The name of the MRT texture which index is requested.
- * @return {number} The texture index.
- */
- function getTextureIndex( textures, name ) {
- for ( let i = 0; i < textures.length; i ++ ) {
- if ( textures[ i ].name === name ) {
- return i;
- }
- }
- return -1;
- }
- /**
- * This node can be used setup a MRT context for rendering. A typical MRT setup for
- * post-processing is shown below:
- * ```js
- * const mrtNode = mrt( {
- * output: output,
- * normal: normalView
- * } ) );
- * ```
- * The MRT output is defined as a dictionary.
- *
- * @augments OutputStructNode
- */
- class MRTNode extends OutputStructNode {
- static get type() {
- return 'MRTNode';
- }
- /**
- * Constructs a new output struct node.
- *
- * @param {Object<string, Node>} outputNodes - The MRT outputs.
- */
- constructor( outputNodes ) {
- super();
- /**
- * A dictionary representing the MRT outputs. The key
- * is the name of the output, the value the node which produces
- * the output result.
- *
- * @type {Object<string, Node>}
- */
- this.outputNodes = outputNodes;
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isMRTNode = true;
- }
- /**
- * Returns `true` if the MRT node has an output with the given name.
- *
- * @param {string} name - The name of the output.
- * @return {NodeBuilder} Whether the MRT node has an output for the given name or not.
- */
- has( name ) {
- return this.outputNodes[ name ] !== undefined;
- }
- /**
- * Returns the output node for the given name.
- *
- * @param {string} name - The name of the output.
- * @return {Node} The output node.
- */
- get( name ) {
- return this.outputNodes[ name ];
- }
- /**
- * Merges the outputs of the given MRT node with the outputs of this node.
- *
- * @param {MRTNode} mrtNode - The MRT to merge.
- * @return {MRTNode} A new MRT node with merged outputs..
- */
- merge( mrtNode ) {
- const outputs = { ...this.outputNodes, ...mrtNode.outputNodes };
- return mrt( outputs );
- }
- setup( builder ) {
- const outputNodes = this.outputNodes;
- const mrt = builder.renderer.getRenderTarget();
- const members = [];
- const textures = mrt.textures;
- for ( const name in outputNodes ) {
- const index = getTextureIndex( textures, name );
- members[ index ] = vec4( outputNodes[ name ] );
- }
- this.members = members;
- return super.setup( builder );
- }
- }
- /**
- * TSL function for creating a MRT node.
- *
- * @tsl
- * @function
- * @param {Object<string, Node>} outputNodes - The MRT outputs.
- * @returns {MRTNode}
- */
- const mrt = /*@__PURE__*/ nodeProxy( MRTNode );
- /**
- * This node represents an operation that reinterprets the bit representation of a value
- * in one type as a value in another type.
- *
- * @augments TempNode
- */
- class BitcastNode extends TempNode {
- static get type() {
- return 'BitcastNode';
- }
- /**
- * Constructs a new bitcast node.
- *
- * @param {Node} valueNode - The value to convert.
- * @param {string} conversionType - The type to convert to.
- * @param {?string} [inputType = null] - The expected input data type of the bitcast operation.
- */
- constructor( valueNode, conversionType, inputType = null ) {
- super();
- /**
- * The data to bitcast to a new type.
- *
- * @type {Node}
- */
- this.valueNode = valueNode;
- /**
- * The type the value will be converted to.
- *
- * @type {string}
- */
- this.conversionType = conversionType;
- /**
- * The expected input data type of the bitcast operation.
- *
- *
- * @type {string}
- * @default null
- */
- this.inputType = inputType;
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isBitcastNode = true;
- }
- getNodeType( builder ) {
- // GLSL aliasing
- if ( this.inputType !== null ) {
- const valueType = this.valueNode.getNodeType( builder );
- const valueLength = builder.getTypeLength( valueType );
- return builder.getTypeFromLength( valueLength, this.conversionType );
- }
- return this.conversionType;
- }
- generate( builder ) {
- const type = this.getNodeType( builder );
- let inputType = '';
- if ( this.inputType !== null ) {
- const valueType = this.valueNode.getNodeType( builder );
- const valueTypeLength = builder.getTypeLength( valueType );
- inputType = valueTypeLength === 1 ? this.inputType : builder.changeComponentType( valueType, this.inputType );
- } else {
- inputType = this.valueNode.getNodeType( builder );
- }
- return `${ builder.getBitcastMethod( type, inputType ) }( ${ this.valueNode.build( builder, inputType ) } )`;
- }
- }
- /**
- * Reinterpret the bit representation of a value in one type as a value in another type.
- *
- * @tsl
- * @function
- * @param {Node | number} x - The parameter.
- * @param {string} y - The new type.
- * @returns {Node}
- */
- const bitcast = /*@__PURE__*/ nodeProxyIntent( BitcastNode ).setParameterLength( 2 );
- /**
- * Bitcasts a float or a vector of floats to a corresponding integer type with the same element size.
- *
- * @tsl
- * @function
- * @param {Node<float>} value - The float or vector of floats to bitcast.
- * @returns {BitcastNode}
- */
- const floatBitsToInt = ( value ) => new BitcastNode( value, 'int', 'float' );
- /**
- * Bitcasts a float or a vector of floats to a corresponding unsigned integer type with the same element size.
- *
- * @tsl
- * @function
- * @param {Node<float>} value - The float or vector of floats to bitcast.
- * @returns {BitcastNode}
- */
- const floatBitsToUint = ( value ) => new BitcastNode( value, 'uint', 'float' );
- /**
- * Bitcasts an integer or a vector of integers to a corresponding float type with the same element size.
- *
- * @tsl
- * @function
- * @param {Node<int>} value - The integer or vector of integers to bitcast.
- * @returns {BitcastNode}
- */
- const intBitsToFloat = ( value ) => new BitcastNode( value, 'float', 'int' );
- /**
- * Bitcast an unsigned integer or a vector of unsigned integers to a corresponding float type with the same element size.
- *
- * @tsl
- * @function
- * @param {Node<uint>} value - The unsigned integer or vector of unsigned integers to bitcast.
- * @returns {BitcastNode}
- */
- const uintBitsToFloat = ( value ) => new BitcastNode( value, 'float', 'uint' );
- const registeredBitcountFunctions = {};
- /**
- * This node represents an operation that counts the bits of a piece of shader data.
- *
- * @augments MathNode
- */
- class BitcountNode extends MathNode {
- static get type() {
- return 'BitcountNode';
- }
- /**
- * Constructs a new math node.
- *
- * @param {'countTrailingZeros'|'countLeadingZeros'|'countOneBits'} method - The method name.
- * @param {Node} aNode - The first input.
- */
- constructor( method, aNode ) {
- super( method, aNode );
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isBitcountNode = true;
- }
- /**
- * Casts the input value of the function to an integer if necessary.
- *
- * @private
- * @param {Node<uint>|Node<int>} inputNode - The input value.
- * @param {Node<uint>} outputNode - The output value.
- * @param {string} elementType - The type of the input value.
- */
- _resolveElementType( inputNode, outputNode, elementType ) {
- if ( elementType === 'int' ) {
- outputNode.assign( bitcast( inputNode, 'uint' ) );
- } else {
- outputNode.assign( inputNode );
- }
- }
- _returnDataNode( inputType ) {
- switch ( inputType ) {
- case 'uint': {
- return uint;
- }
- case 'int': {
- return int;
- }
- case 'uvec2': {
- return uvec2;
- }
- case 'uvec3': {
- return uvec3;
- }
- case 'uvec4': {
- return uvec4;
- }
- case 'ivec2': {
- return ivec2;
- }
- case 'ivec3': {
- return ivec3;
- }
- case 'ivec4': {
- return ivec4;
- }
- }
- }
- /**
- * Creates and registers a reusable GLSL function that emulates the behavior of countTrailingZeros.
- *
- * @private
- * @param {string} method - The name of the function to create.
- * @param {string} elementType - The type of the input value.
- * @returns {Function} - The generated function
- */
- _createTrailingZerosBaseLayout( method, elementType ) {
- const outputConvertNode = this._returnDataNode( elementType );
- const fnDef = Fn( ( [ value ] ) => {
- const v = uint( 0.0 );
- this._resolveElementType( value, v, elementType );
- const f = float( v.bitAnd( negate( v ) ) );
- const uintBits = floatBitsToUint( f );
- const numTrailingZeros = ( uintBits.shiftRight( 23 ) ).sub( 127 );
- return outputConvertNode( numTrailingZeros );
- } ).setLayout( {
- name: method,
- type: elementType,
- inputs: [
- { name: 'value', type: elementType }
- ]
- } );
- return fnDef;
- }
- /**
- * Creates and registers a reusable GLSL function that emulates the behavior of countLeadingZeros.
- *
- * @private
- * @param {string} method - The name of the function to create.
- * @param {string} elementType - The type of the input value.
- * @returns {Function} - The generated function
- */
- _createLeadingZerosBaseLayout( method, elementType ) {
- const outputConvertNode = this._returnDataNode( elementType );
- const fnDef = Fn( ( [ value ] ) => {
- If( value.equal( uint( 0 ) ), () => {
- return uint( 32 );
- } );
- const v = uint( 0 );
- const n = uint( 0 );
- this._resolveElementType( value, v, elementType );
- If( v.shiftRight( 16 ).equal( 0 ), () => {
- n.addAssign( 16 );
- v.shiftLeftAssign( 16 );
- } );
- If( v.shiftRight( 24 ).equal( 0 ), () => {
- n.addAssign( 8 );
- v.shiftLeftAssign( 8 );
- } );
- If( v.shiftRight( 28 ).equal( 0 ), () => {
- n.addAssign( 4 );
- v.shiftLeftAssign( 4 );
- } );
- If( v.shiftRight( 30 ).equal( 0 ), () => {
- n.addAssign( 2 );
- v.shiftLeftAssign( 2 );
- } );
- If( v.shiftRight( 31 ).equal( 0 ), () => {
- n.addAssign( 1 );
- } );
- return outputConvertNode( n );
- } ).setLayout( {
- name: method,
- type: elementType,
- inputs: [
- { name: 'value', type: elementType }
- ]
- } );
- return fnDef;
- }
- /**
- * Creates and registers a reusable GLSL function that emulates the behavior of countOneBits.
- *
- * @private
- * @param {string} method - The name of the function to create.
- * @param {string} elementType - The type of the input value.
- * @returns {Function} - The generated function
- */
- _createOneBitsBaseLayout( method, elementType ) {
- const outputConvertNode = this._returnDataNode( elementType );
- const fnDef = Fn( ( [ value ] ) => {
- const v = uint( 0.0 );
- this._resolveElementType( value, v, elementType );
- v.assign( v.sub( v.shiftRight( uint( 1 ) ).bitAnd( uint( 0x55555555 ) ) ) );
- v.assign( v.bitAnd( uint( 0x33333333 ) ).add( v.shiftRight( uint( 2 ) ).bitAnd( uint( 0x33333333 ) ) ) );
- const numBits = v.add( v.shiftRight( uint( 4 ) ) ).bitAnd( uint( 0xF0F0F0F ) ).mul( uint( 0x1010101 ) ).shiftRight( uint( 24 ) );
- return outputConvertNode( numBits );
- } ).setLayout( {
- name: method,
- type: elementType,
- inputs: [
- { name: 'value', type: elementType }
- ]
- } );
- return fnDef;
- }
- /**
- * Creates and registers a reusable GLSL function that emulates the behavior of the specified bitcount function.
- * including considerations for component-wise bitcounts on vector type inputs.
- *
- * @private
- * @param {string} method - The name of the function to create.
- * @param {string} inputType - The type of the input value.
- * @param {number} typeLength - The vec length of the input value.
- * @param {Function} baseFn - The base function that operates on an individual component of the vector.
- * @returns {Function} - The alias function for the specified bitcount method.
- */
- _createMainLayout( method, inputType, typeLength, baseFn ) {
- const outputConvertNode = this._returnDataNode( inputType );
- const fnDef = Fn( ( [ value ] ) => {
- if ( typeLength === 1 ) {
- return outputConvertNode( baseFn( value ) );
- } else {
- const vec = outputConvertNode( 0 );
- const components = [ 'x', 'y', 'z', 'w' ];
- for ( let i = 0; i < typeLength; i ++ ) {
- const component = components[ i ];
- vec[ component ].assign( baseFn( value[ component ] ) );
- }
- return vec;
- }
- } ).setLayout( {
- name: method,
- type: inputType,
- inputs: [
- { name: 'value', type: inputType }
- ]
- } );
- return fnDef;
- }
- setup( builder ) {
- const { method, aNode } = this;
- const { renderer } = builder;
- if ( renderer.backend.isWebGPUBackend ) {
- // use built-in WGSL functions for WebGPU
- return super.setup( builder );
- }
- const inputType = this.getInputType( builder );
- const elementType = builder.getElementType( inputType );
- const typeLength = builder.getTypeLength( inputType );
- const baseMethod = `${method}_base_${elementType}`;
- const newMethod = `${method}_${inputType}`;
- let baseFn = registeredBitcountFunctions[ baseMethod ];
- if ( baseFn === undefined ) {
- switch ( method ) {
- case BitcountNode.COUNT_LEADING_ZEROS: {
- baseFn = this._createLeadingZerosBaseLayout( baseMethod, elementType );
- break;
- }
- case BitcountNode.COUNT_TRAILING_ZEROS: {
- baseFn = this._createTrailingZerosBaseLayout( baseMethod, elementType );
- break;
- }
- case BitcountNode.COUNT_ONE_BITS: {
- baseFn = this._createOneBitsBaseLayout( baseMethod, elementType );
- break;
- }
- }
- registeredBitcountFunctions[ baseMethod ] = baseFn;
- }
- let fn = registeredBitcountFunctions[ newMethod ];
- if ( fn === undefined ) {
- fn = this._createMainLayout( newMethod, inputType, typeLength, baseFn );
- registeredBitcountFunctions[ newMethod ] = fn;
- }
- const output = Fn( () => {
- return fn(
- aNode,
- );
- } );
- return output();
- }
- }
- BitcountNode.COUNT_TRAILING_ZEROS = 'countTrailingZeros';
- BitcountNode.COUNT_LEADING_ZEROS = 'countLeadingZeros';
- BitcountNode.COUNT_ONE_BITS = 'countOneBits';
- /**
- * Finds the number of consecutive 0 bits from the least significant bit of the input value,
- * which is also the index of the least significant bit of the input value.
- *
- * Can only be used with {@link WebGPURenderer} and a WebGPU backend.
- *
- * @tsl
- * @function
- * @param {Node | number} x - The input value.
- * @returns {Node}
- */
- const countTrailingZeros = /*@__PURE__*/ nodeProxyIntent( BitcountNode, BitcountNode.COUNT_TRAILING_ZEROS ).setParameterLength( 1 );
- /**
- * Finds the number of consecutive 0 bits starting from the most significant bit of the input value.
- *
- * Can only be used with {@link WebGPURenderer} and a WebGPU backend.
- *
- * @tsl
- * @function
- * @param {Node | number} x - The input value.
- * @returns {Node}
- */
- const countLeadingZeros = /*@__PURE__*/ nodeProxyIntent( BitcountNode, BitcountNode.COUNT_LEADING_ZEROS ).setParameterLength( 1 );
- /**
- * Finds the number of '1' bits set in the input value
- *
- * Can only be used with {@link WebGPURenderer} and a WebGPU backend.
- *
- * @tsl
- * @function
- * @returns {Node}
- */
- const countOneBits = /*@__PURE__*/ nodeProxyIntent( BitcountNode, BitcountNode.COUNT_ONE_BITS ).setParameterLength( 1 );
- /**
- * Generates a hash value in the range `[0, 1]` from the given seed.
- *
- * @tsl
- * @function
- * @param {Node<float>} seed - The seed.
- * @return {Node<float>} The hash value.
- */
- const hash = /*@__PURE__*/ Fn( ( [ seed ] ) => {
- // Taken from https://www.shadertoy.com/view/XlGcRh, originally from pcg-random.org
- const state = seed.toUint().mul( 747796405 ).add( 2891336453 );
- const word = state.shiftRight( state.shiftRight( 28 ).add( 4 ) ).bitXor( state ).mul( 277803737 );
- const result = word.shiftRight( 22 ).bitXor( word );
- return result.toFloat().mul( 1 / 2 ** 32 ); // Convert to range [0, 1)
- } );
- /**
- * A function that remaps the `[0,1]` interval into the `[0,1]` interval.
- * The corners are mapped to `0` and the center to `1`.
- * Reference: {@link https://iquilezles.org/articles/functions/}.
- *
- * @tsl
- * @function
- * @param {Node<float>} x - The value to remap.
- * @param {Node<float>} k - Allows to control the remapping functions shape by rising the parabola to a power `k`.
- * @return {Node<float>} The remapped value.
- */
- const parabola = ( x, k ) => pow( mul( 4.0, x.mul( sub( 1.0, x ) ) ), k );
- /**
- * A function that remaps the `[0,1]` interval into the `[0,1]` interval.
- * Expands the sides and compresses the center, and keeps `0.5` mapped to `0.5`.
- * Reference: {@link https://iquilezles.org/articles/functions/}.
- *
- * @tsl
- * @function
- * @param {Node<float>} x - The value to remap.
- * @param {Node<float>} k - `k=1` is the identity curve,`k<1` produces the classic `gain()` shape, and `k>1` produces "s" shaped curves.
- * @return {Node<float>} The remapped value.
- */
- const gain = ( x, k ) => x.lessThan( 0.5 ) ? parabola( x.mul( 2.0 ), k ).div( 2.0 ) : sub( 1.0, parabola( mul( sub( 1.0, x ), 2.0 ), k ).div( 2.0 ) );
- /**
- * A function that remaps the `[0,1]` interval into the `[0,1]` interval.
- * A generalization of the `parabola()`. Keeps the corners mapped to 0 but allows the control of the shape one either side of the curve.
- * Reference: {@link https://iquilezles.org/articles/functions/}.
- *
- * @tsl
- * @function
- * @param {Node<float>} x - The value to remap.
- * @param {Node<float>} a - First control parameter.
- * @param {Node<float>} b - Second control parameter.
- * @return {Node<float>} The remapped value.
- */
- const pcurve = ( x, a, b ) => pow( div( pow( x, a ), add( pow( x, a ), pow( sub( 1.0, x ), b ) ) ), 1.0 / a );
- /**
- * A phase shifted sinus curve that starts at zero and ends at zero, with bouncing behavior.
- * Reference: {@link https://iquilezles.org/articles/functions/}.
- *
- * @tsl
- * @function
- * @param {Node<float>} x - The value to compute the sin for.
- * @param {Node<float>} k - Controls the amount of bounces.
- * @return {Node<float>} The result value.
- */
- const sinc = ( x, k ) => sin( PI.mul( k.mul( x ).sub( 1.0 ) ) ).div( PI.mul( k.mul( x ).sub( 1.0 ) ) );
- /**
- * This node represents an operation that packs floating-point values of a vector into an unsigned 32-bit integer
- *
- * @augments TempNode
- */
- class PackFloatNode extends TempNode {
- static get type() {
- return 'PackFloatNode';
- }
- /**
- *
- * @param {'snorm' | 'unorm' | 'float16'} encoding - The numeric encoding that describes how the float values are mapped to the integer range.
- * @param {Node} vectorNode - The vector node to be packed
- */
- constructor( encoding, vectorNode ) {
- super();
- /**
- * The vector to be packed.
- *
- * @type {Node}
- */
- this.vectorNode = vectorNode;
- /**
- * The numeric encoding.
- *
- * @type {string}
- */
- this.encoding = encoding;
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isPackFloatNode = true;
- }
- getNodeType() {
- return 'uint';
- }
- generate( builder ) {
- const inputType = this.vectorNode.getNodeType( builder );
- return `${ builder.getFloatPackingMethod( this.encoding ) }(${ this.vectorNode.build( builder, inputType )})`;
- }
- }
- /**
- * Converts each component of the normalized float to 16-bit integer values. The results are packed into a single unsigned integer.
- * round(clamp(c, -1, +1) * 32767.0)
- *
- * @tsl
- * @function
- * @param {Node<vec2>} value - The 2-component vector to be packed
- * @returns {Node}
- */
- const packSnorm2x16 = /*@__PURE__*/ nodeProxyIntent( PackFloatNode, 'snorm' ).setParameterLength( 1 );
- /**
- * Converts each component of the normalized float to 16-bit integer values. The results are packed into a single unsigned integer.
- * round(clamp(c, 0, +1) * 65535.0)
- *
- * @tsl
- * @function
- * @param {Node<vec2>} value - The 2-component vector to be packed
- * @returns {Node}
- */
- const packUnorm2x16 = /*@__PURE__*/ nodeProxyIntent( PackFloatNode, 'unorm' ).setParameterLength( 1 );
- /**
- * Converts each component of the vec2 to 16-bit floating-point values. The results are packed into a single unsigned integer.
- *
- * @tsl
- * @function
- * @param {Node<vec2>} value - The 2-component vector to be packed
- * @returns {Node}
- */
- const packHalf2x16 = /*@__PURE__*/ nodeProxyIntent( PackFloatNode, 'float16' ).setParameterLength( 1 );
- /**
- * This node represents an operation that unpacks values from a 32-bit unsigned integer, reinterpreting the results as a floating-point vector
- *
- * @augments TempNode
- */
- class UnpackFloatNode extends TempNode {
- static get type() {
- return 'UnpackFloatNode';
- }
- /**
- *
- * @param {'snorm' | 'unorm' | 'float16'} encoding - The numeric encoding that describes how the integer values are mapped to the float range
- * @param {Node} uintNode - The uint node to be unpacked
- */
- constructor( encoding, uintNode ) {
- super();
- /**
- * The unsigned integer to be unpacked.
- *
- * @type {Node}
- */
- this.uintNode = uintNode;
- /**
- * The numeric encoding.
- *
- * @type {string}
- */
- this.encoding = encoding;
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isUnpackFloatNode = true;
- }
- getNodeType() {
- return 'vec2';
- }
- generate( builder ) {
- const inputType = this.uintNode.getNodeType( builder );
- return `${ builder.getFloatUnpackingMethod( this.encoding ) }(${ this.uintNode.build( builder, inputType )})`;
- }
- }
- /**
- * Unpacks a 32-bit unsigned integer into two 16-bit values, interpreted as normalized signed integers. Returns a vec2 with both values.
- *
- * @tsl
- * @function
- * @param {Node<uint>} value - The unsigned integer to be unpacked
- * @returns {Node}
- */
- const unpackSnorm2x16 = /*@__PURE__*/ nodeProxyIntent( UnpackFloatNode, 'snorm' ).setParameterLength( 1 );
- /**
- * Unpacks a 32-bit unsigned integer into two 16-bit values, interpreted as normalized unsigned integers. Returns a vec2 with both values.
- *
- * @tsl
- * @function
- * @param {Node<uint>} value - The unsigned integer to be unpacked
- * @returns {Node}
- */
- const unpackUnorm2x16 = /*@__PURE__*/ nodeProxyIntent( UnpackFloatNode, 'unorm' ).setParameterLength( 1 );
- /**
- * Unpacks a 32-bit unsigned integer into two 16-bit values, interpreted as 16-bit floating-point numbers. Returns a vec2 with both values.
- *
- * @tsl
- * @function
- * @param {Node<uint>} value - The unsigned integer to be unpacked
- * @returns {Node}
- */
- const unpackHalf2x16 = /*@__PURE__*/ nodeProxyIntent( UnpackFloatNode, 'float16' ).setParameterLength( 1 );
- // https://github.com/cabbibo/glsl-tri-noise-3d
- const tri = /*@__PURE__*/ Fn( ( [ x ] ) => {
- return x.fract().sub( .5 ).abs();
- } ).setLayout( {
- name: 'tri',
- type: 'float',
- inputs: [
- { name: 'x', type: 'float' }
- ]
- } );
- const tri3 = /*@__PURE__*/ Fn( ( [ p ] ) => {
- return vec3( tri( p.z.add( tri( p.y.mul( 1. ) ) ) ), tri( p.z.add( tri( p.x.mul( 1. ) ) ) ), tri( p.y.add( tri( p.x.mul( 1. ) ) ) ) );
- } ).setLayout( {
- name: 'tri3',
- type: 'vec3',
- inputs: [
- { name: 'p', type: 'vec3' }
- ]
- } );
- /**
- * Generates a noise value from the given position, speed and time parameters.
- *
- * @tsl
- * @function
- * @param {Node<vec3>} position - The position.
- * @param {Node<float>} speed - The speed.
- * @param {Node<float>} time - The time.
- * @return {Node<float>} The generated noise.
- */
- const triNoise3D = /*@__PURE__*/ Fn( ( [ position, speed, time ] ) => {
- const p = vec3( position ).toVar();
- const z = float( 1.4 ).toVar();
- const rz = float( 0.0 ).toVar();
- const bp = vec3( p ).toVar();
- Loop( { start: float( 0.0 ), end: float( 3.0 ), type: 'float', condition: '<=' }, () => {
- const dg = vec3( tri3( bp.mul( 2.0 ) ) ).toVar();
- p.addAssign( dg.add( time.mul( float( 0.1 ).mul( speed ) ) ) );
- bp.mulAssign( 1.8 );
- z.mulAssign( 1.5 );
- p.mulAssign( 1.2 );
- const t = float( tri( p.z.add( tri( p.x.add( tri( p.y ) ) ) ) ) ).toVar();
- rz.addAssign( t.div( z ) );
- bp.addAssign( 0.14 );
- } );
- return rz;
- } ).setLayout( {
- name: 'triNoise3D',
- type: 'float',
- inputs: [
- { name: 'position', type: 'vec3' },
- { name: 'speed', type: 'float' },
- { name: 'time', type: 'float' }
- ]
- } );
- /**
- * This class allows to define multiple overloaded versions
- * of the same function. Depending on the parameters of the function
- * call, the node picks the best-fit overloaded version.
- *
- * @augments Node
- */
- class FunctionOverloadingNode extends Node {
- static get type() {
- return 'FunctionOverloadingNode';
- }
- /**
- * Constructs a new function overloading node.
- *
- * @param {Array<Function>} functionNodes - Array of `Fn` function definitions.
- * @param {...Node} parametersNodes - A list of parameter nodes.
- */
- constructor( functionNodes = [], ...parametersNodes ) {
- super();
- /**
- * Array of `Fn` function definitions.
- *
- * @type {Array<Function>}
- */
- this.functionNodes = functionNodes;
- /**
- * A list of parameter nodes.
- *
- * @type {Array<Node>}
- */
- this.parametersNodes = parametersNodes;
- /**
- * The selected overloaded function call.
- *
- * @private
- * @type {ShaderCallNodeInternal}
- */
- this._candidateFn = null;
- /**
- * This node is marked as global.
- *
- * @type {boolean}
- * @default true
- */
- this.global = true;
- }
- /**
- * This method is overwritten since the node type is inferred from
- * the function's return type.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {string} The node type.
- */
- getNodeType( builder ) {
- const candidateFn = this.getCandidateFn( builder );
- return candidateFn.shaderNode.layout.type;
- }
- /**
- * Returns the candidate function for the current parameters.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {FunctionNode} The candidate function.
- */
- getCandidateFn( builder ) {
- const params = this.parametersNodes;
- let candidateFn = this._candidateFn;
- if ( candidateFn === null ) {
- let bestCandidateFn = null;
- let bestScore = -1;
- for ( const functionNode of this.functionNodes ) {
- const shaderNode = functionNode.shaderNode;
- const layout = shaderNode.layout;
- if ( layout === null ) {
- throw new Error( 'FunctionOverloadingNode: FunctionNode must be a layout.' );
- }
- const inputs = layout.inputs;
- if ( params.length === inputs.length ) {
- let currentScore = 0;
- for ( let i = 0; i < params.length; i ++ ) {
- const param = params[ i ];
- const input = inputs[ i ];
- if ( param.getNodeType( builder ) === input.type ) {
- currentScore ++;
- }
- }
- if ( currentScore > bestScore ) {
- bestCandidateFn = functionNode;
- bestScore = currentScore;
- }
- }
- }
- this._candidateFn = candidateFn = bestCandidateFn;
- }
- return candidateFn;
- }
- /**
- * Sets up the node for the current parameters.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {Node} The setup node.
- */
- setup( builder ) {
- const candidateFn = this.getCandidateFn( builder );
- return candidateFn( ...this.parametersNodes );
- }
- }
- const overloadingBaseFn = /*@__PURE__*/ nodeProxy( FunctionOverloadingNode );
- /**
- * TSL function for creating a function overloading node.
- *
- * @tsl
- * @function
- * @param {Array<Function>} functionNodes - Array of `Fn` function definitions.
- * @returns {FunctionOverloadingNode}
- */
- const overloadingFn = ( functionNodes ) => ( ...params ) => overloadingBaseFn( functionNodes, ...params );
- /**
- * Represents the elapsed time in seconds.
- *
- * @tsl
- * @type {UniformNode<float>}
- */
- const time = /*@__PURE__*/ uniform( 0 ).setGroup( renderGroup ).onRenderUpdate( ( frame ) => frame.time );
- /**
- * Represents the delta time in seconds.
- *
- * @tsl
- * @type {UniformNode<float>}
- */
- const deltaTime = /*@__PURE__*/ uniform( 0 ).setGroup( renderGroup ).onRenderUpdate( ( frame ) => frame.deltaTime );
- /**
- * Represents the current frame ID.
- *
- * @tsl
- * @type {UniformNode<uint>}
- */
- const frameId = /*@__PURE__*/ uniform( 0, 'uint' ).setGroup( renderGroup ).onRenderUpdate( ( frame ) => frame.frameId );
- /**
- * Generates a sine wave oscillation based on a timer.
- *
- * @tsl
- * @function
- * @param {Node<float>} t - The timer to generate the oscillation with.
- * @return {Node<float>} The oscillation node.
- */
- const oscSine = ( t = time ) => t.add( 0.75 ).mul( Math.PI * 2 ).sin().mul( 0.5 ).add( 0.5 );
- /**
- * Generates a square wave oscillation based on a timer.
- *
- * @tsl
- * @function
- * @param {Node<float>} t - The timer to generate the oscillation with.
- * @return {Node<float>} The oscillation node.
- */
- const oscSquare = ( t = time ) => t.fract().round();
- /**
- * Generates a triangle wave oscillation based on a timer.
- *
- * @tsl
- * @function
- * @param {Node<float>} t - The timer to generate the oscillation with.
- * @return {Node<float>} The oscillation node.
- */
- const oscTriangle = ( t = time ) => t.add( 0.5 ).fract().mul( 2 ).sub( 1 ).abs();
- /**
- * Generates a sawtooth wave oscillation based on a timer.
- *
- * @tsl
- * @function
- * @param {Node<float>} t - The timer to generate the oscillation with.
- * @return {Node<float>} The oscillation node.
- */
- const oscSawtooth = ( t = time ) => t.fract();
- /**
- * Replaces the default UV coordinates used in texture lookups.
- *
- * ```js
- *material.contextNode = replaceDefaultUV( ( textureNode ) => {
- *
- * // ...
- * return customUVCoordinates;
- *
- *} );
- *```
- *
- * @tsl
- * @function
- * @param {function(Node):Node<vec2>} callback - A callback that receives the texture node
- * and must return the new uv coordinates.
- * @param {Node} [node=null] - An optional node to which the context will be applied.
- * @return {ContextNode} A context node that replaces the default UV coordinates.
- */
- function replaceDefaultUV( callback, node = null ) {
- return context( node, { getUV: callback } );
- }
- /**
- * Rotates the given uv coordinates around a center point
- *
- * @tsl
- * @function
- * @param {Node<vec2>} uv - The uv coordinates.
- * @param {Node<float>} rotation - The rotation defined in radians.
- * @param {Node<vec2>} center - The center of rotation
- * @return {Node<vec2>} The rotated uv coordinates.
- */
- const rotateUV = /*@__PURE__*/ Fn( ( [ uv, rotation, center = vec2( 0.5 ) ] ) => {
- return rotate( uv.sub( center ), rotation ).add( center );
- } );
- /**
- * Applies a spherical warping effect to the given uv coordinates.
- *
- * @tsl
- * @function
- * @param {Node<vec2>} uv - The uv coordinates.
- * @param {Node<float>} strength - The strength of the effect.
- * @param {Node<vec2>} center - The center point
- * @return {Node<vec2>} The updated uv coordinates.
- */
- const spherizeUV = /*@__PURE__*/ Fn( ( [ uv, strength, center = vec2( 0.5 ) ] ) => {
- const delta = uv.sub( center );
- const delta2 = delta.dot( delta );
- const delta4 = delta2.mul( delta2 );
- const deltaOffset = delta4.mul( strength );
- return uv.add( delta.mul( deltaOffset ) );
- } );
- /**
- * This can be used to achieve a billboarding behavior for flat meshes. That means they are
- * oriented always towards the camera.
- *
- * ```js
- * material.vertexNode = billboarding();
- * ```
- *
- * @tsl
- * @function
- * @param {Object} config - The configuration object.
- * @param {?Node<vec3>} [config.position=null] - Can be used to define the vertex positions in world space.
- * @param {boolean} [config.horizontal=true] - Whether to follow the camera rotation horizontally or not.
- * @param {boolean} [config.vertical=false] - Whether to follow the camera rotation vertically or not.
- * @return {Node<vec3>} The updated vertex position in clip space.
- */
- const billboarding = /*@__PURE__*/ Fn( ( { position = null, horizontal = true, vertical = false } ) => {
- let worldMatrix;
- if ( position !== null ) {
- worldMatrix = modelWorldMatrix.toVar();
- worldMatrix[ 3 ][ 0 ] = position.x;
- worldMatrix[ 3 ][ 1 ] = position.y;
- worldMatrix[ 3 ][ 2 ] = position.z;
- } else {
- worldMatrix = modelWorldMatrix;
- }
- const modelViewMatrix = cameraViewMatrix.mul( worldMatrix );
- if ( defined( horizontal ) ) {
- modelViewMatrix[ 0 ][ 0 ] = modelWorldMatrix[ 0 ].length();
- modelViewMatrix[ 0 ][ 1 ] = 0;
- modelViewMatrix[ 0 ][ 2 ] = 0;
- }
- if ( defined( vertical ) ) {
- modelViewMatrix[ 1 ][ 0 ] = 0;
- modelViewMatrix[ 1 ][ 1 ] = modelWorldMatrix[ 1 ].length();
- modelViewMatrix[ 1 ][ 2 ] = 0;
- }
- modelViewMatrix[ 2 ][ 0 ] = 0;
- modelViewMatrix[ 2 ][ 1 ] = 0;
- modelViewMatrix[ 2 ][ 2 ] = 1;
- return cameraProjectionMatrix.mul( modelViewMatrix ).mul( positionLocal );
- } );
- /**
- * A special version of a screen uv function that involves a depth comparison
- * when computing the final uvs. The function mitigates visual errors when
- * using viewport texture nodes for refraction purposes. Without this function
- * objects in front of a refractive surface might appear on the refractive surface
- * which is incorrect.
- *
- * @tsl
- * @function
- * @param {?Node<vec2>} uv - Optional uv coordinates. By default `screenUV` is used.
- * @return {Node<vec2>} The update uv coordinates.
- */
- const viewportSafeUV = /*@__PURE__*/ Fn( ( [ uv = null ] ) => {
- const depth = linearDepth();
- const depthDiff = linearDepth( viewportDepthTexture( uv ) ).sub( depth );
- const finalUV = depthDiff.lessThan( 0 ).select( screenUV, uv );
- return finalUV;
- } );
- /**
- * Can be used to compute texture coordinates for animated sprite sheets.
- *
- * ```js
- * const uvNode = spritesheetUV( vec2( 6, 6 ), uv(), time.mul( animationSpeed ) );
- *
- * material.colorNode = texture( spriteSheet, uvNode );
- * ```
- *
- * @augments Node
- */
- class SpriteSheetUVNode extends Node {
- static get type() {
- return 'SpriteSheetUVNode';
- }
- /**
- * Constructs a new sprite sheet uv node.
- *
- * @param {Node<vec2>} countNode - The node that defines the number of sprites in the x and y direction (e.g 6x6).
- * @param {Node<vec2>} [uvNode=uv()] - The uv node.
- * @param {Node<float>} [frameNode=float()] - The node that defines the current frame/sprite.
- */
- constructor( countNode, uvNode = uv$1(), frameNode = float( 0 ) ) {
- super( 'vec2' );
- /**
- * The node that defines the number of sprites in the x and y direction (e.g 6x6).
- *
- * @type {Node<vec2>}
- */
- this.countNode = countNode;
- /**
- * The uv node.
- *
- * @type {Node<vec2>}
- */
- this.uvNode = uvNode;
- /**
- * The node that defines the current frame/sprite.
- *
- * @type {Node<float>}
- */
- this.frameNode = frameNode;
- }
- setup() {
- const { frameNode, uvNode, countNode } = this;
- const { width, height } = countNode;
- const frameNum = frameNode.mod( width.mul( height ) ).floor();
- const column = frameNum.mod( width );
- const row = height.sub( frameNum.add( 1 ).div( width ).ceil() );
- const scale = countNode.reciprocal();
- const uvFrameOffset = vec2( column, row );
- return uvNode.add( uvFrameOffset ).mul( scale );
- }
- }
- /**
- * TSL function for creating a sprite sheet uv node.
- *
- * @tsl
- * @function
- * @param {Node<vec2>} countNode - The node that defines the number of sprites in the x and y direction (e.g 6x6).
- * @param {?Node<vec2>} [uvNode=uv()] - The uv node.
- * @param {?Node<float>} [frameNode=float()] - The node that defines the current frame/sprite.
- * @returns {SpriteSheetUVNode}
- */
- const spritesheetUV = /*@__PURE__*/ nodeProxy( SpriteSheetUVNode ).setParameterLength( 3 );
- /**
- * TSL function for creating a triplanar textures node.
- *
- * Can be used for triplanar texture mapping.
- *
- * ```js
- * material.colorNode = triplanarTexture( texture( diffuseMap ) );
- * ```
- *
- * @tsl
- * @function
- * @param {Node} textureXNode - First texture node.
- * @param {?Node} [textureYNode=null] - Second texture node. When not set, the shader will sample from `textureXNode` instead.
- * @param {?Node} [textureZNode=null] - Third texture node. When not set, the shader will sample from `textureXNode` instead.
- * @param {?Node<float>} [scaleNode=float(1)] - The scale node.
- * @param {?Node<vec3>} [positionNode=positionLocal] - Vertex positions in local space.
- * @param {?Node<vec3>} [normalNode=normalLocal] - Normals in local space.
- * @returns {Node<vec4>}
- */
- const triplanarTextures = /*@__PURE__*/ Fn( ( [ textureXNode, textureYNode = null, textureZNode = null, scaleNode = float( 1 ), positionNode = positionLocal, normalNode = normalLocal ] ) => {
- // Reference: https://github.com/keijiro/StandardTriplanar
- // Blending factor of triplanar mapping
- let bf = normalNode.abs().normalize();
- bf = bf.div( bf.dot( vec3( 1.0 ) ) );
- // Triplanar mapping
- const tx = positionNode.yz.mul( scaleNode );
- const ty = positionNode.zx.mul( scaleNode );
- const tz = positionNode.xy.mul( scaleNode );
- // Base color
- const textureX = textureXNode.value;
- const textureY = textureYNode !== null ? textureYNode.value : textureX;
- const textureZ = textureZNode !== null ? textureZNode.value : textureX;
- const cx = texture( textureX, tx ).mul( bf.x );
- const cy = texture( textureY, ty ).mul( bf.y );
- const cz = texture( textureZ, tz ).mul( bf.z );
- return add( cx, cy, cz );
- } );
- /**
- * TSL function for creating a triplanar textures node.
- *
- * @tsl
- * @function
- * @param {Node} textureXNode - First texture node.
- * @param {?Node} [textureYNode=null] - Second texture node. When not set, the shader will sample from `textureXNode` instead.
- * @param {?Node} [textureZNode=null] - Third texture node. When not set, the shader will sample from `textureXNode` instead.
- * @param {?Node<float>} [scaleNode=float(1)] - The scale node.
- * @param {?Node<vec3>} [positionNode=positionLocal] - Vertex positions in local space.
- * @param {?Node<vec3>} [normalNode=normalLocal] - Normals in local space.
- * @returns {Node<vec4>}
- */
- const triplanarTexture = ( ...params ) => triplanarTextures( ...params );
- const _reflectorPlane = new Plane();
- const _normal = new Vector3();
- const _reflectorWorldPosition = new Vector3();
- const _cameraWorldPosition = new Vector3();
- const _rotationMatrix = new Matrix4();
- const _lookAtPosition = new Vector3( 0, 0, -1 );
- const clipPlane = new Vector4();
- const _view = new Vector3();
- const _target = new Vector3();
- const _q = new Vector4();
- const _size$2 = new Vector2();
- const _defaultRT = new RenderTarget();
- const _defaultUV = screenUV.flipX();
- _defaultRT.depthTexture = new DepthTexture( 1, 1 );
- let _inReflector = false;
- /**
- * This node can be used to implement mirror-like flat reflective surfaces.
- *
- * ```js
- * const groundReflector = reflector();
- * material.colorNode = groundReflector;
- *
- * const plane = new Mesh( geometry, material );
- * plane.add( groundReflector.target );
- * ```
- *
- * @augments TextureNode
- */
- class ReflectorNode extends TextureNode {
- static get type() {
- return 'ReflectorNode';
- }
- /**
- * Constructs a new reflector node.
- *
- * @param {Object} [parameters={}] - An object holding configuration parameters.
- * @param {Object3D} [parameters.target=new Object3D()] - The 3D object the reflector is linked to.
- * @param {number} [parameters.resolutionScale=1] - The resolution scale.
- * @param {boolean} [parameters.generateMipmaps=false] - Whether mipmaps should be generated or not.
- * @param {boolean} [parameters.bounces=true] - Whether reflectors can render other reflector nodes or not.
- * @param {boolean} [parameters.depth=false] - Whether depth data should be generated or not.
- * @param {number} [parameters.samples] - Anti-Aliasing samples of the internal render-target.
- * @param {TextureNode} [parameters.defaultTexture] - The default texture node.
- * @param {ReflectorBaseNode} [parameters.reflector] - The reflector base node.
- */
- constructor( parameters = {} ) {
- super( parameters.defaultTexture || _defaultRT.texture, _defaultUV );
- /**
- * A reference to the internal reflector base node which holds the actual implementation.
- *
- * @private
- * @type {ReflectorBaseNode}
- * @default ReflectorBaseNode
- */
- this._reflectorBaseNode = parameters.reflector || new ReflectorBaseNode( this, parameters );
- /**
- * A reference to the internal depth node.
- *
- * @private
- * @type {?Node}
- * @default null
- */
- this._depthNode = null;
- this.setUpdateMatrix( false );
- }
- /**
- * A reference to the internal reflector node.
- *
- * @type {ReflectorBaseNode}
- */
- get reflector() {
- return this._reflectorBaseNode;
- }
- /**
- * A reference to 3D object the reflector is linked to.
- *
- * @type {Object3D}
- */
- get target() {
- return this._reflectorBaseNode.target;
- }
- /**
- * Returns a node representing the mirror's depth. That can be used
- * to implement more advanced reflection effects like distance attenuation.
- *
- * @return {Node} The depth node.
- */
- getDepthNode() {
- if ( this._depthNode === null ) {
- if ( this._reflectorBaseNode.depth !== true ) {
- throw new Error( 'THREE.ReflectorNode: Depth node can only be requested when the reflector is created with { depth: true }. ' );
- }
- this._depthNode = nodeObject( new ReflectorNode( {
- defaultTexture: _defaultRT.depthTexture,
- reflector: this._reflectorBaseNode
- } ) );
- }
- return this._depthNode;
- }
- setup( builder ) {
- // ignore if used in post-processing
- if ( ! builder.object.isQuadMesh ) this._reflectorBaseNode.build( builder );
- return super.setup( builder );
- }
- clone() {
- const newNode = new this.constructor( this.reflectorNode );
- newNode.uvNode = this.uvNode;
- newNode.levelNode = this.levelNode;
- newNode.biasNode = this.biasNode;
- newNode.sampler = this.sampler;
- newNode.depthNode = this.depthNode;
- newNode.compareNode = this.compareNode;
- newNode.gradNode = this.gradNode;
- newNode.offsetNode = this.offsetNode;
- newNode._reflectorBaseNode = this._reflectorBaseNode;
- return newNode;
- }
- /**
- * Frees internal resources. Should be called when the node is no longer in use.
- */
- dispose() {
- super.dispose();
- this._reflectorBaseNode.dispose();
- }
- }
- /**
- * Holds the actual implementation of the reflector.
- *
- * TODO: Explain why `ReflectorBaseNode`. Originally the entire logic was implemented
- * in `ReflectorNode`, see #29619.
- *
- * @private
- * @augments Node
- */
- class ReflectorBaseNode extends Node {
- static get type() {
- return 'ReflectorBaseNode';
- }
- /**
- * Constructs a new reflector base node.
- *
- * @param {TextureNode} textureNode - Represents the rendered reflections as a texture node.
- * @param {Object} [parameters={}] - An object holding configuration parameters.
- * @param {Object3D} [parameters.target=new Object3D()] - The 3D object the reflector is linked to.
- * @param {number} [parameters.resolutionScale=1] - The resolution scale.
- * @param {boolean} [parameters.generateMipmaps=false] - Whether mipmaps should be generated or not.
- * @param {boolean} [parameters.bounces=true] - Whether reflectors can render other reflector nodes or not.
- * @param {boolean} [parameters.depth=false] - Whether depth data should be generated or not.
- * @param {number} [parameters.samples] - Anti-Aliasing samples of the internal render-target.
- */
- constructor( textureNode, parameters = {} ) {
- super();
- const {
- target = new Object3D(),
- resolutionScale = 1,
- generateMipmaps = false,
- bounces = true,
- depth = false,
- samples = 0
- } = parameters;
- /**
- * Represents the rendered reflections as a texture node.
- *
- * @type {TextureNode}
- */
- this.textureNode = textureNode;
- /**
- * The 3D object the reflector is linked to.
- *
- * @type {Object3D}
- * @default {new Object3D()}
- */
- this.target = target;
- /**
- * The resolution scale.
- *
- * @type {number}
- * @default {1}
- */
- this.resolutionScale = resolutionScale;
- if ( parameters.resolution !== undefined ) {
- warnOnce( 'ReflectorNode: The "resolution" parameter has been renamed to "resolutionScale".' ); // @deprecated r180
- this.resolutionScale = parameters.resolution;
- }
- /**
- * Whether mipmaps should be generated or not.
- *
- * @type {boolean}
- * @default {false}
- */
- this.generateMipmaps = generateMipmaps;
- /**
- * Whether reflectors can render other reflector nodes or not.
- *
- * @type {boolean}
- * @default {true}
- */
- this.bounces = bounces;
- /**
- * Whether depth data should be generated or not.
- *
- * @type {boolean}
- * @default {false}
- */
- this.depth = depth;
- /**
- * The number of anti-aliasing samples for the render-target
- *
- * @type {number}
- * @default {0}
- */
- this.samples = samples;
- /**
- * The `updateBeforeType` is set to `NodeUpdateType.RENDER` when {@link ReflectorBaseNode#bounces}
- * is `true`. Otherwise it's `NodeUpdateType.FRAME`.
- *
- * @type {string}
- * @default 'render'
- */
- this.updateBeforeType = bounces ? NodeUpdateType.RENDER : NodeUpdateType.FRAME;
- /**
- * Weak map for managing virtual cameras.
- *
- * @type {WeakMap<Camera, Camera>}
- */
- this.virtualCameras = new WeakMap();
- /**
- * Weak map for managing render targets.
- *
- * @type {Map<Camera, RenderTarget>}
- */
- this.renderTargets = new Map();
- /**
- * Force render even if reflector is facing away from camera.
- *
- * @type {boolean}
- * @default {false}
- */
- this.forceUpdate = false;
- /**
- * Whether the reflector has been rendered or not.
- *
- * When the reflector is facing away from the camera,
- * this flag is set to `false` and the texture will be empty(black).
- *
- * @type {boolean}
- * @default {false}
- */
- this.hasOutput = false;
- }
- /**
- * Updates the resolution of the internal render target.
- *
- * @private
- * @param {RenderTarget} renderTarget - The render target to resize.
- * @param {Renderer} renderer - The renderer that is used to determine the new size.
- */
- _updateResolution( renderTarget, renderer ) {
- const resolution = this.resolutionScale;
- renderer.getDrawingBufferSize( _size$2 );
- renderTarget.setSize( Math.round( _size$2.width * resolution ), Math.round( _size$2.height * resolution ) );
- }
- setup( builder ) {
- this._updateResolution( _defaultRT, builder.renderer );
- return super.setup( builder );
- }
- /**
- * Frees internal resources. Should be called when the node is no longer in use.
- */
- dispose() {
- super.dispose();
- for ( const renderTarget of this.renderTargets.values() ) {
- renderTarget.dispose();
- }
- }
- /**
- * Returns a virtual camera for the given camera. The virtual camera is used to
- * render the scene from the reflector's view so correct reflections can be produced.
- *
- * @param {Camera} camera - The scene's camera.
- * @return {Camera} The corresponding virtual camera.
- */
- getVirtualCamera( camera ) {
- let virtualCamera = this.virtualCameras.get( camera );
- if ( virtualCamera === undefined ) {
- virtualCamera = camera.clone();
- this.virtualCameras.set( camera, virtualCamera );
- }
- return virtualCamera;
- }
- /**
- * Returns a render target for the given camera. The reflections are rendered
- * into this render target.
- *
- * @param {Camera} camera - The scene's camera.
- * @return {RenderTarget} The render target.
- */
- getRenderTarget( camera ) {
- let renderTarget = this.renderTargets.get( camera );
- if ( renderTarget === undefined ) {
- renderTarget = new RenderTarget( 0, 0, { type: HalfFloatType, samples: this.samples } );
- if ( this.generateMipmaps === true ) {
- renderTarget.texture.minFilter = LinearMipMapLinearFilter;
- renderTarget.texture.generateMipmaps = true;
- }
- if ( this.depth === true ) {
- renderTarget.depthTexture = new DepthTexture();
- }
- this.renderTargets.set( camera, renderTarget );
- }
- return renderTarget;
- }
- updateBefore( frame ) {
- if ( this.bounces === false && _inReflector ) return false;
- _inReflector = true;
- const { scene, camera, renderer, material } = frame;
- const { target } = this;
- const virtualCamera = this.getVirtualCamera( camera );
- const renderTarget = this.getRenderTarget( virtualCamera );
- renderer.getDrawingBufferSize( _size$2 );
- this._updateResolution( renderTarget, renderer );
- //
- _reflectorWorldPosition.setFromMatrixPosition( target.matrixWorld );
- _cameraWorldPosition.setFromMatrixPosition( camera.matrixWorld );
- _rotationMatrix.extractRotation( target.matrixWorld );
- _normal.set( 0, 0, 1 );
- _normal.applyMatrix4( _rotationMatrix );
- _view.subVectors( _reflectorWorldPosition, _cameraWorldPosition );
- // Avoid rendering when reflector is facing away unless forcing an update
- const isFacingAway = _view.dot( _normal ) > 0;
- let needsClear = false;
- if ( isFacingAway === true && this.forceUpdate === false ) {
- if ( this.hasOutput === false ) {
- _inReflector = false;
- return;
- }
- needsClear = true;
- }
- _view.reflect( _normal ).negate();
- _view.add( _reflectorWorldPosition );
- _rotationMatrix.extractRotation( camera.matrixWorld );
- _lookAtPosition.set( 0, 0, -1 );
- _lookAtPosition.applyMatrix4( _rotationMatrix );
- _lookAtPosition.add( _cameraWorldPosition );
- _target.subVectors( _reflectorWorldPosition, _lookAtPosition );
- _target.reflect( _normal ).negate();
- _target.add( _reflectorWorldPosition );
- //
- virtualCamera.coordinateSystem = camera.coordinateSystem;
- virtualCamera.position.copy( _view );
- virtualCamera.up.set( 0, 1, 0 );
- virtualCamera.up.applyMatrix4( _rotationMatrix );
- virtualCamera.up.reflect( _normal );
- virtualCamera.lookAt( _target );
- virtualCamera.near = camera.near;
- virtualCamera.far = camera.far;
- virtualCamera.updateMatrixWorld();
- virtualCamera.projectionMatrix.copy( camera.projectionMatrix );
- // Now update projection matrix with new clip plane, implementing code from: http://www.terathon.com/code/oblique.html
- // Paper explaining this technique: http://www.terathon.com/lengyel/Lengyel-Oblique.pdf
- _reflectorPlane.setFromNormalAndCoplanarPoint( _normal, _reflectorWorldPosition );
- _reflectorPlane.applyMatrix4( virtualCamera.matrixWorldInverse );
- clipPlane.set( _reflectorPlane.normal.x, _reflectorPlane.normal.y, _reflectorPlane.normal.z, _reflectorPlane.constant );
- const projectionMatrix = virtualCamera.projectionMatrix;
- _q.x = ( Math.sign( clipPlane.x ) + projectionMatrix.elements[ 8 ] ) / projectionMatrix.elements[ 0 ];
- _q.y = ( Math.sign( clipPlane.y ) + projectionMatrix.elements[ 9 ] ) / projectionMatrix.elements[ 5 ];
- _q.z = -1;
- _q.w = ( 1.0 + projectionMatrix.elements[ 10 ] ) / projectionMatrix.elements[ 14 ];
- // Calculate the scaled plane vector
- clipPlane.multiplyScalar( 1.0 / clipPlane.dot( _q ) );
- const clipBias = 0;
- // Replacing the third row of the projection matrix
- projectionMatrix.elements[ 2 ] = clipPlane.x;
- projectionMatrix.elements[ 6 ] = clipPlane.y;
- projectionMatrix.elements[ 10 ] = ( renderer.coordinateSystem === WebGPUCoordinateSystem ) ? ( clipPlane.z - clipBias ) : ( clipPlane.z + 1.0 - clipBias );
- projectionMatrix.elements[ 14 ] = clipPlane.w;
- //
- this.textureNode.value = renderTarget.texture;
- if ( this.depth === true ) {
- this.textureNode.getDepthNode().value = renderTarget.depthTexture;
- }
- material.visible = false;
- const currentRenderTarget = renderer.getRenderTarget();
- const currentMRT = renderer.getMRT();
- const currentAutoClear = renderer.autoClear;
- renderer.setMRT( null );
- renderer.setRenderTarget( renderTarget );
- renderer.autoClear = true;
- const previousName = scene.name;
- scene.name = ( scene.name || 'Scene' ) + ' [ Reflector ]'; // TODO: Add bounce index
- if ( needsClear ) {
- renderer.clear();
- this.hasOutput = false;
- } else {
- renderer.render( scene, virtualCamera );
- this.hasOutput = true;
- }
- scene.name = previousName;
- renderer.setMRT( currentMRT );
- renderer.setRenderTarget( currentRenderTarget );
- renderer.autoClear = currentAutoClear;
- material.visible = true;
- _inReflector = false;
- this.forceUpdate = false;
- }
- /**
- * The resolution scale.
- *
- * @deprecated
- * @type {number}
- * @default {1}
- */
- get resolution() {
- warnOnce( 'ReflectorNode: The "resolution" property has been renamed to "resolutionScale".' ); // @deprecated r180
- return this.resolutionScale;
- }
- set resolution( value ) {
- warnOnce( 'ReflectorNode: The "resolution" property has been renamed to "resolutionScale".' ); // @deprecated r180
- this.resolutionScale = value;
- }
- }
- /**
- * TSL function for creating a reflector node.
- *
- * @tsl
- * @function
- * @param {Object} [parameters={}] - An object holding configuration parameters.
- * @param {Object3D} [parameters.target=new Object3D()] - The 3D object the reflector is linked to.
- * @param {number} [parameters.resolution=1] - The resolution scale.
- * @param {boolean} [parameters.generateMipmaps=false] - Whether mipmaps should be generated or not.
- * @param {boolean} [parameters.bounces=true] - Whether reflectors can render other reflector nodes or not.
- * @param {boolean} [parameters.depth=false] - Whether depth data should be generated or not.
- * @param {number} [parameters.samples] - Anti-Aliasing samples of the internal render-target.
- * @param {TextureNode} [parameters.defaultTexture] - The default texture node.
- * @param {ReflectorBaseNode} [parameters.reflector] - The reflector base node.
- * @returns {ReflectorNode}
- */
- const reflector = ( parameters ) => nodeObject( new ReflectorNode( parameters ) );
- const _camera = /*@__PURE__*/ new OrthographicCamera( -1, 1, 1, -1, 0, 1 );
- /**
- * The purpose of this special geometry is to fill the entire viewport with a single triangle.
- *
- * Reference: {@link https://github.com/mrdoob/three.js/pull/21358}
- *
- * @private
- * @augments BufferGeometry
- */
- class QuadGeometry extends BufferGeometry {
- /**
- * Constructs a new quad geometry.
- *
- * @param {boolean} [flipY=false] - Whether the uv coordinates should be flipped along the vertical axis or not.
- */
- constructor( flipY = false ) {
- super();
- const uv = flipY === false ? [ 0, -1, 0, 1, 2, 1 ] : [ 0, 2, 0, 0, 2, 0 ];
- this.setAttribute( 'position', new Float32BufferAttribute( [ -1, 3, 0, -1, -1, 0, 3, -1, 0 ], 3 ) );
- this.setAttribute( 'uv', new Float32BufferAttribute( uv, 2 ) );
- }
- }
- const _geometry = /*@__PURE__*/ new QuadGeometry();
- /**
- * This module is a helper for passes which need to render a full
- * screen effect which is quite common in context of post processing.
- *
- * The intended usage is to reuse a single quad mesh for rendering
- * subsequent passes by just reassigning the `material` reference.
- *
- * Note: This module can only be used with `WebGPURenderer`.
- *
- * @augments Mesh
- */
- class QuadMesh extends Mesh {
- /**
- * Constructs a new quad mesh.
- *
- * @param {?Material} [material=null] - The material to render the quad mesh with.
- */
- constructor( material = null ) {
- super( _geometry, material );
- /**
- * The camera to render the quad mesh with.
- *
- * @type {OrthographicCamera}
- * @readonly
- */
- this.camera = _camera;
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isQuadMesh = true;
- }
- /**
- * Async version of `render()`.
- *
- * @async
- * @deprecated
- * @param {Renderer} renderer - The renderer.
- * @return {Promise} A Promise that resolves when the render has been finished.
- */
- async renderAsync( renderer ) {
- warnOnce( 'QuadMesh: "renderAsync()" has been deprecated. Use "render()" and "await renderer.init();" when creating the renderer.' ); // @deprecated r181
- await renderer.init();
- renderer.render( this, _camera );
- }
- /**
- * Renders the quad mesh
- *
- * @param {Renderer} renderer - The renderer.
- */
- render( renderer ) {
- renderer.render( this, _camera );
- }
- }
- const _size$1 = /*@__PURE__*/ new Vector2();
- /**
- * `RTTNode` takes another node and uses it with a `QuadMesh` to render into a texture (RTT).
- * This module is especially relevant in context of post processing where certain nodes require
- * texture input for their effects. With the helper function `convertToTexture()` which is based
- * on this module, the node system can automatically ensure texture input if required.
- *
- * @augments TextureNode
- */
- class RTTNode extends TextureNode {
- static get type() {
- return 'RTTNode';
- }
- /**
- * Constructs a new RTT node.
- *
- * @param {Node} node - The node to render a texture with.
- * @param {?number} [width=null] - The width of the internal render target. If not width is applied, the render target is automatically resized.
- * @param {?number} [height=null] - The height of the internal render target.
- * @param {Object} [options={type:HalfFloatType}] - The options for the internal render target.
- */
- constructor( node, width = null, height = null, options = { type: HalfFloatType } ) {
- const renderTarget = new RenderTarget( width, height, options );
- super( renderTarget.texture, uv$1() );
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isRTTNode = true;
- /**
- * The node to render a texture with.
- *
- * @type {Node}
- */
- this.node = node;
- /**
- * The width of the internal render target.
- * If not width is applied, the render target is automatically resized.
- *
- * @type {?number}
- * @default null
- */
- this.width = width;
- /**
- * The height of the internal render target.
- *
- * @type {?number}
- * @default null
- */
- this.height = height;
- /**
- * The pixel ratio
- *
- * @type {number}
- * @default 1
- */
- this.pixelRatio = 1;
- /**
- * The render target
- *
- * @type {RenderTarget}
- */
- this.renderTarget = renderTarget;
- /**
- * Whether the texture requires an update or not.
- *
- * @type {boolean}
- * @default true
- */
- this.textureNeedsUpdate = true;
- /**
- * Whether the texture should automatically be updated or not.
- *
- * @type {boolean}
- * @default true
- */
- this.autoUpdate = true;
- /**
- * The node which is used with the quad mesh for RTT.
- *
- * @private
- * @type {Node}
- * @default null
- */
- this._rttNode = null;
- /**
- * The internal quad mesh for RTT.
- *
- * @private
- * @type {QuadMesh}
- */
- this._quadMesh = new QuadMesh( new NodeMaterial() );
- /**
- * The `updateBeforeType` is set to `NodeUpdateType.RENDER` since the node updates
- * the texture once per render in its {@link RTTNode#updateBefore} method.
- *
- * @type {string}
- * @default 'render'
- */
- this.updateBeforeType = NodeUpdateType.RENDER;
- }
- /**
- * Whether the internal render target should automatically be resized or not.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- get autoResize() {
- return this.width === null;
- }
- setup( builder ) {
- this._rttNode = this.node.context( builder.getSharedContext() );
- this._quadMesh.material.name = 'RTT';
- this._quadMesh.material.needsUpdate = true;
- return super.setup( builder );
- }
- /**
- * Sets the size of the internal render target
- *
- * @param {number} width - The width to set.
- * @param {number} height - The width to set.
- */
- setSize( width, height ) {
- this.width = width;
- this.height = height;
- const effectiveWidth = width * this.pixelRatio;
- const effectiveHeight = height * this.pixelRatio;
- this.renderTarget.setSize( effectiveWidth, effectiveHeight );
- this.textureNeedsUpdate = true;
- }
- /**
- * Sets the pixel ratio. This will also resize the render target.
- *
- * @param {number} pixelRatio - The pixel ratio to set.
- */
- setPixelRatio( pixelRatio ) {
- this.pixelRatio = pixelRatio;
- this.setSize( this.width, this.height );
- }
- updateBefore( { renderer } ) {
- if ( this.textureNeedsUpdate === false && this.autoUpdate === false ) return;
- this.textureNeedsUpdate = false;
- //
- if ( this.autoResize === true ) {
- const pixelRatio = renderer.getPixelRatio();
- const size = renderer.getSize( _size$1 );
- const effectiveWidth = Math.floor( size.width * pixelRatio );
- const effectiveHeight = Math.floor( size.height * pixelRatio );
- if ( effectiveWidth !== this.renderTarget.width || effectiveHeight !== this.renderTarget.height ) {
- this.renderTarget.setSize( effectiveWidth, effectiveHeight );
- this.textureNeedsUpdate = true;
- }
- }
- //
- let name = 'RTT';
- if ( this.node.name ) {
- name = this.node.name + ' [ ' + name + ' ]';
- }
- this._quadMesh.material.fragmentNode = this._rttNode;
- this._quadMesh.name = name;
- //
- const currentRenderTarget = renderer.getRenderTarget();
- renderer.setRenderTarget( this.renderTarget );
- this._quadMesh.render( renderer );
- renderer.setRenderTarget( currentRenderTarget );
- }
- clone() {
- const newNode = new TextureNode( this.value, this.uvNode, this.levelNode );
- newNode.sampler = this.sampler;
- newNode.referenceNode = this;
- return newNode;
- }
- }
- /**
- * TSL function for creating a RTT node.
- *
- * @tsl
- * @function
- * @param {Node} node - The node to render a texture with.
- * @param {?number} [width=null] - The width of the internal render target. If not width is applied, the render target is automatically resized.
- * @param {?number} [height=null] - The height of the internal render target.
- * @param {Object} [options={type:HalfFloatType}] - The options for the internal render target.
- * @returns {RTTNode}
- */
- const rtt = ( node, ...params ) => nodeObject( new RTTNode( nodeObject( node ), ...params ) );
- /**
- * TSL function for converting nodes to textures nodes.
- *
- * @tsl
- * @function
- * @param {Node} node - The node to render a texture with.
- * @param {?number} [width=null] - The width of the internal render target. If not width is applied, the render target is automatically resized.
- * @param {?number} [height=null] - The height of the internal render target.
- * @param {Object} [options={type:HalfFloatType}] - The options for the internal render target.
- * @returns {RTTNode}
- */
- const convertToTexture = ( node, ...params ) => {
- if ( node.isSampleNode || node.isTextureNode ) return node;
- if ( node.isPassNode ) return node.getTextureNode();
- return rtt( node, ...params );
- };
- /**
- * Computes a position in view space based on a fragment's screen position expressed as uv coordinates, the fragments
- * depth value and the camera's inverse projection matrix.
- *
- * @tsl
- * @function
- * @param {Node<vec2>} screenPosition - The fragment's screen position expressed as uv coordinates.
- * @param {Node<float>} depth - The fragment's depth value.
- * @param {Node<mat4>} projectionMatrixInverse - The camera's inverse projection matrix.
- * @return {Node<vec3>} The fragments position in view space.
- */
- const getViewPosition = /*@__PURE__*/ Fn( ( [ screenPosition, depth, projectionMatrixInverse ], builder ) => {
- let clipSpacePosition;
- if ( builder.renderer.coordinateSystem === WebGPUCoordinateSystem ) {
- screenPosition = vec2( screenPosition.x, screenPosition.y.oneMinus() ).mul( 2.0 ).sub( 1.0 );
- clipSpacePosition = vec4( vec3( screenPosition, depth ), 1.0 );
- } else {
- clipSpacePosition = vec4( vec3( screenPosition.x, screenPosition.y.oneMinus(), depth ).mul( 2.0 ).sub( 1.0 ), 1.0 );
- }
- const viewSpacePosition = vec4( projectionMatrixInverse.mul( clipSpacePosition ) );
- return viewSpacePosition.xyz.div( viewSpacePosition.w );
- } );
- /**
- * Computes a screen position expressed as uv coordinates based on a fragment's position in view space
- * and the camera's projection matrix
- *
- * @tsl
- * @function
- * @param {Node<vec3>} viewPosition - The fragments position in view space.
- * @param {Node<mat4>} projectionMatrix - The camera's projection matrix.
- * @return {Node<vec2>} The fragment's screen position expressed as uv coordinates.
- */
- const getScreenPosition = /*@__PURE__*/ Fn( ( [ viewPosition, projectionMatrix ] ) => {
- const sampleClipPos = projectionMatrix.mul( vec4( viewPosition, 1.0 ) );
- const sampleUv = sampleClipPos.xy.div( sampleClipPos.w ).mul( 0.5 ).add( 0.5 ).toVar();
- return vec2( sampleUv.x, sampleUv.y.oneMinus() );
- } );
- /**
- * Computes a normal vector based on depth data. Can be used as a fallback when no normal render
- * target is available or if flat surface normals are required.
- *
- * @tsl
- * @function
- * @param {Node<vec2>} uv - The texture coordinate.
- * @param {DepthTexture} depthTexture - The depth texture.
- * @param {Node<mat4>} projectionMatrixInverse - The camera's inverse projection matrix.
- * @return {Node<vec3>} The computed normal vector.
- */
- const getNormalFromDepth = /*@__PURE__*/ Fn( ( [ uv, depthTexture, projectionMatrixInverse ] ) => {
- const size = textureSize( textureLoad( depthTexture ) );
- const p = ivec2( uv.mul( size ) ).toVar();
- const c0 = textureLoad( depthTexture, p ).toVar();
- const l2 = textureLoad( depthTexture, p.sub( ivec2( 2, 0 ) ) ).toVar();
- const l1 = textureLoad( depthTexture, p.sub( ivec2( 1, 0 ) ) ).toVar();
- const r1 = textureLoad( depthTexture, p.add( ivec2( 1, 0 ) ) ).toVar();
- const r2 = textureLoad( depthTexture, p.add( ivec2( 2, 0 ) ) ).toVar();
- const b2 = textureLoad( depthTexture, p.add( ivec2( 0, 2 ) ) ).toVar();
- const b1 = textureLoad( depthTexture, p.add( ivec2( 0, 1 ) ) ).toVar();
- const t1 = textureLoad( depthTexture, p.sub( ivec2( 0, 1 ) ) ).toVar();
- const t2 = textureLoad( depthTexture, p.sub( ivec2( 0, 2 ) ) ).toVar();
- const dl = abs( sub( float( 2 ).mul( l1 ).sub( l2 ), c0 ) ).toVar();
- const dr = abs( sub( float( 2 ).mul( r1 ).sub( r2 ), c0 ) ).toVar();
- const db = abs( sub( float( 2 ).mul( b1 ).sub( b2 ), c0 ) ).toVar();
- const dt = abs( sub( float( 2 ).mul( t1 ).sub( t2 ), c0 ) ).toVar();
- const ce = getViewPosition( uv, c0, projectionMatrixInverse ).toVar();
- const dpdx = dl.lessThan( dr ).select( ce.sub( getViewPosition( uv.sub( vec2( float( 1 ).div( size.x ), 0 ) ), l1, projectionMatrixInverse ) ), ce.negate().add( getViewPosition( uv.add( vec2( float( 1 ).div( size.x ), 0 ) ), r1, projectionMatrixInverse ) ) );
- const dpdy = db.lessThan( dt ).select( ce.sub( getViewPosition( uv.add( vec2( 0, float( 1 ).div( size.y ) ) ), b1, projectionMatrixInverse ) ), ce.negate().add( getViewPosition( uv.sub( vec2( 0, float( 1 ).div( size.y ) ) ), t1, projectionMatrixInverse ) ) );
- return normalize( cross( dpdx, dpdy ) );
- } );
- /**
- * Interleaved Gradient Noise (IGN) from Jimenez 2014.
- *
- * IGN has "low discrepancy" resulting in evenly distributed samples. It's superior compared to
- * default white noise, blue noise or Bayer.
- *
- * References:
- * - {@link https://www.iryoku.com/next-generation-post-processing-in-call-of-duty-advanced-warfare/}
- * - {@link https://blog.demofox.org/2022/01/01/interleaved-gradient-noise-a-different-kind-of-low-discrepancy-sequence/}
- *
- * @tsl
- * @function
- * @param {Node<vec2>} position - The input position, usually screen coordinates.
- * @return {Node<float>} The noise value.
- */
- const interleavedGradientNoise = Fn( ( [ position ] ) => {
- return fract( float( 52.9829189 ).mul( fract( dot( position, vec2( 0.06711056, 0.00583715 ) ) ) ) );
- } ).setLayout( {
- name: 'interleavedGradientNoise',
- type: 'float',
- inputs: [
- { name: 'position', type: 'vec2' }
- ]
- } );
- /**
- * Vogel disk sampling for uniform circular distribution.
- *
- * This function generates sample points distributed uniformly on a disk using the golden angle,
- * resulting in an efficient low-discrepancy sequence for sampling. The rotation parameter (phi)
- * allows randomizing the pattern per-pixel when combined with IGN.
- *
- * @tsl
- * @function
- * @param {Node<int>} sampleIndex - The index of the current sample (0-based).
- * @param {Node<int>} samplesCount - The total number of samples.
- * @param {Node<float>} phi - Rotation angle in radians (typically from IGN * 2π).
- * @return {Node<vec2>} A 2D point on the unit disk.
- */
- const vogelDiskSample = Fn( ( [ sampleIndex, samplesCount, phi ] ) => {
- const goldenAngle = float( 2.399963229728653 ); // 2π * (2 - φ) where φ is golden ratio
- const r = sqrt( float( sampleIndex ).add( 0.5 ).div( float( samplesCount ) ) );
- const theta = float( sampleIndex ).mul( goldenAngle ).add( phi );
- return vec2( cos( theta ), sin( theta ) ).mul( r );
- } ).setLayout( {
- name: 'vogelDiskSample',
- type: 'vec2',
- inputs: [
- { name: 'sampleIndex', type: 'int' },
- { name: 'samplesCount', type: 'int' },
- { name: 'phi', type: 'float' }
- ]
- } );
- /**
- * Class representing a node that samples a value using a provided callback function.
- *
- * @extends Node
- */
- class SampleNode extends Node {
- /**
- * Returns the type of the node.
- *
- * @type {string}
- * @readonly
- * @static
- */
- static get type() {
- return 'SampleNode';
- }
- /**
- * Creates an instance of SampleNode.
- *
- * @param {Function} callback - The function to be called when sampling. Should accept a UV node and return a value.
- * @param {?Node<vec2>} [uvNode=null] - The UV node to be used in the texture sampling.
- */
- constructor( callback, uvNode = null ) {
- super();
- this.callback = callback;
- /**
- * Represents the texture coordinates.
- *
- * @type {?Node<vec2|vec3>}
- * @default null
- */
- this.uvNode = uvNode;
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isSampleNode = true;
- }
- /**
- * Sets up the node by sampling with the default UV accessor.
- *
- * @returns {Node} The result of the callback function when called with the UV node.
- */
- setup() {
- return this.sample( uv$1() );
- }
- /**
- * Calls the callback function with the provided UV node.
- *
- * @param {Node<vec2>} uv - The UV node or value to be passed to the callback.
- * @returns {Node} The result of the callback function.
- */
- sample( uv ) {
- return this.callback( uv );
- }
- }
- /**
- * Helper function to create a SampleNode wrapped as a node object.
- *
- * @function
- * @param {Function} callback - The function to be called when sampling. Should accept a UV node and return a value.
- * @param {?Node<vec2>} [uv=null] - The UV node to be used in the texture sampling.
- * @returns {SampleNode} The created SampleNode instance wrapped as a node object.
- */
- const sample = ( callback, uv = null ) => nodeObject( new SampleNode( callback, nodeObject( uv ) ) );
- /**
- * EventNode is a node that executes a callback during specific update phases.
- *
- * @augments Node
- */
- class EventNode extends Node {
- static get type() {
- return 'EventNode';
- }
- /**
- * Creates an EventNode.
- *
- * @param {string} eventType - The type of event
- * @param {Function} callback - The callback to execute on update.
- */
- constructor( eventType, callback ) {
- super( 'void' );
- this.eventType = eventType;
- this.callback = callback;
- if ( eventType === EventNode.OBJECT ) {
- this.updateType = NodeUpdateType.OBJECT;
- } else if ( eventType === EventNode.MATERIAL ) {
- this.updateType = NodeUpdateType.RENDER;
- } else if ( eventType === EventNode.BEFORE_OBJECT ) {
- this.updateBeforeType = NodeUpdateType.OBJECT;
- } else if ( eventType === EventNode.BEFORE_MATERIAL ) {
- this.updateBeforeType = NodeUpdateType.RENDER;
- }
- }
- update( frame ) {
- this.callback( frame );
- }
- updateBefore( frame ) {
- this.callback( frame );
- }
- }
- EventNode.OBJECT = 'object';
- EventNode.MATERIAL = 'material';
- EventNode.BEFORE_OBJECT = 'beforeObject';
- EventNode.BEFORE_MATERIAL = 'beforeMaterial';
- /**
- * Helper to create an EventNode and add it to the stack.
- *
- * @param {string} type - The event type.
- * @param {Function} callback - The callback function.
- * @returns {EventNode}
- */
- const createEvent = ( type, callback ) => nodeObject( new EventNode( type, callback ) ).toStack();
- /**
- * Creates an event that triggers a function every time an object (Mesh|Sprite) is rendered.
- *
- * The event will be bound to the declared TSL function `Fn()`; it must be declared within a `Fn()` or the JS function call must be inherited from one.
- *
- * @param {Function} callback - The callback function.
- * @returns {EventNode}
- */
- const OnObjectUpdate = ( callback ) => createEvent( EventNode.OBJECT, callback );
- /**
- * Creates an event that triggers a function when the first object that uses the material is rendered.
- *
- * The event will be bound to the declared TSL function `Fn()`; it must be declared within a `Fn()` or the JS function call must be inherited from one.
- *
- * @param {Function} callback - The callback function.
- * @returns {EventNode}
- */
- const OnMaterialUpdate = ( callback ) => createEvent( EventNode.MATERIAL, callback );
- /**
- * Creates an event that triggers a function before an object (Mesh|Sprite) is updated.
- *
- * The event will be bound to the declared TSL function `Fn()`; it must be declared within a `Fn()` or the JS function call must be inherited from one.
- *
- * @param {Function} callback - The callback function.
- * @returns {EventNode}
- */
- const OnBeforeObjectUpdate = ( callback ) => createEvent( EventNode.BEFORE_OBJECT, callback );
- /**
- * Creates an event that triggers a function before the material is updated.
- *
- * The event will be bound to the declared TSL function `Fn()`; it must be declared within a `Fn()` or the JS function call must be inherited from one.
- *
- * @param {Function} callback - The callback function.
- * @returns {EventNode}
- */
- const OnBeforeMaterialUpdate = ( callback ) => createEvent( EventNode.BEFORE_MATERIAL, callback );
- /**
- * This special type of instanced buffer attribute is intended for compute shaders.
- * In earlier three.js versions it was only possible to update attribute data
- * on the CPU via JavaScript and then upload the data to the GPU. With the
- * new material system and renderer it is now possible to use compute shaders
- * to compute the data for an attribute more efficiently on the GPU.
- *
- * The idea is to create an instance of this class and provide it as an input
- * to {@link StorageBufferNode}.
- *
- * Note: This type of buffer attribute can only be used with `WebGPURenderer`.
- *
- * @augments InstancedBufferAttribute
- */
- class StorageInstancedBufferAttribute extends InstancedBufferAttribute {
- /**
- * Constructs a new storage instanced buffer attribute.
- *
- * @param {number|TypedArray} count - The item count. It is also valid to pass a typed array as an argument.
- * The subsequent parameters are then obsolete.
- * @param {number} itemSize - The item size.
- * @param {TypedArray.constructor} [typeClass=Float32Array] - A typed array constructor.
- */
- constructor( count, itemSize, typeClass = Float32Array ) {
- const array = ArrayBuffer.isView( count ) ? count : new typeClass( count * itemSize );
- super( array, itemSize );
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isStorageInstancedBufferAttribute = true;
- }
- }
- /**
- * This special type of buffer attribute is intended for compute shaders.
- * In earlier three.js versions it was only possible to update attribute data
- * on the CPU via JavaScript and then upload the data to the GPU. With the
- * new material system and renderer it is now possible to use compute shaders
- * to compute the data for an attribute more efficiently on the GPU.
- *
- * The idea is to create an instance of this class and provide it as an input
- * to {@link StorageBufferNode}.
- *
- * Note: This type of buffer attribute can only be used with `WebGPURenderer`.
- *
- * @augments BufferAttribute
- */
- class StorageBufferAttribute extends BufferAttribute {
- /**
- * Constructs a new storage buffer attribute.
- *
- * @param {number|TypedArray} count - The item count. It is also valid to pass a typed array as an argument.
- * The subsequent parameters are then obsolete.
- * @param {number} itemSize - The item size.
- * @param {TypedArray.constructor} [typeClass=Float32Array] - A typed array constructor.
- */
- constructor( count, itemSize, typeClass = Float32Array ) {
- const array = ArrayBuffer.isView( count ) ? count : new typeClass( count * itemSize );
- super( array, itemSize );
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isStorageBufferAttribute = true;
- }
- }
- /**
- * TSL function for creating a storage buffer node with a configured `StorageBufferAttribute`.
- *
- * @tsl
- * @function
- * @param {number|TypedArray} count - The data count. It is also valid to pass a typed array as an argument.
- * @param {string|Struct} [type='float'] - The data type.
- * @returns {StorageBufferNode}
- */
- const attributeArray = ( count, type = 'float' ) => {
- let itemSize, typedArray;
- if ( type.isStruct === true ) {
- itemSize = type.layout.getLength();
- typedArray = getTypedArrayFromType( 'float' );
- } else {
- itemSize = getLengthFromType( type );
- typedArray = getTypedArrayFromType( type );
- }
- const buffer = new StorageBufferAttribute( count, itemSize, typedArray );
- const node = storage( buffer, type, count );
- return node;
- };
- /**
- * TSL function for creating a storage buffer node with a configured `StorageInstancedBufferAttribute`.
- *
- * @tsl
- * @function
- * @param {number|TypedArray} count - The data count. It is also valid to pass a typed array as an argument.
- * @param {string|Struct} [type='float'] - The data type.
- * @returns {StorageBufferNode}
- */
- const instancedArray = ( count, type = 'float' ) => {
- let itemSize, typedArray;
- if ( type.isStruct === true ) {
- itemSize = type.layout.getLength();
- typedArray = getTypedArrayFromType( 'float' );
- } else {
- itemSize = getLengthFromType( type );
- typedArray = getTypedArrayFromType( type );
- }
- const buffer = new StorageInstancedBufferAttribute( count, itemSize, typedArray );
- const node = storage( buffer, type, count );
- return node;
- };
- /**
- * A node for representing the uv coordinates of points.
- *
- * Can only be used with a WebGL backend. In WebGPU, point
- * primitives always have the size of one pixel and can thus
- * can't be used as sprite-like objects that display textures.
- *
- * @augments Node
- */
- class PointUVNode extends Node {
- static get type() {
- return 'PointUVNode';
- }
- /**
- * Constructs a new point uv node.
- */
- constructor() {
- super( 'vec2' );
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isPointUVNode = true;
- }
- generate( /*builder*/ ) {
- return 'vec2( gl_PointCoord.x, 1.0 - gl_PointCoord.y )';
- }
- }
- /**
- * TSL object that represents the uv coordinates of points.
- *
- * @tsl
- * @type {PointUVNode}
- */
- const pointUV = /*@__PURE__*/ nodeImmutable( PointUVNode );
- const _e1 = /*@__PURE__*/ new Euler();
- const _m1 = /*@__PURE__*/ new Matrix4();
- /**
- * This module allows access to a collection of scene properties. The following predefined TSL objects
- * are available for easier use:
- *
- * - `backgroundBlurriness`: A node that represents the scene's background blurriness.
- * - `backgroundIntensity`: A node that represents the scene's background intensity.
- * - `backgroundRotation`: A node that represents the scene's background rotation.
- *
- * @augments Node
- */
- class SceneNode extends Node {
- static get type() {
- return 'SceneNode';
- }
- /**
- * Constructs a new scene node.
- *
- * @param {('backgroundBlurriness'|'backgroundIntensity'|'backgroundRotation')} scope - The scope defines the type of scene property that is accessed.
- * @param {?Scene} [scene=null] - A reference to the scene.
- */
- constructor( scope = SceneNode.BACKGROUND_BLURRINESS, scene = null ) {
- super();
- /**
- * The scope defines the type of scene property that is accessed.
- *
- * @type {('backgroundBlurriness'|'backgroundIntensity'|'backgroundRotation')}
- */
- this.scope = scope;
- /**
- * A reference to the scene that is going to be accessed.
- *
- * @type {?Scene}
- * @default null
- */
- this.scene = scene;
- }
- /**
- * Depending on the scope, the method returns a different type of node that represents
- * the respective scene property.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {Node} The output node.
- */
- setup( builder ) {
- const scope = this.scope;
- const scene = this.scene !== null ? this.scene : builder.scene;
- let output;
- if ( scope === SceneNode.BACKGROUND_BLURRINESS ) {
- output = reference( 'backgroundBlurriness', 'float', scene );
- } else if ( scope === SceneNode.BACKGROUND_INTENSITY ) {
- output = reference( 'backgroundIntensity', 'float', scene );
- } else if ( scope === SceneNode.BACKGROUND_ROTATION ) {
- output = uniform( 'mat4' ).setName( 'backgroundRotation' ).setGroup( renderGroup ).onRenderUpdate( () => {
- const background = scene.background;
- if ( background !== null && background.isTexture && background.mapping !== UVMapping ) {
- _e1.copy( scene.backgroundRotation );
- // accommodate left-handed frame
- _e1.x *= -1; _e1.y *= -1; _e1.z *= -1;
- _m1.makeRotationFromEuler( _e1 );
- } else {
- _m1.identity();
- }
- return _m1;
- } );
- } else {
- error( 'SceneNode: Unknown scope:', scope );
- }
- return output;
- }
- }
- SceneNode.BACKGROUND_BLURRINESS = 'backgroundBlurriness';
- SceneNode.BACKGROUND_INTENSITY = 'backgroundIntensity';
- SceneNode.BACKGROUND_ROTATION = 'backgroundRotation';
- /**
- * TSL object that represents the scene's background blurriness.
- *
- * @tsl
- * @type {SceneNode}
- */
- const backgroundBlurriness = /*@__PURE__*/ nodeImmutable( SceneNode, SceneNode.BACKGROUND_BLURRINESS );
- /**
- * TSL object that represents the scene's background intensity.
- *
- * @tsl
- * @type {SceneNode}
- */
- const backgroundIntensity = /*@__PURE__*/ nodeImmutable( SceneNode, SceneNode.BACKGROUND_INTENSITY );
- /**
- * TSL object that represents the scene's background rotation.
- *
- * @tsl
- * @type {SceneNode}
- */
- const backgroundRotation = /*@__PURE__*/ nodeImmutable( SceneNode, SceneNode.BACKGROUND_ROTATION );
- /**
- * This special version of a texture node can be used to
- * write data into a storage texture with a compute shader.
- *
- * ```js
- * const storageTexture = new THREE.StorageTexture( width, height );
- *
- * const computeTexture = Fn( ( { storageTexture } ) => {
- *
- * const posX = instanceIndex.mod( width );
- * const posY = instanceIndex.div( width );
- * const indexUV = uvec2( posX, posY );
- *
- * // generate RGB values
- *
- * const r = 1;
- * const g = 1;
- * const b = 1;
- *
- * textureStore( storageTexture, indexUV, vec4( r, g, b, 1 ) ).toWriteOnly();
- *
- * } );
- *
- * const computeNode = computeTexture( { storageTexture } ).compute( width * height );
- * renderer.computeAsync( computeNode );
- * ```
- *
- * This node can only be used with a WebGPU backend.
- *
- * @augments TextureNode
- */
- class StorageTextureNode extends TextureNode {
- static get type() {
- return 'StorageTextureNode';
- }
- /**
- * Constructs a new storage texture node.
- *
- * @param {StorageTexture} value - The storage texture.
- * @param {Node<vec2|vec3>} uvNode - The uv node.
- * @param {?Node} [storeNode=null] - The value node that should be stored in the texture.
- */
- constructor( value, uvNode, storeNode = null ) {
- super( value, uvNode );
- /**
- * The value node that should be stored in the texture.
- *
- * @type {?Node}
- * @default null
- */
- this.storeNode = storeNode;
- /**
- * The mip level to write to for storage textures.
- *
- * @type {number}
- * @default 0
- */
- this.mipLevel = 0;
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isStorageTextureNode = true;
- /**
- * The access type of the texture node.
- *
- * @type {string}
- * @default 'writeOnly'
- */
- this.access = NodeAccess.WRITE_ONLY;
- }
- /**
- * Overwrites the default implementation to return a fixed value `'storageTexture'`.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {string} The input type.
- */
- getInputType( /*builder*/ ) {
- return 'storageTexture';
- }
- setup( builder ) {
- super.setup( builder );
- const properties = builder.getNodeProperties( this );
- properties.storeNode = this.storeNode;
- return properties;
- }
- /**
- * Defines the node access.
- *
- * @param {string} value - The node access.
- * @return {StorageTextureNode} A reference to this node.
- */
- setAccess( value ) {
- this.access = value;
- return this;
- }
- /**
- * Sets the mip level to write to.
- *
- * @param {number} level - The mip level.
- * @return {StorageTextureNode} A reference to this node.
- */
- setMipLevel( level ) {
- this.mipLevel = level;
- return this;
- }
- /**
- * Generates the code snippet of the storage node. If no `storeNode`
- * is defined, the texture node is generated as normal texture.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @param {string} output - The current output.
- * @return {string} The generated code snippet.
- */
- generate( builder, output ) {
- let snippet;
- if ( this.storeNode !== null ) {
- snippet = this.generateStore( builder );
- } else {
- snippet = super.generate( builder, output );
- }
- return snippet;
- }
- /**
- * Convenience method for configuring a read/write node access.
- *
- * @return {StorageTextureNode} A reference to this node.
- */
- toReadWrite() {
- return this.setAccess( NodeAccess.READ_WRITE );
- }
- /**
- * Convenience method for configuring a read-only node access.
- *
- * @return {StorageTextureNode} A reference to this node.
- */
- toReadOnly() {
- return this.setAccess( NodeAccess.READ_ONLY );
- }
- /**
- * Convenience method for configuring a write-only node access.
- *
- * @return {StorageTextureNode} A reference to this node.
- */
- toWriteOnly() {
- return this.setAccess( NodeAccess.WRITE_ONLY );
- }
- /**
- * Generates the code snippet of the storage texture node.
- *
- * @param {NodeBuilder} builder - The current node builder.
- */
- generateStore( builder ) {
- const properties = builder.getNodeProperties( this );
- const { uvNode, storeNode, depthNode } = properties;
- const textureProperty = super.generate( builder, 'property' );
- const uvSnippet = uvNode.build( builder, this.value.is3DTexture === true ? 'uvec3' : 'uvec2' );
- const storeSnippet = storeNode.build( builder, 'vec4' );
- const depthSnippet = depthNode ? depthNode.build( builder, 'int' ) : null;
- const snippet = builder.generateTextureStore( builder, textureProperty, uvSnippet, depthSnippet, storeSnippet );
- builder.addLineFlowCode( snippet, this );
- }
- clone() {
- const newNode = super.clone();
- newNode.storeNode = this.storeNode;
- newNode.mipLevel = this.mipLevel;
- return newNode;
- }
- }
- /**
- * TSL function for creating a storage texture node.
- *
- * @tsl
- * @function
- * @param {StorageTexture} value - The storage texture.
- * @param {?Node<vec2|vec3>} uvNode - The uv node.
- * @param {?Node} [storeNode=null] - The value node that should be stored in the texture.
- * @returns {StorageTextureNode}
- */
- const storageTexture = /*@__PURE__*/ nodeProxy( StorageTextureNode ).setParameterLength( 1, 3 );
- /**
- * TODO: Explain difference to `storageTexture()`.
- *
- * @tsl
- * @function
- * @param {StorageTexture} value - The storage texture.
- * @param {Node<vec2|vec3>} uvNode - The uv node.
- * @param {?Node} [storeNode=null] - The value node that should be stored in the texture.
- * @returns {StorageTextureNode}
- */
- const textureStore = ( value, uvNode, storeNode ) => {
- const node = storageTexture( value, uvNode, storeNode );
- if ( storeNode !== null ) node.toStack();
- return node;
- };
- const normal = Fn( ( { texture, uv } ) => {
- const epsilon = 0.0001;
- const ret = vec3().toVar();
- If( uv.x.lessThan( epsilon ), () => {
- ret.assign( vec3( 1, 0, 0 ) );
- } ).ElseIf( uv.y.lessThan( epsilon ), () => {
- ret.assign( vec3( 0, 1, 0 ) );
- } ).ElseIf( uv.z.lessThan( epsilon ), () => {
- ret.assign( vec3( 0, 0, 1 ) );
- } ).ElseIf( uv.x.greaterThan( 1 - epsilon ), () => {
- ret.assign( vec3( -1, 0, 0 ) );
- } ).ElseIf( uv.y.greaterThan( 1 - epsilon ), () => {
- ret.assign( vec3( 0, -1, 0 ) );
- } ).ElseIf( uv.z.greaterThan( 1 - epsilon ), () => {
- ret.assign( vec3( 0, 0, -1 ) );
- } ).Else( () => {
- const step = 0.01;
- const x = texture.sample( uv.add( vec3( - step, 0.0, 0.0 ) ) ).r.sub( texture.sample( uv.add( vec3( step, 0.0, 0.0 ) ) ).r );
- const y = texture.sample( uv.add( vec3( 0.0, - step, 0.0 ) ) ).r.sub( texture.sample( uv.add( vec3( 0.0, step, 0.0 ) ) ).r );
- const z = texture.sample( uv.add( vec3( 0.0, 0.0, - step ) ) ).r.sub( texture.sample( uv.add( vec3( 0.0, 0.0, step ) ) ).r );
- ret.assign( vec3( x, y, z ) );
- } );
- return ret.normalize();
- } );
- /**
- * This type of uniform node represents a 3D texture.
- *
- * @augments TextureNode
- */
- class Texture3DNode extends TextureNode {
- static get type() {
- return 'Texture3DNode';
- }
- /**
- * Constructs a new 3D texture node.
- *
- * @param {Data3DTexture} value - The 3D texture.
- * @param {?Node<vec2|vec3>} [uvNode=null] - The uv node.
- * @param {?Node<int>} [levelNode=null] - The level node.
- */
- constructor( value, uvNode = null, levelNode = null ) {
- super( value, uvNode, levelNode );
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isTexture3DNode = true;
- }
- /**
- * Overwrites the default implementation to return a fixed value `'texture3D'`.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {string} The input type.
- */
- getInputType( /*builder*/ ) {
- return 'texture3D';
- }
- /**
- * Returns a default uv node which is in context of 3D textures a three-dimensional
- * uv node.
- *
- * @return {Node<vec3>} The default uv node.
- */
- getDefaultUV() {
- return vec3( 0.5, 0.5, 0.5 );
- }
- /**
- * Overwritten with an empty implementation since the `updateMatrix` flag is ignored
- * for 3D textures. The uv transformation matrix is not applied to 3D textures.
- *
- * @param {boolean} value - The update toggle.
- */
- setUpdateMatrix( /*value*/ ) { } // Ignore .updateMatrix for 3d TextureNode
- /**
- * Overwrites the default implementation to return the unmodified uv node.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @param {Node} uvNode - The uv node to setup.
- * @return {Node} The unmodified uv node.
- */
- setupUV( builder, uvNode ) {
- const texture = this.value;
- if ( builder.isFlipY() && ( texture.isRenderTargetTexture === true || texture.isFramebufferTexture === true ) ) {
- if ( this.sampler ) {
- uvNode = uvNode.flipY();
- } else {
- uvNode = uvNode.setY( int( textureSize( this, this.levelNode ).y ).sub( uvNode.y ).sub( 1 ) );
- }
- }
- return uvNode;
- }
- /**
- * Generates the uv code snippet.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @param {Node} uvNode - The uv node to generate code for.
- * @return {string} The generated code snippet.
- */
- generateUV( builder, uvNode ) {
- return uvNode.build( builder, this.sampler === true ? 'vec3' : 'ivec3' );
- }
- /**
- * Generates the offset code snippet.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @param {Node} offsetNode - The offset node to generate code for.
- * @return {string} The generated code snippet.
- */
- generateOffset( builder, offsetNode ) {
- return offsetNode.build( builder, 'ivec3' );
- }
- /**
- * TODO.
- *
- * @param {Node<vec3>} uvNode - The uv node .
- * @return {Node<vec3>} TODO.
- */
- normal( uvNode ) {
- return normal( { texture: this, uv: uvNode } );
- }
- }
- /**
- * TSL function for creating a 3D texture node.
- *
- * @tsl
- * @function
- * @param {Data3DTexture} value - The 3D texture.
- * @param {?Node<vec3>} [uvNode=null] - The uv node.
- * @param {?Node<int>} [levelNode=null] - The level node.
- * @returns {Texture3DNode}
- */
- const texture3D = /*@__PURE__*/ nodeProxy( Texture3DNode ).setParameterLength( 1, 3 );
- /**
- * TSL function for creating a texture node that fetches/loads texels without interpolation.
- *
- * @tsl
- * @function
- * @param {?(Texture|TextureNode)} [value=EmptyTexture] - The texture.
- * @param {?Node<vec3>} [uvNode=null] - The uv node.
- * @param {?Node<int>} [levelNode=null] - The level node.
- * @param {?Node<float>} [biasNode=null] - The bias node.
- * @returns {TextureNode}
- */
- const texture3DLoad = ( ...params ) => texture3D( ...params ).setSampler( false );
- /**
- * TSL function for creating a texture node that fetches/loads texels without interpolation.
- *
- * @tsl
- * @function
- * @param {?(Texture|TextureNode)} [value=EmptyTexture] - The texture.
- * @param {?Node<vec3>} [uvNode=null] - The uv node.
- * @param {?Node<int>} [levelNode=null] - The level node.
- * @returns {TextureNode}
- */
- const texture3DLevel = ( value, uvNode, levelNode ) => texture3D( value, uvNode ).level( levelNode );
- /**
- * A special type of reference node that allows to link values in
- * `userData` fields to node objects.
- * ```js
- * sprite.userData.rotation = 1; // stores individual rotation per sprite
- *
- * const material = new THREE.SpriteNodeMaterial();
- * material.rotationNode = userData( 'rotation', 'float' );
- * ```
- * Since `UserDataNode` is extended from {@link ReferenceNode}, the node value
- * will automatically be updated when the `rotation` user data field changes.
- *
- * @augments ReferenceNode
- */
- class UserDataNode extends ReferenceNode {
- static get type() {
- return 'UserDataNode';
- }
- /**
- * Constructs a new user data node.
- *
- * @param {string} property - The property name that should be referenced by the node.
- * @param {string} inputType - The node data type of the reference.
- * @param {?Object} [userData=null] - A reference to the `userData` object. If not provided, the `userData` property of the 3D object that uses the node material is evaluated.
- */
- constructor( property, inputType, userData = null ) {
- super( property, inputType, userData );
- /**
- * A reference to the `userData` object. If not provided, the `userData`
- * property of the 3D object that uses the node material is evaluated.
- *
- * @type {?Object}
- * @default null
- */
- this.userData = userData;
- }
- /**
- * Overwritten to make sure {@link ReferenceNode#reference} points to the correct
- * `userData` field.
- *
- * @param {(NodeFrame|NodeBuilder)} state - The current state to evaluate.
- * @return {Object} A reference to the `userData` field.
- */
- updateReference( state ) {
- this.reference = this.userData !== null ? this.userData : state.object.userData;
- return this.reference;
- }
- }
- /**
- * TSL function for creating a user data node.
- *
- * @tsl
- * @function
- * @param {string} name - The property name that should be referenced by the node.
- * @param {string} inputType - The node data type of the reference.
- * @param {?Object} userData - A reference to the `userData` object. If not provided, the `userData` property of the 3D object that uses the node material is evaluated.
- * @returns {UserDataNode}
- */
- const userData = ( name, inputType, userData ) => nodeObject( new UserDataNode( name, inputType, userData ) );
- const _objectData = new WeakMap();
- /**
- * A node for representing motion or velocity vectors. Foundation
- * for advanced post processing effects like motion blur or TRAA.
- *
- * The node keeps track of the model, view and projection matrices
- * of the previous frame and uses them to compute offsets in NDC space.
- * These offsets represent the final velocity.
- *
- * @augments TempNode
- */
- class VelocityNode extends TempNode {
- static get type() {
- return 'VelocityNode';
- }
- /**
- * Constructs a new vertex color node.
- */
- constructor() {
- super( 'vec2' );
- /**
- * The current projection matrix.
- *
- * @type {?Matrix4}
- * @default null
- */
- this.projectionMatrix = null;
- /**
- * Overwritten since velocity nodes are updated per object.
- *
- * @type {string}
- * @default 'object'
- */
- this.updateType = NodeUpdateType.OBJECT;
- /**
- * Overwritten since velocity nodes save data after the update.
- *
- * @type {string}
- * @default 'object'
- */
- this.updateAfterType = NodeUpdateType.OBJECT;
- /**
- * Uniform node representing the previous model matrix in world space.
- *
- * @type {UniformNode<mat4>}
- * @default null
- */
- this.previousModelWorldMatrix = uniform( new Matrix4() );
- /**
- * Uniform node representing the previous projection matrix.
- *
- * @type {UniformNode<mat4>}
- * @default null
- */
- this.previousProjectionMatrix = uniform( new Matrix4() ).setGroup( renderGroup );
- /**
- * Uniform node representing the previous view matrix.
- *
- * @type {UniformNode<mat4>}
- * @default null
- */
- this.previousCameraViewMatrix = uniform( new Matrix4() );
- }
- /**
- * Sets the given projection matrix.
- *
- * @param {Matrix4} projectionMatrix - The projection matrix to set.
- */
- setProjectionMatrix( projectionMatrix ) {
- this.projectionMatrix = projectionMatrix;
- }
- /**
- * Updates velocity specific uniforms.
- *
- * @param {NodeFrame} frame - A reference to the current node frame.
- */
- update( { frameId, camera, object } ) {
- const previousModelMatrix = getPreviousMatrix( object );
- this.previousModelWorldMatrix.value.copy( previousModelMatrix );
- //
- const cameraData = getData( camera );
- if ( cameraData.frameId !== frameId ) {
- cameraData.frameId = frameId;
- if ( cameraData.previousProjectionMatrix === undefined ) {
- cameraData.previousProjectionMatrix = new Matrix4();
- cameraData.previousCameraViewMatrix = new Matrix4();
- cameraData.currentProjectionMatrix = new Matrix4();
- cameraData.currentCameraViewMatrix = new Matrix4();
- cameraData.previousProjectionMatrix.copy( this.projectionMatrix || camera.projectionMatrix );
- cameraData.previousCameraViewMatrix.copy( camera.matrixWorldInverse );
- } else {
- cameraData.previousProjectionMatrix.copy( cameraData.currentProjectionMatrix );
- cameraData.previousCameraViewMatrix.copy( cameraData.currentCameraViewMatrix );
- }
- cameraData.currentProjectionMatrix.copy( this.projectionMatrix || camera.projectionMatrix );
- cameraData.currentCameraViewMatrix.copy( camera.matrixWorldInverse );
- this.previousProjectionMatrix.value.copy( cameraData.previousProjectionMatrix );
- this.previousCameraViewMatrix.value.copy( cameraData.previousCameraViewMatrix );
- }
- }
- /**
- * Overwritten to updated velocity specific uniforms.
- *
- * @param {NodeFrame} frame - A reference to the current node frame.
- */
- updateAfter( { object } ) {
- getPreviousMatrix( object ).copy( object.matrixWorld );
- }
- /**
- * Implements the velocity computation based on the previous and current vertex data.
- *
- * @param {NodeBuilder} builder - A reference to the current node builder.
- * @return {Node<vec2>} The motion vector.
- */
- setup( /*builder*/ ) {
- const projectionMatrix = ( this.projectionMatrix === null ) ? cameraProjectionMatrix : uniform( this.projectionMatrix );
- const previousModelViewMatrix = this.previousCameraViewMatrix.mul( this.previousModelWorldMatrix );
- const clipPositionCurrent = projectionMatrix.mul( modelViewMatrix ).mul( positionLocal );
- const clipPositionPrevious = this.previousProjectionMatrix.mul( previousModelViewMatrix ).mul( positionPrevious );
- const ndcPositionCurrent = clipPositionCurrent.xy.div( clipPositionCurrent.w );
- const ndcPositionPrevious = clipPositionPrevious.xy.div( clipPositionPrevious.w );
- const velocity = sub( ndcPositionCurrent, ndcPositionPrevious );
- return velocity;
- }
- }
- function getData( object ) {
- let objectData = _objectData.get( object );
- if ( objectData === undefined ) {
- objectData = {};
- _objectData.set( object, objectData );
- }
- return objectData;
- }
- function getPreviousMatrix( object, index = 0 ) {
- const objectData = getData( object );
- let matrix = objectData[ index ];
- if ( matrix === undefined ) {
- objectData[ index ] = matrix = new Matrix4();
- objectData[ index ].copy( object.matrixWorld );
- }
- return matrix;
- }
- /**
- * TSL object that represents the velocity of a render pass.
- *
- * @tsl
- * @type {VelocityNode}
- */
- const velocity = /*@__PURE__*/ nodeImmutable( VelocityNode );
- /**
- * Computes a grayscale value for the given RGB color value.
- *
- * @tsl
- * @function
- * @param {Node<vec3>} color - The color value to compute the grayscale for.
- * @return {Node<vec3>} The grayscale color.
- */
- const grayscale = /*@__PURE__*/ Fn( ( [ color ] ) => {
- return luminance( color.rgb );
- } );
- /**
- * Super-saturates or desaturates the given RGB color.
- *
- * @tsl
- * @function
- * @param {Node<vec3>} color - The input color.
- * @param {Node<float>} [adjustment=1] - Specifies the amount of the conversion. A value under `1` desaturates the color, a value over `1` super-saturates it.
- * @return {Node<vec3>} The saturated color.
- */
- const saturation = /*@__PURE__*/ Fn( ( [ color, adjustment = float( 1 ) ] ) => {
- return adjustment.mix( luminance( color.rgb ), color.rgb );
- } );
- /**
- * Selectively enhance the intensity of less saturated RGB colors. Can result
- * in a more natural and visually appealing image with enhanced color depth
- * compared to {@link ColorAdjustment#saturation}.
- *
- * @tsl
- * @function
- * @param {Node<vec3>} color - The input color.
- * @param {Node<float>} [adjustment=1] - Controls the intensity of the vibrance effect.
- * @return {Node<vec3>} The updated color.
- */
- const vibrance = /*@__PURE__*/ Fn( ( [ color, adjustment = float( 1 ) ] ) => {
- const average = add( color.r, color.g, color.b ).div( 3.0 );
- const mx = color.r.max( color.g.max( color.b ) );
- const amt = mx.sub( average ).mul( adjustment ).mul( -3 );
- return mix( color.rgb, mx, amt );
- } );
- /**
- * Updates the hue component of the given RGB color while preserving its luminance and saturation.
- *
- * @tsl
- * @function
- * @param {Node<vec3>} color - The input color.
- * @param {Node<float>} [adjustment=1] - Defines the degree of hue rotation in radians. A positive value rotates the hue clockwise, while a negative value rotates it counterclockwise.
- * @return {Node<vec3>} The updated color.
- */
- const hue = /*@__PURE__*/ Fn( ( [ color, adjustment = float( 1 ) ] ) => {
- const k = vec3( 0.57735, 0.57735, 0.57735 );
- const cosAngle = adjustment.cos();
- return vec3( color.rgb.mul( cosAngle ).add( k.cross( color.rgb ).mul( adjustment.sin() ).add( k.mul( dot( k, color.rgb ).mul( cosAngle.oneMinus() ) ) ) ) );
- } );
- /**
- * Computes the luminance for the given RGB color value.
- *
- * @tsl
- * @function
- * @param {Node<vec3>} color - The color value to compute the luminance for.
- * @param {?Node<vec3>} luminanceCoefficients - The luminance coefficients. By default predefined values of the current working color space are used.
- * @return {Node<float>} The luminance.
- */
- const luminance = (
- color,
- luminanceCoefficients = vec3( ColorManagement.getLuminanceCoefficients( new Vector3() ) )
- ) => dot( color, luminanceCoefficients );
- /**
- * Color Decision List (CDL) v1.2
- *
- * Compact representation of color grading information, defined by slope, offset, power, and
- * saturation. The CDL should be typically be given input in a log space (such as LogC, ACEScc,
- * or AgX Log), and will return output in the same space. Output may require clamping >=0.
- *
- * @tsl
- * @function
- * @param {Node<vec4>} color Input (-Infinity < input < +Infinity)
- * @param {Node<vec3>} slope Slope (0 ≤ slope < +Infinity)
- * @param {Node<vec3>} offset Offset (-Infinity < offset < +Infinity; typically -1 < offset < 1)
- * @param {Node<vec3>} power Power (0 < power < +Infinity)
- * @param {Node<float>} saturation Saturation (0 ≤ saturation < +Infinity; typically 0 ≤ saturation < 4)
- * @param {Node<vec3>} luminanceCoefficients Luminance coefficients for saturation term, typically Rec. 709
- * @return {Node<vec4>} Output, -Infinity < output < +Infinity
- *
- * References:
- * - ASC CDL v1.2
- * - {@link https://blender.stackexchange.com/a/55239/43930}
- * - {@link https://docs.acescentral.com/specifications/acescc/}
- */
- const cdl = /*@__PURE__*/ Fn( ( [
- color,
- slope = vec3( 1 ),
- offset = vec3( 0 ),
- power = vec3( 1 ),
- saturation = float( 1 ),
- // ASC CDL v1.2 explicitly requires Rec. 709 luminance coefficients.
- luminanceCoefficients = vec3( ColorManagement.getLuminanceCoefficients( new Vector3(), LinearSRGBColorSpace ) )
- ] ) => {
- // NOTE: The ASC CDL v1.2 defines a [0, 1] clamp on the slope+offset term, and another on the
- // saturation term. Per the ACEScc specification and Filament, limits may be omitted to support
- // values outside [0, 1], requiring a workaround for negative values in the power expression.
- const luma = color.rgb.dot( vec3( luminanceCoefficients ) );
- const v = max$1( color.rgb.mul( slope ).add( offset ), 0.0 ).toVar();
- const pv = v.pow( power ).toVar();
- If( v.r.greaterThan( 0.0 ), () => { v.r.assign( pv.r ); } ); // eslint-disable-line
- If( v.g.greaterThan( 0.0 ), () => { v.g.assign( pv.g ); } ); // eslint-disable-line
- If( v.b.greaterThan( 0.0 ), () => { v.b.assign( pv.b ); } ); // eslint-disable-line
- v.assign( luma.add( v.sub( luma ).mul( saturation ) ) );
- return vec4( v.rgb, color.a );
- } );
- /**
- * Represents a posterize effect which reduces the number of colors
- * in an image, resulting in a more blocky and stylized appearance.
- *
- * @augments TempNode
- */
- class PosterizeNode extends TempNode {
- static get type() {
- return 'PosterizeNode';
- }
- /**
- * Constructs a new posterize node.
- *
- * @param {Node} sourceNode - The input color.
- * @param {Node} stepsNode - Controls the intensity of the posterization effect. A lower number results in a more blocky appearance.
- */
- constructor( sourceNode, stepsNode ) {
- super();
- /**
- * The input color.
- *
- * @type {Node}
- */
- this.sourceNode = sourceNode;
- /**
- * Controls the intensity of the posterization effect. A lower number results in a more blocky appearance.
- *
- * @type {Node}
- */
- this.stepsNode = stepsNode;
- }
- setup() {
- const { sourceNode, stepsNode } = this;
- return sourceNode.mul( stepsNode ).floor().div( stepsNode );
- }
- }
- /**
- * TSL function for creating a posterize node.
- *
- * @tsl
- * @function
- * @param {Node} sourceNode - The input color.
- * @param {Node} stepsNode - Controls the intensity of the posterization effect. A lower number results in a more blocky appearance.
- * @returns {PosterizeNode}
- */
- const posterize = /*@__PURE__*/ nodeProxy( PosterizeNode ).setParameterLength( 2 );
- const _size = /*@__PURE__*/ new Vector2();
- /**
- * Represents the texture of a pass node.
- *
- * @augments TextureNode
- */
- class PassTextureNode extends TextureNode {
- static get type() {
- return 'PassTextureNode';
- }
- /**
- * Constructs a new pass texture node.
- *
- * @param {PassNode} passNode - The pass node.
- * @param {Texture} texture - The output texture.
- */
- constructor( passNode, texture ) {
- super( texture );
- /**
- * A reference to the pass node.
- *
- * @type {PassNode}
- */
- this.passNode = passNode;
- this.setUpdateMatrix( false );
- }
- setup( builder ) {
- this.passNode.build( builder );
- return super.setup( builder );
- }
- clone() {
- return new this.constructor( this.passNode, this.value );
- }
- }
- /**
- * An extension of `PassTextureNode` which allows to manage more than one
- * internal texture. Relevant for the `getPreviousTexture()` related API.
- *
- * @augments PassTextureNode
- */
- class PassMultipleTextureNode extends PassTextureNode {
- static get type() {
- return 'PassMultipleTextureNode';
- }
- /**
- * Constructs a new pass texture node.
- *
- * @param {PassNode} passNode - The pass node.
- * @param {string} textureName - The output texture name.
- * @param {boolean} [previousTexture=false] - Whether previous frame data should be used or not.
- */
- constructor( passNode, textureName, previousTexture = false ) {
- // null is passed to the super call since this class does not
- // use an external texture for rendering pass data into. Instead
- // the texture is managed by the pass node itself
- super( passNode, null );
- /**
- * The output texture name.
- *
- * @type {string}
- */
- this.textureName = textureName;
- /**
- * Whether previous frame data should be used or not.
- *
- * @type {boolean}
- */
- this.previousTexture = previousTexture;
- }
- /**
- * Updates the texture reference of this node.
- */
- updateTexture() {
- this.value = this.previousTexture ? this.passNode.getPreviousTexture( this.textureName ) : this.passNode.getTexture( this.textureName );
- }
- setup( builder ) {
- this.updateTexture();
- return super.setup( builder );
- }
- clone() {
- const newNode = new this.constructor( this.passNode, this.textureName, this.previousTexture );
- newNode.uvNode = this.uvNode;
- newNode.levelNode = this.levelNode;
- newNode.biasNode = this.biasNode;
- newNode.sampler = this.sampler;
- newNode.depthNode = this.depthNode;
- newNode.compareNode = this.compareNode;
- newNode.gradNode = this.gradNode;
- newNode.offsetNode = this.offsetNode;
- return newNode;
- }
- }
- /**
- * Represents a render pass (sometimes called beauty pass) in context of post processing.
- * This pass produces a render for the given scene and camera and can provide multiple outputs
- * via MRT for further processing.
- *
- * ```js
- * const postProcessing = new PostProcessing( renderer );
- *
- * const scenePass = pass( scene, camera );
- *
- * postProcessing.outputNode = scenePass;
- * ```
- *
- * @augments TempNode
- */
- class PassNode extends TempNode {
- static get type() {
- return 'PassNode';
- }
- /**
- * Constructs a new pass node.
- *
- * @param {('color'|'depth')} scope - The scope of the pass. The scope determines whether the node outputs color or depth.
- * @param {Scene} scene - A reference to the scene.
- * @param {Camera} camera - A reference to the camera.
- * @param {Object} options - Options for the internal render target.
- */
- constructor( scope, scene, camera, options = {} ) {
- super( 'vec4' );
- /**
- * The scope of the pass. The scope determines whether the node outputs color or depth.
- *
- * @type {('color'|'depth')}
- */
- this.scope = scope;
- /**
- * A reference to the scene.
- *
- * @type {Scene}
- */
- this.scene = scene;
- /**
- * A reference to the camera.
- *
- * @type {Camera}
- */
- this.camera = camera;
- /**
- * Options for the internal render target.
- *
- * @type {Object}
- */
- this.options = options;
- /**
- * The pass's pixel ratio. Will be kept automatically kept in sync with the renderer's pixel ratio.
- *
- * @private
- * @type {number}
- * @default 1
- */
- this._pixelRatio = 1;
- /**
- * The pass's pixel width. Will be kept automatically kept in sync with the renderer's width.
- * @private
- * @type {number}
- * @default 1
- */
- this._width = 1;
- /**
- * The pass's pixel height. Will be kept automatically kept in sync with the renderer's height.
- * @private
- * @type {number}
- * @default 1
- */
- this._height = 1;
- const depthTexture = new DepthTexture();
- depthTexture.isRenderTargetTexture = true;
- //depthTexture.type = FloatType;
- depthTexture.name = 'depth';
- const renderTarget = new RenderTarget( this._width * this._pixelRatio, this._height * this._pixelRatio, { type: HalfFloatType, ...options, } );
- renderTarget.texture.name = 'output';
- renderTarget.depthTexture = depthTexture;
- /**
- * The pass's render target.
- *
- * @type {RenderTarget}
- */
- this.renderTarget = renderTarget;
- /**
- * An optional override material for the pass.
- *
- * @type {Material|null}
- */
- this.overrideMaterial = null;
- /**
- * Whether the pass is transparent.
- *
- * @type {boolean}
- * @default false
- */
- this.transparent = true;
- /**
- * Whether the pass is opaque.
- *
- * @type {boolean}
- * @default true
- */
- this.opaque = true;
- /**
- * An optional global context for the pass.
- *
- * @type {ContextNode|null}
- */
- this.contextNode = null;
- /**
- * A cache for the context node.
- *
- * @private
- * @type {?Object}
- * @default null
- */
- this._contextNodeCache = null;
- /**
- * A dictionary holding the internal result textures.
- *
- * @private
- * @type {Object<string, Texture>}
- */
- this._textures = {
- output: renderTarget.texture,
- depth: depthTexture
- };
- /**
- * A dictionary holding the internal texture nodes.
- *
- * @private
- * @type {Object<string, TextureNode>}
- */
- this._textureNodes = {};
- /**
- * A dictionary holding the internal depth nodes.
- *
- * @private
- * @type {Object}
- */
- this._linearDepthNodes = {};
- /**
- * A dictionary holding the internal viewZ nodes.
- *
- * @private
- * @type {Object}
- */
- this._viewZNodes = {};
- /**
- * A dictionary holding the texture data of the previous frame.
- * Used for computing velocity/motion vectors.
- *
- * @private
- * @type {Object<string, Texture>}
- */
- this._previousTextures = {};
- /**
- * A dictionary holding the texture nodes of the previous frame.
- * Used for computing velocity/motion vectors.
- *
- * @private
- * @type {Object<string, TextureNode>}
- */
- this._previousTextureNodes = {};
- /**
- * The `near` property of the camera as a uniform.
- *
- * @private
- * @type {UniformNode}
- */
- this._cameraNear = uniform( 0 );
- /**
- * The `far` property of the camera as a uniform.
- *
- * @private
- * @type {UniformNode}
- */
- this._cameraFar = uniform( 0 );
- /**
- * A MRT node configuring the MRT settings.
- *
- * @private
- * @type {?MRTNode}
- * @default null
- */
- this._mrt = null;
- /**
- * Layer object for configuring the camera that is used
- * to produce the pass.
- *
- * @private
- * @type {?Layers}
- * @default null
- */
- this._layers = null;
- /**
- * Scales the resolution of the internal render target.
- *
- * @private
- * @type {number}
- * @default 1
- */
- this._resolutionScale = 1;
- /**
- * Custom viewport definition.
- *
- * @private
- * @type {?Vector4}
- * @default null
- */
- this._viewport = null;
- /**
- * Custom scissor definition.
- *
- * @private
- * @type {?Vector4}
- * @default null
- */
- this._scissor = null;
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isPassNode = true;
- /**
- * The `updateBeforeType` is set to `NodeUpdateType.FRAME` since the node renders the
- * scene once per frame in its {@link PassNode#updateBefore} method.
- *
- * @type {string}
- * @default 'frame'
- */
- this.updateBeforeType = NodeUpdateType.FRAME;
- /**
- * This flag is used for global cache.
- *
- * @type {boolean}
- * @default true
- */
- this.global = true;
- }
- /**
- * Sets the resolution scale for the pass.
- * The resolution scale is a factor that is multiplied with the renderer's width and height.
- *
- * @param {number} resolutionScale - The resolution scale to set. A value of `1` means full resolution.
- * @return {PassNode} A reference to this pass.
- */
- setResolutionScale( resolutionScale ) {
- this._resolutionScale = resolutionScale;
- return this;
- }
- /**
- * Gets the current resolution scale of the pass.
- *
- * @return {number} The current resolution scale. A value of `1` means full resolution.
- */
- getResolutionScale() {
- return this._resolutionScale;
- }
- /**
- * Sets the resolution for the pass.
- * The resolution is a factor that is multiplied with the renderer's width and height.
- *
- * @param {number} resolution - The resolution to set. A value of `1` means full resolution.
- * @return {PassNode} A reference to this pass.
- * @deprecated since r181. Use {@link PassNode#setResolutionScale `setResolutionScale()`} instead.
- */
- setResolution( resolution ) { // @deprecated, r181
- warn( 'PassNode: .setResolution() is deprecated. Use .setResolutionScale() instead.' );
- return this.setResolutionScale( resolution );
- }
- /**
- * Gets the current resolution of the pass.
- *
- * @return {number} The current resolution. A value of `1` means full resolution.
- * @deprecated since r181. Use {@link PassNode#getResolutionScale `getResolutionScale()`} instead.
- */
- getResolution() { // @deprecated, r181
- warn( 'PassNode: .getResolution() is deprecated. Use .getResolutionScale() instead.' );
- return this.getResolutionScale();
- }
- /**
- * Sets the layer configuration that should be used when rendering the pass.
- *
- * @param {Layers} layers - The layers object to set.
- * @return {PassNode} A reference to this pass.
- */
- setLayers( layers ) {
- this._layers = layers;
- return this;
- }
- /**
- * Gets the current layer configuration of the pass.
- *
- * @return {?Layers} .
- */
- getLayers() {
- return this._layers;
- }
- /**
- * Sets the given MRT node to setup MRT for this pass.
- *
- * @param {MRTNode} mrt - The MRT object.
- * @return {PassNode} A reference to this pass.
- */
- setMRT( mrt ) {
- this._mrt = mrt;
- return this;
- }
- /**
- * Returns the current MRT node.
- *
- * @return {MRTNode} The current MRT node.
- */
- getMRT() {
- return this._mrt;
- }
- /**
- * Returns the texture for the given output name.
- *
- * @param {string} name - The output name to get the texture for.
- * @return {Texture} The texture.
- */
- getTexture( name ) {
- let texture = this._textures[ name ];
- if ( texture === undefined ) {
- const refTexture = this.renderTarget.texture;
- texture = refTexture.clone();
- texture.name = name;
- this._textures[ name ] = texture;
- this.renderTarget.textures.push( texture );
- }
- return texture;
- }
- /**
- * Returns the texture holding the data of the previous frame for the given output name.
- *
- * @param {string} name - The output name to get the texture for.
- * @return {Texture} The texture holding the data of the previous frame.
- */
- getPreviousTexture( name ) {
- let texture = this._previousTextures[ name ];
- if ( texture === undefined ) {
- texture = this.getTexture( name ).clone();
- this._previousTextures[ name ] = texture;
- }
- return texture;
- }
- /**
- * Switches current and previous textures for the given output name.
- *
- * @param {string} name - The output name.
- */
- toggleTexture( name ) {
- const prevTexture = this._previousTextures[ name ];
- if ( prevTexture !== undefined ) {
- const texture = this._textures[ name ];
- const index = this.renderTarget.textures.indexOf( texture );
- this.renderTarget.textures[ index ] = prevTexture;
- this._textures[ name ] = prevTexture;
- this._previousTextures[ name ] = texture;
- this._textureNodes[ name ].updateTexture();
- this._previousTextureNodes[ name ].updateTexture();
- }
- }
- /**
- * Returns the texture node for the given output name.
- *
- * @param {string} [name='output'] - The output name to get the texture node for.
- * @return {TextureNode} The texture node.
- */
- getTextureNode( name = 'output' ) {
- let textureNode = this._textureNodes[ name ];
- if ( textureNode === undefined ) {
- textureNode = nodeObject( new PassMultipleTextureNode( this, name ) );
- textureNode.updateTexture();
- this._textureNodes[ name ] = textureNode;
- }
- return textureNode;
- }
- /**
- * Returns the previous texture node for the given output name.
- *
- * @param {string} [name='output'] - The output name to get the previous texture node for.
- * @return {TextureNode} The previous texture node.
- */
- getPreviousTextureNode( name = 'output' ) {
- let textureNode = this._previousTextureNodes[ name ];
- if ( textureNode === undefined ) {
- if ( this._textureNodes[ name ] === undefined ) this.getTextureNode( name );
- textureNode = nodeObject( new PassMultipleTextureNode( this, name, true ) );
- textureNode.updateTexture();
- this._previousTextureNodes[ name ] = textureNode;
- }
- return textureNode;
- }
- /**
- * Returns a viewZ node of this pass.
- *
- * @param {string} [name='depth'] - The output name to get the viewZ node for. In most cases the default `'depth'` can be used however the parameter exists for custom depth outputs.
- * @return {Node} The viewZ node.
- */
- getViewZNode( name = 'depth' ) {
- let viewZNode = this._viewZNodes[ name ];
- if ( viewZNode === undefined ) {
- const cameraNear = this._cameraNear;
- const cameraFar = this._cameraFar;
- this._viewZNodes[ name ] = viewZNode = perspectiveDepthToViewZ( this.getTextureNode( name ), cameraNear, cameraFar );
- }
- return viewZNode;
- }
- /**
- * Returns a linear depth node of this pass.
- *
- * @param {string} [name='depth'] - The output name to get the linear depth node for. In most cases the default `'depth'` can be used however the parameter exists for custom depth outputs.
- * @return {Node} The linear depth node.
- */
- getLinearDepthNode( name = 'depth' ) {
- let linearDepthNode = this._linearDepthNodes[ name ];
- if ( linearDepthNode === undefined ) {
- const cameraNear = this._cameraNear;
- const cameraFar = this._cameraFar;
- const viewZNode = this.getViewZNode( name );
- // TODO: just if ( builder.camera.isPerspectiveCamera )
- this._linearDepthNodes[ name ] = linearDepthNode = viewZToOrthographicDepth( viewZNode, cameraNear, cameraFar );
- }
- return linearDepthNode;
- }
- /**
- * Precompiles the pass.
- *
- * Note that this method must be called after the pass configuration is complete.
- * So calls like `setMRT()` and `getTextureNode()` must proceed the precompilation.
- *
- * @async
- * @param {Renderer} renderer - The renderer.
- * @return {Promise} A Promise that resolves when the compile has been finished.
- * @see {@link Renderer#compileAsync}
- */
- async compileAsync( renderer ) {
- const currentRenderTarget = renderer.getRenderTarget();
- const currentMRT = renderer.getMRT();
- renderer.setRenderTarget( this.renderTarget );
- renderer.setMRT( this._mrt );
- await renderer.compileAsync( this.scene, this.camera );
- renderer.setRenderTarget( currentRenderTarget );
- renderer.setMRT( currentMRT );
- }
- setup( { renderer } ) {
- this.renderTarget.samples = this.options.samples === undefined ? renderer.samples : this.options.samples;
- this.renderTarget.texture.type = renderer.getOutputBufferType();
- return this.scope === PassNode.COLOR ? this.getTextureNode() : this.getLinearDepthNode();
- }
- updateBefore( frame ) {
- const { renderer } = frame;
- const { scene } = this;
- let camera;
- let pixelRatio;
- const outputRenderTarget = renderer.getOutputRenderTarget();
- if ( outputRenderTarget && outputRenderTarget.isXRRenderTarget === true ) {
- pixelRatio = 1;
- camera = renderer.xr.getCamera();
- renderer.xr.updateCamera( camera );
- _size.set( outputRenderTarget.width, outputRenderTarget.height );
- } else {
- camera = this.camera;
- pixelRatio = renderer.getPixelRatio();
- renderer.getSize( _size );
- }
- this._pixelRatio = pixelRatio;
- this.setSize( _size.width, _size.height );
- const currentRenderTarget = renderer.getRenderTarget();
- const currentMRT = renderer.getMRT();
- const currentAutoClear = renderer.autoClear;
- const currentTransparent = renderer.transparent;
- const currentOpaque = renderer.opaque;
- const currentMask = camera.layers.mask;
- const currentContextNode = renderer.contextNode;
- const currentOverrideMaterial = scene.overrideMaterial;
- this._cameraNear.value = camera.near;
- this._cameraFar.value = camera.far;
- if ( this._layers !== null ) {
- camera.layers.mask = this._layers.mask;
- }
- for ( const name in this._previousTextures ) {
- this.toggleTexture( name );
- }
- if ( this.overrideMaterial !== null ) {
- scene.overrideMaterial = this.overrideMaterial;
- }
- renderer.setRenderTarget( this.renderTarget );
- renderer.setMRT( this._mrt );
- renderer.autoClear = true;
- renderer.transparent = this.transparent;
- renderer.opaque = this.opaque;
- if ( this.contextNode !== null ) {
- if ( this._contextNodeCache === null || this._contextNodeCache.version !== this.version ) {
- this._contextNodeCache = {
- version: this.version,
- context: context( { ...renderer.contextNode.getFlowContextData(), ...this.contextNode.getFlowContextData() } )
- };
- }
- renderer.contextNode = this._contextNodeCache.context;
- }
- const currentSceneName = scene.name;
- scene.name = this.name ? this.name : scene.name;
- renderer.render( scene, camera );
- scene.name = currentSceneName;
- scene.overrideMaterial = currentOverrideMaterial;
- renderer.setRenderTarget( currentRenderTarget );
- renderer.setMRT( currentMRT );
- renderer.autoClear = currentAutoClear;
- renderer.transparent = currentTransparent;
- renderer.opaque = currentOpaque;
- renderer.contextNode = currentContextNode;
- camera.layers.mask = currentMask;
- }
- /**
- * Sets the size of the pass's render target. Honors the pixel ratio.
- *
- * @param {number} width - The width to set.
- * @param {number} height - The height to set.
- */
- setSize( width, height ) {
- this._width = width;
- this._height = height;
- const effectiveWidth = Math.floor( this._width * this._pixelRatio * this._resolutionScale );
- const effectiveHeight = Math.floor( this._height * this._pixelRatio * this._resolutionScale );
- this.renderTarget.setSize( effectiveWidth, effectiveHeight );
- if ( this._scissor !== null ) this.renderTarget.scissor.copy( this._scissor );
- if ( this._viewport !== null ) this.renderTarget.viewport.copy( this._viewport );
- }
- /**
- * This method allows to define the pass's scissor rectangle. By default, the scissor rectangle is kept
- * in sync with the pass's dimensions. To reverse the process and use auto-sizing again, call the method
- * with `null` as the single argument.
- *
- * @param {?(number | Vector4)} x - The horizontal coordinate for the lower left corner of the box in logical pixel unit.
- * Instead of passing four arguments, the method also works with a single four-dimensional vector.
- * @param {number} y - The vertical coordinate for the lower left corner of the box in logical pixel unit.
- * @param {number} width - The width of the scissor box in logical pixel unit.
- * @param {number} height - The height of the scissor box in logical pixel unit.
- */
- setScissor( x, y, width, height ) {
- if ( x === null ) {
- this._scissor = null;
- } else {
- if ( this._scissor === null ) this._scissor = new Vector4();
- if ( x.isVector4 ) {
- this._scissor.copy( x );
- } else {
- this._scissor.set( x, y, width, height );
- }
- this._scissor.multiplyScalar( this._pixelRatio * this._resolutionScale ).floor();
- }
- }
- /**
- * This method allows to define the pass's viewport. By default, the viewport is kept in sync
- * with the pass's dimensions. To reverse the process and use auto-sizing again, call the method
- * with `null` as the single argument.
- *
- * @param {number | Vector4} x - The horizontal coordinate for the lower left corner of the viewport origin in logical pixel unit.
- * @param {number} y - The vertical coordinate for the lower left corner of the viewport origin in logical pixel unit.
- * @param {number} width - The width of the viewport in logical pixel unit.
- * @param {number} height - The height of the viewport in logical pixel unit.
- */
- setViewport( x, y, width, height ) {
- if ( x === null ) {
- this._viewport = null;
- } else {
- if ( this._viewport === null ) this._viewport = new Vector4();
- if ( x.isVector4 ) {
- this._viewport.copy( x );
- } else {
- this._viewport.set( x, y, width, height );
- }
- this._viewport.multiplyScalar( this._pixelRatio * this._resolutionScale ).floor();
- }
- }
- /**
- * Sets the pixel ratio the pass's render target and updates the size.
- *
- * @param {number} pixelRatio - The pixel ratio to set.
- */
- setPixelRatio( pixelRatio ) {
- this._pixelRatio = pixelRatio;
- this.setSize( this._width, this._height );
- }
- /**
- * Frees internal resources. Should be called when the node is no longer in use.
- */
- dispose() {
- this.renderTarget.dispose();
- }
- }
- /**
- * @static
- * @type {'color'}
- * @default 'color'
- */
- PassNode.COLOR = 'color';
- /**
- * @static
- * @type {'depth'}
- * @default 'depth'
- */
- PassNode.DEPTH = 'depth';
- /**
- * TSL function for creating a pass node.
- *
- * @tsl
- * @function
- * @param {Scene} scene - A reference to the scene.
- * @param {Camera} camera - A reference to the camera.
- * @param {Object} options - Options for the internal render target.
- * @returns {PassNode}
- */
- const pass = ( scene, camera, options ) => nodeObject( new PassNode( PassNode.COLOR, scene, camera, options ) );
- /**
- * TSL function for creating a pass texture node.
- *
- * @tsl
- * @function
- * @param {PassNode} pass - The pass node.
- * @param {Texture} texture - The output texture.
- * @returns {PassTextureNode}
- */
- const passTexture = ( pass, texture ) => nodeObject( new PassTextureNode( pass, texture ) );
- /**
- * TSL function for creating a depth pass node.
- *
- * @tsl
- * @function
- * @param {Scene} scene - A reference to the scene.
- * @param {Camera} camera - A reference to the camera.
- * @param {Object} options - Options for the internal render target.
- * @returns {PassNode}
- */
- const depthPass = ( scene, camera, options ) => nodeObject( new PassNode( PassNode.DEPTH, scene, camera, options ) );
- /**
- * Represents a render pass for producing a toon outline effect on compatible objects.
- * Only 3D objects with materials of type `MeshToonMaterial` and `MeshToonNodeMaterial`
- * will receive the outline.
- *
- * ```js
- * const postProcessing = new PostProcessing( renderer );
- *
- * const scenePass = toonOutlinePass( scene, camera );
- *
- * postProcessing.outputNode = scenePass;
- * ```
- * @augments PassNode
- */
- class ToonOutlinePassNode extends PassNode {
- static get type() {
- return 'ToonOutlinePassNode';
- }
- /**
- * Constructs a new outline pass node.
- *
- * @param {Scene} scene - A reference to the scene.
- * @param {Camera} camera - A reference to the camera.
- * @param {Node} colorNode - Defines the outline's color.
- * @param {Node} thicknessNode - Defines the outline's thickness.
- * @param {Node} alphaNode - Defines the outline's alpha.
- */
- constructor( scene, camera, colorNode, thicknessNode, alphaNode ) {
- super( PassNode.COLOR, scene, camera );
- /**
- * Defines the outline's color.
- *
- * @type {Node}
- */
- this.colorNode = colorNode;
- /**
- * Defines the outline's thickness.
- *
- * @type {Node}
- */
- this.thicknessNode = thicknessNode;
- /**
- * Defines the outline's alpha.
- *
- * @type {Node}
- */
- this.alphaNode = alphaNode;
- /**
- * An internal material cache.
- *
- * @private
- * @type {WeakMap<Material, NodeMaterial>}
- */
- this._materialCache = new WeakMap();
- /**
- * The name of this pass.
- *
- * @type {string}
- * @default 'Outline Pass'
- */
- this.name = 'Outline Pass';
- }
- updateBefore( frame ) {
- const { renderer } = frame;
- const currentRenderObjectFunction = renderer.getRenderObjectFunction();
- renderer.setRenderObjectFunction( ( object, scene, camera, geometry, material, group, lightsNode, clippingContext ) => {
- // only render outline for supported materials
- if ( material.isMeshToonMaterial || material.isMeshToonNodeMaterial ) {
- if ( material.wireframe === false ) {
- const outlineMaterial = this._getOutlineMaterial( material );
- renderer.renderObject( object, scene, camera, geometry, outlineMaterial, group, lightsNode, clippingContext );
- }
- }
- // default
- renderer.renderObject( object, scene, camera, geometry, material, group, lightsNode, clippingContext );
- } );
- super.updateBefore( frame );
- renderer.setRenderObjectFunction( currentRenderObjectFunction );
- }
- /**
- * Creates the material used for outline rendering.
- *
- * @private
- * @return {NodeMaterial} The outline material.
- */
- _createMaterial() {
- const material = new NodeMaterial();
- material.isMeshToonOutlineMaterial = true;
- material.name = 'Toon_Outline';
- material.side = BackSide;
- // vertex node
- const outlineNormal = normalLocal.negate();
- const mvp = cameraProjectionMatrix.mul( modelViewMatrix );
- const ratio = float( 1.0 ); // TODO: support outline thickness ratio for each vertex
- const pos = mvp.mul( vec4( positionLocal, 1.0 ) );
- const pos2 = mvp.mul( vec4( positionLocal.add( outlineNormal ), 1.0 ) );
- const norm = normalize( pos.sub( pos2 ) ); // NOTE: subtract pos2 from pos because BackSide objectNormal is negative
- material.vertexNode = pos.add( norm.mul( this.thicknessNode ).mul( pos.w ).mul( ratio ) );
- // color node
- material.colorNode = vec4( this.colorNode, this.alphaNode );
- return material;
- }
- /**
- * For the given toon material, this method returns a corresponding
- * outline material.
- *
- * @private
- * @param {(MeshToonMaterial|MeshToonNodeMaterial)} originalMaterial - The toon material.
- * @return {NodeMaterial} The outline material.
- */
- _getOutlineMaterial( originalMaterial ) {
- let outlineMaterial = this._materialCache.get( originalMaterial );
- if ( outlineMaterial === undefined ) {
- outlineMaterial = this._createMaterial();
- this._materialCache.set( originalMaterial, outlineMaterial );
- }
- return outlineMaterial;
- }
- }
- /**
- * TSL function for creating a toon outline pass node.
- *
- * @tsl
- * @function
- * @param {Scene} scene - A reference to the scene.
- * @param {Camera} camera - A reference to the camera.
- * @param {Color} color - Defines the outline's color.
- * @param {number} [thickness=0.003] - Defines the outline's thickness.
- * @param {number} [alpha=1] - Defines the outline's alpha.
- * @returns {ToonOutlinePassNode}
- */
- const toonOutlinePass = ( scene, camera, color = new Color( 0, 0, 0 ), thickness = 0.003, alpha = 1 ) => nodeObject( new ToonOutlinePassNode( scene, camera, nodeObject( color ), nodeObject( thickness ), nodeObject( alpha ) ) );
- /**
- * Linear tone mapping, exposure only.
- *
- * @tsl
- * @function
- * @param {Node<vec3>} color - The color that should be tone mapped.
- * @param {Node<float>} exposure - The exposure.
- * @return {Node<vec3>} The tone mapped color.
- */
- const linearToneMapping = /*@__PURE__*/ Fn( ( [ color, exposure ] ) => {
- return color.mul( exposure ).clamp();
- } ).setLayout( {
- name: 'linearToneMapping',
- type: 'vec3',
- inputs: [
- { name: 'color', type: 'vec3' },
- { name: 'exposure', type: 'float' }
- ]
- } );
- /**
- * Reinhard tone mapping.
- *
- * Reference: {@link https://www.cs.utah.edu/docs/techreports/2002/pdf/UUCS-02-001.pdf}
- *
- * @tsl
- * @function
- * @param {Node<vec3>} color - The color that should be tone mapped.
- * @param {Node<float>} exposure - The exposure.
- * @return {Node<vec3>} The tone mapped color.
- */
- const reinhardToneMapping = /*@__PURE__*/ Fn( ( [ color, exposure ] ) => {
- color = color.mul( exposure );
- return color.div( color.add( 1.0 ) ).clamp();
- } ).setLayout( {
- name: 'reinhardToneMapping',
- type: 'vec3',
- inputs: [
- { name: 'color', type: 'vec3' },
- { name: 'exposure', type: 'float' }
- ]
- } );
- /**
- * Cineon tone mapping.
- *
- * Reference: {@link http://filmicworlds.com/blog/filmic-tonemapping-operators/}
- *
- * @tsl
- * @function
- * @param {Node<vec3>} color - The color that should be tone mapped.
- * @param {Node<float>} exposure - The exposure.
- * @return {Node<vec3>} The tone mapped color.
- */
- const cineonToneMapping = /*@__PURE__*/ Fn( ( [ color, exposure ] ) => {
- // filmic operator by Jim Hejl and Richard Burgess-Dawson
- color = color.mul( exposure );
- color = color.sub( 0.004 ).max( 0.0 );
- const a = color.mul( color.mul( 6.2 ).add( 0.5 ) );
- const b = color.mul( color.mul( 6.2 ).add( 1.7 ) ).add( 0.06 );
- return a.div( b ).pow( 2.2 );
- } ).setLayout( {
- name: 'cineonToneMapping',
- type: 'vec3',
- inputs: [
- { name: 'color', type: 'vec3' },
- { name: 'exposure', type: 'float' }
- ]
- } );
- // source: https://github.com/selfshadow/ltc_code/blob/master/webgl/shaders/ltc/ltc_blit.fs
- const RRTAndODTFit = /*@__PURE__*/ Fn( ( [ color ] ) => {
- const a = color.mul( color.add( 0.0245786 ) ).sub( 0.000090537 );
- const b = color.mul( color.add( 0.4329510 ).mul( 0.983729 ) ).add( 0.238081 );
- return a.div( b );
- } );
- /**
- * ACESFilmic tone mapping.
- *
- * Reference: {@link https://github.com/selfshadow/ltc_code/blob/master/webgl/shaders/ltc/ltc_blit.fs}
- *
- * @tsl
- * @function
- * @param {Node<vec3>} color - The color that should be tone mapped.
- * @param {Node<float>} exposure - The exposure.
- * @return {Node<vec3>} The tone mapped color.
- */
- const acesFilmicToneMapping = /*@__PURE__*/ Fn( ( [ color, exposure ] ) => {
- // sRGB => XYZ => D65_2_D60 => AP1 => RRT_SAT
- const ACESInputMat = mat3(
- 0.59719, 0.35458, 0.04823,
- 0.07600, 0.90834, 0.01566,
- 0.02840, 0.13383, 0.83777
- );
- // ODT_SAT => XYZ => D60_2_D65 => sRGB
- const ACESOutputMat = mat3(
- 1.60475, -0.53108, -0.07367,
- -0.10208, 1.10813, -605e-5,
- -327e-5, -0.07276, 1.07602
- );
- color = color.mul( exposure ).div( 0.6 );
- color = ACESInputMat.mul( color );
- // Apply RRT and ODT
- color = RRTAndODTFit( color );
- color = ACESOutputMat.mul( color );
- // Clamp to [0, 1]
- return color.clamp();
- } ).setLayout( {
- name: 'acesFilmicToneMapping',
- type: 'vec3',
- inputs: [
- { name: 'color', type: 'vec3' },
- { name: 'exposure', type: 'float' }
- ]
- } );
- const LINEAR_REC2020_TO_LINEAR_SRGB = /*@__PURE__*/ mat3( vec3( 1.6605, -0.1246, -0.0182 ), vec3( -0.5876, 1.1329, -0.1006 ), vec3( -0.0728, -83e-4, 1.1187 ) );
- const LINEAR_SRGB_TO_LINEAR_REC2020 = /*@__PURE__*/ mat3( vec3( 0.6274, 0.0691, 0.0164 ), vec3( 0.3293, 0.9195, 0.0880 ), vec3( 0.0433, 0.0113, 0.8956 ) );
- const agxDefaultContrastApprox = /*@__PURE__*/ Fn( ( [ x_immutable ] ) => {
- const x = vec3( x_immutable ).toVar();
- const x2 = vec3( x.mul( x ) ).toVar();
- const x4 = vec3( x2.mul( x2 ) ).toVar();
- return float( 15.5 ).mul( x4.mul( x2 ) ).sub( mul( 40.14, x4.mul( x ) ) ).add( mul( 31.96, x4 ).sub( mul( 6.868, x2.mul( x ) ) ).add( mul( 0.4298, x2 ).add( mul( 0.1191, x ).sub( 0.00232 ) ) ) );
- } );
- /**
- * AgX tone mapping.
- *
- * @tsl
- * @function
- * @param {Node<vec3>} color - The color that should be tone mapped.
- * @param {Node<float>} exposure - The exposure.
- * @return {Node<vec3>} The tone mapped color.
- */
- const agxToneMapping = /*@__PURE__*/ Fn( ( [ color, exposure ] ) => {
- const colortone = vec3( color ).toVar();
- const AgXInsetMatrix = mat3( vec3( 0.856627153315983, 0.137318972929847, 0.11189821299995 ), vec3( 0.0951212405381588, 0.761241990602591, 0.0767994186031903 ), vec3( 0.0482516061458583, 0.101439036467562, 0.811302368396859 ) );
- const AgXOutsetMatrix = mat3( vec3( 1.1271005818144368, -0.1413297634984383, -0.14132976349843826 ), vec3( -0.11060664309660323, 1.157823702216272, -0.11060664309660294 ), vec3( -0.016493938717834573, -0.016493938717834257, 1.2519364065950405 ) );
- const AgxMinEv = float( -12.47393 );
- const AgxMaxEv = float( 4.026069 );
- colortone.mulAssign( exposure );
- colortone.assign( LINEAR_SRGB_TO_LINEAR_REC2020.mul( colortone ) );
- colortone.assign( AgXInsetMatrix.mul( colortone ) );
- colortone.assign( max$1( colortone, 1e-10 ) );
- colortone.assign( log2( colortone ) );
- colortone.assign( colortone.sub( AgxMinEv ).div( AgxMaxEv.sub( AgxMinEv ) ) );
- colortone.assign( clamp( colortone, 0.0, 1.0 ) );
- colortone.assign( agxDefaultContrastApprox( colortone ) );
- colortone.assign( AgXOutsetMatrix.mul( colortone ) );
- colortone.assign( pow( max$1( vec3( 0.0 ), colortone ), vec3( 2.2 ) ) );
- colortone.assign( LINEAR_REC2020_TO_LINEAR_SRGB.mul( colortone ) );
- colortone.assign( clamp( colortone, 0.0, 1.0 ) );
- return colortone;
- } ).setLayout( {
- name: 'agxToneMapping',
- type: 'vec3',
- inputs: [
- { name: 'color', type: 'vec3' },
- { name: 'exposure', type: 'float' }
- ]
- } );
- /**
- * Neutral tone mapping.
- *
- * Reference: {@link https://modelviewer.dev/examples/tone-mapping}
- *
- * @tsl
- * @function
- * @param {Node<vec3>} color - The color that should be tone mapped.
- * @param {Node<float>} exposure - The exposure.
- * @return {Node<vec3>} The tone mapped color.
- */
- const neutralToneMapping = /*@__PURE__*/ Fn( ( [ color, exposure ] ) => {
- const StartCompression = float( 0.8 - 0.04 );
- const Desaturation = float( 0.15 );
- color = color.mul( exposure );
- const x = min$1( color.r, min$1( color.g, color.b ) );
- const offset = select( x.lessThan( 0.08 ), x.sub( mul( 6.25, x.mul( x ) ) ), 0.04 );
- color.subAssign( offset );
- const peak = max$1( color.r, max$1( color.g, color.b ) );
- If( peak.lessThan( StartCompression ), () => {
- return color;
- } );
- const d = sub( 1, StartCompression );
- const newPeak = sub( 1, d.mul( d ).div( peak.add( d.sub( StartCompression ) ) ) );
- color.mulAssign( newPeak.div( peak ) );
- const g = sub( 1, div( 1, Desaturation.mul( peak.sub( newPeak ) ).add( 1 ) ) );
- return mix( color, vec3( newPeak ), g );
- } ).setLayout( {
- name: 'neutralToneMapping',
- type: 'vec3',
- inputs: [
- { name: 'color', type: 'vec3' },
- { name: 'exposure', type: 'float' }
- ]
- } );
- /**
- * This class represents native code sections. It is the base
- * class for modules like {@link FunctionNode} which allows to implement
- * functions with native shader languages.
- *
- * @augments Node
- */
- class CodeNode extends Node {
- static get type() {
- return 'CodeNode';
- }
- /**
- * Constructs a new code node.
- *
- * @param {string} [code=''] - The native code.
- * @param {Array<Node>} [includes=[]] - An array of includes.
- * @param {('js'|'wgsl'|'glsl')} [language=''] - The used language.
- */
- constructor( code = '', includes = [], language = '' ) {
- super( 'code' );
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isCodeNode = true;
- /**
- * This flag is used for global cache.
- *
- * @type {boolean}
- * @default true
- */
- this.global = true;
- /**
- * The native code.
- *
- * @type {string}
- * @default ''
- */
- this.code = code;
- /**
- * An array of includes
- *
- * @type {Array<Node>}
- * @default []
- */
- this.includes = includes;
- /**
- * The used language.
- *
- * @type {('js'|'wgsl'|'glsl')}
- * @default ''
- */
- this.language = language;
- }
- /**
- * Sets the includes of this code node.
- *
- * @param {Array<Node>} includes - The includes to set.
- * @return {CodeNode} A reference to this node.
- */
- setIncludes( includes ) {
- this.includes = includes;
- return this;
- }
- /**
- * Returns the includes of this code node.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {Array<Node>} The includes.
- */
- getIncludes( /*builder*/ ) {
- return this.includes;
- }
- generate( builder ) {
- const includes = this.getIncludes( builder );
- for ( const include of includes ) {
- include.build( builder );
- }
- const nodeCode = builder.getCodeFromNode( this, this.getNodeType( builder ) );
- nodeCode.code = this.code;
- return nodeCode.code;
- }
- serialize( data ) {
- super.serialize( data );
- data.code = this.code;
- data.language = this.language;
- }
- deserialize( data ) {
- super.deserialize( data );
- this.code = data.code;
- this.language = data.language;
- }
- }
- /**
- * TSL function for creating a code node.
- *
- * @tsl
- * @function
- * @param {string} [code] - The native code.
- * @param {?Array<Node>} [includes=[]] - An array of includes.
- * @param {?('js'|'wgsl'|'glsl')} [language=''] - The used language.
- * @returns {CodeNode}
- */
- const code = /*@__PURE__*/ nodeProxy( CodeNode ).setParameterLength( 1, 3 );
- /**
- * TSL function for creating a JS code node.
- *
- * @tsl
- * @function
- * @param {string} src - The native code.
- * @param {Array<Node>} includes - An array of includes.
- * @returns {CodeNode}
- */
- const js = ( src, includes ) => code( src, includes, 'js' );
- /**
- * TSL function for creating a WGSL code node.
- *
- * @tsl
- * @function
- * @param {string} src - The native code.
- * @param {Array<Node>} includes - An array of includes.
- * @returns {CodeNode}
- */
- const wgsl = ( src, includes ) => code( src, includes, 'wgsl' );
- /**
- * TSL function for creating a GLSL code node.
- *
- * @tsl
- * @function
- * @param {string} src - The native code.
- * @param {Array<Node>} includes - An array of includes.
- * @returns {CodeNode}
- */
- const glsl = ( src, includes ) => code( src, includes, 'glsl' );
- /**
- * This class represents a native shader function. It can be used to implement
- * certain aspects of a node material with native shader code. There are two predefined
- * TSL functions for easier usage.
- *
- * - `wgslFn`: Creates a WGSL function node.
- * - `glslFn`: Creates a GLSL function node.
- *
- * A basic example with one include looks like so:
- *
- * ```js
- * const desaturateWGSLFn = wgslFn( `
- * fn desaturate( color:vec3<f32> ) -> vec3<f32> {
- * let lum = vec3<f32>( 0.299, 0.587, 0.114 );
- * return vec3<f32>( dot( lum, color ) );
- * }`
- *);
- * const someWGSLFn = wgslFn( `
- * fn someFn( color:vec3<f32> ) -> vec3<f32> {
- * return desaturate( color );
- * }
- * `, [ desaturateWGSLFn ] );
- * material.colorNode = someWGSLFn( { color: texture( map ) } );
- *```
- * @augments CodeNode
- */
- class FunctionNode extends CodeNode {
- static get type() {
- return 'FunctionNode';
- }
- /**
- * Constructs a new function node.
- *
- * @param {string} [code=''] - The native code.
- * @param {Array<Node>} [includes=[]] - An array of includes.
- * @param {('js'|'wgsl'|'glsl')} [language=''] - The used language.
- */
- constructor( code = '', includes = [], language = '' ) {
- super( code, includes, language );
- }
- /**
- * Returns the type of this function node.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {string} The type.
- */
- getNodeType( builder ) {
- return this.getNodeFunction( builder ).type;
- }
- /**
- * Returns the type of a member of this function node.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @param {string} name - The name of the member.
- * @return {string} The type of the member.
- */
- getMemberType( builder, name ) {
- const type = this.getNodeType( builder );
- const structType = builder.getStructTypeNode( type );
- return structType.getMemberType( builder, name );
- }
- /**
- * Returns the inputs of this function node.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {Array<NodeFunctionInput>} The inputs.
- */
- getInputs( builder ) {
- return this.getNodeFunction( builder ).inputs;
- }
- /**
- * Returns the node function for this function node.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {NodeFunction} The node function.
- */
- getNodeFunction( builder ) {
- const nodeData = builder.getDataFromNode( this );
- let nodeFunction = nodeData.nodeFunction;
- if ( nodeFunction === undefined ) {
- nodeFunction = builder.parser.parseFunction( this.code );
- nodeData.nodeFunction = nodeFunction;
- }
- return nodeFunction;
- }
- generate( builder, output ) {
- super.generate( builder );
- const nodeFunction = this.getNodeFunction( builder );
- const name = nodeFunction.name;
- const type = nodeFunction.type;
- const nodeCode = builder.getCodeFromNode( this, type );
- if ( name !== '' ) {
- // use a custom property name
- nodeCode.name = name;
- }
- const propertyName = builder.getPropertyName( nodeCode );
- const code = this.getNodeFunction( builder ).getCode( propertyName );
- nodeCode.code = code + '\n';
- if ( output === 'property' ) {
- return propertyName;
- } else {
- return builder.format( `${ propertyName }()`, type, output );
- }
- }
- }
- const nativeFn = ( code, includes = [], language = '' ) => {
- for ( let i = 0; i < includes.length; i ++ ) {
- const include = includes[ i ];
- // TSL Function: glslFn, wgslFn
- if ( typeof include === 'function' ) {
- includes[ i ] = include.functionNode;
- }
- }
- const functionNode = nodeObject( new FunctionNode( code, includes, language ) );
- const fn = ( ...params ) => functionNode.call( ...params );
- fn.functionNode = functionNode;
- return fn;
- };
- const glslFn = ( code, includes ) => nativeFn( code, includes, 'glsl' );
- const wgslFn = ( code, includes ) => nativeFn( code, includes, 'wgsl' );
- /**
- * `ScriptableNode` uses this class to manage script inputs and outputs.
- *
- * @augments Node
- */
- class ScriptableValueNode extends Node {
- static get type() {
- return 'ScriptableValueNode';
- }
- /**
- * Constructs a new scriptable node.
- *
- * @param {any} [value=null] - The value.
- */
- constructor( value = null ) {
- super();
- /**
- * A reference to the value.
- *
- * @private
- * @default null
- */
- this._value = value;
- /**
- * Depending on the type of `_value`, this property might cache parsed data.
- *
- * @private
- * @default null
- */
- this._cache = null;
- /**
- * If this node represents an input, this property represents the input type.
- *
- * @type {?string}
- * @default null
- */
- this.inputType = null;
- /**
- * If this node represents an output, this property represents the output type.
- *
- * @type {?string}
- * @default null
- */
- this.outputType = null;
- /**
- * An event dispatcher for managing events.
- *
- * @type {EventDispatcher}
- */
- this.events = new EventDispatcher();
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isScriptableValueNode = true;
- }
- /**
- * Whether this node represents an output or not.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- get isScriptableOutputNode() {
- return this.outputType !== null;
- }
- set value( val ) {
- if ( this._value === val ) return;
- if ( this._cache && this.inputType === 'URL' && this.value.value instanceof ArrayBuffer ) {
- URL.revokeObjectURL( this._cache );
- this._cache = null;
- }
- this._value = val;
- this.events.dispatchEvent( { type: 'change' } );
- this.refresh();
- }
- /**
- * The node's value.
- *
- * @type {any}
- */
- get value() {
- return this._value;
- }
- /**
- * Dispatches the `refresh` event.
- */
- refresh() {
- this.events.dispatchEvent( { type: 'refresh' } );
- }
- /**
- * The `value` property usually represents a node or even binary data in form of array buffers.
- * In this case, this method tries to return the actual value behind the complex type.
- *
- * @return {any} The value.
- */
- getValue() {
- const value = this.value;
- if ( value && this._cache === null && this.inputType === 'URL' && value.value instanceof ArrayBuffer ) {
- this._cache = URL.createObjectURL( new Blob( [ value.value ] ) );
- } else if ( value && value.value !== null && value.value !== undefined && (
- ( ( this.inputType === 'URL' || this.inputType === 'String' ) && typeof value.value === 'string' ) ||
- ( this.inputType === 'Number' && typeof value.value === 'number' ) ||
- ( this.inputType === 'Vector2' && value.value.isVector2 ) ||
- ( this.inputType === 'Vector3' && value.value.isVector3 ) ||
- ( this.inputType === 'Vector4' && value.value.isVector4 ) ||
- ( this.inputType === 'Color' && value.value.isColor ) ||
- ( this.inputType === 'Matrix3' && value.value.isMatrix3 ) ||
- ( this.inputType === 'Matrix4' && value.value.isMatrix4 )
- ) ) {
- return value.value;
- }
- return this._cache || value;
- }
- /**
- * Overwritten since the node type is inferred from the value.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {string} The node type.
- */
- getNodeType( builder ) {
- return this.value && this.value.isNode ? this.value.getNodeType( builder ) : 'float';
- }
- setup() {
- return this.value && this.value.isNode ? this.value : float();
- }
- serialize( data ) {
- super.serialize( data );
- if ( this.value !== null ) {
- if ( this.inputType === 'ArrayBuffer' ) {
- data.value = arrayBufferToBase64( this.value );
- } else {
- data.value = this.value ? this.value.toJSON( data.meta ).uuid : null;
- }
- } else {
- data.value = null;
- }
- data.inputType = this.inputType;
- data.outputType = this.outputType;
- }
- deserialize( data ) {
- super.deserialize( data );
- let value = null;
- if ( data.value !== null ) {
- if ( data.inputType === 'ArrayBuffer' ) {
- value = base64ToArrayBuffer( data.value );
- } else if ( data.inputType === 'Texture' ) {
- value = data.meta.textures[ data.value ];
- } else {
- value = data.meta.nodes[ data.value ] || null;
- }
- }
- this.value = value;
- this.inputType = data.inputType;
- this.outputType = data.outputType;
- }
- }
- /**
- * TSL function for creating a scriptable value node.
- *
- * @tsl
- * @function
- * @param {any} [value] - The value.
- * @returns {ScriptableValueNode}
- */
- const scriptableValue = /*@__PURE__*/ nodeProxy( ScriptableValueNode ).setParameterLength( 1 );
- /**
- * A Map-like data structure for managing resources of scriptable nodes.
- *
- * @augments Map
- */
- class Resources extends Map {
- get( key, callback = null, ...params ) {
- if ( this.has( key ) ) return super.get( key );
- if ( callback !== null ) {
- const value = callback( ...params );
- this.set( key, value );
- return value;
- }
- }
- }
- class Parameters {
- constructor( scriptableNode ) {
- this.scriptableNode = scriptableNode;
- }
- get parameters() {
- return this.scriptableNode.parameters;
- }
- get layout() {
- return this.scriptableNode.getLayout();
- }
- getInputLayout( id ) {
- return this.scriptableNode.getInputLayout( id );
- }
- get( name ) {
- const param = this.parameters[ name ];
- const value = param ? param.getValue() : null;
- return value;
- }
- }
- /**
- * Defines the resources (e.g. namespaces) of scriptable nodes.
- *
- * @type {Resources}
- */
- const ScriptableNodeResources = new Resources();
- /**
- * This type of node allows to implement nodes with custom scripts. The script
- * section is represented as an instance of `CodeNode` written with JavaScript.
- * The script itself must adhere to a specific structure.
- *
- * - main(): Executed once by default and every time `node.needsUpdate` is set.
- * - layout: The layout object defines the script's interface (inputs and outputs).
- *
- * ```js
- * ScriptableNodeResources.set( 'TSL', TSL );
- *
- * const scriptableNode = scriptable( js( `
- * layout = {
- * outputType: 'node',
- * elements: [
- * { name: 'source', inputType: 'node' },
- * ]
- * };
- *
- * const { mul, oscSine } = TSL;
- *
- * function main() {
- * const source = parameters.get( 'source' ) || float();
- * return mul( source, oscSine() ) );
- * }
- *
- * ` ) );
- *
- * scriptableNode.setParameter( 'source', color( 1, 0, 0 ) );
- *
- * const material = new THREE.MeshBasicNodeMaterial();
- * material.colorNode = scriptableNode;
- * ```
- *
- * @augments Node
- */
- class ScriptableNode extends Node {
- static get type() {
- return 'ScriptableNode';
- }
- /**
- * Constructs a new scriptable node.
- *
- * @param {?CodeNode} [codeNode=null] - The code node.
- * @param {Object} [parameters={}] - The parameters definition.
- */
- constructor( codeNode = null, parameters = {} ) {
- super();
- /**
- * The code node.
- *
- * @type {?CodeNode}
- * @default null
- */
- this.codeNode = codeNode;
- /**
- * The parameters definition.
- *
- * @type {Object}
- * @default {}
- */
- this.parameters = parameters;
- this._local = new Resources();
- this._output = scriptableValue( null );
- this._outputs = {};
- this._source = this.source;
- this._method = null;
- this._object = null;
- this._value = null;
- this._needsOutputUpdate = true;
- this.onRefresh = this.onRefresh.bind( this );
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isScriptableNode = true;
- }
- /**
- * The source code of the scriptable node.
- *
- * @type {string}
- */
- get source() {
- return this.codeNode ? this.codeNode.code : '';
- }
- /**
- * Sets the reference of a local script variable.
- *
- * @param {string} name - The variable name.
- * @param {Object} value - The reference to set.
- * @return {Resources} The resource map
- */
- setLocal( name, value ) {
- return this._local.set( name, value );
- }
- /**
- * Gets the value of a local script variable.
- *
- * @param {string} name - The variable name.
- * @return {Object} The value.
- */
- getLocal( name ) {
- return this._local.get( name );
- }
- /**
- * Event listener for the `refresh` event.
- */
- onRefresh() {
- this._refresh();
- }
- /**
- * Returns an input from the layout with the given id/name.
- *
- * @param {string} id - The id/name of the input.
- * @return {Object} The element entry.
- */
- getInputLayout( id ) {
- for ( const element of this.getLayout() ) {
- if ( element.inputType && ( element.id === id || element.name === id ) ) {
- return element;
- }
- }
- }
- /**
- * Returns an output from the layout with the given id/name.
- *
- * @param {string} id - The id/name of the output.
- * @return {Object} The element entry.
- */
- getOutputLayout( id ) {
- for ( const element of this.getLayout() ) {
- if ( element.outputType && ( element.id === id || element.name === id ) ) {
- return element;
- }
- }
- }
- /**
- * Defines a script output for the given name and value.
- *
- * @param {string} name - The name of the output.
- * @param {Node} value - The node value.
- * @return {ScriptableNode} A reference to this node.
- */
- setOutput( name, value ) {
- const outputs = this._outputs;
- if ( outputs[ name ] === undefined ) {
- outputs[ name ] = scriptableValue( value );
- } else {
- outputs[ name ].value = value;
- }
- return this;
- }
- /**
- * Returns a script output for the given name.
- *
- * @param {string} name - The name of the output.
- * @return {ScriptableValueNode} The node value.
- */
- getOutput( name ) {
- return this._outputs[ name ];
- }
- /**
- * Returns a parameter for the given name
- *
- * @param {string} name - The name of the parameter.
- * @return {ScriptableValueNode} The node value.
- */
- getParameter( name ) {
- return this.parameters[ name ];
- }
- /**
- * Sets a value for the given parameter name.
- *
- * @param {string} name - The parameter name.
- * @param {any} value - The parameter value.
- * @return {ScriptableNode} A reference to this node.
- */
- setParameter( name, value ) {
- const parameters = this.parameters;
- if ( value && value.isScriptableNode ) {
- this.deleteParameter( name );
- parameters[ name ] = value;
- parameters[ name ].getDefaultOutput().events.addEventListener( 'refresh', this.onRefresh );
- } else if ( value && value.isScriptableValueNode ) {
- this.deleteParameter( name );
- parameters[ name ] = value;
- parameters[ name ].events.addEventListener( 'refresh', this.onRefresh );
- } else if ( parameters[ name ] === undefined ) {
- parameters[ name ] = scriptableValue( value );
- parameters[ name ].events.addEventListener( 'refresh', this.onRefresh );
- } else {
- parameters[ name ].value = value;
- }
- return this;
- }
- /**
- * Returns the value of this node which is the value of
- * the default output.
- *
- * @return {Node} The value.
- */
- getValue() {
- return this.getDefaultOutput().getValue();
- }
- /**
- * Deletes a parameter from the script.
- *
- * @param {string} name - The parameter to remove.
- * @return {ScriptableNode} A reference to this node.
- */
- deleteParameter( name ) {
- let valueNode = this.parameters[ name ];
- if ( valueNode ) {
- if ( valueNode.isScriptableNode ) valueNode = valueNode.getDefaultOutput();
- valueNode.events.removeEventListener( 'refresh', this.onRefresh );
- }
- return this;
- }
- /**
- * Deletes all parameters from the script.
- *
- * @return {ScriptableNode} A reference to this node.
- */
- clearParameters() {
- for ( const name of Object.keys( this.parameters ) ) {
- this.deleteParameter( name );
- }
- this.needsUpdate = true;
- return this;
- }
- /**
- * Calls a function from the script.
- *
- * @param {string} name - The function name.
- * @param {...any} params - A list of parameters.
- * @return {any} The result of the function call.
- */
- call( name, ...params ) {
- const object = this.getObject();
- const method = object[ name ];
- if ( typeof method === 'function' ) {
- return method( ...params );
- }
- }
- /**
- * Asynchronously calls a function from the script.
- *
- * @param {string} name - The function name.
- * @param {...any} params - A list of parameters.
- * @return {Promise<any>} The result of the function call.
- */
- async callAsync( name, ...params ) {
- const object = this.getObject();
- const method = object[ name ];
- if ( typeof method === 'function' ) {
- return method.constructor.name === 'AsyncFunction' ? await method( ...params ) : method( ...params );
- }
- }
- /**
- * Overwritten since the node types is inferred from the script's output.
- *
- * @param {NodeBuilder} builder - The current node builder
- * @return {string} The node type.
- */
- getNodeType( builder ) {
- return this.getDefaultOutputNode().getNodeType( builder );
- }
- /**
- * Refreshes the script node.
- *
- * @param {?string} [output=null] - An optional output.
- */
- refresh( output = null ) {
- if ( output !== null ) {
- this.getOutput( output ).refresh();
- } else {
- this._refresh();
- }
- }
- /**
- * Returns an object representation of the script.
- *
- * @return {Object} The result object.
- */
- getObject() {
- if ( this.needsUpdate ) this.dispose();
- if ( this._object !== null ) return this._object;
- //
- const refresh = () => this.refresh();
- const setOutput = ( id, value ) => this.setOutput( id, value );
- const parameters = new Parameters( this );
- const THREE = ScriptableNodeResources.get( 'THREE' );
- const TSL = ScriptableNodeResources.get( 'TSL' );
- const method = this.getMethod();
- const params = [ parameters, this._local, ScriptableNodeResources, refresh, setOutput, THREE, TSL ];
- this._object = method( ...params );
- const layout = this._object.layout;
- if ( layout ) {
- if ( layout.cache === false ) {
- this._local.clear();
- }
- // default output
- this._output.outputType = layout.outputType || null;
- if ( Array.isArray( layout.elements ) ) {
- for ( const element of layout.elements ) {
- const id = element.id || element.name;
- if ( element.inputType ) {
- if ( this.getParameter( id ) === undefined ) this.setParameter( id, null );
- this.getParameter( id ).inputType = element.inputType;
- }
- if ( element.outputType ) {
- if ( this.getOutput( id ) === undefined ) this.setOutput( id, null );
- this.getOutput( id ).outputType = element.outputType;
- }
- }
- }
- }
- return this._object;
- }
- deserialize( data ) {
- super.deserialize( data );
- for ( const name in this.parameters ) {
- let valueNode = this.parameters[ name ];
- if ( valueNode.isScriptableNode ) valueNode = valueNode.getDefaultOutput();
- valueNode.events.addEventListener( 'refresh', this.onRefresh );
- }
- }
- /**
- * Returns the layout of the script.
- *
- * @return {Object} The script's layout.
- */
- getLayout() {
- return this.getObject().layout;
- }
- /**
- * Returns default node output of the script.
- *
- * @return {Node} The default node output.
- */
- getDefaultOutputNode() {
- const output = this.getDefaultOutput().value;
- if ( output && output.isNode ) {
- return output;
- }
- return float();
- }
- /**
- * Returns default output of the script.
- *
- * @return {ScriptableValueNode} The default output.
- */
- getDefaultOutput() {
- return this._exec()._output;
- }
- /**
- * Returns a function created from the node's script.
- *
- * @return {Function} The function representing the node's code.
- */
- getMethod() {
- if ( this.needsUpdate ) this.dispose();
- if ( this._method !== null ) return this._method;
- //
- const parametersProps = [ 'parameters', 'local', 'global', 'refresh', 'setOutput', 'THREE', 'TSL' ];
- const interfaceProps = [ 'layout', 'init', 'main', 'dispose' ];
- const properties = interfaceProps.join( ', ' );
- const declarations = 'var ' + properties + '; var output = {};\n';
- const returns = '\nreturn { ...output, ' + properties + ' };';
- const code = declarations + this.codeNode.code + returns;
- //
- this._method = new Function( ...parametersProps, code );
- return this._method;
- }
- /**
- * Frees all internal resources.
- */
- dispose() {
- if ( this._method === null ) return;
- if ( this._object && typeof this._object.dispose === 'function' ) {
- this._object.dispose();
- }
- this._method = null;
- this._object = null;
- this._source = null;
- this._value = null;
- this._needsOutputUpdate = true;
- this._output.value = null;
- this._outputs = {};
- }
- setup() {
- return this.getDefaultOutputNode();
- }
- getCacheKey( force ) {
- const values = [ hashString( this.source ), this.getDefaultOutputNode().getCacheKey( force ) ];
- for ( const param in this.parameters ) {
- values.push( this.parameters[ param ].getCacheKey( force ) );
- }
- return hashArray( values );
- }
- set needsUpdate( value ) {
- if ( value === true ) this.dispose();
- }
- get needsUpdate() {
- return this.source !== this._source;
- }
- /**
- * Executes the `main` function of the script.
- *
- * @private
- * @return {ScriptableNode} A reference to this node.
- */
- _exec() {
- if ( this.codeNode === null ) return this;
- if ( this._needsOutputUpdate === true ) {
- this._value = this.call( 'main' );
- this._needsOutputUpdate = false;
- }
- this._output.value = this._value;
- return this;
- }
- /**
- * Executes the refresh.
- *
- * @private
- */
- _refresh() {
- this.needsUpdate = true;
- this._exec();
- this._output.refresh();
- }
- }
- /**
- * TSL function for creating a scriptable node.
- *
- * @tsl
- * @function
- * @param {CodeNode} [codeNode] - The code node.
- * @param {?Object} [parameters={}] - The parameters definition.
- * @returns {ScriptableNode}
- */
- const scriptable = /*@__PURE__*/ nodeProxy( ScriptableNode ).setParameterLength( 1, 2 );
- /**
- * Returns a node that represents the `z` coordinate in view space
- * for the current fragment. It's a different representation of the
- * default depth value.
- *
- * This value can be part of a computation that defines how the fog
- * density increases when moving away from the camera.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {Node} The viewZ node.
- */
- function getViewZNode( builder ) {
- let viewZ;
- const getViewZ = builder.context.getViewZ;
- if ( getViewZ !== undefined ) {
- viewZ = getViewZ( this );
- }
- return ( viewZ || positionView.z ).negate();
- }
- /**
- * Constructs a new range factor node.
- *
- * @tsl
- * @function
- * @param {Node} near - Defines the near value.
- * @param {Node} far - Defines the far value.
- */
- const rangeFogFactor = Fn( ( [ near, far ], builder ) => {
- const viewZ = getViewZNode( builder );
- return smoothstep( near, far, viewZ );
- } );
- /**
- * Represents an exponential squared fog. This type of fog gives
- * a clear view near the camera and a faster than exponentially
- * densening fog farther from the camera.
- *
- * @tsl
- * @function
- * @param {Node} density - Defines the fog density.
- */
- const densityFogFactor = Fn( ( [ density ], builder ) => {
- const viewZ = getViewZNode( builder );
- return density.mul( density, viewZ, viewZ ).negate().exp().oneMinus();
- } );
- /**
- * This class can be used to configure a fog for the scene.
- * Nodes of this type are assigned to `Scene.fogNode`.
- *
- * @tsl
- * @function
- * @param {Node} color - Defines the color of the fog.
- * @param {Node} factor - Defines how the fog is factored in the scene.
- */
- const fog = Fn( ( [ color, factor ] ) => {
- return vec4( factor.toFloat().mix( output.rgb, color.toVec3() ), output.a );
- } );
- // Deprecated
- /**
- * @tsl
- * @function
- * @deprecated since r171. Use `fog( color, rangeFogFactor( near, far ) )` instead.
- *
- * @param {Node} color
- * @param {Node} near
- * @param {Node} far
- * @returns {Function}
- */
- function rangeFog( color, near, far ) { // @deprecated, r171
- warn( 'TSL: "rangeFog( color, near, far )" is deprecated. Use "fog( color, rangeFogFactor( near, far ) )" instead.' );
- return fog( color, rangeFogFactor( near, far ) );
- }
- /**
- * @tsl
- * @function
- * @deprecated since r171. Use `fog( color, densityFogFactor( density ) )` instead.
- *
- * @param {Node} color
- * @param {Node} density
- * @returns {Function}
- */
- function densityFog( color, density ) { // @deprecated, r171
- warn( 'TSL: "densityFog( color, density )" is deprecated. Use "fog( color, densityFogFactor( density ) )" instead.' );
- return fog( color, densityFogFactor( density ) );
- }
- let min = null;
- let max = null;
- /**
- * `RangeNode` generates random instanced attribute data in a defined range.
- * An exemplary use case for this utility node is to generate random per-instance
- * colors:
- * ```js
- * const material = new MeshBasicNodeMaterial();
- * material.colorNode = range( new Color( 0x000000 ), new Color( 0xFFFFFF ) );
- * const mesh = new InstancedMesh( geometry, material, count );
- * ```
- * @augments Node
- */
- class RangeNode extends Node {
- static get type() {
- return 'RangeNode';
- }
- /**
- * Constructs a new range node.
- *
- * @param {Node<any>} [minNode=float()] - A node defining the lower bound of the range.
- * @param {Node<any>} [maxNode=float()] - A node defining the upper bound of the range.
- */
- constructor( minNode = float(), maxNode = float() ) {
- super();
- /**
- * A node defining the lower bound of the range.
- *
- * @type {Node<any>}
- * @default float()
- */
- this.minNode = minNode;
- /**
- * A node defining the upper bound of the range.
- *
- * @type {Node<any>}
- * @default float()
- */
- this.maxNode = maxNode;
- }
- /**
- * Returns the vector length which is computed based on the range definition.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {number} The vector length.
- */
- getVectorLength( builder ) {
- const minNode = this.getConstNode( this.minNode );
- const maxNode = this.getConstNode( this.maxNode );
- const minLength = builder.getTypeLength( getValueType( minNode.value ) );
- const maxLength = builder.getTypeLength( getValueType( maxNode.value ) );
- return minLength > maxLength ? minLength : maxLength;
- }
- /**
- * This method is overwritten since the node type is inferred from range definition.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {string} The node type.
- */
- getNodeType( builder ) {
- return builder.object.count > 1 ? builder.getTypeFromLength( this.getVectorLength( builder ) ) : 'float';
- }
- /**
- * Returns a constant node from the given node by traversing it.
- *
- * @param {Node} node - The node to traverse.
- * @returns {Node} The constant node, if found.
- */
- getConstNode( node ) {
- let output = null;
- node.traverse( n => {
- if ( n.isConstNode === true ) {
- output = n;
- }
- } );
- if ( output === null ) {
- throw new Error( 'THREE.TSL: No "ConstNode" found in node graph.' );
- }
- return output;
- }
- setup( builder ) {
- const object = builder.object;
- let output = null;
- if ( object.count > 1 ) {
- const minNode = this.getConstNode( this.minNode );
- const maxNode = this.getConstNode( this.maxNode );
- const minValue = minNode.value;
- const maxValue = maxNode.value;
- const minLength = builder.getTypeLength( getValueType( minValue ) );
- const maxLength = builder.getTypeLength( getValueType( maxValue ) );
- min = min || new Vector4();
- max = max || new Vector4();
- min.setScalar( 0 );
- max.setScalar( 0 );
- if ( minLength === 1 ) min.setScalar( minValue );
- else if ( minValue.isColor ) min.set( minValue.r, minValue.g, minValue.b, 1 );
- else min.set( minValue.x, minValue.y, minValue.z || 0, minValue.w || 0 );
- if ( maxLength === 1 ) max.setScalar( maxValue );
- else if ( maxValue.isColor ) max.set( maxValue.r, maxValue.g, maxValue.b, 1 );
- else max.set( maxValue.x, maxValue.y, maxValue.z || 0, maxValue.w || 0 );
- const stride = 4;
- const length = stride * object.count;
- const array = new Float32Array( length );
- for ( let i = 0; i < length; i ++ ) {
- const index = i % stride;
- const minElementValue = min.getComponent( index );
- const maxElementValue = max.getComponent( index );
- array[ i ] = MathUtils.lerp( minElementValue, maxElementValue, Math.random() );
- }
- const nodeType = this.getNodeType( builder );
- if ( object.count <= 4096 ) {
- output = buffer( array, 'vec4', object.count ).element( instanceIndex ).convert( nodeType );
- } else {
- // TODO: Improve anonymous buffer attribute creation removing this part
- const bufferAttribute = new InstancedBufferAttribute( array, 4 );
- builder.geometry.setAttribute( '__range' + this.id, bufferAttribute );
- output = instancedBufferAttribute( bufferAttribute ).convert( nodeType );
- }
- } else {
- output = float( 0 );
- }
- return output;
- }
- }
- /**
- * TSL function for creating a range node.
- *
- * @tsl
- * @function
- * @param {Node<any>} [minNode=float()] - A node defining the lower bound of the range.
- * @param {Node<any>} [maxNode=float()] - A node defining the upper bound of the range.
- * @returns {RangeNode}
- */
- const range = /*@__PURE__*/ nodeProxy( RangeNode ).setParameterLength( 2 );
- /**
- * `ComputeBuiltinNode` represents a compute-scope builtin value that expose information
- * about the currently running dispatch and/or the device it is running on.
- *
- * This node can only be used with a WebGPU backend.
- *
- * @augments Node
- */
- class ComputeBuiltinNode extends Node {
- static get type() {
- return 'ComputeBuiltinNode';
- }
- /**
- * Constructs a new compute builtin node.
- *
- * @param {string} builtinName - The built-in name.
- * @param {string} nodeType - The node type.
- */
- constructor( builtinName, nodeType ) {
- super( nodeType );
- /**
- * The built-in name.
- *
- * @private
- * @type {string}
- */
- this._builtinName = builtinName;
- }
- /**
- * This method is overwritten since hash is derived from the built-in name.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {string} The hash.
- */
- getHash( builder ) {
- return this.getBuiltinName( builder );
- }
- /**
- * This method is overwritten since the node type is simply derived from `nodeType`..
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {string} The node type.
- */
- getNodeType( /*builder*/ ) {
- return this.nodeType;
- }
- /**
- * Sets the builtin name.
- *
- * @param {string} builtinName - The built-in name.
- * @return {ComputeBuiltinNode} A reference to this node.
- */
- setBuiltinName( builtinName ) {
- this._builtinName = builtinName;
- return this;
- }
- /**
- * Returns the builtin name.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {string} The builtin name.
- */
- getBuiltinName( /*builder*/ ) {
- return this._builtinName;
- }
- /**
- * Whether the current node builder has the builtin or not.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {boolean} Whether the builder has the builtin or not.
- */
- hasBuiltin( builder ) {
- return builder.hasBuiltin( this._builtinName );
- }
- generate( builder, output ) {
- const builtinName = this.getBuiltinName( builder );
- const nodeType = this.getNodeType( builder );
- if ( builder.shaderStage === 'compute' ) {
- return builder.format( builtinName, nodeType, output );
- } else {
- warn( `ComputeBuiltinNode: Compute built-in value ${builtinName} can not be accessed in the ${builder.shaderStage} stage` );
- return builder.generateConst( nodeType );
- }
- }
- serialize( data ) {
- super.serialize( data );
- data.global = this.global;
- data._builtinName = this._builtinName;
- }
- deserialize( data ) {
- super.deserialize( data );
- this.global = data.global;
- this._builtinName = data._builtinName;
- }
- }
- /**
- * TSL function for creating a compute builtin node.
- *
- * @tsl
- * @function
- * @param {string} name - The built-in name.
- * @param {string} nodeType - The node type.
- * @returns {ComputeBuiltinNode}
- */
- const computeBuiltin = ( name, nodeType ) => nodeObject( new ComputeBuiltinNode( name, nodeType ) );
- /**
- * Represents the number of workgroups dispatched by the compute shader.
- * ```js
- * // Run 512 invocations/threads with a workgroup size of 128.
- * const computeFn = Fn(() => {
- *
- * // numWorkgroups.x = 4
- * storageBuffer.element(0).assign(numWorkgroups.x)
- *
- * })().compute(512, [128]);
- *
- * // Run 512 invocations/threads with the default workgroup size of 64.
- * const computeFn = Fn(() => {
- *
- * // numWorkgroups.x = 8
- * storageBuffer.element(0).assign(numWorkgroups.x)
- *
- * })().compute(512);
- * ```
- *
- * @tsl
- * @type {ComputeBuiltinNode<uvec3>}
- */
- const numWorkgroups = /*@__PURE__*/ computeBuiltin( 'numWorkgroups', 'uvec3' );
- /**
- * Represents the 3-dimensional index of the workgroup the current compute invocation belongs to.
- * ```js
- * // Execute 12 compute threads with a workgroup size of 3.
- * const computeFn = Fn( () => {
- *
- * If( workgroupId.x.mod( 2 ).equal( 0 ), () => {
- *
- * storageBuffer.element( instanceIndex ).assign( instanceIndex );
- *
- * } ).Else( () => {
- *
- * storageBuffer.element( instanceIndex ).assign( 0 );
- *
- * } );
- *
- * } )().compute( 12, [ 3 ] );
- *
- * // workgroupId.x = [0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3];
- * // Buffer Output = [0, 1, 2, 0, 0, 0, 6, 7, 8, 0, 0, 0];
- * ```
- *
- * @tsl
- * @type {ComputeBuiltinNode<uvec3>}
- */
- const workgroupId = /*@__PURE__*/ computeBuiltin( 'workgroupId', 'uvec3' );
- /**
- * A non-linearized 3-dimensional representation of the current invocation's position within a 3D global grid.
- *
- * @tsl
- * @type {ComputeBuiltinNode<uvec3>}
- */
- const globalId = /*@__PURE__*/ computeBuiltin( 'globalId', 'uvec3' );
- /**
- * A non-linearized 3-dimensional representation of the current invocation's position within a 3D workgroup grid.
- *
- * @tsl
- * @type {ComputeBuiltinNode<uvec3>}
- */
- const localId = /*@__PURE__*/ computeBuiltin( 'localId', 'uvec3' );
- /**
- * A device dependent variable that exposes the size of the current invocation's subgroup.
- *
- * @tsl
- * @type {ComputeBuiltinNode<uint>}
- */
- const subgroupSize = /*@__PURE__*/ computeBuiltin( 'subgroupSize', 'uint' );
- /**
- * Represents a GPU control barrier that synchronizes compute operations within a given scope.
- *
- * This node can only be used with a WebGPU backend.
- *
- * @augments Node
- */
- class BarrierNode extends Node {
- /**
- * Constructs a new barrier node.
- *
- * @param {string} scope - The scope defines the behavior of the node.
- */
- constructor( scope ) {
- super();
- this.scope = scope;
- }
- generate( builder ) {
- const { scope } = this;
- const { renderer } = builder;
- if ( renderer.backend.isWebGLBackend === true ) {
- builder.addFlowCode( `\t// ${scope}Barrier \n` );
- } else {
- builder.addLineFlowCode( `${scope}Barrier()`, this );
- }
- }
- }
- /**
- * TSL function for creating a barrier node.
- *
- * @tsl
- * @function
- * @param {string} scope - The scope defines the behavior of the node..
- * @returns {BarrierNode}
- */
- const barrier = nodeProxy( BarrierNode );
- /**
- * TSL function for creating a workgroup barrier. All compute shader
- * invocations must wait for each invocation within a workgroup to
- * complete before the barrier can be surpassed.
- *
- * @tsl
- * @function
- * @returns {BarrierNode}
- */
- const workgroupBarrier = () => barrier( 'workgroup' ).toStack();
- /**
- * TSL function for creating a storage barrier. All invocations must
- * wait for each access to variables within the 'storage' address space
- * to complete before the barrier can be passed.
- *
- * @tsl
- * @function
- * @returns {BarrierNode}
- */
- const storageBarrier = () => barrier( 'storage' ).toStack();
- /**
- * TSL function for creating a texture barrier. All invocations must
- * wait for each access to variables within the 'texture' address space
- * to complete before the barrier can be passed.
- *
- * @tsl
- * @function
- * @returns {BarrierNode}
- */
- const textureBarrier = () => barrier( 'texture' ).toStack();
- /**
- * Represents an element of a 'workgroup' scoped buffer.
- *
- * @augments ArrayElementNode
- */
- class WorkgroupInfoElementNode extends ArrayElementNode {
- /**
- * Constructs a new workgroup info element node.
- *
- * @param {Node} workgroupInfoNode - The workgroup info node.
- * @param {Node} indexNode - The index node that defines the element access.
- */
- constructor( workgroupInfoNode, indexNode ) {
- super( workgroupInfoNode, indexNode );
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isWorkgroupInfoElementNode = true;
- }
- generate( builder, output ) {
- let snippet;
- const isAssignContext = builder.context.assign;
- snippet = super.generate( builder );
- if ( isAssignContext !== true ) {
- const type = this.getNodeType( builder );
- snippet = builder.format( snippet, type, output );
- }
- // TODO: Possibly activate clip distance index on index access rather than from clipping context
- return snippet;
- }
- }
- /**
- * A node allowing the user to create a 'workgroup' scoped buffer within the
- * context of a compute shader. Typically, workgroup scoped buffers are
- * created to hold data that is transferred from a global storage scope into
- * a local workgroup scope. For invocations within a workgroup, data
- * access speeds on 'workgroup' scoped buffers can be significantly faster
- * than similar access operations on globally accessible storage buffers.
- *
- * This node can only be used with a WebGPU backend.
- *
- * @augments Node
- */
- class WorkgroupInfoNode extends Node {
- /**
- * Constructs a new buffer scoped to type scope.
- *
- * @param {string} scope - TODO.
- * @param {string} bufferType - The data type of a 'workgroup' scoped buffer element.
- * @param {number} [bufferCount=0] - The number of elements in the buffer.
- */
- constructor( scope, bufferType, bufferCount = 0 ) {
- super( bufferType );
- /**
- * The buffer type.
- *
- * @type {string}
- */
- this.bufferType = bufferType;
- /**
- * The buffer count.
- *
- * @type {number}
- * @default 0
- */
- this.bufferCount = bufferCount;
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isWorkgroupInfoNode = true;
- /**
- * The data type of the array buffer.
- *
- * @type {string}
- */
- this.elementType = bufferType;
- /**
- * TODO.
- *
- * @type {string}
- */
- this.scope = scope;
- /**
- * The name of the workgroup scoped buffer.
- *
- * @type {string}
- * @default ''
- */
- this.name = '';
- }
- /**
- * Sets the name of this node.
- *
- * @param {string} name - The name to set.
- * @return {WorkgroupInfoNode} A reference to this node.
- */
- setName( name ) {
- this.name = name;
- return this;
- }
- /**
- * Sets the name/label of this node.
- *
- * @deprecated
- * @param {string} name - The name to set.
- * @return {WorkgroupInfoNode} A reference to this node.
- */
- label( name ) {
- warn( 'TSL: "label()" has been deprecated. Use "setName()" instead.' ); // @deprecated r179
- return this.setName( name );
- }
- /**
- * Sets the scope of this node.
- *
- * @param {string} scope - The scope to set.
- * @return {WorkgroupInfoNode} A reference to this node.
- */
- setScope( scope ) {
- this.scope = scope;
- return this;
- }
- /**
- * The data type of the array buffer.
- *
- * @return {string} The element type.
- */
- getElementType() {
- return this.elementType;
- }
- /**
- * Overwrites the default implementation since the input type
- * is inferred from the scope.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {string} The input type.
- */
- getInputType( /*builder*/ ) {
- return `${this.scope}Array`;
- }
- /**
- * This method can be used to access elements via an index node.
- *
- * @param {IndexNode} indexNode - indexNode.
- * @return {WorkgroupInfoElementNode} A reference to an element.
- */
- element( indexNode ) {
- return nodeObject( new WorkgroupInfoElementNode( this, indexNode ) );
- }
- generate( builder ) {
- const name = ( this.name !== '' ) ? this.name : `${this.scope}Array_${this.id}`;
- return builder.getScopedArray( name, this.scope.toLowerCase(), this.bufferType, this.bufferCount );
- }
- }
- /**
- * TSL function for creating a workgroup info node.
- * Creates a new 'workgroup' scoped array buffer.
- *
- * @tsl
- * @function
- * @param {string} type - The data type of a 'workgroup' scoped buffer element.
- * @param {number} [count=0] - The number of elements in the buffer.
- * @returns {WorkgroupInfoNode}
- */
- const workgroupArray = ( type, count ) => nodeObject( new WorkgroupInfoNode( 'Workgroup', type, count ) );
- /**
- * `AtomicFunctionNode` represents any function that can operate on atomic variable types
- * within a shader. In an atomic function, any modification to an atomic variable will
- * occur as an indivisible step with a defined order relative to other modifications.
- * Accordingly, even if multiple atomic functions are modifying an atomic variable at once
- * atomic operations will not interfere with each other.
- *
- * This node can only be used with a WebGPU backend.
- *
- * @augments Node
- */
- class AtomicFunctionNode extends Node {
- static get type() {
- return 'AtomicFunctionNode';
- }
- /**
- * Constructs a new atomic function node.
- *
- * @param {string} method - The signature of the atomic function to construct.
- * @param {Node} pointerNode - An atomic variable or element of an atomic buffer.
- * @param {Node} valueNode - The value that mutates the atomic variable.
- */
- constructor( method, pointerNode, valueNode ) {
- super( 'uint' );
- /**
- * The signature of the atomic function to construct.
- *
- * @type {string}
- */
- this.method = method;
- /**
- * An atomic variable or element of an atomic buffer.
- *
- * @type {Node}
- */
- this.pointerNode = pointerNode;
- /**
- * A value that modifies the atomic variable.
- *
- * @type {Node}
- */
- this.valueNode = valueNode;
- /**
- * Creates a list of the parents for this node for detecting if the node needs to return a value.
- *
- * @type {boolean}
- * @default true
- */
- this.parents = true;
- }
- /**
- * Overwrites the default implementation to return the type of
- * the pointer node.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {string} The input type.
- */
- getInputType( builder ) {
- return this.pointerNode.getNodeType( builder );
- }
- /**
- * Overwritten since the node type is inferred from the input type.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {string} The node type.
- */
- getNodeType( builder ) {
- return this.getInputType( builder );
- }
- generate( builder ) {
- const properties = builder.getNodeProperties( this );
- const parents = properties.parents;
- const method = this.method;
- const type = this.getNodeType( builder );
- const inputType = this.getInputType( builder );
- const a = this.pointerNode;
- const b = this.valueNode;
- const params = [];
- params.push( `&${ a.build( builder, inputType ) }` );
- if ( b !== null ) {
- params.push( b.build( builder, inputType ) );
- }
- const methodSnippet = `${ builder.getMethod( method, type ) }( ${ params.join( ', ' ) } )`;
- const isVoid = parents ? ( parents.length === 1 && parents[ 0 ].isStackNode === true ) : false;
- if ( isVoid ) {
- builder.addLineFlowCode( methodSnippet, this );
- } else {
- if ( properties.constNode === undefined ) {
- properties.constNode = expression( methodSnippet, type ).toConst();
- }
- return properties.constNode.build( builder );
- }
- }
- }
- AtomicFunctionNode.ATOMIC_LOAD = 'atomicLoad';
- AtomicFunctionNode.ATOMIC_STORE = 'atomicStore';
- AtomicFunctionNode.ATOMIC_ADD = 'atomicAdd';
- AtomicFunctionNode.ATOMIC_SUB = 'atomicSub';
- AtomicFunctionNode.ATOMIC_MAX = 'atomicMax';
- AtomicFunctionNode.ATOMIC_MIN = 'atomicMin';
- AtomicFunctionNode.ATOMIC_AND = 'atomicAnd';
- AtomicFunctionNode.ATOMIC_OR = 'atomicOr';
- AtomicFunctionNode.ATOMIC_XOR = 'atomicXor';
- /**
- * TSL function for creating an atomic function node.
- *
- * @tsl
- * @function
- * @param {string} method - The signature of the atomic function to construct.
- * @param {Node} pointerNode - An atomic variable or element of an atomic buffer.
- * @param {Node} valueNode - The value that mutates the atomic variable.
- * @returns {AtomicFunctionNode}
- */
- const atomicNode = nodeProxy( AtomicFunctionNode );
- /**
- * TSL function for appending an atomic function call into the programmatic flow of a compute shader.
- *
- * @tsl
- * @function
- * @param {string} method - The signature of the atomic function to construct.
- * @param {Node} pointerNode - An atomic variable or element of an atomic buffer.
- * @param {Node} valueNode - The value that mutates the atomic variable.
- * @returns {AtomicFunctionNode}
- */
- const atomicFunc = ( method, pointerNode, valueNode ) => {
- return atomicNode( method, pointerNode, valueNode ).toStack();
- };
- /**
- * Loads the value stored in the atomic variable.
- *
- * @tsl
- * @function
- * @param {Node} pointerNode - An atomic variable or element of an atomic buffer.
- * @returns {AtomicFunctionNode}
- */
- const atomicLoad = ( pointerNode ) => atomicFunc( AtomicFunctionNode.ATOMIC_LOAD, pointerNode, null );
- /**
- * Stores a value in the atomic variable.
- *
- * @tsl
- * @function
- * @param {Node} pointerNode - An atomic variable or element of an atomic buffer.
- * @param {Node} valueNode - The value that mutates the atomic variable.
- * @returns {AtomicFunctionNode}
- */
- const atomicStore = ( pointerNode, valueNode ) => atomicFunc( AtomicFunctionNode.ATOMIC_STORE, pointerNode, valueNode );
- /**
- * Increments the value stored in the atomic variable.
- *
- * @tsl
- * @function
- * @param {Node} pointerNode - An atomic variable or element of an atomic buffer.
- * @param {Node} valueNode - The value that mutates the atomic variable.
- * @returns {AtomicFunctionNode}
- */
- const atomicAdd = ( pointerNode, valueNode ) => atomicFunc( AtomicFunctionNode.ATOMIC_ADD, pointerNode, valueNode );
- /**
- * Decrements the value stored in the atomic variable.
- *
- * @tsl
- * @function
- * @param {Node} pointerNode - An atomic variable or element of an atomic buffer.
- * @param {Node} valueNode - The value that mutates the atomic variable.
- * @returns {AtomicFunctionNode}
- */
- const atomicSub = ( pointerNode, valueNode ) => atomicFunc( AtomicFunctionNode.ATOMIC_SUB, pointerNode, valueNode );
- /**
- * Stores in an atomic variable the maximum between its current value and a parameter.
- *
- * @tsl
- * @function
- * @param {Node} pointerNode - An atomic variable or element of an atomic buffer.
- * @param {Node} valueNode - The value that mutates the atomic variable.
- * @returns {AtomicFunctionNode}
- */
- const atomicMax = ( pointerNode, valueNode ) => atomicFunc( AtomicFunctionNode.ATOMIC_MAX, pointerNode, valueNode );
- /**
- * Stores in an atomic variable the minimum between its current value and a parameter.
- *
- * @tsl
- * @function
- * @param {Node} pointerNode - An atomic variable or element of an atomic buffer.
- * @param {Node} valueNode - The value that mutates the atomic variable.
- * @returns {AtomicFunctionNode}
- */
- const atomicMin = ( pointerNode, valueNode ) => atomicFunc( AtomicFunctionNode.ATOMIC_MIN, pointerNode, valueNode );
- /**
- * Stores in an atomic variable the bitwise AND of its value with a parameter.
- *
- * @tsl
- * @function
- * @param {Node} pointerNode - An atomic variable or element of an atomic buffer.
- * @param {Node} valueNode - The value that mutates the atomic variable.
- * @returns {AtomicFunctionNode}
- */
- const atomicAnd = ( pointerNode, valueNode ) => atomicFunc( AtomicFunctionNode.ATOMIC_AND, pointerNode, valueNode );
- /**
- * Stores in an atomic variable the bitwise OR of its value with a parameter.
- *
- * @tsl
- * @function
- * @param {Node} pointerNode - An atomic variable or element of an atomic buffer.
- * @param {Node} valueNode - The value that mutates the atomic variable.
- * @returns {AtomicFunctionNode}
- */
- const atomicOr = ( pointerNode, valueNode ) => atomicFunc( AtomicFunctionNode.ATOMIC_OR, pointerNode, valueNode );
- /**
- * Stores in an atomic variable the bitwise XOR of its value with a parameter.
- *
- * @tsl
- * @function
- * @param {Node} pointerNode - An atomic variable or element of an atomic buffer.
- * @param {Node} valueNode - The value that mutates the atomic variable.
- * @returns {AtomicFunctionNode}
- */
- const atomicXor = ( pointerNode, valueNode ) => atomicFunc( AtomicFunctionNode.ATOMIC_XOR, pointerNode, valueNode );
- /**
- * This class represents a set of built in WGSL shader functions that sync
- * synchronously execute an operation across a subgroup, or 'warp', of compute
- * or fragment shader invocations within a workgroup. Typically, these functions
- * will synchronously execute an operation using data from all active invocations
- * within the subgroup, then broadcast that result to all active invocations. In
- * other graphics APIs, subgroup functions are also referred to as wave intrinsics
- * (DirectX/HLSL) or warp intrinsics (CUDA).
- *
- * @augments TempNode
- */
- class SubgroupFunctionNode extends TempNode {
- static get type() {
- return 'SubgroupFunctionNode';
- }
- /**
- * Constructs a new function node.
- *
- * @param {string} method - The subgroup/wave intrinsic method to construct.
- * @param {Node} [aNode=null] - The method's first argument.
- * @param {Node} [bNode=null] - The method's second argument.
- */
- constructor( method, aNode = null, bNode = null ) {
- super();
- /**
- * The subgroup/wave intrinsic method to construct.
- *
- * @type {String}
- */
- this.method = method;
- /**
- * The method's first argument.
- *
- * @type {Node}
- */
- this.aNode = aNode;
- /**
- * The method's second argument.
- *
- * @type {Node}
- */
- this.bNode = bNode;
- }
- getInputType( builder ) {
- const aType = this.aNode ? this.aNode.getNodeType( builder ) : null;
- const bType = this.bNode ? this.bNode.getNodeType( builder ) : null;
- const aLen = builder.isMatrix( aType ) ? 0 : builder.getTypeLength( aType );
- const bLen = builder.isMatrix( bType ) ? 0 : builder.getTypeLength( bType );
- if ( aLen > bLen ) {
- return aType;
- } else {
- return bType;
- }
- }
- getNodeType( builder ) {
- const method = this.method;
- if ( method === SubgroupFunctionNode.SUBGROUP_ELECT ) {
- return 'bool';
- } else if ( method === SubgroupFunctionNode.SUBGROUP_BALLOT ) {
- return 'uvec4';
- } else {
- return this.getInputType( builder );
- }
- }
- generate( builder, output ) {
- const method = this.method;
- const type = this.getNodeType( builder );
- const inputType = this.getInputType( builder );
- const a = this.aNode;
- const b = this.bNode;
- const params = [];
- if (
- method === SubgroupFunctionNode.SUBGROUP_BROADCAST ||
- method === SubgroupFunctionNode.SUBGROUP_SHUFFLE ||
- method === SubgroupFunctionNode.QUAD_BROADCAST
- ) {
- const bType = b.getNodeType( builder );
- params.push(
- a.build( builder, type ),
- b.build( builder, bType === 'float' ? 'int' : type )
- );
- } else if (
- method === SubgroupFunctionNode.SUBGROUP_SHUFFLE_XOR ||
- method === SubgroupFunctionNode.SUBGROUP_SHUFFLE_DOWN ||
- method === SubgroupFunctionNode.SUBGROUP_SHUFFLE_UP
- ) {
- params.push(
- a.build( builder, type ),
- b.build( builder, 'uint' )
- );
- } else {
- if ( a !== null ) params.push( a.build( builder, inputType ) );
- if ( b !== null ) params.push( b.build( builder, inputType ) );
- }
- const paramsString = params.length === 0 ? '()' : `( ${params.join( ', ' )} )`;
- return builder.format( `${ builder.getMethod( method, type ) }${paramsString}`, type, output );
- }
- serialize( data ) {
- super.serialize( data );
- data.method = this.method;
- }
- deserialize( data ) {
- super.deserialize( data );
- this.method = data.method;
- }
- }
- // 0 inputs
- SubgroupFunctionNode.SUBGROUP_ELECT = 'subgroupElect';
- // 1 input
- SubgroupFunctionNode.SUBGROUP_BALLOT = 'subgroupBallot';
- SubgroupFunctionNode.SUBGROUP_ADD = 'subgroupAdd';
- SubgroupFunctionNode.SUBGROUP_INCLUSIVE_ADD = 'subgroupInclusiveAdd';
- SubgroupFunctionNode.SUBGROUP_EXCLUSIVE_AND = 'subgroupExclusiveAdd';
- SubgroupFunctionNode.SUBGROUP_MUL = 'subgroupMul';
- SubgroupFunctionNode.SUBGROUP_INCLUSIVE_MUL = 'subgroupInclusiveMul';
- SubgroupFunctionNode.SUBGROUP_EXCLUSIVE_MUL = 'subgroupExclusiveMul';
- SubgroupFunctionNode.SUBGROUP_AND = 'subgroupAnd';
- SubgroupFunctionNode.SUBGROUP_OR = 'subgroupOr';
- SubgroupFunctionNode.SUBGROUP_XOR = 'subgroupXor';
- SubgroupFunctionNode.SUBGROUP_MIN = 'subgroupMin';
- SubgroupFunctionNode.SUBGROUP_MAX = 'subgroupMax';
- SubgroupFunctionNode.SUBGROUP_ALL = 'subgroupAll';
- SubgroupFunctionNode.SUBGROUP_ANY = 'subgroupAny';
- SubgroupFunctionNode.SUBGROUP_BROADCAST_FIRST = 'subgroupBroadcastFirst';
- SubgroupFunctionNode.QUAD_SWAP_X = 'quadSwapX';
- SubgroupFunctionNode.QUAD_SWAP_Y = 'quadSwapY';
- SubgroupFunctionNode.QUAD_SWAP_DIAGONAL = 'quadSwapDiagonal';
- // 2 inputs
- SubgroupFunctionNode.SUBGROUP_BROADCAST = 'subgroupBroadcast';
- SubgroupFunctionNode.SUBGROUP_SHUFFLE = 'subgroupShuffle';
- SubgroupFunctionNode.SUBGROUP_SHUFFLE_XOR = 'subgroupShuffleXor';
- SubgroupFunctionNode.SUBGROUP_SHUFFLE_UP = 'subgroupShuffleUp';
- SubgroupFunctionNode.SUBGROUP_SHUFFLE_DOWN = 'subgroupShuffleDown';
- SubgroupFunctionNode.QUAD_BROADCAST = 'quadBroadcast';
- /**
- * Returns true if this invocation has the lowest subgroup_invocation_id
- * among active invocations in the subgroup.
- *
- * @tsl
- * @method
- * @return {bool} The result of the computation.
- */
- const subgroupElect = /*@__PURE__*/ nodeProxyIntent( SubgroupFunctionNode, SubgroupFunctionNode.SUBGROUP_ELECT ).setParameterLength( 0 );
- /**
- * Returns a set of bitfields where the bit corresponding to subgroup_invocation_id
- * is 1 if pred is true for that active invocation and 0 otherwise.
- *
- * @tsl
- * @method
- * @param {bool} pred - A boolean that sets the bit corresponding to the invocations subgroup invocation id.
- * @return {vec4<u32>}- A bitfield corresponding to the pred value of each subgroup invocation.
- */
- const subgroupBallot = /*@__PURE__*/ nodeProxyIntent( SubgroupFunctionNode, SubgroupFunctionNode.SUBGROUP_BALLOT ).setParameterLength( 1 );
- /**
- * A reduction that adds e among all active invocations and returns that result.
- *
- * @tsl
- * @method
- * @param {number} e - The value provided to the reduction by the current invocation.
- * @return {number} The accumulated result of the reduction operation.
- */
- const subgroupAdd = /*@__PURE__*/ nodeProxyIntent( SubgroupFunctionNode, SubgroupFunctionNode.SUBGROUP_ADD ).setParameterLength( 1 );
- /**
- * An inclusive scan returning the sum of e for all active invocations with subgroup_invocation_id less than or equal to this invocation.
- *
- * @tsl
- * @method
- * @param {number} e - The value provided to the inclusive scan by the current invocation.
- * @return {number} The accumulated result of the inclusive scan operation.
- */
- const subgroupInclusiveAdd = /*@__PURE__*/ nodeProxyIntent( SubgroupFunctionNode, SubgroupFunctionNode.SUBGROUP_INCLUSIVE_ADD ).setParameterLength( 1 );
- /**
- * An exclusive scan that returns the sum of e for all active invocations with subgroup_invocation_id less than this invocation.
- *
- * @tsl
- * @method
- * @param {number} e - The value provided to the exclusive scan by the current invocation.
- * @return {number} The accumulated result of the exclusive scan operation.
- */
- const subgroupExclusiveAdd = /*@__PURE__*/ nodeProxyIntent( SubgroupFunctionNode, SubgroupFunctionNode.SUBGROUP_EXCLUSIVE_AND ).setParameterLength( 1 );
- /**
- * A reduction that multiplies e among all active invocations and returns that result.
- *
- * @tsl
- * @method
- * @param {number} e - The value provided to the reduction by the current invocation.
- * @return {number} The accumulated result of the reduction operation.
- */
- const subgroupMul = /*@__PURE__*/ nodeProxyIntent( SubgroupFunctionNode, SubgroupFunctionNode.SUBGROUP_MUL ).setParameterLength( 1 );
- /**
- * An inclusive scan returning the product of e for all active invocations with subgroup_invocation_id less than or equal to this invocation.
- *
- * @tsl
- * @method
- * @param {number} e - The value provided to the inclusive scan by the current invocation.
- * @return {number} The accumulated result of the inclusive scan operation.
- */
- const subgroupInclusiveMul = /*@__PURE__*/ nodeProxyIntent( SubgroupFunctionNode, SubgroupFunctionNode.SUBGROUP_INCLUSIVE_MUL ).setParameterLength( 1 );
- /**
- * An exclusive scan that returns the product of e for all active invocations with subgroup_invocation_id less than this invocation.
- *
- * @tsl
- * @method
- * @param {number} e - The value provided to the exclusive scan by the current invocation.
- * @return {number} The accumulated result of the exclusive scan operation.
- */
- const subgroupExclusiveMul = /*@__PURE__*/ nodeProxyIntent( SubgroupFunctionNode, SubgroupFunctionNode.SUBGROUP_EXCLUSIVE_MUL ).setParameterLength( 1 );
- /**
- * A reduction that performs a bitwise and of e among all active invocations and returns that result.
- *
- * @tsl
- * @method
- * @param {number} e - The value provided to the reduction by the current invocation.
- * @return {number} The result of the reduction operation.
- */
- const subgroupAnd = /*@__PURE__*/ nodeProxyIntent( SubgroupFunctionNode, SubgroupFunctionNode.SUBGROUP_AND ).setParameterLength( 1 );
- /**
- * A reduction that performs a bitwise or of e among all active invocations and returns that result.
- *
- * @tsl
- * @method
- * @param {number} e - The value provided to the reduction by the current invocation.
- * @return {number} The result of the reduction operation.
- */
- const subgroupOr = /*@__PURE__*/ nodeProxyIntent( SubgroupFunctionNode, SubgroupFunctionNode.SUBGROUP_OR ).setParameterLength( 1 );
- /**
- * A reduction that performs a bitwise xor of e among all active invocations and returns that result.
- *
- * @tsl
- * @method
- * @param {number} e - The value provided to the reduction by the current invocation.
- * @return {number} The result of the reduction operation.
- */
- const subgroupXor = /*@__PURE__*/ nodeProxyIntent( SubgroupFunctionNode, SubgroupFunctionNode.SUBGROUP_XOR ).setParameterLength( 1 );
- /**
- * A reduction that performs a min of e among all active invocations and returns that result.
- *
- * @tsl
- * @method
- * @param {number} e - The value provided to the reduction by the current invocation.
- * @return {number} The result of the reduction operation.
- */
- const subgroupMin = /*@__PURE__*/ nodeProxyIntent( SubgroupFunctionNode, SubgroupFunctionNode.SUBGROUP_MIN ).setParameterLength( 1 );
- /**
- * A reduction that performs a max of e among all active invocations and returns that result.
- *
- * @tsl
- * @method
- * @param {number} e - The value provided to the reduction by the current invocation.
- * @return {number} The result of the reduction operation.
- */
- const subgroupMax = /*@__PURE__*/ nodeProxyIntent( SubgroupFunctionNode, SubgroupFunctionNode.SUBGROUP_MAX ).setParameterLength( 1 );
- /**
- * Returns true if e is true for all active invocations in the subgroup.
- *
- * @tsl
- * @method
- * @return {bool} The result of the computation.
- */
- const subgroupAll = /*@__PURE__*/ nodeProxyIntent( SubgroupFunctionNode, SubgroupFunctionNode.SUBGROUP_ALL ).setParameterLength( 0 );
- /**
- * Returns true if e is true for any active invocation in the subgroup
- *
- * @tsl
- * @method
- * @return {bool} The result of the computation.
- */
- const subgroupAny = /*@__PURE__*/ nodeProxyIntent( SubgroupFunctionNode, SubgroupFunctionNode.SUBGROUP_ANY ).setParameterLength( 0 );
- /**
- * Broadcasts e from the active invocation with the lowest subgroup_invocation_id in the subgroup to all other active invocations.
- *
- * @tsl
- * @method
- * @param {number} e - The value to broadcast from the lowest subgroup invocation.
- * @param {number} id - The subgroup invocation to broadcast from.
- * @return {number} The broadcast value.
- */
- const subgroupBroadcastFirst = /*@__PURE__*/ nodeProxyIntent( SubgroupFunctionNode, SubgroupFunctionNode.SUBGROUP_BROADCAST_FIRST ).setParameterLength( 2 );
- /**
- * Swaps e between invocations in the quad in the X direction.
- *
- * @tsl
- * @method
- * @param {number} e - The value to swap from the current invocation.
- * @return {number} The value received from the swap operation.
- */
- const quadSwapX = /*@__PURE__*/ nodeProxyIntent( SubgroupFunctionNode, SubgroupFunctionNode.QUAD_SWAP_X ).setParameterLength( 1 );
- /**
- * Swaps e between invocations in the quad in the Y direction.
- *
- * @tsl
- * @method
- * @param {number} e - The value to swap from the current invocation.
- * @return {number} The value received from the swap operation.
- */
- const quadSwapY = /*@__PURE__*/ nodeProxyIntent( SubgroupFunctionNode, SubgroupFunctionNode.QUAD_SWAP_Y ).setParameterLength( 1 );
- /**
- * Swaps e between invocations in the quad diagonally.
- *
- * @tsl
- * @method
- * @param {number} e - The value to swap from the current invocation.
- * @return {number} The value received from the swap operation.
- */
- const quadSwapDiagonal = /*@__PURE__*/ nodeProxyIntent( SubgroupFunctionNode, SubgroupFunctionNode.QUAD_SWAP_DIAGONAL ).setParameterLength( 1 );
- /**
- * Broadcasts e from the invocation whose subgroup_invocation_id matches id, to all active invocations.
- *
- * @tsl
- * @method
- * @param {number} e - The value to broadcast from subgroup invocation 'id'.
- * @param {number} id - The subgroup invocation to broadcast from.
- * @return {number} The broadcast value.
- */
- const subgroupBroadcast = /*@__PURE__*/ nodeProxyIntent( SubgroupFunctionNode, SubgroupFunctionNode.SUBGROUP_BROADCAST ).setParameterLength( 2 );
- /**
- * Returns v from the active invocation whose subgroup_invocation_id matches id
- *
- * @tsl
- * @method
- * @param {number} v - The value to return from subgroup invocation id^mask.
- * @param {number} id - The subgroup invocation which returns the value v.
- * @return {number} The broadcast value.
- */
- const subgroupShuffle = /*@__PURE__*/ nodeProxyIntent( SubgroupFunctionNode, SubgroupFunctionNode.SUBGROUP_SHUFFLE ).setParameterLength( 2 );
- /**
- * Returns v from the active invocation whose subgroup_invocation_id matches subgroup_invocation_id ^ mask.
- *
- * @tsl
- * @method
- * @param {number} v - The value to return from subgroup invocation id^mask.
- * @param {number} mask - A bitmask that determines the target invocation via a XOR operation.
- * @return {number} The broadcast value.
- */
- const subgroupShuffleXor = /*@__PURE__*/ nodeProxyIntent( SubgroupFunctionNode, SubgroupFunctionNode.SUBGROUP_SHUFFLE_XOR ).setParameterLength( 2 );
- /**
- * Returns v from the active invocation whose subgroup_invocation_id matches subgroup_invocation_id - delta
- *
- * @tsl
- * @method
- * @param {number} v - The value to return from subgroup invocation id^mask.
- * @param {number} delta - A value that offsets the current in.
- * @return {number} The broadcast value.
- */
- const subgroupShuffleUp = /*@__PURE__*/ nodeProxyIntent( SubgroupFunctionNode, SubgroupFunctionNode.SUBGROUP_SHUFFLE_UP ).setParameterLength( 2 );
- /**
- * Returns v from the active invocation whose subgroup_invocation_id matches subgroup_invocation_id + delta
- *
- * @tsl
- * @method
- * @param {number} v - The value to return from subgroup invocation id^mask.
- * @param {number} delta - A value that offsets the current subgroup invocation.
- * @return {number} The broadcast value.
- */
- const subgroupShuffleDown = /*@__PURE__*/ nodeProxyIntent( SubgroupFunctionNode, SubgroupFunctionNode.SUBGROUP_SHUFFLE_DOWN ).setParameterLength( 2 );
- /**
- * Broadcasts e from the quad invocation with id equal to id.
- *
- * @tsl
- * @method
- * @param {number} e - The value to broadcast.
- * @return {number} The broadcast value.
- */
- const quadBroadcast = /*@__PURE__*/ nodeProxyIntent( SubgroupFunctionNode, SubgroupFunctionNode.QUAD_BROADCAST ).setParameterLength( 1 );
- let uniformsLib;
- function getLightData( light ) {
- uniformsLib = uniformsLib || new WeakMap();
- let uniforms = uniformsLib.get( light );
- if ( uniforms === undefined ) uniformsLib.set( light, uniforms = {} );
- return uniforms;
- }
- /**
- * TSL function for getting a shadow matrix uniform node for the given light.
- *
- * @tsl
- * @function
- * @param {Light} light -The light source.
- * @returns {UniformNode<mat4>} The shadow matrix uniform node.
- */
- function lightShadowMatrix( light ) {
- const data = getLightData( light );
- return data.shadowMatrix || ( data.shadowMatrix = uniform( 'mat4' ).setGroup( renderGroup ).onRenderUpdate( ( frame ) => {
- // normally, shadow matrices are updated in ShadowNode. However, if the shadow matrix is used outside
- // of shadow rendering (like in ProjectorLightNode), the shadow matrix still requires an update
- if ( light.castShadow !== true || frame.renderer.shadowMap.enabled === false ) {
- if ( light.shadow.camera.coordinateSystem !== frame.camera.coordinateSystem ) {
- light.shadow.camera.coordinateSystem = frame.camera.coordinateSystem;
- light.shadow.camera.updateProjectionMatrix();
- }
- light.shadow.updateMatrices( light );
- }
- return light.shadow.matrix;
- } ) );
- }
- /**
- * TSL function for getting projected uv coordinates for the given light.
- * Relevant when using maps with spot lights.
- *
- * @tsl
- * @function
- * @param {Light} light -The light source.
- * @param {Node<vec3>} [position=positionWorld] -The position to project.
- * @returns {Node<vec3>} The projected uvs.
- */
- function lightProjectionUV( light, position = positionWorld ) {
- const spotLightCoord = lightShadowMatrix( light ).mul( position );
- const projectionUV = spotLightCoord.xyz.div( spotLightCoord.w );
- return projectionUV;
- }
- /**
- * TSL function for getting the position in world space for the given light.
- *
- * @tsl
- * @function
- * @param {Light} light -The light source.
- * @returns {UniformNode<vec3>} The light's position in world space.
- */
- function lightPosition( light ) {
- const data = getLightData( light );
- return data.position || ( data.position = uniform( new Vector3() ).setGroup( renderGroup ).onRenderUpdate( ( _, self ) => self.value.setFromMatrixPosition( light.matrixWorld ) ) );
- }
- /**
- * TSL function for getting the light target position in world space for the given light.
- *
- * @tsl
- * @function
- * @param {Light} light -The light source.
- * @returns {UniformNode<vec3>} The light target position in world space.
- */
- function lightTargetPosition( light ) {
- const data = getLightData( light );
- return data.targetPosition || ( data.targetPosition = uniform( new Vector3() ).setGroup( renderGroup ).onRenderUpdate( ( _, self ) => self.value.setFromMatrixPosition( light.target.matrixWorld ) ) );
- }
- /**
- * TSL function for getting the position in view space for the given light.
- *
- * @tsl
- * @function
- * @param {Light} light - The light source.
- * @returns {UniformNode<vec3>} The light's position in view space.
- */
- function lightViewPosition( light ) {
- const data = getLightData( light );
- return data.viewPosition || ( data.viewPosition = uniform( new Vector3() ).setGroup( renderGroup ).onRenderUpdate( ( { camera }, self ) => {
- self.value = self.value || new Vector3();
- self.value.setFromMatrixPosition( light.matrixWorld );
- self.value.applyMatrix4( camera.matrixWorldInverse );
- } ) );
- }
- /**
- * TSL function for getting the light target direction for the given light.
- *
- * @tsl
- * @function
- * @param {Light} light -The light source.
- * @returns {Node<vec3>} The light's target direction.
- */
- const lightTargetDirection = ( light ) => cameraViewMatrix.transformDirection( lightPosition( light ).sub( lightTargetPosition( light ) ) );
- const sortLights = ( lights ) => {
- return lights.sort( ( a, b ) => a.id - b.id );
- };
- const getLightNodeById = ( id, lightNodes ) => {
- for ( const lightNode of lightNodes ) {
- if ( lightNode.isAnalyticLightNode && lightNode.light.id === id ) {
- return lightNode;
- }
- }
- return null;
- };
- const _lightsNodeRef = /*@__PURE__*/ new WeakMap();
- const _hashData = [];
- /**
- * This node represents the scene's lighting and manages the lighting model's life cycle
- * for the current build 3D object. It is responsible for computing the total outgoing
- * light in a given lighting context.
- *
- * @augments Node
- */
- class LightsNode extends Node {
- static get type() {
- return 'LightsNode';
- }
- /**
- * Constructs a new lights node.
- */
- constructor() {
- super( 'vec3' );
- /**
- * A node representing the total diffuse light.
- *
- * @type {Node<vec3>}
- */
- this.totalDiffuseNode = property( 'vec3', 'totalDiffuse' );
- /**
- * A node representing the total specular light.
- *
- * @type {Node<vec3>}
- */
- this.totalSpecularNode = property( 'vec3', 'totalSpecular' );
- /**
- * A node representing the outgoing light.
- *
- * @type {Node<vec3>}
- */
- this.outgoingLightNode = property( 'vec3', 'outgoingLight' );
- /**
- * An array representing the lights in the scene.
- *
- * @private
- * @type {Array<Light>}
- */
- this._lights = [];
- /**
- * For each light in the scene, this node will create a
- * corresponding light node.
- *
- * @private
- * @type {?Array<LightingNode>}
- * @default null
- */
- this._lightNodes = null;
- /**
- * A hash for identifying the current light nodes setup.
- *
- * @private
- * @type {?string}
- * @default null
- */
- this._lightNodesHash = null;
- /**
- * `LightsNode` sets this property to `true` by default.
- *
- * @type {boolean}
- * @default true
- */
- this.global = true;
- }
- /**
- * Overwrites the default {@link Node#customCacheKey} implementation by including
- * light data into the cache key.
- *
- * @return {number} The custom cache key.
- */
- customCacheKey() {
- const lights = this._lights;
- for ( let i = 0; i < lights.length; i ++ ) {
- const light = lights[ i ];
- _hashData.push( light.id );
- _hashData.push( light.castShadow ? 1 : 0 );
- if ( light.isSpotLight === true ) {
- const hashMap = ( light.map !== null ) ? light.map.id : -1;
- const hashColorNode = ( light.colorNode ) ? light.colorNode.getCacheKey() : -1;
- _hashData.push( hashMap, hashColorNode );
- }
- }
- const cacheKey = hashArray( _hashData );
- _hashData.length = 0;
- return cacheKey;
- }
- /**
- * Computes a hash value for identifying the current light nodes setup.
- *
- * @param {NodeBuilder} builder - A reference to the current node builder.
- * @return {string} The computed hash.
- */
- getHash( builder ) {
- if ( this._lightNodesHash === null ) {
- if ( this._lightNodes === null ) this.setupLightsNode( builder );
- const hash = [];
- for ( const lightNode of this._lightNodes ) {
- hash.push( lightNode.getHash() );
- }
- this._lightNodesHash = 'lights-' + hash.join( ',' );
- }
- return this._lightNodesHash;
- }
- analyze( builder ) {
- const properties = builder.getNodeProperties( this );
- for ( const node of properties.nodes ) {
- node.build( builder );
- }
- properties.outputNode.build( builder );
- }
- /**
- * Creates lighting nodes for each scene light. This makes it possible to further
- * process lights in the node system.
- *
- * @param {NodeBuilder} builder - A reference to the current node builder.
- */
- setupLightsNode( builder ) {
- const lightNodes = [];
- const previousLightNodes = this._lightNodes;
- const lights = sortLights( this._lights );
- const nodeLibrary = builder.renderer.library;
- for ( const light of lights ) {
- if ( light.isNode ) {
- lightNodes.push( nodeObject( light ) );
- } else {
- let lightNode = null;
- if ( previousLightNodes !== null ) {
- lightNode = getLightNodeById( light.id, previousLightNodes ); // reuse existing light node
- }
- if ( lightNode === null ) {
- // find the corresponding node type for a given light
- const lightNodeClass = nodeLibrary.getLightNodeClass( light.constructor );
- if ( lightNodeClass === null ) {
- warn( `LightsNode.setupNodeLights: Light node not found for ${ light.constructor.name }` );
- continue;
- }
- let lightNode = null;
- if ( ! _lightsNodeRef.has( light ) ) {
- lightNode = nodeObject( new lightNodeClass( light ) );
- _lightsNodeRef.set( light, lightNode );
- } else {
- lightNode = _lightsNodeRef.get( light );
- }
- lightNodes.push( lightNode );
- }
- }
- }
- this._lightNodes = lightNodes;
- }
- /**
- * Sets up a direct light in the lighting model.
- *
- * @param {Object} builder - The builder object containing the context and stack.
- * @param {Object} lightNode - The light node.
- * @param {Object} lightData - The light object containing color and direction properties.
- */
- setupDirectLight( builder, lightNode, lightData ) {
- const { lightingModel, reflectedLight } = builder.context;
- lightingModel.direct( {
- ...lightData,
- lightNode,
- reflectedLight
- }, builder );
- }
- setupDirectRectAreaLight( builder, lightNode, lightData ) {
- const { lightingModel, reflectedLight } = builder.context;
- lightingModel.directRectArea( {
- ...lightData,
- lightNode,
- reflectedLight
- }, builder );
- }
- /**
- * Setups the internal lights by building all respective
- * light nodes.
- *
- * @param {NodeBuilder} builder - A reference to the current node builder.
- * @param {Array<LightingNode>} lightNodes - An array of lighting nodes.
- */
- setupLights( builder, lightNodes ) {
- for ( const lightNode of lightNodes ) {
- lightNode.build( builder );
- }
- }
- getLightNodes( builder ) {
- if ( this._lightNodes === null ) this.setupLightsNode( builder );
- return this._lightNodes;
- }
- /**
- * The implementation makes sure that for each light in the scene
- * there is a corresponding light node. By building the light nodes
- * and evaluating the lighting model the outgoing light is computed.
- *
- * @param {NodeBuilder} builder - A reference to the current node builder.
- * @return {Node<vec3>} A node representing the outgoing light.
- */
- setup( builder ) {
- const currentLightsNode = builder.lightsNode;
- builder.lightsNode = this;
- //
- let outgoingLightNode = this.outgoingLightNode;
- const context = builder.context;
- const lightingModel = context.lightingModel;
- const properties = builder.getNodeProperties( this );
- if ( lightingModel ) {
- const { totalDiffuseNode, totalSpecularNode } = this;
- context.outgoingLight = outgoingLightNode;
- const stack = builder.addStack();
- //
- properties.nodes = stack.nodes;
- //
- lightingModel.start( builder );
- //
- const { backdrop, backdropAlpha } = context;
- const { directDiffuse, directSpecular, indirectDiffuse, indirectSpecular } = context.reflectedLight;
- let totalDiffuse = directDiffuse.add( indirectDiffuse );
- if ( backdrop !== null ) {
- if ( backdropAlpha !== null ) {
- totalDiffuse = vec3( backdropAlpha.mix( totalDiffuse, backdrop ) );
- } else {
- totalDiffuse = vec3( backdrop );
- }
- }
- totalDiffuseNode.assign( totalDiffuse );
- totalSpecularNode.assign( directSpecular.add( indirectSpecular ) );
- outgoingLightNode.assign( totalDiffuseNode.add( totalSpecularNode ) );
- //
- lightingModel.finish( builder );
- //
- outgoingLightNode = outgoingLightNode.bypass( builder.removeStack() );
- } else {
- properties.nodes = [];
- }
- //
- builder.lightsNode = currentLightsNode;
- return outgoingLightNode;
- }
- /**
- * Configures this node with an array of lights.
- *
- * @param {Array<Light>} lights - An array of lights.
- * @return {LightsNode} A reference to this node.
- */
- setLights( lights ) {
- this._lights = lights;
- this._lightNodes = null;
- this._lightNodesHash = null;
- return this;
- }
- /**
- * Returns an array of the scene's lights.
- *
- * @return {Array<Light>} The scene's lights.
- */
- getLights() {
- return this._lights;
- }
- /**
- * Whether the scene has lights or not.
- *
- * @type {boolean}
- */
- get hasLights() {
- return this._lights.length > 0;
- }
- }
- /**
- * TSL function for creating an instance of `LightsNode` and configuring
- * it with the given array of lights.
- *
- * @tsl
- * @function
- * @param {Array<Light>} lights - An array of lights.
- * @return {LightsNode} The created lights node.
- */
- const lights = ( lights = [] ) => nodeObject( new LightsNode() ).setLights( lights );
- /**
- * Base class for all shadow nodes.
- *
- * Shadow nodes encapsulate shadow related logic and are always coupled to lighting nodes.
- * Lighting nodes might share the same shadow node type or use specific ones depending on
- * their requirements.
- *
- * @augments Node
- */
- class ShadowBaseNode extends Node {
- static get type() {
- return 'ShadowBaseNode';
- }
- /**
- * Constructs a new shadow base node.
- *
- * @param {Light} light - The shadow casting light.
- */
- constructor( light ) {
- super();
- /**
- * The shadow casting light.
- *
- * @type {Light}
- */
- this.light = light;
- /**
- * Overwritten since shadows are updated by default per render.
- *
- * @type {string}
- * @default 'render'
- */
- this.updateBeforeType = NodeUpdateType.RENDER;
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isShadowBaseNode = true;
- }
- /**
- * Setups the shadow position node which is by default the predefined TSL node object `shadowPositionWorld`.
- *
- * @param {NodeBuilder} object - A configuration object that must at least hold a material reference.
- */
- setupShadowPosition( { context, material } ) {
- // Use assign inside an Fn()
- shadowPositionWorld.assign( material.receivedShadowPositionNode || context.shadowPositionWorld || positionWorld );
- }
- }
- /**
- * TSL object that represents the vertex position in world space during the shadow pass.
- *
- * @tsl
- * @type {Node<vec3>}
- */
- const shadowPositionWorld = /*@__PURE__*/ property( 'vec3', 'shadowPositionWorld' );
- /**
- * Saves the state of the given renderer and stores it into the given state object.
- *
- * If not state object is provided, the function creates one.
- *
- * @private
- * @function
- * @param {Renderer} renderer - The renderer.
- * @param {Object} [state={}] - The state.
- * @return {Object} The state.
- */
- function saveRendererState( renderer, state = {} ) {
- state.toneMapping = renderer.toneMapping;
- state.toneMappingExposure = renderer.toneMappingExposure;
- state.outputColorSpace = renderer.outputColorSpace;
- state.renderTarget = renderer.getRenderTarget();
- state.activeCubeFace = renderer.getActiveCubeFace();
- state.activeMipmapLevel = renderer.getActiveMipmapLevel();
- state.renderObjectFunction = renderer.getRenderObjectFunction();
- state.pixelRatio = renderer.getPixelRatio();
- state.mrt = renderer.getMRT();
- state.clearColor = renderer.getClearColor( state.clearColor || new Color() );
- state.clearAlpha = renderer.getClearAlpha();
- state.autoClear = renderer.autoClear;
- state.scissorTest = renderer.getScissorTest();
- return state;
- }
- /**
- * Saves the state of the given renderer and stores it into the given state object.
- * Besides, the function also resets the state of the renderer to its default values.
- *
- * If not state object is provided, the function creates one.
- *
- * @private
- * @function
- * @param {Renderer} renderer - The renderer.
- * @param {Object} [state={}] - The state.
- * @return {Object} The state.
- */
- function resetRendererState( renderer, state ) {
- state = saveRendererState( renderer, state );
- renderer.setMRT( null );
- renderer.setRenderObjectFunction( null );
- renderer.setClearColor( 0x000000, 1 );
- renderer.autoClear = true;
- return state;
- }
- /**
- * Restores the state of the given renderer from the given state object.
- *
- * @private
- * @function
- * @param {Renderer} renderer - The renderer.
- * @param {Object} state - The state to restore.
- */
- function restoreRendererState( renderer, state ) {
- renderer.toneMapping = state.toneMapping;
- renderer.toneMappingExposure = state.toneMappingExposure;
- renderer.outputColorSpace = state.outputColorSpace;
- renderer.setRenderTarget( state.renderTarget, state.activeCubeFace, state.activeMipmapLevel );
- renderer.setRenderObjectFunction( state.renderObjectFunction );
- renderer.setPixelRatio( state.pixelRatio );
- renderer.setMRT( state.mrt );
- renderer.setClearColor( state.clearColor, state.clearAlpha );
- renderer.autoClear = state.autoClear;
- renderer.setScissorTest( state.scissorTest );
- }
- /**
- * Saves the state of the given scene and stores it into the given state object.
- *
- * If not state object is provided, the function creates one.
- *
- * @private
- * @function
- * @param {Scene} scene - The scene.
- * @param {Object} [state={}] - The state.
- * @return {Object} The state.
- */
- function saveSceneState( scene, state = {} ) {
- state.background = scene.background;
- state.backgroundNode = scene.backgroundNode;
- state.overrideMaterial = scene.overrideMaterial;
- return state;
- }
- /**
- * Saves the state of the given scene and stores it into the given state object.
- * Besides, the function also resets the state of the scene to its default values.
- *
- * If not state object is provided, the function creates one.
- *
- * @private
- * @function
- * @param {Scene} scene - The scene.
- * @param {Object} [state={}] - The state.
- * @return {Object} The state.
- */
- function resetSceneState( scene, state ) {
- state = saveSceneState( scene, state );
- scene.background = null;
- scene.backgroundNode = null;
- scene.overrideMaterial = null;
- return state;
- }
- /**
- * Restores the state of the given scene from the given state object.
- *
- * @private
- * @function
- * @param {Scene} scene - The scene.
- * @param {Object} state - The state to restore.
- */
- function restoreSceneState( scene, state ) {
- scene.background = state.background;
- scene.backgroundNode = state.backgroundNode;
- scene.overrideMaterial = state.overrideMaterial;
- }
- /**
- * Saves the state of the given renderer and scene and stores it into the given state object.
- *
- * If not state object is provided, the function creates one.
- *
- * @private
- * @function
- * @param {Renderer} renderer - The renderer.
- * @param {Scene} scene - The scene.
- * @param {Object} [state={}] - The state.
- * @return {Object} The state.
- */
- function saveRendererAndSceneState( renderer, scene, state = {} ) {
- state = saveRendererState( renderer, state );
- state = saveSceneState( scene, state );
- return state;
- }
- /**
- * Saves the state of the given renderer and scene and stores it into the given state object.
- * Besides, the function also resets the state of the renderer and scene to its default values.
- *
- * If not state object is provided, the function creates one.
- *
- * @private
- * @function
- * @param {Renderer} renderer - The renderer.
- * @param {Scene} scene - The scene.
- * @param {Object} [state={}] - The state.
- * @return {Object} The state.
- */
- function resetRendererAndSceneState( renderer, scene, state ) {
- state = resetRendererState( renderer, state );
- state = resetSceneState( scene, state );
- return state;
- }
- /**
- * Restores the state of the given renderer and scene from the given state object.
- *
- * @private
- * @function
- * @param {Renderer} renderer - The renderer.
- * @param {Scene} scene - The scene.
- * @param {Object} state - The state to restore.
- */
- function restoreRendererAndSceneState( renderer, scene, state ) {
- restoreRendererState( renderer, state );
- restoreSceneState( scene, state );
- }
- var RendererUtils = /*#__PURE__*/Object.freeze({
- __proto__: null,
- resetRendererAndSceneState: resetRendererAndSceneState,
- resetRendererState: resetRendererState,
- resetSceneState: resetSceneState,
- restoreRendererAndSceneState: restoreRendererAndSceneState,
- restoreRendererState: restoreRendererState,
- restoreSceneState: restoreSceneState,
- saveRendererAndSceneState: saveRendererAndSceneState,
- saveRendererState: saveRendererState,
- saveSceneState: saveSceneState
- });
- const shadowMaterialLib = /*@__PURE__*/ new WeakMap();
- /**
- * A shadow filtering function performing basic filtering. This is in fact an unfiltered version of the shadow map
- * with a binary `[0,1]` result.
- *
- * @method
- * @param {Object} inputs - The input parameter object.
- * @param {DepthTexture} inputs.depthTexture - A reference to the shadow map's texture data.
- * @param {Node<vec3>} inputs.shadowCoord - The shadow coordinates.
- * @return {Node<float>} The filtering result.
- */
- const BasicShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, shadowCoord, depthLayer } ) => {
- let basic = texture( depthTexture, shadowCoord.xy ).setName( 't_basic' );
- if ( depthTexture.isArrayTexture ) {
- basic = basic.depth( depthLayer );
- }
- return basic.compare( shadowCoord.z );
- } );
- /**
- * A shadow filtering function performing PCF filtering with Vogel disk sampling and IGN.
- *
- * Uses 5 samples distributed via Vogel disk pattern, rotated per-pixel using Interleaved
- * Gradient Noise (IGN) to break up banding artifacts. Combined with hardware PCF (4-tap
- * filtering per sample), this effectively provides 20 filtered taps with better distribution.
- *
- * @method
- * @param {Object} inputs - The input parameter object.
- * @param {DepthTexture} inputs.depthTexture - A reference to the shadow map's texture data.
- * @param {Node<vec3>} inputs.shadowCoord - The shadow coordinates.
- * @param {LightShadow} inputs.shadow - The light shadow.
- * @return {Node<float>} The filtering result.
- */
- const PCFShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, shadowCoord, shadow, depthLayer } ) => {
- const depthCompare = ( uv, compare ) => {
- let depth = texture( depthTexture, uv );
- if ( depthTexture.isArrayTexture ) {
- depth = depth.depth( depthLayer );
- }
- return depth.compare( compare );
- };
- const mapSize = reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup );
- const radius = reference( 'radius', 'float', shadow ).setGroup( renderGroup );
- const texelSize = vec2( 1 ).div( mapSize );
- const radiusScaled = radius.mul( texelSize.x );
- // Use IGN to rotate sampling pattern per pixel (phi = IGN * 2π)
- const phi = interleavedGradientNoise( screenCoordinate.xy ).mul( 6.28318530718 );
- // 5 samples using Vogel disk distribution
- return add(
- depthCompare( shadowCoord.xy.add( vogelDiskSample( 0, 5, phi ).mul( radiusScaled ) ), shadowCoord.z ),
- depthCompare( shadowCoord.xy.add( vogelDiskSample( 1, 5, phi ).mul( radiusScaled ) ), shadowCoord.z ),
- depthCompare( shadowCoord.xy.add( vogelDiskSample( 2, 5, phi ).mul( radiusScaled ) ), shadowCoord.z ),
- depthCompare( shadowCoord.xy.add( vogelDiskSample( 3, 5, phi ).mul( radiusScaled ) ), shadowCoord.z ),
- depthCompare( shadowCoord.xy.add( vogelDiskSample( 4, 5, phi ).mul( radiusScaled ) ), shadowCoord.z )
- ).mul( 1 / 5 );
- } );
- /**
- * A shadow filtering function performing PCF soft filtering.
- *
- * @method
- * @param {Object} inputs - The input parameter object.
- * @param {DepthTexture} inputs.depthTexture - A reference to the shadow map's texture data.
- * @param {Node<vec3>} inputs.shadowCoord - The shadow coordinates.
- * @param {LightShadow} inputs.shadow - The light shadow.
- * @return {Node<float>} The filtering result.
- */
- const PCFSoftShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, shadowCoord, shadow, depthLayer } ) => {
- const depthCompare = ( uv, compare ) => {
- let depth = texture( depthTexture, uv );
- if ( depthTexture.isArrayTexture ) {
- depth = depth.depth( depthLayer );
- }
- return depth.compare( compare );
- };
- const mapSize = reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup );
- const texelSize = vec2( 1 ).div( mapSize );
- const dx = texelSize.x;
- const dy = texelSize.y;
- const uv = shadowCoord.xy;
- const f = fract( uv.mul( mapSize ).add( 0.5 ) );
- uv.subAssign( f.mul( texelSize ) );
- return add(
- depthCompare( uv, shadowCoord.z ),
- depthCompare( uv.add( vec2( dx, 0 ) ), shadowCoord.z ),
- depthCompare( uv.add( vec2( 0, dy ) ), shadowCoord.z ),
- depthCompare( uv.add( texelSize ), shadowCoord.z ),
- mix(
- depthCompare( uv.add( vec2( dx.negate(), 0 ) ), shadowCoord.z ),
- depthCompare( uv.add( vec2( dx.mul( 2 ), 0 ) ), shadowCoord.z ),
- f.x
- ),
- mix(
- depthCompare( uv.add( vec2( dx.negate(), dy ) ), shadowCoord.z ),
- depthCompare( uv.add( vec2( dx.mul( 2 ), dy ) ), shadowCoord.z ),
- f.x
- ),
- mix(
- depthCompare( uv.add( vec2( 0, dy.negate() ) ), shadowCoord.z ),
- depthCompare( uv.add( vec2( 0, dy.mul( 2 ) ) ), shadowCoord.z ),
- f.y
- ),
- mix(
- depthCompare( uv.add( vec2( dx, dy.negate() ) ), shadowCoord.z ),
- depthCompare( uv.add( vec2( dx, dy.mul( 2 ) ) ), shadowCoord.z ),
- f.y
- ),
- mix(
- mix(
- depthCompare( uv.add( vec2( dx.negate(), dy.negate() ) ), shadowCoord.z ),
- depthCompare( uv.add( vec2( dx.mul( 2 ), dy.negate() ) ), shadowCoord.z ),
- f.x
- ),
- mix(
- depthCompare( uv.add( vec2( dx.negate(), dy.mul( 2 ) ) ), shadowCoord.z ),
- depthCompare( uv.add( vec2( dx.mul( 2 ), dy.mul( 2 ) ) ), shadowCoord.z ),
- f.x
- ),
- f.y
- )
- ).mul( 1 / 9 );
- } );
- /**
- * A shadow filtering function performing VSM filtering.
- *
- * @method
- * @param {Object} inputs - The input parameter object.
- * @param {DepthTexture} inputs.depthTexture - A reference to the shadow map's texture data.
- * @param {Node<vec3>} inputs.shadowCoord - The shadow coordinates.
- * @return {Node<float>} The filtering result.
- */
- const VSMShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, shadowCoord, depthLayer } ) => {
- let distribution = texture( depthTexture ).sample( shadowCoord.xy );
- if ( depthTexture.isArrayTexture ) {
- distribution = distribution.depth( depthLayer );
- }
- distribution = distribution.rg;
- const mean = distribution.x;
- const variance = max$1( 0.0000001, distribution.y.mul( distribution.y ) );
- const hardShadow = step( shadowCoord.z, mean );
- // Early return if fully lit
- If( hardShadow.equal( 1.0 ), () => {
- return float( 1.0 );
- } );
- // Distance from mean
- const d = shadowCoord.z.sub( mean );
- // Chebyshev's inequality for upper bound on probability
- let p_max = variance.div( variance.add( d.mul( d ) ) );
- // Reduce light bleeding by remapping [amount, 1] to [0, 1]
- p_max = clamp( sub( p_max, 0.3 ).div( 0.65 ) );
- return max$1( hardShadow, p_max );
- } );
- //
- const linearDistance = /*@__PURE__*/ Fn( ( [ position, cameraNear, cameraFar ] ) => {
- let dist = positionWorld.sub( position ).length();
- dist = dist.sub( cameraNear ).div( cameraFar.sub( cameraNear ) );
- dist = dist.saturate(); // clamp to [ 0, 1 ]
- return dist;
- } );
- const linearShadowDistance = ( light ) => {
- const camera = light.shadow.camera;
- const nearDistance = reference( 'near', 'float', camera ).setGroup( renderGroup );
- const farDistance = reference( 'far', 'float', camera ).setGroup( renderGroup );
- const referencePosition = objectPosition( light );
- return linearDistance( referencePosition, nearDistance, farDistance );
- };
- /**
- * Retrieves or creates a shadow material for the given light source.
- *
- * This function checks if a shadow material already exists for the provided light.
- * If not, it creates a new `NodeMaterial` configured for shadow rendering and stores it
- * in the `shadowMaterialLib` for future use.
- *
- * @tsl
- * @function
- * @param {Light} light - The light source for which the shadow material is needed.
- * If the light is a point light, a depth node is calculated
- * using the linear shadow distance.
- * @returns {NodeMaterial} The shadow material associated with the given light.
- */
- const getShadowMaterial = ( light ) => {
- let material = shadowMaterialLib.get( light );
- if ( material === undefined ) {
- const depthNode = light.isPointLight ? linearShadowDistance( light ) : null;
- material = new NodeMaterial();
- material.colorNode = vec4( 0, 0, 0, 1 );
- material.depthNode = depthNode;
- material.isShadowPassMaterial = true; // Use to avoid other overrideMaterial override material.colorNode unintentionally when using material.shadowNode
- material.name = 'ShadowMaterial';
- material.fog = false;
- shadowMaterialLib.set( light, material );
- }
- return material;
- };
- /**
- * Disposes the shadow material for the given light source.
- *
- * @param {Light} light - The light source.
- */
- const disposeShadowMaterial = ( light ) => {
- const material = shadowMaterialLib.get( light );
- if ( material !== undefined ) {
- material.dispose();
- shadowMaterialLib.delete( light );
- }
- };
- //
- const _shadowRenderObjectLibrary = /*@__PURE__*/ new ChainMap();
- const _shadowRenderObjectKeys = [];
- /**
- * Creates a function to render shadow objects in a scene.
- *
- * @tsl
- * @function
- * @param {Renderer} renderer - The renderer.
- * @param {LightShadow} shadow - The light shadow object containing shadow properties.
- * @param {number} shadowType - The type of shadow map (e.g., BasicShadowMap).
- * @param {boolean} useVelocity - Whether to use velocity data for rendering.
- * @return {shadowRenderObjectFunction} A function that renders shadow objects.
- */
- const getShadowRenderObjectFunction = ( renderer, shadow, shadowType, useVelocity ) => {
- _shadowRenderObjectKeys[ 0 ] = renderer;
- _shadowRenderObjectKeys[ 1 ] = shadow;
- let renderObjectFunction = _shadowRenderObjectLibrary.get( _shadowRenderObjectKeys );
- if ( renderObjectFunction === undefined || ( renderObjectFunction.shadowType !== shadowType || renderObjectFunction.useVelocity !== useVelocity ) ) {
- renderObjectFunction = ( object, scene, _camera, geometry, material, group, ...params ) => {
- if ( object.castShadow === true || ( object.receiveShadow && shadowType === VSMShadowMap ) ) {
- if ( useVelocity ) {
- getDataFromObject( object ).useVelocity = true;
- }
- object.onBeforeShadow( renderer, object, _camera, shadow.camera, geometry, scene.overrideMaterial, group );
- renderer.renderObject( object, scene, _camera, geometry, material, group, ...params );
- object.onAfterShadow( renderer, object, _camera, shadow.camera, geometry, scene.overrideMaterial, group );
- }
- };
- renderObjectFunction.shadowType = shadowType;
- renderObjectFunction.useVelocity = useVelocity;
- _shadowRenderObjectLibrary.set( _shadowRenderObjectKeys, renderObjectFunction );
- }
- _shadowRenderObjectKeys[ 0 ] = null;
- _shadowRenderObjectKeys[ 1 ] = null;
- return renderObjectFunction;
- };
- /**
- * Represents the shader code for the first VSM render pass.
- *
- * @private
- * @method
- * @param {Object} inputs - The input parameter object.
- * @param {Node<float>} inputs.samples - The number of samples
- * @param {Node<float>} inputs.radius - The radius.
- * @param {Node<float>} inputs.size - The size.
- * @param {TextureNode} inputs.shadowPass - A reference to the render target's depth data.
- * @return {Node<vec2>} The VSM output.
- */
- const VSMPassVertical = /*@__PURE__*/ Fn( ( { samples, radius, size, shadowPass, depthLayer } ) => {
- const mean = float( 0 ).toVar( 'meanVertical' );
- const squaredMean = float( 0 ).toVar( 'squareMeanVertical' );
- const uvStride = samples.lessThanEqual( float( 1 ) ).select( float( 0 ), float( 2 ).div( samples.sub( 1 ) ) );
- const uvStart = samples.lessThanEqual( float( 1 ) ).select( float( 0 ), float( -1 ) );
- Loop( { start: int( 0 ), end: int( samples ), type: 'int', condition: '<' }, ( { i } ) => {
- const uvOffset = uvStart.add( float( i ).mul( uvStride ) );
- let depth = shadowPass.sample( add( screenCoordinate.xy, vec2( 0, uvOffset ).mul( radius ) ).div( size ) );
- if ( shadowPass.value.isArrayTexture ) {
- depth = depth.depth( depthLayer );
- }
- depth = depth.x;
- mean.addAssign( depth );
- squaredMean.addAssign( depth.mul( depth ) );
- } );
- mean.divAssign( samples );
- squaredMean.divAssign( samples );
- const std_dev = sqrt( squaredMean.sub( mean.mul( mean ) ).max( 0 ) );
- return vec2( mean, std_dev );
- } );
- /**
- * Represents the shader code for the second VSM render pass.
- *
- * @private
- * @method
- * @param {Object} inputs - The input parameter object.
- * @param {Node<float>} inputs.samples - The number of samples
- * @param {Node<float>} inputs.radius - The radius.
- * @param {Node<float>} inputs.size - The size.
- * @param {TextureNode} inputs.shadowPass - The result of the first VSM render pass.
- * @return {Node<vec2>} The VSM output.
- */
- const VSMPassHorizontal = /*@__PURE__*/ Fn( ( { samples, radius, size, shadowPass, depthLayer } ) => {
- const mean = float( 0 ).toVar( 'meanHorizontal' );
- const squaredMean = float( 0 ).toVar( 'squareMeanHorizontal' );
- const uvStride = samples.lessThanEqual( float( 1 ) ).select( float( 0 ), float( 2 ).div( samples.sub( 1 ) ) );
- const uvStart = samples.lessThanEqual( float( 1 ) ).select( float( 0 ), float( -1 ) );
- Loop( { start: int( 0 ), end: int( samples ), type: 'int', condition: '<' }, ( { i } ) => {
- const uvOffset = uvStart.add( float( i ).mul( uvStride ) );
- let distribution = shadowPass.sample( add( screenCoordinate.xy, vec2( uvOffset, 0 ).mul( radius ) ).div( size ) );
- if ( shadowPass.value.isArrayTexture ) {
- distribution = distribution.depth( depthLayer );
- }
- mean.addAssign( distribution.x );
- squaredMean.addAssign( add( distribution.y.mul( distribution.y ), distribution.x.mul( distribution.x ) ) );
- } );
- mean.divAssign( samples );
- squaredMean.divAssign( samples );
- const std_dev = sqrt( squaredMean.sub( mean.mul( mean ) ).max( 0 ) );
- return vec2( mean, std_dev );
- } );
- const _shadowFilterLib = [ BasicShadowFilter, PCFShadowFilter, PCFSoftShadowFilter, VSMShadowFilter ];
- //
- let _rendererState;
- const _quadMesh = /*@__PURE__*/ new QuadMesh();
- /**
- * Represents the default shadow implementation for lighting nodes.
- *
- * @augments ShadowBaseNode
- */
- class ShadowNode extends ShadowBaseNode {
- static get type() {
- return 'ShadowNode';
- }
- /**
- * Constructs a new shadow node.
- *
- * @param {Light} light - The shadow casting light.
- * @param {?LightShadow} [shadow=null] - An optional light shadow.
- */
- constructor( light, shadow = null ) {
- super( light );
- /**
- * The light shadow which defines the properties light's
- * shadow.
- *
- * @type {?LightShadow}
- * @default null
- */
- this.shadow = shadow || light.shadow;
- /**
- * A reference to the shadow map which is a render target.
- *
- * @type {?RenderTarget}
- * @default null
- */
- this.shadowMap = null;
- /**
- * Only relevant for VSM shadows. Render target for the
- * first VSM render pass.
- *
- * @type {?RenderTarget}
- * @default null
- */
- this.vsmShadowMapVertical = null;
- /**
- * Only relevant for VSM shadows. Render target for the
- * second VSM render pass.
- *
- * @type {?RenderTarget}
- * @default null
- */
- this.vsmShadowMapHorizontal = null;
- /**
- * Only relevant for VSM shadows. Node material which
- * is used to render the first VSM pass.
- *
- * @type {?NodeMaterial}
- * @default null
- */
- this.vsmMaterialVertical = null;
- /**
- * Only relevant for VSM shadows. Node material which
- * is used to render the second VSM pass.
- *
- * @type {?NodeMaterial}
- * @default null
- */
- this.vsmMaterialHorizontal = null;
- /**
- * A reference to the output node which defines the
- * final result of this shadow node.
- *
- * @type {?Node}
- * @private
- * @default null
- */
- this._node = null;
- /**
- * The current shadow map type of this shadow node.
- *
- * @type {?number}
- * @private
- * @default null
- */
- this._currentShadowType = null;
- /**
- * A Weak Map holding the current frame ID per camera. Used
- * to control the update of shadow maps.
- *
- * @type {WeakMap<Camera,number>}
- * @private
- */
- this._cameraFrameId = new WeakMap();
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isShadowNode = true;
- /**
- * This index can be used when overriding setupRenderTarget with a RenderTarget Array to specify the depth layer.
- *
- * @type {number}
- * @readonly
- * @default true
- */
- this.depthLayer = 0;
- }
- /**
- * Setups the shadow filtering.
- *
- * @param {NodeBuilder} builder - A reference to the current node builder.
- * @param {Object} inputs - A configuration object that defines the shadow filtering.
- * @param {Function} inputs.filterFn - This function defines the filtering type of the shadow map e.g. PCF.
- * @param {DepthTexture} inputs.depthTexture - A reference to the shadow map's texture data.
- * @param {Node<vec3>} inputs.shadowCoord - Shadow coordinates which are used to sample from the shadow map.
- * @param {LightShadow} inputs.shadow - The light shadow.
- * @return {Node<float>} The result node of the shadow filtering.
- */
- setupShadowFilter( builder, { filterFn, depthTexture, shadowCoord, shadow, depthLayer } ) {
- const frustumTest = shadowCoord.x.greaterThanEqual( 0 )
- .and( shadowCoord.x.lessThanEqual( 1 ) )
- .and( shadowCoord.y.greaterThanEqual( 0 ) )
- .and( shadowCoord.y.lessThanEqual( 1 ) )
- .and( shadowCoord.z.lessThanEqual( 1 ) );
- const shadowNode = filterFn( { depthTexture, shadowCoord, shadow, depthLayer } );
- return frustumTest.select( shadowNode, float( 1 ) );
- }
- /**
- * Setups the shadow coordinates.
- *
- * @param {NodeBuilder} builder - A reference to the current node builder.
- * @param {Node<vec3>} shadowPosition - A node representing the shadow position.
- * @return {Node<vec3>} The shadow coordinates.
- */
- setupShadowCoord( builder, shadowPosition ) {
- const { shadow } = this;
- const { renderer } = builder;
- const bias = reference( 'bias', 'float', shadow ).setGroup( renderGroup );
- let shadowCoord = shadowPosition;
- let coordZ;
- if ( shadow.camera.isOrthographicCamera || renderer.logarithmicDepthBuffer !== true ) {
- shadowCoord = shadowCoord.xyz.div( shadowCoord.w );
- coordZ = shadowCoord.z;
- if ( renderer.coordinateSystem === WebGPUCoordinateSystem ) {
- coordZ = coordZ.mul( 2 ).sub( 1 ); // WebGPU: Conversion [ 0, 1 ] to [ - 1, 1 ]
- }
- } else {
- const w = shadowCoord.w;
- shadowCoord = shadowCoord.xy.div( w ); // <-- Only divide X/Y coords since we don't need Z
- // The normally available "cameraNear" and "cameraFar" nodes cannot be used here because they do not get
- // updated to use the shadow camera. So, we have to declare our own "local" ones here.
- // TODO: How do we get the cameraNear/cameraFar nodes to use the shadow camera so we don't have to declare local ones here?
- const cameraNearLocal = reference( 'near', 'float', shadow.camera ).setGroup( renderGroup );
- const cameraFarLocal = reference( 'far', 'float', shadow.camera ).setGroup( renderGroup );
- coordZ = viewZToLogarithmicDepth( w.negate(), cameraNearLocal, cameraFarLocal );
- }
- shadowCoord = vec3(
- shadowCoord.x,
- shadowCoord.y.oneMinus(), // follow webgpu standards
- coordZ.add( bias )
- );
- return shadowCoord;
- }
- /**
- * Returns the shadow filtering function for the given shadow type.
- *
- * @param {number} type - The shadow type.
- * @return {Function} The filtering function.
- */
- getShadowFilterFn( type ) {
- return _shadowFilterLib[ type ];
- }
- setupRenderTarget( shadow, builder ) {
- const depthTexture = new DepthTexture( shadow.mapSize.width, shadow.mapSize.height );
- depthTexture.name = 'ShadowDepthTexture';
- depthTexture.compareFunction = LessCompare;
- const shadowMap = builder.createRenderTarget( shadow.mapSize.width, shadow.mapSize.height );
- shadowMap.texture.name = 'ShadowMap';
- shadowMap.texture.type = shadow.mapType;
- shadowMap.depthTexture = depthTexture;
- return { shadowMap, depthTexture };
- }
- /**
- * Setups the shadow output node.
- *
- * @param {NodeBuilder} builder - A reference to the current node builder.
- * @return {Node<vec3>} The shadow output node.
- */
- setupShadow( builder ) {
- const { renderer, camera } = builder;
- const { light, shadow } = this;
- const shadowMapType = renderer.shadowMap.type;
- const { depthTexture, shadowMap } = this.setupRenderTarget( shadow, builder );
- shadow.camera.coordinateSystem = camera.coordinateSystem;
- shadow.camera.updateProjectionMatrix();
- // VSM
- if ( shadowMapType === VSMShadowMap && shadow.isPointLightShadow !== true ) {
- depthTexture.compareFunction = null; // VSM does not use textureSampleCompare()/texture2DCompare()
- if ( shadowMap.depth > 1 ) {
- if ( ! shadowMap._vsmShadowMapVertical ) {
- shadowMap._vsmShadowMapVertical = builder.createRenderTarget( shadow.mapSize.width, shadow.mapSize.height, { format: RGFormat, type: HalfFloatType, depth: shadowMap.depth, depthBuffer: false } );
- shadowMap._vsmShadowMapVertical.texture.name = 'VSMVertical';
- }
- this.vsmShadowMapVertical = shadowMap._vsmShadowMapVertical;
- if ( ! shadowMap._vsmShadowMapHorizontal ) {
- shadowMap._vsmShadowMapHorizontal = builder.createRenderTarget( shadow.mapSize.width, shadow.mapSize.height, { format: RGFormat, type: HalfFloatType, depth: shadowMap.depth, depthBuffer: false } );
- shadowMap._vsmShadowMapHorizontal.texture.name = 'VSMHorizontal';
- }
- this.vsmShadowMapHorizontal = shadowMap._vsmShadowMapHorizontal;
- } else {
- this.vsmShadowMapVertical = builder.createRenderTarget( shadow.mapSize.width, shadow.mapSize.height, { format: RGFormat, type: HalfFloatType, depthBuffer: false } );
- this.vsmShadowMapHorizontal = builder.createRenderTarget( shadow.mapSize.width, shadow.mapSize.height, { format: RGFormat, type: HalfFloatType, depthBuffer: false } );
- }
- let shadowPassVertical = texture( depthTexture );
- if ( depthTexture.isArrayTexture ) {
- shadowPassVertical = shadowPassVertical.depth( this.depthLayer );
- }
- let shadowPassHorizontal = texture( this.vsmShadowMapVertical.texture );
- if ( depthTexture.isArrayTexture ) {
- shadowPassHorizontal = shadowPassHorizontal.depth( this.depthLayer );
- }
- const samples = reference( 'blurSamples', 'float', shadow ).setGroup( renderGroup );
- const radius = reference( 'radius', 'float', shadow ).setGroup( renderGroup );
- const size = reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup );
- let material = this.vsmMaterialVertical || ( this.vsmMaterialVertical = new NodeMaterial() );
- material.fragmentNode = VSMPassVertical( { samples, radius, size, shadowPass: shadowPassVertical, depthLayer: this.depthLayer } ).context( builder.getSharedContext() );
- material.name = 'VSMVertical';
- material = this.vsmMaterialHorizontal || ( this.vsmMaterialHorizontal = new NodeMaterial() );
- material.fragmentNode = VSMPassHorizontal( { samples, radius, size, shadowPass: shadowPassHorizontal, depthLayer: this.depthLayer } ).context( builder.getSharedContext() );
- material.name = 'VSMHorizontal';
- }
- //
- const shadowIntensity = reference( 'intensity', 'float', shadow ).setGroup( renderGroup );
- const normalBias = reference( 'normalBias', 'float', shadow ).setGroup( renderGroup );
- const shadowPosition = lightShadowMatrix( light ).mul( shadowPositionWorld.add( normalWorld.mul( normalBias ) ) );
- const shadowCoord = this.setupShadowCoord( builder, shadowPosition );
- //
- const filterFn = shadow.filterNode || this.getShadowFilterFn( renderer.shadowMap.type ) || null;
- if ( filterFn === null ) {
- throw new Error( 'THREE.WebGPURenderer: Shadow map type not supported yet.' );
- }
- const shadowDepthTexture = ( shadowMapType === VSMShadowMap && shadow.isPointLightShadow !== true ) ? this.vsmShadowMapHorizontal.texture : depthTexture;
- const shadowNode = this.setupShadowFilter( builder, { filterFn, shadowTexture: shadowMap.texture, depthTexture: shadowDepthTexture, shadowCoord, shadow, depthLayer: this.depthLayer } );
- let shadowColor;
- if ( shadowMap.texture.isCubeTexture ) {
- // For cube shadow maps (point lights), use cubeTexture with vec3 coordinates
- shadowColor = cubeTexture( shadowMap.texture, shadowCoord.xyz );
- } else {
- shadowColor = texture( shadowMap.texture, shadowCoord );
- if ( depthTexture.isArrayTexture ) {
- shadowColor = shadowColor.depth( this.depthLayer );
- }
- }
- const shadowOutput = mix( 1, shadowNode.rgb.mix( shadowColor, 1 ), shadowIntensity.mul( shadowColor.a ) ).toVar();
- this.shadowMap = shadowMap;
- this.shadow.map = shadowMap;
- // Shadow Output + Inspector
- const inspectName = `${ this.light.type } Shadow [ ${ this.light.name || 'ID: ' + this.light.id } ]`;
- return shadowOutput.toInspector( `${ inspectName } / Color`, () => {
- if ( this.shadowMap.texture.isCubeTexture ) {
- return cubeTexture( this.shadowMap.texture );
- }
- return texture( this.shadowMap.texture );
- } ).toInspector( `${ inspectName } / Depth`, () => {
- // TODO: Use linear depth
- if ( this.shadowMap.texture.isCubeTexture ) {
- return cubeTexture( this.shadowMap.texture ).r.oneMinus();
- }
- return textureLoad( this.shadowMap.depthTexture, uv$1().mul( textureSize( texture( this.shadowMap.depthTexture ) ) ) ).r.oneMinus();
- } );
- }
- /**
- * The implementation performs the setup of the output node. An output is only
- * produces if shadow mapping is globally enabled in the renderer.
- *
- * @param {NodeBuilder} builder - A reference to the current node builder.
- * @return {ShaderCallNodeInternal} The output node.
- */
- setup( builder ) {
- if ( builder.renderer.shadowMap.enabled === false ) return;
- return Fn( () => {
- const currentShadowType = builder.renderer.shadowMap.type;
- if ( this._currentShadowType !== currentShadowType ) {
- this._reset();
- this._node = null;
- }
- let node = this._node;
- this.setupShadowPosition( builder );
- if ( node === null ) {
- this._node = node = this.setupShadow( builder );
- this._currentShadowType = currentShadowType;
- }
- if ( builder.material.shadowNode ) { // @deprecated, r171
- warn( 'NodeMaterial: ".shadowNode" is deprecated. Use ".castShadowNode" instead.' );
- }
- if ( builder.material.receivedShadowNode ) {
- node = builder.material.receivedShadowNode( node );
- }
- return node;
- } )();
- }
- /**
- * Renders the shadow. The logic of this function could be included
- * into {@link ShadowNode#updateShadow} however more specialized shadow
- * nodes might require a custom shadow map rendering. By having a
- * dedicated method, it's easier to overwrite the default behavior.
- *
- * @param {NodeFrame} frame - A reference to the current node frame.
- */
- renderShadow( frame ) {
- const { shadow, shadowMap, light } = this;
- const { renderer, scene } = frame;
- shadow.updateMatrices( light );
- shadowMap.setSize( shadow.mapSize.width, shadow.mapSize.height, shadowMap.depth );
- const currentSceneName = scene.name;
- scene.name = `Shadow Map [ ${ light.name || 'ID: ' + light.id } ]`;
- renderer.render( scene, shadow.camera );
- scene.name = currentSceneName;
- }
- /**
- * Updates the shadow.
- *
- * @param {NodeFrame} frame - A reference to the current node frame.
- */
- updateShadow( frame ) {
- const { shadowMap, light, shadow } = this;
- const { renderer, scene, camera } = frame;
- const shadowType = renderer.shadowMap.type;
- const depthVersion = shadowMap.depthTexture.version;
- this._depthVersionCached = depthVersion;
- const _shadowCameraLayer = shadow.camera.layers.mask;
- if ( ( shadow.camera.layers.mask & 0xFFFFFFFE ) === 0 ) {
- shadow.camera.layers.mask = camera.layers.mask;
- }
- const currentRenderObjectFunction = renderer.getRenderObjectFunction();
- const currentMRT = renderer.getMRT();
- const useVelocity = currentMRT ? currentMRT.has( 'velocity' ) : false;
- _rendererState = resetRendererAndSceneState( renderer, scene, _rendererState );
- scene.overrideMaterial = getShadowMaterial( light );
- renderer.setRenderObjectFunction( getShadowRenderObjectFunction( renderer, shadow, shadowType, useVelocity ) );
- renderer.setClearColor( 0x000000, 0 );
- renderer.setRenderTarget( shadowMap );
- this.renderShadow( frame );
- renderer.setRenderObjectFunction( currentRenderObjectFunction );
- // vsm blur pass
- if ( shadowType === VSMShadowMap && shadow.isPointLightShadow !== true ) {
- this.vsmPass( renderer );
- }
- shadow.camera.layers.mask = _shadowCameraLayer;
- restoreRendererAndSceneState( renderer, scene, _rendererState );
- }
- /**
- * For VSM additional render passes are required.
- *
- * @param {Renderer} renderer - A reference to the current renderer.
- */
- vsmPass( renderer ) {
- const { shadow } = this;
- const depth = this.shadowMap.depth;
- this.vsmShadowMapVertical.setSize( shadow.mapSize.width, shadow.mapSize.height, depth );
- this.vsmShadowMapHorizontal.setSize( shadow.mapSize.width, shadow.mapSize.height, depth );
- renderer.setRenderTarget( this.vsmShadowMapVertical );
- _quadMesh.material = this.vsmMaterialVertical;
- _quadMesh.render( renderer );
- renderer.setRenderTarget( this.vsmShadowMapHorizontal );
- _quadMesh.material = this.vsmMaterialHorizontal;
- _quadMesh.render( renderer );
- }
- /**
- * Frees the internal resources of this shadow node.
- */
- dispose() {
- this._reset();
- super.dispose();
- }
- /**
- * Resets the resouce state of this shadow node.
- *
- * @private
- */
- _reset() {
- this._currentShadowType = null;
- disposeShadowMaterial( this.light );
- if ( this.shadowMap ) {
- this.shadowMap.dispose();
- this.shadowMap = null;
- }
- if ( this.vsmShadowMapVertical !== null ) {
- this.vsmShadowMapVertical.dispose();
- this.vsmShadowMapVertical = null;
- this.vsmMaterialVertical.dispose();
- this.vsmMaterialVertical = null;
- }
- if ( this.vsmShadowMapHorizontal !== null ) {
- this.vsmShadowMapHorizontal.dispose();
- this.vsmShadowMapHorizontal = null;
- this.vsmMaterialHorizontal.dispose();
- this.vsmMaterialHorizontal = null;
- }
- }
- /**
- * The implementation performs the update of the shadow map if necessary.
- *
- * @param {NodeFrame} frame - A reference to the current node frame.
- */
- updateBefore( frame ) {
- const { shadow } = this;
- let needsUpdate = shadow.needsUpdate || shadow.autoUpdate;
- if ( needsUpdate ) {
- if ( this._cameraFrameId[ frame.camera ] === frame.frameId ) {
- needsUpdate = false;
- }
- this._cameraFrameId[ frame.camera ] = frame.frameId;
- }
- if ( needsUpdate ) {
- this.updateShadow( frame );
- if ( this.shadowMap.depthTexture.version === this._depthVersionCached ) {
- shadow.needsUpdate = false;
- }
- }
- }
- }
- /**
- * Shadow Render Object Function.
- *
- * @function shadowRenderObjectFunction
- * @param {Object3D} object - The 3D object to render.
- * @param {Scene} scene - The scene containing the object.
- * @param {Camera} _camera - The camera used for rendering.
- * @param {BufferGeometry} geometry - The geometry of the object.
- * @param {Material} material - The material of the object.
- * @param {Group} group - The group the object belongs to.
- * @param {...any} params - Additional parameters for rendering.
- */
- /**
- * TSL function for creating an instance of `ShadowNode`.
- *
- * @tsl
- * @function
- * @param {Light} light - The shadow casting light.
- * @param {?LightShadow} [shadow] - The light shadow.
- * @return {ShadowNode} The created shadow node.
- */
- const shadow = ( light, shadow ) => nodeObject( new ShadowNode( light, shadow ) );
- const _clearColor$1 = /*@__PURE__*/ new Color();
- const _projScreenMatrix$1 = /*@__PURE__*/ new Matrix4();
- const _lightPositionWorld = /*@__PURE__*/ new Vector3();
- const _lookTarget = /*@__PURE__*/ new Vector3();
- // Cube map face directions and up vectors for point light shadows
- // Face order: +X, -X, +Y, -Y, +Z, -Z
- // WebGPU coordinate system - Y faces swapped to match texture sampling convention
- const _cubeDirectionsWebGPU = [
- /*@__PURE__*/ new Vector3( 1, 0, 0 ), /*@__PURE__*/ new Vector3( -1, 0, 0 ), /*@__PURE__*/ new Vector3( 0, -1, 0 ),
- /*@__PURE__*/ new Vector3( 0, 1, 0 ), /*@__PURE__*/ new Vector3( 0, 0, 1 ), /*@__PURE__*/ new Vector3( 0, 0, -1 )
- ];
- const _cubeUpsWebGPU = [
- /*@__PURE__*/ new Vector3( 0, -1, 0 ), /*@__PURE__*/ new Vector3( 0, -1, 0 ), /*@__PURE__*/ new Vector3( 0, 0, -1 ),
- /*@__PURE__*/ new Vector3( 0, 0, 1 ), /*@__PURE__*/ new Vector3( 0, -1, 0 ), /*@__PURE__*/ new Vector3( 0, -1, 0 )
- ];
- // WebGL coordinate system - standard OpenGL convention
- const _cubeDirectionsWebGL = [
- /*@__PURE__*/ new Vector3( 1, 0, 0 ), /*@__PURE__*/ new Vector3( -1, 0, 0 ), /*@__PURE__*/ new Vector3( 0, 1, 0 ),
- /*@__PURE__*/ new Vector3( 0, -1, 0 ), /*@__PURE__*/ new Vector3( 0, 0, 1 ), /*@__PURE__*/ new Vector3( 0, 0, -1 )
- ];
- const _cubeUpsWebGL = [
- /*@__PURE__*/ new Vector3( 0, -1, 0 ), /*@__PURE__*/ new Vector3( 0, -1, 0 ), /*@__PURE__*/ new Vector3( 0, 0, 1 ),
- /*@__PURE__*/ new Vector3( 0, 0, -1 ), /*@__PURE__*/ new Vector3( 0, -1, 0 ), /*@__PURE__*/ new Vector3( 0, -1, 0 )
- ];
- const BasicPointShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, bd3D, dp } ) => {
- return cubeTexture( depthTexture, bd3D ).compare( dp );
- } );
- /**
- * A shadow filtering function for point lights using Vogel disk sampling and IGN.
- *
- * Uses 5 samples distributed via Vogel disk pattern in tangent space around the
- * sample direction, rotated per-pixel using Interleaved Gradient Noise (IGN).
- *
- * @method
- * @param {Object} inputs - The input parameter object.
- * @param {CubeDepthTexture} inputs.depthTexture - A reference to the shadow cube map.
- * @param {Node<vec3>} inputs.bd3D - The normalized direction from light to fragment.
- * @param {Node<float>} inputs.dp - The depth value to compare against.
- * @param {LightShadow} inputs.shadow - The light shadow.
- * @return {Node<float>} The filtering result.
- */
- const PointShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, bd3D, dp, shadow } ) => {
- const radius = reference( 'radius', 'float', shadow ).setGroup( renderGroup );
- const mapSize = reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup );
- const texelSize = radius.div( mapSize.x );
- // Build a tangent-space coordinate system for applying offsets
- const absDir = abs( bd3D );
- const tangent = normalize( cross( bd3D, absDir.x.greaterThan( absDir.z ).select( vec3( 0, 1, 0 ), vec3( 1, 0, 0 ) ) ) );
- const bitangent = cross( bd3D, tangent );
- // Use IGN to rotate sampling pattern per pixel (phi = IGN * 2π)
- const phi = interleavedGradientNoise( screenCoordinate.xy ).mul( 6.28318530718 );
- // 5 samples using Vogel disk distribution in tangent space
- const sample0 = vogelDiskSample( 0, 5, phi );
- const sample1 = vogelDiskSample( 1, 5, phi );
- const sample2 = vogelDiskSample( 2, 5, phi );
- const sample3 = vogelDiskSample( 3, 5, phi );
- const sample4 = vogelDiskSample( 4, 5, phi );
- return cubeTexture( depthTexture, bd3D.add( tangent.mul( sample0.x ).add( bitangent.mul( sample0.y ) ).mul( texelSize ) ) ).compare( dp )
- .add( cubeTexture( depthTexture, bd3D.add( tangent.mul( sample1.x ).add( bitangent.mul( sample1.y ) ).mul( texelSize ) ) ).compare( dp ) )
- .add( cubeTexture( depthTexture, bd3D.add( tangent.mul( sample2.x ).add( bitangent.mul( sample2.y ) ).mul( texelSize ) ) ).compare( dp ) )
- .add( cubeTexture( depthTexture, bd3D.add( tangent.mul( sample3.x ).add( bitangent.mul( sample3.y ) ).mul( texelSize ) ) ).compare( dp ) )
- .add( cubeTexture( depthTexture, bd3D.add( tangent.mul( sample4.x ).add( bitangent.mul( sample4.y ) ).mul( texelSize ) ) ).compare( dp ) )
- .mul( 1.0 / 5.0 );
- } );
- const pointShadowFilter = /*@__PURE__*/ Fn( ( { filterFn, depthTexture, shadowCoord, shadow } ) => {
- // for point lights, the uniform @vShadowCoord is re-purposed to hold
- // the vector from the light to the world-space position of the fragment.
- const lightToPosition = shadowCoord.xyz.toVar();
- const lightToPositionLength = lightToPosition.length();
- const cameraNearLocal = uniform( 'float' ).setGroup( renderGroup ).onRenderUpdate( () => shadow.camera.near );
- const cameraFarLocal = uniform( 'float' ).setGroup( renderGroup ).onRenderUpdate( () => shadow.camera.far );
- const bias = reference( 'bias', 'float', shadow ).setGroup( renderGroup );
- const result = float( 1.0 ).toVar();
- If( lightToPositionLength.sub( cameraFarLocal ).lessThanEqual( 0.0 ).and( lightToPositionLength.sub( cameraNearLocal ).greaterThanEqual( 0.0 ) ), () => {
- // dp = normalized distance from light to fragment position
- const dp = lightToPositionLength.sub( cameraNearLocal ).div( cameraFarLocal.sub( cameraNearLocal ) ).toVar(); // need to clamp?
- dp.addAssign( bias );
- // bd3D = base direction 3D (direction from light to fragment)
- const bd3D = lightToPosition.normalize();
- // percentage-closer filtering using cube texture sampling
- result.assign( filterFn( { depthTexture, bd3D, dp, shadow } ) );
- } );
- return result;
- } );
- /**
- * Represents the shadow implementation for point light nodes.
- *
- * @augments ShadowNode
- */
- class PointShadowNode extends ShadowNode {
- static get type() {
- return 'PointShadowNode';
- }
- /**
- * Constructs a new point shadow node.
- *
- * @param {PointLight} light - The shadow casting point light.
- * @param {?PointLightShadow} [shadow=null] - An optional point light shadow.
- */
- constructor( light, shadow = null ) {
- super( light, shadow );
- }
- /**
- * Overwrites the default implementation to return point light shadow specific
- * filtering functions.
- *
- * @param {number} type - The shadow type.
- * @return {Function} The filtering function.
- */
- getShadowFilterFn( type ) {
- return type === BasicShadowMap ? BasicPointShadowFilter : PointShadowFilter;
- }
- /**
- * Overwrites the default implementation so the unaltered shadow position is used.
- *
- * @param {NodeBuilder} builder - A reference to the current node builder.
- * @param {Node<vec3>} shadowPosition - A node representing the shadow position.
- * @return {Node<vec3>} The shadow coordinates.
- */
- setupShadowCoord( builder, shadowPosition ) {
- return shadowPosition;
- }
- /**
- * Overwrites the default implementation to only use point light specific
- * shadow filter functions.
- *
- * @param {NodeBuilder} builder - A reference to the current node builder.
- * @param {Object} inputs - A configuration object that defines the shadow filtering.
- * @param {Function} inputs.filterFn - This function defines the filtering type of the shadow map e.g. PCF.
- * @param {DepthTexture} inputs.depthTexture - A reference to the shadow map's depth texture.
- * @param {Node<vec3>} inputs.shadowCoord - Shadow coordinates which are used to sample from the shadow map.
- * @param {LightShadow} inputs.shadow - The light shadow.
- * @return {Node<float>} The result node of the shadow filtering.
- */
- setupShadowFilter( builder, { filterFn, depthTexture, shadowCoord, shadow } ) {
- return pointShadowFilter( { filterFn, depthTexture, shadowCoord, shadow } );
- }
- /**
- * Overwrites the default implementation to create a CubeRenderTarget with CubeDepthTexture.
- *
- * @param {LightShadow} shadow - The light shadow object.
- * @param {NodeBuilder} builder - A reference to the current node builder.
- * @return {Object} An object containing the shadow map and depth texture.
- */
- setupRenderTarget( shadow, builder ) {
- const depthTexture = new CubeDepthTexture( shadow.mapSize.width );
- depthTexture.name = 'PointShadowDepthTexture';
- depthTexture.compareFunction = LessCompare;
- const shadowMap = builder.createCubeRenderTarget( shadow.mapSize.width );
- shadowMap.texture.name = 'PointShadowMap';
- shadowMap.depthTexture = depthTexture;
- return { shadowMap, depthTexture };
- }
- /**
- * Overwrites the default implementation with point light specific
- * rendering code.
- *
- * @param {NodeFrame} frame - A reference to the current node frame.
- */
- renderShadow( frame ) {
- const { shadow, shadowMap, light } = this;
- const { renderer, scene } = frame;
- const camera = shadow.camera;
- const shadowMatrix = shadow.matrix;
- // Select cube directions/ups based on coordinate system
- const isWebGPU = renderer.coordinateSystem === WebGPUCoordinateSystem;
- const cubeDirections = isWebGPU ? _cubeDirectionsWebGPU : _cubeDirectionsWebGL;
- const cubeUps = isWebGPU ? _cubeUpsWebGPU : _cubeUpsWebGL;
- shadowMap.setSize( shadow.mapSize.width, shadow.mapSize.width );
- //
- const previousAutoClear = renderer.autoClear;
- const previousClearColor = renderer.getClearColor( _clearColor$1 );
- const previousClearAlpha = renderer.getClearAlpha();
- renderer.autoClear = false;
- renderer.setClearColor( shadow.clearColor, shadow.clearAlpha );
- // Render each cube face
- for ( let face = 0; face < 6; face ++ ) {
- // Set render target to the specific cube face
- renderer.setRenderTarget( shadowMap, face );
- renderer.clear();
- // Update shadow camera matrices for this face
- const far = light.distance || camera.far;
- if ( far !== camera.far ) {
- camera.far = far;
- camera.updateProjectionMatrix();
- }
- _lightPositionWorld.setFromMatrixPosition( light.matrixWorld );
- camera.position.copy( _lightPositionWorld );
- _lookTarget.copy( camera.position );
- _lookTarget.add( cubeDirections[ face ] );
- camera.up.copy( cubeUps[ face ] );
- camera.lookAt( _lookTarget );
- camera.updateMatrixWorld();
- shadowMatrix.makeTranslation( - _lightPositionWorld.x, - _lightPositionWorld.y, - _lightPositionWorld.z );
- _projScreenMatrix$1.multiplyMatrices( camera.projectionMatrix, camera.matrixWorldInverse );
- shadow._frustum.setFromProjectionMatrix( _projScreenMatrix$1, camera.coordinateSystem, camera.reversedDepth );
- //
- const currentSceneName = scene.name;
- scene.name = `Point Light Shadow [ ${ light.name || 'ID: ' + light.id } ] - Face ${ face + 1 }`;
- renderer.render( scene, camera );
- scene.name = currentSceneName;
- }
- //
- renderer.autoClear = previousAutoClear;
- renderer.setClearColor( previousClearColor, previousClearAlpha );
- }
- }
- /**
- * TSL function for creating an instance of `PointShadowNode`.
- *
- * @tsl
- * @function
- * @param {PointLight} light - The shadow casting point light.
- * @param {?PointLightShadow} [shadow=null] - An optional point light shadow.
- * @return {PointShadowNode} The created point shadow node.
- */
- const pointShadow = ( light, shadow ) => nodeObject( new PointShadowNode( light, shadow ) );
- /**
- * Base class for analytic light nodes.
- *
- * @augments LightingNode
- */
- class AnalyticLightNode extends LightingNode {
- static get type() {
- return 'AnalyticLightNode';
- }
- /**
- * Constructs a new analytic light node.
- *
- * @param {?Light} [light=null] - The light source.
- */
- constructor( light = null ) {
- super();
- /**
- * The light source.
- *
- * @type {?Light}
- * @default null
- */
- this.light = light;
- /**
- * The light's color value.
- *
- * @type {Color}
- */
- this.color = new Color();
- /**
- * The light's color node. Points to `colorNode` of the light source, if set. Otherwise
- * it creates a uniform node based on {@link AnalyticLightNode#color}.
- *
- * @type {Node}
- */
- this.colorNode = ( light && light.colorNode ) || uniform( this.color ).setGroup( renderGroup );
- /**
- * This property is used to retain a reference to the original value of {@link AnalyticLightNode#colorNode}.
- * The final color node is represented by a different node when using shadows.
- *
- * @type {?Node}
- * @default null
- */
- this.baseColorNode = null;
- /**
- * Represents the light's shadow.
- *
- * @type {?ShadowNode}
- * @default null
- */
- this.shadowNode = null;
- /**
- * Represents the light's shadow color.
- *
- * @type {?Node}
- * @default null
- */
- this.shadowColorNode = null;
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isAnalyticLightNode = true;
- /**
- * Overwritten since analytic light nodes are updated
- * once per frame.
- *
- * @type {string}
- * @default 'frame'
- */
- this.updateType = NodeUpdateType.FRAME;
- if ( light && light.shadow ) {
- this._shadowDisposeListener = () => {
- this.disposeShadow();
- };
- light.addEventListener( 'dispose', this._shadowDisposeListener );
- }
- }
- dispose() {
- if ( this._shadowDisposeListener ) {
- this.light.removeEventListener( 'dispose', this._shadowDisposeListener );
- }
- super.dispose();
- }
- /**
- * Frees internal resources related to shadows.
- */
- disposeShadow() {
- if ( this.shadowNode !== null ) {
- this.shadowNode.dispose();
- this.shadowNode = null;
- }
- this.shadowColorNode = null;
- if ( this.baseColorNode !== null ) {
- this.colorNode = this.baseColorNode;
- this.baseColorNode = null;
- }
- }
- getHash() {
- return this.light.uuid;
- }
- /**
- * Returns a node representing a direction vector which points from the current
- * position in view space to the light's position in view space.
- *
- * @param {NodeBuilder} builder - The builder object used for setting up the light.
- * @return {Node<vec3>} The light vector node.
- */
- getLightVector( builder ) {
- return lightViewPosition( this.light ).sub( builder.context.positionView || positionView );
- }
- /**
- * Sets up the direct lighting for the analytic light node.
- *
- * @abstract
- * @param {NodeBuilder} builder - The builder object used for setting up the light.
- * @return {Object|undefined} The direct light data (color and direction).
- */
- setupDirect( /*builder*/ ) { }
- /**
- * Sets up the direct rect area lighting for the analytic light node.
- *
- * @abstract
- * @param {NodeBuilder} builder - The builder object used for setting up the light.
- * @return {Object|undefined} The direct rect area light data.
- */
- setupDirectRectArea( /*builder*/ ) { }
- /**
- * Setups the shadow node for this light. The method exists so concrete light classes
- * can setup different types of shadow nodes.
- *
- * @return {ShadowNode} The created shadow node.
- */
- setupShadowNode() {
- return shadow( this.light );
- }
- /**
- * Setups the shadow for this light. This method is only executed if the light
- * cast shadows and the current build object receives shadows. It incorporates
- * shadows into the lighting computation.
- *
- * @param {NodeBuilder} builder - The current node builder.
- */
- setupShadow( builder ) {
- const { renderer } = builder;
- if ( renderer.shadowMap.enabled === false ) return;
- let shadowColorNode = this.shadowColorNode;
- if ( shadowColorNode === null ) {
- const customShadowNode = this.light.shadow.shadowNode;
- let shadowNode;
- if ( customShadowNode !== undefined ) {
- shadowNode = nodeObject( customShadowNode );
- } else {
- shadowNode = this.setupShadowNode();
- }
- this.shadowNode = shadowNode;
- this.shadowColorNode = shadowColorNode = this.colorNode.mul( shadowNode );
- this.baseColorNode = this.colorNode;
- }
- //
- if ( builder.context.getShadow ) {
- shadowColorNode = builder.context.getShadow( this, builder );
- }
- this.colorNode = shadowColorNode;
- }
- /**
- * Unlike most other nodes, lighting nodes do not return a output node in {@link Node#setup}.
- * The main purpose of lighting nodes is to configure the current {@link LightingModel} and/or
- * invocate the respective interface methods.
- *
- * @param {NodeBuilder} builder - The current node builder.
- */
- setup( builder ) {
- this.colorNode = this.baseColorNode || this.colorNode;
- if ( this.light.castShadow ) {
- if ( builder.object.receiveShadow ) {
- this.setupShadow( builder );
- }
- } else if ( this.shadowNode !== null ) {
- this.shadowNode.dispose();
- this.shadowNode = null;
- this.shadowColorNode = null;
- }
- const directLightData = this.setupDirect( builder );
- const directRectAreaLightData = this.setupDirectRectArea( builder );
- if ( directLightData ) {
- builder.lightsNode.setupDirectLight( builder, this, directLightData );
- }
- if ( directRectAreaLightData ) {
- builder.lightsNode.setupDirectRectAreaLight( builder, this, directRectAreaLightData );
- }
- }
- /**
- * The update method is used to update light uniforms per frame.
- * Potentially overwritten in concrete light nodes to update light
- * specific uniforms.
- *
- * @param {NodeFrame} frame - A reference to the current node frame.
- */
- update( /*frame*/ ) {
- const { light } = this;
- this.color.copy( light.color ).multiplyScalar( light.intensity );
- }
- }
- /**
- * Represents a `discard` shader operation in TSL.
- *
- * @method
- * @param {Object} inputs - The input parameter object.
- * @param {Node<float>} inputs.lightDistance - The distance of the light's position to the current fragment position.
- * @param {Node<float>} inputs.cutoffDistance - The light's cutoff distance.
- * @param {Node<float>} inputs.decayExponent - The light's decay exponent.
- * @return {Node<float>} The distance falloff.
- */
- const getDistanceAttenuation = /*@__PURE__*/ Fn( ( { lightDistance, cutoffDistance, decayExponent } ) => {
- // based upon Frostbite 3 Moving to Physically-based Rendering
- // page 32, equation 26: E[window1]
- // https://seblagarde.files.wordpress.com/2015/07/course_notes_moving_frostbite_to_pbr_v32.pdf
- const distanceFalloff = lightDistance.pow( decayExponent ).max( 0.01 ).reciprocal();
- return cutoffDistance.greaterThan( 0 ).select(
- distanceFalloff.mul( lightDistance.div( cutoffDistance ).pow4().oneMinus().clamp().pow2() ),
- distanceFalloff
- );
- } ); // validated
- const directPointLight = ( { color, lightVector, cutoffDistance, decayExponent } ) => {
- const lightDirection = lightVector.normalize();
- const lightDistance = lightVector.length();
- const attenuation = getDistanceAttenuation( {
- lightDistance,
- cutoffDistance,
- decayExponent
- } );
- const lightColor = color.mul( attenuation );
- return { lightDirection, lightColor };
- };
- /**
- * Module for representing point lights as nodes.
- *
- * @augments AnalyticLightNode
- */
- class PointLightNode extends AnalyticLightNode {
- static get type() {
- return 'PointLightNode';
- }
- /**
- * Constructs a new point light node.
- *
- * @param {?PointLight} [light=null] - The point light source.
- */
- constructor( light = null ) {
- super( light );
- /**
- * Uniform node representing the cutoff distance.
- *
- * @type {UniformNode<float>}
- */
- this.cutoffDistanceNode = uniform( 0 ).setGroup( renderGroup );
- /**
- * Uniform node representing the decay exponent.
- *
- * @type {UniformNode<float>}
- */
- this.decayExponentNode = uniform( 2 ).setGroup( renderGroup );
- }
- /**
- * Overwritten to updated point light specific uniforms.
- *
- * @param {NodeFrame} frame - A reference to the current node frame.
- */
- update( frame ) {
- const { light } = this;
- super.update( frame );
- this.cutoffDistanceNode.value = light.distance;
- this.decayExponentNode.value = light.decay;
- }
- /**
- * Overwritten to setup point light specific shadow.
- *
- * @return {PointShadowNode}
- */
- setupShadowNode() {
- return pointShadow( this.light );
- }
- setupDirect( builder ) {
- return directPointLight( {
- color: this.colorNode,
- lightVector: this.getLightVector( builder ),
- cutoffDistance: this.cutoffDistanceNode,
- decayExponent: this.decayExponentNode
- } );
- }
- }
- /**
- * Creates a 2x2 checkerboard pattern that can be used as procedural texture data.
- *
- * @tsl
- * @function
- * @param {Node<vec2>} coord - The uv coordinates.
- * @return {Node<float>} The result data.
- */
- const checker = /*@__PURE__*/ Fn( ( [ coord = uv$1() ] ) => {
- const uv = coord.mul( 2.0 );
- const cx = uv.x.floor();
- const cy = uv.y.floor();
- const result = cx.add( cy ).mod( 2.0 );
- return result.sign();
- } );
- /**
- * Generates a circle based on the uv coordinates.
- *
- * @tsl
- * @function
- * @param {Node<vec2>} coord - The uv to generate the circle.
- * @return {Node<float>} The circle shape.
- */
- const shapeCircle = Fn( ( [ coord = uv$1() ], { renderer, material } ) => {
- const len2 = lengthSq( coord.mul( 2 ).sub( 1 ) );
- let alpha;
- if ( material.alphaToCoverage && renderer.currentSamples > 0 ) {
- const dlen = float( len2.fwidth() ).toVar();
- alpha = smoothstep( dlen.oneMinus(), dlen.add( 1 ), len2 ).oneMinus();
- } else {
- alpha = select( len2.greaterThan( 1.0 ), 0, 1 );
- }
- return alpha;
- } );
- // Three.js Transpiler
- // https://raw.githubusercontent.com/AcademySoftwareFoundation/MaterialX/main/libraries/stdlib/genglsl/lib/mx_noise.glsl
- const mx_select = /*@__PURE__*/ Fn( ( [ b_immutable, t_immutable, f_immutable ] ) => {
- const f = float( f_immutable ).toVar();
- const t = float( t_immutable ).toVar();
- const b = bool( b_immutable ).toVar();
- return select( b, t, f );
- } ).setLayout( {
- name: 'mx_select',
- type: 'float',
- inputs: [
- { name: 'b', type: 'bool' },
- { name: 't', type: 'float' },
- { name: 'f', type: 'float' }
- ]
- } );
- const mx_negate_if = /*@__PURE__*/ Fn( ( [ val_immutable, b_immutable ] ) => {
- const b = bool( b_immutable ).toVar();
- const val = float( val_immutable ).toVar();
- return select( b, val.negate(), val );
- } ).setLayout( {
- name: 'mx_negate_if',
- type: 'float',
- inputs: [
- { name: 'val', type: 'float' },
- { name: 'b', type: 'bool' }
- ]
- } );
- const mx_floor = /*@__PURE__*/ Fn( ( [ x_immutable ] ) => {
- const x = float( x_immutable ).toVar();
- return int( floor( x ) );
- } ).setLayout( {
- name: 'mx_floor',
- type: 'int',
- inputs: [
- { name: 'x', type: 'float' }
- ]
- } );
- const mx_floorfrac = /*@__PURE__*/ Fn( ( [ x_immutable, i ] ) => {
- const x = float( x_immutable ).toVar();
- i.assign( mx_floor( x ) );
- return x.sub( float( i ) );
- } );
- const mx_bilerp_0 = /*@__PURE__*/ Fn( ( [ v0_immutable, v1_immutable, v2_immutable, v3_immutable, s_immutable, t_immutable ] ) => {
- const t = float( t_immutable ).toVar();
- const s = float( s_immutable ).toVar();
- const v3 = float( v3_immutable ).toVar();
- const v2 = float( v2_immutable ).toVar();
- const v1 = float( v1_immutable ).toVar();
- const v0 = float( v0_immutable ).toVar();
- const s1 = float( sub( 1.0, s ) ).toVar();
- return sub( 1.0, t ).mul( v0.mul( s1 ).add( v1.mul( s ) ) ).add( t.mul( v2.mul( s1 ).add( v3.mul( s ) ) ) );
- } ).setLayout( {
- name: 'mx_bilerp_0',
- type: 'float',
- inputs: [
- { name: 'v0', type: 'float' },
- { name: 'v1', type: 'float' },
- { name: 'v2', type: 'float' },
- { name: 'v3', type: 'float' },
- { name: 's', type: 'float' },
- { name: 't', type: 'float' }
- ]
- } );
- const mx_bilerp_1 = /*@__PURE__*/ Fn( ( [ v0_immutable, v1_immutable, v2_immutable, v3_immutable, s_immutable, t_immutable ] ) => {
- const t = float( t_immutable ).toVar();
- const s = float( s_immutable ).toVar();
- const v3 = vec3( v3_immutable ).toVar();
- const v2 = vec3( v2_immutable ).toVar();
- const v1 = vec3( v1_immutable ).toVar();
- const v0 = vec3( v0_immutable ).toVar();
- const s1 = float( sub( 1.0, s ) ).toVar();
- return sub( 1.0, t ).mul( v0.mul( s1 ).add( v1.mul( s ) ) ).add( t.mul( v2.mul( s1 ).add( v3.mul( s ) ) ) );
- } ).setLayout( {
- name: 'mx_bilerp_1',
- type: 'vec3',
- inputs: [
- { name: 'v0', type: 'vec3' },
- { name: 'v1', type: 'vec3' },
- { name: 'v2', type: 'vec3' },
- { name: 'v3', type: 'vec3' },
- { name: 's', type: 'float' },
- { name: 't', type: 'float' }
- ]
- } );
- const mx_bilerp = /*@__PURE__*/ overloadingFn( [ mx_bilerp_0, mx_bilerp_1 ] );
- const mx_trilerp_0 = /*@__PURE__*/ Fn( ( [ v0_immutable, v1_immutable, v2_immutable, v3_immutable, v4_immutable, v5_immutable, v6_immutable, v7_immutable, s_immutable, t_immutable, r_immutable ] ) => {
- const r = float( r_immutable ).toVar();
- const t = float( t_immutable ).toVar();
- const s = float( s_immutable ).toVar();
- const v7 = float( v7_immutable ).toVar();
- const v6 = float( v6_immutable ).toVar();
- const v5 = float( v5_immutable ).toVar();
- const v4 = float( v4_immutable ).toVar();
- const v3 = float( v3_immutable ).toVar();
- const v2 = float( v2_immutable ).toVar();
- const v1 = float( v1_immutable ).toVar();
- const v0 = float( v0_immutable ).toVar();
- const s1 = float( sub( 1.0, s ) ).toVar();
- const t1 = float( sub( 1.0, t ) ).toVar();
- const r1 = float( sub( 1.0, r ) ).toVar();
- return r1.mul( t1.mul( v0.mul( s1 ).add( v1.mul( s ) ) ).add( t.mul( v2.mul( s1 ).add( v3.mul( s ) ) ) ) ).add( r.mul( t1.mul( v4.mul( s1 ).add( v5.mul( s ) ) ).add( t.mul( v6.mul( s1 ).add( v7.mul( s ) ) ) ) ) );
- } ).setLayout( {
- name: 'mx_trilerp_0',
- type: 'float',
- inputs: [
- { name: 'v0', type: 'float' },
- { name: 'v1', type: 'float' },
- { name: 'v2', type: 'float' },
- { name: 'v3', type: 'float' },
- { name: 'v4', type: 'float' },
- { name: 'v5', type: 'float' },
- { name: 'v6', type: 'float' },
- { name: 'v7', type: 'float' },
- { name: 's', type: 'float' },
- { name: 't', type: 'float' },
- { name: 'r', type: 'float' }
- ]
- } );
- const mx_trilerp_1 = /*@__PURE__*/ Fn( ( [ v0_immutable, v1_immutable, v2_immutable, v3_immutable, v4_immutable, v5_immutable, v6_immutable, v7_immutable, s_immutable, t_immutable, r_immutable ] ) => {
- const r = float( r_immutable ).toVar();
- const t = float( t_immutable ).toVar();
- const s = float( s_immutable ).toVar();
- const v7 = vec3( v7_immutable ).toVar();
- const v6 = vec3( v6_immutable ).toVar();
- const v5 = vec3( v5_immutable ).toVar();
- const v4 = vec3( v4_immutable ).toVar();
- const v3 = vec3( v3_immutable ).toVar();
- const v2 = vec3( v2_immutable ).toVar();
- const v1 = vec3( v1_immutable ).toVar();
- const v0 = vec3( v0_immutable ).toVar();
- const s1 = float( sub( 1.0, s ) ).toVar();
- const t1 = float( sub( 1.0, t ) ).toVar();
- const r1 = float( sub( 1.0, r ) ).toVar();
- return r1.mul( t1.mul( v0.mul( s1 ).add( v1.mul( s ) ) ).add( t.mul( v2.mul( s1 ).add( v3.mul( s ) ) ) ) ).add( r.mul( t1.mul( v4.mul( s1 ).add( v5.mul( s ) ) ).add( t.mul( v6.mul( s1 ).add( v7.mul( s ) ) ) ) ) );
- } ).setLayout( {
- name: 'mx_trilerp_1',
- type: 'vec3',
- inputs: [
- { name: 'v0', type: 'vec3' },
- { name: 'v1', type: 'vec3' },
- { name: 'v2', type: 'vec3' },
- { name: 'v3', type: 'vec3' },
- { name: 'v4', type: 'vec3' },
- { name: 'v5', type: 'vec3' },
- { name: 'v6', type: 'vec3' },
- { name: 'v7', type: 'vec3' },
- { name: 's', type: 'float' },
- { name: 't', type: 'float' },
- { name: 'r', type: 'float' }
- ]
- } );
- const mx_trilerp = /*@__PURE__*/ overloadingFn( [ mx_trilerp_0, mx_trilerp_1 ] );
- const mx_gradient_float_0 = /*@__PURE__*/ Fn( ( [ hash_immutable, x_immutable, y_immutable ] ) => {
- const y = float( y_immutable ).toVar();
- const x = float( x_immutable ).toVar();
- const hash = uint( hash_immutable ).toVar();
- const h = uint( hash.bitAnd( uint( 7 ) ) ).toVar();
- const u = float( mx_select( h.lessThan( uint( 4 ) ), x, y ) ).toVar();
- const v = float( mul( 2.0, mx_select( h.lessThan( uint( 4 ) ), y, x ) ) ).toVar();
- return mx_negate_if( u, bool( h.bitAnd( uint( 1 ) ) ) ).add( mx_negate_if( v, bool( h.bitAnd( uint( 2 ) ) ) ) );
- } ).setLayout( {
- name: 'mx_gradient_float_0',
- type: 'float',
- inputs: [
- { name: 'hash', type: 'uint' },
- { name: 'x', type: 'float' },
- { name: 'y', type: 'float' }
- ]
- } );
- const mx_gradient_float_1 = /*@__PURE__*/ Fn( ( [ hash_immutable, x_immutable, y_immutable, z_immutable ] ) => {
- const z = float( z_immutable ).toVar();
- const y = float( y_immutable ).toVar();
- const x = float( x_immutable ).toVar();
- const hash = uint( hash_immutable ).toVar();
- const h = uint( hash.bitAnd( uint( 15 ) ) ).toVar();
- const u = float( mx_select( h.lessThan( uint( 8 ) ), x, y ) ).toVar();
- const v = float( mx_select( h.lessThan( uint( 4 ) ), y, mx_select( h.equal( uint( 12 ) ).or( h.equal( uint( 14 ) ) ), x, z ) ) ).toVar();
- return mx_negate_if( u, bool( h.bitAnd( uint( 1 ) ) ) ).add( mx_negate_if( v, bool( h.bitAnd( uint( 2 ) ) ) ) );
- } ).setLayout( {
- name: 'mx_gradient_float_1',
- type: 'float',
- inputs: [
- { name: 'hash', type: 'uint' },
- { name: 'x', type: 'float' },
- { name: 'y', type: 'float' },
- { name: 'z', type: 'float' }
- ]
- } );
- const mx_gradient_float = /*@__PURE__*/ overloadingFn( [ mx_gradient_float_0, mx_gradient_float_1 ] );
- const mx_gradient_vec3_0 = /*@__PURE__*/ Fn( ( [ hash_immutable, x_immutable, y_immutable ] ) => {
- const y = float( y_immutable ).toVar();
- const x = float( x_immutable ).toVar();
- const hash = uvec3( hash_immutable ).toVar();
- return vec3( mx_gradient_float( hash.x, x, y ), mx_gradient_float( hash.y, x, y ), mx_gradient_float( hash.z, x, y ) );
- } ).setLayout( {
- name: 'mx_gradient_vec3_0',
- type: 'vec3',
- inputs: [
- { name: 'hash', type: 'uvec3' },
- { name: 'x', type: 'float' },
- { name: 'y', type: 'float' }
- ]
- } );
- const mx_gradient_vec3_1 = /*@__PURE__*/ Fn( ( [ hash_immutable, x_immutable, y_immutable, z_immutable ] ) => {
- const z = float( z_immutable ).toVar();
- const y = float( y_immutable ).toVar();
- const x = float( x_immutable ).toVar();
- const hash = uvec3( hash_immutable ).toVar();
- return vec3( mx_gradient_float( hash.x, x, y, z ), mx_gradient_float( hash.y, x, y, z ), mx_gradient_float( hash.z, x, y, z ) );
- } ).setLayout( {
- name: 'mx_gradient_vec3_1',
- type: 'vec3',
- inputs: [
- { name: 'hash', type: 'uvec3' },
- { name: 'x', type: 'float' },
- { name: 'y', type: 'float' },
- { name: 'z', type: 'float' }
- ]
- } );
- const mx_gradient_vec3 = /*@__PURE__*/ overloadingFn( [ mx_gradient_vec3_0, mx_gradient_vec3_1 ] );
- const mx_gradient_scale2d_0 = /*@__PURE__*/ Fn( ( [ v_immutable ] ) => {
- const v = float( v_immutable ).toVar();
- return mul( 0.6616, v );
- } ).setLayout( {
- name: 'mx_gradient_scale2d_0',
- type: 'float',
- inputs: [
- { name: 'v', type: 'float' }
- ]
- } );
- const mx_gradient_scale3d_0 = /*@__PURE__*/ Fn( ( [ v_immutable ] ) => {
- const v = float( v_immutable ).toVar();
- return mul( 0.9820, v );
- } ).setLayout( {
- name: 'mx_gradient_scale3d_0',
- type: 'float',
- inputs: [
- { name: 'v', type: 'float' }
- ]
- } );
- const mx_gradient_scale2d_1 = /*@__PURE__*/ Fn( ( [ v_immutable ] ) => {
- const v = vec3( v_immutable ).toVar();
- return mul( 0.6616, v );
- } ).setLayout( {
- name: 'mx_gradient_scale2d_1',
- type: 'vec3',
- inputs: [
- { name: 'v', type: 'vec3' }
- ]
- } );
- const mx_gradient_scale2d = /*@__PURE__*/ overloadingFn( [ mx_gradient_scale2d_0, mx_gradient_scale2d_1 ] );
- const mx_gradient_scale3d_1 = /*@__PURE__*/ Fn( ( [ v_immutable ] ) => {
- const v = vec3( v_immutable ).toVar();
- return mul( 0.9820, v );
- } ).setLayout( {
- name: 'mx_gradient_scale3d_1',
- type: 'vec3',
- inputs: [
- { name: 'v', type: 'vec3' }
- ]
- } );
- const mx_gradient_scale3d = /*@__PURE__*/ overloadingFn( [ mx_gradient_scale3d_0, mx_gradient_scale3d_1 ] );
- const mx_rotl32 = /*@__PURE__*/ Fn( ( [ x_immutable, k_immutable ] ) => {
- const k = int( k_immutable ).toVar();
- const x = uint( x_immutable ).toVar();
- return x.shiftLeft( k ).bitOr( x.shiftRight( int( 32 ).sub( k ) ) );
- } ).setLayout( {
- name: 'mx_rotl32',
- type: 'uint',
- inputs: [
- { name: 'x', type: 'uint' },
- { name: 'k', type: 'int' }
- ]
- } );
- const mx_bjmix = /*@__PURE__*/ Fn( ( [ a, b, c ] ) => {
- a.subAssign( c );
- a.bitXorAssign( mx_rotl32( c, int( 4 ) ) );
- c.addAssign( b );
- b.subAssign( a );
- b.bitXorAssign( mx_rotl32( a, int( 6 ) ) );
- a.addAssign( c );
- c.subAssign( b );
- c.bitXorAssign( mx_rotl32( b, int( 8 ) ) );
- b.addAssign( a );
- a.subAssign( c );
- a.bitXorAssign( mx_rotl32( c, int( 16 ) ) );
- c.addAssign( b );
- b.subAssign( a );
- b.bitXorAssign( mx_rotl32( a, int( 19 ) ) );
- a.addAssign( c );
- c.subAssign( b );
- c.bitXorAssign( mx_rotl32( b, int( 4 ) ) );
- b.addAssign( a );
- } );
- const mx_bjfinal = /*@__PURE__*/ Fn( ( [ a_immutable, b_immutable, c_immutable ] ) => {
- const c = uint( c_immutable ).toVar();
- const b = uint( b_immutable ).toVar();
- const a = uint( a_immutable ).toVar();
- c.bitXorAssign( b );
- c.subAssign( mx_rotl32( b, int( 14 ) ) );
- a.bitXorAssign( c );
- a.subAssign( mx_rotl32( c, int( 11 ) ) );
- b.bitXorAssign( a );
- b.subAssign( mx_rotl32( a, int( 25 ) ) );
- c.bitXorAssign( b );
- c.subAssign( mx_rotl32( b, int( 16 ) ) );
- a.bitXorAssign( c );
- a.subAssign( mx_rotl32( c, int( 4 ) ) );
- b.bitXorAssign( a );
- b.subAssign( mx_rotl32( a, int( 14 ) ) );
- c.bitXorAssign( b );
- c.subAssign( mx_rotl32( b, int( 24 ) ) );
- return c;
- } ).setLayout( {
- name: 'mx_bjfinal',
- type: 'uint',
- inputs: [
- { name: 'a', type: 'uint' },
- { name: 'b', type: 'uint' },
- { name: 'c', type: 'uint' }
- ]
- } );
- const mx_bits_to_01 = /*@__PURE__*/ Fn( ( [ bits_immutable ] ) => {
- const bits = uint( bits_immutable ).toVar();
- return float( bits ).div( float( uint( int( 0xffffffff ) ) ) );
- } ).setLayout( {
- name: 'mx_bits_to_01',
- type: 'float',
- inputs: [
- { name: 'bits', type: 'uint' }
- ]
- } );
- const mx_fade = /*@__PURE__*/ Fn( ( [ t_immutable ] ) => {
- const t = float( t_immutable ).toVar();
- return t.mul( t ).mul( t ).mul( t.mul( t.mul( 6.0 ).sub( 15.0 ) ).add( 10.0 ) );
- } ).setLayout( {
- name: 'mx_fade',
- type: 'float',
- inputs: [
- { name: 't', type: 'float' }
- ]
- } );
- const mx_hash_int_0 = /*@__PURE__*/ Fn( ( [ x_immutable ] ) => {
- const x = int( x_immutable ).toVar();
- const len = uint( uint( 1 ) ).toVar();
- const seed = uint( uint( int( 0xdeadbeef ) ).add( len.shiftLeft( uint( 2 ) ) ).add( uint( 13 ) ) ).toVar();
- return mx_bjfinal( seed.add( uint( x ) ), seed, seed );
- } ).setLayout( {
- name: 'mx_hash_int_0',
- type: 'uint',
- inputs: [
- { name: 'x', type: 'int' }
- ]
- } );
- const mx_hash_int_1 = /*@__PURE__*/ Fn( ( [ x_immutable, y_immutable ] ) => {
- const y = int( y_immutable ).toVar();
- const x = int( x_immutable ).toVar();
- const len = uint( uint( 2 ) ).toVar();
- const a = uint().toVar(), b = uint().toVar(), c = uint().toVar();
- a.assign( b.assign( c.assign( uint( int( 0xdeadbeef ) ).add( len.shiftLeft( uint( 2 ) ) ).add( uint( 13 ) ) ) ) );
- a.addAssign( uint( x ) );
- b.addAssign( uint( y ) );
- return mx_bjfinal( a, b, c );
- } ).setLayout( {
- name: 'mx_hash_int_1',
- type: 'uint',
- inputs: [
- { name: 'x', type: 'int' },
- { name: 'y', type: 'int' }
- ]
- } );
- const mx_hash_int_2 = /*@__PURE__*/ Fn( ( [ x_immutable, y_immutable, z_immutable ] ) => {
- const z = int( z_immutable ).toVar();
- const y = int( y_immutable ).toVar();
- const x = int( x_immutable ).toVar();
- const len = uint( uint( 3 ) ).toVar();
- const a = uint().toVar(), b = uint().toVar(), c = uint().toVar();
- a.assign( b.assign( c.assign( uint( int( 0xdeadbeef ) ).add( len.shiftLeft( uint( 2 ) ) ).add( uint( 13 ) ) ) ) );
- a.addAssign( uint( x ) );
- b.addAssign( uint( y ) );
- c.addAssign( uint( z ) );
- return mx_bjfinal( a, b, c );
- } ).setLayout( {
- name: 'mx_hash_int_2',
- type: 'uint',
- inputs: [
- { name: 'x', type: 'int' },
- { name: 'y', type: 'int' },
- { name: 'z', type: 'int' }
- ]
- } );
- const mx_hash_int_3 = /*@__PURE__*/ Fn( ( [ x_immutable, y_immutable, z_immutable, xx_immutable ] ) => {
- const xx = int( xx_immutable ).toVar();
- const z = int( z_immutable ).toVar();
- const y = int( y_immutable ).toVar();
- const x = int( x_immutable ).toVar();
- const len = uint( uint( 4 ) ).toVar();
- const a = uint().toVar(), b = uint().toVar(), c = uint().toVar();
- a.assign( b.assign( c.assign( uint( int( 0xdeadbeef ) ).add( len.shiftLeft( uint( 2 ) ) ).add( uint( 13 ) ) ) ) );
- a.addAssign( uint( x ) );
- b.addAssign( uint( y ) );
- c.addAssign( uint( z ) );
- mx_bjmix( a, b, c );
- a.addAssign( uint( xx ) );
- return mx_bjfinal( a, b, c );
- } ).setLayout( {
- name: 'mx_hash_int_3',
- type: 'uint',
- inputs: [
- { name: 'x', type: 'int' },
- { name: 'y', type: 'int' },
- { name: 'z', type: 'int' },
- { name: 'xx', type: 'int' }
- ]
- } );
- const mx_hash_int_4 = /*@__PURE__*/ Fn( ( [ x_immutable, y_immutable, z_immutable, xx_immutable, yy_immutable ] ) => {
- const yy = int( yy_immutable ).toVar();
- const xx = int( xx_immutable ).toVar();
- const z = int( z_immutable ).toVar();
- const y = int( y_immutable ).toVar();
- const x = int( x_immutable ).toVar();
- const len = uint( uint( 5 ) ).toVar();
- const a = uint().toVar(), b = uint().toVar(), c = uint().toVar();
- a.assign( b.assign( c.assign( uint( int( 0xdeadbeef ) ).add( len.shiftLeft( uint( 2 ) ) ).add( uint( 13 ) ) ) ) );
- a.addAssign( uint( x ) );
- b.addAssign( uint( y ) );
- c.addAssign( uint( z ) );
- mx_bjmix( a, b, c );
- a.addAssign( uint( xx ) );
- b.addAssign( uint( yy ) );
- return mx_bjfinal( a, b, c );
- } ).setLayout( {
- name: 'mx_hash_int_4',
- type: 'uint',
- inputs: [
- { name: 'x', type: 'int' },
- { name: 'y', type: 'int' },
- { name: 'z', type: 'int' },
- { name: 'xx', type: 'int' },
- { name: 'yy', type: 'int' }
- ]
- } );
- const mx_hash_int = /*@__PURE__*/ overloadingFn( [ mx_hash_int_0, mx_hash_int_1, mx_hash_int_2, mx_hash_int_3, mx_hash_int_4 ] );
- const mx_hash_vec3_0 = /*@__PURE__*/ Fn( ( [ x_immutable, y_immutable ] ) => {
- const y = int( y_immutable ).toVar();
- const x = int( x_immutable ).toVar();
- const h = uint( mx_hash_int( x, y ) ).toVar();
- const result = uvec3().toVar();
- result.x.assign( h.bitAnd( int( 0xFF ) ) );
- result.y.assign( h.shiftRight( int( 8 ) ).bitAnd( int( 0xFF ) ) );
- result.z.assign( h.shiftRight( int( 16 ) ).bitAnd( int( 0xFF ) ) );
- return result;
- } ).setLayout( {
- name: 'mx_hash_vec3_0',
- type: 'uvec3',
- inputs: [
- { name: 'x', type: 'int' },
- { name: 'y', type: 'int' }
- ]
- } );
- const mx_hash_vec3_1 = /*@__PURE__*/ Fn( ( [ x_immutable, y_immutable, z_immutable ] ) => {
- const z = int( z_immutable ).toVar();
- const y = int( y_immutable ).toVar();
- const x = int( x_immutable ).toVar();
- const h = uint( mx_hash_int( x, y, z ) ).toVar();
- const result = uvec3().toVar();
- result.x.assign( h.bitAnd( int( 0xFF ) ) );
- result.y.assign( h.shiftRight( int( 8 ) ).bitAnd( int( 0xFF ) ) );
- result.z.assign( h.shiftRight( int( 16 ) ).bitAnd( int( 0xFF ) ) );
- return result;
- } ).setLayout( {
- name: 'mx_hash_vec3_1',
- type: 'uvec3',
- inputs: [
- { name: 'x', type: 'int' },
- { name: 'y', type: 'int' },
- { name: 'z', type: 'int' }
- ]
- } );
- const mx_hash_vec3 = /*@__PURE__*/ overloadingFn( [ mx_hash_vec3_0, mx_hash_vec3_1 ] );
- const mx_perlin_noise_float_0 = /*@__PURE__*/ Fn( ( [ p_immutable ] ) => {
- const p = vec2( p_immutable ).toVar();
- const X = int().toVar(), Y = int().toVar();
- const fx = float( mx_floorfrac( p.x, X ) ).toVar();
- const fy = float( mx_floorfrac( p.y, Y ) ).toVar();
- const u = float( mx_fade( fx ) ).toVar();
- const v = float( mx_fade( fy ) ).toVar();
- const result = float( mx_bilerp( mx_gradient_float( mx_hash_int( X, Y ), fx, fy ), mx_gradient_float( mx_hash_int( X.add( int( 1 ) ), Y ), fx.sub( 1.0 ), fy ), mx_gradient_float( mx_hash_int( X, Y.add( int( 1 ) ) ), fx, fy.sub( 1.0 ) ), mx_gradient_float( mx_hash_int( X.add( int( 1 ) ), Y.add( int( 1 ) ) ), fx.sub( 1.0 ), fy.sub( 1.0 ) ), u, v ) ).toVar();
- return mx_gradient_scale2d( result );
- } ).setLayout( {
- name: 'mx_perlin_noise_float_0',
- type: 'float',
- inputs: [
- { name: 'p', type: 'vec2' }
- ]
- } );
- const mx_perlin_noise_float_1 = /*@__PURE__*/ Fn( ( [ p_immutable ] ) => {
- const p = vec3( p_immutable ).toVar();
- const X = int().toVar(), Y = int().toVar(), Z = int().toVar();
- const fx = float( mx_floorfrac( p.x, X ) ).toVar();
- const fy = float( mx_floorfrac( p.y, Y ) ).toVar();
- const fz = float( mx_floorfrac( p.z, Z ) ).toVar();
- const u = float( mx_fade( fx ) ).toVar();
- const v = float( mx_fade( fy ) ).toVar();
- const w = float( mx_fade( fz ) ).toVar();
- const result = float( mx_trilerp( mx_gradient_float( mx_hash_int( X, Y, Z ), fx, fy, fz ), mx_gradient_float( mx_hash_int( X.add( int( 1 ) ), Y, Z ), fx.sub( 1.0 ), fy, fz ), mx_gradient_float( mx_hash_int( X, Y.add( int( 1 ) ), Z ), fx, fy.sub( 1.0 ), fz ), mx_gradient_float( mx_hash_int( X.add( int( 1 ) ), Y.add( int( 1 ) ), Z ), fx.sub( 1.0 ), fy.sub( 1.0 ), fz ), mx_gradient_float( mx_hash_int( X, Y, Z.add( int( 1 ) ) ), fx, fy, fz.sub( 1.0 ) ), mx_gradient_float( mx_hash_int( X.add( int( 1 ) ), Y, Z.add( int( 1 ) ) ), fx.sub( 1.0 ), fy, fz.sub( 1.0 ) ), mx_gradient_float( mx_hash_int( X, Y.add( int( 1 ) ), Z.add( int( 1 ) ) ), fx, fy.sub( 1.0 ), fz.sub( 1.0 ) ), mx_gradient_float( mx_hash_int( X.add( int( 1 ) ), Y.add( int( 1 ) ), Z.add( int( 1 ) ) ), fx.sub( 1.0 ), fy.sub( 1.0 ), fz.sub( 1.0 ) ), u, v, w ) ).toVar();
- return mx_gradient_scale3d( result );
- } ).setLayout( {
- name: 'mx_perlin_noise_float_1',
- type: 'float',
- inputs: [
- { name: 'p', type: 'vec3' }
- ]
- } );
- const mx_perlin_noise_float = /*@__PURE__*/ overloadingFn( [ mx_perlin_noise_float_0, mx_perlin_noise_float_1 ] );
- const mx_perlin_noise_vec3_0 = /*@__PURE__*/ Fn( ( [ p_immutable ] ) => {
- const p = vec2( p_immutable ).toVar();
- const X = int().toVar(), Y = int().toVar();
- const fx = float( mx_floorfrac( p.x, X ) ).toVar();
- const fy = float( mx_floorfrac( p.y, Y ) ).toVar();
- const u = float( mx_fade( fx ) ).toVar();
- const v = float( mx_fade( fy ) ).toVar();
- const result = vec3( mx_bilerp( mx_gradient_vec3( mx_hash_vec3( X, Y ), fx, fy ), mx_gradient_vec3( mx_hash_vec3( X.add( int( 1 ) ), Y ), fx.sub( 1.0 ), fy ), mx_gradient_vec3( mx_hash_vec3( X, Y.add( int( 1 ) ) ), fx, fy.sub( 1.0 ) ), mx_gradient_vec3( mx_hash_vec3( X.add( int( 1 ) ), Y.add( int( 1 ) ) ), fx.sub( 1.0 ), fy.sub( 1.0 ) ), u, v ) ).toVar();
- return mx_gradient_scale2d( result );
- } ).setLayout( {
- name: 'mx_perlin_noise_vec3_0',
- type: 'vec3',
- inputs: [
- { name: 'p', type: 'vec2' }
- ]
- } );
- const mx_perlin_noise_vec3_1 = /*@__PURE__*/ Fn( ( [ p_immutable ] ) => {
- const p = vec3( p_immutable ).toVar();
- const X = int().toVar(), Y = int().toVar(), Z = int().toVar();
- const fx = float( mx_floorfrac( p.x, X ) ).toVar();
- const fy = float( mx_floorfrac( p.y, Y ) ).toVar();
- const fz = float( mx_floorfrac( p.z, Z ) ).toVar();
- const u = float( mx_fade( fx ) ).toVar();
- const v = float( mx_fade( fy ) ).toVar();
- const w = float( mx_fade( fz ) ).toVar();
- const result = vec3( mx_trilerp( mx_gradient_vec3( mx_hash_vec3( X, Y, Z ), fx, fy, fz ), mx_gradient_vec3( mx_hash_vec3( X.add( int( 1 ) ), Y, Z ), fx.sub( 1.0 ), fy, fz ), mx_gradient_vec3( mx_hash_vec3( X, Y.add( int( 1 ) ), Z ), fx, fy.sub( 1.0 ), fz ), mx_gradient_vec3( mx_hash_vec3( X.add( int( 1 ) ), Y.add( int( 1 ) ), Z ), fx.sub( 1.0 ), fy.sub( 1.0 ), fz ), mx_gradient_vec3( mx_hash_vec3( X, Y, Z.add( int( 1 ) ) ), fx, fy, fz.sub( 1.0 ) ), mx_gradient_vec3( mx_hash_vec3( X.add( int( 1 ) ), Y, Z.add( int( 1 ) ) ), fx.sub( 1.0 ), fy, fz.sub( 1.0 ) ), mx_gradient_vec3( mx_hash_vec3( X, Y.add( int( 1 ) ), Z.add( int( 1 ) ) ), fx, fy.sub( 1.0 ), fz.sub( 1.0 ) ), mx_gradient_vec3( mx_hash_vec3( X.add( int( 1 ) ), Y.add( int( 1 ) ), Z.add( int( 1 ) ) ), fx.sub( 1.0 ), fy.sub( 1.0 ), fz.sub( 1.0 ) ), u, v, w ) ).toVar();
- return mx_gradient_scale3d( result );
- } ).setLayout( {
- name: 'mx_perlin_noise_vec3_1',
- type: 'vec3',
- inputs: [
- { name: 'p', type: 'vec3' }
- ]
- } );
- const mx_perlin_noise_vec3 = /*@__PURE__*/ overloadingFn( [ mx_perlin_noise_vec3_0, mx_perlin_noise_vec3_1 ] );
- const mx_cell_noise_float_0 = /*@__PURE__*/ Fn( ( [ p_immutable ] ) => {
- const p = float( p_immutable ).toVar();
- const ix = int( mx_floor( p ) ).toVar();
- return mx_bits_to_01( mx_hash_int( ix ) );
- } ).setLayout( {
- name: 'mx_cell_noise_float_0',
- type: 'float',
- inputs: [
- { name: 'p', type: 'float' }
- ]
- } );
- const mx_cell_noise_float_1 = /*@__PURE__*/ Fn( ( [ p_immutable ] ) => {
- const p = vec2( p_immutable ).toVar();
- const ix = int( mx_floor( p.x ) ).toVar();
- const iy = int( mx_floor( p.y ) ).toVar();
- return mx_bits_to_01( mx_hash_int( ix, iy ) );
- } ).setLayout( {
- name: 'mx_cell_noise_float_1',
- type: 'float',
- inputs: [
- { name: 'p', type: 'vec2' }
- ]
- } );
- const mx_cell_noise_float_2 = /*@__PURE__*/ Fn( ( [ p_immutable ] ) => {
- const p = vec3( p_immutable ).toVar();
- const ix = int( mx_floor( p.x ) ).toVar();
- const iy = int( mx_floor( p.y ) ).toVar();
- const iz = int( mx_floor( p.z ) ).toVar();
- return mx_bits_to_01( mx_hash_int( ix, iy, iz ) );
- } ).setLayout( {
- name: 'mx_cell_noise_float_2',
- type: 'float',
- inputs: [
- { name: 'p', type: 'vec3' }
- ]
- } );
- const mx_cell_noise_float_3 = /*@__PURE__*/ Fn( ( [ p_immutable ] ) => {
- const p = vec4( p_immutable ).toVar();
- const ix = int( mx_floor( p.x ) ).toVar();
- const iy = int( mx_floor( p.y ) ).toVar();
- const iz = int( mx_floor( p.z ) ).toVar();
- const iw = int( mx_floor( p.w ) ).toVar();
- return mx_bits_to_01( mx_hash_int( ix, iy, iz, iw ) );
- } ).setLayout( {
- name: 'mx_cell_noise_float_3',
- type: 'float',
- inputs: [
- { name: 'p', type: 'vec4' }
- ]
- } );
- const mx_cell_noise_float$1 = /*@__PURE__*/ overloadingFn( [ mx_cell_noise_float_0, mx_cell_noise_float_1, mx_cell_noise_float_2, mx_cell_noise_float_3 ] );
- const mx_cell_noise_vec3_0 = /*@__PURE__*/ Fn( ( [ p_immutable ] ) => {
- const p = float( p_immutable ).toVar();
- const ix = int( mx_floor( p ) ).toVar();
- return vec3( mx_bits_to_01( mx_hash_int( ix, int( 0 ) ) ), mx_bits_to_01( mx_hash_int( ix, int( 1 ) ) ), mx_bits_to_01( mx_hash_int( ix, int( 2 ) ) ) );
- } ).setLayout( {
- name: 'mx_cell_noise_vec3_0',
- type: 'vec3',
- inputs: [
- { name: 'p', type: 'float' }
- ]
- } );
- const mx_cell_noise_vec3_1 = /*@__PURE__*/ Fn( ( [ p_immutable ] ) => {
- const p = vec2( p_immutable ).toVar();
- const ix = int( mx_floor( p.x ) ).toVar();
- const iy = int( mx_floor( p.y ) ).toVar();
- return vec3( mx_bits_to_01( mx_hash_int( ix, iy, int( 0 ) ) ), mx_bits_to_01( mx_hash_int( ix, iy, int( 1 ) ) ), mx_bits_to_01( mx_hash_int( ix, iy, int( 2 ) ) ) );
- } ).setLayout( {
- name: 'mx_cell_noise_vec3_1',
- type: 'vec3',
- inputs: [
- { name: 'p', type: 'vec2' }
- ]
- } );
- const mx_cell_noise_vec3_2 = /*@__PURE__*/ Fn( ( [ p_immutable ] ) => {
- const p = vec3( p_immutable ).toVar();
- const ix = int( mx_floor( p.x ) ).toVar();
- const iy = int( mx_floor( p.y ) ).toVar();
- const iz = int( mx_floor( p.z ) ).toVar();
- return vec3( mx_bits_to_01( mx_hash_int( ix, iy, iz, int( 0 ) ) ), mx_bits_to_01( mx_hash_int( ix, iy, iz, int( 1 ) ) ), mx_bits_to_01( mx_hash_int( ix, iy, iz, int( 2 ) ) ) );
- } ).setLayout( {
- name: 'mx_cell_noise_vec3_2',
- type: 'vec3',
- inputs: [
- { name: 'p', type: 'vec3' }
- ]
- } );
- const mx_cell_noise_vec3_3 = /*@__PURE__*/ Fn( ( [ p_immutable ] ) => {
- const p = vec4( p_immutable ).toVar();
- const ix = int( mx_floor( p.x ) ).toVar();
- const iy = int( mx_floor( p.y ) ).toVar();
- const iz = int( mx_floor( p.z ) ).toVar();
- const iw = int( mx_floor( p.w ) ).toVar();
- return vec3( mx_bits_to_01( mx_hash_int( ix, iy, iz, iw, int( 0 ) ) ), mx_bits_to_01( mx_hash_int( ix, iy, iz, iw, int( 1 ) ) ), mx_bits_to_01( mx_hash_int( ix, iy, iz, iw, int( 2 ) ) ) );
- } ).setLayout( {
- name: 'mx_cell_noise_vec3_3',
- type: 'vec3',
- inputs: [
- { name: 'p', type: 'vec4' }
- ]
- } );
- const mx_cell_noise_vec3 = /*@__PURE__*/ overloadingFn( [ mx_cell_noise_vec3_0, mx_cell_noise_vec3_1, mx_cell_noise_vec3_2, mx_cell_noise_vec3_3 ] );
- const mx_fractal_noise_float$1 = /*@__PURE__*/ Fn( ( [ p_immutable, octaves_immutable, lacunarity_immutable, diminish_immutable ] ) => {
- const diminish = float( diminish_immutable ).toVar();
- const lacunarity = float( lacunarity_immutable ).toVar();
- const octaves = int( octaves_immutable ).toVar();
- const p = vec3( p_immutable ).toVar();
- const result = float( 0.0 ).toVar();
- const amplitude = float( 1.0 ).toVar();
- Loop( octaves, () => {
- result.addAssign( amplitude.mul( mx_perlin_noise_float( p ) ) );
- amplitude.mulAssign( diminish );
- p.mulAssign( lacunarity );
- } );
- return result;
- } ).setLayout( {
- name: 'mx_fractal_noise_float',
- type: 'float',
- inputs: [
- { name: 'p', type: 'vec3' },
- { name: 'octaves', type: 'int' },
- { name: 'lacunarity', type: 'float' },
- { name: 'diminish', type: 'float' }
- ]
- } );
- const mx_fractal_noise_vec3$1 = /*@__PURE__*/ Fn( ( [ p_immutable, octaves_immutable, lacunarity_immutable, diminish_immutable ] ) => {
- const diminish = float( diminish_immutable ).toVar();
- const lacunarity = float( lacunarity_immutable ).toVar();
- const octaves = int( octaves_immutable ).toVar();
- const p = vec3( p_immutable ).toVar();
- const result = vec3( 0.0 ).toVar();
- const amplitude = float( 1.0 ).toVar();
- Loop( octaves, () => {
- result.addAssign( amplitude.mul( mx_perlin_noise_vec3( p ) ) );
- amplitude.mulAssign( diminish );
- p.mulAssign( lacunarity );
- } );
- return result;
- } ).setLayout( {
- name: 'mx_fractal_noise_vec3',
- type: 'vec3',
- inputs: [
- { name: 'p', type: 'vec3' },
- { name: 'octaves', type: 'int' },
- { name: 'lacunarity', type: 'float' },
- { name: 'diminish', type: 'float' }
- ]
- } );
- const mx_fractal_noise_vec2$1 = /*@__PURE__*/ Fn( ( [ p_immutable, octaves_immutable, lacunarity_immutable, diminish_immutable ] ) => {
- const diminish = float( diminish_immutable ).toVar();
- const lacunarity = float( lacunarity_immutable ).toVar();
- const octaves = int( octaves_immutable ).toVar();
- const p = vec3( p_immutable ).toVar();
- return vec2( mx_fractal_noise_float$1( p, octaves, lacunarity, diminish ), mx_fractal_noise_float$1( p.add( vec3( int( 19 ), int( 193 ), int( 17 ) ) ), octaves, lacunarity, diminish ) );
- } ).setLayout( {
- name: 'mx_fractal_noise_vec2',
- type: 'vec2',
- inputs: [
- { name: 'p', type: 'vec3' },
- { name: 'octaves', type: 'int' },
- { name: 'lacunarity', type: 'float' },
- { name: 'diminish', type: 'float' }
- ]
- } );
- const mx_fractal_noise_vec4$1 = /*@__PURE__*/ Fn( ( [ p_immutable, octaves_immutable, lacunarity_immutable, diminish_immutable ] ) => {
- const diminish = float( diminish_immutable ).toVar();
- const lacunarity = float( lacunarity_immutable ).toVar();
- const octaves = int( octaves_immutable ).toVar();
- const p = vec3( p_immutable ).toVar();
- const c = vec3( mx_fractal_noise_vec3$1( p, octaves, lacunarity, diminish ) ).toVar();
- const f = float( mx_fractal_noise_float$1( p.add( vec3( int( 19 ), int( 193 ), int( 17 ) ) ), octaves, lacunarity, diminish ) ).toVar();
- return vec4( c, f );
- } ).setLayout( {
- name: 'mx_fractal_noise_vec4',
- type: 'vec4',
- inputs: [
- { name: 'p', type: 'vec3' },
- { name: 'octaves', type: 'int' },
- { name: 'lacunarity', type: 'float' },
- { name: 'diminish', type: 'float' }
- ]
- } );
- const mx_worley_distance_0 = /*@__PURE__*/ Fn( ( [ p_immutable, x_immutable, y_immutable, xoff_immutable, yoff_immutable, jitter_immutable, metric_immutable ] ) => {
- const metric = int( metric_immutable ).toVar();
- const jitter = float( jitter_immutable ).toVar();
- const yoff = int( yoff_immutable ).toVar();
- const xoff = int( xoff_immutable ).toVar();
- const y = int( y_immutable ).toVar();
- const x = int( x_immutable ).toVar();
- const p = vec2( p_immutable ).toVar();
- const tmp = vec3( mx_cell_noise_vec3( vec2( x.add( xoff ), y.add( yoff ) ) ) ).toVar();
- const off = vec2( tmp.x, tmp.y ).toVar();
- off.subAssign( 0.5 );
- off.mulAssign( jitter );
- off.addAssign( 0.5 );
- const cellpos = vec2( vec2( float( x ), float( y ) ).add( off ) ).toVar();
- const diff = vec2( cellpos.sub( p ) ).toVar();
- If( metric.equal( int( 2 ) ), () => {
- return abs( diff.x ).add( abs( diff.y ) );
- } );
- If( metric.equal( int( 3 ) ), () => {
- return max$1( abs( diff.x ), abs( diff.y ) );
- } );
- return dot( diff, diff );
- } ).setLayout( {
- name: 'mx_worley_distance_0',
- type: 'float',
- inputs: [
- { name: 'p', type: 'vec2' },
- { name: 'x', type: 'int' },
- { name: 'y', type: 'int' },
- { name: 'xoff', type: 'int' },
- { name: 'yoff', type: 'int' },
- { name: 'jitter', type: 'float' },
- { name: 'metric', type: 'int' }
- ]
- } );
- const mx_worley_distance_1 = /*@__PURE__*/ Fn( ( [ p_immutable, x_immutable, y_immutable, z_immutable, xoff_immutable, yoff_immutable, zoff_immutable, jitter_immutable, metric_immutable ] ) => {
- const metric = int( metric_immutable ).toVar();
- const jitter = float( jitter_immutable ).toVar();
- const zoff = int( zoff_immutable ).toVar();
- const yoff = int( yoff_immutable ).toVar();
- const xoff = int( xoff_immutable ).toVar();
- const z = int( z_immutable ).toVar();
- const y = int( y_immutable ).toVar();
- const x = int( x_immutable ).toVar();
- const p = vec3( p_immutable ).toVar();
- const off = vec3( mx_cell_noise_vec3( vec3( x.add( xoff ), y.add( yoff ), z.add( zoff ) ) ) ).toVar();
- off.subAssign( 0.5 );
- off.mulAssign( jitter );
- off.addAssign( 0.5 );
- const cellpos = vec3( vec3( float( x ), float( y ), float( z ) ).add( off ) ).toVar();
- const diff = vec3( cellpos.sub( p ) ).toVar();
- If( metric.equal( int( 2 ) ), () => {
- return abs( diff.x ).add( abs( diff.y ) ).add( abs( diff.z ) );
- } );
- If( metric.equal( int( 3 ) ), () => {
- return max$1( abs( diff.x ), abs( diff.y ), abs( diff.z ) );
- } );
- return dot( diff, diff );
- } ).setLayout( {
- name: 'mx_worley_distance_1',
- type: 'float',
- inputs: [
- { name: 'p', type: 'vec3' },
- { name: 'x', type: 'int' },
- { name: 'y', type: 'int' },
- { name: 'z', type: 'int' },
- { name: 'xoff', type: 'int' },
- { name: 'yoff', type: 'int' },
- { name: 'zoff', type: 'int' },
- { name: 'jitter', type: 'float' },
- { name: 'metric', type: 'int' }
- ]
- } );
- const mx_worley_distance = /*@__PURE__*/ overloadingFn( [ mx_worley_distance_0, mx_worley_distance_1 ] );
- const mx_worley_noise_float_0 = /*@__PURE__*/ Fn( ( [ p_immutable, jitter_immutable, metric_immutable ] ) => {
- const metric = int( metric_immutable ).toVar();
- const jitter = float( jitter_immutable ).toVar();
- const p = vec2( p_immutable ).toVar();
- const X = int().toVar(), Y = int().toVar();
- const localpos = vec2( mx_floorfrac( p.x, X ), mx_floorfrac( p.y, Y ) ).toVar();
- const sqdist = float( 1e6 ).toVar();
- Loop( { start: -1, end: int( 1 ), name: 'x', condition: '<=' }, ( { x } ) => {
- Loop( { start: -1, end: int( 1 ), name: 'y', condition: '<=' }, ( { y } ) => {
- const dist = float( mx_worley_distance( localpos, x, y, X, Y, jitter, metric ) ).toVar();
- sqdist.assign( min$1( sqdist, dist ) );
- } );
- } );
- If( metric.equal( int( 0 ) ), () => {
- sqdist.assign( sqrt( sqdist ) );
- } );
- return sqdist;
- } ).setLayout( {
- name: 'mx_worley_noise_float_0',
- type: 'float',
- inputs: [
- { name: 'p', type: 'vec2' },
- { name: 'jitter', type: 'float' },
- { name: 'metric', type: 'int' }
- ]
- } );
- const mx_worley_noise_vec2_0 = /*@__PURE__*/ Fn( ( [ p_immutable, jitter_immutable, metric_immutable ] ) => {
- const metric = int( metric_immutable ).toVar();
- const jitter = float( jitter_immutable ).toVar();
- const p = vec2( p_immutable ).toVar();
- const X = int().toVar(), Y = int().toVar();
- const localpos = vec2( mx_floorfrac( p.x, X ), mx_floorfrac( p.y, Y ) ).toVar();
- const sqdist = vec2( 1e6, 1e6 ).toVar();
- Loop( { start: -1, end: int( 1 ), name: 'x', condition: '<=' }, ( { x } ) => {
- Loop( { start: -1, end: int( 1 ), name: 'y', condition: '<=' }, ( { y } ) => {
- const dist = float( mx_worley_distance( localpos, x, y, X, Y, jitter, metric ) ).toVar();
- If( dist.lessThan( sqdist.x ), () => {
- sqdist.y.assign( sqdist.x );
- sqdist.x.assign( dist );
- } ).ElseIf( dist.lessThan( sqdist.y ), () => {
- sqdist.y.assign( dist );
- } );
- } );
- } );
- If( metric.equal( int( 0 ) ), () => {
- sqdist.assign( sqrt( sqdist ) );
- } );
- return sqdist;
- } ).setLayout( {
- name: 'mx_worley_noise_vec2_0',
- type: 'vec2',
- inputs: [
- { name: 'p', type: 'vec2' },
- { name: 'jitter', type: 'float' },
- { name: 'metric', type: 'int' }
- ]
- } );
- const mx_worley_noise_vec3_0 = /*@__PURE__*/ Fn( ( [ p_immutable, jitter_immutable, metric_immutable ] ) => {
- const metric = int( metric_immutable ).toVar();
- const jitter = float( jitter_immutable ).toVar();
- const p = vec2( p_immutable ).toVar();
- const X = int().toVar(), Y = int().toVar();
- const localpos = vec2( mx_floorfrac( p.x, X ), mx_floorfrac( p.y, Y ) ).toVar();
- const sqdist = vec3( 1e6, 1e6, 1e6 ).toVar();
- Loop( { start: -1, end: int( 1 ), name: 'x', condition: '<=' }, ( { x } ) => {
- Loop( { start: -1, end: int( 1 ), name: 'y', condition: '<=' }, ( { y } ) => {
- const dist = float( mx_worley_distance( localpos, x, y, X, Y, jitter, metric ) ).toVar();
- If( dist.lessThan( sqdist.x ), () => {
- sqdist.z.assign( sqdist.y );
- sqdist.y.assign( sqdist.x );
- sqdist.x.assign( dist );
- } ).ElseIf( dist.lessThan( sqdist.y ), () => {
- sqdist.z.assign( sqdist.y );
- sqdist.y.assign( dist );
- } ).ElseIf( dist.lessThan( sqdist.z ), () => {
- sqdist.z.assign( dist );
- } );
- } );
- } );
- If( metric.equal( int( 0 ) ), () => {
- sqdist.assign( sqrt( sqdist ) );
- } );
- return sqdist;
- } ).setLayout( {
- name: 'mx_worley_noise_vec3_0',
- type: 'vec3',
- inputs: [
- { name: 'p', type: 'vec2' },
- { name: 'jitter', type: 'float' },
- { name: 'metric', type: 'int' }
- ]
- } );
- const mx_worley_noise_float_1 = /*@__PURE__*/ Fn( ( [ p_immutable, jitter_immutable, metric_immutable ] ) => {
- const metric = int( metric_immutable ).toVar();
- const jitter = float( jitter_immutable ).toVar();
- const p = vec3( p_immutable ).toVar();
- const X = int().toVar(), Y = int().toVar(), Z = int().toVar();
- const localpos = vec3( mx_floorfrac( p.x, X ), mx_floorfrac( p.y, Y ), mx_floorfrac( p.z, Z ) ).toVar();
- const sqdist = float( 1e6 ).toVar();
- Loop( { start: -1, end: int( 1 ), name: 'x', condition: '<=' }, ( { x } ) => {
- Loop( { start: -1, end: int( 1 ), name: 'y', condition: '<=' }, ( { y } ) => {
- Loop( { start: -1, end: int( 1 ), name: 'z', condition: '<=' }, ( { z } ) => {
- const dist = float( mx_worley_distance( localpos, x, y, z, X, Y, Z, jitter, metric ) ).toVar();
- sqdist.assign( min$1( sqdist, dist ) );
- } );
- } );
- } );
- If( metric.equal( int( 0 ) ), () => {
- sqdist.assign( sqrt( sqdist ) );
- } );
- return sqdist;
- } ).setLayout( {
- name: 'mx_worley_noise_float_1',
- type: 'float',
- inputs: [
- { name: 'p', type: 'vec3' },
- { name: 'jitter', type: 'float' },
- { name: 'metric', type: 'int' }
- ]
- } );
- const mx_worley_noise_float$1 = /*@__PURE__*/ overloadingFn( [ mx_worley_noise_float_0, mx_worley_noise_float_1 ] );
- const mx_worley_noise_vec2_1 = /*@__PURE__*/ Fn( ( [ p_immutable, jitter_immutable, metric_immutable ] ) => {
- const metric = int( metric_immutable ).toVar();
- const jitter = float( jitter_immutable ).toVar();
- const p = vec3( p_immutable ).toVar();
- const X = int().toVar(), Y = int().toVar(), Z = int().toVar();
- const localpos = vec3( mx_floorfrac( p.x, X ), mx_floorfrac( p.y, Y ), mx_floorfrac( p.z, Z ) ).toVar();
- const sqdist = vec2( 1e6, 1e6 ).toVar();
- Loop( { start: -1, end: int( 1 ), name: 'x', condition: '<=' }, ( { x } ) => {
- Loop( { start: -1, end: int( 1 ), name: 'y', condition: '<=' }, ( { y } ) => {
- Loop( { start: -1, end: int( 1 ), name: 'z', condition: '<=' }, ( { z } ) => {
- const dist = float( mx_worley_distance( localpos, x, y, z, X, Y, Z, jitter, metric ) ).toVar();
- If( dist.lessThan( sqdist.x ), () => {
- sqdist.y.assign( sqdist.x );
- sqdist.x.assign( dist );
- } ).ElseIf( dist.lessThan( sqdist.y ), () => {
- sqdist.y.assign( dist );
- } );
- } );
- } );
- } );
- If( metric.equal( int( 0 ) ), () => {
- sqdist.assign( sqrt( sqdist ) );
- } );
- return sqdist;
- } ).setLayout( {
- name: 'mx_worley_noise_vec2_1',
- type: 'vec2',
- inputs: [
- { name: 'p', type: 'vec3' },
- { name: 'jitter', type: 'float' },
- { name: 'metric', type: 'int' }
- ]
- } );
- const mx_worley_noise_vec2$1 = /*@__PURE__*/ overloadingFn( [ mx_worley_noise_vec2_0, mx_worley_noise_vec2_1 ] );
- const mx_worley_noise_vec3_1 = /*@__PURE__*/ Fn( ( [ p_immutable, jitter_immutable, metric_immutable ] ) => {
- const metric = int( metric_immutable ).toVar();
- const jitter = float( jitter_immutable ).toVar();
- const p = vec3( p_immutable ).toVar();
- const X = int().toVar(), Y = int().toVar(), Z = int().toVar();
- const localpos = vec3( mx_floorfrac( p.x, X ), mx_floorfrac( p.y, Y ), mx_floorfrac( p.z, Z ) ).toVar();
- const sqdist = vec3( 1e6, 1e6, 1e6 ).toVar();
- Loop( { start: -1, end: int( 1 ), name: 'x', condition: '<=' }, ( { x } ) => {
- Loop( { start: -1, end: int( 1 ), name: 'y', condition: '<=' }, ( { y } ) => {
- Loop( { start: -1, end: int( 1 ), name: 'z', condition: '<=' }, ( { z } ) => {
- const dist = float( mx_worley_distance( localpos, x, y, z, X, Y, Z, jitter, metric ) ).toVar();
- If( dist.lessThan( sqdist.x ), () => {
- sqdist.z.assign( sqdist.y );
- sqdist.y.assign( sqdist.x );
- sqdist.x.assign( dist );
- } ).ElseIf( dist.lessThan( sqdist.y ), () => {
- sqdist.z.assign( sqdist.y );
- sqdist.y.assign( dist );
- } ).ElseIf( dist.lessThan( sqdist.z ), () => {
- sqdist.z.assign( dist );
- } );
- } );
- } );
- } );
- If( metric.equal( int( 0 ) ), () => {
- sqdist.assign( sqrt( sqdist ) );
- } );
- return sqdist;
- } ).setLayout( {
- name: 'mx_worley_noise_vec3_1',
- type: 'vec3',
- inputs: [
- { name: 'p', type: 'vec3' },
- { name: 'jitter', type: 'float' },
- { name: 'metric', type: 'int' }
- ]
- } );
- const mx_worley_noise_vec3$1 = /*@__PURE__*/ overloadingFn( [ mx_worley_noise_vec3_0, mx_worley_noise_vec3_1 ] );
- // Unified Noise 2D
- const mx_unifiednoise2d$1 = /*@__PURE__*/ Fn( ( [
- noiseType_immutable, texcoord_immutable, freq_immutable, offset_immutable,
- jitter_immutable, outmin_immutable, outmax_immutable, clampoutput_immutable,
- octaves_immutable, lacunarity_immutable, diminish_immutable
- ] ) => {
- const noiseType = int( noiseType_immutable ).toVar();
- const texcoord = vec2( texcoord_immutable ).toVar();
- const freq = vec2( freq_immutable ).toVar();
- const offset = vec2( offset_immutable ).toVar();
- const jitter = float( jitter_immutable ).toVar();
- const outmin = float( outmin_immutable ).toVar();
- const outmax = float( outmax_immutable ).toVar();
- const clampoutput = bool( clampoutput_immutable ).toVar();
- const octaves = int( octaves_immutable ).toVar();
- const lacunarity = float( lacunarity_immutable ).toVar();
- const diminish = float( diminish_immutable ).toVar();
- // Compute input position
- const p = texcoord.mul( freq ).add( offset );
- const result = float( 0.0 ).toVar();
- // Perlin
- If( noiseType.equal( int( 0 ) ), () => {
- result.assign( mx_perlin_noise_vec3( p ) );
- } );
- // Cell
- If( noiseType.equal( int( 1 ) ), () => {
- result.assign( mx_cell_noise_vec3( p ) );
- } );
- // Worley (metric=0 = euclidean)
- If( noiseType.equal( int( 2 ) ), () => {
- result.assign( mx_worley_noise_vec3$1( p, jitter, int( 0 ) ) );
- } );
- // Fractal (use vec3(p, 0.0) for 2D input)
- If( noiseType.equal( int( 3 ) ), () => {
- result.assign( mx_fractal_noise_vec3$1( vec3( p, 0.0 ), octaves, lacunarity, diminish ) );
- } );
- // Remap output to [outmin, outmax]
- result.assign( result.mul( outmax.sub( outmin ) ).add( outmin ) );
- // Clamp if requested
- If( clampoutput, () => {
- result.assign( clamp( result, outmin, outmax ) );
- } );
- return result;
- } ).setLayout( {
- name: 'mx_unifiednoise2d',
- type: 'float',
- inputs: [
- { name: 'noiseType', type: 'int' },
- { name: 'texcoord', type: 'vec2' },
- { name: 'freq', type: 'vec2' },
- { name: 'offset', type: 'vec2' },
- { name: 'jitter', type: 'float' },
- { name: 'outmin', type: 'float' },
- { name: 'outmax', type: 'float' },
- { name: 'clampoutput', type: 'bool' },
- { name: 'octaves', type: 'int' },
- { name: 'lacunarity', type: 'float' },
- { name: 'diminish', type: 'float' }
- ]
- } );
- // Unified Noise 3D
- const mx_unifiednoise3d$1 = /*@__PURE__*/ Fn( ( [
- noiseType_immutable, position_immutable, freq_immutable, offset_immutable,
- jitter_immutable, outmin_immutable, outmax_immutable, clampoutput_immutable,
- octaves_immutable, lacunarity_immutable, diminish_immutable
- ] ) => {
- const noiseType = int( noiseType_immutable ).toVar();
- const position = vec3( position_immutable ).toVar();
- const freq = vec3( freq_immutable ).toVar();
- const offset = vec3( offset_immutable ).toVar();
- const jitter = float( jitter_immutable ).toVar();
- const outmin = float( outmin_immutable ).toVar();
- const outmax = float( outmax_immutable ).toVar();
- const clampoutput = bool( clampoutput_immutable ).toVar();
- const octaves = int( octaves_immutable ).toVar();
- const lacunarity = float( lacunarity_immutable ).toVar();
- const diminish = float( diminish_immutable ).toVar();
- // Compute input position
- const p = position.mul( freq ).add( offset );
- const result = float( 0.0 ).toVar();
- // Perlin
- If( noiseType.equal( int( 0 ) ), () => {
- result.assign( mx_perlin_noise_vec3( p ) );
- } );
- // Cell
- If( noiseType.equal( int( 1 ) ), () => {
- result.assign( mx_cell_noise_vec3( p ) );
- } );
- // Worley (metric=0 = euclidean)
- If( noiseType.equal( int( 2 ) ), () => {
- result.assign( mx_worley_noise_vec3$1( p, jitter, int( 0 ) ) );
- } );
- // Fractal
- If( noiseType.equal( int( 3 ) ), () => {
- result.assign( mx_fractal_noise_vec3$1( p, octaves, lacunarity, diminish ) );
- } );
- // Remap output to [outmin, outmax]
- result.assign( result.mul( outmax.sub( outmin ) ).add( outmin ) );
- // Clamp if requested
- If( clampoutput, () => {
- result.assign( clamp( result, outmin, outmax ) );
- } );
- return result;
- } ).setLayout( {
- name: 'mx_unifiednoise3d',
- type: 'float',
- inputs: [
- { name: 'noiseType', type: 'int' },
- { name: 'position', type: 'vec3' },
- { name: 'freq', type: 'vec3' },
- { name: 'offset', type: 'vec3' },
- { name: 'jitter', type: 'float' },
- { name: 'outmin', type: 'float' },
- { name: 'outmax', type: 'float' },
- { name: 'clampoutput', type: 'bool' },
- { name: 'octaves', type: 'int' },
- { name: 'lacunarity', type: 'float' },
- { name: 'diminish', type: 'float' }
- ]
- } );
- // Three.js Transpiler
- // https://github.com/AcademySoftwareFoundation/MaterialX/blob/main/libraries/stdlib/genglsl/lib/mx_hsv.glsl
- const mx_hsvtorgb = /*@__PURE__*/ Fn( ( [ hsv ] ) => {
- const s = hsv.y;
- const v = hsv.z;
- const result = vec3().toVar();
- If( s.lessThan( 0.0001 ), () => {
- result.assign( vec3( v, v, v ) );
- } ).Else( () => {
- let h = hsv.x;
- h = h.sub( floor( h ) ).mul( 6.0 ).toVar(); // TODO: check what .toVar() is needed in node system cache
- const hi = int( trunc( h ) );
- const f = h.sub( float( hi ) );
- const p = v.mul( s.oneMinus() );
- const q = v.mul( s.mul( f ).oneMinus() );
- const t = v.mul( s.mul( f.oneMinus() ).oneMinus() );
- If( hi.equal( int( 0 ) ), () => {
- result.assign( vec3( v, t, p ) );
- } ).ElseIf( hi.equal( int( 1 ) ), () => {
- result.assign( vec3( q, v, p ) );
- } ).ElseIf( hi.equal( int( 2 ) ), () => {
- result.assign( vec3( p, v, t ) );
- } ).ElseIf( hi.equal( int( 3 ) ), () => {
- result.assign( vec3( p, q, v ) );
- } ).ElseIf( hi.equal( int( 4 ) ), () => {
- result.assign( vec3( t, p, v ) );
- } ).Else( () => {
- result.assign( vec3( v, p, q ) );
- } );
- } );
- return result;
- } ).setLayout( {
- name: 'mx_hsvtorgb',
- type: 'vec3',
- inputs: [
- { name: 'hsv', type: 'vec3' }
- ]
- } );
- const mx_rgbtohsv = /*@__PURE__*/ Fn( ( [ c_immutable ] ) => {
- const c = vec3( c_immutable ).toVar();
- const r = float( c.x ).toVar();
- const g = float( c.y ).toVar();
- const b = float( c.z ).toVar();
- const mincomp = float( min$1( r, min$1( g, b ) ) ).toVar();
- const maxcomp = float( max$1( r, max$1( g, b ) ) ).toVar();
- const delta = float( maxcomp.sub( mincomp ) ).toVar();
- const h = float().toVar(), s = float().toVar(), v = float().toVar();
- v.assign( maxcomp );
- If( maxcomp.greaterThan( 0.0 ), () => {
- s.assign( delta.div( maxcomp ) );
- } ).Else( () => {
- s.assign( 0.0 );
- } );
- If( s.lessThanEqual( 0.0 ), () => {
- h.assign( 0.0 );
- } ).Else( () => {
- If( r.greaterThanEqual( maxcomp ), () => {
- h.assign( g.sub( b ).div( delta ) );
- } ).ElseIf( g.greaterThanEqual( maxcomp ), () => {
- h.assign( add( 2.0, b.sub( r ).div( delta ) ) );
- } ).Else( () => {
- h.assign( add( 4.0, r.sub( g ).div( delta ) ) );
- } );
- h.mulAssign( 1.0 / 6.0 );
- If( h.lessThan( 0.0 ), () => {
- h.addAssign( 1.0 );
- } );
- } );
- return vec3( h, s, v );
- } ).setLayout( {
- name: 'mx_rgbtohsv',
- type: 'vec3',
- inputs: [
- { name: 'c', type: 'vec3' }
- ]
- } );
- // Three.js Transpiler
- // https://github.com/AcademySoftwareFoundation/MaterialX/blob/main/libraries/stdlib/genglsl/lib/mx_transform_color.glsl
- const mx_srgb_texture_to_lin_rec709 = /*@__PURE__*/ Fn( ( [ color_immutable ] ) => {
- const color = vec3( color_immutable ).toVar();
- const isAbove = bvec3( greaterThan( color, vec3( 0.04045 ) ) ).toVar();
- const linSeg = vec3( color.div( 12.92 ) ).toVar();
- const powSeg = vec3( pow( max$1( color.add( vec3( 0.055 ) ), vec3( 0.0 ) ).div( 1.055 ), vec3( 2.4 ) ) ).toVar();
- return mix( linSeg, powSeg, isAbove );
- } ).setLayout( {
- name: 'mx_srgb_texture_to_lin_rec709',
- type: 'vec3',
- inputs: [
- { name: 'color', type: 'vec3' }
- ]
- } );
- const mx_aastep = ( threshold, value ) => {
- threshold = float( threshold );
- value = float( value );
- const afwidth = vec2( value.dFdx(), value.dFdy() ).length().mul( 0.70710678118654757 );
- return smoothstep( threshold.sub( afwidth ), threshold.add( afwidth ), value );
- };
- const _ramp = ( a, b, uv, p ) => mix( a, b, uv[ p ].clamp() );
- const mx_ramplr = ( valuel, valuer, texcoord = uv$1() ) => _ramp( valuel, valuer, texcoord, 'x' );
- const mx_ramptb = ( valuet, valueb, texcoord = uv$1() ) => _ramp( valuet, valueb, texcoord, 'y' );
- // Bilinear ramp: interpolate between four corners (tl, tr, bl, br) using texcoord.x and texcoord.y
- const mx_ramp4 = (
- valuetl, valuetr, valuebl, valuebr, texcoord = uv$1()
- ) => {
- const u = texcoord.x.clamp();
- const v = texcoord.y.clamp();
- const top = mix( valuetl, valuetr, u );
- const bottom = mix( valuebl, valuebr, u );
- return mix( top, bottom, v );
- };
- const _split = ( a, b, center, uv, p ) => mix( a, b, mx_aastep( center, uv[ p ] ) );
- const mx_splitlr = ( valuel, valuer, center, texcoord = uv$1() ) => _split( valuel, valuer, center, texcoord, 'x' );
- const mx_splittb = ( valuet, valueb, center, texcoord = uv$1() ) => _split( valuet, valueb, center, texcoord, 'y' );
- const mx_transform_uv = ( uv_scale = 1, uv_offset = 0, uv_geo = uv$1() ) => uv_geo.mul( uv_scale ).add( uv_offset );
- const mx_safepower = ( in1, in2 = 1 ) => {
- in1 = float( in1 );
- return in1.abs().pow( in2 ).mul( in1.sign() );
- };
- const mx_contrast = ( input, amount = 1, pivot = .5 ) => float( input ).sub( pivot ).mul( amount ).add( pivot );
- const mx_noise_float = ( texcoord = uv$1(), amplitude = 1, pivot = 0 ) => mx_perlin_noise_float( texcoord.convert( 'vec2|vec3' ) ).mul( amplitude ).add( pivot );
- //export const mx_noise_vec2 = ( texcoord = uv(), amplitude = 1, pivot = 0 ) => mx_perlin_noise_vec3( texcoord.convert( 'vec2|vec3' ) ).mul( amplitude ).add( pivot );
- const mx_noise_vec3 = ( texcoord = uv$1(), amplitude = 1, pivot = 0 ) => mx_perlin_noise_vec3( texcoord.convert( 'vec2|vec3' ) ).mul( amplitude ).add( pivot );
- const mx_noise_vec4 = ( texcoord = uv$1(), amplitude = 1, pivot = 0 ) => {
- texcoord = texcoord.convert( 'vec2|vec3' ); // overloading type
- const noise_vec4 = vec4( mx_perlin_noise_vec3( texcoord ), mx_perlin_noise_float( texcoord.add( vec2( 19, 73 ) ) ) );
- return noise_vec4.mul( amplitude ).add( pivot );
- };
- const mx_unifiednoise2d = ( noiseType, texcoord = uv$1(), freq = vec2( 1, 1 ), offset = vec2( 0, 0 ), jitter = 1, outmin = 0, outmax = 1, clampoutput = false, octaves = 1, lacunarity = 2, diminish = .5 ) => mx_unifiednoise2d$1( noiseType, texcoord.convert( 'vec2|vec3' ), freq, offset, jitter, outmin, outmax, clampoutput, octaves, lacunarity, diminish );
- const mx_unifiednoise3d = ( noiseType, texcoord = uv$1(), freq = vec2( 1, 1 ), offset = vec2( 0, 0 ), jitter = 1, outmin = 0, outmax = 1, clampoutput = false, octaves = 1, lacunarity = 2, diminish = .5 ) => mx_unifiednoise3d$1( noiseType, texcoord.convert( 'vec2|vec3' ), freq, offset, jitter, outmin, outmax, clampoutput, octaves, lacunarity, diminish );
- const mx_worley_noise_float = ( texcoord = uv$1(), jitter = 1 ) => mx_worley_noise_float$1( texcoord.convert( 'vec2|vec3' ), jitter, int( 1 ) );
- const mx_worley_noise_vec2 = ( texcoord = uv$1(), jitter = 1 ) => mx_worley_noise_vec2$1( texcoord.convert( 'vec2|vec3' ), jitter, int( 1 ) );
- const mx_worley_noise_vec3 = ( texcoord = uv$1(), jitter = 1 ) => mx_worley_noise_vec3$1( texcoord.convert( 'vec2|vec3' ), jitter, int( 1 ) );
- const mx_cell_noise_float = ( texcoord = uv$1() ) => mx_cell_noise_float$1( texcoord.convert( 'vec2|vec3' ) );
- const mx_fractal_noise_float = ( position = uv$1(), octaves = 3, lacunarity = 2, diminish = .5, amplitude = 1 ) => mx_fractal_noise_float$1( position, int( octaves ), lacunarity, diminish ).mul( amplitude );
- const mx_fractal_noise_vec2 = ( position = uv$1(), octaves = 3, lacunarity = 2, diminish = .5, amplitude = 1 ) => mx_fractal_noise_vec2$1( position, int( octaves ), lacunarity, diminish ).mul( amplitude );
- const mx_fractal_noise_vec3 = ( position = uv$1(), octaves = 3, lacunarity = 2, diminish = .5, amplitude = 1 ) => mx_fractal_noise_vec3$1( position, int( octaves ), lacunarity, diminish ).mul( amplitude );
- const mx_fractal_noise_vec4 = ( position = uv$1(), octaves = 3, lacunarity = 2, diminish = .5, amplitude = 1 ) => mx_fractal_noise_vec4$1( position, int( octaves ), lacunarity, diminish ).mul( amplitude );
- // === Moved from MaterialXLoader.js ===
- // Math ops
- const mx_add = ( in1, in2 = float( 0 ) ) => add( in1, in2 );
- const mx_subtract = ( in1, in2 = float( 0 ) ) => sub( in1, in2 );
- const mx_multiply = ( in1, in2 = float( 1 ) ) => mul( in1, in2 );
- const mx_divide = ( in1, in2 = float( 1 ) ) => div( in1, in2 );
- const mx_modulo = ( in1, in2 = float( 1 ) ) => mod( in1, in2 );
- const mx_power = ( in1, in2 = float( 1 ) ) => pow( in1, in2 );
- const mx_atan2 = ( in1 = float( 0 ), in2 = float( 1 ) ) => atan( in1, in2 );
- const mx_timer = () => time;
- const mx_frame = () => frameId;
- const mx_invert = ( in1, amount = float( 1 ) ) => sub( amount, in1 );
- const mx_ifgreater = ( value1, value2, in1, in2 ) => value1.greaterThan( value2 ).mix( in1, in2 );
- const mx_ifgreatereq = ( value1, value2, in1, in2 ) => value1.greaterThanEqual( value2 ).mix( in1, in2 );
- const mx_ifequal = ( value1, value2, in1, in2 ) => value1.equal( value2 ).mix( in1, in2 );
- // Enhanced separate node to support multi-output referencing (outx, outy, outz, outw)
- const mx_separate = ( in1, channelOrOut = null ) => {
- if ( typeof channelOrOut === 'string' ) {
- const map = { x: 0, r: 0, y: 1, g: 1, z: 2, b: 2, w: 3, a: 3 };
- const c = channelOrOut.replace( /^out/, '' ).toLowerCase();
- if ( map[ c ] !== undefined ) return in1.element( map[ c ] );
- }
- if ( typeof channelOrOut === 'number' ) {
- return in1.element( channelOrOut );
- }
- if ( typeof channelOrOut === 'string' && channelOrOut.length === 1 ) {
- const map = { x: 0, r: 0, y: 1, g: 1, z: 2, b: 2, w: 3, a: 3 };
- if ( map[ channelOrOut ] !== undefined ) return in1.element( map[ channelOrOut ] );
- }
- return in1;
- };
- const mx_place2d = (
- texcoord, pivot = vec2( 0.5, 0.5 ), scale = vec2( 1, 1 ), rotate = float( 0 ), offset = vec2( 0, 0 )/*, operationorder = int( 0 )*/
- ) => {
- let uv = texcoord;
- if ( pivot ) uv = uv.sub( pivot );
- if ( scale ) uv = uv.mul( scale );
- if ( rotate ) {
- const rad = rotate.mul( Math.PI / 180.0 );
- const cosR = rad.cos();
- const sinR = rad.sin();
- uv = vec2(
- uv.x.mul( cosR ).sub( uv.y.mul( sinR ) ),
- uv.x.mul( sinR ).add( uv.y.mul( cosR ) )
- );
- }
- if ( pivot ) uv = uv.add( pivot );
- if ( offset ) uv = uv.add( offset );
- return uv;
- };
- const mx_rotate2d = ( input, amount ) => {
- input = vec2( input );
- amount = float( amount );
- const radians = amount.mul( Math.PI / 180.0 );
- return rotate( input, radians );
- };
- const mx_rotate3d = ( input, amount, axis ) => {
- input = vec3( input );
- amount = float( amount );
- axis = vec3( axis );
- const radians = amount.mul( Math.PI / 180.0 );
- const nAxis = axis.normalize();
- const cosA = radians.cos();
- const sinA = radians.sin();
- const oneMinusCosA = float( 1 ).sub( cosA );
- const rot =
- input.mul( cosA )
- .add( nAxis.cross( input ).mul( sinA ) )
- .add( nAxis.mul( nAxis.dot( input ) ).mul( oneMinusCosA ) );
- return rot;
- };
- const mx_heighttonormal = ( input, scale/*, texcoord*/ ) => {
- input = vec3( input );
- scale = float( scale );
- return bumpMap( input, scale );
- };
- /**
- * This computes a parallax corrected normal which is used for box-projected cube mapping (BPCEM).
- *
- * Reference: {@link https://devlog-martinsh.blogspot.com/2011/09/box-projected-cube-environment-mapping.html}
- *
- * ```js
- * const uvNode = getParallaxCorrectNormal( reflectVector, vec3( 200, 100, 100 ), vec3( 0, - 50, 0 ) );
- * material.envNode = pmremTexture( renderTarget.texture, uvNode );
- * ```
- *
- * @tsl
- * @function
- * @param {Node<vec3>} normal - The normal to correct.
- * @param {Node<vec3>} cubeSize - The cube size should reflect the size of the environment (BPCEM is usually applied in closed environments like rooms).
- * @param {Node<vec3>} cubePos - The cube position.
- * @return {Node<vec3>} The parallax corrected normal.
- */
- const getParallaxCorrectNormal = /*@__PURE__*/ Fn( ( [ normal, cubeSize, cubePos ] ) => {
- const nDir = normalize( normal ).toVar();
- const rbmax = sub( float( 0.5 ).mul( cubeSize.sub( cubePos ) ), positionWorld ).div( nDir ).toVar();
- const rbmin = sub( float( -0.5 ).mul( cubeSize.sub( cubePos ) ), positionWorld ).div( nDir ).toVar();
- const rbminmax = vec3().toVar();
- rbminmax.x = nDir.x.greaterThan( float( 0 ) ).select( rbmax.x, rbmin.x );
- rbminmax.y = nDir.y.greaterThan( float( 0 ) ).select( rbmax.y, rbmin.y );
- rbminmax.z = nDir.z.greaterThan( float( 0 ) ).select( rbmax.z, rbmin.z );
- const correction = min$1( rbminmax.x, rbminmax.y, rbminmax.z ).toVar();
- const boxIntersection = positionWorld.add( nDir.mul( correction ) ).toVar();
- return boxIntersection.sub( cubePos );
- } );
- const getShIrradianceAt = /*@__PURE__*/ Fn( ( [ normal, shCoefficients ] ) => {
- // normal is assumed to have unit length
- const x = normal.x, y = normal.y, z = normal.z;
- // band 0
- let result = shCoefficients.element( 0 ).mul( 0.886227 );
- // band 1
- result = result.add( shCoefficients.element( 1 ).mul( 2.0 * 0.511664 ).mul( y ) );
- result = result.add( shCoefficients.element( 2 ).mul( 2.0 * 0.511664 ).mul( z ) );
- result = result.add( shCoefficients.element( 3 ).mul( 2.0 * 0.511664 ).mul( x ) );
- // band 2
- result = result.add( shCoefficients.element( 4 ).mul( 2.0 * 0.429043 ).mul( x ).mul( y ) );
- result = result.add( shCoefficients.element( 5 ).mul( 2.0 * 0.429043 ).mul( y ).mul( z ) );
- result = result.add( shCoefficients.element( 6 ).mul( z.mul( z ).mul( 0.743125 ).sub( 0.247708 ) ) );
- result = result.add( shCoefficients.element( 7 ).mul( 2.0 * 0.429043 ).mul( x ).mul( z ) );
- result = result.add( shCoefficients.element( 8 ).mul( 0.429043 ).mul( mul( x, x ).sub( mul( y, y ) ) ) );
- return result;
- } );
- // constants
- var TSL = /*#__PURE__*/Object.freeze({
- __proto__: null,
- BRDF_GGX: BRDF_GGX,
- BRDF_Lambert: BRDF_Lambert,
- BasicPointShadowFilter: BasicPointShadowFilter,
- BasicShadowFilter: BasicShadowFilter,
- Break: Break,
- Const: Const,
- Continue: Continue,
- DFGLUT: DFGLUT,
- D_GGX: D_GGX,
- Discard: Discard,
- EPSILON: EPSILON,
- F_Schlick: F_Schlick,
- Fn: Fn,
- HALF_PI: HALF_PI,
- INFINITY: INFINITY,
- If: If,
- Loop: Loop,
- NodeAccess: NodeAccess,
- NodeShaderStage: NodeShaderStage,
- NodeType: NodeType,
- NodeUpdateType: NodeUpdateType,
- OnBeforeMaterialUpdate: OnBeforeMaterialUpdate,
- OnBeforeObjectUpdate: OnBeforeObjectUpdate,
- OnMaterialUpdate: OnMaterialUpdate,
- OnObjectUpdate: OnObjectUpdate,
- PCFShadowFilter: PCFShadowFilter,
- PCFSoftShadowFilter: PCFSoftShadowFilter,
- PI: PI,
- PI2: PI2,
- PointShadowFilter: PointShadowFilter,
- Return: Return,
- Schlick_to_F0: Schlick_to_F0,
- ScriptableNodeResources: ScriptableNodeResources,
- ShaderNode: ShaderNode,
- Stack: Stack,
- Switch: Switch,
- TBNViewMatrix: TBNViewMatrix,
- TWO_PI: TWO_PI,
- VSMShadowFilter: VSMShadowFilter,
- V_GGX_SmithCorrelated: V_GGX_SmithCorrelated,
- Var: Var,
- VarIntent: VarIntent,
- abs: abs,
- acesFilmicToneMapping: acesFilmicToneMapping,
- acos: acos,
- add: add,
- addMethodChaining: addMethodChaining,
- addNodeElement: addNodeElement,
- agxToneMapping: agxToneMapping,
- all: all,
- alphaT: alphaT,
- and: and,
- anisotropy: anisotropy,
- anisotropyB: anisotropyB,
- anisotropyT: anisotropyT,
- any: any,
- append: append,
- array: array,
- arrayBuffer: arrayBuffer,
- asin: asin,
- assign: assign,
- atan: atan,
- atan2: atan2,
- atomicAdd: atomicAdd,
- atomicAnd: atomicAnd,
- atomicFunc: atomicFunc,
- atomicLoad: atomicLoad,
- atomicMax: atomicMax,
- atomicMin: atomicMin,
- atomicOr: atomicOr,
- atomicStore: atomicStore,
- atomicSub: atomicSub,
- atomicXor: atomicXor,
- attenuationColor: attenuationColor,
- attenuationDistance: attenuationDistance,
- attribute: attribute,
- attributeArray: attributeArray,
- backgroundBlurriness: backgroundBlurriness,
- backgroundIntensity: backgroundIntensity,
- backgroundRotation: backgroundRotation,
- batch: batch,
- bentNormalView: bentNormalView,
- billboarding: billboarding,
- bitAnd: bitAnd,
- bitNot: bitNot,
- bitOr: bitOr,
- bitXor: bitXor,
- bitangentGeometry: bitangentGeometry,
- bitangentLocal: bitangentLocal,
- bitangentView: bitangentView,
- bitangentWorld: bitangentWorld,
- bitcast: bitcast,
- blendBurn: blendBurn,
- blendColor: blendColor,
- blendDodge: blendDodge,
- blendOverlay: blendOverlay,
- blendScreen: blendScreen,
- blur: blur,
- bool: bool,
- buffer: buffer,
- bufferAttribute: bufferAttribute,
- builtin: builtin,
- builtinAOContext: builtinAOContext,
- builtinShadowContext: builtinShadowContext,
- bumpMap: bumpMap,
- burn: burn,
- bvec2: bvec2,
- bvec3: bvec3,
- bvec4: bvec4,
- bypass: bypass,
- cache: cache,
- call: call,
- cameraFar: cameraFar,
- cameraIndex: cameraIndex,
- cameraNear: cameraNear,
- cameraNormalMatrix: cameraNormalMatrix,
- cameraPosition: cameraPosition,
- cameraProjectionMatrix: cameraProjectionMatrix,
- cameraProjectionMatrixInverse: cameraProjectionMatrixInverse,
- cameraViewMatrix: cameraViewMatrix,
- cameraViewport: cameraViewport,
- cameraWorldMatrix: cameraWorldMatrix,
- cbrt: cbrt,
- cdl: cdl,
- ceil: ceil,
- checker: checker,
- cineonToneMapping: cineonToneMapping,
- clamp: clamp,
- clearcoat: clearcoat,
- clearcoatNormalView: clearcoatNormalView,
- clearcoatRoughness: clearcoatRoughness,
- code: code,
- color: color,
- colorSpaceToWorking: colorSpaceToWorking,
- colorToDirection: colorToDirection,
- compute: compute,
- computeKernel: computeKernel,
- computeSkinning: computeSkinning,
- context: context,
- convert: convert,
- convertColorSpace: convertColorSpace,
- convertToTexture: convertToTexture,
- cos: cos,
- countLeadingZeros: countLeadingZeros,
- countOneBits: countOneBits,
- countTrailingZeros: countTrailingZeros,
- cross: cross,
- cubeTexture: cubeTexture,
- cubeTextureBase: cubeTextureBase,
- dFdx: dFdx,
- dFdy: dFdy,
- dashSize: dashSize,
- debug: debug,
- decrement: decrement,
- decrementBefore: decrementBefore,
- defaultBuildStages: defaultBuildStages,
- defaultShaderStages: defaultShaderStages,
- defined: defined,
- degrees: degrees,
- deltaTime: deltaTime,
- densityFog: densityFog,
- densityFogFactor: densityFogFactor,
- depth: depth,
- depthPass: depthPass,
- determinant: determinant,
- difference: difference,
- diffuseColor: diffuseColor,
- diffuseContribution: diffuseContribution,
- directPointLight: directPointLight,
- directionToColor: directionToColor,
- directionToFaceDirection: directionToFaceDirection,
- dispersion: dispersion,
- disposeShadowMaterial: disposeShadowMaterial,
- distance: distance,
- div: div,
- dodge: dodge,
- dot: dot,
- drawIndex: drawIndex,
- dynamicBufferAttribute: dynamicBufferAttribute,
- element: element,
- emissive: emissive,
- equal: equal,
- equals: equals,
- equirectUV: equirectUV,
- exp: exp,
- exp2: exp2,
- expression: expression,
- faceDirection: faceDirection,
- faceForward: faceForward,
- faceforward: faceforward,
- float: float,
- floatBitsToInt: floatBitsToInt,
- floatBitsToUint: floatBitsToUint,
- floor: floor,
- fog: fog,
- fract: fract,
- frameGroup: frameGroup,
- frameId: frameId,
- frontFacing: frontFacing,
- fwidth: fwidth,
- gain: gain,
- gapSize: gapSize,
- getConstNodeType: getConstNodeType,
- getCurrentStack: getCurrentStack,
- getDirection: getDirection,
- getDistanceAttenuation: getDistanceAttenuation,
- getGeometryRoughness: getGeometryRoughness,
- getNormalFromDepth: getNormalFromDepth,
- getParallaxCorrectNormal: getParallaxCorrectNormal,
- getRoughness: getRoughness,
- getScreenPosition: getScreenPosition,
- getShIrradianceAt: getShIrradianceAt,
- getShadowMaterial: getShadowMaterial,
- getShadowRenderObjectFunction: getShadowRenderObjectFunction,
- getTextureIndex: getTextureIndex,
- getViewPosition: getViewPosition,
- ggxConvolution: ggxConvolution,
- globalId: globalId,
- glsl: glsl,
- glslFn: glslFn,
- grayscale: grayscale,
- greaterThan: greaterThan,
- greaterThanEqual: greaterThanEqual,
- hash: hash,
- highpModelNormalViewMatrix: highpModelNormalViewMatrix,
- highpModelViewMatrix: highpModelViewMatrix,
- hue: hue,
- increment: increment,
- incrementBefore: incrementBefore,
- inspector: inspector,
- instance: instance,
- instanceIndex: instanceIndex,
- instancedArray: instancedArray,
- instancedBufferAttribute: instancedBufferAttribute,
- instancedDynamicBufferAttribute: instancedDynamicBufferAttribute,
- instancedMesh: instancedMesh,
- int: int,
- intBitsToFloat: intBitsToFloat,
- interleavedGradientNoise: interleavedGradientNoise,
- inverse: inverse,
- inverseSqrt: inverseSqrt,
- inversesqrt: inversesqrt,
- invocationLocalIndex: invocationLocalIndex,
- invocationSubgroupIndex: invocationSubgroupIndex,
- ior: ior,
- iridescence: iridescence,
- iridescenceIOR: iridescenceIOR,
- iridescenceThickness: iridescenceThickness,
- isolate: isolate,
- ivec2: ivec2,
- ivec3: ivec3,
- ivec4: ivec4,
- js: js,
- label: label,
- length: length,
- lengthSq: lengthSq,
- lessThan: lessThan,
- lessThanEqual: lessThanEqual,
- lightPosition: lightPosition,
- lightProjectionUV: lightProjectionUV,
- lightShadowMatrix: lightShadowMatrix,
- lightTargetDirection: lightTargetDirection,
- lightTargetPosition: lightTargetPosition,
- lightViewPosition: lightViewPosition,
- lightingContext: lightingContext,
- lights: lights,
- linearDepth: linearDepth,
- linearToneMapping: linearToneMapping,
- localId: localId,
- log: log,
- log2: log2,
- logarithmicDepthToViewZ: logarithmicDepthToViewZ,
- luminance: luminance,
- mat2: mat2,
- mat3: mat3,
- mat4: mat4,
- matcapUV: matcapUV,
- materialAO: materialAO,
- materialAlphaTest: materialAlphaTest,
- materialAnisotropy: materialAnisotropy,
- materialAnisotropyVector: materialAnisotropyVector,
- materialAttenuationColor: materialAttenuationColor,
- materialAttenuationDistance: materialAttenuationDistance,
- materialClearcoat: materialClearcoat,
- materialClearcoatNormal: materialClearcoatNormal,
- materialClearcoatRoughness: materialClearcoatRoughness,
- materialColor: materialColor,
- materialDispersion: materialDispersion,
- materialEmissive: materialEmissive,
- materialEnvIntensity: materialEnvIntensity,
- materialEnvRotation: materialEnvRotation,
- materialIOR: materialIOR,
- materialIridescence: materialIridescence,
- materialIridescenceIOR: materialIridescenceIOR,
- materialIridescenceThickness: materialIridescenceThickness,
- materialLightMap: materialLightMap,
- materialLineDashOffset: materialLineDashOffset,
- materialLineDashSize: materialLineDashSize,
- materialLineGapSize: materialLineGapSize,
- materialLineScale: materialLineScale,
- materialLineWidth: materialLineWidth,
- materialMetalness: materialMetalness,
- materialNormal: materialNormal,
- materialOpacity: materialOpacity,
- materialPointSize: materialPointSize,
- materialReference: materialReference,
- materialReflectivity: materialReflectivity,
- materialRefractionRatio: materialRefractionRatio,
- materialRotation: materialRotation,
- materialRoughness: materialRoughness,
- materialSheen: materialSheen,
- materialSheenRoughness: materialSheenRoughness,
- materialShininess: materialShininess,
- materialSpecular: materialSpecular,
- materialSpecularColor: materialSpecularColor,
- materialSpecularIntensity: materialSpecularIntensity,
- materialSpecularStrength: materialSpecularStrength,
- materialThickness: materialThickness,
- materialTransmission: materialTransmission,
- max: max$1,
- maxMipLevel: maxMipLevel,
- mediumpModelViewMatrix: mediumpModelViewMatrix,
- metalness: metalness,
- min: min$1,
- mix: mix,
- mixElement: mixElement,
- mod: mod,
- modInt: modInt,
- modelDirection: modelDirection,
- modelNormalMatrix: modelNormalMatrix,
- modelPosition: modelPosition,
- modelRadius: modelRadius,
- modelScale: modelScale,
- modelViewMatrix: modelViewMatrix,
- modelViewPosition: modelViewPosition,
- modelViewProjection: modelViewProjection,
- modelWorldMatrix: modelWorldMatrix,
- modelWorldMatrixInverse: modelWorldMatrixInverse,
- morphReference: morphReference,
- mrt: mrt,
- mul: mul,
- mx_aastep: mx_aastep,
- mx_add: mx_add,
- mx_atan2: mx_atan2,
- mx_cell_noise_float: mx_cell_noise_float,
- mx_contrast: mx_contrast,
- mx_divide: mx_divide,
- mx_fractal_noise_float: mx_fractal_noise_float,
- mx_fractal_noise_vec2: mx_fractal_noise_vec2,
- mx_fractal_noise_vec3: mx_fractal_noise_vec3,
- mx_fractal_noise_vec4: mx_fractal_noise_vec4,
- mx_frame: mx_frame,
- mx_heighttonormal: mx_heighttonormal,
- mx_hsvtorgb: mx_hsvtorgb,
- mx_ifequal: mx_ifequal,
- mx_ifgreater: mx_ifgreater,
- mx_ifgreatereq: mx_ifgreatereq,
- mx_invert: mx_invert,
- mx_modulo: mx_modulo,
- mx_multiply: mx_multiply,
- mx_noise_float: mx_noise_float,
- mx_noise_vec3: mx_noise_vec3,
- mx_noise_vec4: mx_noise_vec4,
- mx_place2d: mx_place2d,
- mx_power: mx_power,
- mx_ramp4: mx_ramp4,
- mx_ramplr: mx_ramplr,
- mx_ramptb: mx_ramptb,
- mx_rgbtohsv: mx_rgbtohsv,
- mx_rotate2d: mx_rotate2d,
- mx_rotate3d: mx_rotate3d,
- mx_safepower: mx_safepower,
- mx_separate: mx_separate,
- mx_splitlr: mx_splitlr,
- mx_splittb: mx_splittb,
- mx_srgb_texture_to_lin_rec709: mx_srgb_texture_to_lin_rec709,
- mx_subtract: mx_subtract,
- mx_timer: mx_timer,
- mx_transform_uv: mx_transform_uv,
- mx_unifiednoise2d: mx_unifiednoise2d,
- mx_unifiednoise3d: mx_unifiednoise3d,
- mx_worley_noise_float: mx_worley_noise_float,
- mx_worley_noise_vec2: mx_worley_noise_vec2,
- mx_worley_noise_vec3: mx_worley_noise_vec3,
- negate: negate,
- neutralToneMapping: neutralToneMapping,
- nodeArray: nodeArray,
- nodeImmutable: nodeImmutable,
- nodeObject: nodeObject,
- nodeObjectIntent: nodeObjectIntent,
- nodeObjects: nodeObjects,
- nodeProxy: nodeProxy,
- nodeProxyIntent: nodeProxyIntent,
- normalFlat: normalFlat,
- normalGeometry: normalGeometry,
- normalLocal: normalLocal,
- normalMap: normalMap,
- normalView: normalView,
- normalViewGeometry: normalViewGeometry,
- normalWorld: normalWorld,
- normalWorldGeometry: normalWorldGeometry,
- normalize: normalize,
- not: not,
- notEqual: notEqual,
- numWorkgroups: numWorkgroups,
- objectDirection: objectDirection,
- objectGroup: objectGroup,
- objectPosition: objectPosition,
- objectRadius: objectRadius,
- objectScale: objectScale,
- objectViewPosition: objectViewPosition,
- objectWorldMatrix: objectWorldMatrix,
- oneMinus: oneMinus,
- or: or,
- orthographicDepthToViewZ: orthographicDepthToViewZ,
- oscSawtooth: oscSawtooth,
- oscSine: oscSine,
- oscSquare: oscSquare,
- oscTriangle: oscTriangle,
- output: output,
- outputStruct: outputStruct,
- overlay: overlay,
- overloadingFn: overloadingFn,
- packHalf2x16: packHalf2x16,
- packSnorm2x16: packSnorm2x16,
- packUnorm2x16: packUnorm2x16,
- parabola: parabola,
- parallaxDirection: parallaxDirection,
- parallaxUV: parallaxUV,
- parameter: parameter,
- pass: pass,
- passTexture: passTexture,
- pcurve: pcurve,
- perspectiveDepthToViewZ: perspectiveDepthToViewZ,
- pmremTexture: pmremTexture,
- pointShadow: pointShadow,
- pointUV: pointUV,
- pointWidth: pointWidth,
- positionGeometry: positionGeometry,
- positionLocal: positionLocal,
- positionPrevious: positionPrevious,
- positionView: positionView,
- positionViewDirection: positionViewDirection,
- positionWorld: positionWorld,
- positionWorldDirection: positionWorldDirection,
- posterize: posterize,
- pow: pow,
- pow2: pow2,
- pow3: pow3,
- pow4: pow4,
- premultiplyAlpha: premultiplyAlpha,
- property: property,
- quadBroadcast: quadBroadcast,
- quadSwapDiagonal: quadSwapDiagonal,
- quadSwapX: quadSwapX,
- quadSwapY: quadSwapY,
- radians: radians,
- rand: rand,
- range: range,
- rangeFog: rangeFog,
- rangeFogFactor: rangeFogFactor,
- reciprocal: reciprocal,
- reference: reference,
- referenceBuffer: referenceBuffer,
- reflect: reflect,
- reflectVector: reflectVector,
- reflectView: reflectView,
- reflector: reflector,
- refract: refract,
- refractVector: refractVector,
- refractView: refractView,
- reinhardToneMapping: reinhardToneMapping,
- remap: remap,
- remapClamp: remapClamp,
- renderGroup: renderGroup,
- renderOutput: renderOutput,
- rendererReference: rendererReference,
- replaceDefaultUV: replaceDefaultUV,
- rotate: rotate,
- rotateUV: rotateUV,
- roughness: roughness,
- round: round,
- rtt: rtt,
- sRGBTransferEOTF: sRGBTransferEOTF,
- sRGBTransferOETF: sRGBTransferOETF,
- sample: sample,
- sampler: sampler,
- samplerComparison: samplerComparison,
- saturate: saturate,
- saturation: saturation,
- screen: screen,
- screenCoordinate: screenCoordinate,
- screenDPR: screenDPR,
- screenSize: screenSize,
- screenUV: screenUV,
- scriptable: scriptable,
- scriptableValue: scriptableValue,
- select: select,
- setCurrentStack: setCurrentStack,
- setName: setName,
- shaderStages: shaderStages,
- shadow: shadow,
- shadowPositionWorld: shadowPositionWorld,
- shapeCircle: shapeCircle,
- sharedUniformGroup: sharedUniformGroup,
- sheen: sheen,
- sheenRoughness: sheenRoughness,
- shiftLeft: shiftLeft,
- shiftRight: shiftRight,
- shininess: shininess,
- sign: sign,
- sin: sin,
- sinc: sinc,
- skinning: skinning,
- smoothstep: smoothstep,
- smoothstepElement: smoothstepElement,
- specularColor: specularColor,
- specularColorBlended: specularColorBlended,
- specularF90: specularF90,
- spherizeUV: spherizeUV,
- split: split,
- spritesheetUV: spritesheetUV,
- sqrt: sqrt,
- stack: stack,
- step: step,
- stepElement: stepElement,
- storage: storage,
- storageBarrier: storageBarrier,
- storageObject: storageObject,
- storageTexture: storageTexture,
- string: string,
- struct: struct,
- sub: sub,
- subBuild: subBuild,
- subgroupAdd: subgroupAdd,
- subgroupAll: subgroupAll,
- subgroupAnd: subgroupAnd,
- subgroupAny: subgroupAny,
- subgroupBallot: subgroupBallot,
- subgroupBroadcast: subgroupBroadcast,
- subgroupBroadcastFirst: subgroupBroadcastFirst,
- subgroupElect: subgroupElect,
- subgroupExclusiveAdd: subgroupExclusiveAdd,
- subgroupExclusiveMul: subgroupExclusiveMul,
- subgroupInclusiveAdd: subgroupInclusiveAdd,
- subgroupInclusiveMul: subgroupInclusiveMul,
- subgroupIndex: subgroupIndex,
- subgroupMax: subgroupMax,
- subgroupMin: subgroupMin,
- subgroupMul: subgroupMul,
- subgroupOr: subgroupOr,
- subgroupShuffle: subgroupShuffle,
- subgroupShuffleDown: subgroupShuffleDown,
- subgroupShuffleUp: subgroupShuffleUp,
- subgroupShuffleXor: subgroupShuffleXor,
- subgroupSize: subgroupSize,
- subgroupXor: subgroupXor,
- tan: tan,
- tangentGeometry: tangentGeometry,
- tangentLocal: tangentLocal,
- tangentView: tangentView,
- tangentWorld: tangentWorld,
- texture: texture,
- texture3D: texture3D,
- texture3DLevel: texture3DLevel,
- texture3DLoad: texture3DLoad,
- textureBarrier: textureBarrier,
- textureBicubic: textureBicubic,
- textureBicubicLevel: textureBicubicLevel,
- textureCubeUV: textureCubeUV,
- textureLevel: textureLevel,
- textureLoad: textureLoad,
- textureSize: textureSize,
- textureStore: textureStore,
- thickness: thickness,
- time: time,
- toneMapping: toneMapping,
- toneMappingExposure: toneMappingExposure,
- toonOutlinePass: toonOutlinePass,
- transformDirection: transformDirection,
- transformNormal: transformNormal,
- transformNormalToView: transformNormalToView,
- transformedClearcoatNormalView: transformedClearcoatNormalView,
- transformedNormalView: transformedNormalView,
- transformedNormalWorld: transformedNormalWorld,
- transmission: transmission,
- transpose: transpose,
- triNoise3D: triNoise3D,
- triplanarTexture: triplanarTexture,
- triplanarTextures: triplanarTextures,
- trunc: trunc,
- uint: uint,
- uintBitsToFloat: uintBitsToFloat,
- uniform: uniform,
- uniformArray: uniformArray,
- uniformCubeTexture: uniformCubeTexture,
- uniformFlow: uniformFlow,
- uniformGroup: uniformGroup,
- uniformTexture: uniformTexture,
- unpackHalf2x16: unpackHalf2x16,
- unpackNormal: unpackNormal,
- unpackSnorm2x16: unpackSnorm2x16,
- unpackUnorm2x16: unpackUnorm2x16,
- unpremultiplyAlpha: unpremultiplyAlpha,
- userData: userData,
- uv: uv$1,
- uvec2: uvec2,
- uvec3: uvec3,
- uvec4: uvec4,
- varying: varying,
- varyingProperty: varyingProperty,
- vec2: vec2,
- vec3: vec3,
- vec4: vec4,
- vectorComponents: vectorComponents,
- velocity: velocity,
- vertexColor: vertexColor,
- vertexIndex: vertexIndex,
- vertexStage: vertexStage,
- vibrance: vibrance,
- viewZToLogarithmicDepth: viewZToLogarithmicDepth,
- viewZToOrthographicDepth: viewZToOrthographicDepth,
- viewZToPerspectiveDepth: viewZToPerspectiveDepth,
- viewport: viewport,
- viewportCoordinate: viewportCoordinate,
- viewportDepthTexture: viewportDepthTexture,
- viewportLinearDepth: viewportLinearDepth,
- viewportMipTexture: viewportMipTexture,
- viewportResolution: viewportResolution,
- viewportSafeUV: viewportSafeUV,
- viewportSharedTexture: viewportSharedTexture,
- viewportSize: viewportSize,
- viewportTexture: viewportTexture,
- viewportUV: viewportUV,
- vogelDiskSample: vogelDiskSample,
- wgsl: wgsl,
- wgslFn: wgslFn,
- workgroupArray: workgroupArray,
- workgroupBarrier: workgroupBarrier,
- workgroupId: workgroupId,
- workingToColorSpace: workingToColorSpace,
- xor: xor
- });
- const _clearColor = /*@__PURE__*/ new Color4();
- /**
- * This renderer module manages the background.
- *
- * @private
- * @augments DataMap
- */
- class Background extends DataMap {
- /**
- * Constructs a new background management component.
- *
- * @param {Renderer} renderer - The renderer.
- * @param {Nodes} nodes - Renderer component for managing nodes related logic.
- */
- constructor( renderer, nodes ) {
- super();
- /**
- * The renderer.
- *
- * @type {Renderer}
- */
- this.renderer = renderer;
- /**
- * Renderer component for managing nodes related logic.
- *
- * @type {Nodes}
- */
- this.nodes = nodes;
- }
- /**
- * Updates the background for the given scene. Depending on how `Scene.background`
- * or `Scene.backgroundNode` are configured, this method might configure a simple clear
- * or add a mesh to the render list for rendering the background as a textured plane
- * or skybox.
- *
- * @param {Scene} scene - The scene.
- * @param {RenderList} renderList - The current render list.
- * @param {RenderContext} renderContext - The current render context.
- */
- update( scene, renderList, renderContext ) {
- const renderer = this.renderer;
- const background = this.nodes.getBackgroundNode( scene ) || scene.background;
- let forceClear = false;
- if ( background === null ) {
- // no background settings, use clear color configuration from the renderer
- renderer._clearColor.getRGB( _clearColor );
- _clearColor.a = renderer._clearColor.a;
- } else if ( background.isColor === true ) {
- // background is an opaque color
- background.getRGB( _clearColor );
- _clearColor.a = 1;
- forceClear = true;
- } else if ( background.isNode === true ) {
- const sceneData = this.get( scene );
- const backgroundNode = background;
- _clearColor.copy( renderer._clearColor );
- let backgroundMesh = sceneData.backgroundMesh;
- if ( backgroundMesh === undefined ) {
- const backgroundMeshNode = vec4( backgroundNode ).mul( backgroundIntensity ).context( {
- // @TODO: Add Texture2D support using node context
- getUV: () => backgroundRotation.mul( normalWorldGeometry ),
- getTextureLevel: () => backgroundBlurriness
- } );
- // when using orthographic cameras, we must scale the skybox sphere
- // up to exceed the dimensions of the camera's viewing box.
- const isOrtho = cameraProjectionMatrix.element( 3 ).element( 3 ).equal( 1.0 );
- // calculate the orthographic scale
- // projectionMatrix[1][1] is (1 / top). Invert it to get the height and multiply by 3.0
- // (an arbitrary safety factor) to ensure the skybox is large enough to cover the corners
- // of the rectangular screen
- const orthoScale = div( 1.0, cameraProjectionMatrix.element( 1 ).element( 1 ) ).mul( 3.0 );
- // compute vertex position
- const modifiedPosition = isOrtho.select( positionLocal.mul( orthoScale ), positionLocal );
- let viewProj = cameraProjectionMatrix.mul( modelViewMatrix.mul( vec4( modifiedPosition, 1.0 ) ) );
- // force background to far plane so it does not occlude objects
- viewProj = viewProj.setZ( viewProj.w );
- const nodeMaterial = new NodeMaterial();
- nodeMaterial.name = 'Background.material';
- nodeMaterial.side = BackSide;
- nodeMaterial.depthTest = false;
- nodeMaterial.depthWrite = false;
- nodeMaterial.allowOverride = false;
- nodeMaterial.fog = false;
- nodeMaterial.lights = false;
- nodeMaterial.vertexNode = viewProj;
- nodeMaterial.colorNode = backgroundMeshNode;
- sceneData.backgroundMeshNode = backgroundMeshNode;
- sceneData.backgroundMesh = backgroundMesh = new Mesh( new SphereGeometry( 1, 32, 32 ), nodeMaterial );
- backgroundMesh.frustumCulled = false;
- backgroundMesh.name = 'Background.mesh';
- backgroundMesh.onBeforeRender = function ( renderer, scene, camera ) {
- this.matrixWorld.copyPosition( camera.matrixWorld );
- };
- function onBackgroundDispose() {
- background.removeEventListener( 'dispose', onBackgroundDispose );
- backgroundMesh.material.dispose();
- backgroundMesh.geometry.dispose();
- }
- background.addEventListener( 'dispose', onBackgroundDispose );
- }
- const backgroundCacheKey = backgroundNode.getCacheKey();
- if ( sceneData.backgroundCacheKey !== backgroundCacheKey ) {
- sceneData.backgroundMeshNode.node = vec4( backgroundNode ).mul( backgroundIntensity );
- sceneData.backgroundMeshNode.needsUpdate = true;
- backgroundMesh.material.needsUpdate = true;
- sceneData.backgroundCacheKey = backgroundCacheKey;
- }
- renderList.unshift( backgroundMesh, backgroundMesh.geometry, backgroundMesh.material, 0, 0, null, null );
- } else {
- error( 'Renderer: Unsupported background configuration.', background );
- }
- //
- const environmentBlendMode = renderer.xr.getEnvironmentBlendMode();
- if ( environmentBlendMode === 'additive' ) {
- _clearColor.set( 0, 0, 0, 1 );
- } else if ( environmentBlendMode === 'alpha-blend' ) {
- _clearColor.set( 0, 0, 0, 0 );
- }
- //
- if ( renderer.autoClear === true || forceClear === true ) {
- const clearColorValue = renderContext.clearColorValue;
- clearColorValue.r = _clearColor.r;
- clearColorValue.g = _clearColor.g;
- clearColorValue.b = _clearColor.b;
- clearColorValue.a = _clearColor.a;
- // premultiply alpha
- if ( renderer.backend.isWebGLBackend === true || renderer.alpha === true ) {
- clearColorValue.r *= clearColorValue.a;
- clearColorValue.g *= clearColorValue.a;
- clearColorValue.b *= clearColorValue.a;
- }
- //
- renderContext.depthClearValue = renderer._clearDepth;
- renderContext.stencilClearValue = renderer._clearStencil;
- renderContext.clearColor = renderer.autoClearColor === true;
- renderContext.clearDepth = renderer.autoClearDepth === true;
- renderContext.clearStencil = renderer.autoClearStencil === true;
- } else {
- renderContext.clearColor = false;
- renderContext.clearDepth = false;
- renderContext.clearStencil = false;
- }
- }
- }
- let _id$7 = 0;
- /**
- * A bind group represents a collection of bindings and thus a collection
- * or resources. Bind groups are assigned to pipelines to provide them
- * with the required resources (like uniform buffers or textures).
- *
- * @private
- */
- class BindGroup {
- /**
- * Constructs a new bind group.
- *
- * @param {string} name - The bind group's name.
- * @param {Array<Binding>} bindings - An array of bindings.
- * @param {number} index - The group index.
- * @param {Array<Binding>} bindingsReference - An array of reference bindings.
- */
- constructor( name = '', bindings = [], index = 0, bindingsReference = [] ) {
- /**
- * The bind group's name.
- *
- * @type {string}
- */
- this.name = name;
- /**
- * An array of bindings.
- *
- * @type {Array<Binding>}
- */
- this.bindings = bindings;
- /**
- * The group index.
- *
- * @type {number}
- */
- this.index = index;
- /**
- * An array of reference bindings.
- *
- * @type {Array<Binding>}
- */
- this.bindingsReference = bindingsReference;
- /**
- * The group's ID.
- *
- * @type {number}
- */
- this.id = _id$7 ++;
- }
- }
- /**
- * This module represents the state of a node builder after it was
- * used to build the nodes for a render object. The state holds the
- * results of the build for further processing in the renderer.
- *
- * Render objects with identical cache keys share the same node builder state.
- *
- * @private
- */
- class NodeBuilderState {
- /**
- * Constructs a new node builder state.
- *
- * @param {string} vertexShader - The native vertex shader code.
- * @param {string} fragmentShader - The native fragment shader code.
- * @param {string} computeShader - The native compute shader code.
- * @param {Array<NodeAttribute>} nodeAttributes - An array of node attributes.
- * @param {Array<BindGroup>} bindings - An array of bind groups.
- * @param {Array<Node>} updateNodes - An array of nodes that implement their `update()` method.
- * @param {Array<Node>} updateBeforeNodes - An array of nodes that implement their `updateBefore()` method.
- * @param {Array<Node>} updateAfterNodes - An array of nodes that implement their `updateAfter()` method.
- * @param {NodeMaterialObserver} observer - A node material observer.
- * @param {Array<Object>} transforms - An array with transform attribute objects. Only relevant when using compute shaders with WebGL 2.
- */
- constructor( vertexShader, fragmentShader, computeShader, nodeAttributes, bindings, updateNodes, updateBeforeNodes, updateAfterNodes, observer, transforms = [] ) {
- /**
- * The native vertex shader code.
- *
- * @type {string}
- */
- this.vertexShader = vertexShader;
- /**
- * The native fragment shader code.
- *
- * @type {string}
- */
- this.fragmentShader = fragmentShader;
- /**
- * The native compute shader code.
- *
- * @type {string}
- */
- this.computeShader = computeShader;
- /**
- * An array with transform attribute objects.
- * Only relevant when using compute shaders with WebGL 2.
- *
- * @type {Array<Object>}
- */
- this.transforms = transforms;
- /**
- * An array of node attributes representing
- * the attributes of the shaders.
- *
- * @type {Array<NodeAttribute>}
- */
- this.nodeAttributes = nodeAttributes;
- /**
- * An array of bind groups representing the uniform or storage
- * buffers, texture or samplers of the shader.
- *
- * @type {Array<BindGroup>}
- */
- this.bindings = bindings;
- /**
- * An array of nodes that implement their `update()` method.
- *
- * @type {Array<Node>}
- */
- this.updateNodes = updateNodes;
- /**
- * An array of nodes that implement their `updateBefore()` method.
- *
- * @type {Array<Node>}
- */
- this.updateBeforeNodes = updateBeforeNodes;
- /**
- * An array of nodes that implement their `updateAfter()` method.
- *
- * @type {Array<Node>}
- */
- this.updateAfterNodes = updateAfterNodes;
- /**
- * A node material observer.
- *
- * @type {NodeMaterialObserver}
- */
- this.observer = observer;
- /**
- * How often this state is used by render objects.
- *
- * @type {number}
- */
- this.usedTimes = 0;
- }
- /**
- * This method is used to create a array of bind groups based
- * on the existing bind groups of this state. Shared groups are
- * not cloned.
- *
- * @return {Array<BindGroup>} A array of bind groups.
- */
- createBindings() {
- const bindings = [];
- for ( const instanceGroup of this.bindings ) {
- const shared = instanceGroup.bindings[ 0 ].groupNode.shared; // All bindings in the group must have the same groupNode.
- if ( shared !== true ) {
- const bindingsGroup = new BindGroup( instanceGroup.name, [], instanceGroup.index, instanceGroup.bindingsReference );
- bindings.push( bindingsGroup );
- for ( const instanceBinding of instanceGroup.bindings ) {
- bindingsGroup.bindings.push( instanceBinding.clone() );
- }
- } else {
- bindings.push( instanceGroup );
- }
- }
- return bindings;
- }
- }
- /**
- * {@link NodeBuilder} is going to create instances of this class during the build process
- * of nodes. They represent the final shader attributes that are going to be generated
- * by the builder. Arrays of node attributes is maintained in {@link NodeBuilder#attributes}
- * and {@link NodeBuilder#bufferAttributes} for this purpose.
- */
- class NodeAttribute {
- /**
- * Constructs a new node attribute.
- *
- * @param {string} name - The name of the attribute.
- * @param {string} type - The type of the attribute.
- * @param {?Node} node - An optional reference to the node.
- */
- constructor( name, type, node = null ) {
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isNodeAttribute = true;
- /**
- * The name of the attribute.
- *
- * @type {string}
- */
- this.name = name;
- /**
- * The type of the attribute.
- *
- * @type {string}
- */
- this.type = type;
- /**
- * An optional reference to the node.
- *
- * @type {?Node}
- * @default null
- */
- this.node = node;
- }
- }
- /**
- * {@link NodeBuilder} is going to create instances of this class during the build process
- * of nodes. They represent the final shader uniforms that are going to be generated
- * by the builder. A dictionary of node uniforms is maintained in {@link NodeBuilder#uniforms}
- * for this purpose.
- */
- class NodeUniform {
- /**
- * Constructs a new node uniform.
- *
- * @param {string} name - The name of the uniform.
- * @param {string} type - The type of the uniform.
- * @param {UniformNode} node - An reference to the node.
- */
- constructor( name, type, node ) {
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isNodeUniform = true;
- /**
- * The name of the uniform.
- *
- * @type {string}
- */
- this.name = name;
- /**
- * The type of the uniform.
- *
- * @type {string}
- */
- this.type = type;
- /**
- * An reference to the node.
- *
- * @type {UniformNode}
- */
- this.node = node;
- }
- /**
- * The value of the uniform node.
- *
- * @type {any}
- */
- get value() {
- return this.node.value;
- }
- set value( val ) {
- this.node.value = val;
- }
- /**
- * The id of the uniform node.
- *
- * @type {number}
- */
- get id() {
- return this.node.id;
- }
- /**
- * The uniform node's group.
- *
- * @type {UniformGroupNode}
- */
- get groupNode() {
- return this.node.groupNode;
- }
- }
- /**
- * {@link NodeBuilder} is going to create instances of this class during the build process
- * of nodes. They represent the final shader variables that are going to be generated
- * by the builder. A dictionary of node variables is maintained in {@link NodeBuilder#vars} for
- * this purpose.
- */
- class NodeVar {
- /**
- * Constructs a new node variable.
- *
- * @param {string} name - The name of the variable.
- * @param {string} type - The type of the variable.
- * @param {boolean} [readOnly=false] - The read-only flag.
- * @param {?number} [count=null] - The size.
- */
- constructor( name, type, readOnly = false, count = null ) {
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isNodeVar = true;
- /**
- * The name of the variable.
- *
- * @type {string}
- */
- this.name = name;
- /**
- * The type of the variable.
- *
- * @type {string}
- */
- this.type = type;
- /**
- * The read-only flag.
- *
- * @type {boolean}
- */
- this.readOnly = readOnly;
- /**
- * The size.
- *
- * @type {?number}
- */
- this.count = count;
- }
- }
- /**
- * {@link NodeBuilder} is going to create instances of this class during the build process
- * of nodes. They represent the final shader varyings that are going to be generated
- * by the builder. An array of node varyings is maintained in {@link NodeBuilder#varyings} for
- * this purpose.
- *
- * @augments NodeVar
- */
- class NodeVarying extends NodeVar {
- /**
- * Constructs a new node varying.
- *
- * @param {string} name - The name of the varying.
- * @param {string} type - The type of the varying.
- * @param {?string} interpolationType - The interpolation type of the varying.
- * @param {?string} interpolationSampling - The interpolation sampling type of the varying.
- */
- constructor( name, type, interpolationType = null, interpolationSampling = null ) {
- super( name, type );
- /**
- * Whether this varying requires interpolation or not. This property can be used
- * to check if the varying can be optimized for a variable.
- *
- * @type {boolean}
- * @default false
- */
- this.needsInterpolation = false;
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isNodeVarying = true;
- /**
- * The interpolation type of the varying data.
- *
- * @type {?string}
- * @default null
- */
- this.interpolationType = interpolationType;
- /**
- * The interpolation sampling type of varying data.
- *
- * @type {?string}
- * @default null
- */
- this.interpolationSampling = interpolationSampling;
- }
- }
- /**
- * {@link NodeBuilder} is going to create instances of this class during the build process
- * of nodes. They represent user-defined, native shader code portions that are going to be
- * injected by the builder. A dictionary of node codes is maintained in {@link NodeBuilder#codes}
- * for this purpose.
- */
- class NodeCode {
- /**
- * Constructs a new code node.
- *
- * @param {string} name - The name of the code.
- * @param {string} type - The node type.
- * @param {string} [code=''] - The native shader code.
- */
- constructor( name, type, code = '' ) {
- /**
- * The name of the code.
- *
- * @type {string}
- */
- this.name = name;
- /**
- * The node type.
- *
- * @type {string}
- */
- this.type = type;
- /**
- * The native shader code.
- *
- * @type {string}
- * @default ''
- */
- this.code = code;
- Object.defineProperty( this, 'isNodeCode', { value: true } );
- }
- }
- let _id$6 = 0;
- /**
- * This utility class is used in {@link NodeBuilder} as an internal
- * cache data structure for node data.
- */
- class NodeCache {
- /**
- * Constructs a new node cache.
- *
- * @param {?NodeCache} parent - A reference to a parent cache.
- */
- constructor( parent = null ) {
- /**
- * The id of the cache.
- *
- * @type {number}
- * @readonly
- */
- this.id = _id$6 ++;
- /**
- * A weak map for managing node data.
- *
- * @type {WeakMap<Node, Object>}
- */
- this.nodesData = new WeakMap();
- /**
- * Reference to a parent node cache.
- *
- * @type {?NodeCache}
- * @default null
- */
- this.parent = parent;
- }
- /**
- * Returns the data for the given node.
- *
- * @param {Node} node - The node.
- * @return {?Object} The data for the node.
- */
- getData( node ) {
- let data = this.nodesData.get( node );
- if ( data === undefined && this.parent !== null ) {
- data = this.parent.getData( node );
- }
- return data;
- }
- /**
- * Sets the data for a given node.
- *
- * @param {Node} node - The node.
- * @param {Object} data - The data that should be cached.
- */
- setData( node, data ) {
- this.nodesData.set( node, data );
- }
- }
- class StructType {
- constructor( name, members ) {
- this.name = name;
- this.members = members;
- this.output = false;
- }
- }
- /**
- * Abstract base class for uniforms.
- *
- * @abstract
- * @private
- */
- class Uniform {
- /**
- * Constructs a new uniform.
- *
- * @param {string} name - The uniform's name.
- * @param {any} value - The uniform's value.
- */
- constructor( name, value ) {
- /**
- * The uniform's name.
- *
- * @type {string}
- */
- this.name = name;
- /**
- * The uniform's value.
- *
- * @type {any}
- */
- this.value = value;
- /**
- * Used to build the uniform buffer according to the STD140 layout.
- * Derived uniforms will set this property to a data type specific
- * value.
- *
- * @type {number}
- */
- this.boundary = 0;
- /**
- * The item size. Derived uniforms will set this property to a data
- * type specific value.
- *
- * @type {number}
- */
- this.itemSize = 0;
- /**
- * This property is set by {@link UniformsGroup} and marks
- * the start position in the uniform buffer.
- *
- * @type {number}
- */
- this.offset = 0;
- }
- /**
- * Sets the uniform's value.
- *
- * @param {any} value - The value to set.
- */
- setValue( value ) {
- this.value = value;
- }
- /**
- * Returns the uniform's value.
- *
- * @return {any} The value.
- */
- getValue() {
- return this.value;
- }
- }
- /**
- * Represents a Number uniform.
- *
- * @private
- * @augments Uniform
- */
- class NumberUniform extends Uniform {
- /**
- * Constructs a new Number uniform.
- *
- * @param {string} name - The uniform's name.
- * @param {number} value - The uniform's value.
- */
- constructor( name, value = 0 ) {
- super( name, value );
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isNumberUniform = true;
- this.boundary = 4;
- this.itemSize = 1;
- }
- }
- /**
- * Represents a Vector2 uniform.
- *
- * @private
- * @augments Uniform
- */
- class Vector2Uniform extends Uniform {
- /**
- * Constructs a new Number uniform.
- *
- * @param {string} name - The uniform's name.
- * @param {Vector2} value - The uniform's value.
- */
- constructor( name, value = new Vector2() ) {
- super( name, value );
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isVector2Uniform = true;
- this.boundary = 8;
- this.itemSize = 2;
- }
- }
- /**
- * Represents a Vector3 uniform.
- *
- * @private
- * @augments Uniform
- */
- class Vector3Uniform extends Uniform {
- /**
- * Constructs a new Number uniform.
- *
- * @param {string} name - The uniform's name.
- * @param {Vector3} value - The uniform's value.
- */
- constructor( name, value = new Vector3() ) {
- super( name, value );
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isVector3Uniform = true;
- this.boundary = 16;
- this.itemSize = 3;
- }
- }
- /**
- * Represents a Vector4 uniform.
- *
- * @private
- * @augments Uniform
- */
- class Vector4Uniform extends Uniform {
- /**
- * Constructs a new Number uniform.
- *
- * @param {string} name - The uniform's name.
- * @param {Vector4} value - The uniform's value.
- */
- constructor( name, value = new Vector4() ) {
- super( name, value );
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isVector4Uniform = true;
- this.boundary = 16;
- this.itemSize = 4;
- }
- }
- /**
- * Represents a Color uniform.
- *
- * @private
- * @augments Uniform
- */
- class ColorUniform extends Uniform {
- /**
- * Constructs a new Number uniform.
- *
- * @param {string} name - The uniform's name.
- * @param {Color} value - The uniform's value.
- */
- constructor( name, value = new Color() ) {
- super( name, value );
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isColorUniform = true;
- this.boundary = 16;
- this.itemSize = 3;
- }
- }
- /**
- * Represents a Matrix2 uniform.
- *
- * @private
- * @augments Uniform
- */
- class Matrix2Uniform extends Uniform {
- /**
- * Constructs a new Number uniform.
- *
- * @param {string} name - The uniform's name.
- * @param {Matrix2} value - The uniform's value.
- */
- constructor( name, value = new Matrix2() ) {
- super( name, value );
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isMatrix2Uniform = true;
- this.boundary = 8;
- this.itemSize = 4;
- }
- }
- /**
- * Represents a Matrix3 uniform.
- *
- * @private
- * @augments Uniform
- */
- class Matrix3Uniform extends Uniform {
- /**
- * Constructs a new Number uniform.
- *
- * @param {string} name - The uniform's name.
- * @param {Matrix3} value - The uniform's value.
- */
- constructor( name, value = new Matrix3() ) {
- super( name, value );
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isMatrix3Uniform = true;
- this.boundary = 48;
- this.itemSize = 12;
- }
- }
- /**
- * Represents a Matrix4 uniform.
- *
- * @private
- * @augments Uniform
- */
- class Matrix4Uniform extends Uniform {
- /**
- * Constructs a new Number uniform.
- *
- * @param {string} name - The uniform's name.
- * @param {Matrix4} value - The uniform's value.
- */
- constructor( name, value = new Matrix4() ) {
- super( name, value );
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isMatrix4Uniform = true;
- this.boundary = 64;
- this.itemSize = 16;
- }
- }
- /**
- * A special form of Number uniform binding type.
- * It's value is managed by a node object.
- *
- * @private
- * @augments NumberUniform
- */
- class NumberNodeUniform extends NumberUniform {
- /**
- * Constructs a new node-based Number uniform.
- *
- * @param {NodeUniform} nodeUniform - The node uniform.
- */
- constructor( nodeUniform ) {
- super( nodeUniform.name, nodeUniform.value );
- /**
- * The node uniform.
- *
- * @type {NodeUniform}
- */
- this.nodeUniform = nodeUniform;
- }
- /**
- * Overwritten to return the value of the node uniform.
- *
- * @return {number} The value.
- */
- getValue() {
- return this.nodeUniform.value;
- }
- /**
- * Returns the node uniform data type.
- *
- * @return {string} The data type.
- */
- getType() {
- return this.nodeUniform.type;
- }
- }
- /**
- * A special form of Vector2 uniform binding type.
- * It's value is managed by a node object.
- *
- * @private
- * @augments Vector2Uniform
- */
- class Vector2NodeUniform extends Vector2Uniform {
- /**
- * Constructs a new node-based Vector2 uniform.
- *
- * @param {NodeUniform} nodeUniform - The node uniform.
- */
- constructor( nodeUniform ) {
- super( nodeUniform.name, nodeUniform.value );
- /**
- * The node uniform.
- *
- * @type {NodeUniform}
- */
- this.nodeUniform = nodeUniform;
- }
- /**
- * Overwritten to return the value of the node uniform.
- *
- * @return {Vector2} The value.
- */
- getValue() {
- return this.nodeUniform.value;
- }
- /**
- * Returns the node uniform data type.
- *
- * @return {string} The data type.
- */
- getType() {
- return this.nodeUniform.type;
- }
- }
- /**
- * A special form of Vector3 uniform binding type.
- * It's value is managed by a node object.
- *
- * @private
- * @augments Vector3Uniform
- */
- class Vector3NodeUniform extends Vector3Uniform {
- /**
- * Constructs a new node-based Vector3 uniform.
- *
- * @param {NodeUniform} nodeUniform - The node uniform.
- */
- constructor( nodeUniform ) {
- super( nodeUniform.name, nodeUniform.value );
- /**
- * The node uniform.
- *
- * @type {NodeUniform}
- */
- this.nodeUniform = nodeUniform;
- }
- /**
- * Overwritten to return the value of the node uniform.
- *
- * @return {Vector3} The value.
- */
- getValue() {
- return this.nodeUniform.value;
- }
- /**
- * Returns the node uniform data type.
- *
- * @return {string} The data type.
- */
- getType() {
- return this.nodeUniform.type;
- }
- }
- /**
- * A special form of Vector4 uniform binding type.
- * It's value is managed by a node object.
- *
- * @private
- * @augments Vector4Uniform
- */
- class Vector4NodeUniform extends Vector4Uniform {
- /**
- * Constructs a new node-based Vector4 uniform.
- *
- * @param {NodeUniform} nodeUniform - The node uniform.
- */
- constructor( nodeUniform ) {
- super( nodeUniform.name, nodeUniform.value );
- /**
- * The node uniform.
- *
- * @type {NodeUniform}
- */
- this.nodeUniform = nodeUniform;
- }
- /**
- * Overwritten to return the value of the node uniform.
- *
- * @return {Vector4} The value.
- */
- getValue() {
- return this.nodeUniform.value;
- }
- /**
- * Returns the node uniform data type.
- *
- * @return {string} The data type.
- */
- getType() {
- return this.nodeUniform.type;
- }
- }
- /**
- * A special form of Color uniform binding type.
- * It's value is managed by a node object.
- *
- * @private
- * @augments ColorUniform
- */
- class ColorNodeUniform extends ColorUniform {
- /**
- * Constructs a new node-based Color uniform.
- *
- * @param {NodeUniform} nodeUniform - The node uniform.
- */
- constructor( nodeUniform ) {
- super( nodeUniform.name, nodeUniform.value );
- /**
- * The node uniform.
- *
- * @type {NodeUniform}
- */
- this.nodeUniform = nodeUniform;
- }
- /**
- * Overwritten to return the value of the node uniform.
- *
- * @return {Color} The value.
- */
- getValue() {
- return this.nodeUniform.value;
- }
- /**
- * Returns the node uniform data type.
- *
- * @return {string} The data type.
- */
- getType() {
- return this.nodeUniform.type;
- }
- }
- /**
- * A special form of Matrix2 uniform binding type.
- * It's value is managed by a node object.
- *
- * @private
- * @augments Matrix2Uniform
- */
- class Matrix2NodeUniform extends Matrix2Uniform {
- /**
- * Constructs a new node-based Matrix2 uniform.
- *
- * @param {NodeUniform} nodeUniform - The node uniform.
- */
- constructor( nodeUniform ) {
- super( nodeUniform.name, nodeUniform.value );
- /**
- * The node uniform.
- *
- * @type {NodeUniform}
- */
- this.nodeUniform = nodeUniform;
- }
- /**
- * Overwritten to return the value of the node uniform.
- *
- * @return {Matrix2} The value.
- */
- getValue() {
- return this.nodeUniform.value;
- }
- /**
- * Returns the node uniform data type.
- *
- * @return {string} The data type.
- */
- getType() {
- return this.nodeUniform.type;
- }
- }
- /**
- * A special form of Matrix3 uniform binding type.
- * It's value is managed by a node object.
- *
- * @private
- * @augments Matrix3Uniform
- */
- class Matrix3NodeUniform extends Matrix3Uniform {
- /**
- * Constructs a new node-based Matrix3 uniform.
- *
- * @param {NodeUniform} nodeUniform - The node uniform.
- */
- constructor( nodeUniform ) {
- super( nodeUniform.name, nodeUniform.value );
- /**
- * The node uniform.
- *
- * @type {NodeUniform}
- */
- this.nodeUniform = nodeUniform;
- }
- /**
- * Overwritten to return the value of the node uniform.
- *
- * @return {Matrix3} The value.
- */
- getValue() {
- return this.nodeUniform.value;
- }
- /**
- * Returns the node uniform data type.
- *
- * @return {string} The data type.
- */
- getType() {
- return this.nodeUniform.type;
- }
- }
- /**
- * A special form of Matrix4 uniform binding type.
- * It's value is managed by a node object.
- *
- * @private
- * @augments Matrix4Uniform
- */
- class Matrix4NodeUniform extends Matrix4Uniform {
- /**
- * Constructs a new node-based Matrix4 uniform.
- *
- * @param {NodeUniform} nodeUniform - The node uniform.
- */
- constructor( nodeUniform ) {
- super( nodeUniform.name, nodeUniform.value );
- /**
- * The node uniform.
- *
- * @type {NodeUniform}
- */
- this.nodeUniform = nodeUniform;
- }
- /**
- * Overwritten to return the value of the node uniform.
- *
- * @return {Matrix4} The value.
- */
- getValue() {
- return this.nodeUniform.value;
- }
- /**
- * Returns the node uniform data type.
- *
- * @return {string} The data type.
- */
- getType() {
- return this.nodeUniform.type;
- }
- }
- let _id$5 = 0;
- const sharedNodeData = new WeakMap();
- const rendererCache = new WeakMap();
- const typeFromArray = new Map( [
- [ Int8Array, 'int' ],
- [ Int16Array, 'int' ],
- [ Int32Array, 'int' ],
- [ Uint8Array, 'uint' ],
- [ Uint16Array, 'uint' ],
- [ Uint32Array, 'uint' ],
- [ Float32Array, 'float' ]
- ] );
- const toFloat = ( value ) => {
- if ( /e/g.test( value ) ) {
- return String( value ).replace( /\+/g, '' );
- } else {
- value = Number( value );
- return value + ( value % 1 ? '' : '.0' );
- }
- };
- /**
- * Base class for builders which generate a shader program based
- * on a 3D object and its node material definition.
- */
- class NodeBuilder {
- /**
- * Constructs a new node builder.
- *
- * @param {Object3D} object - The 3D object.
- * @param {Renderer} renderer - The current renderer.
- * @param {NodeParser} parser - A reference to a node parser.
- */
- constructor( object, renderer, parser ) {
- /**
- * The 3D object.
- *
- * @type {Object3D}
- */
- this.object = object;
- /**
- * The material of the 3D object.
- *
- * @type {?Material}
- */
- this.material = ( object && object.material ) || null;
- /**
- * The geometry of the 3D object.
- *
- * @type {?BufferGeometry}
- */
- this.geometry = ( object && object.geometry ) || null;
- /**
- * The current renderer.
- *
- * @type {Renderer}
- */
- this.renderer = renderer;
- /**
- * A reference to a node parser.
- *
- * @type {NodeParser}
- */
- this.parser = parser;
- /**
- * The scene the 3D object belongs to.
- *
- * @type {?Scene}
- * @default null
- */
- this.scene = null;
- /**
- * The camera the 3D object is rendered with.
- *
- * @type {?Camera}
- * @default null
- */
- this.camera = null;
- /**
- * A list of all nodes the builder is processing
- * for this 3D object.
- *
- * @type {Array<Node>}
- */
- this.nodes = [];
- /**
- * A list of all sequential nodes.
- *
- * @type {Array<Node>}
- */
- this.sequentialNodes = [];
- /**
- * A list of all nodes which {@link Node#update} method should be executed.
- *
- * @type {Array<Node>}
- */
- this.updateNodes = [];
- /**
- * A list of all nodes which {@link Node#updateBefore} method should be executed.
- *
- * @type {Array<Node>}
- */
- this.updateBeforeNodes = [];
- /**
- * A list of all nodes which {@link Node#updateAfter} method should be executed.
- *
- * @type {Array<Node>}
- */
- this.updateAfterNodes = [];
- /**
- * A dictionary that assigns each node to a unique hash.
- *
- * @type {Object<number,Node>}
- */
- this.hashNodes = {};
- /**
- * A reference to a node material observer.
- *
- * @type {?NodeMaterialObserver}
- * @default null
- */
- this.observer = null;
- /**
- * A reference to the current lights node.
- *
- * @type {?LightsNode}
- * @default null
- */
- this.lightsNode = null;
- /**
- * A reference to the current environment node.
- *
- * @type {?Node}
- * @default null
- */
- this.environmentNode = null;
- /**
- * A reference to the current fog node.
- *
- * @type {?Node}
- * @default null
- */
- this.fogNode = null;
- /**
- * The current clipping context.
- *
- * @type {?ClippingContext}
- */
- this.clippingContext = null;
- /**
- * The generated vertex shader.
- *
- * @type {?string}
- */
- this.vertexShader = null;
- /**
- * The generated fragment shader.
- *
- * @type {?string}
- */
- this.fragmentShader = null;
- /**
- * The generated compute shader.
- *
- * @type {?string}
- */
- this.computeShader = null;
- /**
- * Nodes used in the primary flow of code generation.
- *
- * @type {Object<string,Array<Node>>}
- */
- this.flowNodes = { vertex: [], fragment: [], compute: [] };
- /**
- * Nodes code from `.flowNodes`.
- *
- * @type {Object<string,string>}
- */
- this.flowCode = { vertex: '', fragment: '', compute: '' };
- /**
- * This dictionary holds the node uniforms of the builder.
- * The uniforms are maintained in an array for each shader stage.
- *
- * @type {Object}
- */
- this.uniforms = { vertex: [], fragment: [], compute: [], index: 0 };
- /**
- * This dictionary holds the output structs of the builder.
- * The structs are maintained in an array for each shader stage.
- *
- * @type {Object}
- */
- this.structs = { vertex: [], fragment: [], compute: [], index: 0 };
- /**
- * This dictionary holds the types of the builder.
- *
- * @type {Object}
- */
- this.types = { vertex: [], fragment: [], compute: [], index: 0 };
- /**
- * This dictionary holds the bindings for each shader stage.
- *
- * @type {Object}
- */
- this.bindings = { vertex: {}, fragment: {}, compute: {} };
- /**
- * This dictionary maintains the binding indices per bind group.
- *
- * @type {Object}
- */
- this.bindingsIndexes = {};
- /**
- * Reference to the array of bind groups.
- *
- * @type {?Array<BindGroup>}
- */
- this.bindGroups = null;
- /**
- * This array holds the node attributes of this builder
- * created via {@link AttributeNode}.
- *
- * @type {Array<NodeAttribute>}
- */
- this.attributes = [];
- /**
- * This array holds the node attributes of this builder
- * created via {@link BufferAttributeNode}.
- *
- * @type {Array<NodeAttribute>}
- */
- this.bufferAttributes = [];
- /**
- * This array holds the node varyings of this builder.
- *
- * @type {Array<NodeVarying>}
- */
- this.varyings = [];
- /**
- * This dictionary holds the (native) node codes of this builder.
- * The codes are maintained in an array for each shader stage.
- *
- * @type {Object<string,Array<NodeCode>>}
- */
- this.codes = {};
- /**
- * This dictionary holds the node variables of this builder.
- * The variables are maintained in an array for each shader stage.
- * This dictionary is also used to count the number of variables
- * according to their type (const, vars).
- *
- * @type {Object<string,Array<NodeVar>|number>}
- */
- this.vars = {};
- /**
- * This dictionary holds the declarations for each shader stage.
- *
- * @type {Object}
- */
- this.declarations = {};
- /**
- * Current code flow.
- * All code generated in this stack will be stored in `.flow`.
- *
- * @type {{code: string}}
- */
- this.flow = { code: '' };
- /**
- * A chain of nodes.
- * Used to check recursive calls in node-graph.
- *
- * @type {Array<Node>}
- */
- this.chaining = [];
- /**
- * The current stack.
- * This reflects the current process in the code block hierarchy,
- * it is useful to know if the current process is inside a conditional for example.
- *
- * @type {StackNode}
- */
- this.stack = stack();
- /**
- * List of stack nodes.
- * The current stack hierarchy is stored in an array.
- *
- * @type {Array<StackNode>}
- */
- this.stacks = [];
- /**
- * A tab value. Used for shader string generation.
- *
- * @type {string}
- * @default '\t'
- */
- this.tab = '\t';
- /**
- * Reference to the current function node.
- *
- * @type {?FunctionNode}
- * @default null
- */
- this.currentFunctionNode = null;
- /**
- * The builder's context.
- *
- * @type {Object}
- */
- this.context = {
- material: this.material
- };
- /**
- * The builder's cache.
- *
- * @type {NodeCache}
- */
- this.cache = new NodeCache();
- /**
- * Since the {@link NodeBuilder#cache} might be temporarily
- * overwritten by other caches, this member retains the reference
- * to the builder's own cache.
- *
- * @type {NodeCache}
- * @default this.cache
- */
- this.globalCache = this.cache;
- this.flowsData = new WeakMap();
- /**
- * The current shader stage.
- *
- * @type {?('vertex'|'fragment'|'compute'|'any')}
- */
- this.shaderStage = null;
- /**
- * The current build stage.
- *
- * @type {?('setup'|'analyze'|'generate')}
- */
- this.buildStage = null;
- /**
- * The sub-build layers.
- *
- * @type {Array<SubBuildNode>}
- * @default []
- */
- this.subBuildLayers = [];
- /**
- * The active stack nodes.
- *
- * @type {Array<StackNode>}
- */
- this.activeStacks = [];
- /**
- * The current sub-build TSL function(Fn).
- *
- * @type {?string}
- * @default null
- */
- this.subBuildFn = null;
- /**
- * The current TSL function(Fn) call node.
- *
- * @type {?Node}
- * @default null
- */
- this.fnCall = null;
- Object.defineProperty( this, 'id', { value: _id$5 ++ } );
- }
- /**
- * Whether the material is opaque or not.
- *
- * @return {boolean} Whether the material is opaque or not.
- */
- isOpaque() {
- const material = this.material;
- return material.transparent === false && material.blending === NormalBlending && material.alphaToCoverage === false;
- }
- /**
- * Returns the bind groups of the current renderer.
- *
- * @return {ChainMap} The cache.
- */
- getBindGroupsCache() {
- let bindGroupsCache = rendererCache.get( this.renderer );
- if ( bindGroupsCache === undefined ) {
- bindGroupsCache = new ChainMap();
- rendererCache.set( this.renderer, bindGroupsCache );
- }
- return bindGroupsCache;
- }
- /**
- * Factory method for creating an instance of {@link RenderTarget} with the given
- * dimensions and options.
- *
- * @param {number} width - The width of the render target.
- * @param {number} height - The height of the render target.
- * @param {Object} options - The options of the render target.
- * @return {RenderTarget} The render target.
- */
- createRenderTarget( width, height, options ) {
- return new RenderTarget( width, height, options );
- }
- /**
- * Factory method for creating an instance of {@link CubeRenderTarget} with the given
- * dimensions and options.
- *
- * @param {number} size - The size of the cube render target.
- * @param {Object} options - The options of the cube render target.
- * @return {CubeRenderTarget} The cube render target.
- */
- createCubeRenderTarget( size, options ) {
- return new CubeRenderTarget( size, options );
- }
- /**
- * Whether the given node is included in the internal array of nodes or not.
- *
- * @param {Node} node - The node to test.
- * @return {boolean} Whether the given node is included in the internal array of nodes or not.
- */
- includes( node ) {
- return this.nodes.includes( node );
- }
- /**
- * Returns the output struct name which is required by
- * {@link OutputStructNode}.
- *
- * @abstract
- * @return {string} The name of the output struct.
- */
- getOutputStructName() {}
- /**
- * Returns a bind group for the given group name and binding.
- *
- * @private
- * @param {string} groupName - The group name.
- * @param {Array<NodeUniformsGroup>} bindings - List of bindings.
- * @return {BindGroup} The bind group
- */
- _getBindGroup( groupName, bindings ) {
- const bindGroupsCache = this.getBindGroupsCache();
- //
- const bindingsArray = [];
- let sharedGroup = true;
- for ( const binding of bindings ) {
- bindingsArray.push( binding );
- sharedGroup = sharedGroup && binding.groupNode.shared !== true;
- }
- //
- let bindGroup;
- if ( sharedGroup ) {
- bindGroup = bindGroupsCache.get( bindingsArray );
- if ( bindGroup === undefined ) {
- bindGroup = new BindGroup( groupName, bindingsArray, this.bindingsIndexes[ groupName ].group, bindingsArray );
- bindGroupsCache.set( bindingsArray, bindGroup );
- }
- } else {
- bindGroup = new BindGroup( groupName, bindingsArray, this.bindingsIndexes[ groupName ].group, bindingsArray );
- }
- return bindGroup;
- }
- /**
- * Returns an array of node uniform groups for the given group name and shader stage.
- *
- * @param {string} groupName - The group name.
- * @param {('vertex'|'fragment'|'compute'|'any')} shaderStage - The shader stage.
- * @return {Array<NodeUniformsGroup>} The array of node uniform groups.
- */
- getBindGroupArray( groupName, shaderStage ) {
- const bindings = this.bindings[ shaderStage ];
- let bindGroup = bindings[ groupName ];
- if ( bindGroup === undefined ) {
- if ( this.bindingsIndexes[ groupName ] === undefined ) {
- this.bindingsIndexes[ groupName ] = { binding: 0, group: Object.keys( this.bindingsIndexes ).length };
- }
- bindings[ groupName ] = bindGroup = [];
- }
- return bindGroup;
- }
- /**
- * Returns a list bindings of all shader stages separated by groups.
- *
- * @return {Array<BindGroup>} The list of bindings.
- */
- getBindings() {
- let bindingsGroups = this.bindGroups;
- if ( bindingsGroups === null ) {
- const groups = {};
- const bindings = this.bindings;
- for ( const shaderStage of shaderStages ) {
- for ( const groupName in bindings[ shaderStage ] ) {
- const uniforms = bindings[ shaderStage ][ groupName ];
- const groupUniforms = groups[ groupName ] || ( groups[ groupName ] = [] );
- groupUniforms.push( ...uniforms );
- }
- }
- bindingsGroups = [];
- for ( const groupName in groups ) {
- const group = groups[ groupName ];
- const bindingsGroup = this._getBindGroup( groupName, group );
- bindingsGroups.push( bindingsGroup );
- }
- this.bindGroups = bindingsGroups;
- }
- return bindingsGroups;
- }
- /**
- * Sorts the bind groups and updates {@link NodeBuilder#bindingsIndexes}.
- */
- sortBindingGroups() {
- const bindingsGroups = this.getBindings();
- bindingsGroups.sort( ( a, b ) => ( a.bindings[ 0 ].groupNode.order - b.bindings[ 0 ].groupNode.order ) );
- for ( let i = 0; i < bindingsGroups.length; i ++ ) {
- const bindingGroup = bindingsGroups[ i ];
- this.bindingsIndexes[ bindingGroup.name ].group = i;
- bindingGroup.index = i;
- }
- }
- /**
- * The builder maintains each node in a hash-based dictionary.
- * This method sets the given node (value) with the given hash (key) into this dictionary.
- *
- * @param {Node} node - The node to add.
- * @param {number} hash - The hash of the node.
- */
- setHashNode( node, hash ) {
- this.hashNodes[ hash ] = node;
- }
- /**
- * Adds a node to this builder.
- *
- * @param {Node} node - The node to add.
- */
- addNode( node ) {
- if ( this.nodes.includes( node ) === false ) {
- this.nodes.push( node );
- this.setHashNode( node, node.getHash( this ) );
- }
- }
- /**
- * It is used to add Nodes that will be used as FRAME and RENDER events,
- * and need to follow a certain sequence in the calls to work correctly.
- * This function should be called after 'setup()' in the 'build()' process to ensure that the child nodes are processed first.
- *
- * @param {Node} node - The node to add.
- */
- addSequentialNode( node ) {
- if ( this.sequentialNodes.includes( node ) === false ) {
- this.sequentialNodes.push( node );
- }
- }
- /**
- * Checks the update types of nodes
- */
- buildUpdateNodes() {
- for ( const node of this.nodes ) {
- const updateType = node.getUpdateType();
- if ( updateType !== NodeUpdateType.NONE ) {
- this.updateNodes.push( node );
- }
- }
- for ( const node of this.sequentialNodes ) {
- const updateBeforeType = node.getUpdateBeforeType();
- const updateAfterType = node.getUpdateAfterType();
- if ( updateBeforeType !== NodeUpdateType.NONE ) {
- this.updateBeforeNodes.push( node );
- }
- if ( updateAfterType !== NodeUpdateType.NONE ) {
- this.updateAfterNodes.push( node );
- }
- }
- }
- /**
- * A reference the current node which is the
- * last node in the chain of nodes.
- *
- * @type {Node}
- */
- get currentNode() {
- return this.chaining[ this.chaining.length - 1 ];
- }
- /**
- * Whether the given texture is filtered or not.
- *
- * @param {Texture} texture - The texture to check.
- * @return {boolean} Whether the given texture is filtered or not.
- */
- isFilteredTexture( texture ) {
- return ( texture.magFilter === LinearFilter || texture.magFilter === LinearMipmapNearestFilter || texture.magFilter === NearestMipmapLinearFilter || texture.magFilter === LinearMipmapLinearFilter ||
- texture.minFilter === LinearFilter || texture.minFilter === LinearMipmapNearestFilter || texture.minFilter === NearestMipmapLinearFilter || texture.minFilter === LinearMipmapLinearFilter );
- }
- /**
- * Adds the given node to the internal node chain.
- * This is used to check recursive calls in node-graph.
- *
- * @param {Node} node - The node to add.
- */
- addChain( node ) {
- /*
- if ( this.chaining.indexOf( node ) !== - 1 ) {
- warn( 'Recursive node: ', node );
- }
- */
- this.chaining.push( node );
- }
- /**
- * Removes the given node from the internal node chain.
- *
- * @param {Node} node - The node to remove.
- */
- removeChain( node ) {
- const lastChain = this.chaining.pop();
- if ( lastChain !== node ) {
- throw new Error( 'NodeBuilder: Invalid node chaining!' );
- }
- }
- /**
- * Returns the native shader method name for a given generic name. E.g.
- * the method name `textureDimensions` matches the WGSL name but must be
- * resolved to `textureSize` in GLSL.
- *
- * @abstract
- * @param {string} method - The method name to resolve.
- * @return {string} The resolved method name.
- */
- getMethod( method ) {
- return method;
- }
- /**
- * Returns the native snippet for a ternary operation. E.g. GLSL would output
- * a ternary op as `cond ? x : y` whereas WGSL would output it as `select(y, x, cond)`
- *
- * @abstract
- * @param {string} condSnippet - The condition determining which expression gets resolved.
- * @param {string} ifSnippet - The expression to resolve to if the condition is true.
- * @param {string} elseSnippet - The expression to resolve to if the condition is false.
- * @return {string} The resolved method name.
- */
- getTernary( /* condSnippet, ifSnippet, elseSnippet*/ ) {
- return null;
- }
- /**
- * Returns a node for the given hash, see {@link NodeBuilder#setHashNode}.
- *
- * @param {number} hash - The hash of the node.
- * @return {Node} The found node.
- */
- getNodeFromHash( hash ) {
- return this.hashNodes[ hash ];
- }
- /**
- * Adds the Node to a target flow so that it can generate code in the 'generate' process.
- *
- * @param {('vertex'|'fragment'|'compute')} shaderStage - The shader stage.
- * @param {Node} node - The node to add.
- * @return {Node} The node.
- */
- addFlow( shaderStage, node ) {
- this.flowNodes[ shaderStage ].push( node );
- return node;
- }
- /**
- * Sets builder's context.
- *
- * @param {Object} context - The context to set.
- */
- setContext( context ) {
- this.context = context;
- }
- /**
- * Returns the builder's current context.
- *
- * @return {Object} The builder's current context.
- */
- getContext() {
- return this.context;
- }
- /**
- * Adds context data to the builder's current context.
- *
- * @param {Object} context - The context to add.
- * @return {Object} The previous context.
- */
- addContext( context ) {
- const previousContext = this.getContext();
- this.setContext( { ...this.context, ...context } );
- return previousContext;
- }
- /**
- * Gets a context used in shader construction that can be shared across different materials.
- * This is necessary since the renderer cache can reuse shaders generated in one material and use them in another.
- *
- * @return {Object} The builder's current context without material.
- */
- getSharedContext() {
- const context = { ...this.context };
- delete context.material;
- delete context.getUV;
- delete context.getOutput;
- delete context.getTextureLevel;
- delete context.getAO;
- delete context.getShadow;
- return context;
- }
- /**
- * Sets builder's cache.
- *
- * @param {NodeCache} cache - The cache to set.
- */
- setCache( cache ) {
- this.cache = cache;
- }
- /**
- * Returns the builder's current cache.
- *
- * @return {NodeCache} The builder's current cache.
- */
- getCache() {
- return this.cache;
- }
- /**
- * Returns a cache for the given node.
- *
- * @param {Node} node - The node.
- * @param {boolean} [parent=true] - Whether this node refers to a shared parent cache or not.
- * @return {NodeCache} The cache.
- */
- getCacheFromNode( node, parent = true ) {
- const data = this.getDataFromNode( node );
- if ( data.cache === undefined ) data.cache = new NodeCache( parent ? this.getCache() : null );
- return data.cache;
- }
- /**
- * Whether the requested feature is available or not.
- *
- * @abstract
- * @param {string} name - The requested feature.
- * @return {boolean} Whether the requested feature is supported or not.
- */
- isAvailable( /*name*/ ) {
- return false;
- }
- /**
- * Returns the vertexIndex input variable as a native shader string.
- *
- * @abstract
- * @return {string} The instanceIndex shader string.
- */
- getVertexIndex() {
- warn( 'Abstract function.' );
- }
- /**
- * Contextually returns either the vertex stage instance index builtin
- * or the linearized index of an compute invocation within a grid of workgroups.
- *
- * @abstract
- * @return {string} The instanceIndex shader string.
- */
- getInstanceIndex() {
- warn( 'Abstract function.' );
- }
- /**
- * Returns the drawIndex input variable as a native shader string.
- * Only relevant for WebGL and its `WEBGL_multi_draw` extension.
- *
- * @abstract
- * @return {?string} The drawIndex shader string.
- */
- getDrawIndex() {
- warn( 'Abstract function.' );
- }
- /**
- * Returns the frontFacing input variable as a native shader string.
- *
- * @abstract
- * @return {string} The frontFacing shader string.
- */
- getFrontFacing() {
- warn( 'Abstract function.' );
- }
- /**
- * Returns the fragCoord input variable as a native shader string.
- *
- * @abstract
- * @return {string} The fragCoord shader string.
- */
- getFragCoord() {
- warn( 'Abstract function.' );
- }
- /**
- * Whether to flip texture data along its vertical axis or not. WebGL needs
- * this method evaluate to `true`, WebGPU to `false`.
- *
- * @abstract
- * @return {boolean} Whether to flip texture data along its vertical axis or not.
- */
- isFlipY() {
- return false;
- }
- /**
- * Calling this method increases the usage count for the given node by one.
- *
- * @param {Node} node - The node to increase the usage count for.
- * @return {number} The updated usage count.
- */
- increaseUsage( node ) {
- const nodeData = this.getDataFromNode( node );
- nodeData.usageCount = nodeData.usageCount === undefined ? 1 : nodeData.usageCount + 1;
- return nodeData.usageCount;
- }
- /**
- * Generates a texture sample shader string for the given texture data.
- *
- * @abstract
- * @param {Texture} texture - The texture.
- * @param {string} textureProperty - The texture property name.
- * @param {string} uvSnippet - Snippet defining the texture coordinates.
- * @return {string} The generated shader string.
- */
- generateTexture( /* texture, textureProperty, uvSnippet */ ) {
- warn( 'Abstract function.' );
- }
- /**
- * Generates a texture LOD shader string for the given texture data.
- *
- * @abstract
- * @param {Texture} texture - The texture.
- * @param {string} textureProperty - The texture property name.
- * @param {string} uvSnippet - Snippet defining the texture coordinates.
- * @param {?string} depthSnippet - Snippet defining the 0-based texture array index to sample.
- * @param {string} levelSnippet - Snippet defining the mip level.
- * @return {string} The generated shader string.
- */
- generateTextureLod( /* texture, textureProperty, uvSnippet, depthSnippet, levelSnippet */ ) {
- warn( 'Abstract function.' );
- }
- /**
- * Generates the array declaration string.
- *
- * @param {string} type - The type.
- * @param {?number} [count] - The count.
- * @return {string} The generated value as a shader string.
- */
- generateArrayDeclaration( type, count ) {
- return this.getType( type ) + '[ ' + count + ' ]';
- }
- /**
- * Generates the array shader string for the given type and value.
- *
- * @param {string} type - The type.
- * @param {?number} [count] - The count.
- * @param {?Array<Node>} [values=null] - The default values.
- * @return {string} The generated value as a shader string.
- */
- generateArray( type, count, values = null ) {
- let snippet = this.generateArrayDeclaration( type, count ) + '( ';
- for ( let i = 0; i < count; i ++ ) {
- const value = values ? values[ i ] : null;
- if ( value !== null ) {
- snippet += value.build( this, type );
- } else {
- snippet += this.generateConst( type );
- }
- if ( i < count - 1 ) snippet += ', ';
- }
- snippet += ' )';
- return snippet;
- }
- /**
- * Generates the struct shader string.
- *
- * @param {string} type - The type.
- * @param {Array<Object>} [membersLayout] - The count.
- * @param {?Array<Node>} [values=null] - The default values.
- * @return {string} The generated value as a shader string.
- */
- generateStruct( type, membersLayout, values = null ) {
- const snippets = [];
- for ( const member of membersLayout ) {
- const { name, type } = member;
- if ( values && values[ name ] && values[ name ].isNode ) {
- snippets.push( values[ name ].build( this, type ) );
- } else {
- snippets.push( this.generateConst( type ) );
- }
- }
- return type + '( ' + snippets.join( ', ' ) + ' )';
- }
- /**
- * Generates the shader string for the given type and value.
- *
- * @param {string} type - The type.
- * @param {?any} [value=null] - The value.
- * @return {string} The generated value as a shader string.
- */
- generateConst( type, value = null ) {
- if ( value === null ) {
- if ( type === 'float' || type === 'int' || type === 'uint' ) value = 0;
- else if ( type === 'bool' ) value = false;
- else if ( type === 'color' ) value = new Color();
- else if ( type === 'vec2' || type === 'uvec2' || type === 'ivec2' ) value = new Vector2();
- else if ( type === 'vec3' || type === 'uvec3' || type === 'ivec3' ) value = new Vector3();
- else if ( type === 'vec4' || type === 'uvec4' || type === 'ivec4' ) value = new Vector4();
- }
- if ( type === 'float' ) return toFloat( value );
- if ( type === 'int' ) return `${ Math.round( value ) }`;
- if ( type === 'uint' ) return value >= 0 ? `${ Math.round( value ) }u` : '0u';
- if ( type === 'bool' ) return value ? 'true' : 'false';
- if ( type === 'color' ) return `${ this.getType( 'vec3' ) }( ${ toFloat( value.r ) }, ${ toFloat( value.g ) }, ${ toFloat( value.b ) } )`;
- const typeLength = this.getTypeLength( type );
- const componentType = this.getComponentType( type );
- const generateConst = value => this.generateConst( componentType, value );
- if ( typeLength === 2 ) {
- return `${ this.getType( type ) }( ${ generateConst( value.x ) }, ${ generateConst( value.y ) } )`;
- } else if ( typeLength === 3 ) {
- return `${ this.getType( type ) }( ${ generateConst( value.x ) }, ${ generateConst( value.y ) }, ${ generateConst( value.z ) } )`;
- } else if ( typeLength === 4 && type !== 'mat2' ) {
- return `${ this.getType( type ) }( ${ generateConst( value.x ) }, ${ generateConst( value.y ) }, ${ generateConst( value.z ) }, ${ generateConst( value.w ) } )`;
- } else if ( typeLength >= 4 && value && ( value.isMatrix2 || value.isMatrix3 || value.isMatrix4 ) ) {
- return `${ this.getType( type ) }( ${ value.elements.map( generateConst ).join( ', ' ) } )`;
- } else if ( typeLength > 4 ) {
- return `${ this.getType( type ) }()`;
- }
- throw new Error( `NodeBuilder: Type '${type}' not found in generate constant attempt.` );
- }
- /**
- * It might be necessary to convert certain data types to different ones
- * so this method can be used to hide the conversion.
- *
- * @param {string} type - The type.
- * @return {string} The updated type.
- */
- getType( type ) {
- if ( type === 'color' ) return 'vec3';
- return type;
- }
- /**
- * Whether the given attribute name is defined in the geometry or not.
- *
- * @param {string} name - The attribute name.
- * @return {boolean} Whether the given attribute name is defined in the geometry.
- */
- hasGeometryAttribute( name ) {
- return this.geometry && this.geometry.getAttribute( name ) !== undefined;
- }
- /**
- * Returns a node attribute for the given name and type.
- *
- * @param {string} name - The attribute's name.
- * @param {string} type - The attribute's type.
- * @return {NodeAttribute} The node attribute.
- */
- getAttribute( name, type ) {
- const attributes = this.attributes;
- // find attribute
- for ( const attribute of attributes ) {
- if ( attribute.name === name ) {
- return attribute;
- }
- }
- // create a new if no exist
- const attribute = new NodeAttribute( name, type );
- this.registerDeclaration( attribute );
- attributes.push( attribute );
- return attribute;
- }
- /**
- * Returns for the given node and shader stage the property name for the shader.
- *
- * @param {Node} node - The node.
- * @param {('vertex'|'fragment'|'compute'|'any')} shaderStage - The shader stage.
- * @return {string} The property name.
- */
- getPropertyName( node/*, shaderStage*/ ) {
- return node.name;
- }
- /**
- * Whether the given type is a vector type or not.
- *
- * @param {string} type - The type to check.
- * @return {boolean} Whether the given type is a vector type or not.
- */
- isVector( type ) {
- return /vec\d/.test( type );
- }
- /**
- * Whether the given type is a matrix type or not.
- *
- * @param {string} type - The type to check.
- * @return {boolean} Whether the given type is a matrix type or not.
- */
- isMatrix( type ) {
- return /mat\d/.test( type );
- }
- /**
- * Whether the given type is a reference type or not.
- *
- * @param {string} type - The type to check.
- * @return {boolean} Whether the given type is a reference type or not.
- */
- isReference( type ) {
- return type === 'void' || type === 'property' || type === 'sampler' || type === 'samplerComparison' || type === 'texture' || type === 'cubeTexture' || type === 'storageTexture' || type === 'depthTexture' || type === 'texture3D';
- }
- /**
- * Checks if the given texture requires a manual conversion to the working color space.
- *
- * @abstract
- * @param {Texture} texture - The texture to check.
- * @return {boolean} Whether the given texture requires a conversion to working color space or not.
- */
- needsToWorkingColorSpace( /*texture*/ ) {
- return false;
- }
- /**
- * Returns the component type of a given texture.
- *
- * @param {Texture} texture - The texture.
- * @return {string} The component type.
- */
- getComponentTypeFromTexture( texture ) {
- const type = texture.type;
- if ( texture.isDataTexture ) {
- if ( type === IntType ) return 'int';
- if ( type === UnsignedIntType ) return 'uint';
- }
- return 'float';
- }
- /**
- * Returns the element type for a given type.
- *
- * @param {string} type - The type.
- * @return {string} The element type.
- */
- getElementType( type ) {
- if ( type === 'mat2' ) return 'vec2';
- if ( type === 'mat3' ) return 'vec3';
- if ( type === 'mat4' ) return 'vec4';
- return this.getComponentType( type );
- }
- /**
- * Returns the component type for a given type.
- *
- * @param {string} type - The type.
- * @return {string} The component type.
- */
- getComponentType( type ) {
- type = this.getVectorType( type );
- if ( type === 'float' || type === 'bool' || type === 'int' || type === 'uint' ) return type;
- const componentType = /(b|i|u|)(vec|mat)([2-4])/.exec( type );
- if ( componentType === null ) return null;
- if ( componentType[ 1 ] === 'b' ) return 'bool';
- if ( componentType[ 1 ] === 'i' ) return 'int';
- if ( componentType[ 1 ] === 'u' ) return 'uint';
- return 'float';
- }
- /**
- * Returns the vector type for a given type.
- *
- * @param {string} type - The type.
- * @return {string} The vector type.
- */
- getVectorType( type ) {
- if ( type === 'color' ) return 'vec3';
- if ( type === 'texture' || type === 'cubeTexture' || type === 'storageTexture' || type === 'texture3D' ) return 'vec4';
- return type;
- }
- /**
- * Returns the data type for the given the length and component type.
- *
- * @param {number} length - The length.
- * @param {string} [componentType='float'] - The component type.
- * @return {string} The type.
- */
- getTypeFromLength( length, componentType = 'float' ) {
- if ( length === 1 ) return componentType;
- let baseType = getTypeFromLength( length );
- const prefix = componentType === 'float' ? '' : componentType[ 0 ];
- // fix edge case for mat2x2 being same size as vec4
- if ( /mat2/.test( componentType ) === true ) {
- baseType = baseType.replace( 'vec', 'mat' );
- }
- return prefix + baseType;
- }
- /**
- * Returns the type for a given typed array.
- *
- * @param {TypedArray} array - The typed array.
- * @return {string} The type.
- */
- getTypeFromArray( array ) {
- return typeFromArray.get( array.constructor );
- }
- /**
- * Returns the type is an integer type.
- *
- * @param {string} type - The type.
- * @return {boolean} Whether the type is an integer type or not.
- */
- isInteger( type ) {
- return /int|uint|(i|u)vec/.test( type );
- }
- /**
- * Returns the type for a given buffer attribute.
- *
- * @param {BufferAttribute} attribute - The buffer attribute.
- * @return {string} The type.
- */
- getTypeFromAttribute( attribute ) {
- let dataAttribute = attribute;
- if ( attribute.isInterleavedBufferAttribute ) dataAttribute = attribute.data;
- const array = dataAttribute.array;
- const itemSize = attribute.itemSize;
- const normalized = attribute.normalized;
- let arrayType;
- if ( ! ( attribute instanceof Float16BufferAttribute ) && normalized !== true ) {
- arrayType = this.getTypeFromArray( array );
- }
- return this.getTypeFromLength( itemSize, arrayType );
- }
- /**
- * Returns the length for the given data type.
- *
- * @param {string} type - The data type.
- * @return {number} The length.
- */
- getTypeLength( type ) {
- const vecType = this.getVectorType( type );
- const vecNum = /vec([2-4])/.exec( vecType );
- if ( vecNum !== null ) return Number( vecNum[ 1 ] );
- if ( vecType === 'float' || vecType === 'bool' || vecType === 'int' || vecType === 'uint' ) return 1;
- if ( /mat2/.test( type ) === true ) return 4;
- if ( /mat3/.test( type ) === true ) return 9;
- if ( /mat4/.test( type ) === true ) return 16;
- return 0;
- }
- /**
- * Returns the vector type for a given matrix type.
- *
- * @param {string} type - The matrix type.
- * @return {string} The vector type.
- */
- getVectorFromMatrix( type ) {
- return type.replace( 'mat', 'vec' );
- }
- /**
- * For a given type this method changes the component type to the
- * given value. E.g. `vec4` should be changed to the new component type
- * `uint` which results in `uvec4`.
- *
- * @param {string} type - The type.
- * @param {string} newComponentType - The new component type.
- * @return {string} The new type.
- */
- changeComponentType( type, newComponentType ) {
- return this.getTypeFromLength( this.getTypeLength( type ), newComponentType );
- }
- /**
- * Returns the integer type pendant for the given type.
- *
- * @param {string} type - The type.
- * @return {string} The integer type.
- */
- getIntegerType( type ) {
- const componentType = this.getComponentType( type );
- if ( componentType === 'int' || componentType === 'uint' ) return type;
- return this.changeComponentType( type, 'int' );
- }
- /**
- * Adds an active stack to the internal stack.
- *
- * @param {StackNode} stack - The stack node to add.
- */
- setActiveStack( stack ) {
- this.activeStacks.push( stack );
- }
- /**
- * Removes the active stack from the internal stack.
- *
- * @param {StackNode} stack - The stack node to remove.
- */
- removeActiveStack( stack ) {
- if ( this.activeStacks[ this.activeStacks.length - 1 ] === stack ) {
- this.activeStacks.pop();
- } else {
- throw new Error( 'NodeBuilder: Invalid active stack removal.' );
- }
- }
- /**
- * Returns the active stack.
- *
- * @return {StackNode} The active stack.
- */
- getActiveStack() {
- return this.activeStacks[ this.activeStacks.length - 1 ];
- }
- /**
- * Returns the base stack.
- *
- * @return {StackNode} The base stack.
- */
- getBaseStack() {
- return this.activeStacks[ 0 ];
- }
- /**
- * Adds a stack node to the internal stack.
- *
- * @return {StackNode} The added stack node.
- */
- addStack() {
- this.stack = stack( this.stack );
- const previousStack = getCurrentStack();
- this.stacks.push( previousStack );
- setCurrentStack( this.stack );
- return this.stack;
- }
- /**
- * Removes the last stack node from the internal stack.
- *
- * @return {StackNode} The removed stack node.
- */
- removeStack() {
- const lastStack = this.stack;
- for ( const node of lastStack.nodes ) {
- const nodeData = this.getDataFromNode( node );
- nodeData.stack = lastStack;
- }
- this.stack = lastStack.parent;
- setCurrentStack( this.stacks.pop() );
- return lastStack;
- }
- /**
- * The builder maintains (cached) data for each node during the building process. This method
- * can be used to get these data for a specific shader stage and cache.
- *
- * @param {Node} node - The node to get the data for.
- * @param {('vertex'|'fragment'|'compute'|'any')} [shaderStage=this.shaderStage] - The shader stage.
- * @param {?NodeCache} cache - An optional cache.
- * @return {Object} The node data.
- */
- getDataFromNode( node, shaderStage = this.shaderStage, cache = null ) {
- cache = cache === null ? ( node.isGlobal( this ) ? this.globalCache : this.cache ) : cache;
- let nodeData = cache.getData( node );
- if ( nodeData === undefined ) {
- nodeData = {};
- cache.setData( node, nodeData );
- }
- if ( nodeData[ shaderStage ] === undefined ) nodeData[ shaderStage ] = {};
- //
- let data = nodeData[ shaderStage ];
- const subBuilds = nodeData.any ? nodeData.any.subBuilds : null;
- const subBuild = this.getClosestSubBuild( subBuilds );
- if ( subBuild ) {
- if ( data.subBuildsCache === undefined ) data.subBuildsCache = {};
- data = data.subBuildsCache[ subBuild ] || ( data.subBuildsCache[ subBuild ] = {} );
- data.subBuilds = subBuilds;
- }
- return data;
- }
- /**
- * Returns the properties for the given node and shader stage.
- *
- * Properties are typically used within a build stage to reference a node's
- * child node or nodes manually assigned to the properties in a separate build stage.
- * A typical usage pattern for defining nodes manually would be assigning dependency nodes
- * to the current node's properties in the setup stage and building those properties in the generate stage.
- *
- * @param {Node} node - The node to get the properties for.
- * @param {('vertex'|'fragment'|'compute'|'any')} [shaderStage='any'] - The shader stage.
- * @return {Object} The node properties.
- */
- getNodeProperties( node, shaderStage = 'any' ) {
- const nodeData = this.getDataFromNode( node, shaderStage );
- return nodeData.properties || ( nodeData.properties = { outputNode: null } );
- }
- /**
- * Returns an instance of {@link NodeAttribute} for the given buffer attribute node.
- *
- * @param {BufferAttributeNode} node - The buffer attribute node.
- * @param {string} type - The node type.
- * @return {NodeAttribute} The node attribute.
- */
- getBufferAttributeFromNode( node, type ) {
- const nodeData = this.getDataFromNode( node, 'vertex' );
- let bufferAttribute = nodeData.bufferAttribute;
- if ( bufferAttribute === undefined ) {
- const index = this.uniforms.index ++;
- bufferAttribute = new NodeAttribute( 'nodeAttribute' + index, type, node );
- this.bufferAttributes.push( bufferAttribute );
- nodeData.bufferAttribute = bufferAttribute;
- }
- return bufferAttribute;
- }
- /**
- * Returns an instance of {@link StructType} for the given struct name and shader stage
- * or null if not found.
- *
- * @param {string} name - The name of the struct.
- * @param {('vertex'|'fragment'|'compute'|'any')} [shaderStage=this.shaderStage] - The shader stage.
- * @return {?StructType} The struct type or null if not found.
- */
- getStructTypeNode( name, shaderStage = this.shaderStage ) {
- return this.types[ shaderStage ][ name ] || null;
- }
- /**
- * Returns an instance of {@link StructType} for the given output struct node.
- *
- * @param {OutputStructNode} node - The output struct node.
- * @param {Array<Object>} membersLayout - The output struct types.
- * @param {?string} [name=null] - The name of the struct.
- * @param {('vertex'|'fragment'|'compute'|'any')} [shaderStage=this.shaderStage] - The shader stage.
- * @return {StructType} The struct type attribute.
- */
- getStructTypeFromNode( node, membersLayout, name = null, shaderStage = this.shaderStage ) {
- const nodeData = this.getDataFromNode( node, shaderStage, this.globalCache );
- let structType = nodeData.structType;
- if ( structType === undefined ) {
- const index = this.structs.index ++;
- if ( name === null ) name = 'StructType' + index;
- structType = new StructType( name, membersLayout );
- this.structs[ shaderStage ].push( structType );
- this.types[ shaderStage ][ name ] = node;
- nodeData.structType = structType;
- }
- return structType;
- }
- /**
- * Returns an instance of {@link StructType} for the given output struct node.
- *
- * @param {OutputStructNode} node - The output struct node.
- * @param {Array<Object>} membersLayout - The output struct types.
- * @return {StructType} The struct type attribute.
- */
- getOutputStructTypeFromNode( node, membersLayout ) {
- const structType = this.getStructTypeFromNode( node, membersLayout, 'OutputType', 'fragment' );
- structType.output = true;
- return structType;
- }
- /**
- * Returns an instance of {@link NodeUniform} for the given uniform node.
- *
- * @param {UniformNode} node - The uniform node.
- * @param {string} type - The uniform type.
- * @param {('vertex'|'fragment'|'compute'|'any')} [shaderStage=this.shaderStage] - The shader stage.
- * @param {?string} name - The name of the uniform.
- * @return {NodeUniform} The node uniform.
- */
- getUniformFromNode( node, type, shaderStage = this.shaderStage, name = null ) {
- const nodeData = this.getDataFromNode( node, shaderStage, this.globalCache );
- let nodeUniform = nodeData.uniform;
- if ( nodeUniform === undefined ) {
- const index = this.uniforms.index ++;
- nodeUniform = new NodeUniform( name || ( 'nodeUniform' + index ), type, node );
- this.uniforms[ shaderStage ].push( nodeUniform );
- this.registerDeclaration( nodeUniform );
- nodeData.uniform = nodeUniform;
- }
- return nodeUniform;
- }
- /**
- * Returns an instance of {@link NodeVar} for the given variable node.
- *
- * @param {VarNode} node - The variable node.
- * @param {?string} name - The variable's name.
- * @param {string} [type=node.getNodeType( this )] - The variable's type.
- * @param {('vertex'|'fragment'|'compute'|'any')} [shaderStage=this.shaderStage] - The shader stage.
- * @param {boolean} [readOnly=false] - Whether the variable is read-only or not.
- *
- * @return {NodeVar} The node variable.
- */
- getVarFromNode( node, name = null, type = node.getNodeType( this ), shaderStage = this.shaderStage, readOnly = false ) {
- const nodeData = this.getDataFromNode( node, shaderStage );
- const subBuildVariable = this.getSubBuildProperty( 'variable', nodeData.subBuilds );
- let nodeVar = nodeData[ subBuildVariable ];
- if ( nodeVar === undefined ) {
- const idNS = readOnly ? '_const' : '_var';
- const vars = this.vars[ shaderStage ] || ( this.vars[ shaderStage ] = [] );
- const id = this.vars[ idNS ] || ( this.vars[ idNS ] = 0 );
- if ( name === null ) {
- name = ( readOnly ? 'nodeConst' : 'nodeVar' ) + id;
- this.vars[ idNS ] ++;
- }
- //
- if ( subBuildVariable !== 'variable' ) {
- name = this.getSubBuildProperty( name, nodeData.subBuilds );
- }
- //
- const count = node.getArrayCount( this );
- nodeVar = new NodeVar( name, type, readOnly, count );
- if ( ! readOnly ) {
- vars.push( nodeVar );
- }
- this.registerDeclaration( nodeVar );
- nodeData[ subBuildVariable ] = nodeVar;
- }
- return nodeVar;
- }
- /**
- * Returns whether a Node or its flow is deterministic, useful for use in `const`.
- *
- * @param {Node} node - The varying node.
- * @return {boolean} Returns true if deterministic.
- */
- isDeterministic( node ) {
- if ( node.isMathNode ) {
- return this.isDeterministic( node.aNode ) &&
- ( node.bNode ? this.isDeterministic( node.bNode ) : true ) &&
- ( node.cNode ? this.isDeterministic( node.cNode ) : true );
- } else if ( node.isOperatorNode ) {
- return this.isDeterministic( node.aNode ) &&
- ( node.bNode ? this.isDeterministic( node.bNode ) : true );
- } else if ( node.isArrayNode ) {
- if ( node.values !== null ) {
- for ( const n of node.values ) {
- if ( ! this.isDeterministic( n ) ) {
- return false;
- }
- }
- }
- return true;
- } else if ( node.isConstNode ) {
- return true;
- }
- return false;
- }
- /**
- * Returns an instance of {@link NodeVarying} for the given varying node.
- *
- * @param {(VaryingNode|PropertyNode)} node - The varying node.
- * @param {?string} name - The varying's name.
- * @param {string} [type=node.getNodeType( this )] - The varying's type.
- * @param {?string} interpolationType - The interpolation type of the varying.
- * @param {?string} interpolationSampling - The interpolation sampling type of the varying.
- * @return {NodeVar} The node varying.
- */
- getVaryingFromNode( node, name = null, type = node.getNodeType( this ), interpolationType = null, interpolationSampling = null ) {
- const nodeData = this.getDataFromNode( node, 'any' );
- const subBuildVarying = this.getSubBuildProperty( 'varying', nodeData.subBuilds );
- let nodeVarying = nodeData[ subBuildVarying ];
- if ( nodeVarying === undefined ) {
- const varyings = this.varyings;
- const index = varyings.length;
- if ( name === null ) name = 'nodeVarying' + index;
- //
- if ( subBuildVarying !== 'varying' ) {
- name = this.getSubBuildProperty( name, nodeData.subBuilds );
- }
- //
- nodeVarying = new NodeVarying( name, type, interpolationType, interpolationSampling );
- varyings.push( nodeVarying );
- this.registerDeclaration( nodeVarying );
- nodeData[ subBuildVarying ] = nodeVarying;
- }
- return nodeVarying;
- }
- /**
- * Registers a node declaration in the current shader stage.
- *
- * @param {Object} node - The node to be registered.
- */
- registerDeclaration( node ) {
- const shaderStage = this.shaderStage;
- const declarations = this.declarations[ shaderStage ] || ( this.declarations[ shaderStage ] = {} );
- const property = this.getPropertyName( node );
- let index = 1;
- let name = property;
- // Automatically renames the property if the name is already in use.
- while ( declarations[ name ] !== undefined ) {
- name = property + '_' + index ++;
- }
- if ( index > 1 ) {
- node.name = name;
- warn( `TSL: Declaration name '${ property }' of '${ node.type }' already in use. Renamed to '${ name }'.` );
- }
- declarations[ name ] = node;
- }
- /**
- * Returns an instance of {@link NodeCode} for the given code node.
- *
- * @param {CodeNode} node - The code node.
- * @param {string} type - The node type.
- * @param {('vertex'|'fragment'|'compute'|'any')} [shaderStage=this.shaderStage] - The shader stage.
- * @return {NodeCode} The node code.
- */
- getCodeFromNode( node, type, shaderStage = this.shaderStage ) {
- const nodeData = this.getDataFromNode( node );
- let nodeCode = nodeData.code;
- if ( nodeCode === undefined ) {
- const codes = this.codes[ shaderStage ] || ( this.codes[ shaderStage ] = [] );
- const index = codes.length;
- nodeCode = new NodeCode( 'nodeCode' + index, type );
- codes.push( nodeCode );
- nodeData.code = nodeCode;
- }
- return nodeCode;
- }
- /**
- * Adds a code flow based on the code-block hierarchy.
- * This is used so that code-blocks like If,Else create their variables locally if the Node
- * is only used inside one of these conditionals in the current shader stage.
- *
- * @param {Node} node - The node to add.
- * @param {Node} nodeBlock - Node-based code-block. Usually 'ConditionalNode'.
- */
- addFlowCodeHierarchy( node, nodeBlock ) {
- const { flowCodes, flowCodeBlock } = this.getDataFromNode( node );
- let needsFlowCode = true;
- let nodeBlockHierarchy = nodeBlock;
- while ( nodeBlockHierarchy ) {
- if ( flowCodeBlock.get( nodeBlockHierarchy ) === true ) {
- needsFlowCode = false;
- break;
- }
- nodeBlockHierarchy = this.getDataFromNode( nodeBlockHierarchy ).parentNodeBlock;
- }
- if ( needsFlowCode ) {
- for ( const flowCode of flowCodes ) {
- this.addLineFlowCode( flowCode );
- }
- }
- }
- /**
- * Add a inline-code to the current flow code-block.
- *
- * @param {Node} node - The node to add.
- * @param {string} code - The code to add.
- * @param {Node} nodeBlock - Current ConditionalNode
- */
- addLineFlowCodeBlock( node, code, nodeBlock ) {
- const nodeData = this.getDataFromNode( node );
- const flowCodes = nodeData.flowCodes || ( nodeData.flowCodes = [] );
- const codeBlock = nodeData.flowCodeBlock || ( nodeData.flowCodeBlock = new WeakMap() );
- flowCodes.push( code );
- codeBlock.set( nodeBlock, true );
- }
- /**
- * Add a inline-code to the current flow.
- *
- * @param {string} code - The code to add.
- * @param {?Node} [node= null] - Optional Node, can help the system understand if the Node is part of a code-block.
- * @return {NodeBuilder} A reference to this node builder.
- */
- addLineFlowCode( code, node = null ) {
- if ( code === '' ) return this;
- if ( node !== null && this.context.nodeBlock ) {
- this.addLineFlowCodeBlock( node, code, this.context.nodeBlock );
- }
- code = this.tab + code;
- if ( ! /;\s*$/.test( code ) ) {
- code = code + ';\n';
- }
- this.flow.code += code;
- return this;
- }
- /**
- * Adds a code to the current code flow.
- *
- * @param {string} code - Shader code.
- * @return {NodeBuilder} A reference to this node builder.
- */
- addFlowCode( code ) {
- this.flow.code += code;
- return this;
- }
- /**
- * Add tab in the code that will be generated so that other snippets respect the current tabulation.
- * Typically used in codes with If,Else.
- *
- * @return {NodeBuilder} A reference to this node builder.
- */
- addFlowTab() {
- this.tab += '\t';
- return this;
- }
- /**
- * Removes a tab.
- *
- * @return {NodeBuilder} A reference to this node builder.
- */
- removeFlowTab() {
- this.tab = this.tab.slice( 0, -1 );
- return this;
- }
- /**
- * Gets the current flow data based on a Node.
- *
- * @param {Node} node - Node that the flow was started.
- * @param {('vertex'|'fragment'|'compute'|'any')} shaderStage - The shader stage.
- * @return {Object} The flow data.
- */
- getFlowData( node/*, shaderStage*/ ) {
- return this.flowsData.get( node );
- }
- /**
- * Executes the node flow based on a root node to generate the final shader code.
- *
- * @param {Node} node - The node to execute.
- * @return {Object} The code flow.
- */
- flowNode( node ) {
- const output = node.getNodeType( this );
- const flowData = this.flowChildNode( node, output );
- this.flowsData.set( node, flowData );
- return flowData;
- }
- /**
- * Includes a node in the current function node.
- *
- * @param {Node} node - The node to include.
- * @returns {void}
- */
- addInclude( node ) {
- if ( this.currentFunctionNode !== null ) {
- this.currentFunctionNode.includes.push( node );
- }
- }
- /**
- * Returns the native shader operator name for a given generic name.
- * It is a similar type of method like {@link NodeBuilder#getMethod}.
- *
- * @param {ShaderNodeInternal} shaderNode - The shader node to build the function node with.
- * @return {FunctionNode} The build function node.
- */
- buildFunctionNode( shaderNode ) {
- const fn = new FunctionNode();
- const previous = this.currentFunctionNode;
- this.currentFunctionNode = fn;
- fn.code = this.buildFunctionCode( shaderNode );
- this.currentFunctionNode = previous;
- return fn;
- }
- /**
- * Generates a code flow based on a TSL function: Fn().
- *
- * @param {ShaderNodeInternal} shaderNode - A function code will be generated based on the input.
- * @return {Object}
- */
- flowShaderNode( shaderNode ) {
- const layout = shaderNode.layout;
- const inputs = {
- [ Symbol.iterator ]() {
- let index = 0;
- const values = Object.values( this );
- return {
- next: () => ( {
- value: values[ index ],
- done: index ++ >= values.length
- } )
- };
- }
- };
- for ( const input of layout.inputs ) {
- inputs[ input.name ] = new ParameterNode( input.type, input.name );
- }
- //
- shaderNode.layout = null;
- const callNode = shaderNode.call( inputs );
- const flowData = this.flowStagesNode( callNode, layout.type );
- shaderNode.layout = layout;
- return flowData;
- }
- /**
- * Executes the node in a specific build stage.
- *
- * This function can be used to arbitrarily execute the specified build stage
- * outside of the standard build process. For instance, if a node's type depends
- * on properties created by the 'setup' stage, then flowBuildStage(node, 'setup')
- * can be used to execute the setup build stage and access its generated nodes
- * before the standard build process begins.
- *
- * @param {Node} node - The node to execute.
- * @param {string} buildStage - The build stage to execute the node in.
- * @param {?(Node|string)} [output=null] - Expected output type. For example 'vec3'.
- * @return {?(Node|string)} The result of the node build.
- */
- flowBuildStage( node, buildStage, output = null ) {
- const previousBuildStage = this.getBuildStage();
- this.setBuildStage( buildStage );
- const result = node.build( this, output );
- this.setBuildStage( previousBuildStage );
- return result;
- }
- /**
- * Runs the node flow through all the steps of creation, 'setup', 'analyze', 'generate'.
- *
- * @param {Node} node - The node to execute.
- * @param {?string} output - Expected output type. For example 'vec3'.
- * @return {Object}
- */
- flowStagesNode( node, output = null ) {
- const previousFlow = this.flow;
- const previousVars = this.vars;
- const previousDeclarations = this.declarations;
- const previousCache = this.cache;
- const previousBuildStage = this.buildStage;
- const previousStack = this.stack;
- const flow = {
- code: ''
- };
- this.flow = flow;
- this.vars = {};
- this.declarations = {};
- this.cache = new NodeCache();
- this.stack = stack();
- for ( const buildStage of defaultBuildStages ) {
- this.setBuildStage( buildStage );
- flow.result = node.build( this, output );
- }
- flow.vars = this.getVars( this.shaderStage );
- this.flow = previousFlow;
- this.vars = previousVars;
- this.declarations = previousDeclarations;
- this.cache = previousCache;
- this.stack = previousStack;
- this.setBuildStage( previousBuildStage );
- return flow;
- }
- /**
- * Returns the native shader operator name for a given generic name.
- * It is a similar type of method like {@link NodeBuilder#getMethod}.
- *
- * @abstract
- * @param {string} op - The operator name to resolve.
- * @return {?string} The resolved operator name.
- */
- getFunctionOperator( /* op */ ) {
- return null;
- }
- /**
- * Builds the given shader node.
- *
- * @abstract
- * @param {ShaderNodeInternal} shaderNode - The shader node.
- * @return {string} The function code.
- */
- buildFunctionCode( /* shaderNode */ ) {
- warn( 'Abstract function.' );
- }
- /**
- * Generates a code flow based on a child Node.
- *
- * @param {Node} node - The node to execute.
- * @param {?string} output - Expected output type. For example 'vec3'.
- * @return {Object} The code flow.
- */
- flowChildNode( node, output = null ) {
- const previousFlow = this.flow;
- const flow = {
- code: ''
- };
- this.flow = flow;
- flow.result = node.build( this, output );
- this.flow = previousFlow;
- return flow;
- }
- /**
- * Executes a flow of code in a different stage.
- *
- * Some nodes like `varying()` have the ability to compute code in vertex-stage and
- * return the value in fragment-stage even if it is being executed in an input fragment.
- *
- * @param {('vertex'|'fragment'|'compute'|'any')} shaderStage - The shader stage.
- * @param {Node} node - The node to execute.
- * @param {?string} output - Expected output type. For example 'vec3'.
- * @param {?string} propertyName - The property name to assign the result.
- * @return {?(Object|Node)} The code flow or node.build() result.
- */
- flowNodeFromShaderStage( shaderStage, node, output = null, propertyName = null ) {
- const previousTab = this.tab;
- const previousCache = this.cache;
- const previousShaderStage = this.shaderStage;
- const previousContext = this.context;
- this.setShaderStage( shaderStage );
- const context = { ...this.context };
- delete context.nodeBlock;
- this.cache = this.globalCache;
- this.tab = '\t';
- this.context = context;
- let result = null;
- if ( this.buildStage === 'generate' ) {
- const flowData = this.flowChildNode( node, output );
- if ( propertyName !== null ) {
- flowData.code += `${ this.tab + propertyName } = ${ flowData.result };\n`;
- }
- this.flowCode[ shaderStage ] = this.flowCode[ shaderStage ] + flowData.code;
- result = flowData;
- } else {
- result = node.build( this );
- }
- this.setShaderStage( previousShaderStage );
- this.cache = previousCache;
- this.tab = previousTab;
- this.context = previousContext;
- return result;
- }
- /**
- * Returns an array holding all node attributes of this node builder.
- *
- * @return {Array<NodeAttribute>} The node attributes of this builder.
- */
- getAttributesArray() {
- return this.attributes.concat( this.bufferAttributes );
- }
- /**
- * Returns the attribute definitions as a shader string for the given shader stage.
- *
- * @abstract
- * @param {('vertex'|'fragment'|'compute'|'any')} shaderStage - The shader stage.
- * @return {string} The attribute code section.
- */
- getAttributes( /*shaderStage*/ ) {
- warn( 'Abstract function.' );
- }
- /**
- * Returns the varying definitions as a shader string for the given shader stage.
- *
- * @abstract
- * @param {('vertex'|'fragment'|'compute'|'any')} shaderStage - The shader stage.
- * @return {string} The varying code section.
- */
- getVaryings( /*shaderStage*/ ) {
- warn( 'Abstract function.' );
- }
- /**
- * Returns a single variable definition as a shader string for the given variable type and name.
- *
- * @param {string} type - The variable's type.
- * @param {string} name - The variable's name.
- * @param {?number} [count=null] - The array length.
- * @return {string} The shader string.
- */
- getVar( type, name, count = null ) {
- return `${ count !== null ? this.generateArrayDeclaration( type, count ) : this.getType( type ) } ${ name }`;
- }
- /**
- * Returns the variable definitions as a shader string for the given shader stage.
- *
- * @param {('vertex'|'fragment'|'compute'|'any')} shaderStage - The shader stage.
- * @return {string} The variable code section.
- */
- getVars( shaderStage ) {
- let snippet = '';
- const vars = this.vars[ shaderStage ];
- if ( vars !== undefined ) {
- for ( const variable of vars ) {
- snippet += `${ this.getVar( variable.type, variable.name ) }; `;
- }
- }
- return snippet;
- }
- /**
- * Returns the uniform definitions as a shader string for the given shader stage.
- *
- * @abstract
- * @param {('vertex'|'fragment'|'compute'|'any')} shaderStage - The shader stage.
- * @return {string} The uniform code section.
- */
- getUniforms( /*shaderStage*/ ) {
- warn( 'Abstract function.' );
- }
- /**
- * Returns the native code definitions as a shader string for the given shader stage.
- *
- * @param {('vertex'|'fragment'|'compute'|'any')} shaderStage - The shader stage.
- * @return {string} The native code section.
- */
- getCodes( shaderStage ) {
- const codes = this.codes[ shaderStage ];
- let code = '';
- if ( codes !== undefined ) {
- for ( const nodeCode of codes ) {
- code += nodeCode.code + '\n';
- }
- }
- return code;
- }
- /**
- * Returns the hash of this node builder.
- *
- * @return {string} The hash.
- */
- getHash() {
- return this.vertexShader + this.fragmentShader + this.computeShader;
- }
- /**
- * Sets the current shader stage.
- *
- * @param {?('vertex'|'fragment'|'compute'|'any')} shaderStage - The shader stage to set.
- */
- setShaderStage( shaderStage ) {
- this.shaderStage = shaderStage;
- }
- /**
- * Returns the current shader stage.
- *
- * @return {?('vertex'|'fragment'|'compute'|'any')} The current shader stage.
- */
- getShaderStage() {
- return this.shaderStage;
- }
- /**
- * Sets the current build stage.
- *
- * @param {?('setup'|'analyze'|'generate')} buildStage - The build stage to set.
- */
- setBuildStage( buildStage ) {
- this.buildStage = buildStage;
- }
- /**
- * Returns the current build stage.
- *
- * @return {?('setup'|'analyze'|'generate')} The current build stage.
- */
- getBuildStage() {
- return this.buildStage;
- }
- /**
- * Controls the code build of the shader stages.
- *
- * @abstract
- */
- buildCode() {
- warn( 'Abstract function.' );
- }
- /**
- * Returns the current sub-build layer.
- *
- * @return {SubBuildNode} The current sub-build layers.
- */
- get subBuild() {
- return this.subBuildLayers[ this.subBuildLayers.length - 1 ] || null;
- }
- /**
- * Adds a sub-build layer to the node builder.
- *
- * @param {SubBuildNode} subBuild - The sub-build layer to add.
- */
- addSubBuild( subBuild ) {
- this.subBuildLayers.push( subBuild );
- }
- /**
- * Removes the last sub-build layer from the node builder.
- *
- * @return {SubBuildNode} The removed sub-build layer.
- */
- removeSubBuild() {
- return this.subBuildLayers.pop();
- }
- /**
- * Returns the closest sub-build layer for the given data.
- *
- * @param {Node|Set<string>|Array<string>} data - The data to get the closest sub-build layer from.
- * @return {?string} The closest sub-build name or null if none found.
- */
- getClosestSubBuild( data ) {
- let subBuilds;
- if ( data && data.isNode ) {
- if ( data.isShaderCallNodeInternal ) {
- subBuilds = data.shaderNode.subBuilds;
- } else if ( data.isStackNode ) {
- subBuilds = [ data.subBuild ];
- } else {
- subBuilds = this.getDataFromNode( data, 'any' ).subBuilds;
- }
- } else if ( data instanceof Set ) {
- subBuilds = [ ...data ];
- } else {
- subBuilds = data;
- }
- if ( ! subBuilds ) return null;
- const subBuildLayers = this.subBuildLayers;
- for ( let i = subBuilds.length - 1; i >= 0; i -- ) {
- const subBuild = subBuilds[ i ];
- if ( subBuildLayers.includes( subBuild ) ) {
- return subBuild;
- }
- }
- return null;
- }
- /**
- * Returns the output node of a sub-build layer.
- *
- * @param {Node} node - The node to get the output from.
- * @return {string} The output node name.
- */
- getSubBuildOutput( node ) {
- return this.getSubBuildProperty( 'outputNode', node );
- }
- /**
- * Returns the sub-build property name for the given property and node.
- *
- * @param {string} [property=''] - The property name.
- * @param {?Node} [node=null] - The node to get the sub-build from.
- * @return {string} The sub-build property name.
- */
- getSubBuildProperty( property = '', node = null ) {
- let subBuild;
- if ( node !== null ) {
- subBuild = this.getClosestSubBuild( node );
- } else {
- subBuild = this.subBuildFn;
- }
- let result;
- if ( subBuild ) {
- result = property ? ( subBuild + '_' + property ) : subBuild;
- } else {
- result = property;
- }
- return result;
- }
- /**
- * Central build method which controls the build for the given object.
- *
- * @return {NodeBuilder} A reference to this node builder.
- */
- build() {
- const { object, material, renderer } = this;
- if ( material !== null ) {
- let nodeMaterial = renderer.library.fromMaterial( material );
- if ( nodeMaterial === null ) {
- error( `NodeMaterial: Material "${ material.type }" is not compatible.` );
- nodeMaterial = new NodeMaterial();
- }
- nodeMaterial.build( this );
- } else {
- this.addFlow( 'compute', object );
- }
- // setup() -> stage 1: create possible new nodes and/or return an output reference node
- // analyze() -> stage 2: analyze nodes to possible optimization and validation
- // generate() -> stage 3: generate shader
- for ( const buildStage of defaultBuildStages ) {
- this.setBuildStage( buildStage );
- if ( this.context.vertex && this.context.vertex.isNode ) {
- this.flowNodeFromShaderStage( 'vertex', this.context.vertex );
- }
- for ( const shaderStage of shaderStages ) {
- this.setShaderStage( shaderStage );
- const flowNodes = this.flowNodes[ shaderStage ];
- for ( const node of flowNodes ) {
- if ( buildStage === 'generate' ) {
- this.flowNode( node );
- } else {
- node.build( this );
- }
- }
- }
- }
- this.setBuildStage( null );
- this.setShaderStage( null );
- // stage 4: build code for a specific output
- this.buildCode();
- this.buildUpdateNodes();
- return this;
- }
- /**
- * Returns shared data object for the given node.
- *
- * @param {Node} node - The node to get shared data from.
- * @return {Object} The shared data.
- */
- getSharedDataFromNode( node ) {
- let data = sharedNodeData.get( node );
- if ( data === undefined ) {
- data = {};
- }
- return data;
- }
- /**
- * Returns a uniform representation which is later used for UBO generation and rendering.
- *
- * @param {NodeUniform} uniformNode - The uniform node.
- * @param {string} type - The requested type.
- * @return {Uniform} The uniform.
- */
- getNodeUniform( uniformNode, type ) {
- const nodeData = this.getSharedDataFromNode( uniformNode );
- let node = nodeData.cache;
- if ( node === undefined ) {
- if ( type === 'float' || type === 'int' || type === 'uint' ) node = new NumberNodeUniform( uniformNode );
- else if ( type === 'vec2' || type === 'ivec2' || type === 'uvec2' ) node = new Vector2NodeUniform( uniformNode );
- else if ( type === 'vec3' || type === 'ivec3' || type === 'uvec3' ) node = new Vector3NodeUniform( uniformNode );
- else if ( type === 'vec4' || type === 'ivec4' || type === 'uvec4' ) node = new Vector4NodeUniform( uniformNode );
- else if ( type === 'color' ) node = new ColorNodeUniform( uniformNode );
- else if ( type === 'mat2' ) node = new Matrix2NodeUniform( uniformNode );
- else if ( type === 'mat3' ) node = new Matrix3NodeUniform( uniformNode );
- else if ( type === 'mat4' ) node = new Matrix4NodeUniform( uniformNode );
- else {
- throw new Error( `Uniform "${ type }" not implemented.` );
- }
- nodeData.cache = node;
- }
- return node;
- }
- /**
- * Formats the given shader snippet from a given type into another one. E.g.
- * this method might be used to convert a simple float string `"1.0"` into a
- * `vec3` representation: `"vec3<f32>( 1.0 )"`.
- *
- * @param {string} snippet - The shader snippet.
- * @param {string} fromType - The source type.
- * @param {string} toType - The target type.
- * @return {string} The updated shader string.
- */
- format( snippet, fromType, toType ) {
- fromType = this.getVectorType( fromType );
- toType = this.getVectorType( toType );
- if ( fromType === toType || toType === null || this.isReference( toType ) ) {
- return snippet;
- }
- const fromTypeLength = this.getTypeLength( fromType );
- const toTypeLength = this.getTypeLength( toType );
- if ( fromTypeLength === 16 && toTypeLength === 9 ) {
- return `${ this.getType( toType ) }( ${ snippet }[ 0 ].xyz, ${ snippet }[ 1 ].xyz, ${ snippet }[ 2 ].xyz )`;
- }
- if ( fromTypeLength === 9 && toTypeLength === 4 ) {
- return `${ this.getType( toType ) }( ${ snippet }[ 0 ].xy, ${ snippet }[ 1 ].xy )`;
- }
- if ( fromTypeLength > 4 ) { // fromType is matrix-like
- // @TODO: ignore for now
- return snippet;
- }
- if ( toTypeLength > 4 || toTypeLength === 0 ) { // toType is matrix-like or unknown
- // @TODO: ignore for now
- return snippet;
- }
- if ( fromTypeLength === toTypeLength ) {
- return `${ this.getType( toType ) }( ${ snippet } )`;
- }
- if ( fromTypeLength > toTypeLength ) {
- snippet = toType === 'bool' ? `all( ${ snippet } )` : `${ snippet }.${ 'xyz'.slice( 0, toTypeLength ) }`;
- return this.format( snippet, this.getTypeFromLength( toTypeLength, this.getComponentType( fromType ) ), toType );
- }
- if ( toTypeLength === 4 && fromTypeLength > 1 ) { // toType is vec4-like
- return `${ this.getType( toType ) }( ${ this.format( snippet, fromType, 'vec3' ) }, 1.0 )`;
- }
- if ( fromTypeLength === 2 ) { // fromType is vec2-like and toType is vec3-like
- return `${ this.getType( toType ) }( ${ this.format( snippet, fromType, 'vec2' ) }, 0.0 )`;
- }
- if ( fromTypeLength === 1 && toTypeLength > 1 && fromType !== this.getComponentType( toType ) ) { // fromType is float-like
- // convert a number value to vector type, e.g:
- // vec3( 1u ) -> vec3( float( 1u ) )
- snippet = `${ this.getType( this.getComponentType( toType ) ) }( ${ snippet } )`;
- }
- return `${ this.getType( toType ) }( ${ snippet } )`; // fromType is float-like
- }
- /**
- * Returns a signature with the engine's current revision.
- *
- * @return {string} The signature.
- */
- getSignature() {
- return `// Three.js r${ REVISION } - Node System\n`;
- }
- }
- /**
- * Management class for updating nodes. The module tracks metrics like
- * the elapsed time, delta time, the render and frame ID to correctly
- * call the node update methods {@link Node#updateBefore}, {@link Node#update}
- * and {@link Node#updateAfter} depending on the node's configuration.
- */
- class NodeFrame {
- /**
- * Constructs a new node fame.
- */
- constructor() {
- /**
- * The elapsed time in seconds.
- *
- * @type {number}
- * @default 0
- */
- this.time = 0;
- /**
- * The delta time in seconds.
- *
- * @type {number}
- * @default 0
- */
- this.deltaTime = 0;
- /**
- * The frame ID.
- *
- * @type {number}
- * @default 0
- */
- this.frameId = 0;
- /**
- * The render ID.
- *
- * @type {number}
- * @default 0
- */
- this.renderId = 0;
- /**
- * Used to control the {@link Node#update} call.
- *
- * @type {WeakMap<Node, Object>}
- */
- this.updateMap = new WeakMap();
- /**
- * Used to control the {@link Node#updateBefore} call.
- *
- * @type {WeakMap<Node, Object>}
- */
- this.updateBeforeMap = new WeakMap();
- /**
- * Used to control the {@link Node#updateAfter} call.
- *
- * @type {WeakMap<Node, Object>}
- */
- this.updateAfterMap = new WeakMap();
- /**
- * A reference to the current renderer.
- *
- * @type {?Renderer}
- * @default null
- */
- this.renderer = null;
- /**
- * A reference to the current material.
- *
- * @type {?Material}
- * @default null
- */
- this.material = null;
- /**
- * A reference to the current camera.
- *
- * @type {?Camera}
- * @default null
- */
- this.camera = null;
- /**
- * A reference to the current 3D object.
- *
- * @type {?Object3D}
- * @default null
- */
- this.object = null;
- /**
- * A reference to the current scene.
- *
- * @type {?Scene}
- * @default null
- */
- this.scene = null;
- }
- /**
- * Returns a dictionary for a given node and update map which
- * is used to correctly call node update methods per frame or render.
- *
- * @private
- * @param {WeakMap<Node, Object>} referenceMap - The reference weak map.
- * @param {Node} nodeRef - The reference to the current node.
- * @return {Object<string,WeakMap<Object, number>>} The dictionary.
- */
- _getMaps( referenceMap, nodeRef ) {
- let maps = referenceMap.get( nodeRef );
- if ( maps === undefined ) {
- maps = {
- renderId: 0,
- frameId: 0,
- };
- referenceMap.set( nodeRef, maps );
- }
- return maps;
- }
- /**
- * This method executes the {@link Node#updateBefore} for the given node.
- * It makes sure {@link Node#updateBeforeType} is honored meaning the update
- * is only executed once per frame, render or object depending on the update
- * type.
- *
- * @param {Node} node - The node that should be updated.
- */
- updateBeforeNode( node ) {
- const updateType = node.getUpdateBeforeType();
- const reference = node.updateReference( this );
- if ( updateType === NodeUpdateType.FRAME ) {
- const nodeUpdateBeforeMap = this._getMaps( this.updateBeforeMap, reference );
- if ( nodeUpdateBeforeMap.frameId !== this.frameId ) {
- const previousFrameId = nodeUpdateBeforeMap.frameId;
- nodeUpdateBeforeMap.frameId = this.frameId;
- if ( node.updateBefore( this ) === false ) {
- nodeUpdateBeforeMap.frameId = previousFrameId;
- }
- }
- } else if ( updateType === NodeUpdateType.RENDER ) {
- const nodeUpdateBeforeMap = this._getMaps( this.updateBeforeMap, reference );
- if ( nodeUpdateBeforeMap.renderId !== this.renderId ) {
- const previousRenderId = nodeUpdateBeforeMap.renderId;
- nodeUpdateBeforeMap.renderId = this.renderId;
- if ( node.updateBefore( this ) === false ) {
- nodeUpdateBeforeMap.renderId = previousRenderId;
- }
- }
- } else if ( updateType === NodeUpdateType.OBJECT ) {
- node.updateBefore( this );
- }
- }
- /**
- * This method executes the {@link Node#updateAfter} for the given node.
- * It makes sure {@link Node#updateAfterType} is honored meaning the update
- * is only executed once per frame, render or object depending on the update
- * type.
- *
- * @param {Node} node - The node that should be updated.
- */
- updateAfterNode( node ) {
- const updateType = node.getUpdateAfterType();
- const reference = node.updateReference( this );
- if ( updateType === NodeUpdateType.FRAME ) {
- const nodeUpdateAfterMap = this._getMaps( this.updateAfterMap, reference );
- if ( nodeUpdateAfterMap.frameId !== this.frameId ) {
- if ( node.updateAfter( this ) !== false ) {
- nodeUpdateAfterMap.frameId = this.frameId;
- }
- }
- } else if ( updateType === NodeUpdateType.RENDER ) {
- const nodeUpdateAfterMap = this._getMaps( this.updateAfterMap, reference );
- if ( nodeUpdateAfterMap.renderId !== this.renderId ) {
- if ( node.updateAfter( this ) !== false ) {
- nodeUpdateAfterMap.renderId = this.renderId;
- }
- }
- } else if ( updateType === NodeUpdateType.OBJECT ) {
- node.updateAfter( this );
- }
- }
- /**
- * This method executes the {@link Node#update} for the given node.
- * It makes sure {@link Node#updateType} is honored meaning the update
- * is only executed once per frame, render or object depending on the update
- * type.
- *
- * @param {Node} node - The node that should be updated.
- */
- updateNode( node ) {
- const updateType = node.getUpdateType();
- const reference = node.updateReference( this );
- if ( updateType === NodeUpdateType.FRAME ) {
- const nodeUpdateMap = this._getMaps( this.updateMap, reference );
- if ( nodeUpdateMap.frameId !== this.frameId ) {
- if ( node.update( this ) !== false ) {
- nodeUpdateMap.frameId = this.frameId;
- }
- }
- } else if ( updateType === NodeUpdateType.RENDER ) {
- const nodeUpdateMap = this._getMaps( this.updateMap, reference );
- if ( nodeUpdateMap.renderId !== this.renderId ) {
- if ( node.update( this ) !== false ) {
- nodeUpdateMap.renderId = this.renderId;
- }
- }
- } else if ( updateType === NodeUpdateType.OBJECT ) {
- node.update( this );
- }
- }
- /**
- * Updates the internal state of the node frame. This method is
- * called by the renderer in its internal animation loop.
- */
- update() {
- this.frameId ++;
- if ( this.lastTime === undefined ) this.lastTime = performance.now();
- this.deltaTime = ( performance.now() - this.lastTime ) / 1000;
- this.lastTime = performance.now();
- this.time += this.deltaTime;
- }
- }
- /**
- * Describes the input of a {@link NodeFunction}.
- */
- class NodeFunctionInput {
- /**
- * Constructs a new node function input.
- *
- * @param {string} type - The input type.
- * @param {string} name - The input name.
- * @param {?number} [count=null] - If the input is an Array, count will be the length.
- * @param {('in'|'out'|'inout')} [qualifier=''] - The parameter qualifier (only relevant for GLSL).
- * @param {boolean} [isConst=false] - Whether the input uses a const qualifier or not (only relevant for GLSL).
- */
- constructor( type, name, count = null, qualifier = '', isConst = false ) {
- /**
- * The input type.
- *
- * @type {string}
- */
- this.type = type;
- /**
- * The input name.
- *
- * @type {string}
- */
- this.name = name;
- /**
- * If the input is an Array, count will be the length.
- *
- * @type {?number}
- * @default null
- */
- this.count = count;
- /**
- *The parameter qualifier (only relevant for GLSL).
- *
- * @type {('in'|'out'|'inout')}
- * @default ''
- */
- this.qualifier = qualifier;
- /**
- * Whether the input uses a const qualifier or not (only relevant for GLSL).
- *
- * @type {boolean}
- * @default false
- */
- this.isConst = isConst;
- }
- }
- NodeFunctionInput.isNodeFunctionInput = true;
- /**
- * Module for representing directional lights as nodes.
- *
- * @augments AnalyticLightNode
- */
- class DirectionalLightNode extends AnalyticLightNode {
- static get type() {
- return 'DirectionalLightNode';
- }
- /**
- * Constructs a new directional light node.
- *
- * @param {?DirectionalLight} [light=null] - The directional light source.
- */
- constructor( light = null ) {
- super( light );
- }
- setupDirect() {
- const lightColor = this.colorNode;
- const lightDirection = lightTargetDirection( this.light );
- return { lightDirection, lightColor };
- }
- }
- const _matrix41 = /*@__PURE__*/ new Matrix4();
- const _matrix42 = /*@__PURE__*/ new Matrix4();
- let _ltcLib = null;
- /**
- * Module for representing rect area lights as nodes.
- *
- * @augments AnalyticLightNode
- */
- class RectAreaLightNode extends AnalyticLightNode {
- static get type() {
- return 'RectAreaLightNode';
- }
- /**
- * Constructs a new rect area light node.
- *
- * @param {?RectAreaLight} [light=null] - The rect area light source.
- */
- constructor( light = null ) {
- super( light );
- /**
- * Uniform node representing the half height of the are light.
- *
- * @type {UniformNode<vec3>}
- */
- this.halfHeight = uniform( new Vector3() ).setGroup( renderGroup );
- /**
- * Uniform node representing the half width of the are light.
- *
- * @type {UniformNode<vec3>}
- */
- this.halfWidth = uniform( new Vector3() ).setGroup( renderGroup );
- /**
- * The `updateType` is set to `NodeUpdateType.RENDER` since the light
- * relies on `viewMatrix` which might vary per render call.
- *
- * @type {string}
- * @default 'render'
- */
- this.updateType = NodeUpdateType.RENDER;
- }
- /**
- * Overwritten to updated rect area light specific uniforms.
- *
- * @param {NodeFrame} frame - A reference to the current node frame.
- */
- update( frame ) {
- super.update( frame );
- const { light } = this;
- const viewMatrix = frame.camera.matrixWorldInverse;
- _matrix42.identity();
- _matrix41.copy( light.matrixWorld );
- _matrix41.premultiply( viewMatrix );
- _matrix42.extractRotation( _matrix41 );
- this.halfWidth.value.set( light.width * 0.5, 0.0, 0.0 );
- this.halfHeight.value.set( 0.0, light.height * 0.5, 0.0 );
- this.halfWidth.value.applyMatrix4( _matrix42 );
- this.halfHeight.value.applyMatrix4( _matrix42 );
- }
- setupDirectRectArea( builder ) {
- let ltc_1, ltc_2;
- if ( builder.isAvailable( 'float32Filterable' ) ) {
- ltc_1 = texture( _ltcLib.LTC_FLOAT_1 );
- ltc_2 = texture( _ltcLib.LTC_FLOAT_2 );
- } else {
- ltc_1 = texture( _ltcLib.LTC_HALF_1 );
- ltc_2 = texture( _ltcLib.LTC_HALF_2 );
- }
- const { colorNode, light } = this;
- const lightPosition = lightViewPosition( light );
- return {
- lightColor: colorNode,
- lightPosition,
- halfWidth: this.halfWidth,
- halfHeight: this.halfHeight,
- ltc_1,
- ltc_2
- };
- }
- /**
- * Used to configure the internal BRDF approximation texture data.
- *
- * @param {RectAreaLightTexturesLib} ltc - The BRDF approximation texture data.
- */
- static setLTC( ltc ) {
- _ltcLib = ltc;
- }
- }
- /**
- * Module for representing spot lights as nodes.
- *
- * @augments AnalyticLightNode
- */
- class SpotLightNode extends AnalyticLightNode {
- static get type() {
- return 'SpotLightNode';
- }
- /**
- * Constructs a new spot light node.
- *
- * @param {?SpotLight} [light=null] - The spot light source.
- */
- constructor( light = null ) {
- super( light );
- /**
- * Uniform node representing the cone cosine.
- *
- * @type {UniformNode<float>}
- */
- this.coneCosNode = uniform( 0 ).setGroup( renderGroup );
- /**
- * Uniform node representing the penumbra cosine.
- *
- * @type {UniformNode<float>}
- */
- this.penumbraCosNode = uniform( 0 ).setGroup( renderGroup );
- /**
- * Uniform node representing the cutoff distance.
- *
- * @type {UniformNode<float>}
- */
- this.cutoffDistanceNode = uniform( 0 ).setGroup( renderGroup );
- /**
- * Uniform node representing the decay exponent.
- *
- * @type {UniformNode<float>}
- */
- this.decayExponentNode = uniform( 0 ).setGroup( renderGroup );
- /**
- * Uniform node representing the light color.
- *
- * @type {UniformNode<Color>}
- */
- this.colorNode = uniform( this.color ).setGroup( renderGroup );
- }
- /**
- * Overwritten to updated spot light specific uniforms.
- *
- * @param {NodeFrame} frame - A reference to the current node frame.
- */
- update( frame ) {
- super.update( frame );
- const { light } = this;
- this.coneCosNode.value = Math.cos( light.angle );
- this.penumbraCosNode.value = Math.cos( light.angle * ( 1 - light.penumbra ) );
- this.cutoffDistanceNode.value = light.distance;
- this.decayExponentNode.value = light.decay;
- }
- /**
- * Computes the spot attenuation for the given angle.
- *
- * @param {NodeBuilder} builder - The node builder.
- * @param {Node<float>} angleCosine - The angle to compute the spot attenuation for.
- * @return {Node<float>} The spot attenuation.
- */
- getSpotAttenuation( builder, angleCosine ) {
- const { coneCosNode, penumbraCosNode } = this;
- return smoothstep( coneCosNode, penumbraCosNode, angleCosine );
- }
- getLightCoord( builder ) {
- const properties = builder.getNodeProperties( this );
- let projectionUV = properties.projectionUV;
- if ( projectionUV === undefined ) {
- projectionUV = lightProjectionUV( this.light, builder.context.positionWorld );
- properties.projectionUV = projectionUV;
- }
- return projectionUV;
- }
- setupDirect( builder ) {
- const { colorNode, cutoffDistanceNode, decayExponentNode, light } = this;
- const lightVector = this.getLightVector( builder );
- const lightDirection = lightVector.normalize();
- const angleCos = lightDirection.dot( lightTargetDirection( light ) );
- const spotAttenuation = this.getSpotAttenuation( builder, angleCos );
- const lightDistance = lightVector.length();
- const lightAttenuation = getDistanceAttenuation( {
- lightDistance,
- cutoffDistance: cutoffDistanceNode,
- decayExponent: decayExponentNode
- } );
- let lightColor = colorNode.mul( spotAttenuation ).mul( lightAttenuation );
- let projected, lightCoord;
- if ( light.colorNode ) {
- lightCoord = this.getLightCoord( builder );
- projected = light.colorNode( lightCoord );
- } else if ( light.map ) {
- lightCoord = this.getLightCoord( builder );
- projected = texture( light.map, lightCoord.xy ).onRenderUpdate( () => light.map );
- }
- if ( projected ) {
- const inSpotLightMap = lightCoord.mul( 2. ).sub( 1. ).abs().lessThan( 1. ).all();
- lightColor = inSpotLightMap.select( lightColor.mul( projected ), lightColor );
- }
- return { lightColor, lightDirection };
- }
- }
- /**
- * An IES version of the default spot light node.
- *
- * @augments SpotLightNode
- */
- class IESSpotLightNode extends SpotLightNode {
- static get type() {
- return 'IESSpotLightNode';
- }
- /**
- * Overwrites the default implementation to compute an IES conform spot attenuation.
- *
- * @param {NodeBuilder} builder - The node builder.
- * @param {Node<float>} angleCosine - The angle to compute the spot attenuation for.
- * @return {Node<float>} The spot attenuation.
- */
- getSpotAttenuation( builder, angleCosine ) {
- const iesMap = this.light.iesMap;
- let spotAttenuation = null;
- if ( iesMap && iesMap.isTexture === true ) {
- const angle = angleCosine.acos().mul( 1.0 / Math.PI );
- spotAttenuation = texture( iesMap, vec2( angle, 0 ), 0 ).r;
- } else {
- spotAttenuation = super.getSpotAttenuation( angleCosine );
- }
- return spotAttenuation;
- }
- }
- const sdBox = /*@__PURE__*/ Fn( ( [ p, b ] ) => {
- const d = p.abs().sub( b );
- return length( max$1( d, 0.0 ) ).add( min$1( max$1( d.x, d.y ), 0.0 ) );
- } );
- /**
- * An implementation of a projector light node.
- *
- * @augments SpotLightNode
- */
- class ProjectorLightNode extends SpotLightNode {
- static get type() {
- return 'ProjectorLightNode';
- }
- update( frame ) {
- super.update( frame );
- const light = this.light;
- this.penumbraCosNode.value = Math.min( Math.cos( light.angle * ( 1 - light.penumbra ) ), .99999 );
- if ( light.aspect === null ) {
- let aspect = 1;
- if ( light.map !== null ) {
- aspect = light.map.width / light.map.height;
- }
- light.shadow.aspect = aspect;
- } else {
- light.shadow.aspect = light.aspect;
- }
- }
- /**
- * Overwrites the default implementation to compute projection attenuation.
- *
- * @param {NodeBuilder} builder - The node builder.
- * @return {Node<float>} The spot attenuation.
- */
- getSpotAttenuation( builder ) {
- const attenuation = float( 0 );
- const penumbraCos = this.penumbraCosNode;
- // compute the fragment's position in the light's clip space
- const spotLightCoord = lightShadowMatrix( this.light ).mul( builder.context.positionWorld || positionWorld );
- // the sign of w determines whether the current fragment is in front or behind the light.
- // to avoid a back-projection, it's important to only compute an attenuation if w is positive
- If( spotLightCoord.w.greaterThan( 0 ), () => {
- const projectionUV = spotLightCoord.xyz.div( spotLightCoord.w );
- const boxDist = sdBox( projectionUV.xy.sub( vec2( 0.5 ) ), vec2( 0.5 ) );
- const angleFactor = div( -1, sub( 1.0, acos( penumbraCos ) ).sub( 1.0 ) );
- attenuation.assign( saturate( boxDist.mul( -2 ).mul( angleFactor ) ) );
- } );
- return attenuation;
- }
- }
- /**
- * Module for representing ambient lights as nodes.
- *
- * @augments AnalyticLightNode
- */
- class AmbientLightNode extends AnalyticLightNode {
- static get type() {
- return 'AmbientLightNode';
- }
- /**
- * Constructs a new ambient light node.
- *
- * @param {?AmbientLight} [light=null] - The ambient light source.
- */
- constructor( light = null ) {
- super( light );
- }
- setup( { context } ) {
- context.irradiance.addAssign( this.colorNode );
- }
- }
- /**
- * Module for representing hemisphere lights as nodes.
- *
- * @augments AnalyticLightNode
- */
- class HemisphereLightNode extends AnalyticLightNode {
- static get type() {
- return 'HemisphereLightNode';
- }
- /**
- * Constructs a new hemisphere light node.
- *
- * @param {?HemisphereLight} [light=null] - The hemisphere light source.
- */
- constructor( light = null ) {
- super( light );
- /**
- * Uniform node representing the light's position.
- *
- * @type {UniformNode<vec3>}
- */
- this.lightPositionNode = lightPosition( light );
- /**
- * A node representing the light's direction.
- *
- * @type {Node<vec3>}
- */
- this.lightDirectionNode = this.lightPositionNode.normalize();
- /**
- * Uniform node representing the light's ground color.
- *
- * @type {UniformNode<vec3>}
- */
- this.groundColorNode = uniform( new Color() ).setGroup( renderGroup );
- }
- /**
- * Overwritten to updated hemisphere light specific uniforms.
- *
- * @param {NodeFrame} frame - A reference to the current node frame.
- */
- update( frame ) {
- const { light } = this;
- super.update( frame );
- this.lightPositionNode.object3d = light;
- this.groundColorNode.value.copy( light.groundColor ).multiplyScalar( light.intensity );
- }
- setup( builder ) {
- const { colorNode, groundColorNode, lightDirectionNode } = this;
- const dotNL = normalWorld.dot( lightDirectionNode );
- const hemiDiffuseWeight = dotNL.mul( 0.5 ).add( 0.5 );
- const irradiance = mix( groundColorNode, colorNode, hemiDiffuseWeight );
- builder.context.irradiance.addAssign( irradiance );
- }
- }
- /**
- * Module for representing light probes as nodes.
- *
- * @augments AnalyticLightNode
- */
- class LightProbeNode extends AnalyticLightNode {
- static get type() {
- return 'LightProbeNode';
- }
- /**
- * Constructs a new light probe node.
- *
- * @param {?LightProbe} [light=null] - The light probe.
- */
- constructor( light = null ) {
- super( light );
- const array = [];
- for ( let i = 0; i < 9; i ++ ) array.push( new Vector3() );
- /**
- * Light probe represented as a uniform of spherical harmonics.
- *
- * @type {UniformArrayNode}
- */
- this.lightProbe = uniformArray( array );
- }
- /**
- * Overwritten to updated light probe specific uniforms.
- *
- * @param {NodeFrame} frame - A reference to the current node frame.
- */
- update( frame ) {
- const { light } = this;
- super.update( frame );
- //
- for ( let i = 0; i < 9; i ++ ) {
- this.lightProbe.array[ i ].copy( light.sh.coefficients[ i ] ).multiplyScalar( light.intensity );
- }
- }
- setup( builder ) {
- const irradiance = getShIrradianceAt( normalWorld, this.lightProbe );
- builder.context.irradiance.addAssign( irradiance );
- }
- }
- /**
- * Base class for node parsers. A derived parser must be implemented
- * for each supported native shader language.
- */
- class NodeParser {
- /**
- * The method parses the given native code an returns a node function.
- *
- * @abstract
- * @param {string} source - The native shader code.
- * @return {NodeFunction} A node function.
- */
- parseFunction( /*source*/ ) {
- warn( 'Abstract function.' );
- }
- }
- /**
- * Base class for node functions. A derived module must be implemented
- * for each supported native shader language. Similar to other `Node*` modules,
- * this class is only relevant during the building process and not used
- * in user-level code.
- */
- class NodeFunction {
- /**
- * Constructs a new node function.
- *
- * @param {string} type - The node type. This type is the return type of the node function.
- * @param {Array<NodeFunctionInput>} inputs - The function's inputs.
- * @param {string} [name=''] - The function's name.
- * @param {string} [precision=''] - The precision qualifier.
- */
- constructor( type, inputs, name = '', precision = '' ) {
- /**
- * The node type. This type is the return type of the node function.
- *
- * @type {string}
- */
- this.type = type;
- /**
- * The function's inputs.
- *
- * @type {Array<NodeFunctionInput>}
- */
- this.inputs = inputs;
- /**
- * The name of the uniform.
- *
- * @type {string}
- * @default ''
- */
- this.name = name;
- /**
- * The precision qualifier.
- *
- * @type {string}
- * @default ''
- */
- this.precision = precision;
- }
- /**
- * This method returns the native code of the node function.
- *
- * @abstract
- * @param {string} name - The function's name.
- * @return {string} A shader code.
- */
- getCode( /*name = this.name*/ ) {
- warn( 'Abstract function.' );
- }
- }
- NodeFunction.isNodeFunction = true;
- const declarationRegexp$1 = /^\s*(highp|mediump|lowp)?\s*([a-z_0-9]+)\s*([a-z_0-9]+)?\s*\(([\s\S]*?)\)/i;
- const propertiesRegexp$1 = /[a-z_0-9]+/ig;
- const pragmaMain = '#pragma main';
- const parse$1 = ( source ) => {
- source = source.trim();
- const pragmaMainIndex = source.indexOf( pragmaMain );
- const mainCode = pragmaMainIndex !== -1 ? source.slice( pragmaMainIndex + pragmaMain.length ) : source;
- const declaration = mainCode.match( declarationRegexp$1 );
- if ( declaration !== null && declaration.length === 5 ) {
- // tokenizer
- const inputsCode = declaration[ 4 ];
- const propsMatches = [];
- let nameMatch = null;
- while ( ( nameMatch = propertiesRegexp$1.exec( inputsCode ) ) !== null ) {
- propsMatches.push( nameMatch );
- }
- // parser
- const inputs = [];
- let i = 0;
- while ( i < propsMatches.length ) {
- const isConst = propsMatches[ i ][ 0 ] === 'const';
- if ( isConst === true ) {
- i ++;
- }
- let qualifier = propsMatches[ i ][ 0 ];
- if ( qualifier === 'in' || qualifier === 'out' || qualifier === 'inout' ) {
- i ++;
- } else {
- qualifier = '';
- }
- const type = propsMatches[ i ++ ][ 0 ];
- let count = Number.parseInt( propsMatches[ i ][ 0 ] );
- if ( Number.isNaN( count ) === false ) i ++;
- else count = null;
- const name = propsMatches[ i ++ ][ 0 ];
- inputs.push( new NodeFunctionInput( type, name, count, qualifier, isConst ) );
- }
- //
- const blockCode = mainCode.substring( declaration[ 0 ].length );
- const name = declaration[ 3 ] !== undefined ? declaration[ 3 ] : '';
- const type = declaration[ 2 ];
- const precision = declaration[ 1 ] !== undefined ? declaration[ 1 ] : '';
- const headerCode = pragmaMainIndex !== -1 ? source.slice( 0, pragmaMainIndex ) : '';
- return {
- type,
- inputs,
- name,
- precision,
- inputsCode,
- blockCode,
- headerCode
- };
- } else {
- throw new Error( 'FunctionNode: Function is not a GLSL code.' );
- }
- };
- /**
- * This class represents a GLSL node function.
- *
- * @augments NodeFunction
- */
- class GLSLNodeFunction extends NodeFunction {
- /**
- * Constructs a new GLSL node function.
- *
- * @param {string} source - The GLSL source.
- */
- constructor( source ) {
- const { type, inputs, name, precision, inputsCode, blockCode, headerCode } = parse$1( source );
- super( type, inputs, name, precision );
- this.inputsCode = inputsCode;
- this.blockCode = blockCode;
- this.headerCode = headerCode;
- }
- /**
- * This method returns the GLSL code of the node function.
- *
- * @param {string} [name=this.name] - The function's name.
- * @return {string} The shader code.
- */
- getCode( name = this.name ) {
- let code;
- const blockCode = this.blockCode;
- if ( blockCode !== '' ) {
- const { type, inputsCode, headerCode, precision } = this;
- let declarationCode = `${ type } ${ name } ( ${ inputsCode.trim() } )`;
- if ( precision !== '' ) {
- declarationCode = `${ precision } ${ declarationCode }`;
- }
- code = headerCode + declarationCode + blockCode;
- } else {
- // interface function
- code = '';
- }
- return code;
- }
- }
- /**
- * A GLSL node parser.
- *
- * @augments NodeParser
- */
- class GLSLNodeParser extends NodeParser {
- /**
- * The method parses the given GLSL code an returns a node function.
- *
- * @param {string} source - The GLSL code.
- * @return {GLSLNodeFunction} A node function.
- */
- parseFunction( source ) {
- return new GLSLNodeFunction( source );
- }
- }
- const _outputNodeMap = new WeakMap();
- const _chainKeys$2 = [];
- const _cacheKeyValues = [];
- /**
- * This renderer module manages node-related objects and is the
- * primary interface between the renderer and the node system.
- *
- * @private
- * @augments DataMap
- */
- class Nodes extends DataMap {
- /**
- * Constructs a new nodes management component.
- *
- * @param {Renderer} renderer - The renderer.
- * @param {Backend} backend - The renderer's backend.
- */
- constructor( renderer, backend ) {
- super();
- /**
- * The renderer.
- *
- * @type {Renderer}
- */
- this.renderer = renderer;
- /**
- * The renderer's backend.
- *
- * @type {Backend}
- */
- this.backend = backend;
- /**
- * The node frame.
- *
- * @type {Renderer}
- */
- this.nodeFrame = new NodeFrame();
- /**
- * A cache for managing node builder states.
- *
- * @type {Map<number,NodeBuilderState>}
- */
- this.nodeBuilderCache = new Map();
- /**
- * A cache for managing data cache key data.
- *
- * @type {ChainMap}
- */
- this.callHashCache = new ChainMap();
- /**
- * A cache for managing node uniforms group data.
- *
- * @type {ChainMap}
- */
- this.groupsData = new ChainMap();
- /**
- * A cache for managing node objects of
- * scene properties like fog or environments.
- *
- * @type {Object<string,WeakMap>}
- */
- this.cacheLib = {};
- }
- /**
- * Returns `true` if the given node uniforms group must be updated or not.
- *
- * @param {NodeUniformsGroup} nodeUniformsGroup - The node uniforms group.
- * @return {boolean} Whether the node uniforms group requires an update or not.
- */
- updateGroup( nodeUniformsGroup ) {
- const groupNode = nodeUniformsGroup.groupNode;
- const name = groupNode.name;
- // objectGroup is always updated
- if ( name === objectGroup.name ) return true;
- // renderGroup is updated once per render/compute call
- if ( name === renderGroup.name ) {
- const uniformsGroupData = this.get( nodeUniformsGroup );
- const renderId = this.nodeFrame.renderId;
- if ( uniformsGroupData.renderId !== renderId ) {
- uniformsGroupData.renderId = renderId;
- return true;
- }
- return false;
- }
- // frameGroup is updated once per frame
- if ( name === frameGroup.name ) {
- const uniformsGroupData = this.get( nodeUniformsGroup );
- const frameId = this.nodeFrame.frameId;
- if ( uniformsGroupData.frameId !== frameId ) {
- uniformsGroupData.frameId = frameId;
- return true;
- }
- return false;
- }
- // other groups are updated just when groupNode.needsUpdate is true
- _chainKeys$2[ 0 ] = groupNode;
- _chainKeys$2[ 1 ] = nodeUniformsGroup;
- let groupData = this.groupsData.get( _chainKeys$2 );
- if ( groupData === undefined ) this.groupsData.set( _chainKeys$2, groupData = {} );
- _chainKeys$2.length = 0;
- if ( groupData.version !== groupNode.version ) {
- groupData.version = groupNode.version;
- return true;
- }
- return false;
- }
- /**
- * Returns the cache key for the given render object.
- *
- * @param {RenderObject} renderObject - The render object.
- * @return {number} The cache key.
- */
- getForRenderCacheKey( renderObject ) {
- return renderObject.initialCacheKey;
- }
- /**
- * Returns a node builder state for the given render object.
- *
- * @param {RenderObject} renderObject - The render object.
- * @return {NodeBuilderState} The node builder state.
- */
- getForRender( renderObject ) {
- const renderObjectData = this.get( renderObject );
- let nodeBuilderState = renderObjectData.nodeBuilderState;
- if ( nodeBuilderState === undefined ) {
- const { nodeBuilderCache } = this;
- const cacheKey = this.getForRenderCacheKey( renderObject );
- nodeBuilderState = nodeBuilderCache.get( cacheKey );
- if ( nodeBuilderState === undefined ) {
- const createNodeBuilder = ( material ) => {
- const nodeBuilder = this.backend.createNodeBuilder( renderObject.object, this.renderer );
- nodeBuilder.scene = renderObject.scene;
- nodeBuilder.material = material;
- nodeBuilder.camera = renderObject.camera;
- nodeBuilder.context.material = material;
- nodeBuilder.lightsNode = renderObject.lightsNode;
- nodeBuilder.environmentNode = this.getEnvironmentNode( renderObject.scene );
- nodeBuilder.fogNode = this.getFogNode( renderObject.scene );
- nodeBuilder.clippingContext = renderObject.clippingContext;
- if ( this.renderer.getOutputRenderTarget() ? this.renderer.getOutputRenderTarget().multiview : false ) {
- nodeBuilder.enableMultiview();
- }
- return nodeBuilder;
- };
- let nodeBuilder = createNodeBuilder( renderObject.material );
- try {
- nodeBuilder.build();
- } catch ( e ) {
- nodeBuilder = createNodeBuilder( new NodeMaterial() );
- nodeBuilder.build();
- error( 'TSL: ' + e );
- }
- nodeBuilderState = this._createNodeBuilderState( nodeBuilder );
- nodeBuilderCache.set( cacheKey, nodeBuilderState );
- }
- nodeBuilderState.usedTimes ++;
- renderObjectData.nodeBuilderState = nodeBuilderState;
- }
- return nodeBuilderState;
- }
- /**
- * Deletes the given object from the internal data map
- *
- * @param {any} object - The object to delete.
- * @return {?Object} The deleted dictionary.
- */
- delete( object ) {
- if ( object.isRenderObject ) {
- const nodeBuilderState = this.get( object ).nodeBuilderState;
- nodeBuilderState.usedTimes --;
- if ( nodeBuilderState.usedTimes === 0 ) {
- this.nodeBuilderCache.delete( this.getForRenderCacheKey( object ) );
- }
- }
- return super.delete( object );
- }
- /**
- * Returns a node builder state for the given compute node.
- *
- * @param {Node} computeNode - The compute node.
- * @return {NodeBuilderState} The node builder state.
- */
- getForCompute( computeNode ) {
- const computeData = this.get( computeNode );
- let nodeBuilderState = computeData.nodeBuilderState;
- if ( nodeBuilderState === undefined ) {
- const nodeBuilder = this.backend.createNodeBuilder( computeNode, this.renderer );
- nodeBuilder.build();
- nodeBuilderState = this._createNodeBuilderState( nodeBuilder );
- computeData.nodeBuilderState = nodeBuilderState;
- }
- return nodeBuilderState;
- }
- /**
- * Creates a node builder state for the given node builder.
- *
- * @private
- * @param {NodeBuilder} nodeBuilder - The node builder.
- * @return {NodeBuilderState} The node builder state.
- */
- _createNodeBuilderState( nodeBuilder ) {
- return new NodeBuilderState(
- nodeBuilder.vertexShader,
- nodeBuilder.fragmentShader,
- nodeBuilder.computeShader,
- nodeBuilder.getAttributesArray(),
- nodeBuilder.getBindings(),
- nodeBuilder.updateNodes,
- nodeBuilder.updateBeforeNodes,
- nodeBuilder.updateAfterNodes,
- nodeBuilder.observer,
- nodeBuilder.transforms
- );
- }
- /**
- * Returns an environment node for the current configured
- * scene environment.
- *
- * @param {Scene} scene - The scene.
- * @return {Node} A node representing the current scene environment.
- */
- getEnvironmentNode( scene ) {
- this.updateEnvironment( scene );
- let environmentNode = null;
- if ( scene.environmentNode && scene.environmentNode.isNode ) {
- environmentNode = scene.environmentNode;
- } else {
- const sceneData = this.get( scene );
- if ( sceneData.environmentNode ) {
- environmentNode = sceneData.environmentNode;
- }
- }
- return environmentNode;
- }
- /**
- * Returns a background node for the current configured
- * scene background.
- *
- * @param {Scene} scene - The scene.
- * @return {Node} A node representing the current scene background.
- */
- getBackgroundNode( scene ) {
- this.updateBackground( scene );
- let backgroundNode = null;
- if ( scene.backgroundNode && scene.backgroundNode.isNode ) {
- backgroundNode = scene.backgroundNode;
- } else {
- const sceneData = this.get( scene );
- if ( sceneData.backgroundNode ) {
- backgroundNode = sceneData.backgroundNode;
- }
- }
- return backgroundNode;
- }
- /**
- * Returns a fog node for the current configured scene fog.
- *
- * @param {Scene} scene - The scene.
- * @return {Node} A node representing the current scene fog.
- */
- getFogNode( scene ) {
- this.updateFog( scene );
- return scene.fogNode || this.get( scene ).fogNode || null;
- }
- /**
- * Returns a cache key for the given scene and lights node.
- * This key is used by `RenderObject` as a part of the dynamic
- * cache key (a key that must be checked every time the render
- * objects is drawn).
- *
- * @param {Scene} scene - The scene.
- * @param {LightsNode} lightsNode - The lights node.
- * @return {number} The cache key.
- */
- getCacheKey( scene, lightsNode ) {
- _chainKeys$2[ 0 ] = scene;
- _chainKeys$2[ 1 ] = lightsNode;
- const callId = this.renderer.info.calls;
- const cacheKeyData = this.callHashCache.get( _chainKeys$2 ) || {};
- if ( cacheKeyData.callId !== callId ) {
- const environmentNode = this.getEnvironmentNode( scene );
- const fogNode = this.getFogNode( scene );
- if ( lightsNode ) _cacheKeyValues.push( lightsNode.getCacheKey( true ) );
- if ( environmentNode ) _cacheKeyValues.push( environmentNode.getCacheKey() );
- if ( fogNode ) _cacheKeyValues.push( fogNode.getCacheKey() );
- _cacheKeyValues.push( this.renderer.getOutputRenderTarget() && this.renderer.getOutputRenderTarget().multiview ? 1 : 0 );
- _cacheKeyValues.push( this.renderer.shadowMap.enabled ? 1 : 0 );
- _cacheKeyValues.push( this.renderer.shadowMap.type );
- cacheKeyData.callId = callId;
- cacheKeyData.cacheKey = hashArray( _cacheKeyValues );
- this.callHashCache.set( _chainKeys$2, cacheKeyData );
- _cacheKeyValues.length = 0;
- }
- _chainKeys$2.length = 0;
- return cacheKeyData.cacheKey;
- }
- /**
- * A boolean that indicates whether tone mapping should be enabled
- * or not.
- *
- * @type {boolean}
- */
- get isToneMappingState() {
- return this.renderer.getRenderTarget() ? false : true;
- }
- /**
- * If a scene background is configured, this method makes sure to
- * represent the background with a corresponding node-based implementation.
- *
- * @param {Scene} scene - The scene.
- */
- updateBackground( scene ) {
- const sceneData = this.get( scene );
- const background = scene.background;
- if ( background ) {
- const forceUpdate = ( scene.backgroundBlurriness === 0 && sceneData.backgroundBlurriness > 0 ) || ( scene.backgroundBlurriness > 0 && sceneData.backgroundBlurriness === 0 );
- if ( sceneData.background !== background || forceUpdate ) {
- const backgroundNode = this.getCacheNode( 'background', background, () => {
- if ( background.isCubeTexture === true || ( background.mapping === EquirectangularReflectionMapping || background.mapping === EquirectangularRefractionMapping || background.mapping === CubeUVReflectionMapping ) ) {
- if ( scene.backgroundBlurriness > 0 || background.mapping === CubeUVReflectionMapping ) {
- return pmremTexture( background );
- } else {
- let envMap;
- if ( background.isCubeTexture === true ) {
- envMap = cubeTexture( background );
- } else {
- envMap = texture( background );
- }
- return cubeMapNode( envMap );
- }
- } else if ( background.isTexture === true ) {
- return texture( background, screenUV.flipY() ).setUpdateMatrix( true );
- } else if ( background.isColor !== true ) {
- error( 'WebGPUNodes: Unsupported background configuration.', background );
- }
- }, forceUpdate );
- sceneData.backgroundNode = backgroundNode;
- sceneData.background = background;
- sceneData.backgroundBlurriness = scene.backgroundBlurriness;
- }
- } else if ( sceneData.backgroundNode ) {
- delete sceneData.backgroundNode;
- delete sceneData.background;
- }
- }
- /**
- * This method is part of the caching of nodes which are used to represents the
- * scene's background, fog or environment.
- *
- * @param {string} type - The type of object to cache.
- * @param {Object} object - The object.
- * @param {Function} callback - A callback that produces a node representation for the given object.
- * @param {boolean} [forceUpdate=false] - Whether an update should be enforced or not.
- * @return {Node} The node representation.
- */
- getCacheNode( type, object, callback, forceUpdate = false ) {
- const nodeCache = this.cacheLib[ type ] || ( this.cacheLib[ type ] = new WeakMap() );
- let node = nodeCache.get( object );
- if ( node === undefined || forceUpdate ) {
- node = callback();
- nodeCache.set( object, node );
- }
- return node;
- }
- /**
- * If a scene fog is configured, this method makes sure to
- * represent the fog with a corresponding node-based implementation.
- *
- * @param {Scene} scene - The scene.
- */
- updateFog( scene ) {
- const sceneData = this.get( scene );
- const sceneFog = scene.fog;
- if ( sceneFog ) {
- if ( sceneData.fog !== sceneFog ) {
- const fogNode = this.getCacheNode( 'fog', sceneFog, () => {
- if ( sceneFog.isFogExp2 ) {
- const color = reference( 'color', 'color', sceneFog ).setGroup( renderGroup );
- const density = reference( 'density', 'float', sceneFog ).setGroup( renderGroup );
- return fog( color, densityFogFactor( density ) );
- } else if ( sceneFog.isFog ) {
- const color = reference( 'color', 'color', sceneFog ).setGroup( renderGroup );
- const near = reference( 'near', 'float', sceneFog ).setGroup( renderGroup );
- const far = reference( 'far', 'float', sceneFog ).setGroup( renderGroup );
- return fog( color, rangeFogFactor( near, far ) );
- } else {
- error( 'Renderer: Unsupported fog configuration.', sceneFog );
- }
- } );
- sceneData.fogNode = fogNode;
- sceneData.fog = sceneFog;
- }
- } else {
- delete sceneData.fogNode;
- delete sceneData.fog;
- }
- }
- /**
- * If a scene environment is configured, this method makes sure to
- * represent the environment with a corresponding node-based implementation.
- *
- * @param {Scene} scene - The scene.
- */
- updateEnvironment( scene ) {
- const sceneData = this.get( scene );
- const environment = scene.environment;
- if ( environment ) {
- if ( sceneData.environment !== environment ) {
- const environmentNode = this.getCacheNode( 'environment', environment, () => {
- if ( environment.isCubeTexture === true ) {
- return cubeTexture( environment );
- } else if ( environment.isTexture === true ) {
- return texture( environment );
- } else {
- error( 'Nodes: Unsupported environment configuration.', environment );
- }
- } );
- sceneData.environmentNode = environmentNode;
- sceneData.environment = environment;
- }
- } else if ( sceneData.environmentNode ) {
- delete sceneData.environmentNode;
- delete sceneData.environment;
- }
- }
- getNodeFrame( renderer = this.renderer, scene = null, object = null, camera = null, material = null ) {
- const nodeFrame = this.nodeFrame;
- nodeFrame.renderer = renderer;
- nodeFrame.scene = scene;
- nodeFrame.object = object;
- nodeFrame.camera = camera;
- nodeFrame.material = material;
- return nodeFrame;
- }
- getNodeFrameForRender( renderObject ) {
- return this.getNodeFrame( renderObject.renderer, renderObject.scene, renderObject.object, renderObject.camera, renderObject.material );
- }
- /**
- * Returns the current output cache key.
- *
- * @return {string} The output cache key.
- */
- getOutputCacheKey() {
- const renderer = this.renderer;
- return renderer.toneMapping + ',' + renderer.currentColorSpace + ',' + renderer.xr.isPresenting;
- }
- /**
- * Checks if the output configuration (tone mapping and color space) for
- * the given target has changed.
- *
- * @param {Texture} outputTarget - The output target.
- * @return {boolean} Whether the output configuration has changed or not.
- */
- hasOutputChange( outputTarget ) {
- const cacheKey = _outputNodeMap.get( outputTarget );
- return cacheKey !== this.getOutputCacheKey();
- }
- /**
- * Returns a node that represents the output configuration (tone mapping and
- * color space) for the current target.
- *
- * @param {Texture} outputTarget - The output target.
- * @return {Node} The output node.
- */
- getOutputNode( outputTarget ) {
- const renderer = this.renderer;
- const cacheKey = this.getOutputCacheKey();
- const output = outputTarget.isArrayTexture ?
- texture3D( outputTarget, vec3( screenUV, builtin( 'gl_ViewID_OVR' ) ) ).renderOutput( renderer.toneMapping, renderer.currentColorSpace ) :
- texture( outputTarget, screenUV ).renderOutput( renderer.toneMapping, renderer.currentColorSpace );
- _outputNodeMap.set( outputTarget, cacheKey );
- return output;
- }
- /**
- * Triggers the call of `updateBefore()` methods
- * for all nodes of the given render object.
- *
- * @param {RenderObject} renderObject - The render object.
- */
- updateBefore( renderObject ) {
- const nodeBuilder = renderObject.getNodeBuilderState();
- for ( const node of nodeBuilder.updateBeforeNodes ) {
- // update frame state for each node
- this.getNodeFrameForRender( renderObject ).updateBeforeNode( node );
- }
- }
- /**
- * Triggers the call of `updateAfter()` methods
- * for all nodes of the given render object.
- *
- * @param {RenderObject} renderObject - The render object.
- */
- updateAfter( renderObject ) {
- const nodeBuilder = renderObject.getNodeBuilderState();
- for ( const node of nodeBuilder.updateAfterNodes ) {
- // update frame state for each node
- this.getNodeFrameForRender( renderObject ).updateAfterNode( node );
- }
- }
- /**
- * Triggers the call of `update()` methods
- * for all nodes of the given compute node.
- *
- * @param {Node} computeNode - The compute node.
- */
- updateForCompute( computeNode ) {
- const nodeFrame = this.getNodeFrame();
- const nodeBuilder = this.getForCompute( computeNode );
- for ( const node of nodeBuilder.updateNodes ) {
- nodeFrame.updateNode( node );
- }
- }
- /**
- * Triggers the call of `update()` methods
- * for all nodes of the given compute node.
- *
- * @param {RenderObject} renderObject - The render object.
- */
- updateForRender( renderObject ) {
- const nodeFrame = this.getNodeFrameForRender( renderObject );
- const nodeBuilder = renderObject.getNodeBuilderState();
- for ( const node of nodeBuilder.updateNodes ) {
- nodeFrame.updateNode( node );
- }
- }
- /**
- * Returns `true` if the given render object requires a refresh.
- *
- * @param {RenderObject} renderObject - The render object.
- * @return {boolean} Whether the given render object requires a refresh or not.
- */
- needsRefresh( renderObject ) {
- const nodeFrame = this.getNodeFrameForRender( renderObject );
- const monitor = renderObject.getMonitor();
- return monitor.needsRefresh( renderObject, nodeFrame );
- }
- /**
- * Frees the internal resources.
- */
- dispose() {
- super.dispose();
- this.nodeFrame = new NodeFrame();
- this.nodeBuilderCache = new Map();
- this.cacheLib = {};
- }
- }
- const _plane = /*@__PURE__*/ new Plane();
- /**
- * Represents the state that is used to perform clipping via clipping planes.
- * There is a default clipping context for each render context. When the
- * scene holds instances of `ClippingGroup`, there will be a context for each
- * group.
- *
- * @private
- */
- class ClippingContext {
- /**
- * Constructs a new clipping context.
- *
- * @param {?ClippingContext} [parentContext=null] - A reference to the parent clipping context.
- */
- constructor( parentContext = null ) {
- /**
- * The clipping context's version.
- *
- * @type {number}
- * @readonly
- */
- this.version = 0;
- /**
- * Whether the intersection of the clipping planes is used to clip objects, rather than their union.
- *
- * @type {?boolean}
- * @default null
- */
- this.clipIntersection = null;
- /**
- * The clipping context's cache key.
- *
- * @type {string}
- */
- this.cacheKey = '';
- /**
- * Whether the shadow pass is active or not.
- *
- * @type {boolean}
- * @default false
- */
- this.shadowPass = false;
- /**
- * The view normal matrix.
- *
- * @type {Matrix3}
- */
- this.viewNormalMatrix = new Matrix3();
- /**
- * Internal cache for maintaining clipping contexts.
- *
- * @type {WeakMap<ClippingGroup,ClippingContext>}
- */
- this.clippingGroupContexts = new WeakMap();
- /**
- * The intersection planes.
- *
- * @type {Array<Vector4>}
- */
- this.intersectionPlanes = [];
- /**
- * The intersection planes.
- *
- * @type {Array<Vector4>}
- */
- this.unionPlanes = [];
- /**
- * The version of the clipping context's parent context.
- *
- * @type {?number}
- * @readonly
- */
- this.parentVersion = null;
- if ( parentContext !== null ) {
- this.viewNormalMatrix = parentContext.viewNormalMatrix;
- this.clippingGroupContexts = parentContext.clippingGroupContexts;
- this.shadowPass = parentContext.shadowPass;
- this.viewMatrix = parentContext.viewMatrix;
- }
- }
- /**
- * Projects the given source clipping planes and writes the result into the
- * destination array.
- *
- * @param {Array<Plane>} source - The source clipping planes.
- * @param {Array<Vector4>} destination - The destination.
- * @param {number} offset - The offset.
- */
- projectPlanes( source, destination, offset ) {
- const l = source.length;
- for ( let i = 0; i < l; i ++ ) {
- _plane.copy( source[ i ] ).applyMatrix4( this.viewMatrix, this.viewNormalMatrix );
- const v = destination[ offset + i ];
- const normal = _plane.normal;
- v.x = - normal.x;
- v.y = - normal.y;
- v.z = - normal.z;
- v.w = _plane.constant;
- }
- }
- /**
- * Updates the root clipping context of a scene.
- *
- * @param {Scene} scene - The scene.
- * @param {Camera} camera - The camera that is used to render the scene.
- */
- updateGlobal( scene, camera ) {
- this.shadowPass = ( scene.overrideMaterial !== null && scene.overrideMaterial.isShadowPassMaterial );
- this.viewMatrix = camera.matrixWorldInverse;
- this.viewNormalMatrix.getNormalMatrix( this.viewMatrix );
- }
- /**
- * Updates the clipping context.
- *
- * @param {ClippingContext} parentContext - The parent context.
- * @param {ClippingGroup} clippingGroup - The clipping group this context belongs to.
- */
- update( parentContext, clippingGroup ) {
- let update = false;
- if ( parentContext.version !== this.parentVersion ) {
- this.intersectionPlanes = Array.from( parentContext.intersectionPlanes );
- this.unionPlanes = Array.from( parentContext.unionPlanes );
- this.parentVersion = parentContext.version;
- }
- if ( this.clipIntersection !== clippingGroup.clipIntersection ) {
- this.clipIntersection = clippingGroup.clipIntersection;
- if ( this.clipIntersection ) {
- this.unionPlanes.length = parentContext.unionPlanes.length;
- } else {
- this.intersectionPlanes.length = parentContext.intersectionPlanes.length;
- }
- }
- const srcClippingPlanes = clippingGroup.clippingPlanes;
- const l = srcClippingPlanes.length;
- let dstClippingPlanes;
- let offset;
- if ( this.clipIntersection ) {
- dstClippingPlanes = this.intersectionPlanes;
- offset = parentContext.intersectionPlanes.length;
- } else {
- dstClippingPlanes = this.unionPlanes;
- offset = parentContext.unionPlanes.length;
- }
- if ( dstClippingPlanes.length !== offset + l ) {
- dstClippingPlanes.length = offset + l;
- for ( let i = 0; i < l; i ++ ) {
- dstClippingPlanes[ offset + i ] = new Vector4();
- }
- update = true;
- }
- this.projectPlanes( srcClippingPlanes, dstClippingPlanes, offset );
- if ( update ) {
- this.version ++;
- this.cacheKey = `${ this.intersectionPlanes.length }:${ this.unionPlanes.length }`;
- }
- }
- /**
- * Returns a clipping context for the given clipping group.
- *
- * @param {ClippingGroup} clippingGroup - The clipping group.
- * @return {ClippingContext} The clipping context.
- */
- getGroupContext( clippingGroup ) {
- if ( this.shadowPass && ! clippingGroup.clipShadows ) return this;
- let context = this.clippingGroupContexts.get( clippingGroup );
- if ( context === undefined ) {
- context = new ClippingContext( this );
- this.clippingGroupContexts.set( clippingGroup, context );
- }
- context.update( this, clippingGroup );
- return context;
- }
- /**
- * The count of union clipping planes.
- *
- * @type {number}
- * @readonly
- */
- get unionClippingCount() {
- return this.unionPlanes.length;
- }
- }
- /**
- * This module is used to represent render bundles inside the renderer
- * for further processing.
- *
- * @private
- */
- class RenderBundle {
- /**
- * Constructs a new bundle group.
- *
- * @param {BundleGroup} bundleGroup - The bundle group.
- * @param {Camera} camera - The camera the bundle group is rendered with.
- */
- constructor( bundleGroup, camera ) {
- this.bundleGroup = bundleGroup;
- this.camera = camera;
- }
- }
- const _chainKeys$1 = [];
- /**
- * This renderer module manages render bundles.
- *
- * @private
- */
- class RenderBundles {
- /**
- * Constructs a new render bundle management component.
- */
- constructor() {
- /**
- * A chain map for maintaining the render bundles.
- *
- * @type {ChainMap}
- */
- this.bundles = new ChainMap();
- }
- /**
- * Returns a render bundle for the given bundle group and camera.
- *
- * @param {BundleGroup} bundleGroup - The bundle group.
- * @param {Camera} camera - The camera the bundle group is rendered with.
- * @return {RenderBundle} The render bundle.
- */
- get( bundleGroup, camera ) {
- const bundles = this.bundles;
- _chainKeys$1[ 0 ] = bundleGroup;
- _chainKeys$1[ 1 ] = camera;
- let bundle = bundles.get( _chainKeys$1 );
- if ( bundle === undefined ) {
- bundle = new RenderBundle( bundleGroup, camera );
- bundles.set( _chainKeys$1, bundle );
- }
- _chainKeys$1.length = 0;
- return bundle;
- }
- /**
- * Frees all internal resources.
- */
- dispose() {
- this.bundles = new ChainMap();
- }
- }
- /**
- * The purpose of a node library is to assign node implementations
- * to existing library features. In `WebGPURenderer` lights, materials
- * which are not based on `NodeMaterial` as well as tone mapping techniques
- * are implemented with node-based modules.
- *
- * @private
- */
- class NodeLibrary {
- /**
- * Constructs a new node library.
- */
- constructor() {
- /**
- * A weak map that maps lights to light nodes.
- *
- * @type {WeakMap<Light.constructor,AnalyticLightNode.constructor>}
- */
- this.lightNodes = new WeakMap();
- /**
- * A map that maps materials to node materials.
- *
- * @type {Map<string,NodeMaterial.constructor>}
- */
- this.materialNodes = new Map();
- /**
- * A map that maps tone mapping techniques (constants)
- * to tone mapping node functions.
- *
- * @type {Map<number,Function>}
- */
- this.toneMappingNodes = new Map();
- }
- /**
- * Returns a matching node material instance for the given material object.
- *
- * This method also assigns/copies the properties of the given material object
- * to the node material. This is done to make sure the current material
- * configuration carries over to the node version.
- *
- * @param {Material} material - A material.
- * @return {NodeMaterial} The corresponding node material.
- */
- fromMaterial( material ) {
- if ( material.isNodeMaterial ) return material;
- let nodeMaterial = null;
- const nodeMaterialClass = this.getMaterialNodeClass( material.type );
- if ( nodeMaterialClass !== null ) {
- nodeMaterial = new nodeMaterialClass();
- for ( const key in material ) {
- nodeMaterial[ key ] = material[ key ];
- }
- }
- return nodeMaterial;
- }
- /**
- * Adds a tone mapping node function for a tone mapping technique (constant).
- *
- * @param {Function} toneMappingNode - The tone mapping node function.
- * @param {number} toneMapping - The tone mapping.
- */
- addToneMapping( toneMappingNode, toneMapping ) {
- this.addType( toneMappingNode, toneMapping, this.toneMappingNodes );
- }
- /**
- * Returns a tone mapping node function for a tone mapping technique (constant).
- *
- * @param {number} toneMapping - The tone mapping.
- * @return {?Function} The tone mapping node function. Returns `null` if no node function is found.
- */
- getToneMappingFunction( toneMapping ) {
- return this.toneMappingNodes.get( toneMapping ) || null;
- }
- /**
- * Returns a node material class definition for a material type.
- *
- * @param {string} materialType - The material type.
- * @return {?NodeMaterial.constructor} The node material class definition. Returns `null` if no node material is found.
- */
- getMaterialNodeClass( materialType ) {
- return this.materialNodes.get( materialType ) || null;
- }
- /**
- * Adds a node material class definition for a given material type.
- *
- * @param {NodeMaterial.constructor} materialNodeClass - The node material class definition.
- * @param {string} materialClassType - The material type.
- */
- addMaterial( materialNodeClass, materialClassType ) {
- this.addType( materialNodeClass, materialClassType, this.materialNodes );
- }
- /**
- * Returns a light node class definition for a light class definition.
- *
- * @param {Light.constructor} light - The light class definition.
- * @return {?AnalyticLightNode.constructor} The light node class definition. Returns `null` if no light node is found.
- */
- getLightNodeClass( light ) {
- return this.lightNodes.get( light ) || null;
- }
- /**
- * Adds a light node class definition for a given light class definition.
- *
- * @param {AnalyticLightNode.constructor} lightNodeClass - The light node class definition.
- * @param {Light.constructor} lightClass - The light class definition.
- */
- addLight( lightNodeClass, lightClass ) {
- this.addClass( lightNodeClass, lightClass, this.lightNodes );
- }
- /**
- * Adds a node class definition for the given type to the provided type library.
- *
- * @param {Node.constructor} nodeClass - The node class definition.
- * @param {number|string} type - The object type.
- * @param {Map<number|string,Node.constructor>} library - The type library.
- */
- addType( nodeClass, type, library ) {
- if ( library.has( type ) ) {
- warn( `Redefinition of node ${ type }` );
- return;
- }
- if ( typeof nodeClass !== 'function' ) throw new Error( `Node class ${ nodeClass.name } is not a class.` );
- if ( typeof type === 'function' || typeof type === 'object' ) throw new Error( `Base class ${ type } is not a class.` );
- library.set( type, nodeClass );
- }
- /**
- * Adds a node class definition for the given class definition to the provided type library.
- *
- * @param {Node.constructor} nodeClass - The node class definition.
- * @param {Node.constructor} baseClass - The class definition.
- * @param {WeakMap<Node.constructor, Node.constructor>} library - The type library.
- */
- addClass( nodeClass, baseClass, library ) {
- if ( library.has( baseClass ) ) {
- warn( `Redefinition of node ${ baseClass.name }` );
- return;
- }
- if ( typeof nodeClass !== 'function' ) throw new Error( `Node class ${ nodeClass.name } is not a class.` );
- if ( typeof baseClass !== 'function' ) throw new Error( `Base class ${ baseClass.name } is not a class.` );
- library.set( baseClass, nodeClass );
- }
- }
- const _defaultLights = /*@__PURE__*/ new LightsNode();
- const _chainKeys = [];
- /**
- * This renderer module manages the lights nodes which are unique
- * per scene and camera combination.
- *
- * The lights node itself is later configured in the render list
- * with the actual lights from the scene.
- *
- * @private
- * @augments ChainMap
- */
- class Lighting extends ChainMap {
- /**
- * Constructs a lighting management component.
- */
- constructor() {
- super();
- }
- /**
- * Creates a new lights node for the given array of lights.
- *
- * @param {Array<Light>} lights - The render object.
- * @return {LightsNode} The lights node.
- */
- createNode( lights = [] ) {
- return new LightsNode().setLights( lights );
- }
- /**
- * Returns a lights node for the given scene and camera.
- *
- * @param {Scene} scene - The scene.
- * @param {Camera} camera - The camera.
- * @return {LightsNode} The lights node.
- */
- getNode( scene, camera ) {
- // ignore post-processing
- if ( scene.isQuadMesh ) return _defaultLights;
- _chainKeys[ 0 ] = scene;
- _chainKeys[ 1 ] = camera;
- let node = this.get( _chainKeys );
- if ( node === undefined ) {
- node = this.createNode();
- this.set( _chainKeys, node );
- }
- _chainKeys.length = 0;
- return node;
- }
- }
- /**
- * A special type of render target that is used when rendering
- * with the WebXR Device API.
- *
- * @private
- * @augments RenderTarget
- */
- class XRRenderTarget extends RenderTarget {
- /**
- * Constructs a new XR render target.
- *
- * @param {number} [width=1] - The width of the render target.
- * @param {number} [height=1] - The height of the render target.
- * @param {Object} [options={}] - The configuration options.
- */
- constructor( width = 1, height = 1, options = {} ) {
- super( width, height, options );
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isXRRenderTarget = true;
- /**
- * Whether the attachments of the render target
- * are defined by external textures. This flag is
- * set to `true` when using the WebXR Layers API.
- *
- * @private
- * @type {boolean}
- * @default false
- */
- this._hasExternalTextures = false;
- /**
- * Whether a depth buffer should automatically be allocated
- * for this XR render target or not.
- *
- * Allocating a depth buffer is the default behavior of XR render
- * targets. However, when using the WebXR Layers API, this flag
- * must be set to `false` when the `ignoreDepthValues` property of
- * the projection layers evaluates to `false`.
- *
- * Reference: {@link https://www.w3.org/TR/webxrlayers-1/#dom-xrprojectionlayer-ignoredepthvalues}.
- *
- * @private
- * @type {boolean}
- * @default true
- */
- this._autoAllocateDepthBuffer = true;
- /**
- * Whether this render target is associated with a XRWebGLLayer.
- *
- * A XRWebGLLayer points to an opaque framebuffer. Basically,
- * this means that you don't have access to its bound color,
- * stencil and depth buffers. We need to handle this framebuffer
- * differently since its textures are always bound.
- *
- * @private
- * @type {boolean}
- * @default false
- * */
- this._isOpaqueFramebuffer = false;
- }
- copy( source ) {
- super.copy( source );
- this._hasExternalTextures = source._hasExternalTextures;
- this._autoAllocateDepthBuffer = source._autoAllocateDepthBuffer;
- this._isOpaqueFramebuffer = source._isOpaqueFramebuffer;
- return this;
- }
- }
- const _cameraLPos = /*@__PURE__*/ new Vector3();
- const _cameraRPos = /*@__PURE__*/ new Vector3();
- /**
- * The XR manager is built on top of the WebXR Device API to
- * manage XR sessions with `WebGPURenderer`.
- *
- * XR is currently only supported with a WebGL 2 backend.
- *
- * @augments EventDispatcher
- */
- class XRManager extends EventDispatcher {
- /**
- * Constructs a new XR manager.
- *
- * @param {Renderer} renderer - The renderer.
- * @param {boolean} [multiview=false] - Enables multiview if the device supports it.
- */
- constructor( renderer, multiview = false ) {
- super();
- /**
- * This flag globally enables XR rendering.
- *
- * @type {boolean}
- * @default false
- */
- this.enabled = false;
- /**
- * Whether the XR device is currently presenting or not.
- *
- * @type {boolean}
- * @default false
- * @readonly
- */
- this.isPresenting = false;
- /**
- * Whether the XR camera should automatically be updated or not.
- *
- * @type {boolean}
- * @default true
- */
- this.cameraAutoUpdate = true;
- /**
- * The renderer.
- *
- * @private
- * @type {Renderer}
- */
- this._renderer = renderer;
- // camera
- /**
- * Represents the camera for the left eye.
- *
- * @private
- * @type {PerspectiveCamera}
- */
- this._cameraL = new PerspectiveCamera();
- this._cameraL.viewport = new Vector4();
- /**
- * Represents the camera for the right eye.
- *
- * @private
- * @type {PerspectiveCamera}
- */
- this._cameraR = new PerspectiveCamera();
- this._cameraR.viewport = new Vector4();
- /**
- * A list of cameras used for rendering the XR views.
- *
- * @private
- * @type {Array<Camera>}
- */
- this._cameras = [ this._cameraL, this._cameraR ];
- /**
- * The main XR camera.
- *
- * @private
- * @type {ArrayCamera}
- */
- this._cameraXR = new ArrayCamera();
- /**
- * The current near value of the XR camera.
- *
- * @private
- * @type {?number}
- * @default null
- */
- this._currentDepthNear = null;
- /**
- * The current far value of the XR camera.
- *
- * @private
- * @type {?number}
- * @default null
- */
- this._currentDepthFar = null;
- /**
- * A list of WebXR controllers requested by the application.
- *
- * @private
- * @type {Array<WebXRController>}
- */
- this._controllers = [];
- /**
- * A list of XR input source. Each input source belongs to
- * an instance of WebXRController.
- *
- * @private
- * @type {Array<XRInputSource?>}
- */
- this._controllerInputSources = [];
- /**
- * The XR render target that represents the rendering destination
- * during an active XR session.
- *
- * @private
- * @type {?RenderTarget}
- * @default null
- */
- this._xrRenderTarget = null;
- /**
- * An array holding all the non-projection layers
- *
- * @private
- * @type {Array<Object>}
- * @default []
- */
- this._layers = [];
- /**
- * Whether the XR session uses layers.
- *
- * @private
- * @type {boolean}
- * @default false
- */
- this._sessionUsesLayers = false;
- /**
- * Whether the device supports binding gl objects.
- *
- * @private
- * @type {boolean}
- * @readonly
- */
- this._supportsGlBinding = typeof XRWebGLBinding !== 'undefined';
- this._frameBufferTargets = null;
- /**
- * Helper function to create native WebXR Layer.
- *
- * @private
- * @type {Function}
- */
- this._createXRLayer = createXRLayer.bind( this );
- /**
- * The current WebGL context.
- *
- * @private
- * @type {?WebGL2RenderingContext}
- * @default null
- */
- this._gl = null;
- /**
- * The current animation context.
- *
- * @private
- * @type {?Window}
- * @default null
- */
- this._currentAnimationContext = null;
- /**
- * The current animation loop.
- *
- * @private
- * @type {?Function}
- * @default null
- */
- this._currentAnimationLoop = null;
- /**
- * The current pixel ratio.
- *
- * @private
- * @type {?number}
- * @default null
- */
- this._currentPixelRatio = null;
- /**
- * The current size of the renderer's canvas
- * in logical pixel unit.
- *
- * @private
- * @type {Vector2}
- */
- this._currentSize = new Vector2();
- /**
- * The default event listener for handling events inside a XR session.
- *
- * @private
- * @type {Function}
- */
- this._onSessionEvent = onSessionEvent.bind( this );
- /**
- * The event listener for handling the end of a XR session.
- *
- * @private
- * @type {Function}
- */
- this._onSessionEnd = onSessionEnd.bind( this );
- /**
- * The event listener for handling the `inputsourceschange` event.
- *
- * @private
- * @type {Function}
- */
- this._onInputSourcesChange = onInputSourcesChange.bind( this );
- /**
- * The animation loop which is used as a replacement for the default
- * animation loop of the application. It is only used when a XR session
- * is active.
- *
- * @private
- * @type {Function}
- */
- this._onAnimationFrame = onAnimationFrame.bind( this );
- /**
- * The current XR reference space.
- *
- * @private
- * @type {?XRReferenceSpace}
- * @default null
- */
- this._referenceSpace = null;
- /**
- * The current XR reference space type.
- *
- * @private
- * @type {XRReferenceSpaceType}
- * @default 'local-floor'
- */
- this._referenceSpaceType = 'local-floor';
- /**
- * A custom reference space defined by the application.
- *
- * @private
- * @type {?XRReferenceSpace}
- * @default null
- */
- this._customReferenceSpace = null;
- /**
- * The framebuffer scale factor.
- *
- * @private
- * @type {number}
- * @default 1
- */
- this._framebufferScaleFactor = 1;
- /**
- * The foveation factor.
- *
- * @private
- * @type {number}
- * @default 1
- */
- this._foveation = 1.0;
- /**
- * A reference to the current XR session.
- *
- * @private
- * @type {?XRSession}
- * @default null
- */
- this._session = null;
- /**
- * A reference to the current XR base layer.
- *
- * @private
- * @type {?XRWebGLLayer}
- * @default null
- */
- this._glBaseLayer = null;
- /**
- * A reference to the current XR binding.
- *
- * @private
- * @type {?XRWebGLBinding}
- * @default null
- */
- this._glBinding = null;
- /**
- * A reference to the current XR projection layer.
- *
- * @private
- * @type {?XRProjectionLayer}
- * @default null
- */
- this._glProjLayer = null;
- /**
- * A reference to the current XR frame.
- *
- * @private
- * @type {?XRFrame}
- * @default null
- */
- this._xrFrame = null;
- /**
- * Whether the browser supports the APIs necessary to use XRProjectionLayers.
- *
- * Note: this does not represent XRSession explicitly requesting
- * `'layers'` as a feature - see `_sessionUsesLayers` and #30112
- *
- * @private
- * @type {boolean}
- * @readonly
- */
- this._supportsLayers = ( this._supportsGlBinding && 'createProjectionLayer' in XRWebGLBinding.prototype ); // eslint-disable-line compat/compat
- /**
- * Whether the usage of multiview has been requested by the application or not.
- *
- * @private
- * @type {boolean}
- * @default false
- * @readonly
- */
- this._useMultiviewIfPossible = multiview;
- /**
- * Whether the usage of multiview is actually enabled. This flag only evaluates to `true`
- * if multiview has been requested by the application and the `OVR_multiview2` is available.
- *
- * @private
- * @type {boolean}
- * @readonly
- */
- this._useMultiview = false;
- }
- /**
- * Returns an instance of `THREE.Group` that represents the transformation
- * of a XR controller in target ray space. The requested controller is defined
- * by the given index.
- *
- * @param {number} index - The index of the XR controller.
- * @return {Group} A group that represents the controller's transformation.
- */
- getController( index ) {
- const controller = this._getController( index );
- return controller.getTargetRaySpace();
- }
- /**
- * Returns an instance of `THREE.Group` that represents the transformation
- * of a XR controller in grip space. The requested controller is defined
- * by the given index.
- *
- * @param {number} index - The index of the XR controller.
- * @return {Group} A group that represents the controller's transformation.
- */
- getControllerGrip( index ) {
- const controller = this._getController( index );
- return controller.getGripSpace();
- }
- /**
- * Returns an instance of `THREE.Group` that represents the transformation
- * of a XR controller in hand space. The requested controller is defined
- * by the given index.
- *
- * @param {number} index - The index of the XR controller.
- * @return {Group} A group that represents the controller's transformation.
- */
- getHand( index ) {
- const controller = this._getController( index );
- return controller.getHandSpace();
- }
- /**
- * Returns the foveation value.
- *
- * @return {number|undefined} The foveation value. Returns `undefined` if no base or projection layer is defined.
- */
- getFoveation() {
- if ( this._glProjLayer === null && this._glBaseLayer === null ) {
- return undefined;
- }
- return this._foveation;
- }
- /**
- * Sets the foveation value.
- *
- * @param {number} foveation - A number in the range `[0,1]` where `0` means no foveation (full resolution)
- * and `1` means maximum foveation (the edges render at lower resolution).
- */
- setFoveation( foveation ) {
- this._foveation = foveation;
- if ( this._glProjLayer !== null ) {
- this._glProjLayer.fixedFoveation = foveation;
- }
- if ( this._glBaseLayer !== null && this._glBaseLayer.fixedFoveation !== undefined ) {
- this._glBaseLayer.fixedFoveation = foveation;
- }
- }
- /**
- * Returns the framebuffer scale factor.
- *
- * @return {number} The framebuffer scale factor.
- */
- getFramebufferScaleFactor() {
- return this._framebufferScaleFactor;
- }
- /**
- * Sets the framebuffer scale factor.
- *
- * This method can not be used during a XR session.
- *
- * @param {number} factor - The framebuffer scale factor.
- */
- setFramebufferScaleFactor( factor ) {
- this._framebufferScaleFactor = factor;
- if ( this.isPresenting === true ) {
- warn( 'XRManager: Cannot change framebuffer scale while presenting.' );
- }
- }
- /**
- * Returns the reference space type.
- *
- * @return {XRReferenceSpaceType} The reference space type.
- */
- getReferenceSpaceType() {
- return this._referenceSpaceType;
- }
- /**
- * Sets the reference space type.
- *
- * This method can not be used during a XR session.
- *
- * @param {XRReferenceSpaceType} type - The reference space type.
- */
- setReferenceSpaceType( type ) {
- this._referenceSpaceType = type;
- if ( this.isPresenting === true ) {
- warn( 'XRManager: Cannot change reference space type while presenting.' );
- }
- }
- /**
- * Returns the XR reference space.
- *
- * @return {XRReferenceSpace} The XR reference space.
- */
- getReferenceSpace() {
- return this._customReferenceSpace || this._referenceSpace;
- }
- /**
- * Sets a custom XR reference space.
- *
- * @param {XRReferenceSpace} space - The XR reference space.
- */
- setReferenceSpace( space ) {
- this._customReferenceSpace = space;
- }
- /**
- * Returns the XR camera.
- *
- * @return {ArrayCamera} The XR camera.
- */
- getCamera() {
- return this._cameraXR;
- }
- /**
- * Returns the environment blend mode from the current XR session.
- *
- * @return {'opaque'|'additive'|'alpha-blend'|undefined} The environment blend mode. Returns `undefined` when used outside of a XR session.
- */
- getEnvironmentBlendMode() {
- if ( this._session !== null ) {
- return this._session.environmentBlendMode;
- }
- }
- /**
- * Returns the current XR binding.
- *
- * Creates a new binding if needed and the browser is
- * capable of doing so.
- *
- * @return {?XRWebGLBinding} The XR binding. Returns `null` if one cannot be created.
- */
- getBinding() {
- if ( this._glBinding === null && this._supportsGlBinding ) {
- this._glBinding = new XRWebGLBinding( this._session, this._gl );
- }
- return this._glBinding;
- }
- /**
- * Returns the current XR frame.
- *
- * @return {?XRFrame} The XR frame. Returns `null` when used outside a XR session.
- */
- getFrame() {
- return this._xrFrame;
- }
- /**
- * Returns `true` if the engine renders to a multiview target.
- *
- * @return {boolean} Whether the engine renders to a multiview render target or not.
- */
- useMultiview() {
- return this._useMultiview;
- }
- /**
- * This method can be used in XR applications to create a quadratic layer that presents a separate
- * rendered scene.
- *
- * @param {number} width - The width of the layer plane in world units.
- * @param {number} height - The height of the layer plane in world units.
- * @param {Vector3} translation - The position/translation of the layer plane in world units.
- * @param {Quaternion} quaternion - The orientation of the layer plane expressed as a quaternion.
- * @param {number} pixelwidth - The width of the layer's render target in pixels.
- * @param {number} pixelheight - The height of the layer's render target in pixels.
- * @param {Function} rendercall - A callback function that renders the layer. Similar to code in
- * the default animation loop, this method can be used to update/transform 3D object in the layer's scene.
- * @param {Object} [attributes={}] - Allows to configure the layer's render target.
- * @return {Mesh} A mesh representing the quadratic XR layer. This mesh should be added to the XR scene.
- */
- createQuadLayer( width, height, translation, quaternion, pixelwidth, pixelheight, rendercall, attributes = {} ) {
- const geometry = new PlaneGeometry( width, height );
- const renderTarget = new XRRenderTarget(
- pixelwidth,
- pixelheight,
- {
- format: RGBAFormat,
- type: UnsignedByteType,
- depthTexture: new DepthTexture(
- pixelwidth,
- pixelheight,
- attributes.stencil ? UnsignedInt248Type : UnsignedIntType,
- undefined,
- undefined,
- undefined,
- undefined,
- undefined,
- undefined,
- attributes.stencil ? DepthStencilFormat : DepthFormat
- ),
- stencilBuffer: attributes.stencil,
- resolveDepthBuffer: false,
- resolveStencilBuffer: false
- } );
- renderTarget._autoAllocateDepthBuffer = true;
- const material = new MeshBasicMaterial( { color: 0xffffff, side: FrontSide } );
- material.map = renderTarget.texture;
- material.map.offset.y = 1;
- material.map.repeat.y = -1;
- const plane = new Mesh( geometry, material );
- plane.position.copy( translation );
- plane.quaternion.copy( quaternion );
- const layer = {
- type: 'quad',
- width: width,
- height: height,
- translation: translation,
- quaternion: quaternion,
- pixelwidth: pixelwidth,
- pixelheight: pixelheight,
- plane: plane,
- material: material,
- rendercall: rendercall,
- renderTarget: renderTarget };
- this._layers.push( layer );
- if ( this._session !== null ) {
- layer.plane.material = new MeshBasicMaterial( { color: 0xffffff, side: FrontSide } );
- layer.plane.material.blending = CustomBlending;
- layer.plane.material.blendEquation = AddEquation;
- layer.plane.material.blendSrc = ZeroFactor;
- layer.plane.material.blendDst = ZeroFactor;
- layer.xrlayer = this._createXRLayer( layer );
- const xrlayers = this._session.renderState.layers;
- xrlayers.unshift( layer.xrlayer );
- this._session.updateRenderState( { layers: xrlayers } );
- } else {
- renderTarget.isXRRenderTarget = false;
- }
- return plane;
- }
- /**
- * This method can be used in XR applications to create a cylindrical layer that presents a separate
- * rendered scene.
- *
- * @param {number} radius - The radius of the cylinder in world units.
- * @param {number} centralAngle - The central angle of the cylinder in radians.
- * @param {number} aspectratio - The aspect ratio.
- * @param {Vector3} translation - The position/translation of the layer plane in world units.
- * @param {Quaternion} quaternion - The orientation of the layer plane expressed as a quaternion.
- * @param {number} pixelwidth - The width of the layer's render target in pixels.
- * @param {number} pixelheight - The height of the layer's render target in pixels.
- * @param {Function} rendercall - A callback function that renders the layer. Similar to code in
- * the default animation loop, this method can be used to update/transform 3D object in the layer's scene.
- * @param {Object} [attributes={}] - Allows to configure the layer's render target.
- * @return {Mesh} A mesh representing the cylindrical XR layer. This mesh should be added to the XR scene.
- */
- createCylinderLayer( radius, centralAngle, aspectratio, translation, quaternion, pixelwidth, pixelheight, rendercall, attributes = {} ) {
- const geometry = new CylinderGeometry( radius, radius, radius * centralAngle / aspectratio, 64, 64, true, Math.PI - centralAngle / 2, centralAngle );
- const renderTarget = new XRRenderTarget(
- pixelwidth,
- pixelheight,
- {
- format: RGBAFormat,
- type: UnsignedByteType,
- depthTexture: new DepthTexture(
- pixelwidth,
- pixelheight,
- attributes.stencil ? UnsignedInt248Type : UnsignedIntType,
- undefined,
- undefined,
- undefined,
- undefined,
- undefined,
- undefined,
- attributes.stencil ? DepthStencilFormat : DepthFormat
- ),
- stencilBuffer: attributes.stencil,
- resolveDepthBuffer: false,
- resolveStencilBuffer: false
- } );
- renderTarget._autoAllocateDepthBuffer = true;
- const material = new MeshBasicMaterial( { color: 0xffffff, side: BackSide } );
- material.map = renderTarget.texture;
- material.map.offset.y = 1;
- material.map.repeat.y = -1;
- const plane = new Mesh( geometry, material );
- plane.position.copy( translation );
- plane.quaternion.copy( quaternion );
- const layer = {
- type: 'cylinder',
- radius: radius,
- centralAngle: centralAngle,
- aspectratio: aspectratio,
- translation: translation,
- quaternion: quaternion,
- pixelwidth: pixelwidth,
- pixelheight: pixelheight,
- plane: plane,
- material: material,
- rendercall: rendercall,
- renderTarget: renderTarget };
- this._layers.push( layer );
- if ( this._session !== null ) {
- layer.plane.material = new MeshBasicMaterial( { color: 0xffffff, side: BackSide } );
- layer.plane.material.blending = CustomBlending;
- layer.plane.material.blendEquation = AddEquation;
- layer.plane.material.blendSrc = ZeroFactor;
- layer.plane.material.blendDst = ZeroFactor;
- layer.xrlayer = this._createXRLayer( layer );
- const xrlayers = this._session.renderState.layers;
- xrlayers.unshift( layer.xrlayer );
- this._session.updateRenderState( { layers: xrlayers } );
- } else {
- renderTarget.isXRRenderTarget = false;
- }
- return plane;
- }
- /**
- * Renders the XR layers that have been previously added to the scene.
- *
- * This method is usually called in your animation loop before rendering
- * the actual scene via `renderer.render( scene, camera );`.
- */
- renderLayers( ) {
- const translationObject = new Vector3();
- const quaternionObject = new Quaternion();
- const renderer = this._renderer;
- const wasPresenting = this.isPresenting;
- const rendererOutputTarget = renderer.getOutputRenderTarget();
- const rendererFramebufferTarget = renderer._frameBufferTarget;
- this.isPresenting = false;
- const rendererSize = new Vector2();
- renderer.getSize( rendererSize );
- const rendererQuad = renderer._quad;
- for ( const layer of this._layers ) {
- layer.renderTarget.isXRRenderTarget = this._session !== null;
- layer.renderTarget._hasExternalTextures = layer.renderTarget.isXRRenderTarget;
- if ( layer.renderTarget.isXRRenderTarget && this._sessionUsesLayers ) {
- layer.xrlayer.transform = new XRRigidTransform( layer.plane.getWorldPosition( translationObject ), layer.plane.getWorldQuaternion( quaternionObject ) );
- const glSubImage = this._glBinding.getSubImage( layer.xrlayer, this._xrFrame );
- renderer.backend.setXRRenderTargetTextures(
- layer.renderTarget,
- glSubImage.colorTexture,
- undefined );
- renderer._setXRLayerSize( layer.renderTarget.width, layer.renderTarget.height );
- renderer.setOutputRenderTarget( layer.renderTarget );
- renderer.setRenderTarget( null );
- renderer._frameBufferTarget = null;
- this._frameBufferTargets || ( this._frameBufferTargets = new WeakMap() );
- const { frameBufferTarget, quad } = this._frameBufferTargets.get( layer.renderTarget ) || { frameBufferTarget: null, quad: null };
- if ( ! frameBufferTarget ) {
- renderer._quad = new QuadMesh( new NodeMaterial() );
- this._frameBufferTargets.set( layer.renderTarget, { frameBufferTarget: renderer._getFrameBufferTarget(), quad: renderer._quad } );
- } else {
- renderer._frameBufferTarget = frameBufferTarget;
- renderer._quad = quad;
- }
- layer.rendercall();
- renderer._frameBufferTarget = null;
- } else {
- renderer.setRenderTarget( layer.renderTarget );
- layer.rendercall();
- }
- }
- renderer.setRenderTarget( null );
- renderer.setOutputRenderTarget( rendererOutputTarget );
- renderer._frameBufferTarget = rendererFramebufferTarget;
- renderer._setXRLayerSize( rendererSize.x, rendererSize.y );
- renderer._quad = rendererQuad;
- this.isPresenting = wasPresenting;
- }
- /**
- * Returns the current XR session.
- *
- * @return {?XRSession} The XR session. Returns `null` when used outside a XR session.
- */
- getSession() {
- return this._session;
- }
- /**
- * After a XR session has been requested usually with one of the `*Button` modules, it
- * is injected into the renderer with this method. This method triggers the start of
- * the actual XR rendering.
- *
- * @async
- * @param {XRSession} session - The XR session to set.
- * @return {Promise} A Promise that resolves when the session has been set.
- */
- async setSession( session ) {
- const renderer = this._renderer;
- const backend = renderer.backend;
- this._gl = renderer.getContext();
- const gl = this._gl;
- const attributes = gl.getContextAttributes();
- this._session = session;
- if ( session !== null ) {
- if ( backend.isWebGPUBackend === true ) throw new Error( 'THREE.XRManager: XR is currently not supported with a WebGPU backend. Use WebGL by passing "{ forceWebGL: true }" to the constructor of the renderer.' );
- session.addEventListener( 'select', this._onSessionEvent );
- session.addEventListener( 'selectstart', this._onSessionEvent );
- session.addEventListener( 'selectend', this._onSessionEvent );
- session.addEventListener( 'squeeze', this._onSessionEvent );
- session.addEventListener( 'squeezestart', this._onSessionEvent );
- session.addEventListener( 'squeezeend', this._onSessionEvent );
- session.addEventListener( 'end', this._onSessionEnd );
- session.addEventListener( 'inputsourceschange', this._onInputSourcesChange );
- await backend.makeXRCompatible();
- this._currentPixelRatio = renderer.getPixelRatio();
- renderer.getSize( this._currentSize );
- this._currentAnimationContext = renderer._animation.getContext();
- this._currentAnimationLoop = renderer._animation.getAnimationLoop();
- renderer._animation.stop();
- //
- if ( this._supportsLayers === true ) {
- // default path using XRProjectionLayer
- let depthFormat = null;
- let depthType = null;
- let glDepthFormat = null;
- if ( renderer.depth ) {
- glDepthFormat = renderer.stencil ? gl.DEPTH24_STENCIL8 : gl.DEPTH_COMPONENT24;
- depthFormat = renderer.stencil ? DepthStencilFormat : DepthFormat;
- depthType = renderer.stencil ? UnsignedInt248Type : UnsignedIntType;
- }
- const projectionlayerInit = {
- colorFormat: gl.RGBA8,
- depthFormat: glDepthFormat,
- scaleFactor: this._framebufferScaleFactor,
- clearOnAccess: false
- };
- if ( this._useMultiviewIfPossible && renderer.hasFeature( 'OVR_multiview2' ) ) {
- projectionlayerInit.textureType = 'texture-array';
- this._useMultiview = true;
- }
- this._glBinding = this.getBinding();
- const glProjLayer = this._glBinding.createProjectionLayer( projectionlayerInit );
- const layersArray = [ glProjLayer ];
- this._glProjLayer = glProjLayer;
- renderer.setPixelRatio( 1 );
- renderer._setXRLayerSize( glProjLayer.textureWidth, glProjLayer.textureHeight );
- const depth = this._useMultiview ? 2 : 1;
- const depthTexture = new DepthTexture( glProjLayer.textureWidth, glProjLayer.textureHeight, depthType, undefined, undefined, undefined, undefined, undefined, undefined, depthFormat, depth );
- this._xrRenderTarget = new XRRenderTarget(
- glProjLayer.textureWidth,
- glProjLayer.textureHeight,
- {
- format: RGBAFormat,
- type: UnsignedByteType,
- colorSpace: renderer.outputColorSpace,
- depthTexture: depthTexture,
- stencilBuffer: renderer.stencil,
- samples: attributes.antialias ? 4 : 0,
- resolveDepthBuffer: ( glProjLayer.ignoreDepthValues === false ),
- resolveStencilBuffer: ( glProjLayer.ignoreDepthValues === false ),
- depth: this._useMultiview ? 2 : 1,
- multiview: this._useMultiview
- } );
- this._xrRenderTarget._hasExternalTextures = true;
- this._xrRenderTarget.depth = this._useMultiview ? 2 : 1;
- this._sessionUsesLayers = session.enabledFeatures.includes( 'layers' );
- this._referenceSpace = await session.requestReferenceSpace( this.getReferenceSpaceType() );
- if ( this._sessionUsesLayers ) {
- // switch layers to native
- for ( const layer of this._layers ) {
- // change material so it "punches" out a hole to show the XR Layer.
- layer.plane.material = new MeshBasicMaterial( { color: 0xffffff, side: layer.type === 'cylinder' ? BackSide : FrontSide } );
- layer.plane.material.blending = CustomBlending;
- layer.plane.material.blendEquation = AddEquation;
- layer.plane.material.blendSrc = ZeroFactor;
- layer.plane.material.blendDst = ZeroFactor;
- layer.xrlayer = this._createXRLayer( layer );
- layersArray.unshift( layer.xrlayer );
- }
- }
- session.updateRenderState( { layers: layersArray } );
- } else {
- // fallback to XRWebGLLayer
- const layerInit = {
- antialias: renderer.currentSamples > 0,
- alpha: true,
- depth: renderer.depth,
- stencil: renderer.stencil,
- framebufferScaleFactor: this.getFramebufferScaleFactor()
- };
- const glBaseLayer = new XRWebGLLayer( session, gl, layerInit );
- this._glBaseLayer = glBaseLayer;
- session.updateRenderState( { baseLayer: glBaseLayer } );
- renderer.setPixelRatio( 1 );
- renderer._setXRLayerSize( glBaseLayer.framebufferWidth, glBaseLayer.framebufferHeight );
- this._xrRenderTarget = new XRRenderTarget(
- glBaseLayer.framebufferWidth,
- glBaseLayer.framebufferHeight,
- {
- format: RGBAFormat,
- type: UnsignedByteType,
- colorSpace: renderer.outputColorSpace,
- stencilBuffer: renderer.stencil,
- resolveDepthBuffer: ( glBaseLayer.ignoreDepthValues === false ),
- resolveStencilBuffer: ( glBaseLayer.ignoreDepthValues === false ),
- }
- );
- this._xrRenderTarget._isOpaqueFramebuffer = true;
- this._referenceSpace = await session.requestReferenceSpace( this.getReferenceSpaceType() );
- }
- //
- this.setFoveation( this.getFoveation() );
- renderer._animation.setAnimationLoop( this._onAnimationFrame );
- renderer._animation.setContext( session );
- renderer._animation.start();
- this.isPresenting = true;
- this.dispatchEvent( { type: 'sessionstart' } );
- }
- }
- /**
- * This method is called by the renderer per frame and updates the XR camera
- * and it sub cameras based on the given camera. The given camera is the "user"
- * camera created on application level and used for non-XR rendering.
- *
- * @param {PerspectiveCamera} camera - The camera.
- */
- updateCamera( camera ) {
- const session = this._session;
- if ( session === null ) return;
- const depthNear = camera.near;
- const depthFar = camera.far;
- const cameraXR = this._cameraXR;
- const cameraL = this._cameraL;
- const cameraR = this._cameraR;
- cameraXR.near = cameraR.near = cameraL.near = depthNear;
- cameraXR.far = cameraR.far = cameraL.far = depthFar;
- cameraXR.isMultiViewCamera = this._useMultiview;
- if ( this._currentDepthNear !== cameraXR.near || this._currentDepthFar !== cameraXR.far ) {
- // Note that the new renderState won't apply until the next frame. See #18320
- session.updateRenderState( {
- depthNear: cameraXR.near,
- depthFar: cameraXR.far
- } );
- this._currentDepthNear = cameraXR.near;
- this._currentDepthFar = cameraXR.far;
- }
- // inherit camera layers and enable eye layers (1 = left, 2 = right)
- cameraXR.layers.mask = camera.layers.mask | 0b110;
- cameraL.layers.mask = cameraXR.layers.mask & 0b011;
- cameraR.layers.mask = cameraXR.layers.mask & 0b101;
- const parent = camera.parent;
- const cameras = cameraXR.cameras;
- updateCamera( cameraXR, parent );
- for ( let i = 0; i < cameras.length; i ++ ) {
- updateCamera( cameras[ i ], parent );
- }
- // update projection matrix for proper view frustum culling
- if ( cameras.length === 2 ) {
- setProjectionFromUnion( cameraXR, cameraL, cameraR );
- } else {
- // assume single camera setup (AR)
- cameraXR.projectionMatrix.copy( cameraL.projectionMatrix );
- }
- // update user camera and its children
- updateUserCamera( camera, cameraXR, parent );
- }
- /**
- * Returns a WebXR controller for the given controller index.
- *
- * @private
- * @param {number} index - The controller index.
- * @return {WebXRController} The XR controller.
- */
- _getController( index ) {
- let controller = this._controllers[ index ];
- if ( controller === undefined ) {
- controller = new WebXRController();
- this._controllers[ index ] = controller;
- }
- return controller;
- }
- }
- /**
- * Assumes 2 cameras that are parallel and share an X-axis, and that
- * the cameras' projection and world matrices have already been set.
- * And that near and far planes are identical for both cameras.
- * Visualization of this technique: https://computergraphics.stackexchange.com/a/4765
- *
- * @param {ArrayCamera} camera - The camera to update.
- * @param {PerspectiveCamera} cameraL - The left camera.
- * @param {PerspectiveCamera} cameraR - The right camera.
- */
- function setProjectionFromUnion( camera, cameraL, cameraR ) {
- _cameraLPos.setFromMatrixPosition( cameraL.matrixWorld );
- _cameraRPos.setFromMatrixPosition( cameraR.matrixWorld );
- const ipd = _cameraLPos.distanceTo( _cameraRPos );
- const projL = cameraL.projectionMatrix.elements;
- const projR = cameraR.projectionMatrix.elements;
- // VR systems will have identical far and near planes, and
- // most likely identical top and bottom frustum extents.
- // Use the left camera for these values.
- const near = projL[ 14 ] / ( projL[ 10 ] - 1 );
- const far = projL[ 14 ] / ( projL[ 10 ] + 1 );
- const topFov = ( projL[ 9 ] + 1 ) / projL[ 5 ];
- const bottomFov = ( projL[ 9 ] - 1 ) / projL[ 5 ];
- const leftFov = ( projL[ 8 ] - 1 ) / projL[ 0 ];
- const rightFov = ( projR[ 8 ] + 1 ) / projR[ 0 ];
- const left = near * leftFov;
- const right = near * rightFov;
- // Calculate the new camera's position offset from the
- // left camera. xOffset should be roughly half `ipd`.
- const zOffset = ipd / ( - leftFov + rightFov );
- const xOffset = zOffset * - leftFov;
- // TODO: Better way to apply this offset?
- cameraL.matrixWorld.decompose( camera.position, camera.quaternion, camera.scale );
- camera.translateX( xOffset );
- camera.translateZ( zOffset );
- camera.matrixWorld.compose( camera.position, camera.quaternion, camera.scale );
- camera.matrixWorldInverse.copy( camera.matrixWorld ).invert();
- // Check if the projection uses an infinite far plane.
- if ( projL[ 10 ] === -1 ) {
- // Use the projection matrix from the left eye.
- // The camera offset is sufficient to include the view volumes
- // of both eyes (assuming symmetric projections).
- camera.projectionMatrix.copy( cameraL.projectionMatrix );
- camera.projectionMatrixInverse.copy( cameraL.projectionMatrixInverse );
- } else {
- // Find the union of the frustum values of the cameras and scale
- // the values so that the near plane's position does not change in world space,
- // although must now be relative to the new union camera.
- const near2 = near + zOffset;
- const far2 = far + zOffset;
- const left2 = left - xOffset;
- const right2 = right + ( ipd - xOffset );
- const top2 = topFov * far / far2 * near2;
- const bottom2 = bottomFov * far / far2 * near2;
- camera.projectionMatrix.makePerspective( left2, right2, top2, bottom2, near2, far2 );
- camera.projectionMatrixInverse.copy( camera.projectionMatrix ).invert();
- }
- }
- /**
- * Updates the world matrices for the given camera based on the parent 3D object.
- *
- * @inner
- * @param {Camera} camera - The camera to update.
- * @param {Object3D} parent - The parent 3D object.
- */
- function updateCamera( camera, parent ) {
- if ( parent === null ) {
- camera.matrixWorld.copy( camera.matrix );
- } else {
- camera.matrixWorld.multiplyMatrices( parent.matrixWorld, camera.matrix );
- }
- camera.matrixWorldInverse.copy( camera.matrixWorld ).invert();
- }
- /**
- * Updates the given camera with the transformation of the XR camera and parent object.
- *
- * @inner
- * @param {Camera} camera - The camera to update.
- * @param {ArrayCamera} cameraXR - The XR camera.
- * @param {Object3D} parent - The parent 3D object.
- */
- function updateUserCamera( camera, cameraXR, parent ) {
- if ( parent === null ) {
- camera.matrix.copy( cameraXR.matrixWorld );
- } else {
- camera.matrix.copy( parent.matrixWorld );
- camera.matrix.invert();
- camera.matrix.multiply( cameraXR.matrixWorld );
- }
- camera.matrix.decompose( camera.position, camera.quaternion, camera.scale );
- camera.updateMatrixWorld( true );
- camera.projectionMatrix.copy( cameraXR.projectionMatrix );
- camera.projectionMatrixInverse.copy( cameraXR.projectionMatrixInverse );
- if ( camera.isPerspectiveCamera ) {
- camera.fov = RAD2DEG * 2 * Math.atan( 1 / camera.projectionMatrix.elements[ 5 ] );
- camera.zoom = 1;
- }
- }
- function onSessionEvent( event ) {
- const controllerIndex = this._controllerInputSources.indexOf( event.inputSource );
- if ( controllerIndex === -1 ) {
- return;
- }
- const controller = this._controllers[ controllerIndex ];
- if ( controller !== undefined ) {
- const referenceSpace = this.getReferenceSpace();
- controller.update( event.inputSource, event.frame, referenceSpace );
- controller.dispatchEvent( { type: event.type, data: event.inputSource } );
- }
- }
- function onSessionEnd() {
- const session = this._session;
- const renderer = this._renderer;
- session.removeEventListener( 'select', this._onSessionEvent );
- session.removeEventListener( 'selectstart', this._onSessionEvent );
- session.removeEventListener( 'selectend', this._onSessionEvent );
- session.removeEventListener( 'squeeze', this._onSessionEvent );
- session.removeEventListener( 'squeezestart', this._onSessionEvent );
- session.removeEventListener( 'squeezeend', this._onSessionEvent );
- session.removeEventListener( 'end', this._onSessionEnd );
- session.removeEventListener( 'inputsourceschange', this._onInputSourcesChange );
- for ( let i = 0; i < this._controllers.length; i ++ ) {
- const inputSource = this._controllerInputSources[ i ];
- if ( inputSource === null ) continue;
- this._controllerInputSources[ i ] = null;
- this._controllers[ i ].disconnect( inputSource );
- }
- this._currentDepthNear = null;
- this._currentDepthFar = null;
- // restore framebuffer/rendering state
- renderer._resetXRState();
- this._session = null;
- this._xrRenderTarget = null;
- this._glBinding = null;
- this._glBaseLayer = null;
- this._glProjLayer = null;
- // switch layers back to emulated
- if ( this._sessionUsesLayers === true ) {
- for ( const layer of this._layers ) {
- // Recreate layer render target to reset state
- layer.renderTarget = new XRRenderTarget(
- layer.pixelwidth,
- layer.pixelheight,
- {
- format: RGBAFormat,
- type: UnsignedByteType,
- depthTexture: new DepthTexture(
- layer.pixelwidth,
- layer.pixelheight,
- layer.stencilBuffer ? UnsignedInt248Type : UnsignedIntType,
- undefined,
- undefined,
- undefined,
- undefined,
- undefined,
- undefined,
- layer.stencilBuffer ? DepthStencilFormat : DepthFormat
- ),
- stencilBuffer: layer.stencilBuffer,
- resolveDepthBuffer: false,
- resolveStencilBuffer: false
- } );
- layer.renderTarget.isXRRenderTarget = false;
- layer.plane.material = layer.material;
- layer.material.map = layer.renderTarget.texture;
- layer.material.map.offset.y = 1;
- layer.material.map.repeat.y = -1;
- delete layer.xrlayer;
- }
- }
- //
- this.isPresenting = false;
- this._useMultiview = false;
- renderer._animation.stop();
- renderer._animation.setAnimationLoop( this._currentAnimationLoop );
- renderer._animation.setContext( this._currentAnimationContext );
- renderer._animation.start();
- renderer.setPixelRatio( this._currentPixelRatio );
- renderer.setSize( this._currentSize.width, this._currentSize.height, false );
- this.dispatchEvent( { type: 'sessionend' } );
- }
- function onInputSourcesChange( event ) {
- const controllers = this._controllers;
- const controllerInputSources = this._controllerInputSources;
- // Notify disconnected
- for ( let i = 0; i < event.removed.length; i ++ ) {
- const inputSource = event.removed[ i ];
- const index = controllerInputSources.indexOf( inputSource );
- if ( index >= 0 ) {
- controllerInputSources[ index ] = null;
- controllers[ index ].disconnect( inputSource );
- }
- }
- // Notify connected
- for ( let i = 0; i < event.added.length; i ++ ) {
- const inputSource = event.added[ i ];
- let controllerIndex = controllerInputSources.indexOf( inputSource );
- if ( controllerIndex === -1 ) {
- // Assign input source a controller that currently has no input source
- for ( let i = 0; i < controllers.length; i ++ ) {
- if ( i >= controllerInputSources.length ) {
- controllerInputSources.push( inputSource );
- controllerIndex = i;
- break;
- } else if ( controllerInputSources[ i ] === null ) {
- controllerInputSources[ i ] = inputSource;
- controllerIndex = i;
- break;
- }
- }
- // If all controllers do currently receive input we ignore new ones
- if ( controllerIndex === -1 ) break;
- }
- const controller = controllers[ controllerIndex ];
- if ( controller ) {
- controller.connect( inputSource );
- }
- }
- }
- // Creation method for native WebXR layers
- function createXRLayer( layer ) {
- if ( layer.type === 'quad' ) {
- return this._glBinding.createQuadLayer( {
- transform: new XRRigidTransform( layer.translation, layer.quaternion ),
- width: layer.width / 2,
- height: layer.height / 2,
- space: this._referenceSpace,
- viewPixelWidth: layer.pixelwidth,
- viewPixelHeight: layer.pixelheight,
- clearOnAccess: false
- } );
- } else {
- return this._glBinding.createCylinderLayer( {
- transform: new XRRigidTransform( layer.translation, layer.quaternion ),
- radius: layer.radius,
- centralAngle: layer.centralAngle,
- aspectRatio: layer.aspectRatio,
- space: this._referenceSpace,
- viewPixelWidth: layer.pixelwidth,
- viewPixelHeight: layer.pixelheight,
- clearOnAccess: false
- } );
- }
- }
- // Animation Loop
- function onAnimationFrame( time, frame ) {
- if ( frame === undefined ) return;
- const cameraXR = this._cameraXR;
- const renderer = this._renderer;
- const backend = renderer.backend;
- const glBaseLayer = this._glBaseLayer;
- const referenceSpace = this.getReferenceSpace();
- const pose = frame.getViewerPose( referenceSpace );
- this._xrFrame = frame;
- if ( pose !== null ) {
- const views = pose.views;
- if ( this._glBaseLayer !== null ) {
- backend.setXRTarget( glBaseLayer.framebuffer );
- }
- let cameraXRNeedsUpdate = false;
- // check if it's necessary to rebuild cameraXR's camera list
- if ( views.length !== cameraXR.cameras.length ) {
- cameraXR.cameras.length = 0;
- cameraXRNeedsUpdate = true;
- }
- for ( let i = 0; i < views.length; i ++ ) {
- const view = views[ i ];
- let viewport;
- if ( this._supportsLayers === true ) {
- const glSubImage = this._glBinding.getViewSubImage( this._glProjLayer, view );
- viewport = glSubImage.viewport;
- // For side-by-side projection, we only produce a single texture for both eyes.
- if ( i === 0 ) {
- backend.setXRRenderTargetTextures(
- this._xrRenderTarget,
- glSubImage.colorTexture,
- ( this._glProjLayer.ignoreDepthValues && ! this._useMultiview ) ? undefined : glSubImage.depthStencilTexture
- );
- }
- } else {
- viewport = glBaseLayer.getViewport( view );
- }
- let camera = this._cameras[ i ];
- if ( camera === undefined ) {
- camera = new PerspectiveCamera();
- camera.layers.enable( i );
- camera.viewport = new Vector4();
- this._cameras[ i ] = camera;
- }
- camera.matrix.fromArray( view.transform.matrix );
- camera.matrix.decompose( camera.position, camera.quaternion, camera.scale );
- camera.projectionMatrix.fromArray( view.projectionMatrix );
- camera.projectionMatrixInverse.copy( camera.projectionMatrix ).invert();
- camera.viewport.set( viewport.x, viewport.y, viewport.width, viewport.height );
- if ( i === 0 ) {
- cameraXR.matrix.copy( camera.matrix );
- cameraXR.matrix.decompose( cameraXR.position, cameraXR.quaternion, cameraXR.scale );
- }
- if ( cameraXRNeedsUpdate === true ) {
- cameraXR.cameras.push( camera );
- }
- }
- renderer.setOutputRenderTarget( this._xrRenderTarget );
- }
- //
- for ( let i = 0; i < this._controllers.length; i ++ ) {
- const inputSource = this._controllerInputSources[ i ];
- const controller = this._controllers[ i ];
- if ( inputSource !== null && controller !== undefined ) {
- controller.update( inputSource, frame, referenceSpace );
- }
- }
- if ( this._currentAnimationLoop ) this._currentAnimationLoop( time, frame );
- if ( frame.detectedPlanes ) {
- this.dispatchEvent( { type: 'planesdetected', data: frame } );
- }
- this._xrFrame = null;
- }
- /**
- * CanvasTarget is a class that represents the final output destination of the renderer.
- *
- * @augments EventDispatcher
- */
- class CanvasTarget extends EventDispatcher {
- /**
- * Constructs a new CanvasTarget.
- *
- * @param {HTMLCanvasElement|OffscreenCanvas} domElement - The canvas element to render to.
- */
- constructor( domElement ) {
- super();
- /**
- * A reference to the canvas element the renderer is drawing to.
- * This value of this property will automatically be created by
- * the renderer.
- *
- * @type {HTMLCanvasElement|OffscreenCanvas}
- */
- this.domElement = domElement;
- /**
- * The renderer's pixel ratio.
- *
- * @private
- * @type {number}
- * @default 1
- */
- this._pixelRatio = 1;
- /**
- * The width of the renderer's default framebuffer in logical pixel unit.
- *
- * @private
- * @type {number}
- */
- this._width = this.domElement.width;
- /**
- * The height of the renderer's default framebuffer in logical pixel unit.
- *
- * @private
- * @type {number}
- */
- this._height = this.domElement.height;
- /**
- * The viewport of the renderer in logical pixel unit.
- *
- * @private
- * @type {Vector4}
- */
- this._viewport = new Vector4( 0, 0, this._width, this._height );
- /**
- * The scissor rectangle of the renderer in logical pixel unit.
- *
- * @private
- * @type {Vector4}
- */
- this._scissor = new Vector4( 0, 0, this._width, this._height );
- /**
- * Whether the scissor test should be enabled or not.
- *
- * @private
- * @type {boolean}
- */
- this._scissorTest = false;
- /**
- * The color texture of the default framebuffer.
- *
- * @type {FramebufferTexture}
- */
- this.colorTexture = new FramebufferTexture();
- /**
- * The depth texture of the default framebuffer.
- *
- * @type {DepthTexture}
- */
- this.depthTexture = new DepthTexture();
- }
- /**
- * Returns the pixel ratio.
- *
- * @return {number} The pixel ratio.
- */
- getPixelRatio() {
- return this._pixelRatio;
- }
- /**
- * Returns the drawing buffer size in physical pixels. This method honors the pixel ratio.
- *
- * @param {Vector2} target - The method writes the result in this target object.
- * @return {Vector2} The drawing buffer size.
- */
- getDrawingBufferSize( target ) {
- return target.set( this._width * this._pixelRatio, this._height * this._pixelRatio ).floor();
- }
- /**
- * Returns the renderer's size in logical pixels. This method does not honor the pixel ratio.
- *
- * @param {Vector2} target - The method writes the result in this target object.
- * @return {Vector2} The renderer's size in logical pixels.
- */
- getSize( target ) {
- return target.set( this._width, this._height );
- }
- /**
- * Sets the given pixel ratio and resizes the canvas if necessary.
- *
- * @param {number} [value=1] - The pixel ratio.
- */
- setPixelRatio( value = 1 ) {
- if ( this._pixelRatio === value ) return;
- this._pixelRatio = value;
- this.setSize( this._width, this._height, false );
- }
- /**
- * This method allows to define the drawing buffer size by specifying
- * width, height and pixel ratio all at once. The size of the drawing
- * buffer is computed with this formula:
- * ```js
- * size.x = width * pixelRatio;
- * size.y = height * pixelRatio;
- * ```
- *
- * @param {number} width - The width in logical pixels.
- * @param {number} height - The height in logical pixels.
- * @param {number} pixelRatio - The pixel ratio.
- */
- setDrawingBufferSize( width, height, pixelRatio ) {
- // Renderer can't be resized while presenting in XR.
- if ( this.xr && this.xr.isPresenting ) return;
- this._width = width;
- this._height = height;
- this._pixelRatio = pixelRatio;
- this.domElement.width = Math.floor( width * pixelRatio );
- this.domElement.height = Math.floor( height * pixelRatio );
- this.setViewport( 0, 0, width, height );
- this._dispatchResize();
- }
- /**
- * Sets the size of the renderer.
- *
- * @param {number} width - The width in logical pixels.
- * @param {number} height - The height in logical pixels.
- * @param {boolean} [updateStyle=true] - Whether to update the `style` attribute of the canvas or not.
- */
- setSize( width, height, updateStyle = true ) {
- // Renderer can't be resized while presenting in XR.
- if ( this.xr && this.xr.isPresenting ) return;
- this._width = width;
- this._height = height;
- this.domElement.width = Math.floor( width * this._pixelRatio );
- this.domElement.height = Math.floor( height * this._pixelRatio );
- if ( updateStyle === true ) {
- this.domElement.style.width = width + 'px';
- this.domElement.style.height = height + 'px';
- }
- this.setViewport( 0, 0, width, height );
- this._dispatchResize();
- }
- /**
- * Returns the scissor rectangle.
- *
- * @param {Vector4} target - The method writes the result in this target object.
- * @return {Vector4} The scissor rectangle.
- */
- getScissor( target ) {
- const scissor = this._scissor;
- target.x = scissor.x;
- target.y = scissor.y;
- target.width = scissor.width;
- target.height = scissor.height;
- return target;
- }
- /**
- * Defines the scissor rectangle.
- *
- * @param {number | Vector4} x - The horizontal coordinate for the lower left corner of the box in logical pixel unit.
- * Instead of passing four arguments, the method also works with a single four-dimensional vector.
- * @param {number} y - The vertical coordinate for the lower left corner of the box in logical pixel unit.
- * @param {number} width - The width of the scissor box in logical pixel unit.
- * @param {number} height - The height of the scissor box in logical pixel unit.
- */
- setScissor( x, y, width, height ) {
- const scissor = this._scissor;
- if ( x.isVector4 ) {
- scissor.copy( x );
- } else {
- scissor.set( x, y, width, height );
- }
- }
- /**
- * Returns the scissor test value.
- *
- * @return {boolean} Whether the scissor test should be enabled or not.
- */
- getScissorTest() {
- return this._scissorTest;
- }
- /**
- * Defines the scissor test.
- *
- * @param {boolean} boolean - Whether the scissor test should be enabled or not.
- */
- setScissorTest( boolean ) {
- this._scissorTest = boolean;
- }
- /**
- * Returns the viewport definition.
- *
- * @param {Vector4} target - The method writes the result in this target object.
- * @return {Vector4} The viewport definition.
- */
- getViewport( target ) {
- return target.copy( this._viewport );
- }
- /**
- * Defines the viewport.
- *
- * @param {number | Vector4} x - The horizontal coordinate for the lower left corner of the viewport origin in logical pixel unit.
- * @param {number} y - The vertical coordinate for the lower left corner of the viewport origin in logical pixel unit.
- * @param {number} width - The width of the viewport in logical pixel unit.
- * @param {number} height - The height of the viewport in logical pixel unit.
- * @param {number} minDepth - The minimum depth value of the viewport. WebGPU only.
- * @param {number} maxDepth - The maximum depth value of the viewport. WebGPU only.
- */
- setViewport( x, y, width, height, minDepth = 0, maxDepth = 1 ) {
- const viewport = this._viewport;
- if ( x.isVector4 ) {
- viewport.copy( x );
- } else {
- viewport.set( x, y, width, height );
- }
- viewport.minDepth = minDepth;
- viewport.maxDepth = maxDepth;
- }
- /**
- * Dispatches the resize event.
- *
- * @private
- */
- _dispatchResize() {
- this.dispatchEvent( { type: 'resize' } );
- }
- /**
- * Frees the GPU-related resources allocated by this instance. Call this
- * method whenever this instance is no longer used in your app.
- *
- * @fires RenderTarget#dispose
- */
- dispose() {
- this.dispatchEvent( { type: 'dispose' } );
- }
- }
- const _scene = /*@__PURE__*/ new Scene();
- const _drawingBufferSize = /*@__PURE__*/ new Vector2();
- const _screen = /*@__PURE__*/ new Vector4();
- const _frustum = /*@__PURE__*/ new Frustum();
- const _frustumArray = /*@__PURE__*/ new FrustumArray();
- const _projScreenMatrix = /*@__PURE__*/ new Matrix4();
- const _vector4 = /*@__PURE__*/ new Vector4();
- /**
- * Base class for renderers.
- */
- class Renderer {
- /**
- * Renderer options.
- *
- * @typedef {Object} Renderer~Options
- * @property {boolean} [logarithmicDepthBuffer=false] - Whether logarithmic depth buffer is enabled or not.
- * @property {boolean} [alpha=true] - Whether the default framebuffer (which represents the final contents of the canvas) should be transparent or opaque.
- * @property {boolean} [depth=true] - Whether the default framebuffer should have a depth buffer or not.
- * @property {boolean} [stencil=false] - Whether the default framebuffer should have a stencil buffer or not.
- * @property {boolean} [antialias=false] - Whether MSAA as the default anti-aliasing should be enabled or not.
- * @property {number} [samples=0] - When `antialias` is `true`, `4` samples are used by default. This parameter can set to any other integer value than 0
- * to overwrite the default.
- * @property {?Function} [getFallback=null] - This callback function can be used to provide a fallback backend, if the primary backend can't be targeted.
- * @property {number} [outputBufferType=HalfFloatType] - Defines the type of output buffers. The default `HalfFloatType` is recommend for best
- * quality. To save memory and bandwidth, `UnsignedByteType` might be used. This will reduce rendering quality though.
- * @property {boolean} [multiview=false] - If set to `true`, the renderer will use multiview during WebXR rendering if supported.
- */
- /**
- * Constructs a new renderer.
- *
- * @param {Backend} backend - The backend the renderer is targeting (e.g. WebGPU or WebGL 2).
- * @param {Renderer~Options} [parameters] - The configuration parameter.
- */
- constructor( backend, parameters = {} ) {
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isRenderer = true;
- //
- const {
- logarithmicDepthBuffer = false,
- alpha = true,
- depth = true,
- stencil = false,
- antialias = false,
- samples = 0,
- getFallback = null,
- outputBufferType = HalfFloatType,
- multiview = false
- } = parameters;
- /**
- * A reference to the current backend.
- *
- * @type {Backend}
- */
- this.backend = backend;
- /**
- * Whether the renderer should automatically clear the current rendering target
- * before execute a `render()` call. The target can be the canvas (default framebuffer)
- * or the current bound render target (custom framebuffer).
- *
- * @type {boolean}
- * @default true
- */
- this.autoClear = true;
- /**
- * When `autoClear` is set to `true`, this property defines whether the renderer
- * should clear the color buffer.
- *
- * @type {boolean}
- * @default true
- */
- this.autoClearColor = true;
- /**
- * When `autoClear` is set to `true`, this property defines whether the renderer
- * should clear the depth buffer.
- *
- * @type {boolean}
- * @default true
- */
- this.autoClearDepth = true;
- /**
- * When `autoClear` is set to `true`, this property defines whether the renderer
- * should clear the stencil buffer.
- *
- * @type {boolean}
- * @default true
- */
- this.autoClearStencil = true;
- /**
- * Whether the default framebuffer should be transparent or opaque.
- *
- * @type {boolean}
- * @default true
- */
- this.alpha = alpha;
- /**
- * Whether logarithmic depth buffer is enabled or not.
- *
- * @type {boolean}
- * @default false
- */
- this.logarithmicDepthBuffer = logarithmicDepthBuffer;
- /**
- * Defines the output color space of the renderer.
- *
- * @type {string}
- * @default SRGBColorSpace
- */
- this.outputColorSpace = SRGBColorSpace;
- /**
- * Defines the tone mapping of the renderer.
- *
- * @type {number}
- * @default NoToneMapping
- */
- this.toneMapping = NoToneMapping;
- /**
- * Defines the tone mapping exposure.
- *
- * @type {number}
- * @default 1
- */
- this.toneMappingExposure = 1.0;
- /**
- * Whether the renderer should sort its render lists or not.
- *
- * Note: Sorting is used to attempt to properly render objects that have some degree of transparency.
- * By definition, sorting objects may not work in all cases. Depending on the needs of application,
- * it may be necessary to turn off sorting and use other methods to deal with transparency rendering
- * e.g. manually determining each object's rendering order.
- *
- * @type {boolean}
- * @default true
- */
- this.sortObjects = true;
- /**
- * Whether the default framebuffer should have a depth buffer or not.
- *
- * @type {boolean}
- * @default true
- */
- this.depth = depth;
- /**
- * Whether the default framebuffer should have a stencil buffer or not.
- *
- * @type {boolean}
- * @default false
- */
- this.stencil = stencil;
- /**
- * Holds a series of statistical information about the GPU memory
- * and the rendering process. Useful for debugging and monitoring.
- *
- * @type {Info}
- */
- this.info = new Info();
- /**
- * A global context node that stores override nodes for specific transformations or calculations.
- * These nodes can be used to replace default behavior in the rendering pipeline.
- *
- * @type {ContextNode}
- * @property {Object} value - The context value object.
- */
- this.contextNode = context();
- /**
- * The node library defines how certain library objects like materials, lights
- * or tone mapping functions are mapped to node types. This is required since
- * although instances of classes like `MeshBasicMaterial` or `PointLight` can
- * be part of the scene graph, they are internally represented as nodes for
- * further processing.
- *
- * @type {NodeLibrary}
- */
- this.library = new NodeLibrary();
- /**
- * A map-like data structure for managing lights.
- *
- * @type {Lighting}
- */
- this.lighting = new Lighting();
- // internals
- /**
- * The number of MSAA samples.
- *
- * @private
- * @type {number}
- * @default 0
- */
- this._samples = samples || ( antialias === true ) ? 4 : 0;
- /**
- * OnCanvasTargetResize callback function.
- *
- * @private
- * @type {Function}
- */
- this._onCanvasTargetResize = this._onCanvasTargetResize.bind( this );
- /**
- * The canvas target for rendering.
- *
- * @private
- * @type {CanvasTarget}
- */
- this._canvasTarget = new CanvasTarget( backend.getDomElement() );
- this._canvasTarget.addEventListener( 'resize', this._onCanvasTargetResize );
- this._canvasTarget.isDefaultCanvasTarget = true;
- /**
- * The inspector provides information about the internal renderer state.
- *
- * @private
- * @type {InspectorBase}
- */
- this._inspector = new InspectorBase();
- this._inspector.setRenderer( this );
- /**
- * This callback function can be used to provide a fallback backend, if the primary backend can't be targeted.
- *
- * @private
- * @type {?Function}
- */
- this._getFallback = getFallback;
- /**
- * A reference to a renderer module for managing shader attributes.
- *
- * @private
- * @type {?Attributes}
- * @default null
- */
- this._attributes = null;
- /**
- * A reference to a renderer module for managing geometries.
- *
- * @private
- * @type {?Geometries}
- * @default null
- */
- this._geometries = null;
- /**
- * A reference to a renderer module for managing node related logic.
- *
- * @private
- * @type {?Nodes}
- * @default null
- */
- this._nodes = null;
- /**
- * A reference to a renderer module for managing the internal animation loop.
- *
- * @private
- * @type {?Animation}
- * @default null
- */
- this._animation = null;
- /**
- * A reference to a renderer module for managing shader program bindings.
- *
- * @private
- * @type {?Bindings}
- * @default null
- */
- this._bindings = null;
- /**
- * A reference to a renderer module for managing render objects.
- *
- * @private
- * @type {?RenderObjects}
- * @default null
- */
- this._objects = null;
- /**
- * A reference to a renderer module for managing render and compute pipelines.
- *
- * @private
- * @type {?Pipelines}
- * @default null
- */
- this._pipelines = null;
- /**
- * A reference to a renderer module for managing render bundles.
- *
- * @private
- * @type {?RenderBundles}
- * @default null
- */
- this._bundles = null;
- /**
- * A reference to a renderer module for managing render lists.
- *
- * @private
- * @type {?RenderLists}
- * @default null
- */
- this._renderLists = null;
- /**
- * A reference to a renderer module for managing render contexts.
- *
- * @private
- * @type {?RenderContexts}
- * @default null
- */
- this._renderContexts = null;
- /**
- * A reference to a renderer module for managing textures.
- *
- * @private
- * @type {?Textures}
- * @default null
- */
- this._textures = null;
- /**
- * A reference to a renderer module for backgrounds.
- *
- * @private
- * @type {?Background}
- * @default null
- */
- this._background = null;
- /**
- * This fullscreen quad is used for internal render passes
- * like the tone mapping and color space output pass.
- *
- * @private
- * @type {QuadMesh}
- */
- this._quad = new QuadMesh( new NodeMaterial() );
- this._quad.name = 'Output Color Transform';
- this._quad.material.name = 'outputColorTransform';
- /**
- * A reference to the current render context.
- *
- * @private
- * @type {?RenderContext}
- * @default null
- */
- this._currentRenderContext = null;
- /**
- * A custom sort function for the opaque render list.
- *
- * @private
- * @type {?Function}
- * @default null
- */
- this._opaqueSort = null;
- /**
- * A custom sort function for the transparent render list.
- *
- * @private
- * @type {?Function}
- * @default null
- */
- this._transparentSort = null;
- /**
- * The framebuffer target.
- *
- * @private
- * @type {?RenderTarget}
- * @default null
- */
- this._frameBufferTarget = null;
- const alphaClear = this.alpha === true ? 0 : 1;
- /**
- * The clear color value.
- *
- * @private
- * @type {Color4}
- */
- this._clearColor = new Color4( 0, 0, 0, alphaClear );
- /**
- * The clear depth value.
- *
- * @private
- * @type {number}
- * @default 1
- */
- this._clearDepth = 1;
- /**
- * The clear stencil value.
- *
- * @private
- * @type {number}
- * @default 0
- */
- this._clearStencil = 0;
- /**
- * The current render target.
- *
- * @private
- * @type {?RenderTarget}
- * @default null
- */
- this._renderTarget = null;
- /**
- * The active cube face.
- *
- * @private
- * @type {number}
- * @default 0
- */
- this._activeCubeFace = 0;
- /**
- * The active mipmap level.
- *
- * @private
- * @type {number}
- * @default 0
- */
- this._activeMipmapLevel = 0;
- /**
- * The current output render target.
- *
- * @private
- * @type {?RenderTarget}
- * @default null
- */
- this._outputRenderTarget = null;
- /**
- * The MRT setting.
- *
- * @private
- * @type {?MRTNode}
- * @default null
- */
- this._mrt = null;
- /**
- * This function defines how a render object is going
- * to be rendered.
- *
- * @private
- * @type {?Function}
- * @default null
- */
- this._renderObjectFunction = null;
- /**
- * Used to keep track of the current render object function.
- *
- * @private
- * @type {?Function}
- * @default null
- */
- this._currentRenderObjectFunction = null;
- /**
- * Used to keep track of the current render bundle.
- *
- * @private
- * @type {?RenderBundle}
- * @default null
- */
- this._currentRenderBundle = null;
- /**
- * Next to `_renderObjectFunction()`, this function provides another hook
- * for influencing the render process of a render object. It is meant for internal
- * use and only relevant for `compileAsync()` right now. Instead of using
- * the default logic of `_renderObjectDirect()` which actually draws the render object,
- * a different function might be used which performs no draw but just the node
- * and pipeline updates.
- *
- * @private
- * @type {?Function}
- * @default null
- */
- this._handleObjectFunction = this._renderObjectDirect;
- /**
- * Indicates whether the device has been lost or not. In WebGL terms, the device
- * lost is considered as a context lost. When this is set to `true`, rendering
- * isn't possible anymore.
- *
- * @private
- * @type {boolean}
- * @default false
- */
- this._isDeviceLost = false;
- /**
- * A callback function that defines what should happen when a device/context lost occurs.
- *
- * @type {Function}
- */
- this.onDeviceLost = this._onDeviceLost;
- /**
- * Defines the type of output buffers. The default `HalfFloatType` is recommend for
- * best quality. To save memory and bandwidth, `UnsignedByteType` might be used.
- * This will reduce rendering quality though.
- *
- * @private
- * @type {number}
- * @default HalfFloatType
- */
- this._outputBufferType = outputBufferType;
- /**
- * A cache for shadow nodes per material
- *
- * @private
- * @type {WeakMap<Material, Object>}
- */
- this._cacheShadowNodes = new WeakMap();
- /**
- * Whether the renderer has been initialized or not.
- *
- * @private
- * @type {boolean}
- * @default false
- */
- this._initialized = false;
- /**
- * A reference to the promise which initializes the renderer.
- *
- * @private
- * @type {?Promise<this>}
- * @default null
- */
- this._initPromise = null;
- /**
- * An array of compilation promises which are used in `compileAsync()`.
- *
- * @private
- * @type {?Array<Promise>}
- * @default null
- */
- this._compilationPromises = null;
- /**
- * Whether the renderer should render transparent render objects or not.
- *
- * @type {boolean}
- * @default true
- */
- this.transparent = true;
- /**
- * Whether the renderer should render opaque render objects or not.
- *
- * @type {boolean}
- * @default true
- */
- this.opaque = true;
- /**
- * Shadow map configuration
- * @typedef {Object} ShadowMapConfig
- * @property {boolean} enabled - Whether to globally enable shadows or not.
- * @property {number} type - The shadow map type.
- */
- /**
- * The renderer's shadow configuration.
- *
- * @type {ShadowMapConfig}
- */
- this.shadowMap = {
- enabled: false,
- type: PCFShadowMap
- };
- /**
- * XR configuration.
- * @typedef {Object} XRConfig
- * @property {boolean} enabled - Whether to globally enable XR or not.
- */
- /**
- * The renderer's XR manager.
- *
- * @type {XRManager}
- */
- this.xr = new XRManager( this, multiview );
- /**
- * Debug configuration.
- * @typedef {Object} DebugConfig
- * @property {boolean} checkShaderErrors - Whether shader errors should be checked or not.
- * @property {?Function} onShaderError - A callback function that is executed when a shader error happens. Only supported with WebGL 2 right now.
- * @property {Function} getShaderAsync - Allows the get the raw shader code for the given scene, camera and 3D object.
- */
- /**
- * The renderer's debug configuration.
- *
- * @type {DebugConfig}
- */
- this.debug = {
- checkShaderErrors: true,
- onShaderError: null,
- getShaderAsync: async ( scene, camera, object ) => {
- await this.compileAsync( scene, camera );
- const renderList = this._renderLists.get( scene, camera );
- const renderContext = this._renderContexts.get( scene, camera, this._renderTarget, this._mrt );
- const material = scene.overrideMaterial || object.material;
- const renderObject = this._objects.get( object, material, scene, camera, renderList.lightsNode, renderContext, renderContext.clippingContext );
- const { fragmentShader, vertexShader } = renderObject.getNodeBuilderState();
- return { fragmentShader, vertexShader };
- }
- };
- }
- /**
- * Initializes the renderer so it is ready for usage.
- *
- * @async
- * @return {Promise<this>} A Promise that resolves when the renderer has been initialized.
- */
- async init() {
- if ( this._initPromise !== null ) {
- return this._initPromise;
- }
- this._initPromise = new Promise( async ( resolve, reject ) => {
- let backend = this.backend;
- try {
- await backend.init( this );
- } catch ( error ) {
- if ( this._getFallback !== null ) {
- // try the fallback
- try {
- this.backend = backend = this._getFallback( error );
- await backend.init( this );
- } catch ( error ) {
- reject( error );
- return;
- }
- } else {
- reject( error );
- return;
- }
- }
- this._nodes = new Nodes( this, backend );
- this._animation = new Animation( this, this._nodes, this.info );
- this._attributes = new Attributes( backend );
- this._background = new Background( this, this._nodes );
- this._geometries = new Geometries( this._attributes, this.info );
- this._textures = new Textures( this, backend, this.info );
- this._pipelines = new Pipelines( backend, this._nodes );
- this._bindings = new Bindings( backend, this._nodes, this._textures, this._attributes, this._pipelines, this.info );
- this._objects = new RenderObjects( this, this._nodes, this._geometries, this._pipelines, this._bindings, this.info );
- this._renderLists = new RenderLists( this.lighting );
- this._bundles = new RenderBundles();
- this._renderContexts = new RenderContexts();
- //
- this._animation.start();
- this._initialized = true;
- //
- this._inspector.init();
- //
- resolve( this );
- } );
- return this._initPromise;
- }
- /**
- * A reference to the canvas element the renderer is drawing to.
- * This value of this property will automatically be created by
- * the renderer.
- *
- * @type {HTMLCanvasElement|OffscreenCanvas}
- */
- get domElement() {
- return this._canvasTarget.domElement;
- }
- /**
- * The coordinate system of the renderer. The value of this property
- * depends on the selected backend. Either `THREE.WebGLCoordinateSystem` or
- * `THREE.WebGPUCoordinateSystem`.
- *
- * @readonly
- * @type {number}
- */
- get coordinateSystem() {
- return this.backend.coordinateSystem;
- }
- /**
- * Compiles all materials in the given scene. This can be useful to avoid a
- * phenomenon which is called "shader compilation stutter", which occurs when
- * rendering an object with a new shader for the first time.
- *
- * If you want to add a 3D object to an existing scene, use the third optional
- * parameter for applying the target scene. Note that the (target) scene's lighting
- * and environment must be configured before calling this method.
- *
- * @async
- * @param {Object3D} scene - The scene or 3D object to precompile.
- * @param {Camera} camera - The camera that is used to render the scene.
- * @param {?Scene} targetScene - If the first argument is a 3D object, this parameter must represent the scene the 3D object is going to be added.
- * @return {Promise} A Promise that resolves when the compile has been finished.
- */
- async compileAsync( scene, camera, targetScene = null ) {
- if ( this._isDeviceLost === true ) return;
- if ( this._initialized === false ) await this.init();
- // preserve render tree
- const nodeFrame = this._nodes.nodeFrame;
- const previousRenderId = nodeFrame.renderId;
- const previousRenderContext = this._currentRenderContext;
- const previousRenderObjectFunction = this._currentRenderObjectFunction;
- const previousCompilationPromises = this._compilationPromises;
- //
- const sceneRef = ( scene.isScene === true ) ? scene : _scene;
- if ( targetScene === null ) targetScene = scene;
- const renderTarget = this._renderTarget;
- const renderContext = this._renderContexts.get( targetScene, camera, renderTarget, this._mrt );
- const activeMipmapLevel = this._activeMipmapLevel;
- const compilationPromises = [];
- this._currentRenderContext = renderContext;
- this._currentRenderObjectFunction = this.renderObject;
- this._handleObjectFunction = this._createObjectPipeline;
- this._compilationPromises = compilationPromises;
- nodeFrame.renderId ++;
- //
- nodeFrame.update();
- //
- renderContext.depth = this.depth;
- renderContext.stencil = this.stencil;
- if ( ! renderContext.clippingContext ) renderContext.clippingContext = new ClippingContext();
- renderContext.clippingContext.updateGlobal( sceneRef, camera );
- //
- sceneRef.onBeforeRender( this, scene, camera, renderTarget );
- //
- const renderList = this._renderLists.get( scene, camera );
- renderList.begin();
- this._projectObject( scene, camera, 0, renderList, renderContext.clippingContext );
- // include lights from target scene
- if ( targetScene !== scene ) {
- targetScene.traverseVisible( function ( object ) {
- if ( object.isLight && object.layers.test( camera.layers ) ) {
- renderList.pushLight( object );
- }
- } );
- }
- renderList.finish();
- //
- if ( renderTarget !== null ) {
- this._textures.updateRenderTarget( renderTarget, activeMipmapLevel );
- const renderTargetData = this._textures.get( renderTarget );
- renderContext.textures = renderTargetData.textures;
- renderContext.depthTexture = renderTargetData.depthTexture;
- } else {
- renderContext.textures = null;
- renderContext.depthTexture = null;
- }
- //
- this._background.update( sceneRef, renderList, renderContext );
- // process render lists
- const opaqueObjects = renderList.opaque;
- const transparentObjects = renderList.transparent;
- const transparentDoublePassObjects = renderList.transparentDoublePass;
- const lightsNode = renderList.lightsNode;
- if ( this.opaque === true && opaqueObjects.length > 0 ) this._renderObjects( opaqueObjects, camera, sceneRef, lightsNode );
- if ( this.transparent === true && transparentObjects.length > 0 ) this._renderTransparents( transparentObjects, transparentDoublePassObjects, camera, sceneRef, lightsNode );
- // restore render tree
- nodeFrame.renderId = previousRenderId;
- this._currentRenderContext = previousRenderContext;
- this._currentRenderObjectFunction = previousRenderObjectFunction;
- this._compilationPromises = previousCompilationPromises;
- this._handleObjectFunction = this._renderObjectDirect;
- // wait for all promises setup by backends awaiting compilation/linking/pipeline creation to complete
- await Promise.all( compilationPromises );
- }
- /**
- * Renders the scene in an async fashion.
- *
- * @async
- * @deprecated
- * @param {Object3D} scene - The scene or 3D object to render.
- * @param {Camera} camera - The camera.
- * @return {Promise} A Promise that resolves when the render has been finished.
- */
- async renderAsync( scene, camera ) {
- warnOnce( 'Renderer: "renderAsync()" has been deprecated. Use "render()" and "await renderer.init();" when creating the renderer.' ); // @deprecated r181
- await this.init();
- this.render( scene, camera );
- }
- /**
- * Can be used to synchronize CPU operations with GPU tasks. So when this method is called,
- * the CPU waits for the GPU to complete its operation (e.g. a compute task).
- *
- * @async
- * @deprecated
- * @return {Promise} A Promise that resolves when synchronization has been finished.
- */
- async waitForGPU() {
- error( 'Renderer: waitForGPU() has been removed. Read https://github.com/mrdoob/three.js/issues/32012 for more information.' );
- }
- //
- set inspector( value ) {
- if ( this._inspector !== null ) {
- this._inspector.setRenderer( null );
- }
- this._inspector = value;
- this._inspector.setRenderer( this );
- }
- /**
- * The inspector instance. The inspector can be any class that extends from `InspectorBase`.
- *
- * @type {InspectorBase}
- */
- get inspector() {
- return this._inspector;
- }
- /**
- * Enables or disables high precision for model-view and normal-view matrices.
- * When enabled, will use CPU 64-bit precision for higher precision instead of GPU 32-bit for higher performance.
- *
- * NOTE: 64-bit precision is not compatible with `InstancedMesh` and `SkinnedMesh`.
- *
- * @param {boolean} value - Whether to enable or disable high precision.
- * @type {boolean}
- */
- set highPrecision( value ) {
- const contextNodeData = this.contextNode.value;
- if ( value === true ) {
- contextNodeData.modelViewMatrix = highpModelViewMatrix;
- contextNodeData.modelNormalViewMatrix = highpModelNormalViewMatrix;
- } else if ( this.highPrecision ) {
- delete contextNodeData.modelViewMatrix;
- delete contextNodeData.modelNormalViewMatrix;
- }
- }
- /**
- * Returns whether high precision is enabled or not.
- *
- * @return {boolean} Whether high precision is enabled or not.
- * @type {boolean}
- */
- get highPrecision() {
- const contextNodeData = this.contextNode.value;
- return contextNodeData.modelViewMatrix === highpModelViewMatrix && contextNodeData.modelNormalViewMatrix === highpModelNormalViewMatrix;
- }
- /**
- * Sets the given MRT configuration.
- *
- * @param {MRTNode} mrt - The MRT node to set.
- * @return {Renderer} A reference to this renderer.
- */
- setMRT( mrt ) {
- this._mrt = mrt;
- return this;
- }
- /**
- * Returns the MRT configuration.
- *
- * @return {MRTNode} The MRT configuration.
- */
- getMRT() {
- return this._mrt;
- }
- /**
- * Returns the output buffer type.
- *
- * @return {number} The output buffer type.
- */
- getOutputBufferType() {
- return this._outputBufferType;
- }
- /**
- * Returns the output buffer type.
- *
- * @deprecated since r182. Use `.getOutputBufferType()` instead.
- * @return {number} The output buffer type.
- */
- getColorBufferType() { // @deprecated, r182
- warnOnce( 'Renderer: ".getColorBufferType()" has been renamed to ".getOutputBufferType()".' );
- return this.getOutputBufferType();
- }
- /**
- * Default implementation of the device lost callback.
- *
- * @private
- * @param {Object} info - Information about the context lost.
- */
- _onDeviceLost( info ) {
- let errorMessage = `THREE.WebGPURenderer: ${info.api} Device Lost:\n\nMessage: ${info.message}`;
- if ( info.reason ) {
- errorMessage += `\nReason: ${info.reason}`;
- }
- error( errorMessage );
- this._isDeviceLost = true;
- }
- /**
- * Renders the given render bundle.
- *
- * @private
- * @param {Object} bundle - Render bundle data.
- * @param {Scene} sceneRef - The scene the render bundle belongs to.
- * @param {LightsNode} lightsNode - The lights node.
- */
- _renderBundle( bundle, sceneRef, lightsNode ) {
- const { bundleGroup, camera, renderList } = bundle;
- const renderContext = this._currentRenderContext;
- //
- const renderBundle = this._bundles.get( bundleGroup, camera );
- const renderBundleData = this.backend.get( renderBundle );
- if ( renderBundleData.renderContexts === undefined ) renderBundleData.renderContexts = new Set();
- //
- const needsUpdate = bundleGroup.version !== renderBundleData.version;
- const renderBundleNeedsUpdate = renderBundleData.renderContexts.has( renderContext ) === false || needsUpdate;
- renderBundleData.renderContexts.add( renderContext );
- if ( renderBundleNeedsUpdate ) {
- this.backend.beginBundle( renderContext );
- if ( renderBundleData.renderObjects === undefined || needsUpdate ) {
- renderBundleData.renderObjects = [];
- }
- this._currentRenderBundle = renderBundle;
- const {
- transparentDoublePass: transparentDoublePassObjects,
- transparent: transparentObjects,
- opaque: opaqueObjects
- } = renderList;
- if ( this.opaque === true && opaqueObjects.length > 0 ) this._renderObjects( opaqueObjects, camera, sceneRef, lightsNode );
- if ( this.transparent === true && transparentObjects.length > 0 ) this._renderTransparents( transparentObjects, transparentDoublePassObjects, camera, sceneRef, lightsNode );
- this._currentRenderBundle = null;
- //
- this.backend.finishBundle( renderContext, renderBundle );
- renderBundleData.version = bundleGroup.version;
- } else {
- const { renderObjects } = renderBundleData;
- for ( let i = 0, l = renderObjects.length; i < l; i ++ ) {
- const renderObject = renderObjects[ i ];
- if ( this._nodes.needsRefresh( renderObject ) ) {
- this._nodes.updateBefore( renderObject );
- this._nodes.updateForRender( renderObject );
- this._bindings.updateForRender( renderObject );
- this._nodes.updateAfter( renderObject );
- }
- }
- }
- this.backend.addBundle( renderContext, renderBundle );
- }
- /**
- * Renders the scene or 3D object with the given camera. This method can only be called
- * if the renderer has been initialized. When using `render()` inside an animation loop,
- * it's guaranteed the renderer will be initialized. The animation loop must be defined
- * with {@link Renderer#setAnimationLoop} though.
- *
- * For all other use cases (like when using on-demand rendering), you must call
- * {@link Renderer#init} before rendering.
- *
- * The target of the method is the default framebuffer (meaning the canvas)
- * or alternatively a render target when specified via `setRenderTarget()`.
- *
- * @param {Object3D} scene - The scene or 3D object to render.
- * @param {Camera} camera - The camera to render the scene with.
- */
- render( scene, camera ) {
- if ( this._initialized === false ) {
- throw new Error( 'Renderer: .render() called before the backend is initialized. Use "await renderer.init();" before rendering.' );
- }
- this._renderScene( scene, camera );
- }
- /**
- * Returns whether the renderer has been initialized or not.
- *
- * @readonly
- * @return {boolean} Whether the renderer has been initialized or not.
- */
- get initialized() {
- return this._initialized;
- }
- /**
- * Returns an internal render target which is used when computing the output tone mapping
- * and color space conversion. Unlike in `WebGLRenderer`, this is done in a separate render
- * pass and not inline to achieve more correct results.
- *
- * @private
- * @return {?RenderTarget} The render target. The method returns `null` if no output conversion should be applied.
- */
- _getFrameBufferTarget() {
- const { currentToneMapping, currentColorSpace } = this;
- const useToneMapping = currentToneMapping !== NoToneMapping;
- const useColorSpace = currentColorSpace !== ColorManagement.workingColorSpace;
- if ( useToneMapping === false && useColorSpace === false ) return null;
- const { width, height } = this.getDrawingBufferSize( _drawingBufferSize );
- const { depth, stencil } = this;
- let frameBufferTarget = this._frameBufferTarget;
- if ( frameBufferTarget === null ) {
- frameBufferTarget = new RenderTarget( width, height, {
- depthBuffer: depth,
- stencilBuffer: stencil,
- type: this._outputBufferType,
- format: RGBAFormat,
- colorSpace: ColorManagement.workingColorSpace,
- generateMipmaps: false,
- minFilter: LinearFilter,
- magFilter: LinearFilter,
- samples: this.samples
- } );
- frameBufferTarget.isPostProcessingRenderTarget = true;
- this._frameBufferTarget = frameBufferTarget;
- }
- const outputRenderTarget = this.getOutputRenderTarget();
- frameBufferTarget.depthBuffer = depth;
- frameBufferTarget.stencilBuffer = stencil;
- if ( outputRenderTarget !== null ) {
- frameBufferTarget.setSize( outputRenderTarget.width, outputRenderTarget.height, outputRenderTarget.depth );
- } else {
- frameBufferTarget.setSize( width, height, 1 );
- }
- const canvasTarget = this._canvasTarget;
- frameBufferTarget.viewport.copy( canvasTarget._viewport );
- frameBufferTarget.scissor.copy( canvasTarget._scissor );
- frameBufferTarget.viewport.multiplyScalar( canvasTarget._pixelRatio );
- frameBufferTarget.scissor.multiplyScalar( canvasTarget._pixelRatio );
- frameBufferTarget.scissorTest = canvasTarget._scissorTest;
- frameBufferTarget.multiview = outputRenderTarget !== null ? outputRenderTarget.multiview : false;
- frameBufferTarget.resolveDepthBuffer = outputRenderTarget !== null ? outputRenderTarget.resolveDepthBuffer : true;
- frameBufferTarget._autoAllocateDepthBuffer = outputRenderTarget !== null ? outputRenderTarget._autoAllocateDepthBuffer : false;
- return frameBufferTarget;
- }
- /**
- * Renders the scene or 3D object with the given camera.
- *
- * @private
- * @param {Object3D} scene - The scene or 3D object to render.
- * @param {Camera} camera - The camera to render the scene with.
- * @param {boolean} [useFrameBufferTarget=true] - Whether to use a framebuffer target or not.
- * @return {RenderContext} The current render context.
- */
- _renderScene( scene, camera, useFrameBufferTarget = true ) {
- if ( this._isDeviceLost === true ) return;
- //
- const frameBufferTarget = useFrameBufferTarget ? this._getFrameBufferTarget() : null;
- // preserve render tree
- const nodeFrame = this._nodes.nodeFrame;
- const previousRenderId = nodeFrame.renderId;
- const previousRenderContext = this._currentRenderContext;
- const previousRenderObjectFunction = this._currentRenderObjectFunction;
- //
- const sceneRef = ( scene.isScene === true ) ? scene : _scene;
- const outputRenderTarget = this._renderTarget || this._outputRenderTarget;
- const activeCubeFace = this._activeCubeFace;
- const activeMipmapLevel = this._activeMipmapLevel;
- //
- let renderTarget;
- if ( frameBufferTarget !== null ) {
- renderTarget = frameBufferTarget;
- this.setRenderTarget( renderTarget );
- } else {
- renderTarget = outputRenderTarget;
- }
- //
- const renderContext = this._renderContexts.get( scene, camera, renderTarget, this._mrt );
- this._currentRenderContext = renderContext;
- this._currentRenderObjectFunction = this._renderObjectFunction || this.renderObject;
- //
- this.info.calls ++;
- this.info.render.calls ++;
- this.info.render.frameCalls ++;
- nodeFrame.renderId = this.info.calls;
- //
- this.backend.updateTimeStampUID( renderContext );
- this.inspector.beginRender( this.backend.getTimestampUID( renderContext ), scene, camera, renderTarget );
- //
- const coordinateSystem = this.coordinateSystem;
- const xr = this.xr;
- if ( camera.coordinateSystem !== coordinateSystem && xr.isPresenting === false ) {
- camera.coordinateSystem = coordinateSystem;
- camera.updateProjectionMatrix();
- if ( camera.isArrayCamera ) {
- for ( const subCamera of camera.cameras ) {
- subCamera.coordinateSystem = coordinateSystem;
- subCamera.updateProjectionMatrix();
- }
- }
- }
- //
- if ( scene.matrixWorldAutoUpdate === true ) scene.updateMatrixWorld();
- if ( camera.parent === null && camera.matrixWorldAutoUpdate === true ) camera.updateMatrixWorld();
- if ( xr.enabled === true && xr.isPresenting === true ) {
- if ( xr.cameraAutoUpdate === true ) xr.updateCamera( camera );
- camera = xr.getCamera(); // use XR camera for rendering
- }
- //
- const canvasTarget = this._canvasTarget;
- let viewport = canvasTarget._viewport;
- let scissor = canvasTarget._scissor;
- let pixelRatio = canvasTarget._pixelRatio;
- if ( renderTarget !== null ) {
- viewport = renderTarget.viewport;
- scissor = renderTarget.scissor;
- pixelRatio = 1;
- }
- this.getDrawingBufferSize( _drawingBufferSize );
- _screen.set( 0, 0, _drawingBufferSize.width, _drawingBufferSize.height );
- const minDepth = ( viewport.minDepth === undefined ) ? 0 : viewport.minDepth;
- const maxDepth = ( viewport.maxDepth === undefined ) ? 1 : viewport.maxDepth;
- renderContext.viewportValue.copy( viewport ).multiplyScalar( pixelRatio ).floor();
- renderContext.viewportValue.width >>= activeMipmapLevel;
- renderContext.viewportValue.height >>= activeMipmapLevel;
- renderContext.viewportValue.minDepth = minDepth;
- renderContext.viewportValue.maxDepth = maxDepth;
- renderContext.viewport = renderContext.viewportValue.equals( _screen ) === false;
- renderContext.scissorValue.copy( scissor ).multiplyScalar( pixelRatio ).floor();
- renderContext.scissor = canvasTarget._scissorTest && renderContext.scissorValue.equals( _screen ) === false;
- renderContext.scissorValue.width >>= activeMipmapLevel;
- renderContext.scissorValue.height >>= activeMipmapLevel;
- if ( ! renderContext.clippingContext ) renderContext.clippingContext = new ClippingContext();
- renderContext.clippingContext.updateGlobal( sceneRef, camera );
- //
- sceneRef.onBeforeRender( this, scene, camera, renderTarget );
- //
- const frustum = camera.isArrayCamera ? _frustumArray : _frustum;
- if ( ! camera.isArrayCamera ) {
- _projScreenMatrix.multiplyMatrices( camera.projectionMatrix, camera.matrixWorldInverse );
- frustum.setFromProjectionMatrix( _projScreenMatrix, camera.coordinateSystem, camera.reversedDepth );
- }
- const renderList = this._renderLists.get( scene, camera );
- renderList.begin();
- this._projectObject( scene, camera, 0, renderList, renderContext.clippingContext );
- renderList.finish();
- if ( this.sortObjects === true ) {
- renderList.sort( this._opaqueSort, this._transparentSort );
- }
- //
- if ( renderTarget !== null ) {
- this._textures.updateRenderTarget( renderTarget, activeMipmapLevel );
- const renderTargetData = this._textures.get( renderTarget );
- renderContext.textures = renderTargetData.textures;
- renderContext.depthTexture = renderTargetData.depthTexture;
- renderContext.width = renderTargetData.width;
- renderContext.height = renderTargetData.height;
- renderContext.renderTarget = renderTarget;
- renderContext.depth = renderTarget.depthBuffer;
- renderContext.stencil = renderTarget.stencilBuffer;
- } else {
- renderContext.textures = null;
- renderContext.depthTexture = null;
- renderContext.width = _drawingBufferSize.width;
- renderContext.height = _drawingBufferSize.height;
- renderContext.depth = this.depth;
- renderContext.stencil = this.stencil;
- }
- renderContext.width >>= activeMipmapLevel;
- renderContext.height >>= activeMipmapLevel;
- renderContext.activeCubeFace = activeCubeFace;
- renderContext.activeMipmapLevel = activeMipmapLevel;
- renderContext.occlusionQueryCount = renderList.occlusionQueryCount;
- //
- renderContext.scissorValue.max( _vector4.set( 0, 0, 0, 0 ) );
- if ( renderContext.scissorValue.x + renderContext.scissorValue.width > renderContext.width ) {
- renderContext.scissorValue.width = Math.max( renderContext.width - renderContext.scissorValue.x, 0 );
- }
- if ( renderContext.scissorValue.y + renderContext.scissorValue.height > renderContext.height ) {
- renderContext.scissorValue.height = Math.max( renderContext.height - renderContext.scissorValue.y, 0 );
- }
- //
- this._background.update( sceneRef, renderList, renderContext );
- //
- renderContext.camera = camera;
- this.backend.beginRender( renderContext );
- // process render lists
- const {
- bundles,
- lightsNode,
- transparentDoublePass: transparentDoublePassObjects,
- transparent: transparentObjects,
- opaque: opaqueObjects
- } = renderList;
- if ( bundles.length > 0 ) this._renderBundles( bundles, sceneRef, lightsNode );
- if ( this.opaque === true && opaqueObjects.length > 0 ) this._renderObjects( opaqueObjects, camera, sceneRef, lightsNode );
- if ( this.transparent === true && transparentObjects.length > 0 ) this._renderTransparents( transparentObjects, transparentDoublePassObjects, camera, sceneRef, lightsNode );
- // finish render pass
- this.backend.finishRender( renderContext );
- // restore render tree
- nodeFrame.renderId = previousRenderId;
- this._currentRenderContext = previousRenderContext;
- this._currentRenderObjectFunction = previousRenderObjectFunction;
- //
- if ( frameBufferTarget !== null ) {
- this.setRenderTarget( outputRenderTarget, activeCubeFace, activeMipmapLevel );
- this._renderOutput( renderTarget );
- }
- //
- sceneRef.onAfterRender( this, scene, camera, renderTarget );
- //
- this.inspector.finishRender( this.backend.getTimestampUID( renderContext ) );
- //
- return renderContext;
- }
- _setXRLayerSize( width, height ) {
- // TODO: Find a better solution to resize the canvas when in XR.
- this._canvasTarget._width = width;
- this._canvasTarget._height = height;
- this.setViewport( 0, 0, width, height );
- }
- /**
- * The output pass performs tone mapping and color space conversion.
- *
- * @private
- * @param {RenderTarget} renderTarget - The current render target.
- */
- _renderOutput( renderTarget ) {
- const quad = this._quad;
- if ( this._nodes.hasOutputChange( renderTarget.texture ) ) {
- quad.material.fragmentNode = this._nodes.getOutputNode( renderTarget.texture );
- quad.material.needsUpdate = true;
- }
- // a clear operation clears the intermediate renderTarget texture, but should not update the screen canvas.
- const currentAutoClear = this.autoClear;
- const currentXR = this.xr.enabled;
- this.autoClear = false;
- this.xr.enabled = false;
- this._renderScene( quad, quad.camera, false );
- this.autoClear = currentAutoClear;
- this.xr.enabled = currentXR;
- }
- /**
- * Returns the maximum available anisotropy for texture filtering.
- *
- * @return {number} The maximum available anisotropy.
- */
- getMaxAnisotropy() {
- return this.backend.getMaxAnisotropy();
- }
- /**
- * Returns the active cube face.
- *
- * @return {number} The active cube face.
- */
- getActiveCubeFace() {
- return this._activeCubeFace;
- }
- /**
- * Returns the active mipmap level.
- *
- * @return {number} The active mipmap level.
- */
- getActiveMipmapLevel() {
- return this._activeMipmapLevel;
- }
- /**
- * Applications are advised to always define the animation loop
- * with this method and not manually with `requestAnimationFrame()`
- * for best compatibility.
- *
- * @async
- * @param {?onAnimationCallback} callback - The application's animation loop.
- * @return {Promise} A Promise that resolves when the set has been executed.
- */
- async setAnimationLoop( callback ) {
- if ( this._initialized === false ) await this.init();
- this._animation.setAnimationLoop( callback );
- }
- /**
- * Returns the current animation loop callback.
- *
- * @return {?Function} The current animation loop callback.
- */
- getAnimationLoop() {
- return this._animation.getAnimationLoop();
- }
- /**
- * Can be used to transfer buffer data from a storage buffer attribute
- * from the GPU to the CPU in context of compute shaders.
- *
- * @async
- * @param {StorageBufferAttribute} attribute - The storage buffer attribute.
- * @return {Promise<ArrayBuffer>} A promise that resolves with the buffer data when the data are ready.
- */
- async getArrayBufferAsync( attribute ) {
- return await this.backend.getArrayBufferAsync( attribute );
- }
- /**
- * Returns the rendering context.
- *
- * @return {GPUCanvasContext|WebGL2RenderingContext} The rendering context.
- */
- getContext() {
- return this.backend.getContext();
- }
- /**
- * Returns the pixel ratio.
- *
- * @return {number} The pixel ratio.
- */
- getPixelRatio() {
- return this._canvasTarget.getPixelRatio();
- }
- /**
- * Returns the drawing buffer size in physical pixels. This method honors the pixel ratio.
- *
- * @param {Vector2} target - The method writes the result in this target object.
- * @return {Vector2} The drawing buffer size.
- */
- getDrawingBufferSize( target ) {
- return this._canvasTarget.getDrawingBufferSize( target );
- }
- /**
- * Returns the renderer's size in logical pixels. This method does not honor the pixel ratio.
- *
- * @param {Vector2} target - The method writes the result in this target object.
- * @return {Vector2} The renderer's size in logical pixels.
- */
- getSize( target ) {
- return this._canvasTarget.getSize( target );
- }
- /**
- * Sets the given pixel ratio and resizes the canvas if necessary.
- *
- * @param {number} [value=1] - The pixel ratio.
- */
- setPixelRatio( value = 1 ) {
- this._canvasTarget.setPixelRatio( value );
- }
- /**
- * This method allows to define the drawing buffer size by specifying
- * width, height and pixel ratio all at once. The size of the drawing
- * buffer is computed with this formula:
- * ```js
- * size.x = width * pixelRatio;
- * size.y = height * pixelRatio;
- * ```
- *
- * @param {number} width - The width in logical pixels.
- * @param {number} height - The height in logical pixels.
- * @param {number} pixelRatio - The pixel ratio.
- */
- setDrawingBufferSize( width, height, pixelRatio ) {
- // Renderer can't be resized while presenting in XR.
- if ( this.xr && this.xr.isPresenting ) return;
- this._canvasTarget.setDrawingBufferSize( width, height, pixelRatio );
- }
- /**
- * Sets the size of the renderer.
- *
- * @param {number} width - The width in logical pixels.
- * @param {number} height - The height in logical pixels.
- * @param {boolean} [updateStyle=true] - Whether to update the `style` attribute of the canvas or not.
- */
- setSize( width, height, updateStyle = true ) {
- // Renderer can't be resized while presenting in XR.
- if ( this.xr && this.xr.isPresenting ) return;
- this._canvasTarget.setSize( width, height, updateStyle );
- }
- /**
- * Defines a manual sort function for the opaque render list.
- * Pass `null` to use the default sort.
- *
- * @param {Function} method - The sort function.
- */
- setOpaqueSort( method ) {
- this._opaqueSort = method;
- }
- /**
- * Defines a manual sort function for the transparent render list.
- * Pass `null` to use the default sort.
- *
- * @param {Function} method - The sort function.
- */
- setTransparentSort( method ) {
- this._transparentSort = method;
- }
- /**
- * Returns the scissor rectangle.
- *
- * @param {Vector4} target - The method writes the result in this target object.
- * @return {Vector4} The scissor rectangle.
- */
- getScissor( target ) {
- return this._canvasTarget.getScissor( target );
- }
- /**
- * Defines the scissor rectangle.
- *
- * @param {number | Vector4} x - The horizontal coordinate for the upper left corner of the box in logical pixel unit.
- * Instead of passing four arguments, the method also works with a single four-dimensional vector.
- * @param {number} y - The vertical coordinate for the upper left corner of the box in logical pixel unit.
- * @param {number} width - The width of the scissor box in logical pixel unit.
- * @param {number} height - The height of the scissor box in logical pixel unit.
- */
- setScissor( x, y, width, height ) {
- this._canvasTarget.setScissor( x, y, width, height );
- }
- /**
- * Returns the scissor test value.
- *
- * @return {boolean} Whether the scissor test should be enabled or not.
- */
- getScissorTest() {
- return this._canvasTarget.getScissorTest();
- }
- /**
- * Defines the scissor test.
- *
- * @param {boolean} boolean - Whether the scissor test should be enabled or not.
- */
- setScissorTest( boolean ) {
- this._canvasTarget.setScissorTest( boolean );
- // TODO: Move it to CanvasTarget event listener.
- this.backend.setScissorTest( boolean );
- }
- /**
- * Returns the viewport definition.
- *
- * @param {Vector4} target - The method writes the result in this target object.
- * @return {Vector4} The viewport definition.
- */
- getViewport( target ) {
- return this._canvasTarget.getViewport( target );
- }
- /**
- * Defines the viewport.
- *
- * @param {number | Vector4} x - The horizontal coordinate for the upper left corner of the viewport origin in logical pixel unit.
- * @param {number} y - The vertical coordinate for the upper left corner of the viewport origin in logical pixel unit.
- * @param {number} width - The width of the viewport in logical pixel unit.
- * @param {number} height - The height of the viewport in logical pixel unit.
- * @param {number} minDepth - The minimum depth value of the viewport. WebGPU only.
- * @param {number} maxDepth - The maximum depth value of the viewport. WebGPU only.
- */
- setViewport( x, y, width, height, minDepth = 0, maxDepth = 1 ) {
- this._canvasTarget.setViewport( x, y, width, height, minDepth, maxDepth );
- }
- /**
- * Returns the clear color.
- *
- * @param {Color} target - The method writes the result in this target object.
- * @return {Color} The clear color.
- */
- getClearColor( target ) {
- return target.copy( this._clearColor );
- }
- /**
- * Defines the clear color and optionally the clear alpha.
- *
- * @param {Color} color - The clear color.
- * @param {number} [alpha=1] - The clear alpha.
- */
- setClearColor( color, alpha = 1 ) {
- this._clearColor.set( color );
- this._clearColor.a = alpha;
- }
- /**
- * Returns the clear alpha.
- *
- * @return {number} The clear alpha.
- */
- getClearAlpha() {
- return this._clearColor.a;
- }
- /**
- * Defines the clear alpha.
- *
- * @param {number} alpha - The clear alpha.
- */
- setClearAlpha( alpha ) {
- this._clearColor.a = alpha;
- }
- /**
- * Returns the clear depth.
- *
- * @return {number} The clear depth.
- */
- getClearDepth() {
- return this._clearDepth;
- }
- /**
- * Defines the clear depth.
- *
- * @param {number} depth - The clear depth.
- */
- setClearDepth( depth ) {
- this._clearDepth = depth;
- }
- /**
- * Returns the clear stencil.
- *
- * @return {number} The clear stencil.
- */
- getClearStencil() {
- return this._clearStencil;
- }
- /**
- * Defines the clear stencil.
- *
- * @param {number} stencil - The clear stencil.
- */
- setClearStencil( stencil ) {
- this._clearStencil = stencil;
- }
- /**
- * This method performs an occlusion query for the given 3D object.
- * It returns `true` if the given 3D object is fully occluded by other
- * 3D objects in the scene.
- *
- * @param {Object3D} object - The 3D object to test.
- * @return {boolean} Whether the 3D object is fully occluded or not.
- */
- isOccluded( object ) {
- const renderContext = this._currentRenderContext;
- return renderContext && this.backend.isOccluded( renderContext, object );
- }
- /**
- * Performs a manual clear operation. This method ignores `autoClear` properties.
- *
- * @param {boolean} [color=true] - Whether the color buffer should be cleared or not.
- * @param {boolean} [depth=true] - Whether the depth buffer should be cleared or not.
- * @param {boolean} [stencil=true] - Whether the stencil buffer should be cleared or not.
- */
- clear( color = true, depth = true, stencil = true ) {
- if ( this._initialized === false ) {
- throw new Error( 'Renderer: .clear() called before the backend is initialized. Use "await renderer.init();" before before using this method.' );
- }
- const renderTarget = this._renderTarget || this._getFrameBufferTarget();
- let renderContext = null;
- if ( renderTarget !== null ) {
- this._textures.updateRenderTarget( renderTarget );
- const renderTargetData = this._textures.get( renderTarget );
- renderContext = this._renderContexts.getForClear( renderTarget );
- renderContext.textures = renderTargetData.textures;
- renderContext.depthTexture = renderTargetData.depthTexture;
- renderContext.width = renderTargetData.width;
- renderContext.height = renderTargetData.height;
- renderContext.renderTarget = renderTarget;
- renderContext.depth = renderTarget.depthBuffer;
- renderContext.stencil = renderTarget.stencilBuffer;
- // #30329
- renderContext.clearColorValue = this.backend.getClearColor();
- renderContext.activeCubeFace = this.getActiveCubeFace();
- renderContext.activeMipmapLevel = this.getActiveMipmapLevel();
- }
- this.backend.clear( color, depth, stencil, renderContext );
- if ( renderTarget !== null && this._renderTarget === null ) {
- this._renderOutput( renderTarget );
- }
- }
- /**
- * Performs a manual clear operation of the color buffer. This method ignores `autoClear` properties.
- */
- clearColor() {
- this.clear( true, false, false );
- }
- /**
- * Performs a manual clear operation of the depth buffer. This method ignores `autoClear` properties.
- */
- clearDepth() {
- this.clear( false, true, false );
- }
- /**
- * Performs a manual clear operation of the stencil buffer. This method ignores `autoClear` properties.
- */
- clearStencil() {
- this.clear( false, false, true );
- }
- /**
- * Async version of {@link Renderer#clear}.
- *
- * @async
- * @deprecated
- * @param {boolean} [color=true] - Whether the color buffer should be cleared or not.
- * @param {boolean} [depth=true] - Whether the depth buffer should be cleared or not.
- * @param {boolean} [stencil=true] - Whether the stencil buffer should be cleared or not.
- * @return {Promise} A Promise that resolves when the clear operation has been executed.
- */
- async clearAsync( color = true, depth = true, stencil = true ) {
- warnOnce( 'Renderer: "clearAsync()" has been deprecated. Use "clear()" and "await renderer.init();" when creating the renderer.' ); // @deprecated r181
- await this.init();
- this.clear( color, depth, stencil );
- }
- /**
- * Async version of {@link Renderer#clearColor}.
- *
- * @async
- * @deprecated
- * @return {Promise} A Promise that resolves when the clear operation has been executed.
- */
- async clearColorAsync() {
- warnOnce( 'Renderer: "clearColorAsync()" has been deprecated. Use "clearColor()" and "await renderer.init();" when creating the renderer.' ); // @deprecated r181
- this.clear( true, false, false );
- }
- /**
- * Async version of {@link Renderer#clearDepth}.
- *
- * @async
- * @deprecated
- * @return {Promise} A Promise that resolves when the clear operation has been executed.
- */
- async clearDepthAsync() {
- warnOnce( 'Renderer: "clearDepthAsync()" has been deprecated. Use "clearDepth()" and "await renderer.init();" when creating the renderer.' ); // @deprecated r181
- this.clear( false, true, false );
- }
- /**
- * Async version of {@link Renderer#clearStencil}.
- *
- * @async
- * @deprecated
- * @return {Promise} A Promise that resolves when the clear operation has been executed.
- */
- async clearStencilAsync() {
- warnOnce( 'Renderer: "clearStencilAsync()" has been deprecated. Use "clearStencil()" and "await renderer.init();" when creating the renderer.' ); // @deprecated r181
- this.clear( false, false, true );
- }
- /**
- * Returns `true` if a framebuffer target is needed to perform tone mapping or color space conversion.
- * If this is the case, the renderer allocates an internal render target for that purpose.
- *
- */
- get needsFrameBufferTarget() {
- const useToneMapping = this.currentToneMapping !== NoToneMapping;
- const useColorSpace = this.currentColorSpace !== ColorManagement.workingColorSpace;
- return useToneMapping || useColorSpace;
- }
- /**
- * The number of samples used for multi-sample anti-aliasing (MSAA).
- *
- * @type {number}
- * @default 0
- */
- get samples() {
- return this._samples;
- }
- /**
- * The current number of samples used for multi-sample anti-aliasing (MSAA).
- *
- * When rendering to a custom render target, the number of samples of that render target is used.
- * If the renderer needs an internal framebuffer target for tone mapping or color space conversion,
- * the number of samples is set to 0.
- *
- * @type {number}
- */
- get currentSamples() {
- let samples = this._samples;
- if ( this._renderTarget !== null ) {
- samples = this._renderTarget.samples;
- } else if ( this.needsFrameBufferTarget ) {
- samples = 0;
- }
- return samples;
- }
- /**
- * The current tone mapping of the renderer. When not producing screen output,
- * the tone mapping is always `NoToneMapping`.
- *
- * @type {number}
- */
- get currentToneMapping() {
- return this.isOutputTarget ? this.toneMapping : NoToneMapping;
- }
- /**
- * The current color space of the renderer. When not producing screen output,
- * the color space is always the working color space.
- *
- * @type {string}
- */
- get currentColorSpace() {
- return this.isOutputTarget ? this.outputColorSpace : ColorManagement.workingColorSpace;
- }
- /**
- * Returns `true` if the rendering settings are set to screen output.
- *
- * @returns {boolean} True if the current render target is the same of output render target or `null`, otherwise false.
- */
- get isOutputTarget() {
- return this._renderTarget === this._outputRenderTarget || this._renderTarget === null;
- }
- /**
- * Frees all internal resources of the renderer. Call this method if the renderer
- * is no longer in use by your app.
- */
- dispose() {
- if ( this._initialized === true ) {
- this.info.dispose();
- this.backend.dispose();
- this._animation.dispose();
- this._objects.dispose();
- this._geometries.dispose();
- this._pipelines.dispose();
- this._nodes.dispose();
- this._bindings.dispose();
- this._renderLists.dispose();
- this._renderContexts.dispose();
- this._textures.dispose();
- if ( this._frameBufferTarget !== null ) this._frameBufferTarget.dispose();
- Object.values( this.backend.timestampQueryPool ).forEach( queryPool => {
- if ( queryPool !== null ) queryPool.dispose();
- } );
- }
- this.setRenderTarget( null );
- this.setAnimationLoop( null );
- }
- /**
- * Sets the given render target. Calling this method means the renderer does not
- * target the default framebuffer (meaning the canvas) anymore but a custom framebuffer.
- * Use `null` as the first argument to reset the state.
- *
- * @param {?RenderTarget} renderTarget - The render target to set.
- * @param {number} [activeCubeFace=0] - The active cube face.
- * @param {number} [activeMipmapLevel=0] - The active mipmap level.
- */
- setRenderTarget( renderTarget, activeCubeFace = 0, activeMipmapLevel = 0 ) {
- this._renderTarget = renderTarget;
- this._activeCubeFace = activeCubeFace;
- this._activeMipmapLevel = activeMipmapLevel;
- }
- /**
- * Returns the current render target.
- *
- * @return {?RenderTarget} The render target. Returns `null` if no render target is set.
- */
- getRenderTarget() {
- return this._renderTarget;
- }
- /**
- * Sets the output render target for the renderer.
- *
- * @param {Object} renderTarget - The render target to set as the output target.
- */
- setOutputRenderTarget( renderTarget ) {
- this._outputRenderTarget = renderTarget;
- }
- /**
- * Returns the current output target.
- *
- * @return {?RenderTarget} The current output render target. Returns `null` if no output target is set.
- */
- getOutputRenderTarget() {
- return this._outputRenderTarget;
- }
- /**
- * Sets the canvas target. The canvas target manages the HTML canvas
- * or the offscreen canvas the renderer draws into.
- *
- * @param {CanvasTarget} canvasTarget - The canvas target.
- */
- setCanvasTarget( canvasTarget ) {
- this._canvasTarget.removeEventListener( 'resize', this._onCanvasTargetResize );
- this._canvasTarget = canvasTarget;
- this._canvasTarget.addEventListener( 'resize', this._onCanvasTargetResize );
- }
- /**
- * Returns the current canvas target.
- *
- * @return {CanvasTarget} The current canvas target.
- */
- getCanvasTarget() {
- return this._canvasTarget;
- }
- /**
- * Resets the renderer to the initial state before WebXR started.
- *
- * @private
- */
- _resetXRState() {
- this.backend.setXRTarget( null );
- this.setOutputRenderTarget( null );
- this.setRenderTarget( null );
- this._frameBufferTarget.dispose();
- this._frameBufferTarget = null;
- }
- /**
- * Callback for {@link Renderer#setRenderObjectFunction}.
- *
- * @callback renderObjectFunction
- * @param {Object3D} object - The 3D object.
- * @param {Scene} scene - The scene the 3D object belongs to.
- * @param {Camera} camera - The camera the object should be rendered with.
- * @param {BufferGeometry} geometry - The object's geometry.
- * @param {Material} material - The object's material.
- * @param {?Object} group - Only relevant for objects using multiple materials. This represents a group entry from the respective `BufferGeometry`.
- * @param {LightsNode} lightsNode - The current lights node.
- * @param {ClippingContext} clippingContext - The clipping context.
- * @param {?string} [passId=null] - An optional ID for identifying the pass.
- */
- /**
- * Sets the given render object function. Calling this method overwrites the default implementation
- * which is {@link Renderer#renderObject}. Defining a custom function can be useful
- * if you want to modify the way objects are rendered. For example you can define things like "every
- * object that has material of a certain type should perform a pre-pass with a special overwrite material".
- * The custom function must always call `renderObject()` in its implementation.
- *
- * Use `null` as the first argument to reset the state.
- *
- * @param {?renderObjectFunction} renderObjectFunction - The render object function.
- */
- setRenderObjectFunction( renderObjectFunction ) {
- this._renderObjectFunction = renderObjectFunction;
- }
- /**
- * Returns the current render object function.
- *
- * @return {?Function} The current render object function. Returns `null` if no function is set.
- */
- getRenderObjectFunction() {
- return this._renderObjectFunction;
- }
- /**
- * Execute a single or an array of compute nodes. This method can only be called
- * if the renderer has been initialized.
- *
- * @param {Node|Array<Node>} computeNodes - The compute node(s).
- * @param {number|Array<number>|IndirectStorageBufferAttribute} [dispatchSize=null]
- * - A single number representing count, or
- * - An array [x, y, z] representing dispatch size, or
- * - A IndirectStorageBufferAttribute for indirect dispatch size.
- * @return {Promise|undefined} A Promise that resolve when the compute has finished. Only returned when the renderer has not been initialized.
- */
- compute( computeNodes, dispatchSize = null ) {
- if ( this._isDeviceLost === true ) return;
- if ( this._initialized === false ) {
- warn( 'Renderer: .compute() called before the backend is initialized. Try using .computeAsync() instead.' );
- return this.computeAsync( computeNodes, dispatchSize );
- }
- //
- const nodeFrame = this._nodes.nodeFrame;
- const previousRenderId = nodeFrame.renderId;
- //
- this.info.calls ++;
- this.info.compute.calls ++;
- this.info.compute.frameCalls ++;
- nodeFrame.renderId = this.info.calls;
- //
- this.backend.updateTimeStampUID( computeNodes );
- this.inspector.beginCompute( this.backend.getTimestampUID( computeNodes ), computeNodes );
- //
- const backend = this.backend;
- const pipelines = this._pipelines;
- const bindings = this._bindings;
- const nodes = this._nodes;
- const computeList = Array.isArray( computeNodes ) ? computeNodes : [ computeNodes ];
- if ( computeList[ 0 ] === undefined || computeList[ 0 ].isComputeNode !== true ) {
- throw new Error( 'THREE.Renderer: .compute() expects a ComputeNode.' );
- }
- backend.beginCompute( computeNodes );
- for ( const computeNode of computeList ) {
- // onInit
- if ( pipelines.has( computeNode ) === false ) {
- const dispose = () => {
- computeNode.removeEventListener( 'dispose', dispose );
- pipelines.delete( computeNode );
- bindings.deleteForCompute( computeNode );
- nodes.delete( computeNode );
- };
- computeNode.addEventListener( 'dispose', dispose );
- //
- const onInitFn = computeNode.onInitFunction;
- if ( onInitFn !== null ) {
- onInitFn.call( computeNode, { renderer: this } );
- }
- }
- nodes.updateForCompute( computeNode );
- bindings.updateForCompute( computeNode );
- const computeBindings = bindings.getForCompute( computeNode );
- const computePipeline = pipelines.getForCompute( computeNode, computeBindings );
- backend.compute( computeNodes, computeNode, computeBindings, computePipeline, dispatchSize );
- }
- backend.finishCompute( computeNodes );
- //
- nodeFrame.renderId = previousRenderId;
- //
- this.inspector.finishCompute( this.backend.getTimestampUID( computeNodes ) );
- }
- /**
- * Execute a single or an array of compute nodes.
- *
- * @async
- * @param {Node|Array<Node>} computeNodes - The compute node(s).
- * @param {number|Array<number>|IndirectStorageBufferAttribute} [dispatchSize=null]
- * - A single number representing count, or
- * - An array [x, y, z] representing dispatch size, or
- * - A IndirectStorageBufferAttribute for indirect dispatch size.
- * @return {Promise} A Promise that resolve when the compute has finished.
- */
- async computeAsync( computeNodes, dispatchSize = null ) {
- if ( this._initialized === false ) await this.init();
- this.compute( computeNodes, dispatchSize );
- }
- /**
- * Checks if the given feature is supported by the selected backend.
- *
- * @async
- * @deprecated
- * @param {string} name - The feature's name.
- * @return {Promise<boolean>} A Promise that resolves with a bool that indicates whether the feature is supported or not.
- */
- async hasFeatureAsync( name ) {
- warnOnce( 'Renderer: "hasFeatureAsync()" has been deprecated. Use "hasFeature()" and "await renderer.init();" when creating the renderer.' ); // @deprecated r181
- await this.init();
- return this.hasFeature( name );
- }
- async resolveTimestampsAsync( type = 'render' ) {
- if ( this._initialized === false ) await this.init();
- return this.backend.resolveTimestampsAsync( type );
- }
- /**
- * Checks if the given feature is supported by the selected backend. If the
- * renderer has not been initialized, this method always returns `false`.
- *
- * @param {string} name - The feature's name.
- * @return {boolean} Whether the feature is supported or not.
- */
- hasFeature( name ) {
- if ( this._initialized === false ) {
- throw new Error( 'Renderer: .hasFeature() called before the backend is initialized. Use "await renderer.init();" before before using this method.' );
- }
- return this.backend.hasFeature( name );
- }
- /**
- * Returns `true` when the renderer has been initialized.
- *
- * @return {boolean} Whether the renderer has been initialized or not.
- */
- hasInitialized() {
- return this._initialized;
- }
- /**
- * Initializes the given textures. Useful for preloading a texture rather than waiting until first render
- * (which can cause noticeable lags due to decode and GPU upload overhead).
- *
- * @async
- * @deprecated
- * @param {Texture} texture - The texture.
- * @return {Promise} A Promise that resolves when the texture has been initialized.
- */
- async initTextureAsync( texture ) {
- warnOnce( 'Renderer: "initTextureAsync()" has been deprecated. Use "initTexture()" and "await renderer.init();" when creating the renderer.' ); // @deprecated r181
- await this.init();
- this.initTexture( texture );
- }
- /**
- * Initializes the given texture. Useful for preloading a texture rather than waiting until first render
- * (which can cause noticeable lags due to decode and GPU upload overhead).
- *
- * This method can only be used if the renderer has been initialized.
- *
- * @param {Texture} texture - The texture.
- */
- initTexture( texture ) {
- if ( this._initialized === false ) {
- throw new Error( 'Renderer: .initTexture() called before the backend is initialized. Use "await renderer.init();" before before using this method.' );
- }
- this._textures.updateTexture( texture );
- }
- /**
- * Copies the current bound framebuffer into the given texture.
- *
- * @param {FramebufferTexture} framebufferTexture - The texture.
- * @param {?(Vector2|Vector4)} [rectangle=null] - A two or four dimensional vector that defines the rectangular portion of the framebuffer that should be copied.
- */
- copyFramebufferToTexture( framebufferTexture, rectangle = null ) {
- if ( rectangle !== null ) {
- if ( rectangle.isVector2 ) {
- rectangle = _vector4.set( rectangle.x, rectangle.y, framebufferTexture.image.width, framebufferTexture.image.height ).floor();
- } else if ( rectangle.isVector4 ) {
- rectangle = _vector4.copy( rectangle ).floor();
- } else {
- error( 'Renderer.copyFramebufferToTexture: Invalid rectangle.' );
- return;
- }
- } else {
- rectangle = _vector4.set( 0, 0, framebufferTexture.image.width, framebufferTexture.image.height );
- }
- //
- let renderContext = this._currentRenderContext;
- let renderTarget;
- if ( renderContext !== null ) {
- renderTarget = renderContext.renderTarget;
- } else {
- renderTarget = this._renderTarget || this._getFrameBufferTarget();
- if ( renderTarget !== null ) {
- this._textures.updateRenderTarget( renderTarget );
- renderContext = this._textures.get( renderTarget );
- }
- }
- //
- this._textures.updateTexture( framebufferTexture, { renderTarget } );
- this.backend.copyFramebufferToTexture( framebufferTexture, renderContext, rectangle );
- this._inspector.copyFramebufferToTexture( framebufferTexture );
- }
- /**
- * Copies data of the given source texture into a destination texture.
- *
- * @param {Texture} srcTexture - The source texture.
- * @param {Texture} dstTexture - The destination texture.
- * @param {Box2|Box3} [srcRegion=null] - A bounding box which describes the source region. Can be two or three-dimensional.
- * @param {Vector2|Vector3} [dstPosition=null] - A vector that represents the origin of the destination region. Can be two or three-dimensional.
- * @param {number} [srcLevel=0] - The source mip level to copy from.
- * @param {number} [dstLevel=0] - The destination mip level to copy to.
- */
- copyTextureToTexture( srcTexture, dstTexture, srcRegion = null, dstPosition = null, srcLevel = 0, dstLevel = 0 ) {
- this._textures.updateTexture( srcTexture );
- this._textures.updateTexture( dstTexture );
- this.backend.copyTextureToTexture( srcTexture, dstTexture, srcRegion, dstPosition, srcLevel, dstLevel );
- this._inspector.copyTextureToTexture( srcTexture, dstTexture );
- }
- /**
- * Reads pixel data from the given render target.
- *
- * @async
- * @param {RenderTarget} renderTarget - The render target to read from.
- * @param {number} x - The `x` coordinate of the copy region's origin.
- * @param {number} y - The `y` coordinate of the copy region's origin.
- * @param {number} width - The width of the copy region.
- * @param {number} height - The height of the copy region.
- * @param {number} [textureIndex=0] - The texture index of a MRT render target.
- * @param {number} [faceIndex=0] - The active cube face index.
- * @return {Promise<TypedArray>} A Promise that resolves when the read has been finished. The resolve provides the read data as a typed array.
- */
- async readRenderTargetPixelsAsync( renderTarget, x, y, width, height, textureIndex = 0, faceIndex = 0 ) {
- return this.backend.copyTextureToBuffer( renderTarget.textures[ textureIndex ], x, y, width, height, faceIndex );
- }
- /**
- * Analyzes the given 3D object's hierarchy and builds render lists from the
- * processed hierarchy.
- *
- * @private
- * @param {Object3D} object - The 3D object to process (usually a scene).
- * @param {Camera} camera - The camera the object is rendered with.
- * @param {number} groupOrder - The group order is derived from the `renderOrder` of groups and is used to group 3D objects within groups.
- * @param {RenderList} renderList - The current render list.
- * @param {ClippingContext} clippingContext - The current clipping context.
- */
- _projectObject( object, camera, groupOrder, renderList, clippingContext ) {
- if ( object.visible === false ) return;
- const visible = object.layers.test( camera.layers );
- if ( visible ) {
- if ( object.isGroup ) {
- groupOrder = object.renderOrder;
- if ( object.isClippingGroup && object.enabled ) clippingContext = clippingContext.getGroupContext( object );
- } else if ( object.isLOD ) {
- if ( object.autoUpdate === true ) object.update( camera );
- } else if ( object.isLight ) {
- renderList.pushLight( object );
- } else if ( object.isSprite ) {
- const frustum = camera.isArrayCamera ? _frustumArray : _frustum;
- if ( ! object.frustumCulled || frustum.intersectsSprite( object, camera ) ) {
- if ( this.sortObjects === true ) {
- _vector4.setFromMatrixPosition( object.matrixWorld ).applyMatrix4( _projScreenMatrix );
- }
- const { geometry, material } = object;
- if ( material.visible ) {
- renderList.push( object, geometry, material, groupOrder, _vector4.z, null, clippingContext );
- }
- }
- } else if ( object.isLineLoop ) {
- error( 'Renderer: Objects of type THREE.LineLoop are not supported. Please use THREE.Line or THREE.LineSegments.' );
- } else if ( object.isMesh || object.isLine || object.isPoints ) {
- const frustum = camera.isArrayCamera ? _frustumArray : _frustum;
- if ( ! object.frustumCulled || frustum.intersectsObject( object, camera ) ) {
- const { geometry, material } = object;
- if ( this.sortObjects === true ) {
- if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere();
- _vector4
- .copy( geometry.boundingSphere.center )
- .applyMatrix4( object.matrixWorld )
- .applyMatrix4( _projScreenMatrix );
- }
- if ( Array.isArray( material ) ) {
- const groups = geometry.groups;
- for ( let i = 0, l = groups.length; i < l; i ++ ) {
- const group = groups[ i ];
- const groupMaterial = material[ group.materialIndex ];
- if ( groupMaterial && groupMaterial.visible ) {
- renderList.push( object, geometry, groupMaterial, groupOrder, _vector4.z, group, clippingContext );
- }
- }
- } else if ( material.visible ) {
- renderList.push( object, geometry, material, groupOrder, _vector4.z, null, clippingContext );
- }
- }
- }
- }
- if ( object.isBundleGroup === true && this.backend.beginBundle !== undefined ) {
- const baseRenderList = renderList;
- // replace render list
- renderList = this._renderLists.get( object, camera );
- renderList.begin();
- baseRenderList.pushBundle( {
- bundleGroup: object,
- camera,
- renderList,
- } );
- renderList.finish();
- }
- const children = object.children;
- for ( let i = 0, l = children.length; i < l; i ++ ) {
- this._projectObject( children[ i ], camera, groupOrder, renderList, clippingContext );
- }
- }
- /**
- * Renders the given render bundles.
- *
- * @private
- * @param {Array<Object>} bundles - Array with render bundle data.
- * @param {Scene} sceneRef - The scene the render bundles belong to.
- * @param {LightsNode} lightsNode - The current lights node.
- */
- _renderBundles( bundles, sceneRef, lightsNode ) {
- for ( const bundle of bundles ) {
- this._renderBundle( bundle, sceneRef, lightsNode );
- }
- }
- /**
- * Renders the transparent objects from the given render lists.
- *
- * @private
- * @param {Array<Object>} renderList - The transparent render list.
- * @param {Array<Object>} doublePassList - The list of transparent objects which require a double pass (e.g. because of transmission).
- * @param {Camera} camera - The camera the render list should be rendered with.
- * @param {Scene} scene - The scene the render list belongs to.
- * @param {LightsNode} lightsNode - The current lights node.
- */
- _renderTransparents( renderList, doublePassList, camera, scene, lightsNode ) {
- if ( doublePassList.length > 0 ) {
- // render back side
- for ( const { material } of doublePassList ) {
- material.side = BackSide;
- }
- this._renderObjects( doublePassList, camera, scene, lightsNode, 'backSide' );
- // render front side
- for ( const { material } of doublePassList ) {
- material.side = FrontSide;
- }
- this._renderObjects( renderList, camera, scene, lightsNode );
- // restore
- for ( const { material } of doublePassList ) {
- material.side = DoubleSide;
- }
- } else {
- this._renderObjects( renderList, camera, scene, lightsNode );
- }
- }
- /**
- * Renders the objects from the given render list.
- *
- * @private
- * @param {Array<Object>} renderList - The render list.
- * @param {Camera} camera - The camera the render list should be rendered with.
- * @param {Scene} scene - The scene the render list belongs to.
- * @param {LightsNode} lightsNode - The current lights node.
- * @param {?string} [passId=null] - An optional ID for identifying the pass.
- */
- _renderObjects( renderList, camera, scene, lightsNode, passId = null ) {
- for ( let i = 0, il = renderList.length; i < il; i ++ ) {
- const { object, geometry, material, group, clippingContext } = renderList[ i ];
- this._currentRenderObjectFunction( object, scene, camera, geometry, material, group, lightsNode, clippingContext, passId );
- }
- }
- /**
- * Retrieves shadow nodes for the given material. This is used to setup shadow passes.
- * The result is cached per material and updated when the material's version changes.
- *
- * @private
- * @param {Material} material
- * @returns {Object} - The shadow nodes for the material.
- */
- _getShadowNodes( material ) {
- const version = material.version;
- let cache = this._cacheShadowNodes.get( material );
- if ( cache === undefined || cache.version !== version ) {
- const hasMap = material.map !== null;
- const hasColorNode = material.colorNode && material.colorNode.isNode;
- const hasCastShadowNode = material.castShadowNode && material.castShadowNode.isNode;
- let positionNode = null;
- let colorNode = null;
- let depthNode = null;
- if ( hasMap || hasColorNode || hasCastShadowNode ) {
- let shadowRGB;
- let shadowAlpha;
- if ( hasCastShadowNode ) {
- shadowRGB = material.castShadowNode.rgb;
- shadowAlpha = material.castShadowNode.a;
- } else {
- shadowRGB = vec3( 0 );
- shadowAlpha = float( 1 );
- }
- if ( hasMap ) {
- shadowAlpha = shadowAlpha.mul( reference( 'map', 'texture', material ).a );
- }
- if ( hasColorNode ) {
- shadowAlpha = shadowAlpha.mul( material.colorNode.a );
- }
- colorNode = vec4( shadowRGB, shadowAlpha );
- }
- if ( material.depthNode && material.depthNode.isNode ) {
- depthNode = material.depthNode;
- }
- if ( material.castShadowPositionNode && material.castShadowPositionNode.isNode ) {
- positionNode = material.castShadowPositionNode;
- } else if ( material.positionNode && material.positionNode.isNode ) {
- positionNode = material.positionNode;
- }
- cache = {
- version,
- colorNode,
- depthNode,
- positionNode
- };
- this._cacheShadowNodes.set( material, cache );
- }
- return cache;
- }
- /**
- * This method represents the default render object function that manages the render lifecycle
- * of the object.
- *
- * @param {Object3D} object - The 3D object.
- * @param {Scene} scene - The scene the 3D object belongs to.
- * @param {Camera} camera - The camera the object should be rendered with.
- * @param {BufferGeometry} geometry - The object's geometry.
- * @param {Material} material - The object's material.
- * @param {?Object} group - Only relevant for objects using multiple materials. This represents a group entry from the respective `BufferGeometry`.
- * @param {LightsNode} lightsNode - The current lights node.
- * @param {?ClippingContext} clippingContext - The clipping context.
- * @param {?string} [passId=null] - An optional ID for identifying the pass.
- */
- renderObject( object, scene, camera, geometry, material, group, lightsNode, clippingContext = null, passId = null ) {
- let materialOverride = false;
- let materialColorNode;
- let materialDepthNode;
- let materialPositionNode;
- let materialSide;
- //
- object.onBeforeRender( this, scene, camera, geometry, material, group );
- //
- if ( material.allowOverride === true && scene.overrideMaterial !== null ) {
- const overrideMaterial = scene.overrideMaterial;
- materialOverride = true;
- // store original nodes
- materialColorNode = scene.overrideMaterial.colorNode;
- materialDepthNode = scene.overrideMaterial.depthNode;
- materialPositionNode = scene.overrideMaterial.positionNode;
- materialSide = scene.overrideMaterial.side;
- if ( material.positionNode && material.positionNode.isNode ) {
- overrideMaterial.positionNode = material.positionNode;
- }
- overrideMaterial.alphaTest = material.alphaTest;
- overrideMaterial.alphaMap = material.alphaMap;
- overrideMaterial.transparent = material.transparent || material.transmission > 0 ||
- ( material.transmissionNode && material.transmissionNode.isNode ) ||
- ( material.backdropNode && material.backdropNode.isNode );
- if ( overrideMaterial.isShadowPassMaterial ) {
- const { colorNode, depthNode, positionNode } = this._getShadowNodes( material );
- overrideMaterial.side = material.shadowSide === null ? material.side : material.shadowSide;
- if ( colorNode !== null ) overrideMaterial.colorNode = colorNode;
- if ( depthNode !== null ) overrideMaterial.depthNode = depthNode;
- if ( positionNode !== null ) overrideMaterial.positionNode = positionNode;
- }
- material = overrideMaterial;
- }
- //
- if ( material.transparent === true && material.side === DoubleSide && material.forceSinglePass === false ) {
- material.side = BackSide;
- this._handleObjectFunction( object, material, scene, camera, lightsNode, group, clippingContext, 'backSide' ); // create backSide pass id
- material.side = FrontSide;
- this._handleObjectFunction( object, material, scene, camera, lightsNode, group, clippingContext, passId ); // use default pass id
- material.side = DoubleSide;
- } else {
- this._handleObjectFunction( object, material, scene, camera, lightsNode, group, clippingContext, passId );
- }
- //
- if ( materialOverride ) {
- scene.overrideMaterial.colorNode = materialColorNode;
- scene.overrideMaterial.depthNode = materialDepthNode;
- scene.overrideMaterial.positionNode = materialPositionNode;
- scene.overrideMaterial.side = materialSide;
- }
- //
- object.onAfterRender( this, scene, camera, geometry, material, group );
- }
- /**
- * This method represents the default `_handleObjectFunction` implementation which creates
- * a render object from the given data and performs the draw command with the selected backend.
- *
- * @private
- * @param {Object3D} object - The 3D object.
- * @param {Material} material - The object's material.
- * @param {Scene} scene - The scene the 3D object belongs to.
- * @param {Camera} camera - The camera the object should be rendered with.
- * @param {LightsNode} lightsNode - The current lights node.
- * @param {?{start: number, count: number}} group - Only relevant for objects using multiple materials. This represents a group entry from the respective `BufferGeometry`.
- * @param {ClippingContext} clippingContext - The clipping context.
- * @param {string} [passId] - An optional ID for identifying the pass.
- */
- _renderObjectDirect( object, material, scene, camera, lightsNode, group, clippingContext, passId ) {
- const renderObject = this._objects.get( object, material, scene, camera, lightsNode, this._currentRenderContext, clippingContext, passId );
- renderObject.drawRange = object.geometry.drawRange;
- renderObject.group = group;
- //
- const needsRefresh = this._nodes.needsRefresh( renderObject );
- if ( needsRefresh ) {
- this._nodes.updateBefore( renderObject );
- this._geometries.updateForRender( renderObject );
- this._nodes.updateForRender( renderObject );
- this._bindings.updateForRender( renderObject );
- }
- this._pipelines.updateForRender( renderObject );
- //
- if ( this._currentRenderBundle !== null ) {
- const renderBundleData = this.backend.get( this._currentRenderBundle );
- renderBundleData.renderObjects.push( renderObject );
- renderObject.bundle = this._currentRenderBundle.bundleGroup;
- }
- this.backend.draw( renderObject, this.info );
- if ( needsRefresh ) this._nodes.updateAfter( renderObject );
- }
- /**
- * A different implementation for `_handleObjectFunction` which only makes sure the object is ready for rendering.
- * Used in `compileAsync()`.
- *
- * @private
- * @param {Object3D} object - The 3D object.
- * @param {Material} material - The object's material.
- * @param {Scene} scene - The scene the 3D object belongs to.
- * @param {Camera} camera - The camera the object should be rendered with.
- * @param {LightsNode} lightsNode - The current lights node.
- * @param {?{start: number, count: number}} group - Only relevant for objects using multiple materials. This represents a group entry from the respective `BufferGeometry`.
- * @param {ClippingContext} clippingContext - The clipping context.
- * @param {string} [passId] - An optional ID for identifying the pass.
- */
- _createObjectPipeline( object, material, scene, camera, lightsNode, group, clippingContext, passId ) {
- const renderObject = this._objects.get( object, material, scene, camera, lightsNode, this._currentRenderContext, clippingContext, passId );
- renderObject.drawRange = object.geometry.drawRange;
- renderObject.group = group;
- //
- this._nodes.updateBefore( renderObject );
- this._geometries.updateForRender( renderObject );
- this._nodes.updateForRender( renderObject );
- this._bindings.updateForRender( renderObject );
- this._pipelines.getForRender( renderObject, this._compilationPromises );
- this._nodes.updateAfter( renderObject );
- }
- /**
- * Callback when the canvas has been resized.
- *
- * @private
- */
- _onCanvasTargetResize() {
- if ( this._initialized ) this.backend.updateSize();
- }
- /**
- * Alias for `compileAsync()`.
- *
- * @method
- * @param {Object3D} scene - The scene or 3D object to precompile.
- * @param {Camera} camera - The camera that is used to render the scene.
- * @param {Scene} targetScene - If the first argument is a 3D object, this parameter must represent the scene the 3D object is going to be added.
- * @return {function(Object3D, Camera, ?Scene): Promise|undefined} A Promise that resolves when the compile has been finished.
- */
- get compile() {
- return this.compileAsync;
- }
- }
- /**
- * A binding represents the connection between a resource (like a texture, sampler
- * or uniform buffer) and the resource definition in a shader stage.
- *
- * This module is an abstract base class for all concrete bindings types.
- *
- * @abstract
- * @private
- */
- class Binding {
- /**
- * Constructs a new binding.
- *
- * @param {string} [name=''] - The binding's name.
- */
- constructor( name = '' ) {
- /**
- * The binding's name.
- *
- * @type {string}
- */
- this.name = name;
- /**
- * A bitmask that defines in what shader stages the
- * binding's resource is accessible.
- *
- * @type {number}
- */
- this.visibility = 0;
- }
- /**
- * Makes sure binding's resource is visible for the given shader stage.
- *
- * @param {number} visibility - The shader stage.
- */
- setVisibility( visibility ) {
- this.visibility |= visibility;
- }
- /**
- * The shader stages in which the binding's resource is visible.
- *
- * @return {number} The visibility bitmask.
- */
- getVisibility() {
- return this.visibility;
- }
- /**
- * Clones the binding.
- *
- * @return {Binding} The cloned binding.
- */
- clone() {
- return Object.assign( new this.constructor(), this );
- }
- }
- /**
- * This function is usually called with the length in bytes of an array buffer.
- * It returns an padded value which ensure chunk size alignment according to STD140 layout.
- *
- * @function
- * @param {number} floatLength - The buffer length.
- * @return {number} The padded length.
- */
- function getFloatLength( floatLength ) {
- // ensure chunk size alignment (STD140 layout)
- return floatLength + ( ( GPU_CHUNK_BYTES - ( floatLength % GPU_CHUNK_BYTES ) ) % GPU_CHUNK_BYTES );
- }
- /**
- * Represents a buffer binding type.
- *
- * @private
- * @abstract
- * @augments Binding
- */
- class Buffer extends Binding {
- /**
- * Constructs a new buffer.
- *
- * @param {string} name - The buffer's name.
- * @param {TypedArray} [buffer=null] - The buffer.
- */
- constructor( name, buffer = null ) {
- super( name );
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isBuffer = true;
- /**
- * The bytes per element.
- *
- * @type {number}
- */
- this.bytesPerElement = Float32Array.BYTES_PER_ELEMENT;
- /**
- * A reference to the internal buffer.
- *
- * @private
- * @type {TypedArray}
- */
- this._buffer = buffer;
- /**
- * An array of update ranges.
- *
- * @private
- * @type {Array<{start: number, count: number}>}
- */
- this._updateRanges = [];
- }
- /**
- * The array of update ranges.
- *
- * @type {Array<{start: number, count: number}>}
- */
- get updateRanges() {
- return this._updateRanges;
- }
- /**
- * Adds an update range.
- *
- * @param {number} start - The start index.
- * @param {number} count - The number of elements.
- */
- addUpdateRange( start, count ) {
- this.updateRanges.push( { start, count } );
- }
- /**
- * Clears all update ranges.
- */
- clearUpdateRanges() {
- this.updateRanges.length = 0;
- }
- /**
- * The buffer's byte length.
- *
- * @type {number}
- * @readonly
- */
- get byteLength() {
- return getFloatLength( this._buffer.byteLength );
- }
- /**
- * A reference to the internal buffer.
- *
- * @type {Float32Array}
- * @readonly
- */
- get buffer() {
- return this._buffer;
- }
- /**
- * Updates the binding.
- *
- * @return {boolean} Whether the buffer has been updated and must be
- * uploaded to the GPU.
- */
- update() {
- return true;
- }
- }
- /**
- * Represents a uniform buffer binding type.
- *
- * @private
- * @augments Buffer
- */
- class UniformBuffer extends Buffer {
- /**
- * Constructs a new uniform buffer.
- *
- * @param {string} name - The buffer's name.
- * @param {TypedArray} [buffer=null] - The buffer.
- */
- constructor( name, buffer = null ) {
- super( name, buffer );
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isUniformBuffer = true;
- }
- }
- let _id$4 = 0;
- /**
- * A special form of uniform buffer binding type.
- * It's buffer value is managed by a node object.
- *
- * @private
- * @augments UniformBuffer
- */
- class NodeUniformBuffer extends UniformBuffer {
- /**
- * Constructs a new node-based uniform buffer.
- *
- * @param {BufferNode} nodeUniform - The uniform buffer node.
- * @param {UniformGroupNode} groupNode - The uniform group node.
- */
- constructor( nodeUniform, groupNode ) {
- super( 'UniformBuffer_' + _id$4 ++, nodeUniform ? nodeUniform.value : null );
- /**
- * The uniform buffer node.
- *
- * @type {BufferNode}
- */
- this.nodeUniform = nodeUniform;
- /**
- * The uniform group node.
- *
- * @type {UniformGroupNode}
- */
- this.groupNode = groupNode;
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isNodeUniformBuffer = true;
- }
- /**
- * The array of update ranges.
- *
- * @param {Array<{start: number, count: number}>} value - The update ranges.
- */
- set updateRanges( value ) {
- this.nodeUniform.updateRanges = value;
- }
- /**
- * The array of update ranges.
- *
- * @type {Array<{start: number, count: number}>}
- */
- get updateRanges() {
- return this.nodeUniform.updateRanges;
- }
- /**
- * Adds a range of data in the data array to be updated on the GPU.
- *
- * @param {number} start - Position at which to start update.
- * @param {number} count - The number of components to update.
- */
- addUpdateRange( start, count ) {
- this.nodeUniform.addUpdateRange( start, count );
- }
- /**
- * Clears all update ranges.
- */
- clearUpdateRanges() {
- this.nodeUniform.clearUpdateRanges();
- }
- /**
- * The uniform buffer.
- *
- * @type {Float32Array}
- */
- get buffer() {
- return this.nodeUniform.value;
- }
- }
- /**
- * This class represents a uniform buffer binding but with
- * an API that allows to maintain individual uniform objects.
- *
- * @private
- * @augments UniformBuffer
- */
- class UniformsGroup extends UniformBuffer {
- /**
- * Constructs a new uniforms group.
- *
- * @param {string} name - The group's name.
- */
- constructor( name ) {
- super( name );
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isUniformsGroup = true;
- /**
- * An array with the raw uniform values.
- *
- * @private
- * @type {?Array<number>}
- * @default null
- */
- this._values = null;
- /**
- * An array of uniform objects.
- *
- * The order of uniforms in this array must match the order of uniforms in the shader.
- *
- * @type {Array<Uniform>}
- */
- this.uniforms = [];
- }
- /**
- * Adds a uniform to this group.
- *
- * @param {Uniform} uniform - The uniform to add.
- * @return {UniformsGroup} A reference to this group.
- */
- addUniform( uniform ) {
- this.uniforms.push( uniform );
- return this;
- }
- /**
- * Removes a uniform from this group.
- *
- * @param {Uniform} uniform - The uniform to remove.
- * @return {UniformsGroup} A reference to this group.
- */
- removeUniform( uniform ) {
- const index = this.uniforms.indexOf( uniform );
- if ( index !== -1 ) {
- this.uniforms.splice( index, 1 );
- }
- return this;
- }
- /**
- * An array with the raw uniform values.
- *
- * @type {Array<number>}
- */
- get values() {
- if ( this._values === null ) {
- this._values = Array.from( this.buffer );
- }
- return this._values;
- }
- /**
- * A Float32 array buffer with the uniform values.
- *
- * @type {Float32Array}
- */
- get buffer() {
- let buffer = this._buffer;
- if ( buffer === null ) {
- const byteLength = this.byteLength;
- buffer = new Float32Array( new ArrayBuffer( byteLength ) );
- this._buffer = buffer;
- }
- return buffer;
- }
- /**
- * The byte length of the buffer with correct buffer alignment.
- *
- * @type {number}
- */
- get byteLength() {
- const bytesPerElement = this.bytesPerElement;
- let offset = 0; // global buffer offset in bytes
- for ( let i = 0, l = this.uniforms.length; i < l; i ++ ) {
- const uniform = this.uniforms[ i ];
- const boundary = uniform.boundary;
- const itemSize = uniform.itemSize * bytesPerElement; // size of the uniform in bytes
- const chunkOffset = offset % GPU_CHUNK_BYTES; // offset in the current chunk
- const chunkPadding = chunkOffset % boundary; // required padding to match boundary
- const chunkStart = chunkOffset + chunkPadding; // start position in the current chunk for the data
- offset += chunkPadding;
- // Check for chunk overflow
- if ( chunkStart !== 0 && ( GPU_CHUNK_BYTES - chunkStart ) < itemSize ) {
- // Add padding to the end of the chunk
- offset += ( GPU_CHUNK_BYTES - chunkStart );
- }
- uniform.offset = offset / bytesPerElement;
- offset += itemSize;
- }
- return Math.ceil( offset / GPU_CHUNK_BYTES ) * GPU_CHUNK_BYTES;
- }
- /**
- * Updates this group by updating each uniform object of
- * the internal uniform list. The uniform objects check if their
- * values has actually changed so this method only returns
- * `true` if there is a real value change.
- *
- * @return {boolean} Whether the uniforms have been updated and
- * must be uploaded to the GPU.
- */
- update() {
- let updated = false;
- for ( const uniform of this.uniforms ) {
- if ( this.updateByType( uniform ) === true ) {
- updated = true;
- }
- }
- return updated;
- }
- /**
- * Updates a given uniform by calling an update method matching
- * the uniforms type.
- *
- * @param {Uniform} uniform - The uniform to update.
- * @return {boolean} Whether the uniform has been updated or not.
- */
- updateByType( uniform ) {
- if ( uniform.isNumberUniform ) return this.updateNumber( uniform );
- if ( uniform.isVector2Uniform ) return this.updateVector2( uniform );
- if ( uniform.isVector3Uniform ) return this.updateVector3( uniform );
- if ( uniform.isVector4Uniform ) return this.updateVector4( uniform );
- if ( uniform.isColorUniform ) return this.updateColor( uniform );
- if ( uniform.isMatrix3Uniform ) return this.updateMatrix3( uniform );
- if ( uniform.isMatrix4Uniform ) return this.updateMatrix4( uniform );
- error( 'WebGPUUniformsGroup: Unsupported uniform type.', uniform );
- }
- /**
- * Updates a given Number uniform.
- *
- * @param {NumberUniform} uniform - The Number uniform.
- * @return {boolean} Whether the uniform has been updated or not.
- */
- updateNumber( uniform ) {
- let updated = false;
- const a = this.values;
- const v = uniform.getValue();
- const offset = uniform.offset;
- const type = uniform.getType();
- if ( a[ offset ] !== v ) {
- const b = this._getBufferForType( type );
- b[ offset ] = a[ offset ] = v;
- updated = true;
- }
- return updated;
- }
- /**
- * Updates a given Vector2 uniform.
- *
- * @param {Vector2Uniform} uniform - The Vector2 uniform.
- * @return {boolean} Whether the uniform has been updated or not.
- */
- updateVector2( uniform ) {
- let updated = false;
- const a = this.values;
- const v = uniform.getValue();
- const offset = uniform.offset;
- const type = uniform.getType();
- if ( a[ offset + 0 ] !== v.x || a[ offset + 1 ] !== v.y ) {
- const b = this._getBufferForType( type );
- b[ offset + 0 ] = a[ offset + 0 ] = v.x;
- b[ offset + 1 ] = a[ offset + 1 ] = v.y;
- updated = true;
- }
- return updated;
- }
- /**
- * Updates a given Vector3 uniform.
- *
- * @param {Vector3Uniform} uniform - The Vector3 uniform.
- * @return {boolean} Whether the uniform has been updated or not.
- */
- updateVector3( uniform ) {
- let updated = false;
- const a = this.values;
- const v = uniform.getValue();
- const offset = uniform.offset;
- const type = uniform.getType();
- if ( a[ offset + 0 ] !== v.x || a[ offset + 1 ] !== v.y || a[ offset + 2 ] !== v.z ) {
- const b = this._getBufferForType( type );
- b[ offset + 0 ] = a[ offset + 0 ] = v.x;
- b[ offset + 1 ] = a[ offset + 1 ] = v.y;
- b[ offset + 2 ] = a[ offset + 2 ] = v.z;
- updated = true;
- }
- return updated;
- }
- /**
- * Updates a given Vector4 uniform.
- *
- * @param {Vector4Uniform} uniform - The Vector4 uniform.
- * @return {boolean} Whether the uniform has been updated or not.
- */
- updateVector4( uniform ) {
- let updated = false;
- const a = this.values;
- const v = uniform.getValue();
- const offset = uniform.offset;
- const type = uniform.getType();
- if ( a[ offset + 0 ] !== v.x || a[ offset + 1 ] !== v.y || a[ offset + 2 ] !== v.z || a[ offset + 4 ] !== v.w ) {
- const b = this._getBufferForType( type );
- b[ offset + 0 ] = a[ offset + 0 ] = v.x;
- b[ offset + 1 ] = a[ offset + 1 ] = v.y;
- b[ offset + 2 ] = a[ offset + 2 ] = v.z;
- b[ offset + 3 ] = a[ offset + 3 ] = v.w;
- updated = true;
- }
- return updated;
- }
- /**
- * Updates a given Color uniform.
- *
- * @param {ColorUniform} uniform - The Color uniform.
- * @return {boolean} Whether the uniform has been updated or not.
- */
- updateColor( uniform ) {
- let updated = false;
- const a = this.values;
- const c = uniform.getValue();
- const offset = uniform.offset;
- if ( a[ offset + 0 ] !== c.r || a[ offset + 1 ] !== c.g || a[ offset + 2 ] !== c.b ) {
- const b = this.buffer;
- b[ offset + 0 ] = a[ offset + 0 ] = c.r;
- b[ offset + 1 ] = a[ offset + 1 ] = c.g;
- b[ offset + 2 ] = a[ offset + 2 ] = c.b;
- updated = true;
- }
- return updated;
- }
- /**
- * Updates a given Matrix3 uniform.
- *
- * @param {Matrix3Uniform} uniform - The Matrix3 uniform.
- * @return {boolean} Whether the uniform has been updated or not.
- */
- updateMatrix3( uniform ) {
- let updated = false;
- const a = this.values;
- const e = uniform.getValue().elements;
- const offset = uniform.offset;
- if ( a[ offset + 0 ] !== e[ 0 ] || a[ offset + 1 ] !== e[ 1 ] || a[ offset + 2 ] !== e[ 2 ] ||
- a[ offset + 4 ] !== e[ 3 ] || a[ offset + 5 ] !== e[ 4 ] || a[ offset + 6 ] !== e[ 5 ] ||
- a[ offset + 8 ] !== e[ 6 ] || a[ offset + 9 ] !== e[ 7 ] || a[ offset + 10 ] !== e[ 8 ] ) {
- const b = this.buffer;
- b[ offset + 0 ] = a[ offset + 0 ] = e[ 0 ];
- b[ offset + 1 ] = a[ offset + 1 ] = e[ 1 ];
- b[ offset + 2 ] = a[ offset + 2 ] = e[ 2 ];
- b[ offset + 4 ] = a[ offset + 4 ] = e[ 3 ];
- b[ offset + 5 ] = a[ offset + 5 ] = e[ 4 ];
- b[ offset + 6 ] = a[ offset + 6 ] = e[ 5 ];
- b[ offset + 8 ] = a[ offset + 8 ] = e[ 6 ];
- b[ offset + 9 ] = a[ offset + 9 ] = e[ 7 ];
- b[ offset + 10 ] = a[ offset + 10 ] = e[ 8 ];
- updated = true;
- }
- return updated;
- }
- /**
- * Updates a given Matrix4 uniform.
- *
- * @param {Matrix4Uniform} uniform - The Matrix4 uniform.
- * @return {boolean} Whether the uniform has been updated or not.
- */
- updateMatrix4( uniform ) {
- let updated = false;
- const a = this.values;
- const e = uniform.getValue().elements;
- const offset = uniform.offset;
- if ( arraysEqual( a, e, offset ) === false ) {
- const b = this.buffer;
- b.set( e, offset );
- setArray( a, e, offset );
- updated = true;
- }
- return updated;
- }
- /**
- * Returns a typed array that matches the given data type.
- *
- * @private
- * @param {string} type - The data type.
- * @return {TypedArray} The typed array.
- */
- _getBufferForType( type ) {
- if ( type === 'int' || type === 'ivec2' || type === 'ivec3' || type === 'ivec4' ) return new Int32Array( this.buffer.buffer );
- if ( type === 'uint' || type === 'uvec2' || type === 'uvec3' || type === 'uvec4' ) return new Uint32Array( this.buffer.buffer );
- return this.buffer;
- }
- }
- /**
- * Sets the values of the second array to the first array.
- *
- * @private
- * @param {TypedArray} a - The first array.
- * @param {TypedArray} b - The second array.
- * @param {number} offset - An index offset for the first array.
- */
- function setArray( a, b, offset ) {
- for ( let i = 0, l = b.length; i < l; i ++ ) {
- a[ offset + i ] = b[ i ];
- }
- }
- /**
- * Returns `true` if the given arrays are equal.
- *
- * @private
- * @param {TypedArray} a - The first array.
- * @param {TypedArray} b - The second array.
- * @param {number} offset - An index offset for the first array.
- * @return {boolean} Whether the given arrays are equal or not.
- */
- function arraysEqual( a, b, offset ) {
- for ( let i = 0, l = b.length; i < l; i ++ ) {
- if ( a[ offset + i ] !== b[ i ] ) return false;
- }
- return true;
- }
- let _id$3 = 0;
- /**
- * A special form of uniforms group that represents
- * the individual uniforms as node-based uniforms.
- *
- * @private
- * @augments UniformsGroup
- */
- class NodeUniformsGroup extends UniformsGroup {
- /**
- * Constructs a new node-based uniforms group.
- *
- * @param {string} name - The group's name.
- * @param {UniformGroupNode} groupNode - The uniform group node.
- */
- constructor( name, groupNode ) {
- super( name );
- /**
- * The group's ID.
- *
- * @type {number}
- */
- this.id = _id$3 ++;
- /**
- * The uniform group node.
- *
- * @type {UniformGroupNode}
- */
- this.groupNode = groupNode;
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isNodeUniformsGroup = true;
- }
- }
- /**
- * Represents a sampler binding type.
- *
- * @private
- * @augments Binding
- */
- class Sampler extends Binding {
- /**
- * Constructs a new sampler.
- *
- * @param {string} name - The samplers's name.
- * @param {?Texture} texture - The texture this binding is referring to.
- */
- constructor( name, texture ) {
- super( name );
- /**
- * The texture the sampler is referring to.
- *
- * @private
- * @type {?Texture}
- */
- this._texture = null;
- /**
- * An event listener which is added to {@link texture}'s dispose event.
- *
- * @private
- * @type {Function}
- */
- this._onTextureDispose = () => {
- this.generation = null;
- this.version = 0;
- };
- // Assignment to the texture via a setter must occur after "_onTextureDispose" is initialized.
- this.texture = texture;
- /**
- * The binding's version.
- *
- * @type {number}
- */
- this.version = texture ? texture.version : 0;
- /**
- * The binding's generation which is an additional version
- * qualifier.
- *
- * @type {?number}
- * @default null
- */
- this.generation = null;
- /**
- * The binding's sampler key.
- *
- * @type {string}
- * @default ''
- */
- this.samplerKey = '';
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isSampler = true;
- }
- /**
- * Sets the texture of this sampler.
- *
- * @param {Texture} value - The texture to set.
- */
- set texture( value ) {
- if ( this._texture === value ) return;
- if ( this._texture ) {
- this._texture.removeEventListener( 'dispose', this._onTextureDispose );
- }
- this._texture = value;
- this.generation = null;
- this.version = 0;
- if ( this._texture ) {
- this._texture.addEventListener( 'dispose', this._onTextureDispose );
- }
- }
- /**
- * Gets the texture of this sampler.
- * @return {?Texture} The texture.
- */
- get texture() {
- return this._texture;
- }
- /**
- * Updates the binding.
- *
- * @return {boolean} Whether the texture has been updated and must be
- * uploaded to the GPU.
- */
- update() {
- const { texture, version } = this;
- if ( version !== texture.version ) {
- this.version = texture.version;
- return true;
- }
- return false;
- }
- clone() {
- const clonedSampler = super.clone();
- // fix dispose handler for cloned instances
- // TODO: Find better solution, see #31747
- clonedSampler._texture = null;
- clonedSampler._onTextureDispose = () => {
- clonedSampler.generation = null;
- clonedSampler.version = 0;
- };
- clonedSampler.texture = this.texture;
- return clonedSampler;
- }
- }
- let _id$2 = 0;
- /**
- * Represents a sampled texture binding type.
- *
- * @private
- * @augments Sampler
- */
- class SampledTexture extends Sampler {
- /**
- * Constructs a new sampled texture.
- *
- * @param {string} name - The sampled texture's name.
- * @param {?Texture} texture - The texture this binding is referring to.
- */
- constructor( name, texture ) {
- super( name, texture );
- /**
- * This identifier.
- *
- * @type {number}
- */
- this.id = _id$2 ++;
- /**
- * Whether the texture is a storage texture or not.
- *
- * @type {boolean}
- * @default false
- */
- this.store = false;
- /**
- * The mip level to bind for storage textures.
- *
- * @type {number}
- * @default 0
- */
- this.mipLevel = 0;
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isSampledTexture = true;
- }
- }
- /**
- * A special form of sampled texture binding type.
- * It's texture value is managed by a node object.
- *
- * @private
- * @augments SampledTexture
- */
- class NodeSampledTexture extends SampledTexture {
- /**
- * Constructs a new node-based sampled texture.
- *
- * @param {string} name - The textures's name.
- * @param {TextureNode} textureNode - The texture node.
- * @param {UniformGroupNode} groupNode - The uniform group node.
- * @param {?string} [access=null] - The access type.
- */
- constructor( name, textureNode, groupNode, access = null ) {
- super( name, textureNode ? textureNode.value : null );
- /**
- * The texture node.
- *
- * @type {TextureNode}
- */
- this.textureNode = textureNode;
- /**
- * The uniform group node.
- *
- * @type {UniformGroupNode}
- */
- this.groupNode = groupNode;
- /**
- * The access type.
- *
- * @type {?string}
- * @default null
- */
- this.access = access;
- }
- /**
- * Updates the binding.
- *
- * @return {boolean} Whether the texture has been updated and must be
- * uploaded to the GPU.
- */
- update() {
- const { textureNode } = this;
- if ( this.texture !== textureNode.value ) {
- this.texture = textureNode.value;
- return true;
- }
- return super.update();
- }
- }
- /**
- * A special form of sampled cube texture binding type.
- * It's texture value is managed by a node object.
- *
- * @private
- * @augments NodeSampledTexture
- */
- class NodeSampledCubeTexture extends NodeSampledTexture {
- /**
- * Constructs a new node-based sampled cube texture.
- *
- * @param {string} name - The textures's name.
- * @param {TextureNode} textureNode - The texture node.
- * @param {UniformGroupNode} groupNode - The uniform group node.
- * @param {?string} [access=null] - The access type.
- */
- constructor( name, textureNode, groupNode, access = null ) {
- super( name, textureNode, groupNode, access );
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isSampledCubeTexture = true;
- }
- }
- /**
- * A special form of sampled 3D texture binding type.
- * It's texture value is managed by a node object.
- *
- * @private
- * @augments NodeSampledTexture
- */
- class NodeSampledTexture3D extends NodeSampledTexture {
- /**
- * Constructs a new node-based sampled 3D texture.
- *
- * @param {string} name - The textures's name.
- * @param {TextureNode} textureNode - The texture node.
- * @param {UniformGroupNode} groupNode - The uniform group node.
- * @param {?string} [access=null] - The access type.
- */
- constructor( name, textureNode, groupNode, access = null ) {
- super( name, textureNode, groupNode, access );
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isSampledTexture3D = true;
- }
- }
- const glslPolyfills = {
- bitcast_int_uint: new CodeNode( /* glsl */'uint tsl_bitcast_int_to_uint ( int x ) { return floatBitsToUint( intBitsToFloat ( x ) ); }' ),
- bitcast_uint_int: new CodeNode( /* glsl */'uint tsl_bitcast_uint_to_int ( uint x ) { return floatBitsToInt( uintBitsToFloat ( x ) ); }' )
- };
- const glslMethods = {
- textureDimensions: 'textureSize',
- equals: 'equal',
- bitcast_float_int: 'floatBitsToInt',
- bitcast_int_float: 'intBitsToFloat',
- bitcast_uint_float: 'uintBitsToFloat',
- bitcast_float_uint: 'floatBitsToUint',
- bitcast_uint_int: 'tsl_bitcast_uint_to_int',
- bitcast_int_uint: 'tsl_bitcast_int_to_uint',
- floatpack_snorm_2x16: 'packSnorm2x16',
- floatpack_unorm_2x16: 'packUnorm2x16',
- floatpack_float16_2x16: 'packHalf2x16',
- floatunpack_snorm_2x16: 'unpackSnorm2x16',
- floatunpack_unorm_2x16: 'unpackUnorm2x16',
- floatunpack_float16_2x16: 'unpackHalf2x16'
- };
- const precisionLib = {
- low: 'lowp',
- medium: 'mediump',
- high: 'highp'
- };
- const supports$1 = {
- swizzleAssign: true,
- storageBuffer: false
- };
- const interpolationTypeMap = {
- perspective: 'smooth',
- linear: 'noperspective'
- };
- const interpolationModeMap = {
- 'centroid': 'centroid'
- };
- const defaultPrecisions = `
- precision highp float;
- precision highp int;
- precision highp sampler2D;
- precision highp sampler3D;
- precision highp samplerCube;
- precision highp sampler2DArray;
- precision highp usampler2D;
- precision highp usampler3D;
- precision highp usamplerCube;
- precision highp usampler2DArray;
- precision highp isampler2D;
- precision highp isampler3D;
- precision highp isamplerCube;
- precision highp isampler2DArray;
- precision lowp sampler2DShadow;
- precision lowp sampler2DArrayShadow;
- precision lowp samplerCubeShadow;
- `;
- /**
- * A node builder targeting GLSL.
- *
- * This module generates GLSL shader code from node materials and also
- * generates the respective bindings and vertex buffer definitions. These
- * data are later used by the renderer to create render and compute pipelines
- * for render objects.
- *
- * @augments NodeBuilder
- */
- class GLSLNodeBuilder extends NodeBuilder {
- /**
- * Constructs a new GLSL node builder renderer.
- *
- * @param {Object3D} object - The 3D object.
- * @param {Renderer} renderer - The renderer.
- */
- constructor( object, renderer ) {
- super( object, renderer, new GLSLNodeParser() );
- /**
- * A dictionary holds for each shader stage ('vertex', 'fragment', 'compute')
- * another dictionary which manages UBOs per group ('render','frame','object').
- *
- * @type {Object<string,Object<string,NodeUniformsGroup>>}
- */
- this.uniformGroups = {};
- /**
- * An array that holds objects defining the varying and attribute data in
- * context of Transform Feedback.
- *
- * @type {Array<Object<string,AttributeNode|string>>}
- */
- this.transforms = [];
- /**
- * A dictionary that holds for each shader stage a Map of used extensions.
- *
- * @type {Object<string,Map<string,Object>>}
- */
- this.extensions = {};
- /**
- * A dictionary that holds for each shader stage an Array of used builtins.
- *
- * @type {Object<string,Array<string>>}
- */
- this.builtins = { vertex: [], fragment: [], compute: [] };
- }
- /**
- * Checks if the given texture requires a manual conversion to the working color space.
- *
- * @param {Texture} texture - The texture to check.
- * @return {boolean} Whether the given texture requires a conversion to working color space or not.
- */
- needsToWorkingColorSpace( texture ) {
- return texture.isVideoTexture === true && texture.colorSpace !== NoColorSpace;
- }
- /**
- * Includes the given method name into the current
- * function node.
- *
- * @private
- * @param {string} name - The method name to include.
- * @return {CodeNode} The respective code node.
- */
- _include( name ) {
- const codeNode = glslPolyfills[ name ];
- codeNode.build( this );
- this.addInclude( codeNode );
- return codeNode;
- }
- /**
- * Returns the native shader method name for a given generic name.
- *
- * @param {string} method - The method name to resolve.
- * @return {string} The resolved GLSL method name.
- */
- getMethod( method ) {
- if ( glslPolyfills[ method ] !== undefined ) {
- this._include( method );
- }
- return glslMethods[ method ] || method;
- }
- /**
- * Returns the bitcast method name for a given input and outputType.
- *
- * @param {string} type - The output type to bitcast to.
- * @param {string} inputType - The input type of the.
- * @return {string} The resolved WGSL bitcast invocation.
- */
- getBitcastMethod( type, inputType ) {
- return this.getMethod( `bitcast_${ inputType }_${ type }` );
- }
- /**
- * Returns the float packing method name for a given numeric encoding.
- *
- * @param {string} encoding - The numeric encoding that describes how the float values are mapped to the integer range.
- * @returns {string} The resolved GLSL float packing method name.
- */
- getFloatPackingMethod( encoding ) {
- return this.getMethod( `floatpack_${ encoding }_2x16` );
- }
- /**
- * Returns the float unpacking method name for a given numeric encoding.
- *
- * @param {string} encoding - The numeric encoding that describes how the integer values are mapped to the float range.
- * @returns {string} The resolved GLSL float unpacking method name.
- */
- getFloatUnpackingMethod( encoding ) {
- return this.getMethod( `floatunpack_${ encoding }_2x16` );
- }
- /**
- * Returns the native snippet for a ternary operation.
- *
- * @param {string} condSnippet - The condition determining which expression gets resolved.
- * @param {string} ifSnippet - The expression to resolve to if the condition is true.
- * @param {string} elseSnippet - The expression to resolve to if the condition is false.
- * @return {string} The resolved method name.
- */
- getTernary( condSnippet, ifSnippet, elseSnippet ) {
- return `${condSnippet} ? ${ifSnippet} : ${elseSnippet}`;
- }
- /**
- * Returns the output struct name. Not relevant for GLSL.
- *
- * @return {string}
- */
- getOutputStructName() {
- return '';
- }
- /**
- * Builds the given shader node.
- *
- * @param {ShaderNodeInternal} shaderNode - The shader node.
- * @return {string} The GLSL function code.
- */
- buildFunctionCode( shaderNode ) {
- const layout = shaderNode.layout;
- const flowData = this.flowShaderNode( shaderNode );
- const parameters = [];
- for ( const input of layout.inputs ) {
- parameters.push( this.getType( input.type ) + ' ' + input.name );
- }
- //
- const code = `${ this.getType( layout.type ) } ${ layout.name }( ${ parameters.join( ', ' ) } ) {
- ${ flowData.vars }
- ${ flowData.code }
- return ${ flowData.result };
- }`;
- //
- return code;
- }
- /**
- * Setups the Pixel Buffer Object (PBO) for the given storage
- * buffer node.
- *
- * @param {StorageBufferNode} storageBufferNode - The storage buffer node.
- */
- setupPBO( storageBufferNode ) {
- const attribute = storageBufferNode.value;
- if ( attribute.pbo === undefined ) {
- const originalArray = attribute.array;
- const numElements = attribute.count * attribute.itemSize;
- const { itemSize } = attribute;
- const isInteger = attribute.array.constructor.name.toLowerCase().includes( 'int' );
- let format = isInteger ? RedIntegerFormat : RedFormat;
- if ( itemSize === 2 ) {
- format = isInteger ? RGIntegerFormat : RGFormat;
- } else if ( itemSize === 3 ) {
- format = isInteger ? RGBIntegerFormat : RGBFormat;
- } else if ( itemSize === 4 ) {
- format = isInteger ? RGBAIntegerFormat : RGBAFormat;
- }
- const typeMap = {
- Float32Array: FloatType,
- Uint8Array: UnsignedByteType,
- Uint16Array: UnsignedShortType,
- Uint32Array: UnsignedIntType,
- Int8Array: ByteType,
- Int16Array: ShortType,
- Int32Array: IntType,
- Uint8ClampedArray: UnsignedByteType,
- };
- const width = Math.pow( 2, Math.ceil( Math.log2( Math.sqrt( numElements / itemSize ) ) ) );
- let height = Math.ceil( ( numElements / itemSize ) / width );
- if ( width * height * itemSize < numElements ) height ++; // Ensure enough space
- const newSize = width * height * itemSize;
- const newArray = new originalArray.constructor( newSize );
- newArray.set( originalArray, 0 );
- attribute.array = newArray;
- const pboTexture = new DataTexture( attribute.array, width, height, format, typeMap[ attribute.array.constructor.name ] || FloatType );
- pboTexture.needsUpdate = true;
- pboTexture.isPBOTexture = true;
- const pbo = new TextureNode( pboTexture, null, null );
- pbo.setPrecision( 'high' );
- attribute.pboNode = pbo;
- attribute.pbo = pbo.value;
- this.getUniformFromNode( attribute.pboNode, 'texture', this.shaderStage, this.context.nodeName );
- }
- }
- /**
- * Returns a GLSL snippet that represents the property name of the given node.
- *
- * @param {Node} node - The node.
- * @param {string} [shaderStage=this.shaderStage] - The shader stage this code snippet is generated for.
- * @return {string} The property name.
- */
- getPropertyName( node, shaderStage = this.shaderStage ) {
- if ( node.isNodeUniform && node.node.isTextureNode !== true && node.node.isBufferNode !== true ) {
- return shaderStage.charAt( 0 ) + '_' + node.name;
- }
- return super.getPropertyName( node, shaderStage );
- }
- /**
- * Setups the Pixel Buffer Object (PBO) for the given storage
- * buffer node.
- *
- * @param {StorageArrayElementNode} storageArrayElementNode - The storage array element node.
- * @return {string} The property name.
- */
- generatePBO( storageArrayElementNode ) {
- const { node, indexNode } = storageArrayElementNode;
- const attribute = node.value;
- if ( this.renderer.backend.has( attribute ) ) {
- const attributeData = this.renderer.backend.get( attribute );
- attributeData.pbo = attribute.pbo;
- }
- const nodeUniform = this.getUniformFromNode( attribute.pboNode, 'texture', this.shaderStage, this.context.nodeName );
- const textureName = this.getPropertyName( nodeUniform );
- this.increaseUsage( indexNode ); // force cache generate to be used as index in x,y
- const indexSnippet = indexNode.build( this, 'uint' );
- const elementNodeData = this.getDataFromNode( storageArrayElementNode );
- let propertyName = elementNodeData.propertyName;
- if ( propertyName === undefined ) {
- // property element
- const nodeVar = this.getVarFromNode( storageArrayElementNode );
- propertyName = this.getPropertyName( nodeVar );
- // property size
- const bufferNodeData = this.getDataFromNode( node );
- let propertySizeName = bufferNodeData.propertySizeName;
- if ( propertySizeName === undefined ) {
- propertySizeName = propertyName + 'Size';
- this.getVarFromNode( node, propertySizeName, 'uint' );
- this.addLineFlowCode( `${ propertySizeName } = uint( textureSize( ${ textureName }, 0 ).x )`, storageArrayElementNode );
- bufferNodeData.propertySizeName = propertySizeName;
- }
- //
- const { itemSize } = attribute;
- const channel = '.' + vectorComponents.join( '' ).slice( 0, itemSize );
- const uvSnippet = `ivec2(${indexSnippet} % ${ propertySizeName }, ${indexSnippet} / ${ propertySizeName })`;
- const snippet = this.generateTextureLoad( null, textureName, uvSnippet, '0', null, null );
- //
- let prefix = 'vec4';
- if ( attribute.pbo.type === UnsignedIntType ) {
- prefix = 'uvec4';
- } else if ( attribute.pbo.type === IntType ) {
- prefix = 'ivec4';
- }
- this.addLineFlowCode( `${ propertyName } = ${prefix}(${ snippet })${channel}`, storageArrayElementNode );
- elementNodeData.propertyName = propertyName;
- }
- return propertyName;
- }
- /**
- * Generates the GLSL snippet that reads a single texel from a texture without sampling or filtering.
- *
- * @param {?Texture} texture - The texture.
- * @param {string} textureProperty - The name of the texture uniform in the shader.
- * @param {string} uvIndexSnippet - A GLSL snippet that represents texture coordinates used for sampling.
- * @param {?string} levelSnippet - A GLSL snippet that represents the mip level, with level 0 containing a full size version of the texture.
- * @param {?string} depthSnippet - A GLSL snippet that represents the 0-based texture array index to sample.
- * @param {?string} offsetSnippet - A GLSL snippet that represents the offset that will be applied to the unnormalized texture coordinate before sampling the texture.
- * @return {string} The GLSL snippet.
- */
- generateTextureLoad( texture, textureProperty, uvIndexSnippet, levelSnippet, depthSnippet, offsetSnippet ) {
- if ( levelSnippet === null ) levelSnippet = '0';
- let snippet;
- if ( depthSnippet ) {
- if ( offsetSnippet ) {
- snippet = `texelFetchOffset( ${ textureProperty }, ivec3( ${ uvIndexSnippet }, ${ depthSnippet } ), ${ levelSnippet }, ${ offsetSnippet } )`;
- } else {
- snippet = `texelFetch( ${ textureProperty }, ivec3( ${ uvIndexSnippet }, ${ depthSnippet } ), ${ levelSnippet } )`;
- }
- } else {
- if ( offsetSnippet ) {
- snippet = `texelFetchOffset( ${ textureProperty }, ${ uvIndexSnippet }, ${ levelSnippet }, ${ offsetSnippet } )`;
- } else {
- snippet = `texelFetch( ${ textureProperty }, ${ uvIndexSnippet }, ${ levelSnippet } )`;
- }
- }
- if ( texture !== null && texture.isDepthTexture ) {
- snippet += '.x';
- }
- return snippet;
- }
- /**
- * Generates the GLSL snippet for sampling/loading the given texture.
- *
- * @param {Texture} texture - The texture.
- * @param {string} textureProperty - The name of the texture uniform in the shader.
- * @param {string} uvSnippet - A GLSL snippet that represents texture coordinates used for sampling.
- * @param {?string} depthSnippet - A GLSL snippet that represents the 0-based texture array index to sample.
- * @param {?string} offsetSnippet - A GLSL snippet that represents the offset that will be applied to the unnormalized texture coordinate before sampling the texture.
- * @return {string} The GLSL snippet.
- */
- generateTexture( texture, textureProperty, uvSnippet, depthSnippet, offsetSnippet ) {
- if ( depthSnippet ) uvSnippet = `vec3( ${ uvSnippet }, ${ depthSnippet } )`;
- if ( texture.isDepthTexture ) {
- if ( offsetSnippet ) return `textureOffset( ${ textureProperty }, ${ uvSnippet }, ${ offsetSnippet } ).x`;
- return `texture( ${ textureProperty }, ${ uvSnippet } ).x`;
- }
- if ( offsetSnippet ) return `textureOffset( ${ textureProperty }, ${ uvSnippet }, ${ offsetSnippet } )`;
- return `texture( ${ textureProperty }, ${ uvSnippet } )`;
- }
- /**
- * Generates the GLSL snippet when sampling textures with explicit mip level.
- *
- * @param {Texture} texture - The texture.
- * @param {string} textureProperty - The name of the texture uniform in the shader.
- * @param {string} uvSnippet - A GLSL snippet that represents texture coordinates used for sampling.
- * @param {string} levelSnippet - A GLSL snippet that represents the mip level, with level 0 containing a full size version of the texture.
- * @param {?string} offsetSnippet - A GLSL snippet that represents the offset that will be applied to the unnormalized texture coordinate before sampling the texture.
- * @return {string} The GLSL snippet.
- */
- generateTextureLevel( texture, textureProperty, uvSnippet, levelSnippet, offsetSnippet ) {
- if ( offsetSnippet ) {
- return `textureLodOffset( ${ textureProperty }, ${ uvSnippet }, ${ levelSnippet }, ${ offsetSnippet } )`;
- }
- return `textureLod( ${ textureProperty }, ${ uvSnippet }, ${ levelSnippet } )`;
- }
- /**
- * Generates the GLSL snippet when sampling textures with a bias to the mip level.
- *
- * @param {Texture} texture - The texture.
- * @param {string} textureProperty - The name of the texture uniform in the shader.
- * @param {string} uvSnippet - A GLSL snippet that represents texture coordinates used for sampling.
- * @param {string} biasSnippet - A GLSL snippet that represents the bias to apply to the mip level before sampling.
- * @param {?string} offsetSnippet - A GLSL snippet that represents the offset that will be applied to the unnormalized texture coordinate before sampling the texture.
- * @return {string} The GLSL snippet.
- */
- generateTextureBias( texture, textureProperty, uvSnippet, biasSnippet, offsetSnippet ) {
- if ( offsetSnippet ) {
- return `textureOffset( ${ textureProperty }, ${ uvSnippet }, ${ offsetSnippet }, ${ biasSnippet } )`;
- }
- return `texture( ${ textureProperty }, ${ uvSnippet }, ${ biasSnippet } )`;
- }
- /**
- * Generates the GLSL snippet for sampling/loading the given texture using explicit gradients.
- *
- * @param {Texture} texture - The texture.
- * @param {string} textureProperty - The name of the texture uniform in the shader.
- * @param {string} uvSnippet - A GLSL snippet that represents texture coordinates used for sampling.
- * @param {Array<string>} gradSnippet - An array holding both gradient GLSL snippets.
- * @param {?string} offsetSnippet - A GLSL snippet that represents the offset that will be applied to the unnormalized texture coordinate before sampling the texture.
- * @return {string} The GLSL snippet.
- */
- generateTextureGrad( texture, textureProperty, uvSnippet, gradSnippet, offsetSnippet ) {
- if ( offsetSnippet ) {
- return `textureGradOffset( ${ textureProperty }, ${ uvSnippet }, ${ gradSnippet[ 0 ] }, ${ gradSnippet[ 1 ] }, ${ offsetSnippet } )`;
- }
- return `textureGrad( ${ textureProperty }, ${ uvSnippet }, ${ gradSnippet[ 0 ] }, ${ gradSnippet[ 1 ] } )`;
- }
- /**
- * Generates the GLSL snippet for sampling a depth texture and comparing the sampled depth values
- * against a reference value.
- *
- * @param {Texture} texture - The texture.
- * @param {string} textureProperty - The name of the texture uniform in the shader.
- * @param {string} uvSnippet - A GLSL snippet that represents texture coordinates used for sampling.
- * @param {string} compareSnippet - A GLSL snippet that represents the reference value.
- * @param {?string} depthSnippet - A GLSL snippet that represents 0-based texture array index to sample.
- * @param {?string} offsetSnippet - A GLSL snippet that represents the offset that will be applied to the unnormalized texture coordinate before sampling the texture.
- * @param {string} [shaderStage=this.shaderStage] - The shader stage this code snippet is generated for.
- * @return {string} The GLSL snippet.
- */
- generateTextureCompare( texture, textureProperty, uvSnippet, compareSnippet, depthSnippet, offsetSnippet, shaderStage = this.shaderStage ) {
- if ( shaderStage === 'fragment' ) {
- // Cube shadow maps use vec4(direction, compareValue)
- if ( texture.isCubeTexture ) {
- return `texture( ${ textureProperty }, vec4( ${ uvSnippet }, ${ compareSnippet } ) )`;
- }
- if ( depthSnippet ) {
- if ( offsetSnippet ) {
- return `textureOffset( ${ textureProperty }, vec4( ${ uvSnippet }, ${ depthSnippet }, ${ compareSnippet } ), ${ offsetSnippet } )`;
- }
- return `texture( ${ textureProperty }, vec4( ${ uvSnippet }, ${ depthSnippet }, ${ compareSnippet } ) )`;
- }
- if ( offsetSnippet ) {
- return `textureOffset( ${ textureProperty }, vec3( ${ uvSnippet }, ${ compareSnippet } ), ${ offsetSnippet } )`;
- }
- return `texture( ${ textureProperty }, vec3( ${ uvSnippet }, ${ compareSnippet } ) )`;
- } else {
- error( `WebGPURenderer: THREE.DepthTexture.compareFunction() does not support ${ shaderStage } shader.` );
- }
- }
- /**
- * Returns the variables of the given shader stage as a GLSL string.
- *
- * @param {string} shaderStage - The shader stage.
- * @return {string} The GLSL snippet that defines the variables.
- */
- getVars( shaderStage ) {
- const snippets = [];
- const vars = this.vars[ shaderStage ];
- if ( vars !== undefined ) {
- for ( const variable of vars ) {
- snippets.push( `${ this.getVar( variable.type, variable.name, variable.count ) };` );
- }
- }
- return snippets.join( '\n\t' );
- }
- /**
- * Returns the uniforms of the given shader stage as a GLSL string.
- *
- * @param {string} shaderStage - The shader stage.
- * @return {string} The GLSL snippet that defines the uniforms.
- */
- getUniforms( shaderStage ) {
- const uniforms = this.uniforms[ shaderStage ];
- const bindingSnippets = [];
- const uniformGroups = {};
- for ( const uniform of uniforms ) {
- let snippet = null;
- let group = false;
- if ( uniform.type === 'texture' || uniform.type === 'texture3D' ) {
- const texture = uniform.node.value;
- let typePrefix = '';
- if ( texture.isDataTexture === true || texture.isData3DTexture === true ) {
- if ( texture.type === UnsignedIntType ) {
- typePrefix = 'u';
- } else if ( texture.type === IntType ) {
- typePrefix = 'i';
- }
- }
- if ( uniform.type === 'texture3D' && texture.isArrayTexture === false ) {
- snippet = `${typePrefix}sampler3D ${ uniform.name };`;
- } else if ( texture.compareFunction ) {
- if ( texture.isArrayTexture === true ) {
- snippet = `sampler2DArrayShadow ${ uniform.name };`;
- } else {
- snippet = `sampler2DShadow ${ uniform.name };`;
- }
- } else if ( texture.isArrayTexture === true || texture.isDataArrayTexture === true || texture.isCompressedArrayTexture === true ) {
- snippet = `${typePrefix}sampler2DArray ${ uniform.name };`;
- } else {
- snippet = `${typePrefix}sampler2D ${ uniform.name };`;
- }
- } else if ( uniform.type === 'cubeTexture' ) {
- snippet = `samplerCube ${ uniform.name };`;
- } else if ( uniform.type === 'cubeDepthTexture' ) {
- snippet = `samplerCubeShadow ${ uniform.name };`;
- } else if ( uniform.type === 'buffer' ) {
- const bufferNode = uniform.node;
- const bufferType = this.getType( bufferNode.bufferType );
- const bufferCount = bufferNode.bufferCount;
- const bufferCountSnippet = bufferCount > 0 ? bufferCount : '';
- snippet = `${bufferNode.name} {\n\t${ bufferType } ${ uniform.name }[${ bufferCountSnippet }];\n};\n`;
- } else {
- const vectorType = this.getVectorType( uniform.type );
- snippet = `${ vectorType } ${ this.getPropertyName( uniform, shaderStage ) };`;
- group = true;
- }
- const precision = uniform.node.precision;
- if ( precision !== null ) {
- snippet = precisionLib[ precision ] + ' ' + snippet;
- }
- if ( group ) {
- snippet = '\t' + snippet;
- const groupName = uniform.groupNode.name;
- const groupSnippets = uniformGroups[ groupName ] || ( uniformGroups[ groupName ] = [] );
- groupSnippets.push( snippet );
- } else {
- snippet = 'uniform ' + snippet;
- bindingSnippets.push( snippet );
- }
- }
- let output = '';
- for ( const name in uniformGroups ) {
- const groupSnippets = uniformGroups[ name ];
- output += this._getGLSLUniformStruct( shaderStage + '_' + name, groupSnippets.join( '\n' ) ) + '\n';
- }
- output += bindingSnippets.join( '\n' );
- return output;
- }
- /**
- * Returns the type for a given buffer attribute.
- *
- * @param {BufferAttribute} attribute - The buffer attribute.
- * @return {string} The type.
- */
- getTypeFromAttribute( attribute ) {
- let nodeType = super.getTypeFromAttribute( attribute );
- if ( /^[iu]/.test( nodeType ) && attribute.gpuType !== IntType ) {
- let dataAttribute = attribute;
- if ( attribute.isInterleavedBufferAttribute ) dataAttribute = attribute.data;
- const array = dataAttribute.array;
- if ( ( array instanceof Uint32Array || array instanceof Int32Array ) === false ) {
- nodeType = nodeType.slice( 1 );
- }
- }
- return nodeType;
- }
- /**
- * Returns the shader attributes of the given shader stage as a GLSL string.
- *
- * @param {string} shaderStage - The shader stage.
- * @return {string} The GLSL snippet that defines the shader attributes.
- */
- getAttributes( shaderStage ) {
- let snippet = '';
- if ( shaderStage === 'vertex' || shaderStage === 'compute' ) {
- const attributes = this.getAttributesArray();
- let location = 0;
- for ( const attribute of attributes ) {
- snippet += `layout( location = ${ location ++ } ) in ${ attribute.type } ${ attribute.name };\n`;
- }
- }
- return snippet;
- }
- /**
- * Returns the members of the given struct type node as a GLSL string.
- *
- * @param {StructTypeNode} struct - The struct type node.
- * @return {string} The GLSL snippet that defines the struct members.
- */
- getStructMembers( struct ) {
- const snippets = [];
- for ( const member of struct.members ) {
- snippets.push( `\t${ member.type } ${ member.name };` );
- }
- return snippets.join( '\n' );
- }
- /**
- * Returns the structs of the given shader stage as a GLSL string.
- *
- * @param {string} shaderStage - The shader stage.
- * @return {string} The GLSL snippet that defines the structs.
- */
- getStructs( shaderStage ) {
- const snippets = [];
- const structs = this.structs[ shaderStage ];
- const outputSnippet = [];
- for ( const struct of structs ) {
- if ( struct.output ) {
- for ( const member of struct.members ) {
- outputSnippet.push( `layout( location = ${ member.index } ) out ${ member.type } ${ member.name };` );
- }
- } else {
- let snippet = 'struct ' + struct.name + ' {\n';
- snippet += this.getStructMembers( struct );
- snippet += '\n};\n';
- snippets.push( snippet );
- }
- }
- if ( outputSnippet.length === 0 ) {
- outputSnippet.push( 'layout( location = 0 ) out vec4 fragColor;' );
- }
- return '\n' + outputSnippet.join( '\n' ) + '\n\n' + snippets.join( '\n' );
- }
- /**
- * Returns the varyings of the given shader stage as a GLSL string.
- *
- * @param {string} shaderStage - The shader stage.
- * @return {string} The GLSL snippet that defines the varyings.
- */
- getVaryings( shaderStage ) {
- let snippet = '';
- const varyings = this.varyings;
- if ( shaderStage === 'vertex' || shaderStage === 'compute' ) {
- for ( const varying of varyings ) {
- if ( shaderStage === 'compute' ) varying.needsInterpolation = true;
- const type = this.getType( varying.type );
- if ( varying.needsInterpolation ) {
- if ( varying.interpolationType ) {
- const interpolationType = interpolationTypeMap[ varying.interpolationType ] || varying.interpolationType;
- const sampling = interpolationModeMap[ varying.interpolationSampling ] || '';
- snippet += `${ interpolationType } ${ sampling } out ${ type } ${ varying.name };\n`;
- } else {
- const flat = type.includes( 'int' ) || type.includes( 'uv' ) || type.includes( 'iv' ) ? 'flat ' : '';
- snippet += `${ flat }out ${ type } ${ varying.name };\n`;
- }
- } else {
- snippet += `${type} ${varying.name};\n`; // generate variable (no varying required)
- }
- }
- } else if ( shaderStage === 'fragment' ) {
- for ( const varying of varyings ) {
- if ( varying.needsInterpolation ) {
- const type = this.getType( varying.type );
- if ( varying.interpolationType ) {
- const interpolationType = interpolationTypeMap[ varying.interpolationType ] || varying.interpolationType;
- const sampling = interpolationModeMap[ varying.interpolationSampling ] || '';
- snippet += `${ interpolationType } ${ sampling } in ${ type } ${ varying.name };\n`;
- } else {
- const flat = type.includes( 'int' ) || type.includes( 'uv' ) || type.includes( 'iv' ) ? 'flat ' : '';
- snippet += `${ flat }in ${ type } ${ varying.name };\n`;
- }
- }
- }
- }
- for ( const builtin of this.builtins[ shaderStage ] ) {
- snippet += `${builtin};\n`;
- }
- return snippet;
- }
- /**
- * Returns the vertex index builtin.
- *
- * @return {string} The vertex index.
- */
- getVertexIndex() {
- return 'uint( gl_VertexID )';
- }
- /**
- * Contextually returns either the vertex stage instance index builtin
- * or the linearized index of an compute invocation within a grid of workgroups.
- *
- * @return {string} The instance index.
- */
- getInstanceIndex() {
- return 'uint( gl_InstanceID )';
- }
- /**
- * Returns a builtin representing the index of an invocation within its workgroup.
- *
- * @return {string} The invocation local index.
- */
- getInvocationLocalIndex() {
- const workgroupSize = this.object.workgroupSize;
- const size = workgroupSize.reduce( ( acc, curr ) => acc * curr, 1 );
- return `uint( gl_InstanceID ) % ${size}u`;
- }
- /**
- * Returns a builtin representing the size of a subgroup within the current shader.
- */
- getSubgroupSize() {
- error( 'GLSLNodeBuilder: WebGLBackend does not support the subgroupSize node' );
- }
- /**
- * Returns a builtin representing the index of an invocation within its subgroup.
- */
- getInvocationSubgroupIndex() {
- error( 'GLSLNodeBuilder: WebGLBackend does not support the invocationSubgroupIndex node' );
- }
- /**
- * Returns a builtin representing the index of the current invocation's subgroup within its workgroup.
- */
- getSubgroupIndex() {
- error( 'GLSLNodeBuilder: WebGLBackend does not support the subgroupIndex node' );
- }
- /**
- * Returns the draw index builtin.
- *
- * @return {?string} The drawIndex shader string. Returns `null` if `WEBGL_multi_draw` isn't supported by the device.
- */
- getDrawIndex() {
- const extensions = this.renderer.backend.extensions;
- if ( extensions.has( 'WEBGL_multi_draw' ) ) {
- return 'uint( gl_DrawID )';
- }
- return null;
- }
- /**
- * Returns the front facing builtin.
- *
- * @return {string} The front facing builtin.
- */
- getFrontFacing() {
- return 'gl_FrontFacing';
- }
- /**
- * Returns the frag coord builtin.
- *
- * @return {string} The frag coord builtin.
- */
- getFragCoord() {
- return 'gl_FragCoord.xy';
- }
- /**
- * Returns the frag depth builtin.
- *
- * @return {string} The frag depth builtin.
- */
- getFragDepth() {
- return 'gl_FragDepth';
- }
- /**
- * Enables the given extension.
- *
- * @param {string} name - The extension name.
- * @param {string} behavior - The extension behavior.
- * @param {string} [shaderStage=this.shaderStage] - The shader stage.
- */
- enableExtension( name, behavior, shaderStage = this.shaderStage ) {
- const map = this.extensions[ shaderStage ] || ( this.extensions[ shaderStage ] = new Map() );
- if ( map.has( name ) === false ) {
- map.set( name, {
- name,
- behavior
- } );
- }
- }
- /**
- * Returns the enabled extensions of the given shader stage as a GLSL string.
- *
- * @param {string} shaderStage - The shader stage.
- * @return {string} The GLSL snippet that defines the enabled extensions.
- */
- getExtensions( shaderStage ) {
- const snippets = [];
- if ( shaderStage === 'vertex' ) {
- const ext = this.renderer.backend.extensions;
- const isBatchedMesh = this.object.isBatchedMesh;
- if ( isBatchedMesh && ext.has( 'WEBGL_multi_draw' ) ) {
- this.enableExtension( 'GL_ANGLE_multi_draw', 'require', shaderStage );
- }
- }
- const extensions = this.extensions[ shaderStage ];
- if ( extensions !== undefined ) {
- for ( const { name, behavior } of extensions.values() ) {
- snippets.push( `#extension ${name} : ${behavior}` );
- }
- }
- return snippets.join( '\n' );
- }
- /**
- * Returns the clip distances builtin.
- *
- * @return {string} The clip distances builtin.
- */
- getClipDistance() {
- return 'gl_ClipDistance';
- }
- /**
- * Whether the requested feature is available or not.
- *
- * @param {string} name - The requested feature.
- * @return {boolean} Whether the requested feature is supported or not.
- */
- isAvailable( name ) {
- let result = supports$1[ name ];
- if ( result === undefined ) {
- let extensionName;
- result = false;
- switch ( name ) {
- case 'float32Filterable':
- extensionName = 'OES_texture_float_linear';
- break;
- case 'clipDistance':
- extensionName = 'WEBGL_clip_cull_distance';
- break;
- }
- if ( extensionName !== undefined ) {
- const extensions = this.renderer.backend.extensions;
- if ( extensions.has( extensionName ) ) {
- extensions.get( extensionName );
- result = true;
- }
- }
- supports$1[ name ] = result;
- }
- return result;
- }
- /**
- * Whether to flip texture data along its vertical axis or not.
- *
- * @return {boolean} Returns always `true` in context of GLSL.
- */
- isFlipY() {
- return true;
- }
- /**
- * Enables hardware clipping.
- *
- * @param {string} planeCount - The clipping plane count.
- */
- enableHardwareClipping( planeCount ) {
- this.enableExtension( 'GL_ANGLE_clip_cull_distance', 'require' );
- this.builtins[ 'vertex' ].push( `out float gl_ClipDistance[ ${ planeCount } ]` );
- }
- /**
- * Enables multiview.
- */
- enableMultiview() {
- this.enableExtension( 'GL_OVR_multiview2', 'require', 'fragment' );
- this.enableExtension( 'GL_OVR_multiview2', 'require', 'vertex' );
- this.builtins[ 'vertex' ].push( 'layout(num_views = 2) in' );
- }
- /**
- * Registers a transform in context of Transform Feedback.
- *
- * @param {string} varyingName - The varying name.
- * @param {AttributeNode} attributeNode - The attribute node.
- */
- registerTransform( varyingName, attributeNode ) {
- this.transforms.push( { varyingName, attributeNode } );
- }
- /**
- * Returns the transforms of the given shader stage as a GLSL string.
- *
- * @param {string} shaderStage - The shader stage.
- * @return {string} The GLSL snippet that defines the transforms.
- */
- getTransforms( /* shaderStage */ ) {
- const transforms = this.transforms;
- let snippet = '';
- for ( let i = 0; i < transforms.length; i ++ ) {
- const transform = transforms[ i ];
- const attributeName = this.getPropertyName( transform.attributeNode );
- if ( attributeName ) snippet += `${ transform.varyingName } = ${ attributeName };\n\t`;
- }
- return snippet;
- }
- /**
- * Returns a GLSL struct based on the given name and variables.
- *
- * @private
- * @param {string} name - The struct name.
- * @param {string} vars - The struct variables.
- * @return {string} The GLSL snippet representing a struct.
- */
- _getGLSLUniformStruct( name, vars ) {
- return `
- layout( std140 ) uniform ${name} {
- ${vars}
- };`;
- }
- /**
- * Returns a GLSL vertex shader based on the given shader data.
- *
- * @private
- * @param {Object} shaderData - The shader data.
- * @return {string} The vertex shader.
- */
- _getGLSLVertexCode( shaderData ) {
- return `#version 300 es
- ${ this.getSignature() }
- // extensions
- ${shaderData.extensions}
- // precision
- ${ defaultPrecisions }
- // uniforms
- ${shaderData.uniforms}
- // varyings
- ${shaderData.varyings}
- // attributes
- ${shaderData.attributes}
- // codes
- ${shaderData.codes}
- void main() {
- // vars
- ${shaderData.vars}
- // transforms
- ${shaderData.transforms}
- // flow
- ${shaderData.flow}
- gl_PointSize = 1.0;
- }
- `;
- }
- /**
- * Returns a GLSL fragment shader based on the given shader data.
- *
- * @private
- * @param {Object} shaderData - The shader data.
- * @return {string} The vertex shader.
- */
- _getGLSLFragmentCode( shaderData ) {
- return `#version 300 es
- ${ this.getSignature() }
- // extensions
- ${shaderData.extensions}
- // precision
- ${ defaultPrecisions }
- // structs
- ${shaderData.structs}
- // uniforms
- ${shaderData.uniforms}
- // varyings
- ${shaderData.varyings}
- // codes
- ${shaderData.codes}
- void main() {
- // vars
- ${shaderData.vars}
- // flow
- ${shaderData.flow}
- }
- `;
- }
- /**
- * Controls the code build of the shader stages.
- */
- buildCode() {
- const shadersData = this.material !== null ? { fragment: {}, vertex: {} } : { compute: {} };
- this.sortBindingGroups();
- for ( const shaderStage in shadersData ) {
- let flow = '// code\n\n';
- flow += this.flowCode[ shaderStage ];
- const flowNodes = this.flowNodes[ shaderStage ];
- const mainNode = flowNodes[ flowNodes.length - 1 ];
- for ( const node of flowNodes ) {
- const flowSlotData = this.getFlowData( node/*, shaderStage*/ );
- const slotName = node.name;
- if ( slotName ) {
- if ( flow.length > 0 ) flow += '\n';
- flow += `\t// flow -> ${ slotName }\n\t`;
- }
- flow += `${ flowSlotData.code }\n\t`;
- if ( node === mainNode && shaderStage !== 'compute' ) {
- flow += '// result\n\t';
- if ( shaderStage === 'vertex' ) {
- flow += 'gl_Position = ';
- flow += `${ flowSlotData.result };`;
- } else if ( shaderStage === 'fragment' ) {
- if ( ! node.outputNode.isOutputStructNode ) {
- flow += 'fragColor = ';
- flow += `${ flowSlotData.result };`;
- }
- }
- }
- }
- const stageData = shadersData[ shaderStage ];
- stageData.extensions = this.getExtensions( shaderStage );
- stageData.uniforms = this.getUniforms( shaderStage );
- stageData.attributes = this.getAttributes( shaderStage );
- stageData.varyings = this.getVaryings( shaderStage );
- stageData.vars = this.getVars( shaderStage );
- stageData.structs = this.getStructs( shaderStage );
- stageData.codes = this.getCodes( shaderStage );
- stageData.transforms = this.getTransforms( shaderStage );
- stageData.flow = flow;
- }
- if ( this.material !== null ) {
- this.vertexShader = this._getGLSLVertexCode( shadersData.vertex );
- this.fragmentShader = this._getGLSLFragmentCode( shadersData.fragment );
- } else {
- this.computeShader = this._getGLSLVertexCode( shadersData.compute );
- }
- }
- /**
- * This method is one of the more important ones since it's responsible
- * for generating a matching binding instance for the given uniform node.
- *
- * These bindings are later used in the renderer to create bind groups
- * and layouts.
- *
- * @param {UniformNode} node - The uniform node.
- * @param {string} type - The node data type.
- * @param {string} shaderStage - The shader stage.
- * @param {?string} [name=null] - An optional uniform name.
- * @return {NodeUniform} The node uniform object.
- */
- getUniformFromNode( node, type, shaderStage, name = null ) {
- const uniformNode = super.getUniformFromNode( node, type, shaderStage, name );
- const nodeData = this.getDataFromNode( node, shaderStage, this.globalCache );
- let uniformGPU = nodeData.uniformGPU;
- if ( uniformGPU === undefined ) {
- const group = node.groupNode;
- const groupName = group.name;
- const bindings = this.getBindGroupArray( groupName, shaderStage );
- if ( type === 'texture' ) {
- uniformGPU = new NodeSampledTexture( uniformNode.name, uniformNode.node, group );
- bindings.push( uniformGPU );
- } else if ( type === 'cubeTexture' || type === 'cubeDepthTexture' ) {
- uniformGPU = new NodeSampledCubeTexture( uniformNode.name, uniformNode.node, group );
- bindings.push( uniformGPU );
- } else if ( type === 'texture3D' ) {
- uniformGPU = new NodeSampledTexture3D( uniformNode.name, uniformNode.node, group );
- bindings.push( uniformGPU );
- } else if ( type === 'buffer' ) {
- uniformNode.name = `buffer${ node.id }`;
- const sharedData = this.getSharedDataFromNode( node );
- let buffer = sharedData.buffer;
- if ( buffer === undefined ) {
- node.name = `NodeBuffer_${ node.id }`;
- buffer = new NodeUniformBuffer( node, group );
- buffer.name = node.name;
- sharedData.buffer = buffer;
- }
- bindings.push( buffer );
- uniformGPU = buffer;
- } else {
- const uniformsStage = this.uniformGroups[ shaderStage ] || ( this.uniformGroups[ shaderStage ] = {} );
- let uniformsGroup = uniformsStage[ groupName ];
- if ( uniformsGroup === undefined ) {
- uniformsGroup = new NodeUniformsGroup( shaderStage + '_' + groupName, group );
- //uniformsGroup.setVisibility( gpuShaderStageLib[ shaderStage ] );
- uniformsStage[ groupName ] = uniformsGroup;
- bindings.push( uniformsGroup );
- }
- uniformGPU = this.getNodeUniform( uniformNode, type );
- uniformsGroup.addUniform( uniformGPU );
- }
- nodeData.uniformGPU = uniformGPU;
- }
- return uniformNode;
- }
- }
- let _vector2 = null;
- let _color4 = null;
- /**
- * Most of the rendering related logic is implemented in the
- * {@link Renderer} module and related management components.
- * Sometimes it is required though to execute commands which are
- * specific to the current 3D backend (which is WebGPU or WebGL 2).
- * This abstract base class defines an interface that encapsulates
- * all backend-related logic. Derived classes for each backend must
- * implement the interface.
- *
- * @abstract
- * @private
- */
- class Backend {
- /**
- * Constructs a new backend.
- *
- * @param {Object} parameters - An object holding parameters for the backend.
- */
- constructor( parameters = {} ) {
- /**
- * The parameters of the backend.
- *
- * @type {Object}
- */
- this.parameters = Object.assign( {}, parameters );
- /**
- * This weak map holds backend-specific data of objects
- * like textures, attributes or render targets.
- *
- * @type {WeakMap<Object, Object>}
- */
- this.data = new WeakMap();
- /**
- * A reference to the renderer.
- *
- * @type {?Renderer}
- * @default null
- */
- this.renderer = null;
- /**
- * A reference to the canvas element the renderer is drawing to.
- *
- * @type {?(HTMLCanvasElement|OffscreenCanvas)}
- * @default null
- */
- this.domElement = null;
- /**
- * A reference to the timestamp query pool.
- *
- * @type {{render: ?TimestampQueryPool, compute: ?TimestampQueryPool}}
- */
- this.timestampQueryPool = {
- [ TimestampQuery.RENDER ]: null,
- [ TimestampQuery.COMPUTE ]: null
- };
- /**
- * Whether to track timestamps with a Timestamp Query API or not.
- *
- * @type {boolean}
- * @default false
- */
- this.trackTimestamp = ( parameters.trackTimestamp === true );
- }
- /**
- * Initializes the backend so it is ready for usage. Concrete backends
- * are supposed to implement their rendering context creation and related
- * operations in this method.
- *
- * @async
- * @param {Renderer} renderer - The renderer.
- * @return {Promise} A Promise that resolves when the backend has been initialized.
- */
- async init( renderer ) {
- this.renderer = renderer;
- }
- /**
- * The coordinate system of the backend.
- *
- * @abstract
- * @type {number}
- * @readonly
- */
- get coordinateSystem() {}
- // render context
- /**
- * This method is executed at the beginning of a render call and
- * can be used by the backend to prepare the state for upcoming
- * draw calls.
- *
- * @abstract
- * @param {RenderContext} renderContext - The render context.
- */
- beginRender( /*renderContext*/ ) {}
- /**
- * This method is executed at the end of a render call and
- * can be used by the backend to finalize work after draw
- * calls.
- *
- * @abstract
- * @param {RenderContext} renderContext - The render context.
- */
- finishRender( /*renderContext*/ ) {}
- /**
- * This method is executed at the beginning of a compute call and
- * can be used by the backend to prepare the state for upcoming
- * compute tasks.
- *
- * @abstract
- * @param {Node|Array<Node>} computeGroup - The compute node(s).
- */
- beginCompute( /*computeGroup*/ ) {}
- /**
- * This method is executed at the end of a compute call and
- * can be used by the backend to finalize work after compute
- * tasks.
- *
- * @abstract
- * @param {Node|Array<Node>} computeGroup - The compute node(s).
- */
- finishCompute( /*computeGroup*/ ) {}
- // render object
- /**
- * Executes a draw command for the given render object.
- *
- * @abstract
- * @param {RenderObject} renderObject - The render object to draw.
- * @param {Info} info - Holds a series of statistical information about the GPU memory and the rendering process.
- */
- draw( /*renderObject, info*/ ) { }
- // compute node
- /**
- * Executes a compute command for the given compute node.
- *
- * @abstract
- * @param {Node|Array<Node>} computeGroup - The group of compute nodes of a compute call. Can be a single compute node.
- * @param {Node} computeNode - The compute node.
- * @param {Array<BindGroup>} bindings - The bindings.
- * @param {ComputePipeline} computePipeline - The compute pipeline.
- */
- compute( /*computeGroup, computeNode, computeBindings, computePipeline*/ ) { }
- // program
- /**
- * Creates a shader program from the given programmable stage.
- *
- * @abstract
- * @param {ProgrammableStage} program - The programmable stage.
- */
- createProgram( /*program*/ ) { }
- /**
- * Destroys the shader program of the given programmable stage.
- *
- * @abstract
- * @param {ProgrammableStage} program - The programmable stage.
- */
- destroyProgram( /*program*/ ) { }
- // bindings
- /**
- * Creates bindings from the given bind group definition.
- *
- * @abstract
- * @param {BindGroup} bindGroup - The bind group.
- * @param {Array<BindGroup>} bindings - Array of bind groups.
- * @param {number} cacheIndex - The cache index.
- * @param {number} version - The version.
- */
- createBindings( /*bindGroup, bindings, cacheIndex, version*/ ) { }
- /**
- * Updates the given bind group definition.
- *
- * @abstract
- * @param {BindGroup} bindGroup - The bind group.
- * @param {Array<BindGroup>} bindings - Array of bind groups.
- * @param {number} cacheIndex - The cache index.
- * @param {number} version - The version.
- */
- updateBindings( /*bindGroup, bindings, cacheIndex, version*/ ) { }
- /**
- * Updates a buffer binding.
- *
- * @abstract
- * @param {Buffer} binding - The buffer binding to update.
- */
- updateBinding( /*binding*/ ) { }
- // pipeline
- /**
- * Creates a render pipeline for the given render object.
- *
- * @abstract
- * @param {RenderObject} renderObject - The render object.
- * @param {Array<Promise>} promises - An array of compilation promises which are used in `compileAsync()`.
- */
- createRenderPipeline( /*renderObject, promises*/ ) { }
- /**
- * Creates a compute pipeline for the given compute node.
- *
- * @abstract
- * @param {ComputePipeline} computePipeline - The compute pipeline.
- * @param {Array<BindGroup>} bindings - The bindings.
- */
- createComputePipeline( /*computePipeline, bindings*/ ) { }
- // cache key
- /**
- * Returns `true` if the render pipeline requires an update.
- *
- * @abstract
- * @param {RenderObject} renderObject - The render object.
- * @return {boolean} Whether the render pipeline requires an update or not.
- */
- needsRenderUpdate( /*renderObject*/ ) { }
- /**
- * Returns a cache key that is used to identify render pipelines.
- *
- * @abstract
- * @param {RenderObject} renderObject - The render object.
- * @return {string} The cache key.
- */
- getRenderCacheKey( /*renderObject*/ ) { }
- // node builder
- /**
- * Returns a node builder for the given render object.
- *
- * @abstract
- * @param {RenderObject} renderObject - The render object.
- * @param {Renderer} renderer - The renderer.
- * @return {NodeBuilder} The node builder.
- */
- createNodeBuilder( /*renderObject, renderer*/ ) { }
- // textures
- /**
- * Updates a GPU sampler for the given texture.
- *
- * @abstract
- * @param {Texture} texture - The texture to update the sampler for.
- * @return {string} The current sampler key.
- */
- updateSampler( /*texture*/ ) { }
- /**
- * Creates a default texture for the given texture that can be used
- * as a placeholder until the actual texture is ready for usage.
- *
- * @abstract
- * @param {Texture} texture - The texture to create a default texture for.
- */
- createDefaultTexture( /*texture*/ ) { }
- /**
- * Defines a texture on the GPU for the given texture object.
- *
- * @abstract
- * @param {Texture} texture - The texture.
- * @param {Object} [options={}] - Optional configuration parameter.
- */
- createTexture( /*texture, options={}*/ ) { }
- /**
- * Uploads the updated texture data to the GPU.
- *
- * @abstract
- * @param {Texture} texture - The texture.
- * @param {Object} [options={}] - Optional configuration parameter.
- */
- updateTexture( /*texture, options = {}*/ ) { }
- /**
- * Generates mipmaps for the given texture.
- *
- * @abstract
- * @param {Texture} texture - The texture.
- */
- generateMipmaps( /*texture*/ ) { }
- /**
- * Destroys the GPU data for the given texture object.
- *
- * @abstract
- * @param {Texture} texture - The texture.
- * @param {boolean} [isDefaultTexture=false] - Whether the texture uses a default GPU texture or not.
- */
- destroyTexture( /*texture, isDefaultTexture*/ ) { }
- /**
- * Returns texture data as a typed array.
- *
- * @abstract
- * @async
- * @param {Texture} texture - The texture to copy.
- * @param {number} x - The x coordinate of the copy origin.
- * @param {number} y - The y coordinate of the copy origin.
- * @param {number} width - The width of the copy.
- * @param {number} height - The height of the copy.
- * @param {number} faceIndex - The face index.
- * @return {Promise<TypedArray>} A Promise that resolves with a typed array when the copy operation has finished.
- */
- async copyTextureToBuffer( /*texture, x, y, width, height, faceIndex*/ ) {}
- /**
- * Copies data of the given source texture to the given destination texture.
- *
- * @abstract
- * @param {Texture} srcTexture - The source texture.
- * @param {Texture} dstTexture - The destination texture.
- * @param {?(Box3|Box2)} [srcRegion=null] - The region of the source texture to copy.
- * @param {?(Vector2|Vector3)} [dstPosition=null] - The destination position of the copy.
- * @param {number} [srcLevel=0] - The source mip level to copy from.
- * @param {number} [dstLevel=0] - The destination mip level to copy to.
- */
- copyTextureToTexture( /*srcTexture, dstTexture, srcRegion = null, dstPosition = null, srcLevel = 0, dstLevel = 0*/ ) {}
- /**
- * Copies the current bound framebuffer to the given texture.
- *
- * @abstract
- * @param {Texture} texture - The destination texture.
- * @param {RenderContext} renderContext - The render context.
- * @param {Vector4} rectangle - A four dimensional vector defining the origin and dimension of the copy.
- */
- copyFramebufferToTexture( /*texture, renderContext, rectangle*/ ) {}
- // attributes
- /**
- * Creates the GPU buffer of a shader attribute.
- *
- * @abstract
- * @param {BufferAttribute} attribute - The buffer attribute.
- */
- createAttribute( /*attribute*/ ) { }
- /**
- * Creates the GPU buffer of an indexed shader attribute.
- *
- * @abstract
- * @param {BufferAttribute} attribute - The indexed buffer attribute.
- */
- createIndexAttribute( /*attribute*/ ) { }
- /**
- * Creates the GPU buffer of a storage attribute.
- *
- * @abstract
- * @param {BufferAttribute} attribute - The buffer attribute.
- */
- createStorageAttribute( /*attribute*/ ) { }
- /**
- * Updates the GPU buffer of a shader attribute.
- *
- * @abstract
- * @param {BufferAttribute} attribute - The buffer attribute to update.
- */
- updateAttribute( /*attribute*/ ) { }
- /**
- * Destroys the GPU buffer of a shader attribute.
- *
- * @abstract
- * @param {BufferAttribute} attribute - The buffer attribute to destroy.
- */
- destroyAttribute( /*attribute*/ ) { }
- // canvas
- /**
- * Returns the backend's rendering context.
- *
- * @abstract
- * @return {Object} The rendering context.
- */
- getContext() { }
- /**
- * Backends can use this method if they have to run
- * logic when the renderer gets resized.
- *
- * @abstract
- */
- updateSize() { }
- /**
- * Updates the viewport with the values from the given render context.
- *
- * @abstract
- * @param {RenderContext} renderContext - The render context.
- */
- updateViewport( /*renderContext*/ ) {}
- // utils
- /**
- * Updates a unique identifier for the given render context that can be used
- * to allocate resources like occlusion queries or timestamp queries.
- *
- * @param {RenderContext|ComputeNode} abstractRenderContext - The render context.
- */
- updateTimeStampUID( abstractRenderContext ) {
- const contextData = this.get( abstractRenderContext );
- const frame = this.renderer.info.frame;
- let prefix;
- if ( abstractRenderContext.isComputeNode === true ) {
- prefix = 'c:' + this.renderer.info.compute.frameCalls;
- } else {
- prefix = 'r:' + this.renderer.info.render.frameCalls;
- }
- contextData.timestampUID = prefix + ':' + abstractRenderContext.id + ':f' + frame;
- }
- /**
- * Returns a unique identifier for the given render context that can be used
- * to allocate resources like occlusion queries or timestamp queries.
- *
- * @param {RenderContext|ComputeNode} abstractRenderContext - The render context.
- * @return {string} The unique identifier.
- */
- getTimestampUID( abstractRenderContext ) {
- return this.get( abstractRenderContext ).timestampUID;
- }
- /**
- * Returns all timestamp frames for the given type.
- *
- * @param {string} type - The type of the time stamp.
- * @return {Array<number>} The timestamp frames.
- */
- getTimestampFrames( type ) {
- const queryPool = this.timestampQueryPool[ type ];
- return queryPool ? queryPool.getTimestampFrames() : [];
- }
- /**
- * Returns the query pool for the given uid.
- *
- * @param {string} uid - The unique identifier.
- * @return {TimestampQueryPool} The query pool.
- */
- _getQueryPool( uid ) {
- const type = uid.startsWith( 'c:' ) ? TimestampQuery.COMPUTE : TimestampQuery.RENDER;
- const queryPool = this.timestampQueryPool[ type ];
- return queryPool;
- }
- /**
- * Returns the timestamp for the given uid.
- *
- * @param {string} uid - The unique identifier.
- * @return {number} The timestamp.
- */
- getTimestamp( uid ) {
- const queryPool = this._getQueryPool( uid );
- return queryPool.getTimestamp( uid );
- }
- /**
- * Returns `true` if a timestamp for the given uid is available.
- *
- * @param {string} uid - The unique identifier.
- * @return {boolean} Whether the timestamp is available or not.
- */
- hasTimestamp( uid ) {
- const queryPool = this._getQueryPool( uid );
- return queryPool.hasTimestamp( uid );
- }
- /**
- * Returns `true` if the given 3D object is fully occluded by other
- * 3D objects in the scene. Backends must implement this method by using
- * a Occlusion Query API.
- *
- * @abstract
- * @param {RenderContext} renderContext - The render context.
- * @param {Object3D} object - The 3D object to test.
- * @return {boolean} Whether the 3D object is fully occluded or not.
- */
- isOccluded( /*renderContext, object*/ ) {}
- /**
- * Resolves the time stamp for the given render context and type.
- *
- * @async
- * @abstract
- * @param {string} [type='render'] - The type of the time stamp.
- * @return {Promise<number>} A Promise that resolves with the time stamp.
- */
- async resolveTimestampsAsync( type = 'render' ) {
- if ( ! this.trackTimestamp ) {
- warnOnce( 'WebGPURenderer: Timestamp tracking is disabled.' );
- return;
- }
- const queryPool = this.timestampQueryPool[ type ];
- if ( ! queryPool ) {
- return;
- }
- const duration = await queryPool.resolveQueriesAsync();
- this.renderer.info[ type ].timestamp = duration;
- return duration;
- }
- /**
- * This method performs a readback operation by moving buffer data from
- * a storage buffer attribute from the GPU to the CPU.
- *
- * @async
- * @param {StorageBufferAttribute} attribute - The storage buffer attribute.
- * @return {Promise<ArrayBuffer>} A promise that resolves with the buffer data when the data are ready.
- */
- async getArrayBufferAsync( /* attribute */ ) {}
- /**
- * Checks if the given feature is supported by the backend.
- *
- * @async
- * @abstract
- * @param {string} name - The feature's name.
- * @return {Promise<boolean>} A Promise that resolves with a bool that indicates whether the feature is supported or not.
- */
- async hasFeatureAsync( /*name*/ ) { }
- /**
- * Checks if the given feature is supported by the backend.
- *
- * @abstract
- * @param {string} name - The feature's name.
- * @return {boolean} Whether the feature is supported or not.
- */
- hasFeature( /*name*/ ) {}
- /**
- * Returns the maximum anisotropy texture filtering value.
- *
- * @abstract
- * @return {number} The maximum anisotropy texture filtering value.
- */
- getMaxAnisotropy() {}
- /**
- * Returns the drawing buffer size.
- *
- * @return {Vector2} The drawing buffer size.
- */
- getDrawingBufferSize() {
- _vector2 = _vector2 || new Vector2();
- return this.renderer.getDrawingBufferSize( _vector2 );
- }
- /**
- * Defines the scissor test.
- *
- * @abstract
- * @param {boolean} boolean - Whether the scissor test should be enabled or not.
- */
- setScissorTest( /*boolean*/ ) { }
- /**
- * Returns the clear color and alpha into a single
- * color object.
- *
- * @return {Color4} The clear color.
- */
- getClearColor() {
- const renderer = this.renderer;
- _color4 = _color4 || new Color4();
- renderer.getClearColor( _color4 );
- _color4.getRGB( _color4 );
- return _color4;
- }
- /**
- * Returns the DOM element. If no DOM element exists, the backend
- * creates a new one.
- *
- * @return {HTMLCanvasElement} The DOM element.
- */
- getDomElement() {
- let domElement = this.domElement;
- if ( domElement === null ) {
- domElement = ( this.parameters.canvas !== undefined ) ? this.parameters.canvas : createCanvasElement();
- // OffscreenCanvas does not have setAttribute, see #22811
- if ( 'setAttribute' in domElement ) domElement.setAttribute( 'data-engine', `three.js r${REVISION} webgpu` );
- this.domElement = domElement;
- }
- return domElement;
- }
- /**
- * Sets a dictionary for the given object into the
- * internal data structure.
- *
- * @param {Object} object - The object.
- * @param {Object} value - The dictionary to set.
- */
- set( object, value ) {
- this.data.set( object, value );
- }
- /**
- * Returns the dictionary for the given object.
- *
- * @param {Object} object - The object.
- * @return {Object} The object's dictionary.
- */
- get( object ) {
- let map = this.data.get( object );
- if ( map === undefined ) {
- map = {};
- this.data.set( object, map );
- }
- return map;
- }
- /**
- * Checks if the given object has a dictionary
- * with data defined.
- *
- * @param {Object} object - The object.
- * @return {boolean} Whether a dictionary for the given object as been defined or not.
- */
- has( object ) {
- return this.data.has( object );
- }
- /**
- * Deletes an object from the internal data structure.
- *
- * @param {Object} object - The object to delete.
- */
- delete( object ) {
- this.data.delete( object );
- }
- /**
- * Delete GPU data associated with a bind group.
- *
- * @abstract
- * @param {BindGroup} bindGroup - The bind group.
- */
- deleteBindGroupData( /*bindGroup*/ ) { }
- /**
- * Frees internal resources.
- *
- * @abstract
- */
- dispose() { }
- }
- let _id$1 = 0;
- /**
- * This module is internally used in context of compute shaders.
- * This type of shader is not natively supported in WebGL 2 and
- * thus implemented via Transform Feedback. `DualAttributeData`
- * manages the related data.
- *
- * @private
- */
- class DualAttributeData {
- constructor( attributeData, dualBuffer ) {
- this.buffers = [ attributeData.bufferGPU, dualBuffer ];
- this.type = attributeData.type;
- this.bufferType = attributeData.bufferType;
- this.pbo = attributeData.pbo;
- this.byteLength = attributeData.byteLength;
- this.bytesPerElement = attributeData.BYTES_PER_ELEMENT;
- this.version = attributeData.version;
- this.isInteger = attributeData.isInteger;
- this.activeBufferIndex = 0;
- this.baseId = attributeData.id;
- }
- get id() {
- return `${ this.baseId }|${ this.activeBufferIndex }`;
- }
- get bufferGPU() {
- return this.buffers[ this.activeBufferIndex ];
- }
- get transformBuffer() {
- return this.buffers[ this.activeBufferIndex ^ 1 ];
- }
- switchBuffers() {
- this.activeBufferIndex ^= 1;
- }
- }
- /**
- * A WebGL 2 backend utility module for managing shader attributes.
- *
- * @private
- */
- class WebGLAttributeUtils {
- /**
- * Constructs a new utility object.
- *
- * @param {WebGLBackend} backend - The WebGL 2 backend.
- */
- constructor( backend ) {
- /**
- * A reference to the WebGL 2 backend.
- *
- * @type {WebGLBackend}
- */
- this.backend = backend;
- }
- /**
- * Creates the GPU buffer for the given buffer attribute.
- *
- * @param {BufferAttribute} attribute - The buffer attribute.
- * @param {GLenum } bufferType - A flag that indicates the buffer type and thus binding point target.
- */
- createAttribute( attribute, bufferType ) {
- const backend = this.backend;
- const { gl } = backend;
- const array = attribute.array;
- const usage = attribute.usage || gl.STATIC_DRAW;
- const bufferAttribute = attribute.isInterleavedBufferAttribute ? attribute.data : attribute;
- const bufferData = backend.get( bufferAttribute );
- let bufferGPU = bufferData.bufferGPU;
- if ( bufferGPU === undefined ) {
- bufferGPU = this._createBuffer( gl, bufferType, array, usage );
- bufferData.bufferGPU = bufferGPU;
- bufferData.bufferType = bufferType;
- bufferData.version = bufferAttribute.version;
- }
- //attribute.onUploadCallback();
- let type;
- if ( array instanceof Float32Array ) {
- type = gl.FLOAT;
- } else if ( typeof Float16Array !== 'undefined' && array instanceof Float16Array ) {
- type = gl.HALF_FLOAT;
- } else if ( array instanceof Uint16Array ) {
- if ( attribute.isFloat16BufferAttribute ) {
- type = gl.HALF_FLOAT;
- } else {
- type = gl.UNSIGNED_SHORT;
- }
- } else if ( array instanceof Int16Array ) {
- type = gl.SHORT;
- } else if ( array instanceof Uint32Array ) {
- type = gl.UNSIGNED_INT;
- } else if ( array instanceof Int32Array ) {
- type = gl.INT;
- } else if ( array instanceof Int8Array ) {
- type = gl.BYTE;
- } else if ( array instanceof Uint8Array ) {
- type = gl.UNSIGNED_BYTE;
- } else if ( array instanceof Uint8ClampedArray ) {
- type = gl.UNSIGNED_BYTE;
- } else {
- throw new Error( 'THREE.WebGLBackend: Unsupported buffer data format: ' + array );
- }
- let attributeData = {
- bufferGPU,
- bufferType,
- type,
- byteLength: array.byteLength,
- bytesPerElement: array.BYTES_PER_ELEMENT,
- version: attribute.version,
- pbo: attribute.pbo,
- isInteger: type === gl.INT || type === gl.UNSIGNED_INT || attribute.gpuType === IntType,
- id: _id$1 ++
- };
- if ( attribute.isStorageBufferAttribute || attribute.isStorageInstancedBufferAttribute ) {
- // create buffer for transform feedback use
- const bufferGPUDual = this._createBuffer( gl, bufferType, array, usage );
- attributeData = new DualAttributeData( attributeData, bufferGPUDual );
- }
- backend.set( attribute, attributeData );
- }
- /**
- * Updates the GPU buffer of the given buffer attribute.
- *
- * @param {BufferAttribute} attribute - The buffer attribute.
- */
- updateAttribute( attribute ) {
- const backend = this.backend;
- const { gl } = backend;
- const array = attribute.array;
- const bufferAttribute = attribute.isInterleavedBufferAttribute ? attribute.data : attribute;
- const bufferData = backend.get( bufferAttribute );
- const bufferType = bufferData.bufferType;
- const updateRanges = attribute.isInterleavedBufferAttribute ? attribute.data.updateRanges : attribute.updateRanges;
- gl.bindBuffer( bufferType, bufferData.bufferGPU );
- if ( updateRanges.length === 0 ) {
- // Not using update ranges
- gl.bufferSubData( bufferType, 0, array );
- } else {
- for ( let i = 0, l = updateRanges.length; i < l; i ++ ) {
- const range = updateRanges[ i ];
- gl.bufferSubData( bufferType, range.start * array.BYTES_PER_ELEMENT,
- array, range.start, range.count );
- }
- bufferAttribute.clearUpdateRanges();
- }
- gl.bindBuffer( bufferType, null );
- bufferData.version = bufferAttribute.version;
- }
- /**
- * Destroys the GPU buffer of the given buffer attribute.
- *
- * @param {BufferAttribute} attribute - The buffer attribute.
- */
- destroyAttribute( attribute ) {
- const backend = this.backend;
- const { gl } = backend;
- if ( attribute.isInterleavedBufferAttribute ) {
- backend.delete( attribute.data );
- }
- const attributeData = backend.get( attribute );
- gl.deleteBuffer( attributeData.bufferGPU );
- backend.delete( attribute );
- }
- /**
- * This method performs a readback operation by moving buffer data from
- * a storage buffer attribute from the GPU to the CPU.
- *
- * @async
- * @param {StorageBufferAttribute} attribute - The storage buffer attribute.
- * @return {Promise<ArrayBuffer>} A promise that resolves with the buffer data when the data are ready.
- */
- async getArrayBufferAsync( attribute ) {
- const backend = this.backend;
- const { gl } = backend;
- const bufferAttribute = attribute.isInterleavedBufferAttribute ? attribute.data : attribute;
- const { bufferGPU } = backend.get( bufferAttribute );
- const array = attribute.array;
- const byteLength = array.byteLength;
- gl.bindBuffer( gl.COPY_READ_BUFFER, bufferGPU );
- const writeBuffer = gl.createBuffer();
- gl.bindBuffer( gl.COPY_WRITE_BUFFER, writeBuffer );
- gl.bufferData( gl.COPY_WRITE_BUFFER, byteLength, gl.STREAM_READ );
- gl.copyBufferSubData( gl.COPY_READ_BUFFER, gl.COPY_WRITE_BUFFER, 0, 0, byteLength );
- await backend.utils._clientWaitAsync();
- const dstBuffer = new attribute.array.constructor( array.length );
- // Ensure the buffer is bound before reading
- gl.bindBuffer( gl.COPY_WRITE_BUFFER, writeBuffer );
- gl.getBufferSubData( gl.COPY_WRITE_BUFFER, 0, dstBuffer );
- gl.deleteBuffer( writeBuffer );
- gl.bindBuffer( gl.COPY_READ_BUFFER, null );
- gl.bindBuffer( gl.COPY_WRITE_BUFFER, null );
- return dstBuffer.buffer;
- }
- /**
- * Creates a WebGL buffer with the given data.
- *
- * @private
- * @param {WebGL2RenderingContext} gl - The rendering context.
- * @param {GLenum } bufferType - A flag that indicates the buffer type and thus binding point target.
- * @param {TypedArray} array - The array of the buffer attribute.
- * @param {GLenum} usage - The usage.
- * @return {WebGLBuffer} The WebGL buffer.
- */
- _createBuffer( gl, bufferType, array, usage ) {
- const bufferGPU = gl.createBuffer();
- gl.bindBuffer( bufferType, bufferGPU );
- gl.bufferData( bufferType, array, usage );
- gl.bindBuffer( bufferType, null );
- return bufferGPU;
- }
- }
- let equationToGL, factorToGL;
- /**
- * A WebGL 2 backend utility module for managing the WebGL state.
- *
- * The major goal of this module is to reduce the number of state changes
- * by caching the WEbGL state with a series of variables. In this way, the
- * renderer only executes state change commands when necessary which
- * improves the overall performance.
- *
- * @private
- */
- class WebGLState {
- /**
- * Constructs a new utility object.
- *
- * @param {WebGLBackend} backend - The WebGL 2 backend.
- */
- constructor( backend ) {
- /**
- * A reference to the WebGL 2 backend.
- *
- * @type {WebGLBackend}
- */
- this.backend = backend;
- /**
- * A reference to the rendering context.
- *
- * @type {WebGL2RenderingContext}
- */
- this.gl = this.backend.gl;
- // Below properties are intended to cache
- // the WebGL state and are not explicitly
- // documented for convenience reasons.
- this.enabled = {};
- this.currentFlipSided = null;
- this.currentCullFace = null;
- this.currentProgram = null;
- this.currentBlendingEnabled = false;
- this.currentBlending = null;
- this.currentBlendSrc = null;
- this.currentBlendDst = null;
- this.currentBlendSrcAlpha = null;
- this.currentBlendDstAlpha = null;
- this.currentPremultipledAlpha = null;
- this.currentPolygonOffsetFactor = null;
- this.currentPolygonOffsetUnits = null;
- this.currentColorMask = null;
- this.currentDepthFunc = null;
- this.currentDepthMask = null;
- this.currentStencilFunc = null;
- this.currentStencilRef = null;
- this.currentStencilFuncMask = null;
- this.currentStencilFail = null;
- this.currentStencilZFail = null;
- this.currentStencilZPass = null;
- this.currentStencilMask = null;
- this.currentLineWidth = null;
- this.currentClippingPlanes = 0;
- this.currentVAO = null;
- this.currentIndex = null;
- this.currentBoundFramebuffers = {};
- this.currentDrawbuffers = new WeakMap();
- this.maxTextures = this.gl.getParameter( this.gl.MAX_TEXTURE_IMAGE_UNITS );
- this.currentTextureSlot = null;
- this.currentBoundTextures = {};
- this.currentBoundBufferBases = {};
- this._init();
- }
- /**
- * Inits the state of the utility.
- *
- * @private
- */
- _init() {
- const gl = this.gl;
- // Store only WebGL constants here.
- equationToGL = {
- [ AddEquation ]: gl.FUNC_ADD,
- [ SubtractEquation ]: gl.FUNC_SUBTRACT,
- [ ReverseSubtractEquation ]: gl.FUNC_REVERSE_SUBTRACT
- };
- factorToGL = {
- [ ZeroFactor ]: gl.ZERO,
- [ OneFactor ]: gl.ONE,
- [ SrcColorFactor ]: gl.SRC_COLOR,
- [ SrcAlphaFactor ]: gl.SRC_ALPHA,
- [ SrcAlphaSaturateFactor ]: gl.SRC_ALPHA_SATURATE,
- [ DstColorFactor ]: gl.DST_COLOR,
- [ DstAlphaFactor ]: gl.DST_ALPHA,
- [ OneMinusSrcColorFactor ]: gl.ONE_MINUS_SRC_COLOR,
- [ OneMinusSrcAlphaFactor ]: gl.ONE_MINUS_SRC_ALPHA,
- [ OneMinusDstColorFactor ]: gl.ONE_MINUS_DST_COLOR,
- [ OneMinusDstAlphaFactor ]: gl.ONE_MINUS_DST_ALPHA
- };
- const scissorParam = gl.getParameter( gl.SCISSOR_BOX );
- const viewportParam = gl.getParameter( gl.VIEWPORT );
- this.currentScissor = new Vector4().fromArray( scissorParam );
- this.currentViewport = new Vector4().fromArray( viewportParam );
- this._tempVec4 = new Vector4();
- }
- /**
- * Enables the given WebGL capability.
- *
- * This method caches the capability state so
- * `gl.enable()` is only called when necessary.
- *
- * @param {GLenum} id - The capability to enable.
- */
- enable( id ) {
- const { enabled } = this;
- if ( enabled[ id ] !== true ) {
- this.gl.enable( id );
- enabled[ id ] = true;
- }
- }
- /**
- * Disables the given WebGL capability.
- *
- * This method caches the capability state so
- * `gl.disable()` is only called when necessary.
- *
- * @param {GLenum} id - The capability to enable.
- */
- disable( id ) {
- const { enabled } = this;
- if ( enabled[ id ] !== false ) {
- this.gl.disable( id );
- enabled[ id ] = false;
- }
- }
- /**
- * Specifies whether polygons are front- or back-facing
- * by setting the winding orientation.
- *
- * This method caches the state so `gl.frontFace()` is only
- * called when necessary.
- *
- * @param {boolean} flipSided - Whether triangles flipped their sides or not.
- */
- setFlipSided( flipSided ) {
- if ( this.currentFlipSided !== flipSided ) {
- const { gl } = this;
- if ( flipSided ) {
- gl.frontFace( gl.CW );
- } else {
- gl.frontFace( gl.CCW );
- }
- this.currentFlipSided = flipSided;
- }
- }
- /**
- * Specifies whether or not front- and/or back-facing
- * polygons can be culled.
- *
- * This method caches the state so `gl.cullFace()` is only
- * called when necessary.
- *
- * @param {number} cullFace - Defines which polygons are candidates for culling.
- */
- setCullFace( cullFace ) {
- const { gl } = this;
- if ( cullFace !== CullFaceNone ) {
- this.enable( gl.CULL_FACE );
- if ( cullFace !== this.currentCullFace ) {
- if ( cullFace === CullFaceBack ) {
- gl.cullFace( gl.BACK );
- } else if ( cullFace === CullFaceFront ) {
- gl.cullFace( gl.FRONT );
- } else {
- gl.cullFace( gl.FRONT_AND_BACK );
- }
- }
- } else {
- this.disable( gl.CULL_FACE );
- }
- this.currentCullFace = cullFace;
- }
- /**
- * Specifies the width of line primitives.
- *
- * This method caches the state so `gl.lineWidth()` is only
- * called when necessary.
- *
- * @param {number} width - The line width.
- */
- setLineWidth( width ) {
- const { currentLineWidth, gl } = this;
- if ( width !== currentLineWidth ) {
- gl.lineWidth( width );
- this.currentLineWidth = width;
- }
- }
- setMRTBlending( textures ) {
- const gl = this.gl;
- const drawBuffersIndexedExt = this.backend.drawBuffersIndexedExt;
- if ( ! drawBuffersIndexedExt ) return;
- for ( let i = 1; i < textures.length; i ++ ) {
- // use opaque blending for additional render targets
- drawBuffersIndexedExt.blendFuncSeparateiOES( i, gl.ONE, gl.ZERO, gl.ONE, gl.ZERO );
- }
- }
- /**
- * Defines the blending.
- *
- * This method caches the state so `gl.blendEquation()`, `gl.blendEquationSeparate()`,
- * `gl.blendFunc()` and `gl.blendFuncSeparate()` are only called when necessary.
- *
- * @param {number} blending - The blending type.
- * @param {number} blendEquation - The blending equation.
- * @param {number} blendSrc - Only relevant for custom blending. The RGB source blending factor.
- * @param {number} blendDst - Only relevant for custom blending. The RGB destination blending factor.
- * @param {number} blendEquationAlpha - Only relevant for custom blending. The blending equation for alpha.
- * @param {number} blendSrcAlpha - Only relevant for custom blending. The alpha source blending factor.
- * @param {number} blendDstAlpha - Only relevant for custom blending. The alpha destination blending factor.
- * @param {boolean} premultipliedAlpha - Whether premultiplied alpha is enabled or not.
- */
- setBlending( blending, blendEquation, blendSrc, blendDst, blendEquationAlpha, blendSrcAlpha, blendDstAlpha, premultipliedAlpha ) {
- const { gl } = this;
- if ( blending === NoBlending ) {
- if ( this.currentBlendingEnabled === true ) {
- this.disable( gl.BLEND );
- this.currentBlendingEnabled = false;
- }
- return;
- }
- if ( this.currentBlendingEnabled === false ) {
- this.enable( gl.BLEND );
- this.currentBlendingEnabled = true;
- }
- if ( blending !== CustomBlending ) {
- if ( blending !== this.currentBlending || premultipliedAlpha !== this.currentPremultipledAlpha ) {
- if ( this.currentBlendEquation !== AddEquation || this.currentBlendEquationAlpha !== AddEquation ) {
- gl.blendEquation( gl.FUNC_ADD );
- this.currentBlendEquation = AddEquation;
- this.currentBlendEquationAlpha = AddEquation;
- }
- if ( premultipliedAlpha ) {
- switch ( blending ) {
- case NormalBlending:
- gl.blendFuncSeparate( gl.ONE, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA );
- break;
- case AdditiveBlending:
- gl.blendFunc( gl.ONE, gl.ONE );
- break;
- case SubtractiveBlending:
- gl.blendFuncSeparate( gl.ZERO, gl.ONE_MINUS_SRC_COLOR, gl.ZERO, gl.ONE );
- break;
- case MultiplyBlending:
- gl.blendFuncSeparate( gl.DST_COLOR, gl.ONE_MINUS_SRC_ALPHA, gl.ZERO, gl.ONE );
- break;
- default:
- error( 'WebGLState: Invalid blending: ', blending );
- break;
- }
- } else {
- switch ( blending ) {
- case NormalBlending:
- gl.blendFuncSeparate( gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA );
- break;
- case AdditiveBlending:
- gl.blendFuncSeparate( gl.SRC_ALPHA, gl.ONE, gl.ONE, gl.ONE );
- break;
- case SubtractiveBlending:
- error( 'WebGLState: SubtractiveBlending requires material.premultipliedAlpha = true' );
- break;
- case MultiplyBlending:
- error( 'WebGLState: MultiplyBlending requires material.premultipliedAlpha = true' );
- break;
- default:
- error( 'WebGLState: Invalid blending: ', blending );
- break;
- }
- }
- this.currentBlendSrc = null;
- this.currentBlendDst = null;
- this.currentBlendSrcAlpha = null;
- this.currentBlendDstAlpha = null;
- this.currentBlending = blending;
- this.currentPremultipledAlpha = premultipliedAlpha;
- }
- return;
- }
- // custom blending
- blendEquationAlpha = blendEquationAlpha || blendEquation;
- blendSrcAlpha = blendSrcAlpha || blendSrc;
- blendDstAlpha = blendDstAlpha || blendDst;
- if ( blendEquation !== this.currentBlendEquation || blendEquationAlpha !== this.currentBlendEquationAlpha ) {
- gl.blendEquationSeparate( equationToGL[ blendEquation ], equationToGL[ blendEquationAlpha ] );
- this.currentBlendEquation = blendEquation;
- this.currentBlendEquationAlpha = blendEquationAlpha;
- }
- if ( blendSrc !== this.currentBlendSrc || blendDst !== this.currentBlendDst || blendSrcAlpha !== this.currentBlendSrcAlpha || blendDstAlpha !== this.currentBlendDstAlpha ) {
- gl.blendFuncSeparate( factorToGL[ blendSrc ], factorToGL[ blendDst ], factorToGL[ blendSrcAlpha ], factorToGL[ blendDstAlpha ] );
- this.currentBlendSrc = blendSrc;
- this.currentBlendDst = blendDst;
- this.currentBlendSrcAlpha = blendSrcAlpha;
- this.currentBlendDstAlpha = blendDstAlpha;
- }
- this.currentBlending = blending;
- this.currentPremultipledAlpha = false;
- }
- /**
- * Specifies whether colors can be written when rendering
- * into a framebuffer or not.
- *
- * This method caches the state so `gl.colorMask()` is only
- * called when necessary.
- *
- * @param {boolean} colorMask - The color mask.
- */
- setColorMask( colorMask ) {
- if ( this.currentColorMask !== colorMask ) {
- this.gl.colorMask( colorMask, colorMask, colorMask, colorMask );
- this.currentColorMask = colorMask;
- }
- }
- /**
- * Specifies whether the depth test is enabled or not.
- *
- * @param {boolean} depthTest - Whether the depth test is enabled or not.
- */
- setDepthTest( depthTest ) {
- const { gl } = this;
- if ( depthTest ) {
- this.enable( gl.DEPTH_TEST );
- } else {
- this.disable( gl.DEPTH_TEST );
- }
- }
- /**
- * Specifies whether depth values can be written when rendering
- * into a framebuffer or not.
- *
- * This method caches the state so `gl.depthMask()` is only
- * called when necessary.
- *
- * @param {boolean} depthMask - The depth mask.
- */
- setDepthMask( depthMask ) {
- if ( this.currentDepthMask !== depthMask ) {
- this.gl.depthMask( depthMask );
- this.currentDepthMask = depthMask;
- }
- }
- /**
- * Specifies the depth compare function.
- *
- * This method caches the state so `gl.depthFunc()` is only
- * called when necessary.
- *
- * @param {number} depthFunc - The depth compare function.
- */
- setDepthFunc( depthFunc ) {
- if ( this.currentDepthFunc !== depthFunc ) {
- const { gl } = this;
- switch ( depthFunc ) {
- case NeverDepth:
- gl.depthFunc( gl.NEVER );
- break;
- case AlwaysDepth:
- gl.depthFunc( gl.ALWAYS );
- break;
- case LessDepth:
- gl.depthFunc( gl.LESS );
- break;
- case LessEqualDepth:
- gl.depthFunc( gl.LEQUAL );
- break;
- case EqualDepth:
- gl.depthFunc( gl.EQUAL );
- break;
- case GreaterEqualDepth:
- gl.depthFunc( gl.GEQUAL );
- break;
- case GreaterDepth:
- gl.depthFunc( gl.GREATER );
- break;
- case NotEqualDepth:
- gl.depthFunc( gl.NOTEQUAL );
- break;
- default:
- gl.depthFunc( gl.LEQUAL );
- }
- this.currentDepthFunc = depthFunc;
- }
- }
- /**
- * Specifies the scissor box.
- *
- * @param {number} x - The x-coordinate of the lower left corner of the viewport.
- * @param {number} y - The y-coordinate of the lower left corner of the viewport.
- * @param {number} width - The width of the viewport.
- * @param {number} height - The height of the viewport.
- *
- */
- scissor( x, y, width, height ) {
- const scissor = this._tempVec4.set( x, y, width, height );
- if ( this.currentScissor.equals( scissor ) === false ) {
- const { gl } = this;
- gl.scissor( scissor.x, scissor.y, scissor.z, scissor.w );
- this.currentScissor.copy( scissor );
- }
- }
- /**
- * Specifies the viewport.
- *
- * @param {number} x - The x-coordinate of the lower left corner of the viewport.
- * @param {number} y - The y-coordinate of the lower left corner of the viewport.
- * @param {number} width - The width of the viewport.
- * @param {number} height - The height of the viewport.
- *
- */
- viewport( x, y, width, height ) {
- const viewport = this._tempVec4.set( x, y, width, height );
- if ( this.currentViewport.equals( viewport ) === false ) {
- const { gl } = this;
- gl.viewport( viewport.x, viewport.y, viewport.z, viewport.w );
- this.currentViewport.copy( viewport );
- }
- }
- /**
- * Defines the scissor test.
- *
- * @param {boolean} boolean - Whether the scissor test should be enabled or not.
- */
- setScissorTest( boolean ) {
- const gl = this.gl;
- if ( boolean ) {
- this.enable( gl.SCISSOR_TEST );
- } else {
- this.disable( gl.SCISSOR_TEST );
- }
- }
- /**
- * Specifies whether the stencil test is enabled or not.
- *
- * @param {boolean} stencilTest - Whether the stencil test is enabled or not.
- */
- setStencilTest( stencilTest ) {
- const { gl } = this;
- if ( stencilTest ) {
- this.enable( gl.STENCIL_TEST );
- } else {
- this.disable( gl.STENCIL_TEST );
- }
- }
- /**
- * Specifies whether stencil values can be written when rendering
- * into a framebuffer or not.
- *
- * This method caches the state so `gl.stencilMask()` is only
- * called when necessary.
- *
- * @param {boolean} stencilMask - The stencil mask.
- */
- setStencilMask( stencilMask ) {
- if ( this.currentStencilMask !== stencilMask ) {
- this.gl.stencilMask( stencilMask );
- this.currentStencilMask = stencilMask;
- }
- }
- /**
- * Specifies whether the stencil test functions.
- *
- * This method caches the state so `gl.stencilFunc()` is only
- * called when necessary.
- *
- * @param {number} stencilFunc - The stencil compare function.
- * @param {number} stencilRef - The reference value for the stencil test.
- * @param {number} stencilMask - A bit-wise mask that is used to AND the reference value and the stored stencil value when the test is done.
- */
- setStencilFunc( stencilFunc, stencilRef, stencilMask ) {
- if ( this.currentStencilFunc !== stencilFunc ||
- this.currentStencilRef !== stencilRef ||
- this.currentStencilFuncMask !== stencilMask ) {
- this.gl.stencilFunc( stencilFunc, stencilRef, stencilMask );
- this.currentStencilFunc = stencilFunc;
- this.currentStencilRef = stencilRef;
- this.currentStencilFuncMask = stencilMask;
- }
- }
- /**
- * Specifies whether the stencil test operation.
- *
- * This method caches the state so `gl.stencilOp()` is only
- * called when necessary.
- *
- * @param {number} stencilFail - The function to use when the stencil test fails.
- * @param {number} stencilZFail - The function to use when the stencil test passes, but the depth test fail.
- * @param {number} stencilZPass - The function to use when both the stencil test and the depth test pass,
- * or when the stencil test passes and there is no depth buffer or depth testing is disabled.
- */
- setStencilOp( stencilFail, stencilZFail, stencilZPass ) {
- if ( this.currentStencilFail !== stencilFail ||
- this.currentStencilZFail !== stencilZFail ||
- this.currentStencilZPass !== stencilZPass ) {
- this.gl.stencilOp( stencilFail, stencilZFail, stencilZPass );
- this.currentStencilFail = stencilFail;
- this.currentStencilZFail = stencilZFail;
- this.currentStencilZPass = stencilZPass;
- }
- }
- /**
- * Configures the WebGL state for the given material.
- *
- * @param {Material} material - The material to configure the state for.
- * @param {number} frontFaceCW - Whether the front faces are counter-clockwise or not.
- * @param {number} hardwareClippingPlanes - The number of hardware clipping planes.
- */
- setMaterial( material, frontFaceCW, hardwareClippingPlanes ) {
- const { gl } = this;
- material.side === DoubleSide
- ? this.disable( gl.CULL_FACE )
- : this.enable( gl.CULL_FACE );
- let flipSided = ( material.side === BackSide );
- if ( frontFaceCW ) flipSided = ! flipSided;
- this.setFlipSided( flipSided );
- ( material.blending === NormalBlending && material.transparent === false )
- ? this.setBlending( NoBlending )
- : this.setBlending( material.blending, material.blendEquation, material.blendSrc, material.blendDst, material.blendEquationAlpha, material.blendSrcAlpha, material.blendDstAlpha, material.premultipliedAlpha );
- this.setDepthFunc( material.depthFunc );
- this.setDepthTest( material.depthTest );
- this.setDepthMask( material.depthWrite );
- this.setColorMask( material.colorWrite );
- const stencilWrite = material.stencilWrite;
- this.setStencilTest( stencilWrite );
- if ( stencilWrite ) {
- this.setStencilMask( material.stencilWriteMask );
- this.setStencilFunc( material.stencilFunc, material.stencilRef, material.stencilFuncMask );
- this.setStencilOp( material.stencilFail, material.stencilZFail, material.stencilZPass );
- }
- this.setPolygonOffset( material.polygonOffset, material.polygonOffsetFactor, material.polygonOffsetUnits );
- material.alphaToCoverage === true && this.backend.renderer.currentSamples > 0
- ? this.enable( gl.SAMPLE_ALPHA_TO_COVERAGE )
- : this.disable( gl.SAMPLE_ALPHA_TO_COVERAGE );
- if ( hardwareClippingPlanes > 0 ) {
- if ( this.currentClippingPlanes !== hardwareClippingPlanes ) {
- const CLIP_DISTANCE0_WEBGL = 0x3000;
- for ( let i = 0; i < 8; i ++ ) {
- if ( i < hardwareClippingPlanes ) {
- this.enable( CLIP_DISTANCE0_WEBGL + i );
- } else {
- this.disable( CLIP_DISTANCE0_WEBGL + i );
- }
- }
- }
- }
- }
- /**
- * Specifies the polygon offset.
- *
- * This method caches the state so `gl.polygonOffset()` is only
- * called when necessary.
- *
- * @param {boolean} polygonOffset - Whether polygon offset is enabled or not.
- * @param {number} factor - The scale factor for the variable depth offset for each polygon.
- * @param {number} units - The multiplier by which an implementation-specific value is multiplied with to create a constant depth offset.
- */
- setPolygonOffset( polygonOffset, factor, units ) {
- const { gl } = this;
- if ( polygonOffset ) {
- this.enable( gl.POLYGON_OFFSET_FILL );
- if ( this.currentPolygonOffsetFactor !== factor || this.currentPolygonOffsetUnits !== units ) {
- gl.polygonOffset( factor, units );
- this.currentPolygonOffsetFactor = factor;
- this.currentPolygonOffsetUnits = units;
- }
- } else {
- this.disable( gl.POLYGON_OFFSET_FILL );
- }
- }
- /**
- * Defines the usage of the given WebGL program.
- *
- * This method caches the state so `gl.useProgram()` is only
- * called when necessary.
- *
- * @param {WebGLProgram} program - The WebGL program to use.
- * @return {boolean} Whether a program change has been executed or not.
- */
- useProgram( program ) {
- if ( this.currentProgram !== program ) {
- this.gl.useProgram( program );
- this.currentProgram = program;
- return true;
- }
- return false;
- }
- /**
- * Sets the vertex state by binding the given VAO and element buffer.
- *
- * @param {WebGLVertexArrayObject} vao - The VAO.
- * @param {?WebGLBuffer} indexBuffer - The index buffer.
- * @return {boolean} Whether a vertex state has been changed or not.
- */
- setVertexState( vao, indexBuffer = null ) {
- const gl = this.gl;
- if ( this.currentVAO !== vao || this.currentIndex !== indexBuffer ) {
- gl.bindVertexArray( vao );
- if ( indexBuffer !== null ) {
- gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, indexBuffer );
- }
- this.currentVAO = vao;
- this.currentIndex = indexBuffer;
- return true;
- }
- return false;
- }
- /**
- * Resets the vertex array state by resetting the VAO and element buffer.
- */
- resetVertexState() {
- const gl = this.gl;
- gl.bindVertexArray( null );
- gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, null );
- this.currentVAO = null;
- this.currentIndex = null;
- }
- // framebuffer
- /**
- * Binds the given framebuffer.
- *
- * This method caches the state so `gl.bindFramebuffer()` is only
- * called when necessary.
- *
- * @param {number} target - The binding point (target).
- * @param {WebGLFramebuffer} framebuffer - The WebGL framebuffer to bind.
- * @return {boolean} Whether a bind has been executed or not.
- */
- bindFramebuffer( target, framebuffer ) {
- const { gl, currentBoundFramebuffers } = this;
- if ( currentBoundFramebuffers[ target ] !== framebuffer ) {
- gl.bindFramebuffer( target, framebuffer );
- currentBoundFramebuffers[ target ] = framebuffer;
- // gl.DRAW_FRAMEBUFFER is equivalent to gl.FRAMEBUFFER
- if ( target === gl.DRAW_FRAMEBUFFER ) {
- currentBoundFramebuffers[ gl.FRAMEBUFFER ] = framebuffer;
- }
- if ( target === gl.FRAMEBUFFER ) {
- currentBoundFramebuffers[ gl.DRAW_FRAMEBUFFER ] = framebuffer;
- }
- return true;
- }
- return false;
- }
- /**
- * Defines draw buffers to which fragment colors are written into.
- * Configures the MRT setup of custom framebuffers.
- *
- * This method caches the state so `gl.drawBuffers()` is only
- * called when necessary.
- *
- * @param {RenderContext} renderContext - The render context.
- * @param {WebGLFramebuffer} framebuffer - The WebGL framebuffer.
- */
- drawBuffers( renderContext, framebuffer ) {
- const { gl } = this;
- let drawBuffers = [];
- let needsUpdate = false;
- if ( renderContext.textures !== null ) {
- drawBuffers = this.currentDrawbuffers.get( framebuffer );
- if ( drawBuffers === undefined ) {
- drawBuffers = [];
- this.currentDrawbuffers.set( framebuffer, drawBuffers );
- }
- const textures = renderContext.textures;
- if ( drawBuffers.length !== textures.length || drawBuffers[ 0 ] !== gl.COLOR_ATTACHMENT0 ) {
- for ( let i = 0, il = textures.length; i < il; i ++ ) {
- drawBuffers[ i ] = gl.COLOR_ATTACHMENT0 + i;
- }
- drawBuffers.length = textures.length;
- needsUpdate = true;
- }
- } else {
- if ( drawBuffers[ 0 ] !== gl.BACK ) {
- drawBuffers[ 0 ] = gl.BACK;
- needsUpdate = true;
- }
- }
- if ( needsUpdate ) {
- gl.drawBuffers( drawBuffers );
- }
- }
- // texture
- /**
- * Makes the given texture unit active.
- *
- * This method caches the state so `gl.activeTexture()` is only
- * called when necessary.
- *
- * @param {number} webglSlot - The texture unit to make active.
- */
- activeTexture( webglSlot ) {
- const { gl, currentTextureSlot, maxTextures } = this;
- if ( webglSlot === undefined ) webglSlot = gl.TEXTURE0 + maxTextures - 1;
- if ( currentTextureSlot !== webglSlot ) {
- gl.activeTexture( webglSlot );
- this.currentTextureSlot = webglSlot;
- }
- }
- /**
- * Binds the given WebGL texture to a target.
- *
- * This method caches the state so `gl.bindTexture()` is only
- * called when necessary.
- *
- * @param {number} webglType - The binding point (target).
- * @param {WebGLTexture} webglTexture - The WebGL texture to bind.
- * @param {number} webglSlot - The texture.
- */
- bindTexture( webglType, webglTexture, webglSlot ) {
- const { gl, currentTextureSlot, currentBoundTextures, maxTextures } = this;
- if ( webglSlot === undefined ) {
- if ( currentTextureSlot === null ) {
- webglSlot = gl.TEXTURE0 + maxTextures - 1;
- } else {
- webglSlot = currentTextureSlot;
- }
- }
- let boundTexture = currentBoundTextures[ webglSlot ];
- if ( boundTexture === undefined ) {
- boundTexture = { type: undefined, texture: undefined };
- currentBoundTextures[ webglSlot ] = boundTexture;
- }
- if ( boundTexture.type !== webglType || boundTexture.texture !== webglTexture ) {
- if ( currentTextureSlot !== webglSlot ) {
- gl.activeTexture( webglSlot );
- this.currentTextureSlot = webglSlot;
- }
- gl.bindTexture( webglType, webglTexture );
- boundTexture.type = webglType;
- boundTexture.texture = webglTexture;
- }
- }
- /**
- * Binds a given WebGL buffer to a given binding point (target) at a given index.
- *
- * This method caches the state so `gl.bindBufferBase()` is only
- * called when necessary.
- *
- * @param {number} target - The target for the bind operation.
- * @param {number} index - The index of the target.
- * @param {WebGLBuffer} buffer - The WebGL buffer.
- * @return {boolean} Whether a bind has been executed or not.
- */
- bindBufferBase( target, index, buffer ) {
- const { gl } = this;
- const key = `${target}-${index}`;
- if ( this.currentBoundBufferBases[ key ] !== buffer ) {
- gl.bindBufferBase( target, index, buffer );
- this.currentBoundBufferBases[ key ] = buffer;
- return true;
- }
- return false;
- }
- /**
- * Unbinds the current bound texture.
- *
- * This method caches the state so `gl.bindTexture()` is only
- * called when necessary.
- */
- unbindTexture() {
- const { gl, currentTextureSlot, currentBoundTextures } = this;
- const boundTexture = currentBoundTextures[ currentTextureSlot ];
- if ( boundTexture !== undefined && boundTexture.type !== undefined ) {
- gl.bindTexture( boundTexture.type, null );
- boundTexture.type = undefined;
- boundTexture.texture = undefined;
- }
- }
- }
- /**
- * A WebGL 2 backend utility module with common helpers.
- *
- * @private
- */
- class WebGLUtils {
- /**
- * Constructs a new utility object.
- *
- * @param {WebGLBackend} backend - The WebGL 2 backend.
- */
- constructor( backend ) {
- /**
- * A reference to the WebGL 2 backend.
- *
- * @type {WebGLBackend}
- */
- this.backend = backend;
- /**
- * A reference to the rendering context.
- *
- * @type {WebGL2RenderingContext}
- */
- this.gl = this.backend.gl;
- /**
- * A reference to a backend module holding extension-related
- * utility functions.
- *
- * @type {WebGLExtensions}
- */
- this.extensions = backend.extensions;
- }
- /**
- * Converts the given three.js constant into a WebGL constant.
- * The method currently supports the conversion of texture formats
- * and types.
- *
- * @param {number} p - The three.js constant.
- * @param {string} [colorSpace=NoColorSpace] - The color space.
- * @return {?number} The corresponding WebGL constant.
- */
- convert( p, colorSpace = NoColorSpace ) {
- const { gl, extensions } = this;
- let extension;
- const transfer = ColorManagement.getTransfer( colorSpace );
- if ( p === UnsignedByteType ) return gl.UNSIGNED_BYTE;
- if ( p === UnsignedShort4444Type ) return gl.UNSIGNED_SHORT_4_4_4_4;
- if ( p === UnsignedShort5551Type ) return gl.UNSIGNED_SHORT_5_5_5_1;
- if ( p === UnsignedInt5999Type ) return gl.UNSIGNED_INT_5_9_9_9_REV;
- if ( p === UnsignedInt101111Type ) return gl.UNSIGNED_INT_10F_11F_11F_REV;
- if ( p === ByteType ) return gl.BYTE;
- if ( p === ShortType ) return gl.SHORT;
- if ( p === UnsignedShortType ) return gl.UNSIGNED_SHORT;
- if ( p === IntType ) return gl.INT;
- if ( p === UnsignedIntType ) return gl.UNSIGNED_INT;
- if ( p === FloatType ) return gl.FLOAT;
- if ( p === HalfFloatType ) {
- return gl.HALF_FLOAT;
- }
- if ( p === AlphaFormat ) return gl.ALPHA;
- if ( p === RGBFormat ) return gl.RGB;
- if ( p === RGBAFormat ) return gl.RGBA;
- if ( p === DepthFormat ) return gl.DEPTH_COMPONENT;
- if ( p === DepthStencilFormat ) return gl.DEPTH_STENCIL;
- // WebGL2 formats.
- if ( p === RedFormat ) return gl.RED;
- if ( p === RedIntegerFormat ) return gl.RED_INTEGER;
- if ( p === RGFormat ) return gl.RG;
- if ( p === RGIntegerFormat ) return gl.RG_INTEGER;
- if ( p === RGBAIntegerFormat ) return gl.RGBA_INTEGER;
- // S3TC
- if ( p === RGB_S3TC_DXT1_Format || p === RGBA_S3TC_DXT1_Format || p === RGBA_S3TC_DXT3_Format || p === RGBA_S3TC_DXT5_Format ) {
- if ( transfer === SRGBTransfer ) {
- extension = extensions.get( 'WEBGL_compressed_texture_s3tc_srgb' );
- if ( extension !== null ) {
- if ( p === RGB_S3TC_DXT1_Format ) return extension.COMPRESSED_SRGB_S3TC_DXT1_EXT;
- if ( p === RGBA_S3TC_DXT1_Format ) return extension.COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT;
- if ( p === RGBA_S3TC_DXT3_Format ) return extension.COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT;
- if ( p === RGBA_S3TC_DXT5_Format ) return extension.COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT;
- } else {
- return null;
- }
- } else {
- extension = extensions.get( 'WEBGL_compressed_texture_s3tc' );
- if ( extension !== null ) {
- if ( p === RGB_S3TC_DXT1_Format ) return extension.COMPRESSED_RGB_S3TC_DXT1_EXT;
- if ( p === RGBA_S3TC_DXT1_Format ) return extension.COMPRESSED_RGBA_S3TC_DXT1_EXT;
- if ( p === RGBA_S3TC_DXT3_Format ) return extension.COMPRESSED_RGBA_S3TC_DXT3_EXT;
- if ( p === RGBA_S3TC_DXT5_Format ) return extension.COMPRESSED_RGBA_S3TC_DXT5_EXT;
- } else {
- return null;
- }
- }
- }
- // PVRTC
- if ( p === RGB_PVRTC_4BPPV1_Format || p === RGB_PVRTC_2BPPV1_Format || p === RGBA_PVRTC_4BPPV1_Format || p === RGBA_PVRTC_2BPPV1_Format ) {
- extension = extensions.get( 'WEBGL_compressed_texture_pvrtc' );
- if ( extension !== null ) {
- if ( p === RGB_PVRTC_4BPPV1_Format ) return extension.COMPRESSED_RGB_PVRTC_4BPPV1_IMG;
- if ( p === RGB_PVRTC_2BPPV1_Format ) return extension.COMPRESSED_RGB_PVRTC_2BPPV1_IMG;
- if ( p === RGBA_PVRTC_4BPPV1_Format ) return extension.COMPRESSED_RGBA_PVRTC_4BPPV1_IMG;
- if ( p === RGBA_PVRTC_2BPPV1_Format ) return extension.COMPRESSED_RGBA_PVRTC_2BPPV1_IMG;
- } else {
- return null;
- }
- }
- // ETC
- if ( p === RGB_ETC1_Format || p === RGB_ETC2_Format || p === RGBA_ETC2_EAC_Format || p === R11_EAC_Format || p === SIGNED_R11_EAC_Format || p === RG11_EAC_Format || p === SIGNED_RG11_EAC_Format ) {
- extension = extensions.get( 'WEBGL_compressed_texture_etc' );
- if ( extension !== null ) {
- if ( p === RGB_ETC1_Format || p === RGB_ETC2_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ETC2 : extension.COMPRESSED_RGB8_ETC2;
- if ( p === RGBA_ETC2_EAC_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ETC2_EAC : extension.COMPRESSED_RGBA8_ETC2_EAC;
- if ( p === R11_EAC_Format ) return extension.COMPRESSED_R11_EAC;
- if ( p === SIGNED_R11_EAC_Format ) return extension.COMPRESSED_SIGNED_R11_EAC;
- if ( p === RG11_EAC_Format ) return extension.COMPRESSED_RG11_EAC;
- if ( p === SIGNED_RG11_EAC_Format ) return extension.COMPRESSED_SIGNED_RG11_EAC;
- } else {
- return null;
- }
- }
- // ASTC
- if ( p === RGBA_ASTC_4x4_Format || p === RGBA_ASTC_5x4_Format || p === RGBA_ASTC_5x5_Format ||
- p === RGBA_ASTC_6x5_Format || p === RGBA_ASTC_6x6_Format || p === RGBA_ASTC_8x5_Format ||
- p === RGBA_ASTC_8x6_Format || p === RGBA_ASTC_8x8_Format || p === RGBA_ASTC_10x5_Format ||
- p === RGBA_ASTC_10x6_Format || p === RGBA_ASTC_10x8_Format || p === RGBA_ASTC_10x10_Format ||
- p === RGBA_ASTC_12x10_Format || p === RGBA_ASTC_12x12_Format ) {
- extension = extensions.get( 'WEBGL_compressed_texture_astc' );
- if ( extension !== null ) {
- if ( p === RGBA_ASTC_4x4_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR : extension.COMPRESSED_RGBA_ASTC_4x4_KHR;
- if ( p === RGBA_ASTC_5x4_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR : extension.COMPRESSED_RGBA_ASTC_5x4_KHR;
- if ( p === RGBA_ASTC_5x5_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR : extension.COMPRESSED_RGBA_ASTC_5x5_KHR;
- if ( p === RGBA_ASTC_6x5_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR : extension.COMPRESSED_RGBA_ASTC_6x5_KHR;
- if ( p === RGBA_ASTC_6x6_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR : extension.COMPRESSED_RGBA_ASTC_6x6_KHR;
- if ( p === RGBA_ASTC_8x5_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR : extension.COMPRESSED_RGBA_ASTC_8x5_KHR;
- if ( p === RGBA_ASTC_8x6_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR : extension.COMPRESSED_RGBA_ASTC_8x6_KHR;
- if ( p === RGBA_ASTC_8x8_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR : extension.COMPRESSED_RGBA_ASTC_8x8_KHR;
- if ( p === RGBA_ASTC_10x5_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR : extension.COMPRESSED_RGBA_ASTC_10x5_KHR;
- if ( p === RGBA_ASTC_10x6_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_10x6_KHR : extension.COMPRESSED_RGBA_ASTC_10x6_KHR;
- if ( p === RGBA_ASTC_10x8_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR : extension.COMPRESSED_RGBA_ASTC_10x8_KHR;
- if ( p === RGBA_ASTC_10x10_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR : extension.COMPRESSED_RGBA_ASTC_10x10_KHR;
- if ( p === RGBA_ASTC_12x10_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR : extension.COMPRESSED_RGBA_ASTC_12x10_KHR;
- if ( p === RGBA_ASTC_12x12_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR : extension.COMPRESSED_RGBA_ASTC_12x12_KHR;
- } else {
- return null;
- }
- }
- // BPTC
- if ( p === RGBA_BPTC_Format ) {
- extension = extensions.get( 'EXT_texture_compression_bptc' );
- if ( extension !== null ) {
- if ( p === RGBA_BPTC_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB_ALPHA_BPTC_UNORM_EXT : extension.COMPRESSED_RGBA_BPTC_UNORM_EXT;
- } else {
- return null;
- }
- }
- // RGTC
- if ( p === RED_RGTC1_Format || p === SIGNED_RED_RGTC1_Format || p === RED_GREEN_RGTC2_Format || p === SIGNED_RED_GREEN_RGTC2_Format ) {
- extension = extensions.get( 'EXT_texture_compression_rgtc' );
- if ( extension !== null ) {
- if ( p === RED_RGTC1_Format ) return extension.COMPRESSED_RED_RGTC1_EXT;
- if ( p === SIGNED_RED_RGTC1_Format ) return extension.COMPRESSED_SIGNED_RED_RGTC1_EXT;
- if ( p === RED_GREEN_RGTC2_Format ) return extension.COMPRESSED_RED_GREEN_RGTC2_EXT;
- if ( p === SIGNED_RED_GREEN_RGTC2_Format ) return extension.COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT;
- } else {
- return null;
- }
- }
- //
- if ( p === UnsignedInt248Type ) {
- return gl.UNSIGNED_INT_24_8;
- }
- // if "p" can't be resolved, assume the user defines a WebGL constant as a string (fallback/workaround for packed RGB formats)
- return ( gl[ p ] !== undefined ) ? gl[ p ] : null;
- }
- /**
- * This method can be used to synchronize the CPU with the GPU by waiting until
- * ongoing GPU commands have been completed.
- *
- * @private
- * @return {Promise} A promise that resolves when all ongoing GPU commands have been completed.
- */
- _clientWaitAsync() {
- const { gl } = this;
- const sync = gl.fenceSync( gl.SYNC_GPU_COMMANDS_COMPLETE, 0 );
- gl.flush();
- return new Promise( ( resolve, reject ) => {
- function test() {
- const res = gl.clientWaitSync( sync, gl.SYNC_FLUSH_COMMANDS_BIT, 0 );
- if ( res === gl.WAIT_FAILED ) {
- gl.deleteSync( sync );
- reject();
- return;
- }
- if ( res === gl.TIMEOUT_EXPIRED ) {
- requestAnimationFrame( test );
- return;
- }
- gl.deleteSync( sync );
- resolve();
- }
- test();
- } );
- }
- }
- let initialized = false, wrappingToGL, filterToGL, compareToGL;
- /**
- * A WebGL 2 backend utility module for managing textures.
- *
- * @private
- */
- class WebGLTextureUtils {
- /**
- * Constructs a new utility object.
- *
- * @param {WebGLBackend} backend - The WebGL 2 backend.
- */
- constructor( backend ) {
- /**
- * A reference to the WebGL 2 backend.
- *
- * @type {WebGLBackend}
- */
- this.backend = backend;
- /**
- * A reference to the rendering context.
- *
- * @type {WebGL2RenderingContext}
- */
- this.gl = backend.gl;
- /**
- * A reference to a backend module holding extension-related
- * utility functions.
- *
- * @type {WebGLExtensions}
- */
- this.extensions = backend.extensions;
- /**
- * A dictionary for managing default textures. The key
- * is the binding point (target), the value the WEbGL texture object.
- *
- * @type {Object<GLenum,WebGLTexture>}
- */
- this.defaultTextures = {};
- /**
- * A scratch framebuffer used for attaching the source texture in
- * {@link copyTextureToTexture}.
- *
- * @private
- * @type {?WebGLFramebuffer}
- */
- this._srcFramebuffer = null;
- /**
- * A scratch framebuffer used for attaching the destination texture in
- * {@link copyTextureToTexture}.
- *
- * @private
- * @type {?WebGLFramebuffer}
- */
- this._dstFramebuffer = null;
- if ( initialized === false ) {
- this._init();
- initialized = true;
- }
- }
- /**
- * Inits the state of the utility.
- *
- * @private
- */
- _init() {
- const gl = this.gl;
- // Store only WebGL constants here.
- wrappingToGL = {
- [ RepeatWrapping ]: gl.REPEAT,
- [ ClampToEdgeWrapping ]: gl.CLAMP_TO_EDGE,
- [ MirroredRepeatWrapping ]: gl.MIRRORED_REPEAT
- };
- filterToGL = {
- [ NearestFilter ]: gl.NEAREST,
- [ NearestMipmapNearestFilter ]: gl.NEAREST_MIPMAP_NEAREST,
- [ NearestMipmapLinearFilter ]: gl.NEAREST_MIPMAP_LINEAR,
- [ LinearFilter ]: gl.LINEAR,
- [ LinearMipmapNearestFilter ]: gl.LINEAR_MIPMAP_NEAREST,
- [ LinearMipmapLinearFilter ]: gl.LINEAR_MIPMAP_LINEAR
- };
- compareToGL = {
- [ NeverCompare ]: gl.NEVER,
- [ AlwaysCompare ]: gl.ALWAYS,
- [ LessCompare ]: gl.LESS,
- [ LessEqualCompare ]: gl.LEQUAL,
- [ EqualCompare ]: gl.EQUAL,
- [ GreaterEqualCompare ]: gl.GEQUAL,
- [ GreaterCompare ]: gl.GREATER,
- [ NotEqualCompare ]: gl.NOTEQUAL
- };
- }
- /**
- * Returns the native texture type for the given texture.
- *
- * @param {Texture} texture - The texture.
- * @return {GLenum} The native texture type.
- */
- getGLTextureType( texture ) {
- const { gl } = this;
- let glTextureType;
- if ( texture.isCubeTexture === true ) {
- glTextureType = gl.TEXTURE_CUBE_MAP;
- } else if ( texture.isArrayTexture === true || texture.isDataArrayTexture === true || texture.isCompressedArrayTexture === true ) {
- glTextureType = gl.TEXTURE_2D_ARRAY;
- } else if ( texture.isData3DTexture === true ) { // TODO: isCompressed3DTexture, wait for #26642
- glTextureType = gl.TEXTURE_3D;
- } else {
- glTextureType = gl.TEXTURE_2D;
- }
- return glTextureType;
- }
- /**
- * Returns the native texture type for the given texture.
- *
- * @param {?string} internalFormatName - The internal format name. When `null`, the internal format is derived from the subsequent parameters.
- * @param {GLenum} glFormat - The WebGL format.
- * @param {GLenum} glType - The WebGL type.
- * @param {string} colorSpace - The texture's color space.
- * @param {boolean} [forceLinearTransfer=false] - Whether to force a linear transfer or not.
- * @return {GLenum} The internal format.
- */
- getInternalFormat( internalFormatName, glFormat, glType, colorSpace, forceLinearTransfer = false ) {
- const { gl, extensions } = this;
- if ( internalFormatName !== null ) {
- if ( gl[ internalFormatName ] !== undefined ) return gl[ internalFormatName ];
- warn( 'WebGLBackend: Attempt to use non-existing WebGL internal format \'' + internalFormatName + '\'' );
- }
- let internalFormat = glFormat;
- if ( glFormat === gl.RED ) {
- if ( glType === gl.FLOAT ) internalFormat = gl.R32F;
- if ( glType === gl.HALF_FLOAT ) internalFormat = gl.R16F;
- if ( glType === gl.UNSIGNED_BYTE ) internalFormat = gl.R8;
- if ( glType === gl.UNSIGNED_SHORT ) internalFormat = gl.R16;
- if ( glType === gl.UNSIGNED_INT ) internalFormat = gl.R32UI;
- if ( glType === gl.BYTE ) internalFormat = gl.R8I;
- if ( glType === gl.SHORT ) internalFormat = gl.R16I;
- if ( glType === gl.INT ) internalFormat = gl.R32I;
- }
- if ( glFormat === gl.RED_INTEGER ) {
- if ( glType === gl.UNSIGNED_BYTE ) internalFormat = gl.R8UI;
- if ( glType === gl.UNSIGNED_SHORT ) internalFormat = gl.R16UI;
- if ( glType === gl.UNSIGNED_INT ) internalFormat = gl.R32UI;
- if ( glType === gl.BYTE ) internalFormat = gl.R8I;
- if ( glType === gl.SHORT ) internalFormat = gl.R16I;
- if ( glType === gl.INT ) internalFormat = gl.R32I;
- }
- if ( glFormat === gl.RG ) {
- if ( glType === gl.FLOAT ) internalFormat = gl.RG32F;
- if ( glType === gl.HALF_FLOAT ) internalFormat = gl.RG16F;
- if ( glType === gl.UNSIGNED_BYTE ) internalFormat = gl.RG8;
- if ( glType === gl.UNSIGNED_SHORT ) internalFormat = gl.RG16;
- if ( glType === gl.UNSIGNED_INT ) internalFormat = gl.RG32UI;
- if ( glType === gl.BYTE ) internalFormat = gl.RG8I;
- if ( glType === gl.SHORT ) internalFormat = gl.RG16I;
- if ( glType === gl.INT ) internalFormat = gl.RG32I;
- }
- if ( glFormat === gl.RG_INTEGER ) {
- if ( glType === gl.UNSIGNED_BYTE ) internalFormat = gl.RG8UI;
- if ( glType === gl.UNSIGNED_SHORT ) internalFormat = gl.RG16UI;
- if ( glType === gl.UNSIGNED_INT ) internalFormat = gl.RG32UI;
- if ( glType === gl.BYTE ) internalFormat = gl.RG8I;
- if ( glType === gl.SHORT ) internalFormat = gl.RG16I;
- if ( glType === gl.INT ) internalFormat = gl.RG32I;
- }
- if ( glFormat === gl.RGB ) {
- const transfer = forceLinearTransfer ? LinearTransfer : ColorManagement.getTransfer( colorSpace );
- if ( glType === gl.FLOAT ) internalFormat = gl.RGB32F;
- if ( glType === gl.HALF_FLOAT ) internalFormat = gl.RGB16F;
- if ( glType === gl.UNSIGNED_BYTE ) internalFormat = gl.RGB8;
- if ( glType === gl.UNSIGNED_SHORT ) internalFormat = gl.RGB16;
- if ( glType === gl.UNSIGNED_INT ) internalFormat = gl.RGB32UI;
- if ( glType === gl.BYTE ) internalFormat = gl.RGB8I;
- if ( glType === gl.SHORT ) internalFormat = gl.RGB16I;
- if ( glType === gl.INT ) internalFormat = gl.RGB32I;
- if ( glType === gl.UNSIGNED_BYTE ) internalFormat = ( transfer === SRGBTransfer ) ? gl.SRGB8 : gl.RGB8;
- if ( glType === gl.UNSIGNED_SHORT_5_6_5 ) internalFormat = gl.RGB565;
- if ( glType === gl.UNSIGNED_SHORT_5_5_5_1 ) internalFormat = gl.RGB5_A1;
- if ( glType === gl.UNSIGNED_SHORT_4_4_4_4 ) internalFormat = gl.RGB4;
- if ( glType === gl.UNSIGNED_INT_5_9_9_9_REV ) internalFormat = gl.RGB9_E5;
- if ( glType === gl.UNSIGNED_INT_10F_11F_11F_REV ) internalFormat = gl.R11F_G11F_B10F;
- }
- if ( glFormat === gl.RGB_INTEGER ) {
- if ( glType === gl.UNSIGNED_BYTE ) internalFormat = gl.RGB8UI;
- if ( glType === gl.UNSIGNED_SHORT ) internalFormat = gl.RGB16UI;
- if ( glType === gl.UNSIGNED_INT ) internalFormat = gl.RGB32UI;
- if ( glType === gl.BYTE ) internalFormat = gl.RGB8I;
- if ( glType === gl.SHORT ) internalFormat = gl.RGB16I;
- if ( glType === gl.INT ) internalFormat = gl.RGB32I;
- }
- if ( glFormat === gl.RGBA ) {
- const transfer = forceLinearTransfer ? LinearTransfer : ColorManagement.getTransfer( colorSpace );
- if ( glType === gl.FLOAT ) internalFormat = gl.RGBA32F;
- if ( glType === gl.HALF_FLOAT ) internalFormat = gl.RGBA16F;
- if ( glType === gl.UNSIGNED_BYTE ) internalFormat = gl.RGBA8;
- if ( glType === gl.UNSIGNED_SHORT ) internalFormat = gl.RGBA16;
- if ( glType === gl.UNSIGNED_INT ) internalFormat = gl.RGBA32UI;
- if ( glType === gl.BYTE ) internalFormat = gl.RGBA8I;
- if ( glType === gl.SHORT ) internalFormat = gl.RGBA16I;
- if ( glType === gl.INT ) internalFormat = gl.RGBA32I;
- if ( glType === gl.UNSIGNED_BYTE ) internalFormat = ( transfer === SRGBTransfer ) ? gl.SRGB8_ALPHA8 : gl.RGBA8;
- if ( glType === gl.UNSIGNED_SHORT_4_4_4_4 ) internalFormat = gl.RGBA4;
- if ( glType === gl.UNSIGNED_SHORT_5_5_5_1 ) internalFormat = gl.RGB5_A1;
- }
- if ( glFormat === gl.RGBA_INTEGER ) {
- if ( glType === gl.UNSIGNED_BYTE ) internalFormat = gl.RGBA8UI;
- if ( glType === gl.UNSIGNED_SHORT ) internalFormat = gl.RGBA16UI;
- if ( glType === gl.UNSIGNED_INT ) internalFormat = gl.RGBA32UI;
- if ( glType === gl.BYTE ) internalFormat = gl.RGBA8I;
- if ( glType === gl.SHORT ) internalFormat = gl.RGBA16I;
- if ( glType === gl.INT ) internalFormat = gl.RGBA32I;
- }
- if ( glFormat === gl.DEPTH_COMPONENT ) {
- if ( glType === gl.UNSIGNED_SHORT ) internalFormat = gl.DEPTH_COMPONENT16;
- if ( glType === gl.UNSIGNED_INT ) internalFormat = gl.DEPTH_COMPONENT24;
- if ( glType === gl.FLOAT ) internalFormat = gl.DEPTH_COMPONENT32F;
- }
- if ( glFormat === gl.DEPTH_STENCIL ) {
- if ( glType === gl.UNSIGNED_INT_24_8 ) internalFormat = gl.DEPTH24_STENCIL8;
- }
- if ( internalFormat === gl.R16F || internalFormat === gl.R32F ||
- internalFormat === gl.RG16F || internalFormat === gl.RG32F ||
- internalFormat === gl.RGBA16F || internalFormat === gl.RGBA32F ) {
- extensions.get( 'EXT_color_buffer_float' );
- }
- return internalFormat;
- }
- /**
- * Sets the texture parameters for the given texture.
- *
- * @param {GLenum} textureType - The texture type.
- * @param {Texture} texture - The texture.
- */
- setTextureParameters( textureType, texture ) {
- const { gl, extensions, backend } = this;
- const workingPrimaries = ColorManagement.getPrimaries( ColorManagement.workingColorSpace );
- const texturePrimaries = texture.colorSpace === NoColorSpace ? null : ColorManagement.getPrimaries( texture.colorSpace );
- const unpackConversion = texture.colorSpace === NoColorSpace || workingPrimaries === texturePrimaries ? gl.NONE : gl.BROWSER_DEFAULT_WEBGL;
- gl.pixelStorei( gl.UNPACK_FLIP_Y_WEBGL, texture.flipY );
- gl.pixelStorei( gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, texture.premultiplyAlpha );
- gl.pixelStorei( gl.UNPACK_ALIGNMENT, texture.unpackAlignment );
- gl.pixelStorei( gl.UNPACK_COLORSPACE_CONVERSION_WEBGL, unpackConversion );
- gl.texParameteri( textureType, gl.TEXTURE_WRAP_S, wrappingToGL[ texture.wrapS ] );
- gl.texParameteri( textureType, gl.TEXTURE_WRAP_T, wrappingToGL[ texture.wrapT ] );
- if ( textureType === gl.TEXTURE_3D || textureType === gl.TEXTURE_2D_ARRAY ) {
- // WebGL 2 does not support wrapping for depth 2D array textures
- if ( ! texture.isArrayTexture ) {
- gl.texParameteri( textureType, gl.TEXTURE_WRAP_R, wrappingToGL[ texture.wrapR ] );
- }
- }
- gl.texParameteri( textureType, gl.TEXTURE_MAG_FILTER, filterToGL[ texture.magFilter ] );
- const hasMipmaps = texture.mipmaps !== undefined && texture.mipmaps.length > 0;
- // follow WebGPU backend mapping for texture filtering
- const minFilter = texture.minFilter === LinearFilter && hasMipmaps ? LinearMipmapLinearFilter : texture.minFilter;
- gl.texParameteri( textureType, gl.TEXTURE_MIN_FILTER, filterToGL[ minFilter ] );
- if ( texture.compareFunction ) {
- gl.texParameteri( textureType, gl.TEXTURE_COMPARE_MODE, gl.COMPARE_REF_TO_TEXTURE );
- gl.texParameteri( textureType, gl.TEXTURE_COMPARE_FUNC, compareToGL[ texture.compareFunction ] );
- }
- if ( extensions.has( 'EXT_texture_filter_anisotropic' ) === true ) {
- if ( texture.magFilter === NearestFilter ) return;
- if ( texture.minFilter !== NearestMipmapLinearFilter && texture.minFilter !== LinearMipmapLinearFilter ) return;
- if ( texture.type === FloatType && extensions.has( 'OES_texture_float_linear' ) === false ) return; // verify extension for WebGL 1 and WebGL 2
- if ( texture.anisotropy > 1 ) {
- const extension = extensions.get( 'EXT_texture_filter_anisotropic' );
- gl.texParameterf( textureType, extension.TEXTURE_MAX_ANISOTROPY_EXT, Math.min( texture.anisotropy, backend.getMaxAnisotropy() ) );
- }
- }
- }
- /**
- * Creates a default texture for the given texture that can be used
- * as a placeholder until the actual texture is ready for usage.
- *
- * @param {Texture} texture - The texture to create a default texture for.
- */
- createDefaultTexture( texture ) {
- const { gl, backend, defaultTextures } = this;
- const glTextureType = this.getGLTextureType( texture );
- let textureGPU = defaultTextures[ glTextureType ];
- if ( textureGPU === undefined ) {
- textureGPU = gl.createTexture();
- backend.state.bindTexture( glTextureType, textureGPU );
- gl.texParameteri( glTextureType, gl.TEXTURE_MIN_FILTER, gl.NEAREST );
- gl.texParameteri( glTextureType, gl.TEXTURE_MAG_FILTER, gl.NEAREST );
- // gl.texImage2D( glTextureType, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, data );
- defaultTextures[ glTextureType ] = textureGPU;
- }
- backend.set( texture, {
- textureGPU,
- glTextureType
- } );
- }
- /**
- * Defines a texture on the GPU for the given texture object.
- *
- * @param {Texture} texture - The texture.
- * @param {Object} [options={}] - Optional configuration parameter.
- * @return {undefined}
- */
- createTexture( texture, options ) {
- const { gl, backend } = this;
- const { levels, width, height, depth } = options;
- const glFormat = backend.utils.convert( texture.format, texture.colorSpace );
- const glType = backend.utils.convert( texture.type );
- const glInternalFormat = this.getInternalFormat( texture.internalFormat, glFormat, glType, texture.colorSpace, texture.isVideoTexture );
- const textureGPU = gl.createTexture();
- const glTextureType = this.getGLTextureType( texture );
- backend.state.bindTexture( glTextureType, textureGPU );
- this.setTextureParameters( glTextureType, texture );
- if ( texture.isArrayTexture || texture.isDataArrayTexture || texture.isCompressedArrayTexture ) {
- gl.texStorage3D( gl.TEXTURE_2D_ARRAY, levels, glInternalFormat, width, height, depth );
- } else if ( texture.isData3DTexture ) {
- gl.texStorage3D( gl.TEXTURE_3D, levels, glInternalFormat, width, height, depth );
- } else if ( ! texture.isVideoTexture ) {
- gl.texStorage2D( glTextureType, levels, glInternalFormat, width, height );
- }
- backend.set( texture, {
- textureGPU,
- glTextureType,
- glFormat,
- glType,
- glInternalFormat
- } );
- }
- /**
- * Uploads texture buffer data to the GPU memory.
- *
- * @param {WebGLBuffer} buffer - The buffer data.
- * @param {Texture} texture - The texture,
- */
- copyBufferToTexture( buffer, texture ) {
- const { gl, backend } = this;
- const { textureGPU, glTextureType, glFormat, glType } = backend.get( texture );
- const { width, height } = texture.source.data;
- gl.bindBuffer( gl.PIXEL_UNPACK_BUFFER, buffer );
- backend.state.bindTexture( glTextureType, textureGPU );
- gl.pixelStorei( gl.UNPACK_FLIP_Y_WEBGL, false );
- gl.pixelStorei( gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false );
- gl.texSubImage2D( glTextureType, 0, 0, 0, width, height, glFormat, glType, 0 );
- gl.bindBuffer( gl.PIXEL_UNPACK_BUFFER, null );
- backend.state.unbindTexture();
- // debug
- // const framebuffer = gl.createFramebuffer();
- // backend.state.bindFramebuffer( gl.FRAMEBUFFER, framebuffer );
- // gl.framebufferTexture2D( gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, glTextureType, textureGPU, 0 );
- // const readout = new Float32Array( width * height * 4 );
- // const altFormat = gl.getParameter( gl.IMPLEMENTATION_COLOR_READ_FORMAT );
- // const altType = gl.getParameter( gl.IMPLEMENTATION_COLOR_READ_TYPE );
- // gl.readPixels( 0, 0, width, height, altFormat, altType, readout );
- // backend.state.bindFramebuffer( gl.FRAMEBUFFER, null );
- // log( readout );
- }
- /**
- * Uploads the updated texture data to the GPU.
- *
- * @param {Texture} texture - The texture.
- * @param {Object} [options={}] - Optional configuration parameter.
- */
- updateTexture( texture, options ) {
- const { gl } = this;
- const { width, height } = options;
- const { textureGPU, glTextureType, glFormat, glType, glInternalFormat } = this.backend.get( texture );
- if ( texture.isRenderTargetTexture || ( textureGPU === undefined /* unsupported texture format */ ) )
- return;
- this.backend.state.bindTexture( glTextureType, textureGPU );
- this.setTextureParameters( glTextureType, texture );
- if ( texture.isCompressedTexture ) {
- const mipmaps = texture.mipmaps;
- const image = options.image;
- for ( let i = 0; i < mipmaps.length; i ++ ) {
- const mipmap = mipmaps[ i ];
- if ( texture.isCompressedArrayTexture ) {
- if ( texture.format !== gl.RGBA ) {
- if ( glFormat !== null ) {
- gl.compressedTexSubImage3D( gl.TEXTURE_2D_ARRAY, i, 0, 0, 0, mipmap.width, mipmap.height, image.depth, glFormat, mipmap.data );
- } else {
- warn( 'WebGLBackend: Attempt to load unsupported compressed texture format in .uploadTexture()' );
- }
- } else {
- gl.texSubImage3D( gl.TEXTURE_2D_ARRAY, i, 0, 0, 0, mipmap.width, mipmap.height, image.depth, glFormat, glType, mipmap.data );
- }
- } else {
- if ( glFormat !== null ) {
- gl.compressedTexSubImage2D( gl.TEXTURE_2D, i, 0, 0, mipmap.width, mipmap.height, glFormat, mipmap.data );
- } else {
- warn( 'WebGLBackend: Unsupported compressed texture format' );
- }
- }
- }
- } else if ( texture.isCubeTexture ) {
- const images = options.images;
- const mipmaps = texture.mipmaps;
- for ( let i = 0; i < 6; i ++ ) {
- const image = getImage( images[ i ] );
- gl.texSubImage2D( gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, 0, 0, width, height, glFormat, glType, image );
- for ( let j = 0; j < mipmaps.length; j ++ ) {
- const mipmap = mipmaps[ j ];
- const image = getImage( mipmap.images[ i ] );
- gl.texSubImage2D( gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j + 1, 0, 0, image.width, image.height, glFormat, glType, image );
- }
- }
- } else if ( texture.isDataArrayTexture || texture.isArrayTexture ) {
- const image = options.image;
- if ( texture.layerUpdates.size > 0 ) {
- const layerByteLength = getByteLength( image.width, image.height, texture.format, texture.type );
- for ( const layerIndex of texture.layerUpdates ) {
- const layerData = image.data.subarray(
- layerIndex * layerByteLength / image.data.BYTES_PER_ELEMENT,
- ( layerIndex + 1 ) * layerByteLength / image.data.BYTES_PER_ELEMENT
- );
- gl.texSubImage3D( gl.TEXTURE_2D_ARRAY, 0, 0, 0, layerIndex, image.width, image.height, 1, glFormat, glType, layerData );
- }
- texture.clearLayerUpdates();
- } else {
- gl.texSubImage3D( gl.TEXTURE_2D_ARRAY, 0, 0, 0, 0, image.width, image.height, image.depth, glFormat, glType, image.data );
- }
- } else if ( texture.isData3DTexture ) {
- const image = options.image;
- gl.texSubImage3D( gl.TEXTURE_3D, 0, 0, 0, 0, image.width, image.height, image.depth, glFormat, glType, image.data );
- } else if ( texture.isVideoTexture ) {
- texture.update();
- gl.texImage2D( glTextureType, 0, glInternalFormat, glFormat, glType, options.image );
- } else {
- const mipmaps = texture.mipmaps;
- if ( mipmaps.length > 0 ) {
- for ( let i = 0, il = mipmaps.length; i < il; i ++ ) {
- const mipmap = mipmaps[ i ];
- const image = getImage( mipmap );
- gl.texSubImage2D( glTextureType, i, 0, 0, mipmap.width, mipmap.height, glFormat, glType, image );
- }
- } else {
- const image = getImage( options.image );
- gl.texSubImage2D( glTextureType, 0, 0, 0, width, height, glFormat, glType, image );
- }
- }
- }
- /**
- * Generates mipmaps for the given texture.
- *
- * @param {Texture} texture - The texture.
- */
- generateMipmaps( texture ) {
- const { gl, backend } = this;
- const { textureGPU, glTextureType } = backend.get( texture );
- backend.state.bindTexture( glTextureType, textureGPU );
- gl.generateMipmap( glTextureType );
- }
- /**
- * Deallocates the render buffers of the given render target.
- *
- * @param {RenderTarget} renderTarget - The render target.
- */
- deallocateRenderBuffers( renderTarget ) {
- const { gl, backend } = this;
- // remove framebuffer reference
- if ( renderTarget ) {
- const renderContextData = backend.get( renderTarget );
- renderContextData.renderBufferStorageSetup = undefined;
- if ( renderContextData.framebuffers ) {
- for ( const cacheKey in renderContextData.framebuffers ) {
- gl.deleteFramebuffer( renderContextData.framebuffers[ cacheKey ] );
- }
- delete renderContextData.framebuffers;
- }
- if ( renderContextData.depthRenderbuffer ) {
- gl.deleteRenderbuffer( renderContextData.depthRenderbuffer );
- delete renderContextData.depthRenderbuffer;
- }
- if ( renderContextData.stencilRenderbuffer ) {
- gl.deleteRenderbuffer( renderContextData.stencilRenderbuffer );
- delete renderContextData.stencilRenderbuffer;
- }
- if ( renderContextData.msaaFrameBuffer ) {
- gl.deleteFramebuffer( renderContextData.msaaFrameBuffer );
- delete renderContextData.msaaFrameBuffer;
- }
- if ( renderContextData.msaaRenderbuffers ) {
- for ( let i = 0; i < renderContextData.msaaRenderbuffers.length; i ++ ) {
- gl.deleteRenderbuffer( renderContextData.msaaRenderbuffers[ i ] );
- }
- delete renderContextData.msaaRenderbuffers;
- }
- }
- }
- /**
- * Destroys the GPU data for the given texture object.
- *
- * @param {Texture} texture - The texture.
- * @param {boolean} [isDefaultTexture=false] - Whether the texture uses a default GPU texture or not.
- */
- destroyTexture( texture, isDefaultTexture = false ) {
- const { gl, backend } = this;
- const { textureGPU, renderTarget } = backend.get( texture );
- this.deallocateRenderBuffers( renderTarget );
- if ( isDefaultTexture === false ) {
- gl.deleteTexture( textureGPU );
- }
- backend.delete( texture );
- }
- /**
- * Copies data of the given source texture to the given destination texture.
- *
- * @param {Texture} srcTexture - The source texture.
- * @param {Texture} dstTexture - The destination texture.
- * @param {?(Box3|Box2)} [srcRegion=null] - The region of the source texture to copy.
- * @param {?(Vector2|Vector3)} [dstPosition=null] - The destination position of the copy.
- * @param {number} [srcLevel=0] - The source mip level to copy from.
- * @param {number} [dstLevel=0] - The destination mip level to copy to.
- */
- copyTextureToTexture( srcTexture, dstTexture, srcRegion = null, dstPosition = null, srcLevel = 0, dstLevel = 0 ) {
- const { gl, backend } = this;
- const { state } = this.backend;
- const { textureGPU: dstTextureGPU, glTextureType, glType, glFormat } = backend.get( dstTexture );
- state.bindTexture( glTextureType, dstTextureGPU );
- // gather the necessary dimensions to copy
- let width, height, depth, minX, minY, minZ;
- let dstX, dstY, dstZ;
- const image = srcTexture.isCompressedTexture ? srcTexture.mipmaps[ dstLevel ] : srcTexture.image;
- if ( srcRegion !== null ) {
- width = srcRegion.max.x - srcRegion.min.x;
- height = srcRegion.max.y - srcRegion.min.y;
- depth = srcRegion.isBox3 ? srcRegion.max.z - srcRegion.min.z : 1;
- minX = srcRegion.min.x;
- minY = srcRegion.min.y;
- minZ = srcRegion.isBox3 ? srcRegion.min.z : 0;
- } else {
- const levelScale = Math.pow( 2, - srcLevel );
- width = Math.floor( image.width * levelScale );
- height = Math.floor( image.height * levelScale );
- if ( srcTexture.isDataArrayTexture || srcTexture.isArrayTexture ) {
- depth = image.depth;
- } else if ( srcTexture.isData3DTexture ) {
- depth = Math.floor( image.depth * levelScale );
- } else {
- depth = 1;
- }
- minX = 0;
- minY = 0;
- minZ = 0;
- }
- if ( dstPosition !== null ) {
- dstX = dstPosition.x;
- dstY = dstPosition.y;
- dstZ = dstPosition.z;
- } else {
- dstX = 0;
- dstY = 0;
- dstZ = 0;
- }
- gl.pixelStorei( gl.UNPACK_FLIP_Y_WEBGL, dstTexture.flipY );
- gl.pixelStorei( gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, dstTexture.premultiplyAlpha );
- gl.pixelStorei( gl.UNPACK_ALIGNMENT, dstTexture.unpackAlignment );
- // used for copying data from cpu
- const currentUnpackRowLen = gl.getParameter( gl.UNPACK_ROW_LENGTH );
- const currentUnpackImageHeight = gl.getParameter( gl.UNPACK_IMAGE_HEIGHT );
- const currentUnpackSkipPixels = gl.getParameter( gl.UNPACK_SKIP_PIXELS );
- const currentUnpackSkipRows = gl.getParameter( gl.UNPACK_SKIP_ROWS );
- const currentUnpackSkipImages = gl.getParameter( gl.UNPACK_SKIP_IMAGES );
- gl.pixelStorei( gl.UNPACK_ROW_LENGTH, image.width );
- gl.pixelStorei( gl.UNPACK_IMAGE_HEIGHT, image.height );
- gl.pixelStorei( gl.UNPACK_SKIP_PIXELS, minX );
- gl.pixelStorei( gl.UNPACK_SKIP_ROWS, minY );
- gl.pixelStorei( gl.UNPACK_SKIP_IMAGES, minZ );
- // set up the src texture
- const isSrc3D = srcTexture.isDataArrayTexture || srcTexture.isData3DTexture || dstTexture.isArrayTexture;
- const isDst3D = dstTexture.isDataArrayTexture || dstTexture.isData3DTexture || dstTexture.isArrayTexture;
- if ( srcTexture.isDepthTexture ) {
- const srcTextureData = backend.get( srcTexture );
- const dstTextureData = backend.get( dstTexture );
- const srcRenderContextData = backend.get( srcTextureData.renderTarget );
- const dstRenderContextData = backend.get( dstTextureData.renderTarget );
- const srcFramebuffer = srcRenderContextData.framebuffers[ srcTextureData.cacheKey ];
- const dstFramebuffer = dstRenderContextData.framebuffers[ dstTextureData.cacheKey ];
- state.bindFramebuffer( gl.READ_FRAMEBUFFER, srcFramebuffer );
- state.bindFramebuffer( gl.DRAW_FRAMEBUFFER, dstFramebuffer );
- for ( let i = 0; i < depth; i ++ ) {
- // if the source or destination are a 3d target then a layer needs to be bound
- if ( isSrc3D ) {
- gl.framebufferTextureLayer( gl.READ_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, srcTextureData.textureGPU, srcLevel, minZ + i );
- gl.framebufferTextureLayer( gl.DRAW_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, dstTextureGPU, dstLevel, dstZ + i );
- }
- gl.blitFramebuffer( minX, minY, width, height, dstX, dstY, width, height, gl.DEPTH_BUFFER_BIT, gl.NEAREST );
- }
- state.bindFramebuffer( gl.READ_FRAMEBUFFER, null );
- state.bindFramebuffer( gl.DRAW_FRAMEBUFFER, null );
- } else if ( srcLevel !== 0 || srcTexture.isRenderTargetTexture || backend.has( srcTexture ) ) {
- // get the appropriate frame buffers
- const srcTextureData = backend.get( srcTexture );
- if ( this._srcFramebuffer === null ) this._srcFramebuffer = gl.createFramebuffer();
- if ( this._dstFramebuffer === null ) this._dstFramebuffer = gl.createFramebuffer();
- // bind the frame buffer targets
- state.bindFramebuffer( gl.READ_FRAMEBUFFER, this._srcFramebuffer );
- state.bindFramebuffer( gl.DRAW_FRAMEBUFFER, this._dstFramebuffer );
- for ( let i = 0; i < depth; i ++ ) {
- // assign the correct layers and mip maps to the frame buffers
- if ( isSrc3D ) {
- gl.framebufferTextureLayer( gl.READ_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, srcTextureData.textureGPU, srcLevel, minZ + i );
- } else {
- gl.framebufferTexture2D( gl.READ_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, srcTextureData.textureGPU, srcLevel );
- }
- if ( isDst3D ) {
- gl.framebufferTextureLayer( gl.DRAW_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, dstTextureGPU, dstLevel, dstZ + i );
- } else {
- gl.framebufferTexture2D( gl.DRAW_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, dstTextureGPU, dstLevel );
- }
- // copy the data using the fastest function that can achieve the copy
- if ( srcLevel !== 0 ) {
- gl.blitFramebuffer( minX, minY, width, height, dstX, dstY, width, height, gl.COLOR_BUFFER_BIT, gl.NEAREST );
- } else if ( isDst3D ) {
- gl.copyTexSubImage3D( glTextureType, dstLevel, dstX, dstY, dstZ + i, minX, minY, width, height );
- } else {
- gl.copyTexSubImage2D( glTextureType, dstLevel, dstX, dstY, minX, minY, width, height );
- }
- }
- // unbind read, draw buffers
- state.bindFramebuffer( gl.READ_FRAMEBUFFER, null );
- state.bindFramebuffer( gl.DRAW_FRAMEBUFFER, null );
- } else {
- if ( isDst3D ) {
- // copy data into the 3d texture
- if ( srcTexture.isDataTexture || srcTexture.isData3DTexture ) {
- gl.texSubImage3D( glTextureType, dstLevel, dstX, dstY, dstZ, width, height, depth, glFormat, glType, image.data );
- } else if ( dstTexture.isCompressedArrayTexture ) {
- gl.compressedTexSubImage3D( glTextureType, dstLevel, dstX, dstY, dstZ, width, height, depth, glFormat, image.data );
- } else {
- gl.texSubImage3D( glTextureType, dstLevel, dstX, dstY, dstZ, width, height, depth, glFormat, glType, image );
- }
- } else {
- // copy data into the 2d texture
- if ( srcTexture.isDataTexture ) {
- gl.texSubImage2D( gl.TEXTURE_2D, dstLevel, dstX, dstY, width, height, glFormat, glType, image.data );
- } else if ( srcTexture.isCompressedTexture ) {
- gl.compressedTexSubImage2D( gl.TEXTURE_2D, dstLevel, dstX, dstY, image.width, image.height, glFormat, image.data );
- } else {
- gl.texSubImage2D( gl.TEXTURE_2D, dstLevel, dstX, dstY, width, height, glFormat, glType, image );
- }
- }
- }
- // reset values
- gl.pixelStorei( gl.UNPACK_ROW_LENGTH, currentUnpackRowLen );
- gl.pixelStorei( gl.UNPACK_IMAGE_HEIGHT, currentUnpackImageHeight );
- gl.pixelStorei( gl.UNPACK_SKIP_PIXELS, currentUnpackSkipPixels );
- gl.pixelStorei( gl.UNPACK_SKIP_ROWS, currentUnpackSkipRows );
- gl.pixelStorei( gl.UNPACK_SKIP_IMAGES, currentUnpackSkipImages );
- // Generate mipmaps only when copying level 0
- if ( dstLevel === 0 && dstTexture.generateMipmaps ) {
- gl.generateMipmap( glTextureType );
- }
- state.unbindTexture();
- }
- /**
- * Copies the current bound framebuffer to the given texture.
- *
- * @param {Texture} texture - The destination texture.
- * @param {RenderContext} renderContext - The render context.
- * @param {Vector4} rectangle - A four dimensional vector defining the origin and dimension of the copy.
- */
- copyFramebufferToTexture( texture, renderContext, rectangle ) {
- const { gl } = this;
- const { state } = this.backend;
- const { textureGPU } = this.backend.get( texture );
- const { x, y, z: width, w: height } = rectangle;
- const requireDrawFrameBuffer = texture.isDepthTexture === true || ( renderContext.renderTarget && renderContext.renderTarget.samples > 0 );
- const srcHeight = renderContext.renderTarget ? renderContext.renderTarget.height : this.backend.getDrawingBufferSize().y;
- if ( requireDrawFrameBuffer ) {
- const partial = ( x !== 0 || y !== 0 );
- let mask;
- let attachment;
- if ( texture.isDepthTexture === true ) {
- mask = gl.DEPTH_BUFFER_BIT;
- attachment = gl.DEPTH_ATTACHMENT;
- if ( renderContext.stencil ) {
- mask |= gl.STENCIL_BUFFER_BIT;
- }
- } else {
- mask = gl.COLOR_BUFFER_BIT;
- attachment = gl.COLOR_ATTACHMENT0;
- }
- if ( partial ) {
- const renderTargetContextData = this.backend.get( renderContext.renderTarget );
- const fb = renderTargetContextData.framebuffers[ renderContext.getCacheKey() ];
- const msaaFrameBuffer = renderTargetContextData.msaaFrameBuffer;
- state.bindFramebuffer( gl.DRAW_FRAMEBUFFER, fb );
- state.bindFramebuffer( gl.READ_FRAMEBUFFER, msaaFrameBuffer );
- const flippedY = srcHeight - y - height;
- gl.blitFramebuffer( x, flippedY, x + width, flippedY + height, x, flippedY, x + width, flippedY + height, mask, gl.NEAREST );
- state.bindFramebuffer( gl.READ_FRAMEBUFFER, fb );
- state.bindTexture( gl.TEXTURE_2D, textureGPU );
- gl.copyTexSubImage2D( gl.TEXTURE_2D, 0, 0, 0, x, flippedY, width, height );
- state.unbindTexture();
- } else {
- const fb = gl.createFramebuffer();
- state.bindFramebuffer( gl.DRAW_FRAMEBUFFER, fb );
- gl.framebufferTexture2D( gl.DRAW_FRAMEBUFFER, attachment, gl.TEXTURE_2D, textureGPU, 0 );
- gl.blitFramebuffer( 0, 0, width, height, 0, 0, width, height, mask, gl.NEAREST );
- gl.deleteFramebuffer( fb );
- }
- } else {
- state.bindTexture( gl.TEXTURE_2D, textureGPU );
- gl.copyTexSubImage2D( gl.TEXTURE_2D, 0, 0, 0, x, srcHeight - height - y, width, height );
- state.unbindTexture();
- }
- if ( texture.generateMipmaps ) this.generateMipmaps( texture );
- this.backend._setFramebuffer( renderContext );
- }
- /**
- * SetupS storage for internal depth/stencil buffers and bind to correct framebuffer.
- *
- * @param {WebGLRenderbuffer} renderbuffer - The render buffer.
- * @param {RenderContext} renderContext - The render context.
- * @param {number} samples - The MSAA sample count.
- * @param {boolean} [useMultisampledRTT=false] - Whether to use WEBGL_multisampled_render_to_texture or not.
- */
- setupRenderBufferStorage( renderbuffer, renderContext, samples, useMultisampledRTT = false ) {
- const { gl } = this;
- const renderTarget = renderContext.renderTarget;
- const { depthTexture, depthBuffer, stencilBuffer, width, height } = renderTarget;
- gl.bindRenderbuffer( gl.RENDERBUFFER, renderbuffer );
- if ( depthBuffer && ! stencilBuffer ) {
- let glInternalFormat = gl.DEPTH_COMPONENT24;
- if ( useMultisampledRTT === true ) {
- const multisampledRTTExt = this.extensions.get( 'WEBGL_multisampled_render_to_texture' );
- multisampledRTTExt.renderbufferStorageMultisampleEXT( gl.RENDERBUFFER, renderTarget.samples, glInternalFormat, width, height );
- } else if ( samples > 0 ) {
- if ( depthTexture && depthTexture.isDepthTexture ) {
- if ( depthTexture.type === gl.FLOAT ) {
- glInternalFormat = gl.DEPTH_COMPONENT32F;
- }
- }
- gl.renderbufferStorageMultisample( gl.RENDERBUFFER, samples, glInternalFormat, width, height );
- } else {
- gl.renderbufferStorage( gl.RENDERBUFFER, glInternalFormat, width, height );
- }
- gl.framebufferRenderbuffer( gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, renderbuffer );
- } else if ( depthBuffer && stencilBuffer ) {
- if ( samples > 0 ) {
- gl.renderbufferStorageMultisample( gl.RENDERBUFFER, samples, gl.DEPTH24_STENCIL8, width, height );
- } else {
- gl.renderbufferStorage( gl.RENDERBUFFER, gl.DEPTH_STENCIL, width, height );
- }
- gl.framebufferRenderbuffer( gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, renderbuffer );
- }
- gl.bindRenderbuffer( gl.RENDERBUFFER, null );
- }
- /**
- * Returns texture data as a typed array.
- *
- * @async
- * @param {Texture} texture - The texture to copy.
- * @param {number} x - The x coordinate of the copy origin.
- * @param {number} y - The y coordinate of the copy origin.
- * @param {number} width - The width of the copy.
- * @param {number} height - The height of the copy.
- * @param {number} faceIndex - The face index.
- * @return {Promise<TypedArray>} A Promise that resolves with a typed array when the copy operation has finished.
- */
- async copyTextureToBuffer( texture, x, y, width, height, faceIndex ) {
- const { backend, gl } = this;
- const { textureGPU, glFormat, glType } = this.backend.get( texture );
- const fb = gl.createFramebuffer();
- backend.state.bindFramebuffer( gl.READ_FRAMEBUFFER, fb );
- const target = texture.isCubeTexture ? gl.TEXTURE_CUBE_MAP_POSITIVE_X + faceIndex : gl.TEXTURE_2D;
- gl.framebufferTexture2D( gl.READ_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, target, textureGPU, 0 );
- const typedArrayType = this._getTypedArrayType( glType );
- const bytesPerTexel = this._getBytesPerTexel( glType, glFormat );
- const elementCount = width * height;
- const byteLength = elementCount * bytesPerTexel;
- const buffer = gl.createBuffer();
- gl.bindBuffer( gl.PIXEL_PACK_BUFFER, buffer );
- gl.bufferData( gl.PIXEL_PACK_BUFFER, byteLength, gl.STREAM_READ );
- gl.readPixels( x, y, width, height, glFormat, glType, 0 );
- gl.bindBuffer( gl.PIXEL_PACK_BUFFER, null );
- await backend.utils._clientWaitAsync();
- const dstBuffer = new typedArrayType( byteLength / typedArrayType.BYTES_PER_ELEMENT );
- gl.bindBuffer( gl.PIXEL_PACK_BUFFER, buffer );
- gl.getBufferSubData( gl.PIXEL_PACK_BUFFER, 0, dstBuffer );
- gl.bindBuffer( gl.PIXEL_PACK_BUFFER, null );
- backend.state.bindFramebuffer( gl.READ_FRAMEBUFFER, null );
- gl.deleteFramebuffer( fb );
- return dstBuffer;
- }
- /**
- * Returns the corresponding typed array type for the given WebGL data type.
- *
- * @private
- * @param {GLenum} glType - The WebGL data type.
- * @return {TypedArray.constructor} The typed array type.
- */
- _getTypedArrayType( glType ) {
- const { gl } = this;
- if ( glType === gl.UNSIGNED_BYTE ) return Uint8Array;
- if ( glType === gl.UNSIGNED_SHORT_4_4_4_4 ) return Uint16Array;
- if ( glType === gl.UNSIGNED_SHORT_5_5_5_1 ) return Uint16Array;
- if ( glType === gl.UNSIGNED_SHORT_5_6_5 ) return Uint16Array;
- if ( glType === gl.UNSIGNED_SHORT ) return Uint16Array;
- if ( glType === gl.UNSIGNED_INT ) return Uint32Array;
- if ( glType === gl.HALF_FLOAT ) return Uint16Array;
- if ( glType === gl.FLOAT ) return Float32Array;
- throw new Error( `Unsupported WebGL type: ${glType}` );
- }
- /**
- * Returns the bytes-per-texel value for the given WebGL data type and texture format.
- *
- * @private
- * @param {GLenum} glType - The WebGL data type.
- * @param {GLenum} glFormat - The WebGL texture format.
- * @return {number} The bytes-per-texel.
- */
- _getBytesPerTexel( glType, glFormat ) {
- const { gl } = this;
- let bytesPerComponent = 0;
- if ( glType === gl.UNSIGNED_BYTE ) bytesPerComponent = 1;
- if ( glType === gl.UNSIGNED_SHORT_4_4_4_4 ||
- glType === gl.UNSIGNED_SHORT_5_5_5_1 ||
- glType === gl.UNSIGNED_SHORT_5_6_5 ||
- glType === gl.UNSIGNED_SHORT ||
- glType === gl.HALF_FLOAT ) bytesPerComponent = 2;
- if ( glType === gl.UNSIGNED_INT ||
- glType === gl.FLOAT ) bytesPerComponent = 4;
- if ( glFormat === gl.RGBA ) return bytesPerComponent * 4;
- if ( glFormat === gl.RGB ) return bytesPerComponent * 3;
- if ( glFormat === gl.ALPHA ) return bytesPerComponent;
- }
- /**
- * Frees the internal resources.
- */
- dispose() {
- const { gl } = this;
- if ( this._srcFramebuffer !== null ) gl.deleteFramebuffer( this._srcFramebuffer );
- if ( this._dstFramebuffer !== null ) gl.deleteFramebuffer( this._dstFramebuffer );
- }
- }
- function getImage( source ) {
- if ( source.isDataTexture ) {
- return source.image.data;
- } else if ( ( typeof HTMLImageElement !== 'undefined' && source instanceof HTMLImageElement ) ||
- ( typeof HTMLCanvasElement !== 'undefined' && source instanceof HTMLCanvasElement ) ||
- ( typeof ImageBitmap !== 'undefined' && source instanceof ImageBitmap ) ||
- ( typeof OffscreenCanvas !== 'undefined' && source instanceof OffscreenCanvas ) ) {
- return source;
- }
- return source.data;
- }
- /**
- * A WebGL 2 backend utility module for managing extensions.
- *
- * @private
- */
- class WebGLExtensions {
- /**
- * Constructs a new utility object.
- *
- * @param {WebGLBackend} backend - The WebGL 2 backend.
- */
- constructor( backend ) {
- /**
- * A reference to the WebGL 2 backend.
- *
- * @type {WebGLBackend}
- */
- this.backend = backend;
- /**
- * A reference to the rendering context.
- *
- * @type {WebGL2RenderingContext}
- */
- this.gl = this.backend.gl;
- /**
- * A list with all the supported WebGL extensions.
- *
- * @type {Array<string>}
- */
- this.availableExtensions = this.gl.getSupportedExtensions();
- /**
- * A dictionary with requested WebGL extensions.
- * The key is the name of the extension, the value
- * the requested extension object.
- *
- * @type {Object<string,Object>}
- */
- this.extensions = {};
- }
- /**
- * Returns the extension object for the given extension name.
- *
- * @param {string} name - The extension name.
- * @return {Object} The extension object.
- */
- get( name ) {
- let extension = this.extensions[ name ];
- if ( extension === undefined ) {
- extension = this.gl.getExtension( name );
- this.extensions[ name ] = extension;
- }
- return extension;
- }
- /**
- * Returns `true` if the requested extension is available.
- *
- * @param {string} name - The extension name.
- * @return {boolean} Whether the given extension is available or not.
- */
- has( name ) {
- return this.availableExtensions.includes( name );
- }
- }
- /**
- * A WebGL 2 backend utility module for managing the device's capabilities.
- *
- * @private
- */
- class WebGLCapabilities {
- /**
- * Constructs a new utility object.
- *
- * @param {WebGLBackend} backend - The WebGL 2 backend.
- */
- constructor( backend ) {
- /**
- * A reference to the WebGL 2 backend.
- *
- * @type {WebGLBackend}
- */
- this.backend = backend;
- /**
- * This value holds the cached max anisotropy value.
- *
- * @type {?number}
- * @default null
- */
- this.maxAnisotropy = null;
- }
- /**
- * Returns the maximum anisotropy texture filtering value. This value
- * depends on the device and is reported by the `EXT_texture_filter_anisotropic`
- * WebGL extension.
- *
- * @return {number} The maximum anisotropy texture filtering value.
- */
- getMaxAnisotropy() {
- if ( this.maxAnisotropy !== null ) return this.maxAnisotropy;
- const gl = this.backend.gl;
- const extensions = this.backend.extensions;
- if ( extensions.has( 'EXT_texture_filter_anisotropic' ) === true ) {
- const extension = extensions.get( 'EXT_texture_filter_anisotropic' );
- this.maxAnisotropy = gl.getParameter( extension.MAX_TEXTURE_MAX_ANISOTROPY_EXT );
- } else {
- this.maxAnisotropy = 0;
- }
- return this.maxAnisotropy;
- }
- }
- const GLFeatureName = {
- 'WEBGL_multi_draw': 'WEBGL_multi_draw',
- 'WEBGL_compressed_texture_astc': 'texture-compression-astc',
- 'WEBGL_compressed_texture_etc': 'texture-compression-etc2',
- 'WEBGL_compressed_texture_etc1': 'texture-compression-etc1',
- 'WEBGL_compressed_texture_pvrtc': 'texture-compression-pvrtc',
- 'WEBGL_compressed_texture_s3tc': 'texture-compression-s3tc',
- 'EXT_texture_compression_bptc': 'texture-compression-bc',
- 'EXT_disjoint_timer_query_webgl2': 'timestamp-query',
- 'OVR_multiview2': 'OVR_multiview2'
- };
- class WebGLBufferRenderer {
- constructor( backend ) {
- this.gl = backend.gl;
- this.extensions = backend.extensions;
- this.info = backend.renderer.info;
- this.mode = null;
- this.index = 0;
- this.type = null;
- this.object = null;
- }
- render( start, count ) {
- const { gl, mode, object, type, info, index } = this;
- if ( index !== 0 ) {
- gl.drawElements( mode, count, type, start );
- } else {
- gl.drawArrays( mode, start, count );
- }
- info.update( object, count, 1 );
- }
- renderInstances( start, count, primcount ) {
- const { gl, mode, type, index, object, info } = this;
- if ( primcount === 0 ) return;
- if ( index !== 0 ) {
- gl.drawElementsInstanced( mode, count, type, start, primcount );
- } else {
- gl.drawArraysInstanced( mode, start, count, primcount );
- }
- info.update( object, count, primcount );
- }
- renderMultiDraw( starts, counts, drawCount ) {
- const { extensions, mode, object, info } = this;
- if ( drawCount === 0 ) return;
- const extension = extensions.get( 'WEBGL_multi_draw' );
- if ( extension === null ) {
- for ( let i = 0; i < drawCount; i ++ ) {
- this.render( starts[ i ], counts[ i ] );
- }
- } else {
- if ( this.index !== 0 ) {
- extension.multiDrawElementsWEBGL( mode, counts, 0, this.type, starts, 0, drawCount );
- } else {
- extension.multiDrawArraysWEBGL( mode, starts, 0, counts, 0, drawCount );
- }
- let elementCount = 0;
- for ( let i = 0; i < drawCount; i ++ ) {
- elementCount += counts[ i ];
- }
- info.update( object, elementCount, 1 );
- }
- }
- renderMultiDrawInstances( starts, counts, drawCount, primcount ) {
- const { extensions, mode, object, info } = this;
- if ( drawCount === 0 ) return;
- const extension = extensions.get( 'WEBGL_multi_draw' );
- if ( extension === null ) {
- for ( let i = 0; i < drawCount; i ++ ) {
- this.renderInstances( starts[ i ], counts[ i ], primcount[ i ] );
- }
- } else {
- if ( this.index !== 0 ) {
- extension.multiDrawElementsInstancedWEBGL( mode, counts, 0, this.type, starts, 0, primcount, 0, drawCount );
- } else {
- extension.multiDrawArraysInstancedWEBGL( mode, starts, 0, counts, 0, primcount, 0, drawCount );
- }
- let elementCount = 0;
- for ( let i = 0; i < drawCount; i ++ ) {
- elementCount += counts[ i ] * primcount[ i ];
- }
- info.update( object, elementCount, 1 );
- }
- }
- //
- }
- /**
- * Abstract base class of a timestamp query pool.
- *
- * @abstract
- */
- class TimestampQueryPool {
- /**
- * Creates a new timestamp query pool.
- *
- * @param {number} [maxQueries=256] - Maximum number of queries this pool can hold.
- */
- constructor( maxQueries = 256 ) {
- /**
- * Whether to track timestamps or not.
- *
- * @type {boolean}
- * @default true
- */
- this.trackTimestamp = true;
- /**
- * Maximum number of queries this pool can hold.
- *
- * @type {number}
- * @default 256
- */
- this.maxQueries = maxQueries;
- /**
- * How many queries allocated so far.
- *
- * @type {number}
- * @default 0
- */
- this.currentQueryIndex = 0;
- /**
- * Tracks offsets for different contexts.
- *
- * @type {Map<string, number>}
- */
- this.queryOffsets = new Map();
- /**
- * Whether the pool has been disposed or not.
- *
- * @type {boolean}
- * @default false
- */
- this.isDisposed = false;
- /**
- * TODO
- *
- * @type {number}
- * @default 0
- */
- this.lastValue = 0;
- /**
- * Stores all timestamp frames.
- *
- * @type {Array<number>}
- */
- this.frames = [];
- /**
- * TODO
- *
- * @type {boolean}
- * @default false
- */
- this.pendingResolve = false;
- /**
- * Stores the latest timestamp for each render context.
- *
- * @type {Map<string, number>}
- */
- this.timestamps = new Map();
- }
- /**
- * Returns all timestamp frames.
- *
- * @return {Array<number>} The timestamp frames.
- */
- getTimestampFrames() {
- return this.frames;
- }
- /**
- * Returns the timestamp for a given render context.
- *
- * @param {string} uid - A unique identifier for the render context.
- * @return {?number} The timestamp, or undefined if not available.
- */
- getTimestamp( uid ) {
- let timestamp = this.timestamps.get( uid );
- if ( timestamp === undefined ) {
- warn( `TimestampQueryPool: No timestamp available for uid ${ uid }.` );
- timestamp = 0;
- }
- return timestamp;
- }
- /**
- * Returns whether a timestamp is available for a given render context.
- *
- * @param {string} uid - A unique identifier for the render context.
- * @return {boolean} True if a timestamp is available, false otherwise.
- */
- hasTimestamp( uid ) {
- return this.timestamps.has( uid );
- }
- /**
- * Allocate queries for a specific uid.
- *
- * @abstract
- * @param {string} uid - A unique identifier for the render context.
- * @param {number} frameId - The current frame identifier.
- * @returns {?number}
- */
- allocateQueriesForContext( /* uid, frameId */ ) {}
- /**
- * Resolve all timestamps and return data (or process them).
- *
- * @abstract
- * @async
- * @returns {Promise<number>|number} The resolved timestamp value.
- */
- async resolveQueriesAsync() {}
- /**
- * Dispose of the query pool.
- *
- * @abstract
- */
- dispose() {}
- }
- /**
- * Manages a pool of WebGL timestamp queries for performance measurement.
- * Handles creation, execution, and resolution of timer queries using WebGL extensions.
- *
- * @augments TimestampQueryPool
- */
- class WebGLTimestampQueryPool extends TimestampQueryPool {
- /**
- * Creates a new WebGL timestamp query pool.
- *
- * @param {WebGLRenderingContext|WebGL2RenderingContext} gl - The WebGL context.
- * @param {string} type - The type identifier for this query pool.
- * @param {number} [maxQueries=2048] - Maximum number of queries this pool can hold.
- */
- constructor( gl, type, maxQueries = 2048 ) {
- super( maxQueries );
- this.gl = gl;
- this.type = type;
- // Check for timer query extensions
- this.ext = gl.getExtension( 'EXT_disjoint_timer_query_webgl2' ) ||
- gl.getExtension( 'EXT_disjoint_timer_query' );
- if ( ! this.ext ) {
- warn( 'EXT_disjoint_timer_query not supported; timestamps will be disabled.' );
- this.trackTimestamp = false;
- return;
- }
- // Create query objects
- this.queries = [];
- for ( let i = 0; i < this.maxQueries; i ++ ) {
- this.queries.push( gl.createQuery() );
- }
- this.activeQuery = null;
- this.queryStates = new Map(); // Track state of each query: 'inactive', 'started', 'ended'
- }
- /**
- * Allocates a pair of queries for a given render context.
- *
- * @param {string} uid - A unique identifier for the render context.
- * @returns {?number} The base offset for the allocated queries, or null if allocation failed.
- */
- allocateQueriesForContext( uid ) {
- if ( ! this.trackTimestamp ) return null;
- // Check if we have enough space for a new query pair
- if ( this.currentQueryIndex + 2 > this.maxQueries ) {
- warnOnce( `WebGPUTimestampQueryPool [${ this.type }]: Maximum number of queries exceeded, when using trackTimestamp it is necessary to resolves the queries via renderer.resolveTimestampsAsync( THREE.TimestampQuery.${ this.type.toUpperCase() } ).` );
- return null;
- }
- const baseOffset = this.currentQueryIndex;
- this.currentQueryIndex += 2;
- // Initialize query states
- this.queryStates.set( baseOffset, 'inactive' );
- this.queryOffsets.set( uid, baseOffset );
- return baseOffset;
- }
- /**
- * Begins a timestamp query for the specified render context.
- *
- * @param {string} uid - A unique identifier for the render context.
- */
- beginQuery( uid ) {
- if ( ! this.trackTimestamp || this.isDisposed ) {
- return;
- }
- const baseOffset = this.queryOffsets.get( uid );
- if ( baseOffset == null ) {
- return;
- }
- // Don't start a new query if there's an active one
- if ( this.activeQuery !== null ) {
- return;
- }
- const query = this.queries[ baseOffset ];
- if ( ! query ) {
- return;
- }
- try {
- // Only begin if query is inactive
- if ( this.queryStates.get( baseOffset ) === 'inactive' ) {
- this.gl.beginQuery( this.ext.TIME_ELAPSED_EXT, query );
- this.activeQuery = baseOffset;
- this.queryStates.set( baseOffset, 'started' );
- }
- } catch ( e ) {
- error( 'Error in beginQuery:', e );
- this.activeQuery = null;
- this.queryStates.set( baseOffset, 'inactive' );
- }
- }
- /**
- * Ends the active timestamp query for the specified render context.
- *
- * @param {string} uid - A unique identifier for the render context.
- */
- endQuery( uid ) {
- if ( ! this.trackTimestamp || this.isDisposed ) {
- return;
- }
- const baseOffset = this.queryOffsets.get( uid );
- if ( baseOffset == null ) {
- return;
- }
- // Only end if this is the active query
- if ( this.activeQuery !== baseOffset ) {
- return;
- }
- try {
- this.gl.endQuery( this.ext.TIME_ELAPSED_EXT );
- this.queryStates.set( baseOffset, 'ended' );
- this.activeQuery = null;
- } catch ( e ) {
- error( 'Error in endQuery:', e );
- // Reset state on error
- this.queryStates.set( baseOffset, 'inactive' );
- this.activeQuery = null;
- }
- }
- /**
- * Asynchronously resolves all completed queries and returns the total duration.
- *
- * @async
- * @returns {Promise<number>} The total duration in milliseconds, or the last valid value if resolution fails.
- */
- async resolveQueriesAsync() {
- if ( ! this.trackTimestamp || this.pendingResolve ) {
- return this.lastValue;
- }
- this.pendingResolve = true;
- try {
- // Wait for all ended queries to complete
- const resolvePromises = new Map();
- for ( const [ uid, baseOffset ] of this.queryOffsets ) {
- const state = this.queryStates.get( baseOffset );
- if ( state === 'ended' ) {
- const query = this.queries[ baseOffset ];
- resolvePromises.set( uid, this.resolveQuery( query ) );
- }
- }
- if ( resolvePromises.size === 0 ) {
- return this.lastValue;
- }
- //
- const framesDuration = {};
- const frames = [];
- for ( const [ uid, promise ] of resolvePromises ) {
- const match = uid.match( /^(.*):f(\d+)$/ );
- const frame = parseInt( match[ 2 ] );
- if ( frames.includes( frame ) === false ) {
- frames.push( frame );
- }
- if ( framesDuration[ frame ] === undefined ) framesDuration[ frame ] = 0;
- const duration = await promise;
- this.timestamps.set( uid, duration );
- framesDuration[ frame ] += duration;
- }
- // Return the total duration of the last frame
- const totalDuration = framesDuration[ frames[ frames.length - 1 ] ];
- // Store the last valid result
- this.lastValue = totalDuration;
- this.frames = frames;
- // Reset states
- this.currentQueryIndex = 0;
- this.queryOffsets.clear();
- this.queryStates.clear();
- this.activeQuery = null;
- return totalDuration;
- } catch ( e ) {
- error( 'Error resolving queries:', e );
- return this.lastValue;
- } finally {
- this.pendingResolve = false;
- }
- }
- /**
- * Resolves a single query, checking for completion and disjoint operation.
- *
- * @async
- * @param {WebGLQuery} query - The query object to resolve.
- * @returns {Promise<number>} The elapsed time in milliseconds.
- */
- async resolveQuery( query ) {
- return new Promise( ( resolve ) => {
- if ( this.isDisposed ) {
- resolve( this.lastValue );
- return;
- }
- let timeoutId;
- let isResolved = false;
- const cleanup = () => {
- if ( timeoutId ) {
- clearTimeout( timeoutId );
- timeoutId = null;
- }
- };
- const finalizeResolution = ( value ) => {
- if ( ! isResolved ) {
- isResolved = true;
- cleanup();
- resolve( value );
- }
- };
- const checkQuery = () => {
- if ( this.isDisposed ) {
- finalizeResolution( this.lastValue );
- return;
- }
- try {
- // Check if the GPU timer was disjoint (i.e., timing was unreliable)
- const disjoint = this.gl.getParameter( this.ext.GPU_DISJOINT_EXT );
- if ( disjoint ) {
- finalizeResolution( this.lastValue );
- return;
- }
- const available = this.gl.getQueryParameter( query, this.gl.QUERY_RESULT_AVAILABLE );
- if ( ! available ) {
- timeoutId = setTimeout( checkQuery, 1 );
- return;
- }
- const elapsed = this.gl.getQueryParameter( query, this.gl.QUERY_RESULT );
- resolve( Number( elapsed ) / 1e6 ); // Convert nanoseconds to milliseconds
- } catch ( e ) {
- error( 'Error checking query:', e );
- resolve( this.lastValue );
- }
- };
- checkQuery();
- } );
- }
- /**
- * Releases all resources held by this query pool.
- * This includes deleting all query objects and clearing internal state.
- */
- dispose() {
- if ( this.isDisposed ) {
- return;
- }
- this.isDisposed = true;
- if ( ! this.trackTimestamp ) return;
- for ( const query of this.queries ) {
- this.gl.deleteQuery( query );
- }
- this.queries = [];
- this.queryStates.clear();
- this.queryOffsets.clear();
- this.lastValue = 0;
- this.activeQuery = null;
- }
- }
- /**
- * A backend implementation targeting WebGL 2.
- *
- * @private
- * @augments Backend
- */
- class WebGLBackend extends Backend {
- /**
- * WebGLBackend options.
- *
- * @typedef {Object} WebGLBackend~Options
- * @property {boolean} [logarithmicDepthBuffer=false] - Whether logarithmic depth buffer is enabled or not.
- * @property {boolean} [alpha=true] - Whether the default framebuffer (which represents the final contents of the canvas) should be transparent or opaque.
- * @property {boolean} [depth=true] - Whether the default framebuffer should have a depth buffer or not.
- * @property {boolean} [stencil=false] - Whether the default framebuffer should have a stencil buffer or not.
- * @property {boolean} [antialias=false] - Whether MSAA as the default anti-aliasing should be enabled or not.
- * @property {number} [samples=0] - When `antialias` is `true`, `4` samples are used by default. Set this parameter to any other integer value than 0 to overwrite the default.
- * @property {boolean} [forceWebGL=false] - If set to `true`, the renderer uses a WebGL 2 backend no matter if WebGPU is supported or not.
- * @property {WebGL2RenderingContext} [context=undefined] - A WebGL 2 rendering context.
- */
- /**
- * Constructs a new WebGPU backend.
- *
- * @param {WebGLBackend~Options} [parameters] - The configuration parameter.
- */
- constructor( parameters = {} ) {
- super( parameters );
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isWebGLBackend = true;
- /**
- * A reference to a backend module holding shader attribute-related
- * utility functions.
- *
- * @type {?WebGLAttributeUtils}
- * @default null
- */
- this.attributeUtils = null;
- /**
- * A reference to a backend module holding extension-related
- * utility functions.
- *
- * @type {?WebGLExtensions}
- * @default null
- */
- this.extensions = null;
- /**
- * A reference to a backend module holding capability-related
- * utility functions.
- *
- * @type {?WebGLCapabilities}
- * @default null
- */
- this.capabilities = null;
- /**
- * A reference to a backend module holding texture-related
- * utility functions.
- *
- * @type {?WebGLTextureUtils}
- * @default null
- */
- this.textureUtils = null;
- /**
- * A reference to a backend module holding renderer-related
- * utility functions.
- *
- * @type {?WebGLBufferRenderer}
- * @default null
- */
- this.bufferRenderer = null;
- /**
- * A reference to the rendering context.
- *
- * @type {?WebGL2RenderingContext}
- * @default null
- */
- this.gl = null;
- /**
- * A reference to a backend module holding state-related
- * utility functions.
- *
- * @type {?WebGLState}
- * @default null
- */
- this.state = null;
- /**
- * A reference to a backend module holding common
- * utility functions.
- *
- * @type {?WebGLUtils}
- * @default null
- */
- this.utils = null;
- /**
- * Dictionary for caching VAOs.
- *
- * @type {Object<string,WebGLVertexArrayObject>}
- */
- this.vaoCache = {};
- /**
- * Dictionary for caching transform feedback objects.
- *
- * @type {Object<string,WebGLTransformFeedback>}
- */
- this.transformFeedbackCache = {};
- /**
- * Controls if `gl.RASTERIZER_DISCARD` should be enabled or not.
- * Only relevant when using compute shaders.
- *
- * @type {boolean}
- * @default false
- */
- this.discard = false;
- /**
- * A reference to the `EXT_disjoint_timer_query_webgl2` extension. `null` if the
- * device does not support the extension.
- *
- * @type {?EXTDisjointTimerQueryWebGL2}
- * @default null
- */
- this.disjoint = null;
- /**
- * A reference to the `KHR_parallel_shader_compile` extension. `null` if the
- * device does not support the extension.
- *
- * @type {?KHRParallelShaderCompile}
- * @default null
- */
- this.parallel = null;
- /**
- * A reference to the current render context.
- *
- * @private
- * @type {RenderContext}
- * @default null
- */
- this._currentContext = null;
- /**
- * A unique collection of bindings.
- *
- * @private
- * @type {WeakSet<Array<BindGroup>>}
- */
- this._knownBindings = new WeakSet();
- /**
- * Whether the device supports framebuffers invalidation or not.
- *
- * @private
- * @type {boolean}
- */
- this._supportsInvalidateFramebuffer = typeof navigator === 'undefined' ? false : /OculusBrowser/g.test( navigator.userAgent );
- /**
- * The target framebuffer when rendering with
- * the WebXR device API.
- *
- * @private
- * @type {?WebGLFramebuffer}
- * @default null
- */
- this._xrFramebuffer = null;
- }
- /**
- * Initializes the backend so it is ready for usage.
- *
- * @param {Renderer} renderer - The renderer.
- */
- init( renderer ) {
- super.init( renderer );
- //
- const parameters = this.parameters;
- const contextAttributes = {
- antialias: renderer.currentSamples > 0,
- alpha: true, // always true for performance reasons
- depth: renderer.depth,
- stencil: renderer.stencil
- };
- const glContext = ( parameters.context !== undefined ) ? parameters.context : renderer.domElement.getContext( 'webgl2', contextAttributes );
- function onContextLost( event ) {
- event.preventDefault();
- const contextLossInfo = {
- api: 'WebGL',
- message: event.statusMessage || 'Unknown reason',
- reason: null,
- originalEvent: event
- };
- renderer.onDeviceLost( contextLossInfo );
- }
- this._onContextLost = onContextLost;
- renderer.domElement.addEventListener( 'webglcontextlost', onContextLost, false );
- this.gl = glContext;
- this.extensions = new WebGLExtensions( this );
- this.capabilities = new WebGLCapabilities( this );
- this.attributeUtils = new WebGLAttributeUtils( this );
- this.textureUtils = new WebGLTextureUtils( this );
- this.bufferRenderer = new WebGLBufferRenderer( this );
- this.state = new WebGLState( this );
- this.utils = new WebGLUtils( this );
- this.extensions.get( 'EXT_color_buffer_float' );
- this.extensions.get( 'WEBGL_clip_cull_distance' );
- this.extensions.get( 'OES_texture_float_linear' );
- this.extensions.get( 'EXT_color_buffer_half_float' );
- this.extensions.get( 'WEBGL_multisampled_render_to_texture' );
- this.extensions.get( 'WEBGL_render_shared_exponent' );
- this.extensions.get( 'WEBGL_multi_draw' );
- this.extensions.get( 'OVR_multiview2' );
- this.disjoint = this.extensions.get( 'EXT_disjoint_timer_query_webgl2' );
- this.parallel = this.extensions.get( 'KHR_parallel_shader_compile' );
- this.drawBuffersIndexedExt = this.extensions.get( 'OES_draw_buffers_indexed' );
- }
- /**
- * The coordinate system of the backend.
- *
- * @type {number}
- * @readonly
- */
- get coordinateSystem() {
- return WebGLCoordinateSystem;
- }
- /**
- * This method performs a readback operation by moving buffer data from
- * a storage buffer attribute from the GPU to the CPU.
- *
- * @async
- * @param {StorageBufferAttribute} attribute - The storage buffer attribute.
- * @return {Promise<ArrayBuffer>} A promise that resolves with the buffer data when the data are ready.
- */
- async getArrayBufferAsync( attribute ) {
- return await this.attributeUtils.getArrayBufferAsync( attribute );
- }
- /**
- * Ensures the backend is XR compatible.
- *
- * @async
- * @return {Promise} A Promise that resolve when the renderer is XR compatible.
- */
- async makeXRCompatible() {
- const attributes = this.gl.getContextAttributes();
- if ( attributes.xrCompatible !== true ) {
- await this.gl.makeXRCompatible();
- }
- }
- /**
- * Sets the XR rendering destination.
- *
- * @param {WebGLFramebuffer} xrFramebuffer - The XR framebuffer.
- */
- setXRTarget( xrFramebuffer ) {
- this._xrFramebuffer = xrFramebuffer;
- }
- /**
- * Configures the given XR render target with external textures.
- *
- * This method is only relevant when using the WebXR Layers API.
- *
- * @param {XRRenderTarget} renderTarget - The XR render target.
- * @param {WebGLTexture} colorTexture - A native color texture.
- * @param {?WebGLTexture} [depthTexture=null] - A native depth texture.
- */
- setXRRenderTargetTextures( renderTarget, colorTexture, depthTexture = null ) {
- const gl = this.gl;
- this.set( renderTarget.texture, { textureGPU: colorTexture, glInternalFormat: gl.RGBA8 } ); // see #24698 why RGBA8 and not SRGB8_ALPHA8 is used
- if ( depthTexture !== null ) {
- const glInternalFormat = renderTarget.stencilBuffer ? gl.DEPTH24_STENCIL8 : gl.DEPTH_COMPONENT24;
- this.set( renderTarget.depthTexture, { textureGPU: depthTexture, glInternalFormat: glInternalFormat } );
- // The multisample_render_to_texture extension doesn't work properly if there
- // are midframe flushes and an external depth texture.
- if ( ( this.extensions.has( 'WEBGL_multisampled_render_to_texture' ) === true ) && renderTarget._autoAllocateDepthBuffer === true && renderTarget.multiview === false ) {
- warn( 'WebGLBackend: Render-to-texture extension was disabled because an external texture was provided' );
- }
- renderTarget._autoAllocateDepthBuffer = false;
- }
- }
- /**
- * Inits a time stamp query for the given render context.
- *
- * @param {string} type - The type of the timestamp query.
- * @param {string} uid - A unique identifier for the timestamp query.
- */
- initTimestampQuery( type, uid ) {
- if ( ! this.disjoint || ! this.trackTimestamp ) return;
- if ( ! this.timestampQueryPool[ type ] ) {
- // TODO: Variable maxQueries?
- this.timestampQueryPool[ type ] = new WebGLTimestampQueryPool( this.gl, type, 2048 );
- }
- const timestampQueryPool = this.timestampQueryPool[ type ];
- const baseOffset = timestampQueryPool.allocateQueriesForContext( uid );
- if ( baseOffset !== null ) {
- timestampQueryPool.beginQuery( uid );
- }
- }
- // timestamp utils
- /**
- * Prepares the timestamp buffer.
- *
- * @param {string} type - The type of the timestamp query.
- * @param {string} uid - A unique identifier for the timestamp query.
- */
- prepareTimestampBuffer( type, uid ) {
- if ( ! this.disjoint || ! this.trackTimestamp ) return;
- const timestampQueryPool = this.timestampQueryPool[ type ];
- timestampQueryPool.endQuery( uid );
- }
- /**
- * Returns the backend's rendering context.
- *
- * @return {WebGL2RenderingContext} The rendering context.
- */
- getContext() {
- return this.gl;
- }
- /**
- * This method is executed at the beginning of a render call and prepares
- * the WebGL state for upcoming render calls
- *
- * @param {RenderContext} renderContext - The render context.
- */
- beginRender( renderContext ) {
- const { state } = this;
- const renderContextData = this.get( renderContext );
- //
- if ( renderContext.viewport ) {
- this.updateViewport( renderContext );
- } else {
- const { width, height } = this.getDrawingBufferSize();
- state.viewport( 0, 0, width, height );
- }
- if ( renderContext.scissor ) {
- const { x, y, width, height } = renderContext.scissorValue;
- state.scissor( x, renderContext.height - height - y, width, height );
- }
- //
- this.initTimestampQuery( TimestampQuery.RENDER, this.getTimestampUID( renderContext ) );
- renderContextData.previousContext = this._currentContext;
- this._currentContext = renderContext;
- this._setFramebuffer( renderContext );
- this.clear( renderContext.clearColor, renderContext.clearDepth, renderContext.clearStencil, renderContext, false );
- const occlusionQueryCount = renderContext.occlusionQueryCount;
- if ( occlusionQueryCount > 0 ) {
- // Get a reference to the array of objects with queries. The renderContextData property
- // can be changed by another render pass before the async reading of all previous queries complete
- renderContextData.currentOcclusionQueries = renderContextData.occlusionQueries;
- renderContextData.currentOcclusionQueryObjects = renderContextData.occlusionQueryObjects;
- renderContextData.lastOcclusionObject = null;
- renderContextData.occlusionQueries = new Array( occlusionQueryCount );
- renderContextData.occlusionQueryObjects = new Array( occlusionQueryCount );
- renderContextData.occlusionQueryIndex = 0;
- }
- }
- /**
- * This method is executed at the end of a render call and finalizes work
- * after draw calls.
- *
- * @param {RenderContext} renderContext - The render context.
- */
- finishRender( renderContext ) {
- const { gl, state } = this;
- const renderContextData = this.get( renderContext );
- const previousContext = renderContextData.previousContext;
- state.resetVertexState();
- const occlusionQueryCount = renderContext.occlusionQueryCount;
- if ( occlusionQueryCount > 0 ) {
- if ( occlusionQueryCount > renderContextData.occlusionQueryIndex ) {
- gl.endQuery( gl.ANY_SAMPLES_PASSED );
- }
- this.resolveOccludedAsync( renderContext );
- }
- const textures = renderContext.textures;
- if ( textures !== null ) {
- for ( let i = 0; i < textures.length; i ++ ) {
- const texture = textures[ i ];
- if ( texture.generateMipmaps ) {
- this.generateMipmaps( texture );
- }
- }
- }
- this._currentContext = previousContext;
- this._resolveRenderTarget( renderContext );
- if ( previousContext !== null ) {
- this._setFramebuffer( previousContext );
- if ( previousContext.viewport ) {
- this.updateViewport( previousContext );
- } else {
- const { width, height } = this.getDrawingBufferSize();
- state.viewport( 0, 0, width, height );
- }
- }
- this.prepareTimestampBuffer( TimestampQuery.RENDER, this.getTimestampUID( renderContext ) );
- }
- /**
- * This method processes the result of occlusion queries and writes it
- * into render context data.
- *
- * @async
- * @param {RenderContext} renderContext - The render context.
- */
- resolveOccludedAsync( renderContext ) {
- const renderContextData = this.get( renderContext );
- // handle occlusion query results
- const { currentOcclusionQueries, currentOcclusionQueryObjects } = renderContextData;
- if ( currentOcclusionQueries && currentOcclusionQueryObjects ) {
- const occluded = new WeakSet();
- const { gl } = this;
- renderContextData.currentOcclusionQueryObjects = null;
- renderContextData.currentOcclusionQueries = null;
- const check = () => {
- let completed = 0;
- // check all queries and requeue as appropriate
- for ( let i = 0; i < currentOcclusionQueries.length; i ++ ) {
- const query = currentOcclusionQueries[ i ];
- if ( query === null ) continue;
- if ( gl.getQueryParameter( query, gl.QUERY_RESULT_AVAILABLE ) ) {
- if ( gl.getQueryParameter( query, gl.QUERY_RESULT ) === 0 ) occluded.add( currentOcclusionQueryObjects[ i ] );
- currentOcclusionQueries[ i ] = null;
- gl.deleteQuery( query );
- completed ++;
- }
- }
- if ( completed < currentOcclusionQueries.length ) {
- requestAnimationFrame( check );
- } else {
- renderContextData.occluded = occluded;
- }
- };
- check();
- }
- }
- /**
- * Returns `true` if the given 3D object is fully occluded by other
- * 3D objects in the scene.
- *
- * @param {RenderContext} renderContext - The render context.
- * @param {Object3D} object - The 3D object to test.
- * @return {boolean} Whether the 3D object is fully occluded or not.
- */
- isOccluded( renderContext, object ) {
- const renderContextData = this.get( renderContext );
- return renderContextData.occluded && renderContextData.occluded.has( object );
- }
- /**
- * Updates the viewport with the values from the given render context.
- *
- * @param {RenderContext} renderContext - The render context.
- */
- updateViewport( renderContext ) {
- const { state } = this;
- const { x, y, width, height } = renderContext.viewportValue;
- state.viewport( x, renderContext.height - height - y, width, height );
- }
- /**
- * Defines the scissor test.
- *
- * @param {boolean} boolean - Whether the scissor test should be enabled or not.
- */
- setScissorTest( boolean ) {
- const state = this.state;
- state.setScissorTest( boolean );
- }
- /**
- * Returns the clear color and alpha into a single
- * color object.
- *
- * @return {Color4} The clear color.
- */
- getClearColor() {
- const clearColor = super.getClearColor();
- // Since the canvas is always created with alpha: true,
- // WebGL must always premultiply the clear color.
- clearColor.r *= clearColor.a;
- clearColor.g *= clearColor.a;
- clearColor.b *= clearColor.a;
- return clearColor;
- }
- /**
- * Performs a clear operation.
- *
- * @param {boolean} color - Whether the color buffer should be cleared or not.
- * @param {boolean} depth - Whether the depth buffer should be cleared or not.
- * @param {boolean} stencil - Whether the stencil buffer should be cleared or not.
- * @param {?Object} [descriptor=null] - The render context of the current set render target.
- * @param {boolean} [setFrameBuffer=true] - Controls whether the intermediate framebuffer should be set or not.
- * @param {boolean} [resolveRenderTarget=true] - Controls whether an active render target should be resolved
- * or not. Only relevant for explicit clears.
- */
- clear( color, depth, stencil, descriptor = null, setFrameBuffer = true, resolveRenderTarget = true ) {
- const { gl, renderer } = this;
- if ( descriptor === null ) {
- const clearColor = this.getClearColor();
- descriptor = {
- textures: null,
- clearColorValue: clearColor
- };
- }
- //
- let clear = 0;
- if ( color ) clear |= gl.COLOR_BUFFER_BIT;
- if ( depth ) clear |= gl.DEPTH_BUFFER_BIT;
- if ( stencil ) clear |= gl.STENCIL_BUFFER_BIT;
- if ( clear !== 0 ) {
- let clearColor;
- if ( descriptor.clearColorValue ) {
- clearColor = descriptor.clearColorValue;
- } else {
- clearColor = this.getClearColor();
- }
- const clearDepth = renderer.getClearDepth();
- const clearStencil = renderer.getClearStencil();
- if ( depth ) this.state.setDepthMask( true );
- if ( descriptor.textures === null ) {
- gl.clearColor( clearColor.r, clearColor.g, clearColor.b, clearColor.a );
- gl.clear( clear );
- } else {
- if ( setFrameBuffer ) this._setFramebuffer( descriptor );
- if ( color ) {
- for ( let i = 0; i < descriptor.textures.length; i ++ ) {
- if ( i === 0 ) {
- gl.clearBufferfv( gl.COLOR, i, [ clearColor.r, clearColor.g, clearColor.b, clearColor.a ] );
- } else {
- gl.clearBufferfv( gl.COLOR, i, [ 0, 0, 0, 1 ] );
- }
- }
- }
- if ( depth && stencil ) {
- gl.clearBufferfi( gl.DEPTH_STENCIL, 0, clearDepth, clearStencil );
- } else if ( depth ) {
- gl.clearBufferfv( gl.DEPTH, 0, [ clearDepth ] );
- } else if ( stencil ) {
- gl.clearBufferiv( gl.STENCIL, 0, [ clearStencil ] );
- }
- if ( setFrameBuffer && resolveRenderTarget ) this._resolveRenderTarget( descriptor );
- }
- }
- }
- /**
- * This method is executed at the beginning of a compute call and
- * prepares the state for upcoming compute tasks.
- *
- * @param {Node|Array<Node>} computeGroup - The compute node(s).
- */
- beginCompute( computeGroup ) {
- const { state, gl } = this;
- //
- state.bindFramebuffer( gl.FRAMEBUFFER, null );
- this.initTimestampQuery( TimestampQuery.COMPUTE, this.getTimestampUID( computeGroup ) );
- }
- /**
- * Executes a compute command for the given compute node.
- *
- * @param {Node|Array<Node>} computeGroup - The group of compute nodes of a compute call. Can be a single compute node.
- * @param {Node} computeNode - The compute node.
- * @param {Array<BindGroup>} bindings - The bindings.
- * @param {ComputePipeline} pipeline - The compute pipeline.
- * @param {?number} [count=null] - The count of compute invocations. If `null`, the count is determined by the compute node.
- */
- compute( computeGroup, computeNode, bindings, pipeline, count = null ) {
- const { state, gl } = this;
- if ( this.discard === false ) {
- // required here to handle async behaviour of render.compute()
- state.enable( gl.RASTERIZER_DISCARD );
- this.discard = true;
- }
- const { programGPU, transformBuffers, attributes } = this.get( pipeline );
- const vaoKey = this._getVaoKey( attributes );
- const vaoGPU = this.vaoCache[ vaoKey ];
- if ( vaoGPU === undefined ) {
- this.vaoCache[ vaoKey ] = this._createVao( attributes );
- } else {
- state.setVertexState( vaoGPU );
- }
- state.useProgram( programGPU );
- this._bindUniforms( bindings );
- const transformFeedbackGPU = this._getTransformFeedback( transformBuffers );
- gl.bindTransformFeedback( gl.TRANSFORM_FEEDBACK, transformFeedbackGPU );
- gl.beginTransformFeedback( gl.POINTS );
- count = ( count !== null ) ? count : computeNode.count;
- if ( Array.isArray( count ) ) {
- warnOnce( 'WebGLBackend.compute(): The count parameter must be a single number, not an array.' );
- count = count[ 0 ];
- } else if ( count && typeof count === 'object' && count.isIndirectStorageBufferAttribute ) {
- warnOnce( 'WebGLBackend.compute(): The count parameter must be a single number, not IndirectStorageBufferAttribute' );
- count = computeNode.count;
- }
- if ( attributes[ 0 ].isStorageInstancedBufferAttribute ) {
- gl.drawArraysInstanced( gl.POINTS, 0, 1, count );
- } else {
- gl.drawArrays( gl.POINTS, 0, count );
- }
- gl.endTransformFeedback();
- gl.bindTransformFeedback( gl.TRANSFORM_FEEDBACK, null );
- // switch active buffers
- for ( let i = 0; i < transformBuffers.length; i ++ ) {
- const dualAttributeData = transformBuffers[ i ];
- if ( dualAttributeData.pbo && this.has( dualAttributeData.pbo ) ) {
- this.textureUtils.copyBufferToTexture( dualAttributeData.transformBuffer, dualAttributeData.pbo );
- }
- dualAttributeData.switchBuffers();
- }
- }
- /**
- * This method is executed at the end of a compute call and
- * finalizes work after compute tasks.
- *
- * @param {Node|Array<Node>} computeGroup - The compute node(s).
- */
- finishCompute( computeGroup ) {
- const { state, gl } = this;
- this.discard = false;
- state.disable( gl.RASTERIZER_DISCARD );
- this.prepareTimestampBuffer( TimestampQuery.COMPUTE, this.getTimestampUID( computeGroup ) );
- if ( this._currentContext ) {
- this._setFramebuffer( this._currentContext );
- }
- }
- /**
- * Internal to determine if the current render target is a render target array with depth 2D array texture.
- *
- * @param {RenderContext} renderContext - The render context.
- * @return {boolean} Whether the render target is a render target array with depth 2D array texture.
- *
- * @private
- */
- _isRenderCameraDepthArray( renderContext ) {
- return renderContext.depthTexture && renderContext.depthTexture.isArrayTexture && renderContext.camera.isArrayCamera;
- }
- /**
- * Executes a draw command for the given render object.
- *
- * @param {RenderObject} renderObject - The render object to draw.
- * @param {Info} info - Holds a series of statistical information about the GPU memory and the rendering process.
- */
- draw( renderObject/*, info*/ ) {
- const { object, pipeline, material, context, hardwareClippingPlanes } = renderObject;
- const { programGPU } = this.get( pipeline );
- const { gl, state } = this;
- const contextData = this.get( context );
- const drawParams = renderObject.getDrawParameters();
- if ( drawParams === null ) return;
- //
- this._bindUniforms( renderObject.getBindings() );
- const frontFaceCW = ( object.isMesh && object.matrixWorld.determinant() < 0 );
- state.setMaterial( material, frontFaceCW, hardwareClippingPlanes );
- if ( context.textures !== null && context.textures.length > 1 ) {
- state.setMRTBlending( context.textures );
- }
- state.useProgram( programGPU );
- // vertex state
- const attributes = renderObject.getAttributes();
- const attributesData = this.get( attributes );
- let vaoGPU = attributesData.vaoGPU;
- if ( vaoGPU === undefined ) {
- const vaoKey = this._getVaoKey( attributes );
- vaoGPU = this.vaoCache[ vaoKey ];
- if ( vaoGPU === undefined ) {
- vaoGPU = this._createVao( attributes );
- this.vaoCache[ vaoKey ] = vaoGPU;
- attributesData.vaoGPU = vaoGPU;
- }
- }
- const index = renderObject.getIndex();
- const indexGPU = ( index !== null ) ? this.get( index ).bufferGPU : null;
- state.setVertexState( vaoGPU, indexGPU );
- //
- const lastObject = contextData.lastOcclusionObject;
- if ( lastObject !== object && lastObject !== undefined ) {
- if ( lastObject !== null && lastObject.occlusionTest === true ) {
- gl.endQuery( gl.ANY_SAMPLES_PASSED );
- contextData.occlusionQueryIndex ++;
- }
- if ( object.occlusionTest === true ) {
- const query = gl.createQuery();
- gl.beginQuery( gl.ANY_SAMPLES_PASSED, query );
- contextData.occlusionQueries[ contextData.occlusionQueryIndex ] = query;
- contextData.occlusionQueryObjects[ contextData.occlusionQueryIndex ] = object;
- }
- contextData.lastOcclusionObject = object;
- }
- //
- const renderer = this.bufferRenderer;
- if ( object.isPoints ) renderer.mode = gl.POINTS;
- else if ( object.isLineSegments ) renderer.mode = gl.LINES;
- else if ( object.isLine ) renderer.mode = gl.LINE_STRIP;
- else if ( object.isLineLoop ) renderer.mode = gl.LINE_LOOP;
- else {
- if ( material.wireframe === true ) {
- state.setLineWidth( material.wireframeLinewidth * this.renderer.getPixelRatio() );
- renderer.mode = gl.LINES;
- } else {
- renderer.mode = gl.TRIANGLES;
- }
- }
- //
- const { vertexCount, instanceCount } = drawParams;
- let { firstVertex } = drawParams;
- renderer.object = object;
- if ( index !== null ) {
- firstVertex *= index.array.BYTES_PER_ELEMENT;
- const indexData = this.get( index );
- renderer.index = index.count;
- renderer.type = indexData.type;
- } else {
- renderer.index = 0;
- }
- const draw = () => {
- if ( object.isBatchedMesh ) {
- if ( object._multiDrawInstances !== null ) {
- // @deprecated, r174
- warnOnce( 'WebGLBackend: renderMultiDrawInstances has been deprecated and will be removed in r184. Append to renderMultiDraw arguments and use indirection.' );
- renderer.renderMultiDrawInstances( object._multiDrawStarts, object._multiDrawCounts, object._multiDrawCount, object._multiDrawInstances );
- } else if ( ! this.hasFeature( 'WEBGL_multi_draw' ) ) {
- warnOnce( 'WebGLBackend: WEBGL_multi_draw not supported.' );
- } else {
- renderer.renderMultiDraw( object._multiDrawStarts, object._multiDrawCounts, object._multiDrawCount );
- }
- } else if ( instanceCount > 1 ) {
- renderer.renderInstances( firstVertex, vertexCount, instanceCount );
- } else {
- renderer.render( firstVertex, vertexCount );
- }
- };
- if ( renderObject.camera.isArrayCamera === true && renderObject.camera.cameras.length > 0 && renderObject.camera.isMultiViewCamera === false ) {
- const cameraData = this.get( renderObject.camera );
- const cameras = renderObject.camera.cameras;
- const cameraIndex = renderObject.getBindingGroup( 'cameraIndex' ).bindings[ 0 ];
- if ( cameraData.indexesGPU === undefined || cameraData.indexesGPU.length !== cameras.length ) {
- const data = new Uint32Array( [ 0, 0, 0, 0 ] );
- const indexesGPU = [];
- for ( let i = 0, len = cameras.length; i < len; i ++ ) {
- const bufferGPU = gl.createBuffer();
- data[ 0 ] = i;
- gl.bindBuffer( gl.UNIFORM_BUFFER, bufferGPU );
- gl.bufferData( gl.UNIFORM_BUFFER, data, gl.STATIC_DRAW );
- indexesGPU.push( bufferGPU );
- }
- cameraData.indexesGPU = indexesGPU; // TODO: Create a global library for this
- }
- const cameraIndexData = this.get( cameraIndex );
- const pixelRatio = this.renderer.getPixelRatio();
- const renderTarget = this._currentContext.renderTarget;
- const isRenderCameraDepthArray = this._isRenderCameraDepthArray( this._currentContext );
- const prevActiveCubeFace = this._currentContext.activeCubeFace;
- if ( isRenderCameraDepthArray ) {
- // Clear the depth texture
- const textureData = this.get( renderTarget.depthTexture );
- if ( textureData.clearedRenderId !== this.renderer._nodes.nodeFrame.renderId ) {
- textureData.clearedRenderId = this.renderer._nodes.nodeFrame.renderId;
- const { stencilBuffer } = renderTarget;
- for ( let i = 0, len = cameras.length; i < len; i ++ ) {
- this.renderer._activeCubeFace = i;
- this._currentContext.activeCubeFace = i;
- this._setFramebuffer( this._currentContext );
- this.clear( false, true, stencilBuffer, this._currentContext, false, false );
- }
- this.renderer._activeCubeFace = prevActiveCubeFace;
- this._currentContext.activeCubeFace = prevActiveCubeFace;
- }
- }
- for ( let i = 0, len = cameras.length; i < len; i ++ ) {
- const subCamera = cameras[ i ];
- if ( object.layers.test( subCamera.layers ) ) {
- if ( isRenderCameraDepthArray ) {
- // Update the active layer
- this.renderer._activeCubeFace = i;
- this._currentContext.activeCubeFace = i;
- this._setFramebuffer( this._currentContext );
- }
- const vp = subCamera.viewport;
- if ( vp !== undefined ) {
- const x = vp.x * pixelRatio;
- const y = vp.y * pixelRatio;
- const width = vp.width * pixelRatio;
- const height = vp.height * pixelRatio;
- state.viewport(
- Math.floor( x ),
- Math.floor( renderObject.context.height - height - y ),
- Math.floor( width ),
- Math.floor( height )
- );
- }
- state.bindBufferBase( gl.UNIFORM_BUFFER, cameraIndexData.index, cameraData.indexesGPU[ i ] );
- draw();
- }
- this._currentContext.activeCubeFace = prevActiveCubeFace;
- this.renderer._activeCubeFace = prevActiveCubeFace;
- }
- } else {
- draw();
- }
- }
- /**
- * Explain why always null is returned.
- *
- * @param {RenderObject} renderObject - The render object.
- * @return {boolean} Whether the render pipeline requires an update or not.
- */
- needsRenderUpdate( /*renderObject*/ ) {
- return false;
- }
- /**
- * Explain why no cache key is computed.
- *
- * @param {RenderObject} renderObject - The render object.
- * @return {string} The cache key.
- */
- getRenderCacheKey( /*renderObject*/ ) {
- return '';
- }
- // textures
- /**
- * Creates a default texture for the given texture that can be used
- * as a placeholder until the actual texture is ready for usage.
- *
- * @param {Texture} texture - The texture to create a default texture for.
- */
- createDefaultTexture( texture ) {
- this.textureUtils.createDefaultTexture( texture );
- }
- /**
- * Defines a texture on the GPU for the given texture object.
- *
- * @param {Texture} texture - The texture.
- * @param {Object} [options={}] - Optional configuration parameter.
- */
- createTexture( texture, options ) {
- this.textureUtils.createTexture( texture, options );
- }
- /**
- * Uploads the updated texture data to the GPU.
- *
- * @param {Texture} texture - The texture.
- * @param {Object} [options={}] - Optional configuration parameter.
- */
- updateTexture( texture, options ) {
- this.textureUtils.updateTexture( texture, options );
- }
- /**
- * Generates mipmaps for the given texture.
- *
- * @param {Texture} texture - The texture.
- */
- generateMipmaps( texture ) {
- this.textureUtils.generateMipmaps( texture );
- }
- /**
- * Destroys the GPU data for the given texture object.
- *
- * @param {Texture} texture - The texture.
- * @param {boolean} [isDefaultTexture=false] - Whether the texture uses a default GPU texture or not.
- */
- destroyTexture( texture, isDefaultTexture = false ) {
- this.textureUtils.destroyTexture( texture, isDefaultTexture );
- }
- /**
- * Returns texture data as a typed array.
- *
- * @async
- * @param {Texture} texture - The texture to copy.
- * @param {number} x - The x coordinate of the copy origin.
- * @param {number} y - The y coordinate of the copy origin.
- * @param {number} width - The width of the copy.
- * @param {number} height - The height of the copy.
- * @param {number} faceIndex - The face index.
- * @return {Promise<TypedArray>} A Promise that resolves with a typed array when the copy operation has finished.
- */
- async copyTextureToBuffer( texture, x, y, width, height, faceIndex ) {
- return this.textureUtils.copyTextureToBuffer( texture, x, y, width, height, faceIndex );
- }
- /**
- * This method does nothing since WebGL 2 has no concept of samplers.
- *
- * @param {Texture} texture - The texture to update the sampler for.
- * @return {string} The current sampler key.
- */
- updateSampler( /*texture*/ ) {
- return '';
- }
- // node builder
- /**
- * Returns a node builder for the given render object.
- *
- * @param {RenderObject} object - The render object.
- * @param {Renderer} renderer - The renderer.
- * @return {GLSLNodeBuilder} The node builder.
- */
- createNodeBuilder( object, renderer ) {
- return new GLSLNodeBuilder( object, renderer );
- }
- // program
- /**
- * Creates a shader program from the given programmable stage.
- *
- * @param {ProgrammableStage} program - The programmable stage.
- */
- createProgram( program ) {
- const gl = this.gl;
- const { stage, code } = program;
- const shader = stage === 'fragment' ? gl.createShader( gl.FRAGMENT_SHADER ) : gl.createShader( gl.VERTEX_SHADER );
- gl.shaderSource( shader, code );
- gl.compileShader( shader );
- this.set( program, {
- shaderGPU: shader
- } );
- }
- /**
- * Destroys the shader program of the given programmable stage.
- *
- * @param {ProgrammableStage} program - The programmable stage.
- */
- destroyProgram( program ) {
- this.delete( program );
- }
- /**
- * Creates a render pipeline for the given render object.
- *
- * @param {RenderObject} renderObject - The render object.
- * @param {Array<Promise>} promises - An array of compilation promises which are used in `compileAsync()`.
- */
- createRenderPipeline( renderObject, promises ) {
- const gl = this.gl;
- const pipeline = renderObject.pipeline;
- // Program
- const { fragmentProgram, vertexProgram } = pipeline;
- const programGPU = gl.createProgram();
- const fragmentShader = this.get( fragmentProgram ).shaderGPU;
- const vertexShader = this.get( vertexProgram ).shaderGPU;
- gl.attachShader( programGPU, fragmentShader );
- gl.attachShader( programGPU, vertexShader );
- gl.linkProgram( programGPU );
- this.set( pipeline, {
- programGPU,
- fragmentShader,
- vertexShader
- } );
- if ( promises !== null && this.parallel ) {
- const p = new Promise( ( resolve /*, reject*/ ) => {
- const parallel = this.parallel;
- const checkStatus = () => {
- if ( gl.getProgramParameter( programGPU, parallel.COMPLETION_STATUS_KHR ) ) {
- this._completeCompile( renderObject, pipeline );
- resolve();
- } else {
- requestAnimationFrame( checkStatus );
- }
- };
- checkStatus();
- } );
- promises.push( p );
- return;
- }
- this._completeCompile( renderObject, pipeline );
- }
- /**
- * Formats the source code of error messages.
- *
- * @private
- * @param {string} string - The code.
- * @param {number} errorLine - The error line.
- * @return {string} The formatted code.
- */
- _handleSource( string, errorLine ) {
- const lines = string.split( '\n' );
- const lines2 = [];
- const from = Math.max( errorLine - 6, 0 );
- const to = Math.min( errorLine + 6, lines.length );
- for ( let i = from; i < to; i ++ ) {
- const line = i + 1;
- lines2.push( `${line === errorLine ? '>' : ' '} ${line}: ${lines[ i ]}` );
- }
- return lines2.join( '\n' );
- }
- /**
- * Gets the shader compilation errors from the info log.
- *
- * @private
- * @param {WebGL2RenderingContext} gl - The rendering context.
- * @param {WebGLShader} shader - The WebGL shader object.
- * @param {string} type - The shader type.
- * @return {string} The shader errors.
- */
- _getShaderErrors( gl, shader, type ) {
- const status = gl.getShaderParameter( shader, gl.COMPILE_STATUS );
- const shaderInfoLog = gl.getShaderInfoLog( shader ) || '';
- const errors = shaderInfoLog.trim();
- if ( status && errors === '' ) return '';
- const errorMatches = /ERROR: 0:(\d+)/.exec( errors );
- if ( errorMatches ) {
- const errorLine = parseInt( errorMatches[ 1 ] );
- return type.toUpperCase() + '\n\n' + errors + '\n\n' + this._handleSource( gl.getShaderSource( shader ), errorLine );
- } else {
- return errors;
- }
- }
- /**
- * Logs shader compilation errors.
- *
- * @private
- * @param {WebGLProgram} programGPU - The WebGL program.
- * @param {WebGLShader} glFragmentShader - The fragment shader as a native WebGL shader object.
- * @param {WebGLShader} glVertexShader - The vertex shader as a native WebGL shader object.
- */
- _logProgramError( programGPU, glFragmentShader, glVertexShader ) {
- if ( this.renderer.debug.checkShaderErrors ) {
- const gl = this.gl;
- const programInfoLog = gl.getProgramInfoLog( programGPU ) || '';
- const programLog = programInfoLog.trim();
- if ( gl.getProgramParameter( programGPU, gl.LINK_STATUS ) === false ) {
- if ( typeof this.renderer.debug.onShaderError === 'function' ) {
- this.renderer.debug.onShaderError( gl, programGPU, glVertexShader, glFragmentShader );
- } else {
- // default error reporting
- const vertexErrors = this._getShaderErrors( gl, glVertexShader, 'vertex' );
- const fragmentErrors = this._getShaderErrors( gl, glFragmentShader, 'fragment' );
- error(
- 'THREE.WebGLProgram: Shader Error ' + gl.getError() + ' - ' +
- 'VALIDATE_STATUS ' + gl.getProgramParameter( programGPU, gl.VALIDATE_STATUS ) + '\n\n' +
- 'Program Info Log: ' + programLog + '\n' +
- vertexErrors + '\n' +
- fragmentErrors
- );
- }
- } else if ( programLog !== '' ) {
- warn( 'WebGLProgram: Program Info Log:', programLog );
- }
- }
- }
- /**
- * Completes the shader program setup for the given render object.
- *
- * @private
- * @param {RenderObject} renderObject - The render object.
- * @param {RenderPipeline} pipeline - The render pipeline.
- */
- _completeCompile( renderObject, pipeline ) {
- const { state, gl } = this;
- const pipelineData = this.get( pipeline );
- const { programGPU, fragmentShader, vertexShader } = pipelineData;
- if ( gl.getProgramParameter( programGPU, gl.LINK_STATUS ) === false ) {
- this._logProgramError( programGPU, fragmentShader, vertexShader );
- }
- state.useProgram( programGPU );
- // Bindings
- const bindings = renderObject.getBindings();
- this._setupBindings( bindings, programGPU );
- //
- this.set( pipeline, {
- programGPU
- } );
- }
- /**
- * Creates a compute pipeline for the given compute node.
- *
- * @param {ComputePipeline} computePipeline - The compute pipeline.
- * @param {Array<BindGroup>} bindings - The bindings.
- */
- createComputePipeline( computePipeline, bindings ) {
- const { state, gl } = this;
- // Program
- const fragmentProgram = {
- stage: 'fragment',
- code: '#version 300 es\nprecision highp float;\nvoid main() {}'
- };
- this.createProgram( fragmentProgram );
- const { computeProgram } = computePipeline;
- const programGPU = gl.createProgram();
- const fragmentShader = this.get( fragmentProgram ).shaderGPU;
- const vertexShader = this.get( computeProgram ).shaderGPU;
- const transforms = computeProgram.transforms;
- const transformVaryingNames = [];
- const transformAttributeNodes = [];
- for ( let i = 0; i < transforms.length; i ++ ) {
- const transform = transforms[ i ];
- transformVaryingNames.push( transform.varyingName );
- transformAttributeNodes.push( transform.attributeNode );
- }
- gl.attachShader( programGPU, fragmentShader );
- gl.attachShader( programGPU, vertexShader );
- gl.transformFeedbackVaryings(
- programGPU,
- transformVaryingNames,
- gl.SEPARATE_ATTRIBS
- );
- gl.linkProgram( programGPU );
- if ( gl.getProgramParameter( programGPU, gl.LINK_STATUS ) === false ) {
- this._logProgramError( programGPU, fragmentShader, vertexShader );
- }
- state.useProgram( programGPU );
- // Bindings
- this._setupBindings( bindings, programGPU );
- const attributeNodes = computeProgram.attributes;
- const attributes = [];
- const transformBuffers = [];
- for ( let i = 0; i < attributeNodes.length; i ++ ) {
- const attribute = attributeNodes[ i ].node.attribute;
- attributes.push( attribute );
- if ( ! this.has( attribute ) ) this.attributeUtils.createAttribute( attribute, gl.ARRAY_BUFFER );
- }
- for ( let i = 0; i < transformAttributeNodes.length; i ++ ) {
- const attribute = transformAttributeNodes[ i ].attribute;
- if ( ! this.has( attribute ) ) this.attributeUtils.createAttribute( attribute, gl.ARRAY_BUFFER );
- const attributeData = this.get( attribute );
- transformBuffers.push( attributeData );
- }
- //
- this.set( computePipeline, {
- programGPU,
- transformBuffers,
- attributes
- } );
- }
- /**
- * Creates bindings from the given bind group definition.
- *
- * @param {BindGroup} bindGroup - The bind group.
- * @param {Array<BindGroup>} bindings - Array of bind groups.
- * @param {number} cacheIndex - The cache index.
- * @param {number} version - The version.
- */
- createBindings( bindGroup, bindings /*, cacheIndex, version*/ ) {
- if ( this._knownBindings.has( bindings ) === false ) {
- this._knownBindings.add( bindings );
- let uniformBuffers = 0;
- let textures = 0;
- for ( const bindGroup of bindings ) {
- this.set( bindGroup, {
- textures: textures,
- uniformBuffers: uniformBuffers
- } );
- for ( const binding of bindGroup.bindings ) {
- if ( binding.isUniformBuffer ) uniformBuffers ++;
- if ( binding.isSampledTexture ) textures ++;
- }
- }
- }
- this.updateBindings( bindGroup, bindings );
- }
- /**
- * Updates the given bind group definition.
- *
- * @param {BindGroup} bindGroup - The bind group.
- * @param {Array<BindGroup>} bindings - Array of bind groups.
- * @param {number} cacheIndex - The cache index.
- * @param {number} version - The version.
- */
- updateBindings( bindGroup /*, bindings, cacheIndex, version*/ ) {
- const { gl } = this;
- const bindGroupData = this.get( bindGroup );
- let i = bindGroupData.uniformBuffers;
- let t = bindGroupData.textures;
- for ( const binding of bindGroup.bindings ) {
- const map = this.get( binding );
- if ( binding.isUniformsGroup || binding.isUniformBuffer ) {
- const array = binding.buffer;
- let { bufferGPU } = this.get( array );
- if ( bufferGPU === undefined ) {
- // create
- bufferGPU = gl.createBuffer();
- gl.bindBuffer( gl.UNIFORM_BUFFER, bufferGPU );
- gl.bufferData( gl.UNIFORM_BUFFER, array.byteLength, gl.DYNAMIC_DRAW );
- this.set( array, { bufferGPU } );
- } else {
- gl.bindBuffer( gl.UNIFORM_BUFFER, bufferGPU );
- }
- // update
- const updateRanges = binding.updateRanges;
- gl.bindBuffer( gl.UNIFORM_BUFFER, bufferGPU );
- if ( updateRanges.length === 0 ) {
- gl.bufferData( gl.UNIFORM_BUFFER, array, gl.DYNAMIC_DRAW );
- } else {
- const isTyped = isTypedArray( array );
- const byteOffsetFactor = isTyped ? 1 : array.BYTES_PER_ELEMENT;
- for ( let i = 0, l = updateRanges.length; i < l; i ++ ) {
- const range = updateRanges[ i ];
- const dataOffset = range.start * byteOffsetFactor;
- const size = range.count * byteOffsetFactor;
- const bufferOffset = dataOffset * ( isTyped ? array.BYTES_PER_ELEMENT : 1 ); // bufferOffset is always in bytes
- gl.bufferSubData( gl.UNIFORM_BUFFER, bufferOffset, array, dataOffset, size );
- }
- }
- map.index = i ++;
- map.bufferGPU = bufferGPU;
- this.set( binding, map );
- } else if ( binding.isSampledTexture ) {
- const { textureGPU, glTextureType } = this.get( binding.texture );
- map.index = t ++;
- map.textureGPU = textureGPU;
- map.glTextureType = glTextureType;
- this.set( binding, map );
- }
- }
- }
- /**
- * Updates a buffer binding.
- *
- * @param {Buffer} binding - The buffer binding to update.
- */
- updateBinding( binding ) {
- const gl = this.gl;
- if ( binding.isUniformsGroup || binding.isUniformBuffer ) {
- const bindingData = this.get( binding );
- const bufferGPU = bindingData.bufferGPU;
- const array = binding.buffer;
- const updateRanges = binding.updateRanges;
- gl.bindBuffer( gl.UNIFORM_BUFFER, bufferGPU );
- if ( updateRanges.length === 0 ) {
- gl.bufferData( gl.UNIFORM_BUFFER, array, gl.DYNAMIC_DRAW );
- } else {
- const isTyped = isTypedArray( array );
- const byteOffsetFactor = isTyped ? 1 : array.BYTES_PER_ELEMENT;
- for ( let i = 0, l = updateRanges.length; i < l; i ++ ) {
- const range = updateRanges[ i ];
- const dataOffset = range.start * byteOffsetFactor;
- const size = range.count * byteOffsetFactor;
- const bufferOffset = dataOffset * ( isTyped ? array.BYTES_PER_ELEMENT : 1 ); // bufferOffset is always in bytes
- gl.bufferSubData( gl.UNIFORM_BUFFER, bufferOffset, array, dataOffset, size );
- }
- }
- }
- }
- // attributes
- /**
- * Creates the GPU buffer of an indexed shader attribute.
- *
- * @param {BufferAttribute} attribute - The indexed buffer attribute.
- */
- createIndexAttribute( attribute ) {
- const gl = this.gl;
- this.attributeUtils.createAttribute( attribute, gl.ELEMENT_ARRAY_BUFFER );
- }
- /**
- * Creates the GPU buffer of a shader attribute.
- *
- * @param {BufferAttribute} attribute - The buffer attribute.
- */
- createAttribute( attribute ) {
- if ( this.has( attribute ) ) return;
- const gl = this.gl;
- this.attributeUtils.createAttribute( attribute, gl.ARRAY_BUFFER );
- }
- /**
- * Creates the GPU buffer of a storage attribute.
- *
- * @param {BufferAttribute} attribute - The buffer attribute.
- */
- createStorageAttribute( attribute ) {
- if ( this.has( attribute ) ) return;
- const gl = this.gl;
- this.attributeUtils.createAttribute( attribute, gl.ARRAY_BUFFER );
- }
- /**
- * Updates the GPU buffer of a shader attribute.
- *
- * @param {BufferAttribute} attribute - The buffer attribute to update.
- */
- updateAttribute( attribute ) {
- this.attributeUtils.updateAttribute( attribute );
- }
- /**
- * Destroys the GPU buffer of a shader attribute.
- *
- * @param {BufferAttribute} attribute - The buffer attribute to destroy.
- */
- destroyAttribute( attribute ) {
- this.attributeUtils.destroyAttribute( attribute );
- }
- /**
- * Checks if the given feature is supported by the backend.
- *
- * @param {string} name - The feature's name.
- * @return {boolean} Whether the feature is supported or not.
- */
- hasFeature( name ) {
- const keysMatching = Object.keys( GLFeatureName ).filter( key => GLFeatureName[ key ] === name );
- const extensions = this.extensions;
- for ( let i = 0; i < keysMatching.length; i ++ ) {
- if ( extensions.has( keysMatching[ i ] ) ) return true;
- }
- return false;
- }
- /**
- * Returns the maximum anisotropy texture filtering value.
- *
- * @return {number} The maximum anisotropy texture filtering value.
- */
- getMaxAnisotropy() {
- return this.capabilities.getMaxAnisotropy();
- }
- /**
- * Copies data of the given source texture to the given destination texture.
- *
- * @param {Texture} srcTexture - The source texture.
- * @param {Texture} dstTexture - The destination texture.
- * @param {?(Box3|Box2)} [srcRegion=null] - The region of the source texture to copy.
- * @param {?(Vector2|Vector3)} [dstPosition=null] - The destination position of the copy.
- * @param {number} [srcLevel=0] - The source mip level to copy from.
- * @param {number} [dstLevel=0] - The destination mip level to copy to.
- */
- copyTextureToTexture( srcTexture, dstTexture, srcRegion = null, dstPosition = null, srcLevel = 0, dstLevel = 0 ) {
- this.textureUtils.copyTextureToTexture( srcTexture, dstTexture, srcRegion, dstPosition, srcLevel, dstLevel );
- }
- /**
- * Copies the current bound framebuffer to the given texture.
- *
- * @param {Texture} texture - The destination texture.
- * @param {RenderContext} renderContext - The render context.
- * @param {Vector4} rectangle - A four dimensional vector defining the origin and dimension of the copy.
- */
- copyFramebufferToTexture( texture, renderContext, rectangle ) {
- this.textureUtils.copyFramebufferToTexture( texture, renderContext, rectangle );
- }
- /**
- * Configures the active framebuffer from the given render context.
- *
- * @private
- * @param {RenderContext} descriptor - The render context.
- */
- _setFramebuffer( descriptor ) {
- const { gl, state } = this;
- let currentFrameBuffer = null;
- if ( descriptor.textures !== null ) {
- const renderTarget = descriptor.renderTarget;
- const renderTargetContextData = this.get( renderTarget );
- const { samples, depthBuffer, stencilBuffer } = renderTarget;
- const isCube = renderTarget.isWebGLCubeRenderTarget === true;
- const isRenderTarget3D = renderTarget.isRenderTarget3D === true;
- const isRenderTargetArray = renderTarget.depth > 1;
- const isXRRenderTarget = renderTarget.isXRRenderTarget === true;
- const _hasExternalTextures = ( isXRRenderTarget === true && renderTarget._hasExternalTextures === true );
- let msaaFb = renderTargetContextData.msaaFrameBuffer;
- let depthRenderbuffer = renderTargetContextData.depthRenderbuffer;
- const multisampledRTTExt = this.extensions.get( 'WEBGL_multisampled_render_to_texture' );
- const multiviewExt = this.extensions.get( 'OVR_multiview2' );
- const useMultisampledRTT = this._useMultisampledExtension( renderTarget );
- const cacheKey = getCacheKey( descriptor );
- let fb;
- if ( isCube ) {
- renderTargetContextData.cubeFramebuffers || ( renderTargetContextData.cubeFramebuffers = {} );
- fb = renderTargetContextData.cubeFramebuffers[ cacheKey ];
- } else if ( isXRRenderTarget && _hasExternalTextures === false ) {
- fb = this._xrFramebuffer;
- } else {
- renderTargetContextData.framebuffers || ( renderTargetContextData.framebuffers = {} );
- fb = renderTargetContextData.framebuffers[ cacheKey ];
- }
- if ( fb === undefined ) {
- fb = gl.createFramebuffer();
- state.bindFramebuffer( gl.FRAMEBUFFER, fb );
- const textures = descriptor.textures;
- const depthInvalidationArray = [];
- if ( isCube ) {
- renderTargetContextData.cubeFramebuffers[ cacheKey ] = fb;
- const { textureGPU } = this.get( textures[ 0 ] );
- const cubeFace = this.renderer._activeCubeFace;
- const mipLevel = this.renderer._activeMipmapLevel;
- gl.framebufferTexture2D( gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_CUBE_MAP_POSITIVE_X + cubeFace, textureGPU, mipLevel );
- } else {
- renderTargetContextData.framebuffers[ cacheKey ] = fb;
- for ( let i = 0; i < textures.length; i ++ ) {
- const texture = textures[ i ];
- const textureData = this.get( texture );
- textureData.renderTarget = descriptor.renderTarget;
- textureData.cacheKey = cacheKey; // required for copyTextureToTexture()
- const attachment = gl.COLOR_ATTACHMENT0 + i;
- if ( renderTarget.multiview ) {
- multiviewExt.framebufferTextureMultisampleMultiviewOVR( gl.FRAMEBUFFER, attachment, textureData.textureGPU, 0, samples, 0, 2 );
- } else if ( isRenderTarget3D || isRenderTargetArray ) {
- const layer = this.renderer._activeCubeFace;
- const mipLevel = this.renderer._activeMipmapLevel;
- gl.framebufferTextureLayer( gl.FRAMEBUFFER, attachment, textureData.textureGPU, mipLevel, layer );
- } else {
- if ( useMultisampledRTT ) {
- multisampledRTTExt.framebufferTexture2DMultisampleEXT( gl.FRAMEBUFFER, attachment, gl.TEXTURE_2D, textureData.textureGPU, 0, samples );
- } else {
- const mipLevel = this.renderer._activeMipmapLevel;
- gl.framebufferTexture2D( gl.FRAMEBUFFER, attachment, gl.TEXTURE_2D, textureData.textureGPU, mipLevel );
- }
- }
- }
- }
- const depthStyle = stencilBuffer ? gl.DEPTH_STENCIL_ATTACHMENT : gl.DEPTH_ATTACHMENT;
- if ( renderTarget._autoAllocateDepthBuffer === true ) {
- const renderbuffer = gl.createRenderbuffer();
- this.textureUtils.setupRenderBufferStorage( renderbuffer, descriptor, 0, useMultisampledRTT );
- renderTargetContextData.xrDepthRenderbuffer = renderbuffer;
- depthInvalidationArray.push( stencilBuffer ? gl.DEPTH_STENCIL_ATTACHMENT : gl.DEPTH_ATTACHMENT );
- gl.bindRenderbuffer( gl.RENDERBUFFER, renderbuffer );
- gl.framebufferRenderbuffer( gl.FRAMEBUFFER, depthStyle, gl.RENDERBUFFER, renderbuffer );
- } else {
- if ( descriptor.depthTexture !== null ) {
- depthInvalidationArray.push( stencilBuffer ? gl.DEPTH_STENCIL_ATTACHMENT : gl.DEPTH_ATTACHMENT );
- const textureData = this.get( descriptor.depthTexture );
- textureData.renderTarget = descriptor.renderTarget;
- textureData.cacheKey = cacheKey; // required for copyTextureToTexture()
- if ( renderTarget.multiview ) {
- multiviewExt.framebufferTextureMultisampleMultiviewOVR( gl.FRAMEBUFFER, depthStyle, textureData.textureGPU, 0, samples, 0, 2 );
- } else if ( _hasExternalTextures && useMultisampledRTT ) {
- multisampledRTTExt.framebufferTexture2DMultisampleEXT( gl.FRAMEBUFFER, depthStyle, gl.TEXTURE_2D, textureData.textureGPU, 0, samples );
- } else {
- if ( descriptor.depthTexture.isArrayTexture ) {
- const layer = this.renderer._activeCubeFace;
- gl.framebufferTextureLayer( gl.FRAMEBUFFER, depthStyle, textureData.textureGPU, 0, layer );
- } else if ( descriptor.depthTexture.isCubeTexture ) {
- const cubeFace = this.renderer._activeCubeFace;
- gl.framebufferTexture2D( gl.FRAMEBUFFER, depthStyle, gl.TEXTURE_CUBE_MAP_POSITIVE_X + cubeFace, textureData.textureGPU, 0 );
- } else {
- gl.framebufferTexture2D( gl.FRAMEBUFFER, depthStyle, gl.TEXTURE_2D, textureData.textureGPU, 0 );
- }
- }
- }
- }
- renderTargetContextData.depthInvalidationArray = depthInvalidationArray;
- } else {
- const isRenderCameraDepthArray = this._isRenderCameraDepthArray( descriptor );
- if ( isRenderCameraDepthArray ) {
- state.bindFramebuffer( gl.FRAMEBUFFER, fb );
- const layer = this.renderer._activeCubeFace;
- const depthData = this.get( descriptor.depthTexture );
- const depthStyle = stencilBuffer ? gl.DEPTH_STENCIL_ATTACHMENT : gl.DEPTH_ATTACHMENT;
- gl.framebufferTextureLayer(
- gl.FRAMEBUFFER,
- depthStyle,
- depthData.textureGPU,
- 0,
- layer
- );
- }
- // rebind external XR textures
- if ( ( isXRRenderTarget || useMultisampledRTT || renderTarget.multiview ) && ( renderTarget._isOpaqueFramebuffer !== true ) ) {
- state.bindFramebuffer( gl.FRAMEBUFFER, fb );
- // rebind color
- const textureData = this.get( descriptor.textures[ 0 ] );
- if ( renderTarget.multiview ) {
- multiviewExt.framebufferTextureMultisampleMultiviewOVR( gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, textureData.textureGPU, 0, samples, 0, 2 );
- } else if ( useMultisampledRTT ) {
- multisampledRTTExt.framebufferTexture2DMultisampleEXT( gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, textureData.textureGPU, 0, samples );
- } else {
- gl.framebufferTexture2D( gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, textureData.textureGPU, 0 );
- }
- // rebind depth
- const depthStyle = stencilBuffer ? gl.DEPTH_STENCIL_ATTACHMENT : gl.DEPTH_ATTACHMENT;
- if ( renderTarget._autoAllocateDepthBuffer === true ) {
- const renderbuffer = renderTargetContextData.xrDepthRenderbuffer;
- gl.bindRenderbuffer( gl.RENDERBUFFER, renderbuffer );
- gl.framebufferRenderbuffer( gl.FRAMEBUFFER, depthStyle, gl.RENDERBUFFER, renderbuffer );
- } else {
- const textureData = this.get( descriptor.depthTexture );
- if ( renderTarget.multiview ) {
- multiviewExt.framebufferTextureMultisampleMultiviewOVR( gl.FRAMEBUFFER, depthStyle, textureData.textureGPU, 0, samples, 0, 2 );
- } else if ( useMultisampledRTT ) {
- multisampledRTTExt.framebufferTexture2DMultisampleEXT( gl.FRAMEBUFFER, depthStyle, gl.TEXTURE_2D, textureData.textureGPU, 0, samples );
- } else {
- gl.framebufferTexture2D( gl.FRAMEBUFFER, depthStyle, gl.TEXTURE_2D, textureData.textureGPU, 0 );
- }
- }
- }
- }
- if ( samples > 0 && useMultisampledRTT === false && ! renderTarget.multiview ) {
- if ( msaaFb === undefined ) {
- const invalidationArray = [];
- msaaFb = gl.createFramebuffer();
- state.bindFramebuffer( gl.FRAMEBUFFER, msaaFb );
- const msaaRenderbuffers = [];
- const textures = descriptor.textures;
- for ( let i = 0; i < textures.length; i ++ ) {
- msaaRenderbuffers[ i ] = gl.createRenderbuffer();
- gl.bindRenderbuffer( gl.RENDERBUFFER, msaaRenderbuffers[ i ] );
- invalidationArray.push( gl.COLOR_ATTACHMENT0 + i );
- const texture = descriptor.textures[ i ];
- const textureData = this.get( texture );
- gl.renderbufferStorageMultisample( gl.RENDERBUFFER, samples, textureData.glInternalFormat, descriptor.width, descriptor.height );
- gl.framebufferRenderbuffer( gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + i, gl.RENDERBUFFER, msaaRenderbuffers[ i ] );
- }
- gl.bindRenderbuffer( gl.RENDERBUFFER, null );
- renderTargetContextData.msaaFrameBuffer = msaaFb;
- renderTargetContextData.msaaRenderbuffers = msaaRenderbuffers;
- if ( depthBuffer && depthRenderbuffer === undefined ) {
- depthRenderbuffer = gl.createRenderbuffer();
- this.textureUtils.setupRenderBufferStorage( depthRenderbuffer, descriptor, samples );
- renderTargetContextData.depthRenderbuffer = depthRenderbuffer;
- const depthStyle = stencilBuffer ? gl.DEPTH_STENCIL_ATTACHMENT : gl.DEPTH_ATTACHMENT;
- invalidationArray.push( depthStyle );
- }
- renderTargetContextData.invalidationArray = invalidationArray;
- }
- currentFrameBuffer = renderTargetContextData.msaaFrameBuffer;
- } else {
- currentFrameBuffer = fb;
- }
- state.drawBuffers( descriptor, fb );
- }
- state.bindFramebuffer( gl.FRAMEBUFFER, currentFrameBuffer );
- }
- /**
- * Computes the VAO key for the given index and attributes.
- *
- * @private
- * @param {Array<BufferAttribute>} attributes - An array of buffer attributes.
- * @return {string} The VAO key.
- */
- _getVaoKey( attributes ) {
- let key = '';
- for ( let i = 0; i < attributes.length; i ++ ) {
- const attributeData = this.get( attributes[ i ] );
- key += ':' + attributeData.id;
- }
- return key;
- }
- /**
- * Creates a VAO from the index and attributes.
- *
- * @private
- * @param {Array<BufferAttribute>} attributes - An array of buffer attributes.
- * @return {Object} The VAO data.
- */
- _createVao( attributes ) {
- const { gl } = this;
- const vaoGPU = gl.createVertexArray();
- gl.bindVertexArray( vaoGPU );
- for ( let i = 0; i < attributes.length; i ++ ) {
- const attribute = attributes[ i ];
- const attributeData = this.get( attribute );
- gl.bindBuffer( gl.ARRAY_BUFFER, attributeData.bufferGPU );
- gl.enableVertexAttribArray( i );
- let stride, offset;
- if ( attribute.isInterleavedBufferAttribute === true ) {
- stride = attribute.data.stride * attributeData.bytesPerElement;
- offset = attribute.offset * attributeData.bytesPerElement;
- } else {
- stride = 0;
- offset = 0;
- }
- if ( attributeData.isInteger ) {
- gl.vertexAttribIPointer( i, attribute.itemSize, attributeData.type, stride, offset );
- } else {
- gl.vertexAttribPointer( i, attribute.itemSize, attributeData.type, attribute.normalized, stride, offset );
- }
- if ( attribute.isInstancedBufferAttribute && ! attribute.isInterleavedBufferAttribute ) {
- gl.vertexAttribDivisor( i, attribute.meshPerAttribute );
- } else if ( attribute.isInterleavedBufferAttribute && attribute.data.isInstancedInterleavedBuffer ) {
- gl.vertexAttribDivisor( i, attribute.data.meshPerAttribute );
- }
- }
- gl.bindBuffer( gl.ARRAY_BUFFER, null );
- return vaoGPU;
- }
- /**
- * Creates a transform feedback from the given transform buffers.
- *
- * @private
- * @param {Array<DualAttributeData>} transformBuffers - The transform buffers.
- * @return {WebGLTransformFeedback} The transform feedback.
- */
- _getTransformFeedback( transformBuffers ) {
- let key = '';
- for ( let i = 0; i < transformBuffers.length; i ++ ) {
- key += ':' + transformBuffers[ i ].id;
- }
- let transformFeedbackGPU = this.transformFeedbackCache[ key ];
- if ( transformFeedbackGPU !== undefined ) {
- return transformFeedbackGPU;
- }
- const { gl } = this;
- transformFeedbackGPU = gl.createTransformFeedback();
- gl.bindTransformFeedback( gl.TRANSFORM_FEEDBACK, transformFeedbackGPU );
- for ( let i = 0; i < transformBuffers.length; i ++ ) {
- const attributeData = transformBuffers[ i ];
- gl.bindBufferBase( gl.TRANSFORM_FEEDBACK_BUFFER, i, attributeData.transformBuffer );
- }
- gl.bindTransformFeedback( gl.TRANSFORM_FEEDBACK, null );
- this.transformFeedbackCache[ key ] = transformFeedbackGPU;
- return transformFeedbackGPU;
- }
- /**
- * Setups the given bindings.
- *
- * @private
- * @param {Array<BindGroup>} bindings - The bindings.
- * @param {WebGLProgram} programGPU - The WebGL program.
- */
- _setupBindings( bindings, programGPU ) {
- const gl = this.gl;
- for ( const bindGroup of bindings ) {
- for ( const binding of bindGroup.bindings ) {
- const bindingData = this.get( binding );
- const index = bindingData.index;
- if ( binding.isUniformsGroup || binding.isUniformBuffer ) {
- const location = gl.getUniformBlockIndex( programGPU, binding.name );
- gl.uniformBlockBinding( programGPU, location, index );
- } else if ( binding.isSampledTexture ) {
- const location = gl.getUniformLocation( programGPU, binding.name );
- gl.uniform1i( location, index );
- }
- }
- }
- }
- /**
- * Binds the given uniforms.
- *
- * @private
- * @param {Array<BindGroup>} bindings - The bindings.
- */
- _bindUniforms( bindings ) {
- const { gl, state } = this;
- for ( const bindGroup of bindings ) {
- for ( const binding of bindGroup.bindings ) {
- const bindingData = this.get( binding );
- const index = bindingData.index;
- if ( binding.isUniformsGroup || binding.isUniformBuffer ) {
- // TODO USE bindBufferRange to group multiple uniform buffers
- state.bindBufferBase( gl.UNIFORM_BUFFER, index, bindingData.bufferGPU );
- } else if ( binding.isSampledTexture ) {
- state.bindTexture( bindingData.glTextureType, bindingData.textureGPU, gl.TEXTURE0 + index );
- }
- }
- }
- }
- /**
- * The method ensures multisampled render targets are resolved.
- *
- * @private
- * @param {RenderContext} renderContext - The render context.
- */
- _resolveRenderTarget( renderContext ) {
- const { gl, state } = this;
- const renderTarget = renderContext.renderTarget;
- if ( renderContext.textures !== null && renderTarget ) {
- const renderTargetContextData = this.get( renderTarget );
- if ( renderTarget.samples > 0 && this._useMultisampledExtension( renderTarget ) === false ) {
- const fb = renderTargetContextData.framebuffers[ renderContext.getCacheKey() ];
- let mask = gl.COLOR_BUFFER_BIT;
- if ( renderTarget.resolveDepthBuffer ) {
- if ( renderTarget.depthBuffer ) mask |= gl.DEPTH_BUFFER_BIT;
- if ( renderTarget.stencilBuffer && renderTarget.resolveStencilBuffer ) mask |= gl.STENCIL_BUFFER_BIT;
- }
- const msaaFrameBuffer = renderTargetContextData.msaaFrameBuffer;
- const msaaRenderbuffers = renderTargetContextData.msaaRenderbuffers;
- const textures = renderContext.textures;
- const isMRT = textures.length > 1;
- state.bindFramebuffer( gl.READ_FRAMEBUFFER, msaaFrameBuffer );
- state.bindFramebuffer( gl.DRAW_FRAMEBUFFER, fb );
- if ( isMRT ) {
- // blitFramebuffer() can only copy/resolve the first color attachment of a framebuffer. When using MRT,
- // the engine temporarily removes all attachments and then configures each attachment for the resolve.
- for ( let i = 0; i < textures.length; i ++ ) {
- gl.framebufferRenderbuffer( gl.READ_FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + i, gl.RENDERBUFFER, null );
- gl.framebufferTexture2D( gl.DRAW_FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + i, gl.TEXTURE_2D, null, 0 );
- }
- }
- for ( let i = 0; i < textures.length; i ++ ) {
- if ( isMRT ) {
- // configure attachment for resolve
- const { textureGPU } = this.get( textures[ i ] );
- gl.framebufferRenderbuffer( gl.READ_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, msaaRenderbuffers[ i ] );
- gl.framebufferTexture2D( gl.DRAW_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, textureGPU, 0 );
- }
- if ( renderContext.scissor ) {
- const { x, y, width, height } = renderContext.scissorValue;
- const viewY = renderContext.height - height - y;
- gl.blitFramebuffer( x, viewY, x + width, viewY + height, x, viewY, x + width, viewY + height, mask, gl.NEAREST );
- } else {
- gl.blitFramebuffer( 0, 0, renderContext.width, renderContext.height, 0, 0, renderContext.width, renderContext.height, mask, gl.NEAREST );
- }
- }
- if ( isMRT ) {
- // restore attachments
- for ( let i = 0; i < textures.length; i ++ ) {
- const { textureGPU } = this.get( textures[ i ] );
- gl.framebufferRenderbuffer( gl.READ_FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + i, gl.RENDERBUFFER, msaaRenderbuffers[ i ] );
- gl.framebufferTexture2D( gl.DRAW_FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + i, gl.TEXTURE_2D, textureGPU, 0 );
- }
- }
- if ( this._supportsInvalidateFramebuffer === true ) {
- gl.invalidateFramebuffer( gl.READ_FRAMEBUFFER, renderTargetContextData.invalidationArray );
- }
- } else if ( renderTarget.resolveDepthBuffer === false && renderTargetContextData.framebuffers ) {
- const fb = renderTargetContextData.framebuffers[ renderContext.getCacheKey() ];
- state.bindFramebuffer( gl.DRAW_FRAMEBUFFER, fb );
- gl.invalidateFramebuffer( gl.DRAW_FRAMEBUFFER, renderTargetContextData.depthInvalidationArray );
- }
- }
- }
- /**
- * Returns `true` if the `WEBGL_multisampled_render_to_texture` extension
- * should be used when MSAA is enabled.
- *
- * @private
- * @param {RenderTarget} renderTarget - The render target that should be multisampled.
- * @return {boolean} Whether to use the `WEBGL_multisampled_render_to_texture` extension for MSAA or not.
- */
- _useMultisampledExtension( renderTarget ) {
- if ( renderTarget.multiview === true ) {
- return true;
- }
- return renderTarget.samples > 0 && this.extensions.has( 'WEBGL_multisampled_render_to_texture' ) === true && renderTarget._autoAllocateDepthBuffer !== false;
- }
- /**
- * Frees internal resources.
- */
- dispose() {
- if ( this.textureUtils !== null ) this.textureUtils.dispose();
- const extension = this.extensions.get( 'WEBGL_lose_context' );
- if ( extension ) extension.loseContext();
- this.renderer.domElement.removeEventListener( 'webglcontextlost', this._onContextLost );
- }
- }
- const GPUPrimitiveTopology = {
- PointList: 'point-list',
- LineList: 'line-list',
- LineStrip: 'line-strip',
- TriangleList: 'triangle-list',
- TriangleStrip: 'triangle-strip',
- };
- const GPUShaderStage = ( typeof self !== 'undefined' ) ? self.GPUShaderStage : { VERTEX: 1, FRAGMENT: 2, COMPUTE: 4 };
- const GPUCompareFunction = {
- Never: 'never',
- Less: 'less',
- Equal: 'equal',
- LessEqual: 'less-equal',
- Greater: 'greater',
- NotEqual: 'not-equal',
- GreaterEqual: 'greater-equal',
- Always: 'always'
- };
- const GPUStoreOp = {
- Store: 'store'};
- const GPULoadOp = {
- Load: 'load',
- Clear: 'clear'
- };
- const GPUFrontFace = {
- CCW: 'ccw',
- CW: 'cw'
- };
- const GPUCullMode = {
- None: 'none',
- Back: 'back'
- };
- const GPUIndexFormat = {
- Uint16: 'uint16',
- Uint32: 'uint32'
- };
- const GPUTextureFormat = {
- // 8-bit formats
- R8Unorm: 'r8unorm',
- R8Snorm: 'r8snorm',
- R8Uint: 'r8uint',
- R8Sint: 'r8sint',
- // 16-bit formats
- R16Uint: 'r16uint',
- R16Sint: 'r16sint',
- R16Float: 'r16float',
- RG8Unorm: 'rg8unorm',
- RG8Snorm: 'rg8snorm',
- RG8Uint: 'rg8uint',
- RG8Sint: 'rg8sint',
- // 32-bit formats
- R32Uint: 'r32uint',
- R32Sint: 'r32sint',
- R32Float: 'r32float',
- RG16Uint: 'rg16uint',
- RG16Sint: 'rg16sint',
- RG16Float: 'rg16float',
- RGBA8Unorm: 'rgba8unorm',
- RGBA8UnormSRGB: 'rgba8unorm-srgb',
- RGBA8Snorm: 'rgba8snorm',
- RGBA8Uint: 'rgba8uint',
- RGBA8Sint: 'rgba8sint',
- BGRA8Unorm: 'bgra8unorm',
- BGRA8UnormSRGB: 'bgra8unorm-srgb',
- // Packed 32-bit formats
- RGB9E5UFloat: 'rgb9e5ufloat',
- RGB10A2Unorm: 'rgb10a2unorm',
- RG11B10UFloat: 'rg11b10ufloat',
- // 64-bit formats
- RG32Uint: 'rg32uint',
- RG32Sint: 'rg32sint',
- RG32Float: 'rg32float',
- RGBA16Uint: 'rgba16uint',
- RGBA16Sint: 'rgba16sint',
- RGBA16Float: 'rgba16float',
- // 128-bit formats
- RGBA32Uint: 'rgba32uint',
- RGBA32Sint: 'rgba32sint',
- RGBA32Float: 'rgba32float',
- Depth16Unorm: 'depth16unorm',
- Depth24Plus: 'depth24plus',
- Depth24PlusStencil8: 'depth24plus-stencil8',
- Depth32Float: 'depth32float',
- // 'depth32float-stencil8' extension
- Depth32FloatStencil8: 'depth32float-stencil8',
- // BC compressed formats usable if 'texture-compression-bc' is both
- // supported by the device/user agent and enabled in requestDevice.
- BC1RGBAUnorm: 'bc1-rgba-unorm',
- BC1RGBAUnormSRGB: 'bc1-rgba-unorm-srgb',
- BC2RGBAUnorm: 'bc2-rgba-unorm',
- BC2RGBAUnormSRGB: 'bc2-rgba-unorm-srgb',
- BC3RGBAUnorm: 'bc3-rgba-unorm',
- BC3RGBAUnormSRGB: 'bc3-rgba-unorm-srgb',
- BC4RUnorm: 'bc4-r-unorm',
- BC4RSnorm: 'bc4-r-snorm',
- BC5RGUnorm: 'bc5-rg-unorm',
- BC5RGSnorm: 'bc5-rg-snorm',
- BC6HRGBUFloat: 'bc6h-rgb-ufloat',
- BC6HRGBFloat: 'bc6h-rgb-float',
- BC7RGBAUnorm: 'bc7-rgba-unorm',
- BC7RGBAUnormSRGB: 'bc7-rgba-unorm-srgb',
- // ETC2 compressed formats usable if 'texture-compression-etc2' is both
- // supported by the device/user agent and enabled in requestDevice.
- ETC2RGB8Unorm: 'etc2-rgb8unorm',
- ETC2RGB8UnormSRGB: 'etc2-rgb8unorm-srgb',
- ETC2RGB8A1Unorm: 'etc2-rgb8a1unorm',
- ETC2RGB8A1UnormSRGB: 'etc2-rgb8a1unorm-srgb',
- ETC2RGBA8Unorm: 'etc2-rgba8unorm',
- ETC2RGBA8UnormSRGB: 'etc2-rgba8unorm-srgb',
- EACR11Unorm: 'eac-r11unorm',
- EACR11Snorm: 'eac-r11snorm',
- EACRG11Unorm: 'eac-rg11unorm',
- EACRG11Snorm: 'eac-rg11snorm',
- // ASTC compressed formats usable if 'texture-compression-astc' is both
- // supported by the device/user agent and enabled in requestDevice.
- ASTC4x4Unorm: 'astc-4x4-unorm',
- ASTC4x4UnormSRGB: 'astc-4x4-unorm-srgb',
- ASTC5x4Unorm: 'astc-5x4-unorm',
- ASTC5x4UnormSRGB: 'astc-5x4-unorm-srgb',
- ASTC5x5Unorm: 'astc-5x5-unorm',
- ASTC5x5UnormSRGB: 'astc-5x5-unorm-srgb',
- ASTC6x5Unorm: 'astc-6x5-unorm',
- ASTC6x5UnormSRGB: 'astc-6x5-unorm-srgb',
- ASTC6x6Unorm: 'astc-6x6-unorm',
- ASTC6x6UnormSRGB: 'astc-6x6-unorm-srgb',
- ASTC8x5Unorm: 'astc-8x5-unorm',
- ASTC8x5UnormSRGB: 'astc-8x5-unorm-srgb',
- ASTC8x6Unorm: 'astc-8x6-unorm',
- ASTC8x6UnormSRGB: 'astc-8x6-unorm-srgb',
- ASTC8x8Unorm: 'astc-8x8-unorm',
- ASTC8x8UnormSRGB: 'astc-8x8-unorm-srgb',
- ASTC10x5Unorm: 'astc-10x5-unorm',
- ASTC10x5UnormSRGB: 'astc-10x5-unorm-srgb',
- ASTC10x6Unorm: 'astc-10x6-unorm',
- ASTC10x6UnormSRGB: 'astc-10x6-unorm-srgb',
- ASTC10x8Unorm: 'astc-10x8-unorm',
- ASTC10x8UnormSRGB: 'astc-10x8-unorm-srgb',
- ASTC10x10Unorm: 'astc-10x10-unorm',
- ASTC10x10UnormSRGB: 'astc-10x10-unorm-srgb',
- ASTC12x10Unorm: 'astc-12x10-unorm',
- ASTC12x10UnormSRGB: 'astc-12x10-unorm-srgb',
- ASTC12x12Unorm: 'astc-12x12-unorm',
- ASTC12x12UnormSRGB: 'astc-12x12-unorm-srgb',
- };
- const GPUAddressMode = {
- ClampToEdge: 'clamp-to-edge',
- Repeat: 'repeat',
- MirrorRepeat: 'mirror-repeat'
- };
- const GPUFilterMode = {
- Linear: 'linear',
- Nearest: 'nearest'
- };
- const GPUBlendFactor = {
- Zero: 'zero',
- One: 'one',
- Src: 'src',
- OneMinusSrc: 'one-minus-src',
- SrcAlpha: 'src-alpha',
- OneMinusSrcAlpha: 'one-minus-src-alpha',
- Dst: 'dst',
- OneMinusDst: 'one-minus-dst',
- DstAlpha: 'dst-alpha',
- OneMinusDstAlpha: 'one-minus-dst-alpha',
- SrcAlphaSaturated: 'src-alpha-saturated',
- Constant: 'constant',
- OneMinusConstant: 'one-minus-constant'
- };
- const GPUBlendOperation = {
- Add: 'add',
- Subtract: 'subtract',
- ReverseSubtract: 'reverse-subtract',
- Min: 'min',
- Max: 'max'
- };
- const GPUColorWriteFlags = {
- None: 0,
- All: 0xF
- };
- const GPUStencilOperation = {
- Keep: 'keep',
- Zero: 'zero',
- Replace: 'replace',
- Invert: 'invert',
- IncrementClamp: 'increment-clamp',
- DecrementClamp: 'decrement-clamp',
- IncrementWrap: 'increment-wrap',
- DecrementWrap: 'decrement-wrap'
- };
- const GPUBufferBindingType = {
- Storage: 'storage',
- ReadOnlyStorage: 'read-only-storage'
- };
- const GPUStorageTextureAccess = {
- WriteOnly: 'write-only',
- ReadOnly: 'read-only',
- ReadWrite: 'read-write',
- };
- const GPUSamplerBindingType = {
- NonFiltering: 'non-filtering',
- Comparison: 'comparison'
- };
- const GPUTextureSampleType = {
- Float: 'float',
- UnfilterableFloat: 'unfilterable-float',
- Depth: 'depth',
- SInt: 'sint',
- UInt: 'uint'
- };
- const GPUTextureDimension = {
- TwoD: '2d',
- ThreeD: '3d'
- };
- const GPUTextureViewDimension = {
- TwoD: '2d',
- TwoDArray: '2d-array',
- Cube: 'cube',
- ThreeD: '3d'
- };
- const GPUTextureAspect = {
- All: 'all'};
- const GPUInputStepMode = {
- Vertex: 'vertex',
- Instance: 'instance'
- };
- const GPUFeatureName = {
- CoreFeaturesAndLimits: 'core-features-and-limits',
- DepthClipControl: 'depth-clip-control',
- Depth32FloatStencil8: 'depth32float-stencil8',
- TextureCompressionBC: 'texture-compression-bc',
- TextureCompressionBCSliced3D: 'texture-compression-bc-sliced-3d',
- TextureCompressionETC2: 'texture-compression-etc2',
- TextureCompressionASTC: 'texture-compression-astc',
- TextureCompressionASTCSliced3D: 'texture-compression-astc-sliced-3d',
- TimestampQuery: 'timestamp-query',
- IndirectFirstInstance: 'indirect-first-instance',
- ShaderF16: 'shader-f16',
- RG11B10UFloat: 'rg11b10ufloat-renderable',
- BGRA8UNormStorage: 'bgra8unorm-storage',
- Float32Filterable: 'float32-filterable',
- Float32Blendable: 'float32-blendable',
- ClipDistances: 'clip-distances',
- DualSourceBlending: 'dual-source-blending',
- Subgroups: 'subgroups',
- TextureFormatsTier1: 'texture-formats-tier1',
- TextureFormatsTier2: 'texture-formats-tier2'
- };
- const GPUFeatureMap = {
- 'texture-compression-s3tc': 'texture-compression-bc',
- 'texture-compression-etc1': 'texture-compression-etc2'
- };
- /**
- * A special form of sampler binding type.
- * It's texture value is managed by a node object.
- *
- * @private
- * @augments Sampler
- */
- class NodeSampler extends Sampler {
- /**
- * Constructs a new node-based sampler.
- *
- * @param {string} name - The samplers's name.
- * @param {TextureNode} textureNode - The texture node.
- * @param {UniformGroupNode} groupNode - The uniform group node.
- */
- constructor( name, textureNode, groupNode ) {
- super( name, textureNode ? textureNode.value : null );
- /**
- * The texture node.
- *
- * @type {TextureNode}
- */
- this.textureNode = textureNode;
- /**
- * The uniform group node.
- *
- * @type {UniformGroupNode}
- */
- this.groupNode = groupNode;
- }
- /**
- * Updates the texture value of this sampler.
- *
- * @return {boolean} Whether the sampler needs an update or not.
- */
- update() {
- const { textureNode } = this;
- if ( this.texture !== textureNode.value ) {
- this.texture = textureNode.value;
- return true;
- }
- return super.update();
- }
- }
- /**
- * Represents a storage buffer binding type.
- *
- * @private
- * @augments Buffer
- */
- class StorageBuffer extends Buffer {
- /**
- * Constructs a new uniform buffer.
- *
- * @param {string} name - The buffer's name.
- * @param {BufferAttribute} attribute - The buffer attribute.
- */
- constructor( name, attribute ) {
- super( name, attribute ? attribute.array : null );
- /**
- * This flag can be used for type testing.
- *
- * @type {BufferAttribute}
- */
- this.attribute = attribute;
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isStorageBuffer = true;
- }
- }
- let _id = 0;
- /**
- * A special form of storage buffer binding type.
- * It's buffer value is managed by a node object.
- *
- * @private
- * @augments StorageBuffer
- */
- class NodeStorageBuffer extends StorageBuffer {
- /**
- * Constructs a new node-based storage buffer.
- *
- * @param {StorageBufferNode} nodeUniform - The storage buffer node.
- * @param {UniformGroupNode} groupNode - The uniform group node.
- */
- constructor( nodeUniform, groupNode ) {
- super( 'StorageBuffer_' + _id ++, nodeUniform ? nodeUniform.value : null );
- /**
- * The node uniform.
- *
- * @type {StorageBufferNode}
- */
- this.nodeUniform = nodeUniform;
- /**
- * The access type.
- *
- * @type {string}
- */
- this.access = nodeUniform ? nodeUniform.access : NodeAccess.READ_WRITE;
- /**
- * The uniform group node.
- *
- * @type {UniformGroupNode}
- */
- this.groupNode = groupNode;
- }
- /**
- * The storage buffer.
- *
- * @type {BufferAttribute}
- */
- get buffer() {
- return this.nodeUniform.value;
- }
- }
- /**
- * A WebGPU backend utility module used by {@link WebGPUTextureUtils}.
- *
- * @private
- */
- class WebGPUTexturePassUtils extends DataMap {
- /**
- * Constructs a new utility object.
- *
- * @param {GPUDevice} device - The WebGPU device.
- */
- constructor( device ) {
- super();
- /**
- * The WebGPU device.
- *
- * @type {GPUDevice}
- */
- this.device = device;
- const mipmapVertexSource = `
- struct VarysStruct {
- @builtin( position ) Position: vec4<f32>,
- @location( 0 ) vTex : vec2<f32>
- };
- @vertex
- fn main( @builtin( vertex_index ) vertexIndex : u32 ) -> VarysStruct {
- var Varys : VarysStruct;
- var pos = array< vec2<f32>, 4 >(
- vec2<f32>( -1.0, 1.0 ),
- vec2<f32>( 1.0, 1.0 ),
- vec2<f32>( -1.0, -1.0 ),
- vec2<f32>( 1.0, -1.0 )
- );
- var tex = array< vec2<f32>, 4 >(
- vec2<f32>( 0.0, 0.0 ),
- vec2<f32>( 1.0, 0.0 ),
- vec2<f32>( 0.0, 1.0 ),
- vec2<f32>( 1.0, 1.0 )
- );
- Varys.vTex = tex[ vertexIndex ];
- Varys.Position = vec4<f32>( pos[ vertexIndex ], 0.0, 1.0 );
- return Varys;
- }
- `;
- const mipmapFragmentSource = `
- @group( 0 ) @binding( 0 )
- var imgSampler : sampler;
- @group( 0 ) @binding( 1 )
- var img : texture_2d<f32>;
- @fragment
- fn main( @location( 0 ) vTex : vec2<f32> ) -> @location( 0 ) vec4<f32> {
- return textureSample( img, imgSampler, vTex );
- }
- `;
- const flipYFragmentSource = `
- @group( 0 ) @binding( 0 )
- var imgSampler : sampler;
- @group( 0 ) @binding( 1 )
- var img : texture_2d<f32>;
- @fragment
- fn main( @location( 0 ) vTex : vec2<f32> ) -> @location( 0 ) vec4<f32> {
- return textureSample( img, imgSampler, vec2( vTex.x, 1.0 - vTex.y ) );
- }
- `;
- /**
- * The mipmap GPU sampler.
- *
- * @type {GPUSampler}
- */
- this.mipmapSampler = device.createSampler( { minFilter: GPUFilterMode.Linear } );
- /**
- * The flipY GPU sampler.
- *
- * @type {GPUSampler}
- */
- this.flipYSampler = device.createSampler( { minFilter: GPUFilterMode.Nearest } ); //@TODO?: Consider using textureLoad()
- /**
- * A cache for GPU render pipelines used for copy/transfer passes.
- * Every texture format requires a unique pipeline.
- *
- * @type {Object<string,GPURenderPipeline>}
- */
- this.transferPipelines = {};
- /**
- * A cache for GPU render pipelines used for flipY passes.
- * Every texture format requires a unique pipeline.
- *
- * @type {Object<string,GPURenderPipeline>}
- */
- this.flipYPipelines = {};
- /**
- * The mipmap vertex shader module.
- *
- * @type {GPUShaderModule}
- */
- this.mipmapVertexShaderModule = device.createShaderModule( {
- label: 'mipmapVertex',
- code: mipmapVertexSource
- } );
- /**
- * The mipmap fragment shader module.
- *
- * @type {GPUShaderModule}
- */
- this.mipmapFragmentShaderModule = device.createShaderModule( {
- label: 'mipmapFragment',
- code: mipmapFragmentSource
- } );
- /**
- * The flipY fragment shader module.
- *
- * @type {GPUShaderModule}
- */
- this.flipYFragmentShaderModule = device.createShaderModule( {
- label: 'flipYFragment',
- code: flipYFragmentSource
- } );
- }
- /**
- * Returns a render pipeline for the internal copy render pass. The pass
- * requires a unique render pipeline for each texture format.
- *
- * @param {string} format - The GPU texture format
- * @return {GPURenderPipeline} The GPU render pipeline.
- */
- getTransferPipeline( format ) {
- let pipeline = this.transferPipelines[ format ];
- if ( pipeline === undefined ) {
- pipeline = this.device.createRenderPipeline( {
- label: `mipmap-${ format }`,
- vertex: {
- module: this.mipmapVertexShaderModule,
- entryPoint: 'main'
- },
- fragment: {
- module: this.mipmapFragmentShaderModule,
- entryPoint: 'main',
- targets: [ { format } ]
- },
- primitive: {
- topology: GPUPrimitiveTopology.TriangleStrip,
- stripIndexFormat: GPUIndexFormat.Uint32
- },
- layout: 'auto'
- } );
- this.transferPipelines[ format ] = pipeline;
- }
- return pipeline;
- }
- /**
- * Returns a render pipeline for the flipY render pass. The pass
- * requires a unique render pipeline for each texture format.
- *
- * @param {string} format - The GPU texture format
- * @return {GPURenderPipeline} The GPU render pipeline.
- */
- getFlipYPipeline( format ) {
- let pipeline = this.flipYPipelines[ format ];
- if ( pipeline === undefined ) {
- pipeline = this.device.createRenderPipeline( {
- label: `flipY-${ format }`,
- vertex: {
- module: this.mipmapVertexShaderModule,
- entryPoint: 'main'
- },
- fragment: {
- module: this.flipYFragmentShaderModule,
- entryPoint: 'main',
- targets: [ { format } ]
- },
- primitive: {
- topology: GPUPrimitiveTopology.TriangleStrip,
- stripIndexFormat: GPUIndexFormat.Uint32
- },
- layout: 'auto'
- } );
- this.flipYPipelines[ format ] = pipeline;
- }
- return pipeline;
- }
- /**
- * Flip the contents of the given GPU texture along its vertical axis.
- *
- * @param {GPUTexture} textureGPU - The GPU texture object.
- * @param {Object} textureGPUDescriptor - The texture descriptor.
- * @param {number} [baseArrayLayer=0] - The index of the first array layer accessible to the texture view.
- */
- flipY( textureGPU, textureGPUDescriptor, baseArrayLayer = 0 ) {
- const format = textureGPUDescriptor.format;
- const { width, height } = textureGPUDescriptor.size;
- const transferPipeline = this.getTransferPipeline( format );
- const flipYPipeline = this.getFlipYPipeline( format );
- const tempTexture = this.device.createTexture( {
- size: { width, height, depthOrArrayLayers: 1 },
- format,
- usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING
- } );
- const srcView = textureGPU.createView( {
- baseMipLevel: 0,
- mipLevelCount: 1,
- dimension: GPUTextureViewDimension.TwoD,
- baseArrayLayer
- } );
- const dstView = tempTexture.createView( {
- baseMipLevel: 0,
- mipLevelCount: 1,
- dimension: GPUTextureViewDimension.TwoD,
- baseArrayLayer: 0
- } );
- const commandEncoder = this.device.createCommandEncoder( {} );
- const pass = ( pipeline, sourceView, destinationView ) => {
- const bindGroupLayout = pipeline.getBindGroupLayout( 0 ); // @TODO: Consider making this static.
- const bindGroup = this.device.createBindGroup( {
- layout: bindGroupLayout,
- entries: [ {
- binding: 0,
- resource: this.flipYSampler
- }, {
- binding: 1,
- resource: sourceView
- } ]
- } );
- const passEncoder = commandEncoder.beginRenderPass( {
- colorAttachments: [ {
- view: destinationView,
- loadOp: GPULoadOp.Clear,
- storeOp: GPUStoreOp.Store,
- clearValue: [ 0, 0, 0, 0 ]
- } ]
- } );
- passEncoder.setPipeline( pipeline );
- passEncoder.setBindGroup( 0, bindGroup );
- passEncoder.draw( 4, 1, 0, 0 );
- passEncoder.end();
- };
- pass( transferPipeline, srcView, dstView );
- pass( flipYPipeline, dstView, srcView );
- this.device.queue.submit( [ commandEncoder.finish() ] );
- tempTexture.destroy();
- }
- /**
- * Generates mipmaps for the given GPU texture.
- *
- * @param {GPUTexture} textureGPU - The GPU texture object.
- * @param {Object} textureGPUDescriptor - The texture descriptor.
- * @param {number} [baseArrayLayer=0] - The index of the first array layer accessible to the texture view.
- * @param {?GPUCommandEncoder} [encoder=null] - An optional command encoder used to generate mipmaps.
- */
- generateMipmaps( textureGPU, textureGPUDescriptor, baseArrayLayer = 0, encoder = null ) {
- const textureData = this.get( textureGPU );
- if ( textureData.layers === undefined ) {
- textureData.layers = [];
- }
- const passes = textureData.layers[ baseArrayLayer ] || this._mipmapCreateBundles( textureGPU, textureGPUDescriptor, baseArrayLayer );
- const commandEncoder = encoder || this.device.createCommandEncoder( { label: 'mipmapEncoder' } );
- this._mipmapRunBundles( commandEncoder, passes );
- if ( encoder === null ) this.device.queue.submit( [ commandEncoder.finish() ] );
- textureData.layers[ baseArrayLayer ] = passes;
- }
- /**
- * Since multiple copy render passes are required to generate mipmaps, the passes
- * are managed as render bundles to improve performance.
- *
- * @param {GPUTexture} textureGPU - The GPU texture object.
- * @param {Object} textureGPUDescriptor - The texture descriptor.
- * @param {number} baseArrayLayer - The index of the first array layer accessible to the texture view.
- * @return {Array<Object>} An array of render bundles.
- */
- _mipmapCreateBundles( textureGPU, textureGPUDescriptor, baseArrayLayer ) {
- const pipeline = this.getTransferPipeline( textureGPUDescriptor.format );
- const bindGroupLayout = pipeline.getBindGroupLayout( 0 ); // @TODO: Consider making this static.
- let srcView = textureGPU.createView( {
- baseMipLevel: 0,
- mipLevelCount: 1,
- dimension: GPUTextureViewDimension.TwoD,
- baseArrayLayer
- } );
- const passes = [];
- for ( let i = 1; i < textureGPUDescriptor.mipLevelCount; i ++ ) {
- const bindGroup = this.device.createBindGroup( {
- layout: bindGroupLayout,
- entries: [ {
- binding: 0,
- resource: this.mipmapSampler
- }, {
- binding: 1,
- resource: srcView
- } ]
- } );
- const dstView = textureGPU.createView( {
- baseMipLevel: i,
- mipLevelCount: 1,
- dimension: GPUTextureViewDimension.TwoD,
- baseArrayLayer
- } );
- const passDescriptor = {
- colorAttachments: [ {
- view: dstView,
- loadOp: GPULoadOp.Clear,
- storeOp: GPUStoreOp.Store,
- clearValue: [ 0, 0, 0, 0 ]
- } ]
- };
- const passEncoder = this.device.createRenderBundleEncoder( {
- colorFormats: [ textureGPUDescriptor.format ]
- } );
- passEncoder.setPipeline( pipeline );
- passEncoder.setBindGroup( 0, bindGroup );
- passEncoder.draw( 4, 1, 0, 0 );
- passes.push( {
- renderBundles: [ passEncoder.finish() ],
- passDescriptor
- } );
- srcView = dstView;
- }
- return passes;
- }
- /**
- * Executes the render bundles.
- *
- * @param {GPUCommandEncoder} commandEncoder - The GPU command encoder.
- * @param {Array<Object>} passes - An array of render bundles.
- */
- _mipmapRunBundles( commandEncoder, passes ) {
- const levels = passes.length;
- for ( let i = 0; i < levels; i ++ ) {
- const pass = passes[ i ];
- const passEncoder = commandEncoder.beginRenderPass( pass.passDescriptor );
- passEncoder.executeBundles( pass.renderBundles );
- passEncoder.end();
- }
- }
- }
- const _compareToWebGPU = {
- [ NeverCompare ]: 'never',
- [ LessCompare ]: 'less',
- [ EqualCompare ]: 'equal',
- [ LessEqualCompare ]: 'less-equal',
- [ GreaterCompare ]: 'greater',
- [ GreaterEqualCompare ]: 'greater-equal',
- [ AlwaysCompare ]: 'always',
- [ NotEqualCompare ]: 'not-equal'
- };
- const _flipMap = [ 0, 1, 3, 2, 4, 5 ];
- /**
- * A WebGPU backend utility module for managing textures.
- *
- * @private
- */
- class WebGPUTextureUtils {
- /**
- * Constructs a new utility object.
- *
- * @param {WebGPUBackend} backend - The WebGPU backend.
- */
- constructor( backend ) {
- /**
- * A reference to the WebGPU backend.
- *
- * @type {WebGPUBackend}
- */
- this.backend = backend;
- /**
- * A reference to the pass utils.
- *
- * @type {?WebGPUTexturePassUtils}
- * @default null
- */
- this._passUtils = null;
- /**
- * A dictionary for managing default textures. The key
- * is the texture format, the value the texture object.
- *
- * @type {Object<string,Texture>}
- */
- this.defaultTexture = {};
- /**
- * A dictionary for managing default cube textures. The key
- * is the texture format, the value the texture object.
- *
- * @type {Object<string,CubeTexture>}
- */
- this.defaultCubeTexture = {};
- /**
- * A default video frame.
- *
- * @type {?VideoFrame}
- * @default null
- */
- this.defaultVideoFrame = null;
- /**
- * A cache of shared texture samplers.
- *
- * @type {Map<string, Object>}
- */
- this._samplerCache = new Map();
- }
- /**
- * Creates a GPU sampler for the given texture.
- *
- * @param {Texture} texture - The texture to create the sampler for.
- * @return {string} The current sampler key.
- */
- updateSampler( texture ) {
- const backend = this.backend;
- const samplerKey = texture.minFilter + '-' + texture.magFilter + '-' +
- texture.wrapS + '-' + texture.wrapT + '-' + ( texture.wrapR || '0' ) + '-' +
- texture.anisotropy + '-' + ( texture.compareFunction || 0 );
- let samplerData = this._samplerCache.get( samplerKey );
- if ( samplerData === undefined ) {
- const samplerDescriptorGPU = {
- addressModeU: this._convertAddressMode( texture.wrapS ),
- addressModeV: this._convertAddressMode( texture.wrapT ),
- addressModeW: this._convertAddressMode( texture.wrapR ),
- magFilter: this._convertFilterMode( texture.magFilter ),
- minFilter: this._convertFilterMode( texture.minFilter ),
- mipmapFilter: this._convertFilterMode( texture.minFilter ),
- maxAnisotropy: 1
- };
- // anisotropy can only be used when all filter modes are set to linear.
- if ( samplerDescriptorGPU.magFilter === GPUFilterMode.Linear && samplerDescriptorGPU.minFilter === GPUFilterMode.Linear && samplerDescriptorGPU.mipmapFilter === GPUFilterMode.Linear ) {
- samplerDescriptorGPU.maxAnisotropy = texture.anisotropy;
- }
- if ( texture.isDepthTexture && texture.compareFunction !== null ) {
- samplerDescriptorGPU.compare = _compareToWebGPU[ texture.compareFunction ];
- }
- const sampler = backend.device.createSampler( samplerDescriptorGPU );
- samplerData = { sampler, usedTimes: 0 };
- this._samplerCache.set( samplerKey, samplerData );
- }
- const textureData = backend.get( texture );
- if ( textureData.sampler !== samplerData.sampler ) {
- // check if previous sampler is unused so it can be deleted
- if ( textureData.sampler !== undefined ) {
- const oldSamplerData = this._samplerCache.get( textureData.samplerKey );
- oldSamplerData.usedTimes --;
- if ( oldSamplerData.usedTimes === 0 ) {
- this._samplerCache.delete( textureData.samplerKey );
- }
- }
- // update to new sampler data
- textureData.samplerKey = samplerKey;
- textureData.sampler = samplerData.sampler;
- samplerData.usedTimes ++;
- }
- return samplerKey;
- }
- /**
- * Creates a default texture for the given texture that can be used
- * as a placeholder until the actual texture is ready for usage.
- *
- * @param {Texture} texture - The texture to create a default texture for.
- */
- createDefaultTexture( texture ) {
- let textureGPU;
- const format = getFormat( texture );
- if ( texture.isCubeTexture ) {
- textureGPU = this._getDefaultCubeTextureGPU( format );
- } else {
- textureGPU = this._getDefaultTextureGPU( format );
- }
- this.backend.get( texture ).texture = textureGPU;
- }
- /**
- * Defines a texture on the GPU for the given texture object.
- *
- * @param {Texture} texture - The texture.
- * @param {Object} [options={}] - Optional configuration parameter.
- */
- createTexture( texture, options = {} ) {
- const backend = this.backend;
- const textureData = backend.get( texture );
- if ( textureData.initialized ) {
- throw new Error( 'WebGPUTextureUtils: Texture already initialized.' );
- }
- if ( texture.isExternalTexture ) {
- textureData.texture = texture.sourceTexture;
- textureData.initialized = true;
- return;
- }
- if ( options.needsMipmaps === undefined ) options.needsMipmaps = false;
- if ( options.levels === undefined ) options.levels = 1;
- if ( options.depth === undefined ) options.depth = 1;
- const { width, height, depth, levels } = options;
- if ( texture.isFramebufferTexture ) {
- if ( options.renderTarget ) {
- options.format = this.backend.utils.getCurrentColorFormat( options.renderTarget );
- } else {
- options.format = this.backend.utils.getPreferredCanvasFormat();
- }
- }
- const dimension = this._getDimension( texture );
- const format = texture.internalFormat || options.format || getFormat( texture, backend.device );
- textureData.format = format;
- const { samples, primarySamples, isMSAA } = backend.utils.getTextureSampleData( texture );
- let usage = GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.COPY_SRC;
- if ( texture.isStorageTexture === true ) {
- usage |= GPUTextureUsage.STORAGE_BINDING;
- }
- if ( texture.isCompressedTexture !== true && texture.isCompressedArrayTexture !== true && format !== GPUTextureFormat.RGB9E5UFloat ) {
- usage |= GPUTextureUsage.RENDER_ATTACHMENT;
- }
- const textureDescriptorGPU = {
- label: texture.name,
- size: {
- width: width,
- height: height,
- depthOrArrayLayers: depth,
- },
- mipLevelCount: levels,
- sampleCount: primarySamples,
- dimension: dimension,
- format: format,
- usage: usage
- };
- // texture creation
- if ( format === undefined ) {
- warn( 'WebGPURenderer: Texture format not supported.' );
- this.createDefaultTexture( texture );
- return;
- }
- if ( texture.isCubeTexture ) {
- textureDescriptorGPU.textureBindingViewDimension = GPUTextureViewDimension.Cube;
- }
- textureData.texture = backend.device.createTexture( textureDescriptorGPU );
- if ( isMSAA ) {
- const msaaTextureDescriptorGPU = Object.assign( {}, textureDescriptorGPU );
- msaaTextureDescriptorGPU.label = msaaTextureDescriptorGPU.label + '-msaa';
- msaaTextureDescriptorGPU.sampleCount = samples;
- msaaTextureDescriptorGPU.mipLevelCount = 1; // See https://www.w3.org/TR/webgpu/#texture-creation
- textureData.msaaTexture = backend.device.createTexture( msaaTextureDescriptorGPU );
- }
- textureData.initialized = true;
- textureData.textureDescriptorGPU = textureDescriptorGPU;
- }
- /**
- * Destroys the GPU data for the given texture object.
- *
- * @param {Texture} texture - The texture.
- * @param {boolean} [isDefaultTexture=false] - Whether the texture uses a default GPU texture or not.
- */
- destroyTexture( texture, isDefaultTexture = false ) {
- const backend = this.backend;
- const textureData = backend.get( texture );
- if ( textureData.texture !== undefined && isDefaultTexture === false ) textureData.texture.destroy();
- if ( textureData.msaaTexture !== undefined ) textureData.msaaTexture.destroy();
- backend.delete( texture );
- }
- /**
- * Generates mipmaps for the given texture.
- *
- * @param {Texture} texture - The texture.
- * @param {?GPUCommandEncoder} [encoder=null] - An optional command encoder used to generate mipmaps.
- */
- generateMipmaps( texture, encoder = null ) {
- const textureData = this.backend.get( texture );
- if ( texture.isCubeTexture ) {
- for ( let i = 0; i < 6; i ++ ) {
- this._generateMipmaps( textureData.texture, textureData.textureDescriptorGPU, i, encoder );
- }
- } else {
- const depth = texture.image.depth || 1;
- for ( let i = 0; i < depth; i ++ ) {
- this._generateMipmaps( textureData.texture, textureData.textureDescriptorGPU, i, encoder );
- }
- }
- }
- /**
- * Returns the color buffer representing the color
- * attachment of the default framebuffer.
- *
- * @return {GPUTexture} The color buffer.
- */
- getColorBuffer() {
- const backend = this.backend;
- const canvasTarget = backend.renderer.getCanvasTarget();
- const { width, height } = backend.getDrawingBufferSize();
- const samples = backend.renderer.currentSamples;
- const colorTexture = canvasTarget.colorTexture;
- const colorTextureData = backend.get( colorTexture );
- if ( colorTexture.width === width && colorTexture.height === height && colorTexture.samples === samples ) {
- return colorTextureData.texture;
- }
- // recreate
- let colorBuffer = colorTextureData.texture;
- if ( colorBuffer ) colorBuffer.destroy();
- colorBuffer = backend.device.createTexture( {
- label: 'colorBuffer',
- size: {
- width: width,
- height: height,
- depthOrArrayLayers: 1
- },
- sampleCount: backend.utils.getSampleCount( backend.renderer.currentSamples ),
- format: backend.utils.getPreferredCanvasFormat(),
- usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC
- } );
- //
- colorTexture.source.width = width;
- colorTexture.source.height = height;
- colorTexture.samples = samples;
- colorTextureData.texture = colorBuffer;
- return colorBuffer;
- }
- /**
- * Returns the depth buffer representing the depth
- * attachment of the default framebuffer.
- *
- * @param {boolean} [depth=true] - Whether depth is enabled or not.
- * @param {boolean} [stencil=false] - Whether stencil is enabled or not.
- * @return {GPUTexture} The depth buffer.
- */
- getDepthBuffer( depth = true, stencil = false ) {
- const backend = this.backend;
- const canvasTarget = backend.renderer.getCanvasTarget();
- const { width, height } = backend.getDrawingBufferSize();
- const samples = backend.renderer.currentSamples;
- const depthTexture = canvasTarget.depthTexture;
- if ( depthTexture.width === width &&
- depthTexture.height === height &&
- depthTexture.samples === samples &&
- depthTexture.depth === depth &&
- depthTexture.stencil === stencil ) {
- return backend.get( depthTexture ).texture;
- }
- //
- const depthTextureGPU = backend.get( depthTexture ).texture;
- let format, type;
- if ( stencil ) {
- format = DepthStencilFormat;
- type = UnsignedInt248Type;
- } else if ( depth ) {
- format = DepthFormat;
- type = UnsignedIntType;
- }
- if ( depthTextureGPU !== undefined ) {
- if ( depthTexture.image.width === width && depthTexture.image.height === height && depthTexture.format === format && depthTexture.type === type && depthTexture.samples === samples ) {
- return depthTextureGPU;
- }
- this.destroyTexture( depthTexture );
- }
- // recreate
- depthTexture.name = 'depthBuffer';
- depthTexture.format = format;
- depthTexture.type = type;
- depthTexture.image.width = width;
- depthTexture.image.height = height;
- depthTexture.samples = samples;
- this.createTexture( depthTexture, { width, height } );
- return backend.get( depthTexture ).texture;
- }
- /**
- * Uploads the updated texture data to the GPU.
- *
- * @param {Texture} texture - The texture.
- * @param {Object} [options={}] - Optional configuration parameter.
- */
- updateTexture( texture, options ) {
- const textureData = this.backend.get( texture );
- const mipmaps = texture.mipmaps;
- const { textureDescriptorGPU } = textureData;
- if ( texture.isRenderTargetTexture || ( textureDescriptorGPU === undefined /* unsupported texture format */ ) )
- return;
- // transfer texture data
- if ( texture.isDataTexture ) {
- if ( mipmaps.length > 0 ) {
- for ( let i = 0, il = mipmaps.length; i < il; i ++ ) {
- const mipmap = mipmaps[ i ];
- this._copyBufferToTexture( mipmap, textureData.texture, textureDescriptorGPU, 0, texture.flipY, 0, i );
- }
- } else {
- this._copyBufferToTexture( options.image, textureData.texture, textureDescriptorGPU, 0, texture.flipY );
- }
- } else if ( texture.isArrayTexture || texture.isDataArrayTexture || texture.isData3DTexture ) {
- for ( let i = 0; i < options.image.depth; i ++ ) {
- this._copyBufferToTexture( options.image, textureData.texture, textureDescriptorGPU, i, texture.flipY, i );
- }
- } else if ( texture.isCompressedTexture || texture.isCompressedArrayTexture ) {
- this._copyCompressedBufferToTexture( texture.mipmaps, textureData.texture, textureDescriptorGPU );
- } else if ( texture.isCubeTexture ) {
- this._copyCubeMapToTexture( texture, textureData.texture, textureDescriptorGPU );
- } else {
- if ( mipmaps.length > 0 ) {
- for ( let i = 0, il = mipmaps.length; i < il; i ++ ) {
- const mipmap = mipmaps[ i ];
- this._copyImageToTexture( mipmap, textureData.texture, textureDescriptorGPU, 0, texture.flipY, texture.premultiplyAlpha, i );
- }
- } else {
- this._copyImageToTexture( options.image, textureData.texture, textureDescriptorGPU, 0, texture.flipY, texture.premultiplyAlpha );
- }
- }
- //
- textureData.version = texture.version;
- }
- /**
- * Returns texture data as a typed array.
- *
- * @async
- * @param {Texture} texture - The texture to copy.
- * @param {number} x - The x coordinate of the copy origin.
- * @param {number} y - The y coordinate of the copy origin.
- * @param {number} width - The width of the copy.
- * @param {number} height - The height of the copy.
- * @param {number} faceIndex - The face index.
- * @return {Promise<TypedArray>} A Promise that resolves with a typed array when the copy operation has finished.
- */
- async copyTextureToBuffer( texture, x, y, width, height, faceIndex ) {
- const device = this.backend.device;
- const textureData = this.backend.get( texture );
- const textureGPU = textureData.texture;
- const format = textureData.textureDescriptorGPU.format;
- const bytesPerTexel = this._getBytesPerTexel( format );
- let bytesPerRow = width * bytesPerTexel;
- bytesPerRow = Math.ceil( bytesPerRow / 256 ) * 256; // Align to 256 bytes
- const readBuffer = device.createBuffer(
- {
- size: ( ( height - 1 ) * bytesPerRow ) + ( width * bytesPerTexel ), // see https://github.com/mrdoob/three.js/issues/31658#issuecomment-3229442010
- usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ
- }
- );
- const encoder = device.createCommandEncoder();
- encoder.copyTextureToBuffer(
- {
- texture: textureGPU,
- origin: { x, y, z: faceIndex },
- },
- {
- buffer: readBuffer,
- bytesPerRow: bytesPerRow
- },
- {
- width: width,
- height: height
- }
- );
- const typedArrayType = this._getTypedArrayType( format );
- device.queue.submit( [ encoder.finish() ] );
- await readBuffer.mapAsync( GPUMapMode.READ );
- const buffer = readBuffer.getMappedRange();
- return new typedArrayType( buffer );
- }
- /**
- * Frees all internal resources.
- */
- dispose() {
- this._samplerCache.clear();
- }
- /**
- * Returns the default GPU texture for the given format.
- *
- * @private
- * @param {string} format - The GPU format.
- * @return {GPUTexture} The GPU texture.
- */
- _getDefaultTextureGPU( format ) {
- let defaultTexture = this.defaultTexture[ format ];
- if ( defaultTexture === undefined ) {
- const texture = new Texture();
- texture.minFilter = NearestFilter;
- texture.magFilter = NearestFilter;
- this.createTexture( texture, { width: 1, height: 1, format } );
- this.defaultTexture[ format ] = defaultTexture = texture;
- }
- return this.backend.get( defaultTexture ).texture;
- }
- /**
- * Returns the default GPU cube texture for the given format.
- *
- * @private
- * @param {string} format - The GPU format.
- * @return {GPUTexture} The GPU texture.
- */
- _getDefaultCubeTextureGPU( format ) {
- let defaultCubeTexture = this.defaultCubeTexture[ format ];
- if ( defaultCubeTexture === undefined ) {
- const texture = new CubeTexture();
- texture.minFilter = NearestFilter;
- texture.magFilter = NearestFilter;
- this.createTexture( texture, { width: 1, height: 1, depth: 6 } );
- this.defaultCubeTexture[ format ] = defaultCubeTexture = texture;
- }
- return this.backend.get( defaultCubeTexture ).texture;
- }
- /**
- * Uploads cube texture image data to the GPU memory.
- *
- * @private
- * @param {CubeTexture} texture - The cube texture.
- * @param {GPUTexture} textureGPU - The GPU texture.
- * @param {Object} textureDescriptorGPU - The GPU texture descriptor.
- */
- _copyCubeMapToTexture( texture, textureGPU, textureDescriptorGPU ) {
- const images = texture.images;
- const mipmaps = texture.mipmaps;
- for ( let i = 0; i < 6; i ++ ) {
- const image = images[ i ];
- const flipIndex = texture.flipY === true ? _flipMap[ i ] : i;
- if ( image.isDataTexture ) {
- this._copyBufferToTexture( image.image, textureGPU, textureDescriptorGPU, flipIndex, texture.flipY );
- } else {
- this._copyImageToTexture( image, textureGPU, textureDescriptorGPU, flipIndex, texture.flipY, texture.premultiplyAlpha );
- }
- for ( let j = 0; j < mipmaps.length; j ++ ) {
- const mipmap = mipmaps[ j ];
- const image = mipmap.images[ i ];
- if ( image.isDataTexture ) {
- this._copyBufferToTexture( image.image, textureGPU, textureDescriptorGPU, flipIndex, texture.flipY, 0, j + 1 );
- } else {
- this._copyImageToTexture( image, textureGPU, textureDescriptorGPU, flipIndex, texture.flipY, texture.premultiplyAlpha, j + 1 );
- }
- }
- }
- }
- /**
- * Uploads texture image data to the GPU memory.
- *
- * @private
- * @param {HTMLImageElement|ImageBitmap|HTMLCanvasElement} image - The image data.
- * @param {GPUTexture} textureGPU - The GPU texture.
- * @param {Object} textureDescriptorGPU - The GPU texture descriptor.
- * @param {number} originDepth - The origin depth.
- * @param {boolean} flipY - Whether to flip texture data along their vertical axis or not.
- * @param {boolean} premultiplyAlpha - Whether the texture should have its RGB channels premultiplied by the alpha channel or not.
- * @param {number} [mipLevel=0] - The mip level where the data should be copied to.
- */
- _copyImageToTexture( image, textureGPU, textureDescriptorGPU, originDepth, flipY, premultiplyAlpha, mipLevel = 0 ) {
- const device = this.backend.device;
- const width = ( mipLevel > 0 ) ? image.width : textureDescriptorGPU.size.width;
- const height = ( mipLevel > 0 ) ? image.height : textureDescriptorGPU.size.height;
- try {
- device.queue.copyExternalImageToTexture(
- {
- source: image,
- flipY: flipY
- }, {
- texture: textureGPU,
- mipLevel: mipLevel,
- origin: { x: 0, y: 0, z: originDepth },
- premultipliedAlpha: premultiplyAlpha
- }, {
- width: width,
- height: height,
- depthOrArrayLayers: 1
- }
- );
- } catch ( _ ) {} // try/catch has been added to fix bad video frame data on certain devices, see #32391
- }
- /**
- * Returns the pass utils singleton.
- *
- * @private
- * @return {WebGPUTexturePassUtils} The utils instance.
- */
- _getPassUtils() {
- let passUtils = this._passUtils;
- if ( passUtils === null ) {
- this._passUtils = passUtils = new WebGPUTexturePassUtils( this.backend.device );
- }
- return passUtils;
- }
- /**
- * Generates mipmaps for the given GPU texture.
- *
- * @private
- * @param {GPUTexture} textureGPU - The GPU texture object.
- * @param {Object} textureDescriptorGPU - The texture descriptor.
- * @param {number} [baseArrayLayer=0] - The index of the first array layer accessible to the texture view.
- * @param {?GPUCommandEncoder} [encoder=null] - An optional command encoder used to generate mipmaps.
- */
- _generateMipmaps( textureGPU, textureDescriptorGPU, baseArrayLayer = 0, encoder = null ) {
- this._getPassUtils().generateMipmaps( textureGPU, textureDescriptorGPU, baseArrayLayer, encoder );
- }
- /**
- * Flip the contents of the given GPU texture along its vertical axis.
- *
- * @private
- * @param {GPUTexture} textureGPU - The GPU texture object.
- * @param {Object} textureDescriptorGPU - The texture descriptor.
- * @param {number} [originDepth=0] - The origin depth.
- */
- _flipY( textureGPU, textureDescriptorGPU, originDepth = 0 ) {
- this._getPassUtils().flipY( textureGPU, textureDescriptorGPU, originDepth );
- }
- /**
- * Uploads texture buffer data to the GPU memory.
- *
- * @private
- * @param {Object} image - An object defining the image buffer data.
- * @param {GPUTexture} textureGPU - The GPU texture.
- * @param {Object} textureDescriptorGPU - The GPU texture descriptor.
- * @param {number} originDepth - The origin depth.
- * @param {boolean} flipY - Whether to flip texture data along their vertical axis or not.
- * @param {number} [depth=0] - The depth offset when copying array or 3D texture data.
- * @param {number} [mipLevel=0] - The mip level where the data should be copied to.
- */
- _copyBufferToTexture( image, textureGPU, textureDescriptorGPU, originDepth, flipY, depth = 0, mipLevel = 0 ) {
- // @TODO: Consider to use GPUCommandEncoder.copyBufferToTexture()
- // @TODO: Consider to support valid buffer layouts with other formats like RGB
- const device = this.backend.device;
- const data = image.data;
- const bytesPerTexel = this._getBytesPerTexel( textureDescriptorGPU.format );
- const bytesPerRow = image.width * bytesPerTexel;
- device.queue.writeTexture(
- {
- texture: textureGPU,
- mipLevel: mipLevel,
- origin: { x: 0, y: 0, z: originDepth }
- },
- data,
- {
- offset: image.width * image.height * bytesPerTexel * depth,
- bytesPerRow
- },
- {
- width: image.width,
- height: image.height,
- depthOrArrayLayers: 1
- } );
- if ( flipY === true ) {
- this._flipY( textureGPU, textureDescriptorGPU, originDepth );
- }
- }
- /**
- * Uploads compressed texture data to the GPU memory.
- *
- * @private
- * @param {Array<Object>} mipmaps - An array with mipmap data.
- * @param {GPUTexture} textureGPU - The GPU texture.
- * @param {Object} textureDescriptorGPU - The GPU texture descriptor.
- */
- _copyCompressedBufferToTexture( mipmaps, textureGPU, textureDescriptorGPU ) {
- // @TODO: Consider to use GPUCommandEncoder.copyBufferToTexture()
- const device = this.backend.device;
- const blockData = this._getBlockData( textureDescriptorGPU.format );
- const isArrayTexture = textureDescriptorGPU.size.depthOrArrayLayers > 1;
- for ( let i = 0; i < mipmaps.length; i ++ ) {
- const mipmap = mipmaps[ i ];
- const width = mipmap.width;
- const height = mipmap.height;
- const depth = isArrayTexture ? textureDescriptorGPU.size.depthOrArrayLayers : 1;
- const bytesPerRow = Math.ceil( width / blockData.width ) * blockData.byteLength;
- const bytesPerImage = bytesPerRow * Math.ceil( height / blockData.height );
- for ( let j = 0; j < depth; j ++ ) {
- device.queue.writeTexture(
- {
- texture: textureGPU,
- mipLevel: i,
- origin: { x: 0, y: 0, z: j }
- },
- mipmap.data,
- {
- offset: j * bytesPerImage,
- bytesPerRow,
- rowsPerImage: Math.ceil( height / blockData.height )
- },
- {
- width: Math.ceil( width / blockData.width ) * blockData.width,
- height: Math.ceil( height / blockData.height ) * blockData.height,
- depthOrArrayLayers: 1
- }
- );
- }
- }
- }
- /**
- * This method is only relevant for compressed texture formats. It returns a block
- * data descriptor for the given GPU compressed texture format.
- *
- * @private
- * @param {string} format - The GPU compressed texture format.
- * @return {Object} The block data descriptor.
- */
- _getBlockData( format ) {
- if ( format === GPUTextureFormat.BC1RGBAUnorm || format === GPUTextureFormat.BC1RGBAUnormSRGB ) return { byteLength: 8, width: 4, height: 4 }; // DXT1
- if ( format === GPUTextureFormat.BC2RGBAUnorm || format === GPUTextureFormat.BC2RGBAUnormSRGB ) return { byteLength: 16, width: 4, height: 4 }; // DXT3
- if ( format === GPUTextureFormat.BC3RGBAUnorm || format === GPUTextureFormat.BC3RGBAUnormSRGB ) return { byteLength: 16, width: 4, height: 4 }; // DXT5
- if ( format === GPUTextureFormat.BC4RUnorm || format === GPUTextureFormat.BC4RSnorm ) return { byteLength: 8, width: 4, height: 4 }; // RGTC1
- if ( format === GPUTextureFormat.BC5RGUnorm || format === GPUTextureFormat.BC5RGSnorm ) return { byteLength: 16, width: 4, height: 4 }; // RGTC2
- if ( format === GPUTextureFormat.BC6HRGBUFloat || format === GPUTextureFormat.BC6HRGBFloat ) return { byteLength: 16, width: 4, height: 4 }; // BPTC (float)
- if ( format === GPUTextureFormat.BC7RGBAUnorm || format === GPUTextureFormat.BC7RGBAUnormSRGB ) return { byteLength: 16, width: 4, height: 4 }; // BPTC (unorm)
- if ( format === GPUTextureFormat.ETC2RGB8Unorm || format === GPUTextureFormat.ETC2RGB8UnormSRGB ) return { byteLength: 8, width: 4, height: 4 };
- if ( format === GPUTextureFormat.ETC2RGB8A1Unorm || format === GPUTextureFormat.ETC2RGB8A1UnormSRGB ) return { byteLength: 8, width: 4, height: 4 };
- if ( format === GPUTextureFormat.ETC2RGBA8Unorm || format === GPUTextureFormat.ETC2RGBA8UnormSRGB ) return { byteLength: 16, width: 4, height: 4 };
- if ( format === GPUTextureFormat.EACR11Unorm ) return { byteLength: 8, width: 4, height: 4 };
- if ( format === GPUTextureFormat.EACR11Snorm ) return { byteLength: 8, width: 4, height: 4 };
- if ( format === GPUTextureFormat.EACRG11Unorm ) return { byteLength: 16, width: 4, height: 4 };
- if ( format === GPUTextureFormat.EACRG11Snorm ) return { byteLength: 16, width: 4, height: 4 };
- if ( format === GPUTextureFormat.ASTC4x4Unorm || format === GPUTextureFormat.ASTC4x4UnormSRGB ) return { byteLength: 16, width: 4, height: 4 };
- if ( format === GPUTextureFormat.ASTC5x4Unorm || format === GPUTextureFormat.ASTC5x4UnormSRGB ) return { byteLength: 16, width: 5, height: 4 };
- if ( format === GPUTextureFormat.ASTC5x5Unorm || format === GPUTextureFormat.ASTC5x5UnormSRGB ) return { byteLength: 16, width: 5, height: 5 };
- if ( format === GPUTextureFormat.ASTC6x5Unorm || format === GPUTextureFormat.ASTC6x5UnormSRGB ) return { byteLength: 16, width: 6, height: 5 };
- if ( format === GPUTextureFormat.ASTC6x6Unorm || format === GPUTextureFormat.ASTC6x6UnormSRGB ) return { byteLength: 16, width: 6, height: 6 };
- if ( format === GPUTextureFormat.ASTC8x5Unorm || format === GPUTextureFormat.ASTC8x5UnormSRGB ) return { byteLength: 16, width: 8, height: 5 };
- if ( format === GPUTextureFormat.ASTC8x6Unorm || format === GPUTextureFormat.ASTC8x6UnormSRGB ) return { byteLength: 16, width: 8, height: 6 };
- if ( format === GPUTextureFormat.ASTC8x8Unorm || format === GPUTextureFormat.ASTC8x8UnormSRGB ) return { byteLength: 16, width: 8, height: 8 };
- if ( format === GPUTextureFormat.ASTC10x5Unorm || format === GPUTextureFormat.ASTC10x5UnormSRGB ) return { byteLength: 16, width: 10, height: 5 };
- if ( format === GPUTextureFormat.ASTC10x6Unorm || format === GPUTextureFormat.ASTC10x6UnormSRGB ) return { byteLength: 16, width: 10, height: 6 };
- if ( format === GPUTextureFormat.ASTC10x8Unorm || format === GPUTextureFormat.ASTC10x8UnormSRGB ) return { byteLength: 16, width: 10, height: 8 };
- if ( format === GPUTextureFormat.ASTC10x10Unorm || format === GPUTextureFormat.ASTC10x10UnormSRGB ) return { byteLength: 16, width: 10, height: 10 };
- if ( format === GPUTextureFormat.ASTC12x10Unorm || format === GPUTextureFormat.ASTC12x10UnormSRGB ) return { byteLength: 16, width: 12, height: 10 };
- if ( format === GPUTextureFormat.ASTC12x12Unorm || format === GPUTextureFormat.ASTC12x12UnormSRGB ) return { byteLength: 16, width: 12, height: 12 };
- }
- /**
- * Converts the three.js uv wrapping constants to GPU address mode constants.
- *
- * @private
- * @param {number} value - The three.js constant defining a uv wrapping mode.
- * @return {string} The GPU address mode.
- */
- _convertAddressMode( value ) {
- let addressMode = GPUAddressMode.ClampToEdge;
- if ( value === RepeatWrapping ) {
- addressMode = GPUAddressMode.Repeat;
- } else if ( value === MirroredRepeatWrapping ) {
- addressMode = GPUAddressMode.MirrorRepeat;
- }
- return addressMode;
- }
- /**
- * Converts the three.js filter constants to GPU filter constants.
- *
- * @private
- * @param {number} value - The three.js constant defining a filter mode.
- * @return {string} The GPU filter mode.
- */
- _convertFilterMode( value ) {
- let filterMode = GPUFilterMode.Linear;
- if ( value === NearestFilter || value === NearestMipmapNearestFilter || value === NearestMipmapLinearFilter ) {
- filterMode = GPUFilterMode.Nearest;
- }
- return filterMode;
- }
- /**
- * Returns the bytes-per-texel value for the given GPU texture format.
- *
- * @private
- * @param {string} format - The GPU texture format.
- * @return {number} The bytes-per-texel.
- */
- _getBytesPerTexel( format ) {
- // 8-bit formats
- if ( format === GPUTextureFormat.R8Unorm ||
- format === GPUTextureFormat.R8Snorm ||
- format === GPUTextureFormat.R8Uint ||
- format === GPUTextureFormat.R8Sint ) return 1;
- // 16-bit formats
- if ( format === GPUTextureFormat.R16Uint ||
- format === GPUTextureFormat.R16Sint ||
- format === GPUTextureFormat.R16Float ||
- format === GPUTextureFormat.RG8Unorm ||
- format === GPUTextureFormat.RG8Snorm ||
- format === GPUTextureFormat.RG8Uint ||
- format === GPUTextureFormat.RG8Sint ) return 2;
- // 32-bit formats
- if ( format === GPUTextureFormat.R32Uint ||
- format === GPUTextureFormat.R32Sint ||
- format === GPUTextureFormat.R32Float ||
- format === GPUTextureFormat.RG16Uint ||
- format === GPUTextureFormat.RG16Sint ||
- format === GPUTextureFormat.RG16Float ||
- format === GPUTextureFormat.RGBA8Unorm ||
- format === GPUTextureFormat.RGBA8UnormSRGB ||
- format === GPUTextureFormat.RGBA8Snorm ||
- format === GPUTextureFormat.RGBA8Uint ||
- format === GPUTextureFormat.RGBA8Sint ||
- format === GPUTextureFormat.BGRA8Unorm ||
- format === GPUTextureFormat.BGRA8UnormSRGB ||
- // Packed 32-bit formats
- format === GPUTextureFormat.RGB9E5UFloat ||
- format === GPUTextureFormat.RGB10A2Unorm ||
- format === GPUTextureFormat.RG11B10UFloat ||
- format === GPUTextureFormat.Depth32Float ||
- format === GPUTextureFormat.Depth24Plus ||
- format === GPUTextureFormat.Depth24PlusStencil8 ||
- format === GPUTextureFormat.Depth32FloatStencil8 ) return 4;
- // 64-bit formats
- if ( format === GPUTextureFormat.RG32Uint ||
- format === GPUTextureFormat.RG32Sint ||
- format === GPUTextureFormat.RG32Float ||
- format === GPUTextureFormat.RGBA16Uint ||
- format === GPUTextureFormat.RGBA16Sint ||
- format === GPUTextureFormat.RGBA16Float ) return 8;
- // 128-bit formats
- if ( format === GPUTextureFormat.RGBA32Uint ||
- format === GPUTextureFormat.RGBA32Sint ||
- format === GPUTextureFormat.RGBA32Float ) return 16;
- }
- /**
- * Returns the corresponding typed array type for the given GPU texture format.
- *
- * @private
- * @param {string} format - The GPU texture format.
- * @return {TypedArray.constructor} The typed array type.
- */
- _getTypedArrayType( format ) {
- if ( format === GPUTextureFormat.R8Uint ) return Uint8Array;
- if ( format === GPUTextureFormat.R8Sint ) return Int8Array;
- if ( format === GPUTextureFormat.R8Unorm ) return Uint8Array;
- if ( format === GPUTextureFormat.R8Snorm ) return Int8Array;
- if ( format === GPUTextureFormat.RG8Uint ) return Uint8Array;
- if ( format === GPUTextureFormat.RG8Sint ) return Int8Array;
- if ( format === GPUTextureFormat.RG8Unorm ) return Uint8Array;
- if ( format === GPUTextureFormat.RG8Snorm ) return Int8Array;
- if ( format === GPUTextureFormat.RGBA8Uint ) return Uint8Array;
- if ( format === GPUTextureFormat.RGBA8Sint ) return Int8Array;
- if ( format === GPUTextureFormat.RGBA8Unorm || format === GPUTextureFormat.RGBA8UnormSRGB ) return Uint8Array;
- if ( format === GPUTextureFormat.RGBA8Snorm ) return Int8Array;
- if ( format === GPUTextureFormat.R16Uint ) return Uint16Array;
- if ( format === GPUTextureFormat.R16Sint ) return Int16Array;
- if ( format === GPUTextureFormat.RG16Uint ) return Uint16Array;
- if ( format === GPUTextureFormat.RG16Sint ) return Int16Array;
- if ( format === GPUTextureFormat.RGBA16Uint ) return Uint16Array;
- if ( format === GPUTextureFormat.RGBA16Sint ) return Int16Array;
- if ( format === GPUTextureFormat.R16Float ) return Uint16Array;
- if ( format === GPUTextureFormat.RG16Float ) return Uint16Array;
- if ( format === GPUTextureFormat.RGBA16Float ) return Uint16Array;
- if ( format === GPUTextureFormat.R32Uint ) return Uint32Array;
- if ( format === GPUTextureFormat.R32Sint ) return Int32Array;
- if ( format === GPUTextureFormat.R32Float ) return Float32Array;
- if ( format === GPUTextureFormat.RG32Uint ) return Uint32Array;
- if ( format === GPUTextureFormat.RG32Sint ) return Int32Array;
- if ( format === GPUTextureFormat.RG32Float ) return Float32Array;
- if ( format === GPUTextureFormat.RGBA32Uint ) return Uint32Array;
- if ( format === GPUTextureFormat.RGBA32Sint ) return Int32Array;
- if ( format === GPUTextureFormat.RGBA32Float ) return Float32Array;
- if ( format === GPUTextureFormat.BGRA8Unorm || format === GPUTextureFormat.BGRA8UnormSRGB ) return Uint8Array;
- if ( format === GPUTextureFormat.RGB10A2Unorm ) return Uint32Array;
- if ( format === GPUTextureFormat.RGB9E5UFloat ) return Uint32Array;
- if ( format === GPUTextureFormat.RG11B10UFloat ) return Uint32Array;
- if ( format === GPUTextureFormat.Depth32Float ) return Float32Array;
- if ( format === GPUTextureFormat.Depth24Plus ) return Uint32Array;
- if ( format === GPUTextureFormat.Depth24PlusStencil8 ) return Uint32Array;
- if ( format === GPUTextureFormat.Depth32FloatStencil8 ) return Float32Array;
- }
- /**
- * Returns the GPU dimensions for the given texture.
- *
- * @private
- * @param {Texture} texture - The texture.
- * @return {string} The GPU dimension.
- */
- _getDimension( texture ) {
- let dimension;
- if ( texture.is3DTexture || texture.isData3DTexture ) {
- dimension = GPUTextureDimension.ThreeD;
- } else {
- dimension = GPUTextureDimension.TwoD;
- }
- return dimension;
- }
- }
- /**
- * Returns the GPU format for the given texture.
- *
- * @param {Texture} texture - The texture.
- * @param {?GPUDevice} [device=null] - The GPU device which is used for feature detection.
- * It is not necessary to apply the device for most formats.
- * @return {string} The GPU format.
- */
- function getFormat( texture, device = null ) {
- const format = texture.format;
- const type = texture.type;
- const colorSpace = texture.colorSpace;
- const transfer = ColorManagement.getTransfer( colorSpace );
- let formatGPU;
- if ( texture.isCompressedTexture === true || texture.isCompressedArrayTexture === true ) {
- switch ( format ) {
- case RGB_S3TC_DXT1_Format:
- case RGBA_S3TC_DXT1_Format:
- formatGPU = ( transfer === SRGBTransfer ) ? GPUTextureFormat.BC1RGBAUnormSRGB : GPUTextureFormat.BC1RGBAUnorm;
- break;
- case RGBA_S3TC_DXT3_Format:
- formatGPU = ( transfer === SRGBTransfer ) ? GPUTextureFormat.BC2RGBAUnormSRGB : GPUTextureFormat.BC2RGBAUnorm;
- break;
- case RGBA_S3TC_DXT5_Format:
- formatGPU = ( transfer === SRGBTransfer ) ? GPUTextureFormat.BC3RGBAUnormSRGB : GPUTextureFormat.BC3RGBAUnorm;
- break;
- case RED_RGTC1_Format:
- formatGPU = GPUTextureFormat.BC4RUnorm;
- break;
- case SIGNED_RED_RGTC1_Format:
- formatGPU = GPUTextureFormat.BC4RSnorm;
- break;
- case RED_GREEN_RGTC2_Format:
- formatGPU = GPUTextureFormat.BC5RGUnorm;
- break;
- case SIGNED_RED_GREEN_RGTC2_Format:
- formatGPU = GPUTextureFormat.BC5RGSnorm;
- break;
- case RGBA_BPTC_Format:
- formatGPU = ( transfer === SRGBTransfer ) ? GPUTextureFormat.BC7RGBAUnormSRGB : GPUTextureFormat.BC7RGBAUnorm;
- break;
- case RGB_ETC2_Format:
- case RGB_ETC1_Format:
- formatGPU = ( transfer === SRGBTransfer ) ? GPUTextureFormat.ETC2RGB8UnormSRGB : GPUTextureFormat.ETC2RGB8Unorm;
- break;
- case RGBA_ETC2_EAC_Format:
- formatGPU = ( transfer === SRGBTransfer ) ? GPUTextureFormat.ETC2RGBA8UnormSRGB : GPUTextureFormat.ETC2RGBA8Unorm;
- break;
- case R11_EAC_Format:
- formatGPU = GPUTextureFormat.EACR11Unorm;
- break;
- case SIGNED_R11_EAC_Format:
- formatGPU = GPUTextureFormat.EACR11Snorm;
- break;
- case RG11_EAC_Format:
- formatGPU = GPUTextureFormat.EACRG11Unorm;
- break;
- case SIGNED_RG11_EAC_Format:
- formatGPU = GPUTextureFormat.EACRG11Snorm;
- break;
- case RGBA_ASTC_4x4_Format:
- formatGPU = ( transfer === SRGBTransfer ) ? GPUTextureFormat.ASTC4x4UnormSRGB : GPUTextureFormat.ASTC4x4Unorm;
- break;
- case RGBA_ASTC_5x4_Format:
- formatGPU = ( transfer === SRGBTransfer ) ? GPUTextureFormat.ASTC5x4UnormSRGB : GPUTextureFormat.ASTC5x4Unorm;
- break;
- case RGBA_ASTC_5x5_Format:
- formatGPU = ( transfer === SRGBTransfer ) ? GPUTextureFormat.ASTC5x5UnormSRGB : GPUTextureFormat.ASTC5x5Unorm;
- break;
- case RGBA_ASTC_6x5_Format:
- formatGPU = ( transfer === SRGBTransfer ) ? GPUTextureFormat.ASTC6x5UnormSRGB : GPUTextureFormat.ASTC6x5Unorm;
- break;
- case RGBA_ASTC_6x6_Format:
- formatGPU = ( transfer === SRGBTransfer ) ? GPUTextureFormat.ASTC6x6UnormSRGB : GPUTextureFormat.ASTC6x6Unorm;
- break;
- case RGBA_ASTC_8x5_Format:
- formatGPU = ( transfer === SRGBTransfer ) ? GPUTextureFormat.ASTC8x5UnormSRGB : GPUTextureFormat.ASTC8x5Unorm;
- break;
- case RGBA_ASTC_8x6_Format:
- formatGPU = ( transfer === SRGBTransfer ) ? GPUTextureFormat.ASTC8x6UnormSRGB : GPUTextureFormat.ASTC8x6Unorm;
- break;
- case RGBA_ASTC_8x8_Format:
- formatGPU = ( transfer === SRGBTransfer ) ? GPUTextureFormat.ASTC8x8UnormSRGB : GPUTextureFormat.ASTC8x8Unorm;
- break;
- case RGBA_ASTC_10x5_Format:
- formatGPU = ( transfer === SRGBTransfer ) ? GPUTextureFormat.ASTC10x5UnormSRGB : GPUTextureFormat.ASTC10x5Unorm;
- break;
- case RGBA_ASTC_10x6_Format:
- formatGPU = ( transfer === SRGBTransfer ) ? GPUTextureFormat.ASTC10x6UnormSRGB : GPUTextureFormat.ASTC10x6Unorm;
- break;
- case RGBA_ASTC_10x8_Format:
- formatGPU = ( transfer === SRGBTransfer ) ? GPUTextureFormat.ASTC10x8UnormSRGB : GPUTextureFormat.ASTC10x8Unorm;
- break;
- case RGBA_ASTC_10x10_Format:
- formatGPU = ( transfer === SRGBTransfer ) ? GPUTextureFormat.ASTC10x10UnormSRGB : GPUTextureFormat.ASTC10x10Unorm;
- break;
- case RGBA_ASTC_12x10_Format:
- formatGPU = ( transfer === SRGBTransfer ) ? GPUTextureFormat.ASTC12x10UnormSRGB : GPUTextureFormat.ASTC12x10Unorm;
- break;
- case RGBA_ASTC_12x12_Format:
- formatGPU = ( transfer === SRGBTransfer ) ? GPUTextureFormat.ASTC12x12UnormSRGB : GPUTextureFormat.ASTC12x12Unorm;
- break;
- case RGBAFormat:
- formatGPU = ( transfer === SRGBTransfer ) ? GPUTextureFormat.RGBA8UnormSRGB : GPUTextureFormat.RGBA8Unorm;
- break;
- default:
- error( 'WebGPURenderer: Unsupported texture format.', format );
- }
- } else {
- switch ( format ) {
- case RGBAFormat:
- switch ( type ) {
- case ByteType:
- formatGPU = GPUTextureFormat.RGBA8Snorm;
- break;
- case ShortType:
- formatGPU = GPUTextureFormat.RGBA16Sint;
- break;
- case UnsignedShortType:
- formatGPU = GPUTextureFormat.RGBA16Uint;
- break;
- case UnsignedIntType:
- formatGPU = GPUTextureFormat.RGBA32Uint;
- break;
- case IntType:
- formatGPU = GPUTextureFormat.RGBA32Sint;
- break;
- case UnsignedByteType:
- formatGPU = ( transfer === SRGBTransfer ) ? GPUTextureFormat.RGBA8UnormSRGB : GPUTextureFormat.RGBA8Unorm;
- break;
- case HalfFloatType:
- formatGPU = GPUTextureFormat.RGBA16Float;
- break;
- case FloatType:
- formatGPU = GPUTextureFormat.RGBA32Float;
- break;
- default:
- error( 'WebGPURenderer: Unsupported texture type with RGBAFormat.', type );
- }
- break;
- case RGBFormat:
- switch ( type ) {
- case UnsignedInt5999Type:
- formatGPU = GPUTextureFormat.RGB9E5UFloat;
- break;
- case UnsignedInt101111Type:
- formatGPU = GPUTextureFormat.RG11B10UFloat;
- break;
- default:
- error( 'WebGPURenderer: Unsupported texture type with RGBFormat.', type );
- }
- break;
- case RedFormat:
- switch ( type ) {
- case ByteType:
- formatGPU = GPUTextureFormat.R8Snorm;
- break;
- case ShortType:
- formatGPU = GPUTextureFormat.R16Sint;
- break;
- case UnsignedShortType:
- formatGPU = GPUTextureFormat.R16Uint;
- break;
- case UnsignedIntType:
- formatGPU = GPUTextureFormat.R32Uint;
- break;
- case IntType:
- formatGPU = GPUTextureFormat.R32Sint;
- break;
- case UnsignedByteType:
- formatGPU = GPUTextureFormat.R8Unorm;
- break;
- case HalfFloatType:
- formatGPU = GPUTextureFormat.R16Float;
- break;
- case FloatType:
- formatGPU = GPUTextureFormat.R32Float;
- break;
- default:
- error( 'WebGPURenderer: Unsupported texture type with RedFormat.', type );
- }
- break;
- case RGFormat:
- switch ( type ) {
- case ByteType:
- formatGPU = GPUTextureFormat.RG8Snorm;
- break;
- case ShortType:
- formatGPU = GPUTextureFormat.RG16Sint;
- break;
- case UnsignedShortType:
- formatGPU = GPUTextureFormat.RG16Uint;
- break;
- case UnsignedIntType:
- formatGPU = GPUTextureFormat.RG32Uint;
- break;
- case IntType:
- formatGPU = GPUTextureFormat.RG32Sint;
- break;
- case UnsignedByteType:
- formatGPU = GPUTextureFormat.RG8Unorm;
- break;
- case HalfFloatType:
- formatGPU = GPUTextureFormat.RG16Float;
- break;
- case FloatType:
- formatGPU = GPUTextureFormat.RG32Float;
- break;
- default:
- error( 'WebGPURenderer: Unsupported texture type with RGFormat.', type );
- }
- break;
- case DepthFormat:
- switch ( type ) {
- case UnsignedShortType:
- formatGPU = GPUTextureFormat.Depth16Unorm;
- break;
- case UnsignedIntType:
- formatGPU = GPUTextureFormat.Depth24Plus;
- break;
- case FloatType:
- formatGPU = GPUTextureFormat.Depth32Float;
- break;
- default:
- error( 'WebGPURenderer: Unsupported texture type with DepthFormat.', type );
- }
- break;
- case DepthStencilFormat:
- switch ( type ) {
- case UnsignedInt248Type:
- formatGPU = GPUTextureFormat.Depth24PlusStencil8;
- break;
- case FloatType:
- if ( device && device.features.has( GPUFeatureName.Depth32FloatStencil8 ) === false ) {
- error( 'WebGPURenderer: Depth textures with DepthStencilFormat + FloatType can only be used with the "depth32float-stencil8" GPU feature.' );
- }
- formatGPU = GPUTextureFormat.Depth32FloatStencil8;
- break;
- default:
- error( 'WebGPURenderer: Unsupported texture type with DepthStencilFormat.', type );
- }
- break;
- case RedIntegerFormat:
- switch ( type ) {
- case IntType:
- formatGPU = GPUTextureFormat.R32Sint;
- break;
- case UnsignedIntType:
- formatGPU = GPUTextureFormat.R32Uint;
- break;
- default:
- error( 'WebGPURenderer: Unsupported texture type with RedIntegerFormat.', type );
- }
- break;
- case RGIntegerFormat:
- switch ( type ) {
- case IntType:
- formatGPU = GPUTextureFormat.RG32Sint;
- break;
- case UnsignedIntType:
- formatGPU = GPUTextureFormat.RG32Uint;
- break;
- default:
- error( 'WebGPURenderer: Unsupported texture type with RGIntegerFormat.', type );
- }
- break;
- case RGBAIntegerFormat:
- switch ( type ) {
- case IntType:
- formatGPU = GPUTextureFormat.RGBA32Sint;
- break;
- case UnsignedIntType:
- formatGPU = GPUTextureFormat.RGBA32Uint;
- break;
- default:
- error( 'WebGPURenderer: Unsupported texture type with RGBAIntegerFormat.', type );
- }
- break;
- default:
- error( 'WebGPURenderer: Unsupported texture format.', format );
- }
- }
- return formatGPU;
- }
- const declarationRegexp = /^[fn]*\s*([a-z_0-9]+)?\s*\(([\s\S]*?)\)\s*[\-\>]*\s*([a-z_0-9]+(?:<[\s\S]+?>)?)/i;
- const propertiesRegexp = /([a-z_0-9]+)\s*:\s*([a-z_0-9]+(?:<[\s\S]+?>)?)/ig;
- const wgslTypeLib$1 = {
- 'f32': 'float',
- 'i32': 'int',
- 'u32': 'uint',
- 'bool': 'bool',
- 'vec2<f32>': 'vec2',
- 'vec2<i32>': 'ivec2',
- 'vec2<u32>': 'uvec2',
- 'vec2<bool>': 'bvec2',
- 'vec2f': 'vec2',
- 'vec2i': 'ivec2',
- 'vec2u': 'uvec2',
- 'vec2b': 'bvec2',
- 'vec3<f32>': 'vec3',
- 'vec3<i32>': 'ivec3',
- 'vec3<u32>': 'uvec3',
- 'vec3<bool>': 'bvec3',
- 'vec3f': 'vec3',
- 'vec3i': 'ivec3',
- 'vec3u': 'uvec3',
- 'vec3b': 'bvec3',
- 'vec4<f32>': 'vec4',
- 'vec4<i32>': 'ivec4',
- 'vec4<u32>': 'uvec4',
- 'vec4<bool>': 'bvec4',
- 'vec4f': 'vec4',
- 'vec4i': 'ivec4',
- 'vec4u': 'uvec4',
- 'vec4b': 'bvec4',
- 'mat2x2<f32>': 'mat2',
- 'mat2x2f': 'mat2',
- 'mat3x3<f32>': 'mat3',
- 'mat3x3f': 'mat3',
- 'mat4x4<f32>': 'mat4',
- 'mat4x4f': 'mat4',
- 'sampler': 'sampler',
- 'texture_1d': 'texture',
- 'texture_2d': 'texture',
- 'texture_2d_array': 'texture',
- 'texture_multisampled_2d': 'cubeTexture',
- 'texture_depth_2d': 'depthTexture',
- 'texture_depth_2d_array': 'depthTexture',
- 'texture_depth_multisampled_2d': 'depthTexture',
- 'texture_depth_cube': 'depthTexture',
- 'texture_depth_cube_array': 'depthTexture',
- 'texture_3d': 'texture3D',
- 'texture_cube': 'cubeTexture',
- 'texture_cube_array': 'cubeTexture',
- 'texture_storage_1d': 'storageTexture',
- 'texture_storage_2d': 'storageTexture',
- 'texture_storage_2d_array': 'storageTexture',
- 'texture_storage_3d': 'storageTexture'
- };
- const parse = ( source ) => {
- source = source.trim();
- const declaration = source.match( declarationRegexp );
- if ( declaration !== null && declaration.length === 4 ) {
- const inputsCode = declaration[ 2 ];
- const propsMatches = [];
- let match = null;
- while ( ( match = propertiesRegexp.exec( inputsCode ) ) !== null ) {
- propsMatches.push( { name: match[ 1 ], type: match[ 2 ] } );
- }
- // Process matches to correctly pair names and types
- const inputs = [];
- for ( let i = 0; i < propsMatches.length; i ++ ) {
- const { name, type } = propsMatches[ i ];
- let resolvedType = type;
- if ( resolvedType.startsWith( 'ptr' ) ) {
- resolvedType = 'pointer';
- } else {
- if ( resolvedType.startsWith( 'texture' ) ) {
- resolvedType = type.split( '<' )[ 0 ];
- }
- resolvedType = wgslTypeLib$1[ resolvedType ];
- }
- inputs.push( new NodeFunctionInput( resolvedType, name ) );
- }
- const blockCode = source.substring( declaration[ 0 ].length );
- const outputType = declaration[ 3 ] || 'void';
- const name = declaration[ 1 ] !== undefined ? declaration[ 1 ] : '';
- const type = wgslTypeLib$1[ outputType ] || outputType;
- return {
- type,
- inputs,
- name,
- inputsCode,
- blockCode,
- outputType
- };
- } else {
- throw new Error( 'FunctionNode: Function is not a WGSL code.' );
- }
- };
- /**
- * This class represents a WSL node function.
- *
- * @augments NodeFunction
- */
- class WGSLNodeFunction extends NodeFunction {
- /**
- * Constructs a new WGSL node function.
- *
- * @param {string} source - The WGSL source.
- */
- constructor( source ) {
- const { type, inputs, name, inputsCode, blockCode, outputType } = parse( source );
- super( type, inputs, name );
- this.inputsCode = inputsCode;
- this.blockCode = blockCode;
- this.outputType = outputType;
- }
- /**
- * This method returns the WGSL code of the node function.
- *
- * @param {string} [name=this.name] - The function's name.
- * @return {string} The shader code.
- */
- getCode( name = this.name ) {
- const outputType = this.outputType !== 'void' ? '-> ' + this.outputType : '';
- return `fn ${ name } ( ${ this.inputsCode.trim() } ) ${ outputType }` + this.blockCode;
- }
- }
- /**
- * A WGSL node parser.
- *
- * @augments NodeParser
- */
- class WGSLNodeParser extends NodeParser {
- /**
- * The method parses the given WGSL code an returns a node function.
- *
- * @param {string} source - The WGSL code.
- * @return {WGSLNodeFunction} A node function.
- */
- parseFunction( source ) {
- return new WGSLNodeFunction( source );
- }
- }
- const accessNames = {
- [ NodeAccess.READ_ONLY ]: 'read',
- [ NodeAccess.WRITE_ONLY ]: 'write',
- [ NodeAccess.READ_WRITE ]: 'read_write'
- };
- const wrapNames = {
- [ RepeatWrapping ]: 'repeat',
- [ ClampToEdgeWrapping ]: 'clamp',
- [ MirroredRepeatWrapping ]: 'mirror'
- };
- const gpuShaderStageLib = {
- 'vertex': GPUShaderStage.VERTEX,
- 'fragment': GPUShaderStage.FRAGMENT,
- 'compute': GPUShaderStage.COMPUTE
- };
- const supports = {
- instance: true,
- swizzleAssign: false,
- storageBuffer: true
- };
- const wgslFnOpLib = {
- '^^': 'tsl_xor'
- };
- const wgslTypeLib = {
- float: 'f32',
- int: 'i32',
- uint: 'u32',
- bool: 'bool',
- color: 'vec3<f32>',
- vec2: 'vec2<f32>',
- ivec2: 'vec2<i32>',
- uvec2: 'vec2<u32>',
- bvec2: 'vec2<bool>',
- vec3: 'vec3<f32>',
- ivec3: 'vec3<i32>',
- uvec3: 'vec3<u32>',
- bvec3: 'vec3<bool>',
- vec4: 'vec4<f32>',
- ivec4: 'vec4<i32>',
- uvec4: 'vec4<u32>',
- bvec4: 'vec4<bool>',
- mat2: 'mat2x2<f32>',
- mat3: 'mat3x3<f32>',
- mat4: 'mat4x4<f32>'
- };
- const wgslCodeCache = {};
- const wgslPolyfill = {
- tsl_xor: new CodeNode( 'fn tsl_xor( a : bool, b : bool ) -> bool { return ( a || b ) && !( a && b ); }' ),
- mod_float: new CodeNode( 'fn tsl_mod_float( x : f32, y : f32 ) -> f32 { return x - y * floor( x / y ); }' ),
- mod_vec2: new CodeNode( 'fn tsl_mod_vec2( x : vec2f, y : vec2f ) -> vec2f { return x - y * floor( x / y ); }' ),
- mod_vec3: new CodeNode( 'fn tsl_mod_vec3( x : vec3f, y : vec3f ) -> vec3f { return x - y * floor( x / y ); }' ),
- mod_vec4: new CodeNode( 'fn tsl_mod_vec4( x : vec4f, y : vec4f ) -> vec4f { return x - y * floor( x / y ); }' ),
- equals_bool: new CodeNode( 'fn tsl_equals_bool( a : bool, b : bool ) -> bool { return a == b; }' ),
- equals_bvec2: new CodeNode( 'fn tsl_equals_bvec2( a : vec2f, b : vec2f ) -> vec2<bool> { return vec2<bool>( a.x == b.x, a.y == b.y ); }' ),
- equals_bvec3: new CodeNode( 'fn tsl_equals_bvec3( a : vec3f, b : vec3f ) -> vec3<bool> { return vec3<bool>( a.x == b.x, a.y == b.y, a.z == b.z ); }' ),
- equals_bvec4: new CodeNode( 'fn tsl_equals_bvec4( a : vec4f, b : vec4f ) -> vec4<bool> { return vec4<bool>( a.x == b.x, a.y == b.y, a.z == b.z, a.w == b.w ); }' ),
- repeatWrapping_float: new CodeNode( 'fn tsl_repeatWrapping_float( coord: f32 ) -> f32 { return fract( coord ); }' ),
- mirrorWrapping_float: new CodeNode( 'fn tsl_mirrorWrapping_float( coord: f32 ) -> f32 { let mirrored = fract( coord * 0.5 ) * 2.0; return 1.0 - abs( 1.0 - mirrored ); }' ),
- clampWrapping_float: new CodeNode( 'fn tsl_clampWrapping_float( coord: f32 ) -> f32 { return clamp( coord, 0.0, 1.0 ); }' ),
- biquadraticTexture: new CodeNode( /* wgsl */`
- fn tsl_biquadraticTexture( map : texture_2d<f32>, coord : vec2f, iRes : vec2u, level : u32 ) -> vec4f {
- let res = vec2f( iRes );
- let uvScaled = coord * res;
- let uvWrapping = ( ( uvScaled % res ) + res ) % res;
- // https://www.shadertoy.com/view/WtyXRy
- let uv = uvWrapping - 0.5;
- let iuv = floor( uv );
- let f = fract( uv );
- let rg1 = textureLoad( map, vec2u( iuv + vec2( 0.5, 0.5 ) ) % iRes, level );
- let rg2 = textureLoad( map, vec2u( iuv + vec2( 1.5, 0.5 ) ) % iRes, level );
- let rg3 = textureLoad( map, vec2u( iuv + vec2( 0.5, 1.5 ) ) % iRes, level );
- let rg4 = textureLoad( map, vec2u( iuv + vec2( 1.5, 1.5 ) ) % iRes, level );
- return mix( mix( rg1, rg2, f.x ), mix( rg3, rg4, f.x ), f.y );
- }
- ` )
- };
- const wgslMethods = {
- dFdx: 'dpdx',
- dFdy: '- dpdy',
- mod_float: 'tsl_mod_float',
- mod_vec2: 'tsl_mod_vec2',
- mod_vec3: 'tsl_mod_vec3',
- mod_vec4: 'tsl_mod_vec4',
- equals_bool: 'tsl_equals_bool',
- equals_bvec2: 'tsl_equals_bvec2',
- equals_bvec3: 'tsl_equals_bvec3',
- equals_bvec4: 'tsl_equals_bvec4',
- inversesqrt: 'inverseSqrt',
- bitcast: 'bitcast<f32>',
- floatpack_snorm_2x16: 'pack2x16snorm',
- floatpack_unorm_2x16: 'pack2x16unorm',
- floatpack_float16_2x16: 'pack2x16float',
- floatunpack_snorm_2x16: 'unpack2x16snorm',
- floatunpack_unorm_2x16: 'unpack2x16unorm',
- floatunpack_float16_2x16: 'unpack2x16float'
- };
- //
- let diagnostics = '';
- if ( ( typeof navigator !== 'undefined' && /Firefox|Deno/g.test( navigator.userAgent ) ) !== true ) {
- diagnostics += 'diagnostic( off, derivative_uniformity );\n';
- }
- /**
- * A node builder targeting WGSL.
- *
- * This module generates WGSL shader code from node materials and also
- * generates the respective bindings and vertex buffer definitions. These
- * data are later used by the renderer to create render and compute pipelines
- * for render objects.
- *
- * @augments NodeBuilder
- */
- class WGSLNodeBuilder extends NodeBuilder {
- /**
- * Constructs a new WGSL node builder renderer.
- *
- * @param {Object3D} object - The 3D object.
- * @param {Renderer} renderer - The renderer.
- */
- constructor( object, renderer ) {
- super( object, renderer, new WGSLNodeParser() );
- /**
- * A dictionary that holds for each shader stage ('vertex', 'fragment', 'compute')
- * another dictionary which manages UBOs per group ('render','frame','object').
- *
- * @type {Object<string,Object<string,NodeUniformsGroup>>}
- */
- this.uniformGroups = {};
- /**
- * A dictionary that holds for each shader stage a Map of builtins.
- *
- * @type {Object<string,Map<string,Object>>}
- */
- this.builtins = {};
- /**
- * A dictionary that holds for each shader stage a Set of directives.
- *
- * @type {Object<string,Set<string>>}
- */
- this.directives = {};
- /**
- * A map for managing scope arrays. Only relevant for when using
- * {@link WorkgroupInfoNode} in context of compute shaders.
- *
- * @type {Map<string,Object>}
- */
- this.scopedArrays = new Map();
- }
- /**
- * Generates the WGSL snippet for sampled textures.
- *
- * @private
- * @param {Texture} texture - The texture.
- * @param {string} textureProperty - The name of the texture uniform in the shader.
- * @param {string} uvSnippet - A WGSL snippet that represents texture coordinates used for sampling.
- * @param {?string} depthSnippet - A WGSL snippet that represents 0-based texture array index to sample.
- * @param {?string} offsetSnippet - A WGSL snippet that represents the offset that will be applied to the unnormalized texture coordinate before sampling the texture.
- * @param {string} [shaderStage=this.shaderStage] - The shader stage this code snippet is generated for.
- * @return {string} The WGSL snippet.
- */
- _generateTextureSample( texture, textureProperty, uvSnippet, depthSnippet, offsetSnippet, shaderStage = this.shaderStage ) {
- if ( shaderStage === 'fragment' ) {
- if ( depthSnippet ) {
- if ( offsetSnippet ) {
- return `textureSample( ${ textureProperty }, ${ textureProperty }_sampler, ${ uvSnippet }, ${ depthSnippet }, ${ offsetSnippet } )`;
- }
- return `textureSample( ${ textureProperty }, ${ textureProperty }_sampler, ${ uvSnippet }, ${ depthSnippet } )`;
- } else {
- if ( offsetSnippet ) {
- return `textureSample( ${ textureProperty }, ${ textureProperty }_sampler, ${ uvSnippet }, ${ offsetSnippet } )`;
- }
- return `textureSample( ${ textureProperty }, ${ textureProperty }_sampler, ${ uvSnippet } )`;
- }
- } else {
- return this.generateTextureSampleLevel( texture, textureProperty, uvSnippet, '0', depthSnippet );
- }
- }
- /**
- * Generates the WGSL snippet when sampling textures with explicit mip level.
- *
- * @private
- * @param {Texture} texture - The texture.
- * @param {string} textureProperty - The name of the texture uniform in the shader.
- * @param {string} uvSnippet - A WGSL snippet that represents texture coordinates used for sampling.
- * @param {string} levelSnippet - A WGSL snippet that represents the mip level, with level 0 containing a full size version of the texture.
- * @param {string} depthSnippet - A WGSL snippet that represents 0-based texture array index to sample.
- * @param {?string} offsetSnippet - A WGSL snippet that represents the offset that will be applied to the unnormalized texture coordinate before sampling the texture.
- * @return {string} The WGSL snippet.
- */
- generateTextureSampleLevel( texture, textureProperty, uvSnippet, levelSnippet, depthSnippet, offsetSnippet ) {
- if ( this.isUnfilterable( texture ) === false ) {
- if ( offsetSnippet ) {
- return `textureSampleLevel( ${ textureProperty }, ${ textureProperty }_sampler, ${ uvSnippet }, ${ levelSnippet }, ${ offsetSnippet } )`;
- }
- return `textureSampleLevel( ${ textureProperty }, ${ textureProperty }_sampler, ${ uvSnippet }, ${ levelSnippet } )`;
- } else if ( this.isFilteredTexture( texture ) ) {
- return this.generateFilteredTexture( texture, textureProperty, uvSnippet, offsetSnippet, levelSnippet );
- } else {
- return this.generateTextureLod( texture, textureProperty, uvSnippet, depthSnippet, offsetSnippet, levelSnippet );
- }
- }
- /**
- * Generates a wrap function used in context of textures.
- *
- * @param {Texture} texture - The texture to generate the function for.
- * @return {string} The name of the generated function.
- */
- generateWrapFunction( texture ) {
- const functionName = `tsl_coord_${ wrapNames[ texture.wrapS ] }S_${ wrapNames[ texture.wrapT ] }_${ texture.is3DTexture || texture.isData3DTexture ? '3d' : '2d' }T`;
- let nodeCode = wgslCodeCache[ functionName ];
- if ( nodeCode === undefined ) {
- const includes = [];
- // For 3D textures, use vec3f; for texture arrays, keep vec2f since array index is separate
- const coordType = texture.is3DTexture || texture.isData3DTexture ? 'vec3f' : 'vec2f';
- let code = `fn ${ functionName }( coord : ${ coordType } ) -> ${ coordType } {\n\n\treturn ${ coordType }(\n`;
- const addWrapSnippet = ( wrap, axis ) => {
- if ( wrap === RepeatWrapping ) {
- includes.push( wgslPolyfill.repeatWrapping_float );
- code += `\t\ttsl_repeatWrapping_float( coord.${ axis } )`;
- } else if ( wrap === ClampToEdgeWrapping ) {
- includes.push( wgslPolyfill.clampWrapping_float );
- code += `\t\ttsl_clampWrapping_float( coord.${ axis } )`;
- } else if ( wrap === MirroredRepeatWrapping ) {
- includes.push( wgslPolyfill.mirrorWrapping_float );
- code += `\t\ttsl_mirrorWrapping_float( coord.${ axis } )`;
- } else {
- code += `\t\tcoord.${ axis }`;
- warn( `WebGPURenderer: Unsupported texture wrap type "${ wrap }" for vertex shader.` );
- }
- };
- addWrapSnippet( texture.wrapS, 'x' );
- code += ',\n';
- addWrapSnippet( texture.wrapT, 'y' );
- if ( texture.is3DTexture || texture.isData3DTexture ) {
- code += ',\n';
- addWrapSnippet( texture.wrapR, 'z' );
- }
- code += '\n\t);\n\n}\n';
- wgslCodeCache[ functionName ] = nodeCode = new CodeNode( code, includes );
- }
- nodeCode.build( this );
- return functionName;
- }
- /**
- * Generates the array declaration string.
- *
- * @param {string} type - The type.
- * @param {?number} [count] - The count.
- * @return {string} The generated value as a shader string.
- */
- generateArrayDeclaration( type, count ) {
- return `array< ${ this.getType( type ) }, ${ count } >`;
- }
- /**
- * Generates a WGSL variable that holds the texture dimension of the given texture.
- * It also returns information about the number of layers (elements) of an arrayed
- * texture as well as the cube face count of cube textures.
- *
- * @param {Texture} texture - The texture to generate the function for.
- * @param {string} textureProperty - The name of the video texture uniform in the shader.
- * @param {string} levelSnippet - A WGSL snippet that represents the mip level, with level 0 containing a full size version of the texture.
- * @return {string} The name of the dimension variable.
- */
- generateTextureDimension( texture, textureProperty, levelSnippet ) {
- const textureData = this.getDataFromNode( texture, this.shaderStage, this.globalCache );
- if ( textureData.dimensionsSnippet === undefined ) textureData.dimensionsSnippet = {};
- let textureDimensionNode = textureData.dimensionsSnippet[ levelSnippet ];
- if ( textureData.dimensionsSnippet[ levelSnippet ] === undefined ) {
- let textureDimensionsParams;
- let dimensionType;
- const { primarySamples } = this.renderer.backend.utils.getTextureSampleData( texture );
- const isMultisampled = primarySamples > 1;
- if ( texture.is3DTexture || texture.isData3DTexture ) {
- dimensionType = 'vec3<u32>';
- } else {
- // Regular 2D textures, depth textures, etc.
- dimensionType = 'vec2<u32>';
- }
- // Build parameters string based on texture type and multisampling
- if ( isMultisampled || texture.isStorageTexture ) {
- textureDimensionsParams = textureProperty;
- } else {
- textureDimensionsParams = `${textureProperty}${levelSnippet ? `, u32( ${ levelSnippet } )` : ''}`;
- }
- textureDimensionNode = new VarNode( new ExpressionNode( `textureDimensions( ${ textureDimensionsParams } )`, dimensionType ) );
- textureData.dimensionsSnippet[ levelSnippet ] = textureDimensionNode;
- if ( texture.isArrayTexture || texture.isDataArrayTexture || texture.is3DTexture || texture.isData3DTexture ) {
- textureData.arrayLayerCount = new VarNode(
- new ExpressionNode(
- `textureNumLayers(${textureProperty})`,
- 'u32'
- )
- );
- }
- // For cube textures, we know it's always 6 faces
- if ( texture.isTextureCube ) {
- textureData.cubeFaceCount = new VarNode(
- new ExpressionNode( '6u', 'u32' )
- );
- }
- }
- return textureDimensionNode.build( this );
- }
- /**
- * Generates the WGSL snippet for a manual filtered texture.
- *
- * @param {Texture} texture - The texture.
- * @param {string} textureProperty - The name of the texture uniform in the shader.
- * @param {string} uvSnippet - A WGSL snippet that represents texture coordinates used for sampling.
- * @param {?string} offsetSnippet - A WGSL snippet that represents the offset that will be applied to the unnormalized texture coordinate before sampling the texture.
- * @param {string} [levelSnippet='0u'] - A WGSL snippet that represents the mip level, with level 0 containing a full size version of the texture.
- * @return {string} The WGSL snippet.
- */
- generateFilteredTexture( texture, textureProperty, uvSnippet, offsetSnippet, levelSnippet = '0u' ) {
- this._include( 'biquadraticTexture' );
- const wrapFunction = this.generateWrapFunction( texture );
- const textureDimension = this.generateTextureDimension( texture, textureProperty, levelSnippet );
- if ( offsetSnippet ) {
- uvSnippet = `${ uvSnippet } + vec2<f32>(${ offsetSnippet }) / ${ textureDimension }`;
- }
- return `tsl_biquadraticTexture( ${ textureProperty }, ${ wrapFunction }( ${ uvSnippet } ), ${ textureDimension }, u32( ${ levelSnippet } ) )`;
- }
- /**
- * Generates the WGSL snippet for a texture lookup with explicit level-of-detail.
- * Since it's a lookup, no sampling or filtering is applied.
- *
- * @param {Texture} texture - The texture.
- * @param {string} textureProperty - The name of the texture uniform in the shader.
- * @param {string} uvSnippet - A WGSL snippet that represents texture coordinates used for sampling.
- * @param {?string} depthSnippet - A WGSL snippet that represents 0-based texture array index to sample.
- * @param {?string} offsetSnippet - A WGSL snippet that represents the offset that will be applied to the unnormalized texture coordinate before sampling the texture.
- * @param {string} [levelSnippet='0u'] - A WGSL snippet that represents the mip level, with level 0 containing a full size version of the texture.
- * @return {string} The WGSL snippet.
- */
- generateTextureLod( texture, textureProperty, uvSnippet, depthSnippet, offsetSnippet, levelSnippet = '0u' ) {
- const wrapFunction = this.generateWrapFunction( texture );
- const textureDimension = this.generateTextureDimension( texture, textureProperty, levelSnippet );
- const vecType = texture.is3DTexture || texture.isData3DTexture ? 'vec3' : 'vec2';
- if ( offsetSnippet ) {
- uvSnippet = `${ uvSnippet } + ${ vecType }<f32>(${ offsetSnippet }) / ${ vecType }<f32>( ${ textureDimension } )`;
- }
- const coordSnippet = `${ vecType }<u32>( ${ wrapFunction }( ${ uvSnippet } ) * ${ vecType }<f32>( ${ textureDimension } ) )`;
- return this.generateTextureLoad( texture, textureProperty, coordSnippet, levelSnippet, depthSnippet, null );
- }
- /**
- * Generates the WGSL snippet that reads a single texel from a texture without sampling or filtering.
- *
- * @param {Texture} texture - The texture.
- * @param {string} textureProperty - The name of the texture uniform in the shader.
- * @param {string} uvIndexSnippet - A WGSL snippet that represents texture coordinates used for sampling.
- * @param {?string} levelSnippet - A WGSL snippet that represents the mip level, with level 0 containing a full size version of the texture.
- * @param {?string} depthSnippet - A WGSL snippet that represents 0-based texture array index to sample.
- * @param {?string} offsetSnippet - A WGSL snippet that represents the offset that will be applied to the unnormalized texture coordinate before sampling the texture.
- * @return {string} The WGSL snippet.
- */
- generateTextureLoad( texture, textureProperty, uvIndexSnippet, levelSnippet, depthSnippet, offsetSnippet ) {
- if ( levelSnippet === null ) levelSnippet = '0u';
- if ( offsetSnippet ) {
- uvIndexSnippet = `${ uvIndexSnippet } + ${ offsetSnippet }`;
- }
- let snippet;
- if ( depthSnippet ) {
- snippet = `textureLoad( ${ textureProperty }, ${ uvIndexSnippet }, ${ depthSnippet }, u32( ${ levelSnippet } ) )`;
- } else {
- snippet = `textureLoad( ${ textureProperty }, ${ uvIndexSnippet }, u32( ${ levelSnippet } ) )`;
- if ( this.renderer.backend.compatibilityMode && texture.isDepthTexture ) {
- snippet += '.x';
- }
- }
- return snippet;
- }
- /**
- * Generates the WGSL snippet that writes a single texel to a texture.
- *
- * @param {Texture} texture - The texture.
- * @param {string} textureProperty - The name of the texture uniform in the shader.
- * @param {string} uvIndexSnippet - A WGSL snippet that represents texture coordinates used for sampling.
- * @param {?string} depthSnippet - A WGSL snippet that represents 0-based texture array index to sample.
- * @param {string} valueSnippet - A WGSL snippet that represent the new texel value.
- * @return {string} The WGSL snippet.
- */
- generateTextureStore( texture, textureProperty, uvIndexSnippet, depthSnippet, valueSnippet ) {
- let snippet;
- if ( depthSnippet ) {
- snippet = `textureStore( ${ textureProperty }, ${ uvIndexSnippet }, ${ depthSnippet }, ${ valueSnippet } )`;
- } else {
- snippet = `textureStore( ${ textureProperty }, ${ uvIndexSnippet }, ${ valueSnippet } )`;
- }
- return snippet;
- }
- /**
- * Returns `true` if the sampled values of the given texture should be compared against a reference value.
- *
- * @param {Texture} texture - The texture.
- * @return {boolean} Whether the sampled values of the given texture should be compared against a reference value or not.
- */
- isSampleCompare( texture ) {
- return texture.isDepthTexture === true && texture.compareFunction !== null;
- }
- /**
- * Returns `true` if the given texture is unfilterable.
- *
- * @param {Texture} texture - The texture.
- * @return {boolean} Whether the given texture is unfilterable or not.
- */
- isUnfilterable( texture ) {
- return this.getComponentTypeFromTexture( texture ) !== 'float' ||
- ( ! this.isAvailable( 'float32Filterable' ) && texture.isDataTexture === true && texture.type === FloatType ) ||
- ( this.isSampleCompare( texture ) === false && texture.minFilter === NearestFilter && texture.magFilter === NearestFilter ) ||
- this.renderer.backend.utils.getTextureSampleData( texture ).primarySamples > 1;
- }
- /**
- * Generates the WGSL snippet for sampling/loading the given texture.
- *
- * @param {Texture} texture - The texture.
- * @param {string} textureProperty - The name of the texture uniform in the shader.
- * @param {string} uvSnippet - A WGSL snippet that represents texture coordinates used for sampling.
- * @param {?string} depthSnippet - A WGSL snippet that represents 0-based texture array index to sample.
- * @param {?string} offsetSnippet - A WGSL snippet that represents the offset that will be applied to the unnormalized texture coordinate before sampling the texture.
- * @param {string} [shaderStage=this.shaderStage] - The shader stage this code snippet is generated for.
- * @return {string} The WGSL snippet.
- */
- generateTexture( texture, textureProperty, uvSnippet, depthSnippet, offsetSnippet, shaderStage = this.shaderStage ) {
- let snippet = null;
- if ( this.isUnfilterable( texture ) ) {
- snippet = this.generateTextureLod( texture, textureProperty, uvSnippet, depthSnippet, offsetSnippet, '0', shaderStage );
- } else {
- snippet = this._generateTextureSample( texture, textureProperty, uvSnippet, depthSnippet, offsetSnippet, shaderStage );
- }
- return snippet;
- }
- /**
- * Generates the WGSL snippet for sampling/loading the given texture using explicit gradients.
- *
- * @param {Texture} texture - The texture.
- * @param {string} textureProperty - The name of the texture uniform in the shader.
- * @param {string} uvSnippet - A WGSL snippet that represents texture coordinates used for sampling.
- * @param {Array<string>} gradSnippet - An array holding both gradient WGSL snippets.
- * @param {?string} depthSnippet - A WGSL snippet that represents 0-based texture array index to sample.
- * @param {?string} offsetSnippet - A WGSL snippet that represents the offset that will be applied to the unnormalized texture coordinate before sampling the texture.
- * @param {string} [shaderStage=this.shaderStage] - The shader stage this code snippet is generated for.
- * @return {string} The WGSL snippet.
- */
- generateTextureGrad( texture, textureProperty, uvSnippet, gradSnippet, depthSnippet, offsetSnippet, shaderStage = this.shaderStage ) {
- if ( shaderStage === 'fragment' ) {
- // TODO handle i32 or u32 --> uvSnippet, array_index: A, ddx, ddy
- if ( offsetSnippet ) {
- return `textureSampleGrad( ${ textureProperty }, ${ textureProperty }_sampler, ${ uvSnippet }, ${ gradSnippet[ 0 ] }, ${ gradSnippet[ 1 ] }, ${ offsetSnippet } )`;
- }
- return `textureSampleGrad( ${ textureProperty }, ${ textureProperty }_sampler, ${ uvSnippet }, ${ gradSnippet[ 0 ] }, ${ gradSnippet[ 1 ] } )`;
- } else {
- error( `WebGPURenderer: THREE.TextureNode.gradient() does not support ${ shaderStage } shader.` );
- }
- }
- /**
- * Generates the WGSL snippet for sampling a depth texture and comparing the sampled depth values
- * against a reference value.
- *
- * @param {Texture} texture - The texture.
- * @param {string} textureProperty - The name of the texture uniform in the shader.
- * @param {string} uvSnippet - A WGSL snippet that represents texture coordinates used for sampling.
- * @param {string} compareSnippet - A WGSL snippet that represents the reference value.
- * @param {?string} depthSnippet - A WGSL snippet that represents 0-based texture array index to sample.
- * @param {?string} offsetSnippet - A WGSL snippet that represents the offset that will be applied to the unnormalized texture coordinate before sampling the texture.
- * @param {string} [shaderStage=this.shaderStage] - The shader stage this code snippet is generated for.
- * @return {string} The WGSL snippet.
- */
- generateTextureCompare( texture, textureProperty, uvSnippet, compareSnippet, depthSnippet, offsetSnippet, shaderStage = this.shaderStage ) {
- if ( shaderStage === 'fragment' ) {
- if ( texture.isDepthTexture === true && texture.isArrayTexture === true ) {
- if ( offsetSnippet ) {
- return `textureSampleCompare( ${ textureProperty }, ${ textureProperty }_sampler, ${ uvSnippet }, ${ depthSnippet }, ${ compareSnippet }, ${ offsetSnippet } )`;
- }
- return `textureSampleCompare( ${ textureProperty }, ${ textureProperty }_sampler, ${ uvSnippet }, ${ depthSnippet }, ${ compareSnippet } )`;
- }
- if ( offsetSnippet ) {
- return `textureSampleCompare( ${ textureProperty }, ${ textureProperty }_sampler, ${ uvSnippet }, ${ compareSnippet }, ${ offsetSnippet } )`;
- }
- return `textureSampleCompare( ${ textureProperty }, ${ textureProperty }_sampler, ${ uvSnippet }, ${ compareSnippet } )`;
- } else {
- error( `WebGPURenderer: THREE.DepthTexture.compareFunction() does not support ${ shaderStage } shader.` );
- }
- }
- /**
- * Generates the WGSL snippet when sampling textures with explicit mip level.
- *
- * @param {Texture} texture - The texture.
- * @param {string} textureProperty - The name of the texture uniform in the shader.
- * @param {string} uvSnippet - A WGSL snippet that represents texture coordinates used for sampling.
- * @param {string} levelSnippet - A WGSL snippet that represents the mip level, with level 0 containing a full size version of the texture.
- * @param {?string} depthSnippet - A WGSL snippet that represents 0-based texture array index to sample.
- * @param {?string} offsetSnippet - A WGSL snippet that represents the offset that will be applied to the unnormalized texture coordinate before sampling the texture.
- * @param {string} [shaderStage=this.shaderStage] - The shader stage this code snippet is generated for.
- * @return {string} The WGSL snippet.
- */
- generateTextureLevel( texture, textureProperty, uvSnippet, levelSnippet, depthSnippet, offsetSnippet ) {
- if ( this.isUnfilterable( texture ) === false ) {
- if ( offsetSnippet ) {
- return `textureSampleLevel( ${ textureProperty }, ${ textureProperty }_sampler, ${ uvSnippet }, ${ levelSnippet }, ${ offsetSnippet } )`;
- }
- return `textureSampleLevel( ${ textureProperty }, ${ textureProperty }_sampler, ${ uvSnippet }, ${ levelSnippet } )`;
- } else if ( this.isFilteredTexture( texture ) ) {
- return this.generateFilteredTexture( texture, textureProperty, uvSnippet, offsetSnippet, levelSnippet );
- } else {
- return this.generateTextureLod( texture, textureProperty, uvSnippet, depthSnippet, offsetSnippet, levelSnippet );
- }
- }
- /**
- * Generates the WGSL snippet when sampling textures with a bias to the mip level.
- *
- * @param {Texture} texture - The texture.
- * @param {string} textureProperty - The name of the texture uniform in the shader.
- * @param {string} uvSnippet - A WGSL snippet that represents texture coordinates used for sampling.
- * @param {string} biasSnippet - A WGSL snippet that represents the bias to apply to the mip level before sampling.
- * @param {?string} depthSnippet - A WGSL snippet that represents 0-based texture array index to sample.
- * @param {?string} offsetSnippet - A WGSL snippet that represents the offset that will be applied to the unnormalized texture coordinate before sampling the texture.
- * @param {string} [shaderStage=this.shaderStage] - The shader stage this code snippet is generated for.
- * @return {string} The WGSL snippet.
- */
- generateTextureBias( texture, textureProperty, uvSnippet, biasSnippet, depthSnippet, offsetSnippet, shaderStage = this.shaderStage ) {
- if ( shaderStage === 'fragment' ) {
- if ( offsetSnippet ) {
- return `textureSampleBias( ${ textureProperty }, ${ textureProperty }_sampler, ${ uvSnippet }, ${ biasSnippet }, ${ offsetSnippet } )`;
- }
- return `textureSampleBias( ${ textureProperty }, ${ textureProperty }_sampler, ${ uvSnippet }, ${ biasSnippet } )`;
- } else {
- error( `WebGPURenderer: THREE.TextureNode.biasNode does not support ${ shaderStage } shader.` );
- }
- }
- /**
- * Returns a WGSL snippet that represents the property name of the given node.
- *
- * @param {Node} node - The node.
- * @param {string} [shaderStage=this.shaderStage] - The shader stage this code snippet is generated for.
- * @return {string} The property name.
- */
- getPropertyName( node, shaderStage = this.shaderStage ) {
- if ( node.isNodeVarying === true && node.needsInterpolation === true ) {
- if ( shaderStage === 'vertex' ) {
- return `varyings.${ node.name }`;
- }
- } else if ( node.isNodeUniform === true ) {
- const name = node.name;
- const type = node.type;
- if ( type === 'texture' || type === 'cubeTexture' || type === 'cubeDepthTexture' || type === 'storageTexture' || type === 'texture3D' ) {
- return name;
- } else if ( type === 'buffer' || type === 'storageBuffer' || type === 'indirectStorageBuffer' ) {
- if ( this.isCustomStruct( node ) ) {
- return name;
- }
- return name + '.value';
- } else {
- return node.groupNode.name + '.' + name;
- }
- }
- return super.getPropertyName( node );
- }
- /**
- * Returns the output struct name.
- *
- * @return {string} The name of the output struct.
- */
- getOutputStructName() {
- return 'output';
- }
- /**
- * Returns the native shader operator name for a given generic name.
- *
- * @param {string} op - The operator name to resolve.
- * @return {?string} The resolved operator name.
- */
- getFunctionOperator( op ) {
- const fnOp = wgslFnOpLib[ op ];
- if ( fnOp !== undefined ) {
- this._include( fnOp );
- return fnOp;
- }
- return null;
- }
- /**
- * Returns the node access for the given node and shader stage.
- *
- * @param {StorageTextureNode|StorageBufferNode} node - The storage node.
- * @param {string} shaderStage - The shader stage.
- * @return {string} The node access.
- */
- getNodeAccess( node, shaderStage ) {
- if ( shaderStage !== 'compute' ) {
- if ( node.isAtomic === true ) {
- warn( 'WebGPURenderer: Atomic operations are only supported in compute shaders.' );
- return NodeAccess.READ_WRITE;
- }
- return NodeAccess.READ_ONLY;
- }
- return node.access;
- }
- /**
- * Returns A WGSL snippet representing the storage access.
- *
- * @param {StorageTextureNode|StorageBufferNode} node - The storage node.
- * @param {string} shaderStage - The shader stage.
- * @return {string} The WGSL snippet representing the storage access.
- */
- getStorageAccess( node, shaderStage ) {
- return accessNames[ this.getNodeAccess( node, shaderStage ) ];
- }
- /**
- * This method is one of the more important ones since it's responsible
- * for generating a matching binding instance for the given uniform node.
- *
- * These bindings are later used in the renderer to create bind groups
- * and layouts.
- *
- * @param {UniformNode} node - The uniform node.
- * @param {string} type - The node data type.
- * @param {string} shaderStage - The shader stage.
- * @param {?string} [name=null] - An optional uniform name.
- * @return {NodeUniform} The node uniform object.
- */
- getUniformFromNode( node, type, shaderStage, name = null ) {
- const uniformNode = super.getUniformFromNode( node, type, shaderStage, name );
- const nodeData = this.getDataFromNode( node, shaderStage, this.globalCache );
- if ( nodeData.uniformGPU === undefined ) {
- let uniformGPU;
- const group = node.groupNode;
- const groupName = group.name;
- const bindings = this.getBindGroupArray( groupName, shaderStage );
- if ( type === 'texture' || type === 'cubeTexture' || type === 'cubeDepthTexture' || type === 'storageTexture' || type === 'texture3D' ) {
- let texture = null;
- const access = this.getNodeAccess( node, shaderStage );
- if ( type === 'texture' || type === 'storageTexture' ) {
- if ( node.value.is3DTexture === true ) {
- texture = new NodeSampledTexture3D( uniformNode.name, uniformNode.node, group, access );
- } else {
- texture = new NodeSampledTexture( uniformNode.name, uniformNode.node, group, access );
- }
- } else if ( type === 'cubeTexture' || type === 'cubeDepthTexture' ) {
- texture = new NodeSampledCubeTexture( uniformNode.name, uniformNode.node, group, access );
- } else if ( type === 'texture3D' ) {
- texture = new NodeSampledTexture3D( uniformNode.name, uniformNode.node, group, access );
- }
- texture.store = node.isStorageTextureNode === true;
- texture.mipLevel = texture.store ? node.mipLevel : 0;
- texture.setVisibility( gpuShaderStageLib[ shaderStage ] );
- if ( this.isUnfilterable( node.value ) === false && texture.store === false ) {
- const sampler = new NodeSampler( `${ uniformNode.name }_sampler`, uniformNode.node, group );
- sampler.setVisibility( gpuShaderStageLib[ shaderStage ] );
- bindings.push( sampler, texture );
- uniformGPU = [ sampler, texture ];
- } else {
- bindings.push( texture );
- uniformGPU = [ texture ];
- }
- } else if ( type === 'buffer' || type === 'storageBuffer' || type === 'indirectStorageBuffer' ) {
- const sharedData = this.getSharedDataFromNode( node );
- let buffer = sharedData.buffer;
- if ( buffer === undefined ) {
- const bufferClass = type === 'buffer' ? NodeUniformBuffer : NodeStorageBuffer;
- buffer = new bufferClass( node, group );
- sharedData.buffer = buffer;
- }
- buffer.setVisibility( buffer.getVisibility() | gpuShaderStageLib[ shaderStage ] );
- bindings.push( buffer );
- uniformGPU = buffer;
- uniformNode.name = name ? name : 'NodeBuffer_' + uniformNode.id;
- } else {
- const uniformsStage = this.uniformGroups[ shaderStage ] || ( this.uniformGroups[ shaderStage ] = {} );
- let uniformsGroup = uniformsStage[ groupName ];
- if ( uniformsGroup === undefined ) {
- uniformsGroup = new NodeUniformsGroup( groupName, group );
- uniformsGroup.setVisibility( gpuShaderStageLib[ shaderStage ] );
- uniformsStage[ groupName ] = uniformsGroup;
- bindings.push( uniformsGroup );
- }
- uniformGPU = this.getNodeUniform( uniformNode, type );
- uniformsGroup.addUniform( uniformGPU );
- }
- nodeData.uniformGPU = uniformGPU;
- }
- return uniformNode;
- }
- /**
- * This method should be used whenever builtins are required in nodes.
- * The internal builtins data structure will make sure builtins are
- * defined in the WGSL source.
- *
- * @param {string} name - The builtin name.
- * @param {string} property - The property name.
- * @param {string} type - The node data type.
- * @param {string} [shaderStage=this.shaderStage] - The shader stage this code snippet is generated for.
- * @return {string} The property name.
- */
- getBuiltin( name, property, type, shaderStage = this.shaderStage ) {
- const map = this.builtins[ shaderStage ] || ( this.builtins[ shaderStage ] = new Map() );
- if ( map.has( name ) === false ) {
- map.set( name, {
- name,
- property,
- type
- } );
- }
- return property;
- }
- /**
- * Returns `true` if the given builtin is defined in the given shader stage.
- *
- * @param {string} name - The builtin name.
- * @param {string} [shaderStage=this.shaderStage] - The shader stage this code snippet is generated for.
- * @return {boolean} Whether the given builtin is defined in the given shader stage or not.
- */
- hasBuiltin( name, shaderStage = this.shaderStage ) {
- return ( this.builtins[ shaderStage ] !== undefined && this.builtins[ shaderStage ].has( name ) );
- }
- /**
- * Returns the vertex index builtin.
- *
- * @return {string} The vertex index.
- */
- getVertexIndex() {
- if ( this.shaderStage === 'vertex' ) {
- return this.getBuiltin( 'vertex_index', 'vertexIndex', 'u32', 'attribute' );
- }
- return 'vertexIndex';
- }
- /**
- * Builds the given shader node.
- *
- * @param {ShaderNodeInternal} shaderNode - The shader node.
- * @return {string} The WGSL function code.
- */
- buildFunctionCode( shaderNode ) {
- const layout = shaderNode.layout;
- const flowData = this.flowShaderNode( shaderNode );
- const parameters = [];
- for ( const input of layout.inputs ) {
- parameters.push( input.name + ' : ' + this.getType( input.type ) );
- }
- //
- let code = `fn ${ layout.name }( ${ parameters.join( ', ' ) } ) -> ${ this.getType( layout.type ) } {
- ${ flowData.vars }
- ${ flowData.code }
- `;
- if ( flowData.result ) {
- code += `\treturn ${ flowData.result };\n`;
- }
- code += '\n}\n';
- //
- return code;
- }
- /**
- * Contextually returns either the vertex stage instance index builtin
- * or the linearized index of an compute invocation within a grid of workgroups.
- *
- * @return {string} The instance index.
- */
- getInstanceIndex() {
- if ( this.shaderStage === 'vertex' ) {
- return this.getBuiltin( 'instance_index', 'instanceIndex', 'u32', 'attribute' );
- }
- return 'instanceIndex';
- }
- /**
- * Returns a builtin representing the index of a compute invocation within the scope of a workgroup load.
- *
- * @return {string} The invocation local index.
- */
- getInvocationLocalIndex() {
- return this.getBuiltin( 'local_invocation_index', 'invocationLocalIndex', 'u32', 'attribute' );
- }
- /**
- * Returns a builtin representing the size of a subgroup within the current shader.
- *
- * @return {string} The subgroup size.
- */
- getSubgroupSize() {
- this.enableSubGroups();
- return this.getBuiltin( 'subgroup_size', 'subgroupSize', 'u32', 'attribute' );
- }
- /**
- * Returns a builtin representing the index of a compute invocation within the scope of a subgroup.
- *
- * @return {string} The invocation subgroup index.
- */
- getInvocationSubgroupIndex() {
- this.enableSubGroups();
- return this.getBuiltin( 'subgroup_invocation_id', 'invocationSubgroupIndex', 'u32', 'attribute' );
- }
- /**
- * Returns a builtin representing the index of a compute invocation's subgroup within its workgroup.
- *
- * @return {string} The subgroup index.
- */
- getSubgroupIndex() {
- this.enableSubGroups();
- return this.getBuiltin( 'subgroup_id', 'subgroupIndex', 'u32', 'attribute' );
- }
- /**
- * Overwritten as a NOP since this method is intended for the WebGL 2 backend.
- *
- * @return {null} Null.
- */
- getDrawIndex() {
- return null;
- }
- /**
- * Returns the front facing builtin.
- *
- * @return {string} The front facing builtin.
- */
- getFrontFacing() {
- return this.getBuiltin( 'front_facing', 'isFront', 'bool' );
- }
- /**
- * Returns the frag coord builtin.
- *
- * @return {string} The frag coord builtin.
- */
- getFragCoord() {
- return this.getBuiltin( 'position', 'fragCoord', 'vec4<f32>' ) + '.xy';
- }
- /**
- * Returns the frag depth builtin.
- *
- * @return {string} The frag depth builtin.
- */
- getFragDepth() {
- return 'output.' + this.getBuiltin( 'frag_depth', 'depth', 'f32', 'output' );
- }
- /**
- * Returns the clip distances builtin.
- *
- * @return {string} The clip distances builtin.
- */
- getClipDistance() {
- return 'varyings.hw_clip_distances';
- }
- /**
- * Whether to flip texture data along its vertical axis or not.
- *
- * @return {boolean} Returns always `false` in context of WGSL.
- */
- isFlipY() {
- return false;
- }
- /**
- * Enables the given directive for the given shader stage.
- *
- * @param {string} name - The directive name.
- * @param {string} [shaderStage=this.shaderStage] - The shader stage to enable the directive for.
- */
- enableDirective( name, shaderStage = this.shaderStage ) {
- const stage = this.directives[ shaderStage ] || ( this.directives[ shaderStage ] = new Set() );
- stage.add( name );
- }
- /**
- * Returns the directives of the given shader stage as a WGSL string.
- *
- * @param {string} shaderStage - The shader stage.
- * @return {string} A WGSL snippet that enables the directives of the given stage.
- */
- getDirectives( shaderStage ) {
- const snippets = [];
- const directives = this.directives[ shaderStage ];
- if ( directives !== undefined ) {
- for ( const directive of directives ) {
- snippets.push( `enable ${directive};` );
- }
- }
- return snippets.join( '\n' );
- }
- /**
- * Enables the 'subgroups' directive.
- */
- enableSubGroups() {
- this.enableDirective( 'subgroups' );
- }
- /**
- * Enables the 'subgroups-f16' directive.
- */
- enableSubgroupsF16() {
- this.enableDirective( 'subgroups-f16' );
- }
- /**
- * Enables the 'clip_distances' directive.
- */
- enableClipDistances() {
- this.enableDirective( 'clip_distances' );
- }
- /**
- * Enables the 'f16' directive.
- */
- enableShaderF16() {
- this.enableDirective( 'f16' );
- }
- /**
- * Enables the 'dual_source_blending' directive.
- */
- enableDualSourceBlending() {
- this.enableDirective( 'dual_source_blending' );
- }
- /**
- * Enables hardware clipping.
- *
- * @param {string} planeCount - The clipping plane count.
- */
- enableHardwareClipping( planeCount ) {
- this.enableClipDistances();
- this.getBuiltin( 'clip_distances', 'hw_clip_distances', `array<f32, ${ planeCount } >`, 'vertex' );
- }
- /**
- * Returns the builtins of the given shader stage as a WGSL string.
- *
- * @param {string} shaderStage - The shader stage.
- * @return {string} A WGSL snippet that represents the builtins of the given stage.
- */
- getBuiltins( shaderStage ) {
- const snippets = [];
- const builtins = this.builtins[ shaderStage ];
- if ( builtins !== undefined ) {
- for ( const { name, property, type } of builtins.values() ) {
- snippets.push( `@builtin( ${name} ) ${property} : ${type}` );
- }
- }
- return snippets.join( ',\n\t' );
- }
- /**
- * This method should be used when a new scoped buffer is used in context of
- * compute shaders. It adds the array to the internal data structure which is
- * later used to generate the respective WGSL.
- *
- * @param {string} name - The array name.
- * @param {string} scope - The scope.
- * @param {string} bufferType - The buffer type.
- * @param {string} bufferCount - The buffer count.
- * @return {string} The array name.
- */
- getScopedArray( name, scope, bufferType, bufferCount ) {
- if ( this.scopedArrays.has( name ) === false ) {
- this.scopedArrays.set( name, {
- name,
- scope,
- bufferType,
- bufferCount
- } );
- }
- return name;
- }
- /**
- * Returns the scoped arrays of the given shader stage as a WGSL string.
- *
- * @param {string} shaderStage - The shader stage.
- * @return {string|undefined} The WGSL snippet that defines the scoped arrays.
- * Returns `undefined` when used in the vertex or fragment stage.
- */
- getScopedArrays( shaderStage ) {
- if ( shaderStage !== 'compute' ) {
- return;
- }
- const snippets = [];
- for ( const { name, scope, bufferType, bufferCount } of this.scopedArrays.values() ) {
- const type = this.getType( bufferType );
- snippets.push( `var<${scope}> ${name}: array< ${type}, ${bufferCount} >;` );
- }
- return snippets.join( '\n' );
- }
- /**
- * Returns the shader attributes of the given shader stage as a WGSL string.
- *
- * @param {string} shaderStage - The shader stage.
- * @return {string} The WGSL snippet that defines the shader attributes.
- */
- getAttributes( shaderStage ) {
- const snippets = [];
- if ( shaderStage === 'compute' ) {
- this.getBuiltin( 'global_invocation_id', 'globalId', 'vec3<u32>', 'attribute' );
- this.getBuiltin( 'workgroup_id', 'workgroupId', 'vec3<u32>', 'attribute' );
- this.getBuiltin( 'local_invocation_id', 'localId', 'vec3<u32>', 'attribute' );
- this.getBuiltin( 'num_workgroups', 'numWorkgroups', 'vec3<u32>', 'attribute' );
- if ( this.renderer.hasFeature( 'subgroups' ) ) {
- this.enableDirective( 'subgroups', shaderStage );
- this.getBuiltin( 'subgroup_size', 'subgroupSize', 'u32', 'attribute' );
- }
- }
- if ( shaderStage === 'vertex' || shaderStage === 'compute' ) {
- const builtins = this.getBuiltins( 'attribute' );
- if ( builtins ) snippets.push( builtins );
- const attributes = this.getAttributesArray();
- for ( let index = 0, length = attributes.length; index < length; index ++ ) {
- const attribute = attributes[ index ];
- const name = attribute.name;
- const type = this.getType( attribute.type );
- snippets.push( `@location( ${index} ) ${ name } : ${ type }` );
- }
- }
- return snippets.join( ',\n\t' );
- }
- /**
- * Returns the members of the given struct type node as a WGSL string.
- *
- * @param {StructTypeNode} struct - The struct type node.
- * @return {string} The WGSL snippet that defines the struct members.
- */
- getStructMembers( struct ) {
- const snippets = [];
- for ( const member of struct.members ) {
- const prefix = struct.output ? '@location( ' + member.index + ' ) ' : '';
- let type = this.getType( member.type );
- if ( member.atomic ) {
- type = 'atomic< ' + type + ' >';
- }
- snippets.push( `\t${ prefix + member.name } : ${ type }` );
- }
- if ( struct.output ) {
- snippets.push( `\t${ this.getBuiltins( 'output' ) }` );
- }
- return snippets.join( ',\n' );
- }
- /**
- * Returns the structs of the given shader stage as a WGSL string.
- *
- * @param {string} shaderStage - The shader stage.
- * @return {string} The WGSL snippet that defines the structs.
- */
- getStructs( shaderStage ) {
- let result = '';
- const structs = this.structs[ shaderStage ];
- if ( structs.length > 0 ) {
- const snippets = [];
- for ( const struct of structs ) {
- let snippet = `struct ${ struct.name } {\n`;
- snippet += this.getStructMembers( struct );
- snippet += '\n};';
- snippets.push( snippet );
- }
- result = '\n' + snippets.join( '\n\n' ) + '\n';
- }
- return result;
- }
- /**
- * Returns a WGSL string representing a variable.
- *
- * @param {string} type - The variable's type.
- * @param {string} name - The variable's name.
- * @param {?number} [count=null] - The array length.
- * @return {string} The WGSL snippet that defines a variable.
- */
- getVar( type, name, count = null ) {
- let snippet = `var ${ name } : `;
- if ( count !== null ) {
- snippet += this.generateArrayDeclaration( type, count );
- } else {
- snippet += this.getType( type );
- }
- return snippet;
- }
- /**
- * Returns the variables of the given shader stage as a WGSL string.
- *
- * @param {string} shaderStage - The shader stage.
- * @return {string} The WGSL snippet that defines the variables.
- */
- getVars( shaderStage ) {
- const snippets = [];
- const vars = this.vars[ shaderStage ];
- if ( vars !== undefined ) {
- for ( const variable of vars ) {
- snippets.push( `\t${ this.getVar( variable.type, variable.name, variable.count ) };` );
- }
- }
- return `\n${ snippets.join( '\n' ) }\n`;
- }
- /**
- * Returns the varyings of the given shader stage as a WGSL string.
- *
- * @param {string} shaderStage - The shader stage.
- * @return {string} The WGSL snippet that defines the varyings.
- */
- getVaryings( shaderStage ) {
- const snippets = [];
- if ( shaderStage === 'vertex' ) {
- this.getBuiltin( 'position', 'Vertex', 'vec4<f32>', 'vertex' );
- }
- if ( shaderStage === 'vertex' || shaderStage === 'fragment' ) {
- const varyings = this.varyings;
- const vars = this.vars[ shaderStage ];
- for ( let index = 0; index < varyings.length; index ++ ) {
- const varying = varyings[ index ];
- if ( varying.needsInterpolation ) {
- let attributesSnippet = `@location( ${index} )`;
- if ( varying.interpolationType ) {
- const samplingSnippet = varying.interpolationSampling !== null ? `, ${ varying.interpolationSampling } )` : ' )';
- attributesSnippet += ` @interpolate( ${ varying.interpolationType }${ samplingSnippet }`;
- // Otherwise, optimize interpolation when sensible
- } else if ( /^(int|uint|ivec|uvec)/.test( varying.type ) ) {
- attributesSnippet += ` @interpolate( ${ this.renderer.backend.compatibilityMode ? 'flat, either' : 'flat' } )`;
- }
- snippets.push( `${ attributesSnippet } ${ varying.name } : ${ this.getType( varying.type ) }` );
- } else if ( shaderStage === 'vertex' && vars.includes( varying ) === false ) {
- vars.push( varying );
- }
- }
- }
- const builtins = this.getBuiltins( shaderStage );
- if ( builtins ) snippets.push( builtins );
- const code = snippets.join( ',\n\t' );
- return shaderStage === 'vertex' ? this._getWGSLStruct( 'VaryingsStruct', '\t' + code ) : code;
- }
- isCustomStruct( nodeUniform ) {
- const attribute = nodeUniform.value;
- const bufferNode = nodeUniform.node;
- const isAttributeStructType = ( attribute.isBufferAttribute || attribute.isInstancedBufferAttribute ) && bufferNode.structTypeNode !== null;
- const isStructArray =
- ( bufferNode.value && bufferNode.value.array ) &&
- ( typeof bufferNode.value.itemSize === 'number' && bufferNode.value.array.length > bufferNode.value.itemSize );
- return isAttributeStructType && ! isStructArray;
- }
- /**
- * Returns the uniforms of the given shader stage as a WGSL string.
- *
- * @param {string} shaderStage - The shader stage.
- * @return {string} The WGSL snippet that defines the uniforms.
- */
- getUniforms( shaderStage ) {
- const uniforms = this.uniforms[ shaderStage ];
- const bindingSnippets = [];
- const bufferSnippets = [];
- const structSnippets = [];
- const uniformGroups = {};
- for ( const uniform of uniforms ) {
- const groupName = uniform.groupNode.name;
- const uniformIndexes = this.bindingsIndexes[ groupName ];
- if ( uniform.type === 'texture' || uniform.type === 'cubeTexture' || uniform.type === 'cubeDepthTexture' || uniform.type === 'storageTexture' || uniform.type === 'texture3D' ) {
- const texture = uniform.node.value;
- if ( this.isUnfilterable( texture ) === false && uniform.node.isStorageTextureNode !== true ) {
- if ( this.isSampleCompare( texture ) ) {
- bindingSnippets.push( `@binding( ${ uniformIndexes.binding ++ } ) @group( ${ uniformIndexes.group } ) var ${ uniform.name }_sampler : sampler_comparison;` );
- } else {
- bindingSnippets.push( `@binding( ${ uniformIndexes.binding ++ } ) @group( ${ uniformIndexes.group } ) var ${ uniform.name }_sampler : sampler;` );
- }
- }
- let textureType;
- let multisampled = '';
- const { primarySamples } = this.renderer.backend.utils.getTextureSampleData( texture );
- if ( primarySamples > 1 ) {
- multisampled = '_multisampled';
- }
- if ( texture.isCubeTexture === true && texture.isDepthTexture === true ) {
- textureType = 'texture_depth_cube';
- } else if ( texture.isCubeTexture === true ) {
- textureType = 'texture_cube<f32>';
- } else if ( texture.isDepthTexture === true ) {
- if ( this.renderer.backend.compatibilityMode && texture.compareFunction === null ) {
- textureType = `texture${ multisampled }_2d<f32>`;
- } else {
- textureType = `texture_depth${ multisampled }_2d${ texture.isArrayTexture === true ? '_array' : '' }`;
- }
- } else if ( uniform.node.isStorageTextureNode === true ) {
- const format = getFormat( texture );
- const access = this.getStorageAccess( uniform.node, shaderStage );
- const is3D = uniform.node.value.is3DTexture;
- const isArrayTexture = uniform.node.value.isArrayTexture;
- const dimension = is3D ? '3d' : `2d${ isArrayTexture ? '_array' : '' }`;
- textureType = `texture_storage_${ dimension }<${ format }, ${ access }>`;
- } else if ( texture.isArrayTexture === true || texture.isDataArrayTexture === true || texture.isCompressedArrayTexture === true ) {
- textureType = 'texture_2d_array<f32>';
- } else if ( texture.is3DTexture === true || texture.isData3DTexture === true ) {
- textureType = 'texture_3d<f32>';
- } else {
- const componentPrefix = this.getComponentTypeFromTexture( texture ).charAt( 0 );
- textureType = `texture${ multisampled }_2d<${ componentPrefix }32>`;
- }
- bindingSnippets.push( `@binding( ${ uniformIndexes.binding ++ } ) @group( ${ uniformIndexes.group } ) var ${ uniform.name } : ${ textureType };` );
- } else if ( uniform.type === 'buffer' || uniform.type === 'storageBuffer' || uniform.type === 'indirectStorageBuffer' ) {
- const bufferNode = uniform.node;
- const bufferType = this.getType( bufferNode.getNodeType( this ) );
- const bufferCount = bufferNode.bufferCount;
- const bufferCountSnippet = bufferCount > 0 && uniform.type === 'buffer' ? ', ' + bufferCount : '';
- const bufferAccessMode = bufferNode.isStorageBufferNode ? `storage, ${ this.getStorageAccess( bufferNode, shaderStage ) }` : 'uniform';
- if ( this.isCustomStruct( uniform ) ) {
- bufferSnippets.push( `@binding( ${ uniformIndexes.binding ++ } ) @group( ${ uniformIndexes.group } ) var<${ bufferAccessMode }> ${ uniform.name } : ${ bufferType };` );
- } else {
- const bufferTypeSnippet = bufferNode.isAtomic ? `atomic<${ bufferType }>` : `${ bufferType }`;
- const bufferSnippet = `\tvalue : array< ${ bufferTypeSnippet }${ bufferCountSnippet } >`;
- bufferSnippets.push( this._getWGSLStructBinding( uniform.name, bufferSnippet, bufferAccessMode, uniformIndexes.binding ++, uniformIndexes.group ) );
- }
- } else {
- const vectorType = this.getType( this.getVectorType( uniform.type ) );
- const groupName = uniform.groupNode.name;
- const group = uniformGroups[ groupName ] || ( uniformGroups[ groupName ] = {
- index: uniformIndexes.binding ++,
- id: uniformIndexes.group,
- snippets: []
- } );
- group.snippets.push( `\t${ uniform.name } : ${ vectorType }` );
- }
- }
- for ( const name in uniformGroups ) {
- const group = uniformGroups[ name ];
- structSnippets.push( this._getWGSLStructBinding( name, group.snippets.join( ',\n' ), 'uniform', group.index, group.id ) );
- }
- let code = bindingSnippets.join( '\n' );
- code += bufferSnippets.join( '\n' );
- code += structSnippets.join( '\n' );
- return code;
- }
- /**
- * Controls the code build of the shader stages.
- */
- buildCode() {
- const shadersData = this.material !== null ? { fragment: {}, vertex: {} } : { compute: {} };
- this.sortBindingGroups();
- for ( const shaderStage in shadersData ) {
- this.shaderStage = shaderStage;
- const stageData = shadersData[ shaderStage ];
- stageData.uniforms = this.getUniforms( shaderStage );
- stageData.attributes = this.getAttributes( shaderStage );
- stageData.varyings = this.getVaryings( shaderStage );
- stageData.structs = this.getStructs( shaderStage );
- stageData.vars = this.getVars( shaderStage );
- stageData.codes = this.getCodes( shaderStage );
- stageData.directives = this.getDirectives( shaderStage );
- stageData.scopedArrays = this.getScopedArrays( shaderStage );
- //
- let flow = '// code\n\n';
- flow += this.flowCode[ shaderStage ];
- const flowNodes = this.flowNodes[ shaderStage ];
- const mainNode = flowNodes[ flowNodes.length - 1 ];
- const outputNode = mainNode.outputNode;
- const isOutputStruct = ( outputNode !== undefined && outputNode.isOutputStructNode === true );
- for ( const node of flowNodes ) {
- const flowSlotData = this.getFlowData( node/*, shaderStage*/ );
- const slotName = node.name;
- if ( slotName ) {
- if ( flow.length > 0 ) flow += '\n';
- flow += `\t// flow -> ${ slotName }\n`;
- }
- flow += `${ flowSlotData.code }\n\t`;
- if ( node === mainNode && shaderStage !== 'compute' ) {
- flow += '// result\n\n\t';
- if ( shaderStage === 'vertex' ) {
- flow += `varyings.Vertex = ${ flowSlotData.result };`;
- } else if ( shaderStage === 'fragment' ) {
- if ( isOutputStruct ) {
- stageData.returnType = outputNode.getNodeType( this );
- stageData.structs += 'var<private> output : ' + stageData.returnType + ';';
- flow += `return ${ flowSlotData.result };`;
- } else {
- let structSnippet = '\t@location(0) color: vec4<f32>';
- const builtins = this.getBuiltins( 'output' );
- if ( builtins ) structSnippet += ',\n\t' + builtins;
- stageData.returnType = 'OutputStruct';
- stageData.structs += this._getWGSLStruct( 'OutputStruct', structSnippet );
- stageData.structs += '\nvar<private> output : OutputStruct;';
- flow += `output.color = ${ flowSlotData.result };\n\n\treturn output;`;
- }
- }
- }
- }
- stageData.flow = flow;
- }
- this.shaderStage = null;
- if ( this.material !== null ) {
- this.vertexShader = this._getWGSLVertexCode( shadersData.vertex );
- this.fragmentShader = this._getWGSLFragmentCode( shadersData.fragment );
- } else {
- // Early strictly validated in computeNode
- const workgroupSize = this.object.workgroupSize;
- this.computeShader = this._getWGSLComputeCode( shadersData.compute, workgroupSize );
- }
- }
- /**
- * Returns the native shader method name for a given generic name.
- *
- * @param {string} method - The method name to resolve.
- * @param {?string} [output=null] - An optional output.
- * @return {string} The resolved WGSL method name.
- */
- getMethod( method, output = null ) {
- let wgslMethod;
- if ( output !== null ) {
- wgslMethod = this._getWGSLMethod( method + '_' + output );
- }
- if ( wgslMethod === undefined ) {
- wgslMethod = this._getWGSLMethod( method );
- }
- return wgslMethod || method;
- }
- /**
- * Returns the bitcast method name for a given input and outputType.
- *
- * @param {string} type - The output type to bitcast to.
- * @return {string} The resolved WGSL bitcast invocation.
- */
- getBitcastMethod( type ) {
- const dataType = this.getType( type );
- return `bitcast<${ dataType }>`;
- }
- /**
- * Returns the float packing method name for a given numeric encoding.
- *
- * @param {string} encoding - The numeric encoding that describes how the float values are mapped to the integer range.
- * @returns {string} The resolve WGSL float packing method name.
- */
- getFloatPackingMethod( encoding ) {
- return this.getMethod( `floatpack_${ encoding }_2x16` );
- }
- /**
- * Returns the float unpacking method name for a given numeric encoding.
- *
- * @param {string} encoding - The numeric encoding that describes how the integer values are mapped to the float range.
- * @returns {string} The resolve WGSL float unpacking method name.
- */
- getFloatUnpackingMethod( encoding ) {
- return this.getMethod( `floatunpack_${ encoding }_2x16` );
- }
- /**
- * Returns the native snippet for a ternary operation.
- *
- * @param {string} condSnippet - The condition determining which expression gets resolved.
- * @param {string} ifSnippet - The expression to resolve to if the condition is true.
- * @param {string} elseSnippet - The expression to resolve to if the condition is false.
- * @return {string} The resolved method name.
- */
- getTernary( condSnippet, ifSnippet, elseSnippet ) {
- return `select( ${elseSnippet}, ${ifSnippet}, ${condSnippet} )`;
- }
- /**
- * Returns the WGSL type of the given node data type.
- *
- * @param {string} type - The node data type.
- * @return {string} The WGSL type.
- */
- getType( type ) {
- return wgslTypeLib[ type ] || type;
- }
- /**
- * Whether the requested feature is available or not.
- *
- * @param {string} name - The requested feature.
- * @return {boolean} Whether the requested feature is supported or not.
- */
- isAvailable( name ) {
- let result = supports[ name ];
- if ( result === undefined ) {
- if ( name === 'float32Filterable' ) {
- result = this.renderer.hasFeature( 'float32-filterable' );
- } else if ( name === 'clipDistance' ) {
- result = this.renderer.hasFeature( 'clip-distances' );
- }
- supports[ name ] = result;
- }
- return result;
- }
- /**
- * Returns the native shader method name for a given generic name.
- *
- * @private
- * @param {string} method - The method name to resolve.
- * @return {string} The resolved WGSL method name.
- */
- _getWGSLMethod( method ) {
- if ( wgslPolyfill[ method ] !== undefined ) {
- this._include( method );
- }
- return wgslMethods[ method ];
- }
- /**
- * Includes the given method name into the current
- * function node.
- *
- * @private
- * @param {string} name - The method name to include.
- * @return {CodeNode} The respective code node.
- */
- _include( name ) {
- const codeNode = wgslPolyfill[ name ];
- codeNode.build( this );
- this.addInclude( codeNode );
- return codeNode;
- }
- /**
- * Returns a WGSL vertex shader based on the given shader data.
- *
- * @private
- * @param {Object} shaderData - The shader data.
- * @return {string} The vertex shader.
- */
- _getWGSLVertexCode( shaderData ) {
- return `${ this.getSignature() }
- // directives
- ${shaderData.directives}
- // structs
- ${shaderData.structs}
- // uniforms
- ${shaderData.uniforms}
- // varyings
- ${shaderData.varyings}
- var<private> varyings : VaryingsStruct;
- // codes
- ${shaderData.codes}
- @vertex
- fn main( ${shaderData.attributes} ) -> VaryingsStruct {
- // vars
- ${shaderData.vars}
- // flow
- ${shaderData.flow}
- return varyings;
- }
- `;
- }
- /**
- * Returns a WGSL fragment shader based on the given shader data.
- *
- * @private
- * @param {Object} shaderData - The shader data.
- * @return {string} The vertex shader.
- */
- _getWGSLFragmentCode( shaderData ) {
- return `${ this.getSignature() }
- // global
- ${ diagnostics }
- // structs
- ${shaderData.structs}
- // uniforms
- ${shaderData.uniforms}
- // codes
- ${shaderData.codes}
- @fragment
- fn main( ${shaderData.varyings} ) -> ${shaderData.returnType} {
- // vars
- ${shaderData.vars}
- // flow
- ${shaderData.flow}
- }
- `;
- }
- /**
- * Returns a WGSL compute shader based on the given shader data.
- *
- * @private
- * @param {Object} shaderData - The shader data.
- * @param {string} workgroupSize - The workgroup size.
- * @return {string} The vertex shader.
- */
- _getWGSLComputeCode( shaderData, workgroupSize ) {
- const [ workgroupSizeX, workgroupSizeY, workgroupSizeZ ] = workgroupSize;
- return `${ this.getSignature() }
- // directives
- ${ shaderData.directives }
- // system
- var<private> instanceIndex : u32;
- // locals
- ${ shaderData.scopedArrays }
- // structs
- ${ shaderData.structs }
- // uniforms
- ${ shaderData.uniforms }
- // codes
- ${ shaderData.codes }
- @compute @workgroup_size( ${ workgroupSizeX }, ${ workgroupSizeY }, ${ workgroupSizeZ } )
- fn main( ${ shaderData.attributes } ) {
- // system
- instanceIndex = globalId.x
- + globalId.y * ( ${ workgroupSizeX } * numWorkgroups.x )
- + globalId.z * ( ${ workgroupSizeX } * numWorkgroups.x ) * ( ${ workgroupSizeY } * numWorkgroups.y );
- // vars
- ${ shaderData.vars }
- // flow
- ${ shaderData.flow }
- }
- `;
- }
- /**
- * Returns a WGSL struct based on the given name and variables.
- *
- * @private
- * @param {string} name - The struct name.
- * @param {string} vars - The struct variables.
- * @return {string} The WGSL snippet representing a struct.
- */
- _getWGSLStruct( name, vars ) {
- return `
- struct ${name} {
- ${vars}
- };`;
- }
- /**
- * Returns a WGSL struct binding.
- *
- * @private
- * @param {string} name - The struct name.
- * @param {string} vars - The struct variables.
- * @param {string} access - The access.
- * @param {number} [binding=0] - The binding index.
- * @param {number} [group=0] - The group index.
- * @return {string} The WGSL snippet representing a struct binding.
- */
- _getWGSLStructBinding( name, vars, access, binding = 0, group = 0 ) {
- const structName = name + 'Struct';
- const structSnippet = this._getWGSLStruct( structName, vars );
- return `${structSnippet}
- @binding( ${ binding } ) @group( ${ group } )
- var<${access}> ${ name } : ${ structName };`;
- }
- }
- /**
- * A WebGPU backend utility module with common helpers.
- *
- * @private
- */
- class WebGPUUtils {
- /**
- * Constructs a new utility object.
- *
- * @param {WebGPUBackend} backend - The WebGPU backend.
- */
- constructor( backend ) {
- /**
- * A reference to the WebGPU backend.
- *
- * @type {WebGPUBackend}
- */
- this.backend = backend;
- }
- /**
- * Returns the depth/stencil GPU format for the given render context.
- *
- * @param {RenderContext} renderContext - The render context.
- * @return {string} The depth/stencil GPU texture format.
- */
- getCurrentDepthStencilFormat( renderContext ) {
- let format;
- if ( renderContext.depthTexture !== null ) {
- format = this.getTextureFormatGPU( renderContext.depthTexture );
- } else if ( renderContext.depth && renderContext.stencil ) {
- format = GPUTextureFormat.Depth24PlusStencil8;
- } else if ( renderContext.depth ) {
- format = GPUTextureFormat.Depth24Plus;
- }
- return format;
- }
- /**
- * Returns the GPU format for the given texture.
- *
- * @param {Texture} texture - The texture.
- * @return {string} The GPU texture format.
- */
- getTextureFormatGPU( texture ) {
- return this.backend.get( texture ).format;
- }
- /**
- * Returns an object that defines the multi-sampling state of the given texture.
- *
- * @param {Texture} texture - The texture.
- * @return {Object} The multi-sampling state.
- */
- getTextureSampleData( texture ) {
- let samples;
- if ( texture.isFramebufferTexture ) {
- samples = 1;
- } else if ( texture.isDepthTexture && ! texture.renderTarget ) {
- const renderer = this.backend.renderer;
- const renderTarget = renderer.getRenderTarget();
- samples = renderTarget ? renderTarget.samples : renderer.currentSamples;
- } else if ( texture.renderTarget ) {
- samples = texture.renderTarget.samples;
- }
- samples = samples || 1;
- const isMSAA = samples > 1 && texture.renderTarget !== null && ( texture.isDepthTexture !== true && texture.isFramebufferTexture !== true );
- const primarySamples = isMSAA ? 1 : samples;
- return { samples, primarySamples, isMSAA };
- }
- /**
- * Returns the default color attachment's GPU format of the current render context.
- *
- * @param {RenderContext} renderContext - The render context.
- * @return {string} The GPU texture format of the default color attachment.
- */
- getCurrentColorFormat( renderContext ) {
- let format;
- if ( renderContext.textures !== null ) {
- format = this.getTextureFormatGPU( renderContext.textures[ 0 ] );
- } else {
- format = this.getPreferredCanvasFormat(); // default context format
- }
- return format;
- }
- /**
- * Returns the GPU formats of all color attachments of the current render context.
- *
- * @param {RenderContext} renderContext - The render context.
- * @return {Array<string>} The GPU texture formats of all color attachments.
- */
- getCurrentColorFormats( renderContext ) {
- if ( renderContext.textures !== null ) {
- return renderContext.textures.map( t => this.getTextureFormatGPU( t ) );
- } else {
- return [ this.getPreferredCanvasFormat() ]; // default context format
- }
- }
- /**
- * Returns the output color space of the current render context.
- *
- * @param {RenderContext} renderContext - The render context.
- * @return {string} The output color space.
- */
- getCurrentColorSpace( renderContext ) {
- if ( renderContext.textures !== null ) {
- return renderContext.textures[ 0 ].colorSpace;
- }
- return this.backend.renderer.outputColorSpace;
- }
- /**
- * Returns GPU primitive topology for the given object and material.
- *
- * @param {Object3D} object - The 3D object.
- * @param {Material} material - The material.
- * @return {string} The GPU primitive topology.
- */
- getPrimitiveTopology( object, material ) {
- if ( object.isPoints ) return GPUPrimitiveTopology.PointList;
- else if ( object.isLineSegments || ( object.isMesh && material.wireframe === true ) ) return GPUPrimitiveTopology.LineList;
- else if ( object.isLine ) return GPUPrimitiveTopology.LineStrip;
- else if ( object.isMesh ) return GPUPrimitiveTopology.TriangleList;
- }
- /**
- * Returns a modified sample count from the given sample count value.
- *
- * That is required since WebGPU only supports either 1 or 4.
- *
- * @param {number} sampleCount - The input sample count.
- * @return {number} The (potentially updated) output sample count.
- */
- getSampleCount( sampleCount ) {
- return sampleCount >= 4 ? 4 : 1;
- }
- /**
- * Returns the sample count of the given render context.
- *
- * @param {RenderContext} renderContext - The render context.
- * @return {number} The sample count.
- */
- getSampleCountRenderContext( renderContext ) {
- if ( renderContext.textures !== null ) {
- return this.getSampleCount( renderContext.sampleCount );
- }
- return this.getSampleCount( this.backend.renderer.currentSamples );
- }
- /**
- * Returns the preferred canvas format.
- *
- * There is a separate method for this so it's possible to
- * honor edge cases for specific devices.
- *
- * @return {string} The GPU texture format of the canvas.
- */
- getPreferredCanvasFormat() {
- const parameters = this.backend.parameters;
- const bufferType = parameters.outputType;
- if ( bufferType === undefined ) {
- return navigator.gpu.getPreferredCanvasFormat();
- } else if ( bufferType === UnsignedByteType ) {
- return GPUTextureFormat.BGRA8Unorm;
- } else if ( bufferType === HalfFloatType ) {
- return GPUTextureFormat.RGBA16Float;
- } else {
- throw new Error( 'Unsupported output buffer type.' );
- }
- }
- }
- const typedArraysToVertexFormatPrefix = new Map( [
- [ Int8Array, [ 'sint8', 'snorm8' ]],
- [ Uint8Array, [ 'uint8', 'unorm8' ]],
- [ Int16Array, [ 'sint16', 'snorm16' ]],
- [ Uint16Array, [ 'uint16', 'unorm16' ]],
- [ Int32Array, [ 'sint32', 'snorm32' ]],
- [ Uint32Array, [ 'uint32', 'unorm32' ]],
- [ Float32Array, [ 'float32', ]],
- ] );
- if ( typeof Float16Array !== 'undefined' ) {
- typedArraysToVertexFormatPrefix.set( Float16Array, [ 'float16' ] );
- }
- const typedAttributeToVertexFormatPrefix = new Map( [
- [ Float16BufferAttribute, [ 'float16', ]],
- ] );
- const typeArraysToVertexFormatPrefixForItemSize1 = new Map( [
- [ Int32Array, 'sint32' ],
- [ Int16Array, 'sint32' ], // patch for INT16
- [ Uint32Array, 'uint32' ],
- [ Uint16Array, 'uint32' ], // patch for UINT16
- [ Float32Array, 'float32' ]
- ] );
- /**
- * A WebGPU backend utility module for managing shader attributes.
- *
- * @private
- */
- class WebGPUAttributeUtils {
- /**
- * Constructs a new utility object.
- *
- * @param {WebGPUBackend} backend - The WebGPU backend.
- */
- constructor( backend ) {
- /**
- * A reference to the WebGPU backend.
- *
- * @type {WebGPUBackend}
- */
- this.backend = backend;
- }
- /**
- * Creates the GPU buffer for the given buffer attribute.
- *
- * @param {BufferAttribute} attribute - The buffer attribute.
- * @param {GPUBufferUsage} usage - A flag that indicates how the buffer may be used after its creation.
- */
- createAttribute( attribute, usage ) {
- const bufferAttribute = this._getBufferAttribute( attribute );
- const backend = this.backend;
- const bufferData = backend.get( bufferAttribute );
- let buffer = bufferData.buffer;
- if ( buffer === undefined ) {
- const device = backend.device;
- let array = bufferAttribute.array;
- // patch for INT16 and UINT16
- if ( attribute.normalized === false ) {
- if ( array.constructor === Int16Array || array.constructor === Int8Array ) {
- array = new Int32Array( array );
- } else if ( array.constructor === Uint16Array || array.constructor === Uint8Array ) {
- array = new Uint32Array( array );
- if ( usage & GPUBufferUsage.INDEX ) {
- for ( let i = 0; i < array.length; i ++ ) {
- if ( array[ i ] === 0xffff ) array[ i ] = 0xffffffff; // use correct primitive restart index
- }
- }
- }
- }
- bufferAttribute.array = array;
- if ( ( bufferAttribute.isStorageBufferAttribute || bufferAttribute.isStorageInstancedBufferAttribute ) && bufferAttribute.itemSize === 3 ) {
- array = new array.constructor( bufferAttribute.count * 4 );
- for ( let i = 0; i < bufferAttribute.count; i ++ ) {
- array.set( bufferAttribute.array.subarray( i * 3, i * 3 + 3 ), i * 4 );
- }
- // Update BufferAttribute
- bufferAttribute.itemSize = 4;
- bufferAttribute.array = array;
- bufferData._force3to4BytesAlignment = true;
- }
- // ensure 4 byte alignment
- const byteLength = array.byteLength;
- const size = byteLength + ( ( 4 - ( byteLength % 4 ) ) % 4 );
- buffer = device.createBuffer( {
- label: bufferAttribute.name,
- size: size,
- usage: usage,
- mappedAtCreation: true
- } );
- new array.constructor( buffer.getMappedRange() ).set( array );
- buffer.unmap();
- bufferData.buffer = buffer;
- }
- }
- /**
- * Updates the GPU buffer of the given buffer attribute.
- *
- * @param {BufferAttribute} attribute - The buffer attribute.
- */
- updateAttribute( attribute ) {
- const bufferAttribute = this._getBufferAttribute( attribute );
- const backend = this.backend;
- const device = backend.device;
- const bufferData = backend.get( bufferAttribute );
- const buffer = backend.get( bufferAttribute ).buffer;
- let array = bufferAttribute.array;
- // if storage buffer ensure 4 byte alignment
- if ( bufferData._force3to4BytesAlignment === true ) {
- array = new array.constructor( bufferAttribute.count * 4 );
- for ( let i = 0; i < bufferAttribute.count; i ++ ) {
- array.set( bufferAttribute.array.subarray( i * 3, i * 3 + 3 ), i * 4 );
- }
- bufferAttribute.array = array;
- }
- const updateRanges = bufferAttribute.updateRanges;
- if ( updateRanges.length === 0 ) {
- // Not using update ranges
- device.queue.writeBuffer(
- buffer,
- 0,
- array,
- 0
- );
- } else {
- const isTyped = isTypedArray( array );
- const byteOffsetFactor = isTyped ? 1 : array.BYTES_PER_ELEMENT;
- for ( let i = 0, l = updateRanges.length; i < l; i ++ ) {
- const range = updateRanges[ i ];
- let dataOffset, size;
- if ( bufferData._force3to4BytesAlignment === true ) {
- const vertexStart = Math.floor( range.start / 3 );
- const vertexCount = Math.ceil( range.count / 3 );
- dataOffset = vertexStart * 4 * byteOffsetFactor;
- size = vertexCount * 4 * byteOffsetFactor;
- } else {
- dataOffset = range.start * byteOffsetFactor;
- size = range.count * byteOffsetFactor;
- }
- const bufferOffset = dataOffset * ( isTyped ? array.BYTES_PER_ELEMENT : 1 ); // bufferOffset is always in bytes
- device.queue.writeBuffer(
- buffer,
- bufferOffset,
- array,
- dataOffset,
- size
- );
- }
- bufferAttribute.clearUpdateRanges();
- }
- }
- /**
- * This method creates the vertex buffer layout data which are
- * require when creating a render pipeline for the given render object.
- *
- * @param {RenderObject} renderObject - The render object.
- * @return {Array<Object>} An array holding objects which describe the vertex buffer layout.
- */
- createShaderVertexBuffers( renderObject ) {
- const attributes = renderObject.getAttributes();
- const vertexBuffers = new Map();
- for ( let slot = 0; slot < attributes.length; slot ++ ) {
- const geometryAttribute = attributes[ slot ];
- const bytesPerElement = geometryAttribute.array.BYTES_PER_ELEMENT;
- const bufferAttribute = this._getBufferAttribute( geometryAttribute );
- let vertexBufferLayout = vertexBuffers.get( bufferAttribute );
- if ( vertexBufferLayout === undefined ) {
- let arrayStride, stepMode;
- if ( geometryAttribute.isInterleavedBufferAttribute === true ) {
- arrayStride = geometryAttribute.data.stride * bytesPerElement;
- stepMode = geometryAttribute.data.isInstancedInterleavedBuffer ? GPUInputStepMode.Instance : GPUInputStepMode.Vertex;
- } else {
- arrayStride = geometryAttribute.itemSize * bytesPerElement;
- stepMode = geometryAttribute.isInstancedBufferAttribute ? GPUInputStepMode.Instance : GPUInputStepMode.Vertex;
- }
- // patch for INT16 and UINT16
- if ( geometryAttribute.normalized === false && ( geometryAttribute.array.constructor === Int16Array || geometryAttribute.array.constructor === Uint16Array ) ) {
- arrayStride = 4;
- }
- vertexBufferLayout = {
- arrayStride,
- attributes: [],
- stepMode
- };
- vertexBuffers.set( bufferAttribute, vertexBufferLayout );
- }
- const format = this._getVertexFormat( geometryAttribute );
- const offset = ( geometryAttribute.isInterleavedBufferAttribute === true ) ? geometryAttribute.offset * bytesPerElement : 0;
- vertexBufferLayout.attributes.push( {
- shaderLocation: slot,
- offset,
- format
- } );
- }
- return Array.from( vertexBuffers.values() );
- }
- /**
- * Destroys the GPU buffer of the given buffer attribute.
- *
- * @param {BufferAttribute} attribute - The buffer attribute.
- */
- destroyAttribute( attribute ) {
- const backend = this.backend;
- const data = backend.get( this._getBufferAttribute( attribute ) );
- data.buffer.destroy();
- backend.delete( attribute );
- }
- /**
- * This method performs a readback operation by moving buffer data from
- * a storage buffer attribute from the GPU to the CPU.
- *
- * @async
- * @param {StorageBufferAttribute} attribute - The storage buffer attribute.
- * @return {Promise<ArrayBuffer>} A promise that resolves with the buffer data when the data are ready.
- */
- async getArrayBufferAsync( attribute ) {
- const backend = this.backend;
- const device = backend.device;
- const data = backend.get( this._getBufferAttribute( attribute ) );
- const bufferGPU = data.buffer;
- const size = bufferGPU.size;
- const readBufferGPU = device.createBuffer( {
- label: `${ attribute.name }_readback`,
- size,
- usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ
- } );
- const cmdEncoder = device.createCommandEncoder( {
- label: `readback_encoder_${ attribute.name }`
- } );
- cmdEncoder.copyBufferToBuffer(
- bufferGPU,
- 0,
- readBufferGPU,
- 0,
- size
- );
- const gpuCommands = cmdEncoder.finish();
- device.queue.submit( [ gpuCommands ] );
- await readBufferGPU.mapAsync( GPUMapMode.READ );
- const arrayBuffer = readBufferGPU.getMappedRange();
- const dstBuffer = new attribute.array.constructor( arrayBuffer.slice( 0 ) );
- readBufferGPU.unmap();
- return dstBuffer.buffer;
- }
- /**
- * Returns the vertex format of the given buffer attribute.
- *
- * @private
- * @param {BufferAttribute} geometryAttribute - The buffer attribute.
- * @return {string|undefined} The vertex format (e.g. 'float32x3').
- */
- _getVertexFormat( geometryAttribute ) {
- const { itemSize, normalized } = geometryAttribute;
- const ArrayType = geometryAttribute.array.constructor;
- const AttributeType = geometryAttribute.constructor;
- let format;
- if ( itemSize === 1 ) {
- format = typeArraysToVertexFormatPrefixForItemSize1.get( ArrayType );
- } else {
- const prefixOptions = typedAttributeToVertexFormatPrefix.get( AttributeType ) || typedArraysToVertexFormatPrefix.get( ArrayType );
- const prefix = prefixOptions[ normalized ? 1 : 0 ];
- if ( prefix ) {
- const bytesPerUnit = ArrayType.BYTES_PER_ELEMENT * itemSize;
- const paddedBytesPerUnit = Math.floor( ( bytesPerUnit + 3 ) / 4 ) * 4;
- const paddedItemSize = paddedBytesPerUnit / ArrayType.BYTES_PER_ELEMENT;
- if ( paddedItemSize % 1 ) {
- throw new Error( 'THREE.WebGPUAttributeUtils: Bad vertex format item size.' );
- }
- format = `${prefix}x${paddedItemSize}`;
- }
- }
- if ( ! format ) {
- error( 'WebGPUAttributeUtils: Vertex format not supported yet.' );
- }
- return format;
- }
- /**
- * Utility method for handling interleaved buffer attributes correctly.
- * To process them, their `InterleavedBuffer` is returned.
- *
- * @private
- * @param {BufferAttribute} attribute - The attribute.
- * @return {BufferAttribute|InterleavedBuffer}
- */
- _getBufferAttribute( attribute ) {
- if ( attribute.isInterleavedBufferAttribute ) attribute = attribute.data;
- return attribute;
- }
- }
- /**
- * Class representing a WebGPU bind group layout.
- *
- * @private
- */
- class BindGroupLayout {
- /**
- * Constructs a new layout.
- *
- * @param {GPUBindGroupLayout} layoutGPU - A GPU Bind Group Layout.
- */
- constructor( layoutGPU ) {
- /**
- * The current GPUBindGroupLayout.
- *
- * @type {GPUBindGroupLayout}
- */
- this.layoutGPU = layoutGPU;
- /**
- * The number of bind groups that use this layout.
- *
- * @type {number}
- */
- this.usedTimes = 0;
- }
- }
- /**
- * A WebGPU backend utility module for managing bindings.
- *
- * When reading the documentation it's helpful to keep in mind that
- * all class definitions starting with 'GPU*' are modules from the
- * WebGPU API. So for example `BindGroup` is a class from the engine
- * whereas `GPUBindGroup` is a class from WebGPU.
- *
- * @private
- */
- class WebGPUBindingUtils {
- /**
- * Constructs a new utility object.
- *
- * @param {WebGPUBackend} backend - The WebGPU backend.
- */
- constructor( backend ) {
- /**
- * A reference to the WebGPU backend.
- *
- * @type {WebGPUBackend}
- */
- this.backend = backend;
- /**
- * A cache that maps combinations of layout entries to existing bind group layouts.
- *
- * @private
- * @type {Map<string, BindGroupLayout>}
- */
- this._bindGroupLayoutCache = new Map();
- }
- /**
- * Creates a GPU bind group layout for the given bind group.
- *
- * @param {BindGroup} bindGroup - The bind group.
- * @return {GPUBindGroupLayout} The GPU bind group layout.
- */
- createBindingsLayout( bindGroup ) {
- const backend = this.backend;
- const device = backend.device;
- const bindingsData = backend.get( bindGroup );
- // check if the the bind group already has a layout
- if ( bindingsData.layout ) {
- return bindingsData.layout.layoutGPU;
- }
- // if not, assing one
- const entries = this._createLayoutEntries( bindGroup );
- const bindGroupLayoutKey = JSON.stringify( entries );
- // try to find an existing layout in the cache
- let bindGroupLayout = this._bindGroupLayoutCache.get( bindGroupLayoutKey );
- // if not create a new one
- if ( bindGroupLayout === undefined ) {
- bindGroupLayout = new BindGroupLayout( device.createBindGroupLayout( { entries } ) );
- this._bindGroupLayoutCache.set( bindGroupLayoutKey, bindGroupLayout );
- }
- bindGroupLayout.usedTimes ++;
- bindingsData.layout = bindGroupLayout;
- bindingsData.layoutKey = bindGroupLayoutKey;
- return bindGroupLayout.layoutGPU;
- }
- /**
- * Creates bindings from the given bind group definition.
- *
- * @param {BindGroup} bindGroup - The bind group.
- * @param {Array<BindGroup>} bindings - Array of bind groups.
- * @param {number} cacheIndex - The cache index.
- * @param {number} version - The version.
- */
- createBindings( bindGroup, bindings, cacheIndex, version = 0 ) {
- const { backend } = this;
- const bindingsData = backend.get( bindGroup );
- // setup (static) binding layout and (dynamic) binding group
- const bindLayoutGPU = this.createBindingsLayout( bindGroup );
- let bindGroupGPU;
- if ( cacheIndex > 0 ) {
- if ( bindingsData.groups === undefined ) {
- bindingsData.groups = [];
- bindingsData.versions = [];
- }
- if ( bindingsData.versions[ cacheIndex ] === version ) {
- bindGroupGPU = bindingsData.groups[ cacheIndex ];
- }
- }
- if ( bindGroupGPU === undefined ) {
- bindGroupGPU = this.createBindGroup( bindGroup, bindLayoutGPU );
- if ( cacheIndex > 0 ) {
- bindingsData.groups[ cacheIndex ] = bindGroupGPU;
- bindingsData.versions[ cacheIndex ] = version;
- }
- }
- bindingsData.group = bindGroupGPU;
- }
- /**
- * Updates a buffer binding.
- *
- * @param {Buffer} binding - The buffer binding to update.
- */
- updateBinding( binding ) {
- const backend = this.backend;
- const device = backend.device;
- const array = binding.buffer; // cpu
- const buffer = backend.get( binding ).buffer; // gpu
- const updateRanges = binding.updateRanges;
- if ( updateRanges.length === 0 ) {
- device.queue.writeBuffer(
- buffer,
- 0,
- array,
- 0
- );
- } else {
- const isTyped = isTypedArray( array );
- const byteOffsetFactor = isTyped ? 1 : array.BYTES_PER_ELEMENT;
- for ( let i = 0, l = updateRanges.length; i < l; i ++ ) {
- const range = updateRanges[ i ];
- const dataOffset = range.start * byteOffsetFactor;
- const size = range.count * byteOffsetFactor;
- const bufferOffset = dataOffset * ( isTyped ? array.BYTES_PER_ELEMENT : 1 ); // bufferOffset is always in bytes
- device.queue.writeBuffer(
- buffer,
- bufferOffset,
- array,
- dataOffset,
- size
- );
- }
- binding.clearUpdateRanges();
- }
- }
- /**
- * Creates a GPU bind group for the camera index.
- *
- * @param {Uint32Array} data - The index data.
- * @param {GPUBindGroupLayout} layoutGPU - The GPU bind group layout.
- * @return {GPUBindGroup} The GPU bind group.
- */
- createBindGroupIndex( data, layoutGPU ) {
- const backend = this.backend;
- const device = backend.device;
- const usage = GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST;
- const index = data[ 0 ];
- const buffer = device.createBuffer( {
- label: 'bindingCameraIndex_' + index,
- size: 16, // uint(4) * 4
- usage: usage
- } );
- device.queue.writeBuffer( buffer, 0, data, 0 );
- const entries = [ { binding: 0, resource: { buffer } } ];
- return device.createBindGroup( {
- label: 'bindGroupCameraIndex_' + index,
- layout: layoutGPU,
- entries
- } );
- }
- /**
- * Creates a GPU bind group for the given bind group and GPU layout.
- *
- * @param {BindGroup} bindGroup - The bind group.
- * @param {GPUBindGroupLayout} layoutGPU - The GPU bind group layout.
- * @return {GPUBindGroup} The GPU bind group.
- */
- createBindGroup( bindGroup, layoutGPU ) {
- const backend = this.backend;
- const device = backend.device;
- let bindingPoint = 0;
- const entriesGPU = [];
- for ( const binding of bindGroup.bindings ) {
- if ( binding.isUniformBuffer ) {
- const bindingData = backend.get( binding );
- if ( bindingData.buffer === undefined ) {
- const byteLength = binding.byteLength;
- const usage = GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST;
- const visibilities = [];
- if ( binding.visibility & GPUShaderStage.VERTEX ) {
- visibilities.push( 'vertex' );
- }
- if ( binding.visibility & GPUShaderStage.FRAGMENT ) {
- visibilities.push( 'fragment' );
- }
- if ( binding.visibility & GPUShaderStage.COMPUTE ) {
- visibilities.push( 'compute' );
- }
- const bufferVisibility = `(${visibilities.join( ',' )})`;
- const bufferGPU = device.createBuffer( {
- label: `bindingBuffer${binding.id}_${binding.name}_${bufferVisibility}`,
- size: byteLength,
- usage: usage
- } );
- bindingData.buffer = bufferGPU;
- }
- entriesGPU.push( { binding: bindingPoint, resource: { buffer: bindingData.buffer } } );
- } else if ( binding.isStorageBuffer ) {
- const bindingData = backend.get( binding );
- if ( bindingData.buffer === undefined ) {
- const attribute = binding.attribute;
- //const usage = GPUBufferUsage.STORAGE | GPUBufferUsage.VERTEX | /*GPUBufferUsage.COPY_SRC |*/ GPUBufferUsage.COPY_DST;
- //backend.attributeUtils.createAttribute( attribute, usage ); // @TODO: Move it to universal renderer
- bindingData.buffer = backend.get( attribute ).buffer;
- }
- entriesGPU.push( { binding: bindingPoint, resource: { buffer: bindingData.buffer } } );
- } else if ( binding.isSampledTexture ) {
- const textureData = backend.get( binding.texture );
- let resourceGPU;
- if ( textureData.externalTexture !== undefined ) {
- resourceGPU = device.importExternalTexture( { source: textureData.externalTexture } );
- } else {
- const mipLevelCount = binding.store ? 1 : textureData.texture.mipLevelCount;
- const baseMipLevel = binding.store ? binding.mipLevel : 0;
- let propertyName = `view-${ textureData.texture.width }-${ textureData.texture.height }`;
- if ( textureData.texture.depthOrArrayLayers > 1 ) {
- propertyName += `-${ textureData.texture.depthOrArrayLayers }`;
- }
- propertyName += `-${ mipLevelCount }-${ baseMipLevel }`;
- resourceGPU = textureData[ propertyName ];
- if ( resourceGPU === undefined ) {
- const aspectGPU = GPUTextureAspect.All;
- let dimensionViewGPU;
- if ( binding.isSampledCubeTexture ) {
- dimensionViewGPU = GPUTextureViewDimension.Cube;
- } else if ( binding.isSampledTexture3D ) {
- dimensionViewGPU = GPUTextureViewDimension.ThreeD;
- } else if ( binding.texture.isArrayTexture || binding.texture.isDataArrayTexture || binding.texture.isCompressedArrayTexture ) {
- dimensionViewGPU = GPUTextureViewDimension.TwoDArray;
- } else {
- dimensionViewGPU = GPUTextureViewDimension.TwoD;
- }
- resourceGPU = textureData[ propertyName ] = textureData.texture.createView( { aspect: aspectGPU, dimension: dimensionViewGPU, mipLevelCount, baseMipLevel } );
- }
- }
- entriesGPU.push( { binding: bindingPoint, resource: resourceGPU } );
- } else if ( binding.isSampler ) {
- const textureGPU = backend.get( binding.texture );
- entriesGPU.push( { binding: bindingPoint, resource: textureGPU.sampler } );
- }
- bindingPoint ++;
- }
- return device.createBindGroup( {
- label: 'bindGroup_' + bindGroup.name,
- layout: layoutGPU,
- entries: entriesGPU
- } );
- }
- /**
- * Creates a GPU bind group layout entries for the given bind group.
- *
- * @private
- * @param {BindGroup} bindGroup - The bind group.
- * @return {Array<GPUBindGroupLayoutEntry>} The GPU bind group layout entries.
- */
- _createLayoutEntries( bindGroup ) {
- const entries = [];
- let index = 0;
- for ( const binding of bindGroup.bindings ) {
- const backend = this.backend;
- const bindingGPU = {
- binding: index,
- visibility: binding.visibility
- };
- if ( binding.isUniformBuffer || binding.isStorageBuffer ) {
- const buffer = {}; // GPUBufferBindingLayout
- if ( binding.isStorageBuffer ) {
- if ( binding.visibility & GPUShaderStage.COMPUTE ) {
- // compute
- if ( binding.access === NodeAccess.READ_WRITE || binding.access === NodeAccess.WRITE_ONLY ) {
- buffer.type = GPUBufferBindingType.Storage;
- } else {
- buffer.type = GPUBufferBindingType.ReadOnlyStorage;
- }
- } else {
- buffer.type = GPUBufferBindingType.ReadOnlyStorage;
- }
- }
- bindingGPU.buffer = buffer;
- } else if ( binding.isSampledTexture && binding.store ) {
- const storageTexture = {}; // GPUStorageTextureBindingLayout
- storageTexture.format = this.backend.get( binding.texture ).texture.format;
- const access = binding.access;
- if ( access === NodeAccess.READ_WRITE ) {
- storageTexture.access = GPUStorageTextureAccess.ReadWrite;
- } else if ( access === NodeAccess.WRITE_ONLY ) {
- storageTexture.access = GPUStorageTextureAccess.WriteOnly;
- } else {
- storageTexture.access = GPUStorageTextureAccess.ReadOnly;
- }
- if ( binding.texture.isArrayTexture ) {
- storageTexture.viewDimension = GPUTextureViewDimension.TwoDArray;
- } else if ( binding.texture.is3DTexture ) {
- storageTexture.viewDimension = GPUTextureViewDimension.ThreeD;
- }
- bindingGPU.storageTexture = storageTexture;
- } else if ( binding.isSampledTexture ) {
- const texture = {}; // GPUTextureBindingLayout
- const { primarySamples } = backend.utils.getTextureSampleData( binding.texture );
- if ( primarySamples > 1 ) {
- texture.multisampled = true;
- if ( ! binding.texture.isDepthTexture ) {
- texture.sampleType = GPUTextureSampleType.UnfilterableFloat;
- }
- }
- if ( binding.texture.isDepthTexture ) {
- if ( backend.compatibilityMode && binding.texture.compareFunction === null ) {
- texture.sampleType = GPUTextureSampleType.UnfilterableFloat;
- } else {
- texture.sampleType = GPUTextureSampleType.Depth;
- }
- } else if ( binding.texture.isDataTexture || binding.texture.isDataArrayTexture || binding.texture.isData3DTexture ) {
- const type = binding.texture.type;
- if ( type === IntType ) {
- texture.sampleType = GPUTextureSampleType.SInt;
- } else if ( type === UnsignedIntType ) {
- texture.sampleType = GPUTextureSampleType.UInt;
- } else if ( type === FloatType ) {
- if ( this.backend.hasFeature( 'float32-filterable' ) ) {
- texture.sampleType = GPUTextureSampleType.Float;
- } else {
- texture.sampleType = GPUTextureSampleType.UnfilterableFloat;
- }
- }
- }
- if ( binding.isSampledCubeTexture ) {
- texture.viewDimension = GPUTextureViewDimension.Cube;
- } else if ( binding.texture.isArrayTexture || binding.texture.isDataArrayTexture || binding.texture.isCompressedArrayTexture ) {
- texture.viewDimension = GPUTextureViewDimension.TwoDArray;
- } else if ( binding.isSampledTexture3D ) {
- texture.viewDimension = GPUTextureViewDimension.ThreeD;
- }
- bindingGPU.texture = texture;
- } else if ( binding.isSampler ) {
- const sampler = {}; // GPUSamplerBindingLayout
- if ( binding.texture.isDepthTexture ) {
- if ( binding.texture.compareFunction !== null ) {
- sampler.type = GPUSamplerBindingType.Comparison;
- } else if ( backend.compatibilityMode ) {
- sampler.type = GPUSamplerBindingType.NonFiltering;
- }
- }
- bindingGPU.sampler = sampler;
- } else {
- error( `WebGPUBindingUtils: Unsupported binding "${ binding }".` );
- }
- entries.push( bindingGPU );
- index ++;
- }
- return entries;
- }
- /**
- * Delete the data associated with a bind group.
- *
- * @param {BindGroup} bindGroup - The bind group.
- */
- deleteBindGroupData( bindGroup ) {
- const { backend } = this;
- const bindingsData = backend.get( bindGroup );
- if ( bindingsData.layout ) {
- bindingsData.layout.usedTimes --;
- if ( bindingsData.layout.usedTimes === 0 ) {
- this._bindGroupLayoutCache.delete( bindingsData.layoutKey );
- }
- bindingsData.layout = undefined;
- bindingsData.layoutKey = undefined;
- }
- }
- /**
- * Frees internal resources.
- */
- dispose() {
- this._bindGroupLayoutCache.clear();
- }
- }
- /**
- * A WebGPU backend utility module for managing pipelines.
- *
- * @private
- */
- class WebGPUPipelineUtils {
- /**
- * Constructs a new utility object.
- *
- * @param {WebGPUBackend} backend - The WebGPU backend.
- */
- constructor( backend ) {
- /**
- * A reference to the WebGPU backend.
- *
- * @type {WebGPUBackend}
- */
- this.backend = backend;
- /**
- * A Weak Map that tracks the active pipeline for render or compute passes.
- *
- * @private
- * @type {WeakMap<(GPURenderPassEncoder|GPUComputePassEncoder),(GPURenderPipeline|GPUComputePipeline)>}
- */
- this._activePipelines = new WeakMap();
- }
- /**
- * Sets the given pipeline for the given pass. The method makes sure to only set the
- * pipeline when necessary.
- *
- * @param {(GPURenderPassEncoder|GPUComputePassEncoder)} pass - The pass encoder.
- * @param {(GPURenderPipeline|GPUComputePipeline)} pipeline - The pipeline.
- */
- setPipeline( pass, pipeline ) {
- const currentPipeline = this._activePipelines.get( pass );
- if ( currentPipeline !== pipeline ) {
- pass.setPipeline( pipeline );
- this._activePipelines.set( pass, pipeline );
- }
- }
- /**
- * Returns the sample count derived from the given render context.
- *
- * @private
- * @param {RenderContext} renderContext - The render context.
- * @return {number} The sample count.
- */
- _getSampleCount( renderContext ) {
- return this.backend.utils.getSampleCountRenderContext( renderContext );
- }
- /**
- * Creates a render pipeline for the given render object.
- *
- * @param {RenderObject} renderObject - The render object.
- * @param {Array<Promise>} promises - An array of compilation promises which are used in `compileAsync()`.
- */
- createRenderPipeline( renderObject, promises ) {
- const { object, material, geometry, pipeline } = renderObject;
- const { vertexProgram, fragmentProgram } = pipeline;
- const backend = this.backend;
- const device = backend.device;
- const utils = backend.utils;
- const pipelineData = backend.get( pipeline );
- // bind group layouts
- const bindGroupLayouts = [];
- for ( const bindGroup of renderObject.getBindings() ) {
- const bindingsData = backend.get( bindGroup );
- const { layoutGPU } = bindingsData.layout;
- bindGroupLayouts.push( layoutGPU );
- }
- // vertex buffers
- const vertexBuffers = backend.attributeUtils.createShaderVertexBuffers( renderObject );
- // blending
- let blending;
- if ( material.blending !== NoBlending && ( material.blending !== NormalBlending || material.transparent !== false ) ) {
- blending = this._getBlending( material );
- }
- // stencil
- let stencilFront = {};
- if ( material.stencilWrite === true ) {
- stencilFront = {
- compare: this._getStencilCompare( material ),
- failOp: this._getStencilOperation( material.stencilFail ),
- depthFailOp: this._getStencilOperation( material.stencilZFail ),
- passOp: this._getStencilOperation( material.stencilZPass )
- };
- }
- const colorWriteMask = this._getColorWriteMask( material );
- const targets = [];
- if ( renderObject.context.textures !== null ) {
- const textures = renderObject.context.textures;
- for ( let i = 0; i < textures.length; i ++ ) {
- const colorFormat = utils.getTextureFormatGPU( textures[ i ] );
- if ( i === 0 ) {
- targets.push( {
- format: colorFormat,
- blend: blending,
- writeMask: colorWriteMask
- } );
- } else {
- targets.push( {
- format: colorFormat,
- writeMask: colorWriteMask
- } );
- }
- }
- } else {
- const colorFormat = utils.getCurrentColorFormat( renderObject.context );
- targets.push( {
- format: colorFormat,
- blend: blending,
- writeMask: colorWriteMask
- } );
- }
- const vertexModule = backend.get( vertexProgram ).module;
- const fragmentModule = backend.get( fragmentProgram ).module;
- const primitiveState = this._getPrimitiveState( object, geometry, material );
- const depthCompare = this._getDepthCompare( material );
- const depthStencilFormat = utils.getCurrentDepthStencilFormat( renderObject.context );
- const sampleCount = this._getSampleCount( renderObject.context );
- const pipelineDescriptor = {
- label: `renderPipeline_${ material.name || material.type }_${ material.id }`,
- vertex: Object.assign( {}, vertexModule, { buffers: vertexBuffers } ),
- fragment: Object.assign( {}, fragmentModule, { targets } ),
- primitive: primitiveState,
- multisample: {
- count: sampleCount,
- alphaToCoverageEnabled: material.alphaToCoverage && sampleCount > 1
- },
- layout: device.createPipelineLayout( {
- bindGroupLayouts
- } )
- };
- const depthStencil = {};
- const renderDepth = renderObject.context.depth;
- const renderStencil = renderObject.context.stencil;
- if ( renderDepth === true || renderStencil === true ) {
- if ( renderDepth === true ) {
- depthStencil.format = depthStencilFormat;
- depthStencil.depthWriteEnabled = material.depthWrite;
- depthStencil.depthCompare = depthCompare;
- }
- if ( renderStencil === true ) {
- depthStencil.stencilFront = stencilFront;
- depthStencil.stencilBack = {}; // three.js does not provide an API to configure the back function (gl.stencilFuncSeparate() was never used)
- depthStencil.stencilReadMask = material.stencilFuncMask;
- depthStencil.stencilWriteMask = material.stencilWriteMask;
- }
- if ( material.polygonOffset === true ) {
- depthStencil.depthBias = material.polygonOffsetUnits;
- depthStencil.depthBiasSlopeScale = material.polygonOffsetFactor;
- depthStencil.depthBiasClamp = 0; // three.js does not provide an API to configure this value
- }
- pipelineDescriptor.depthStencil = depthStencil;
- }
- // create pipeline
- device.pushErrorScope( 'validation' );
- if ( promises === null ) {
- pipelineData.pipeline = device.createRenderPipeline( pipelineDescriptor );
- device.popErrorScope().then( ( err ) => {
- if ( err !== null ) {
- pipelineData.error = true;
- error( err.message );
- }
- } );
- } else {
- const p = new Promise( async ( resolve /*, reject*/ ) => {
- try {
- pipelineData.pipeline = await device.createRenderPipelineAsync( pipelineDescriptor );
- } catch ( err ) { }
- const errorScope = await device.popErrorScope();
- if ( errorScope !== null ) {
- pipelineData.error = true;
- error( errorScope.message );
- }
- resolve();
- } );
- promises.push( p );
- }
- }
- /**
- * Creates GPU render bundle encoder for the given render context.
- *
- * @param {RenderContext} renderContext - The render context.
- * @param {?string} [label='renderBundleEncoder'] - The label.
- * @return {GPURenderBundleEncoder} The GPU render bundle encoder.
- */
- createBundleEncoder( renderContext, label = 'renderBundleEncoder' ) {
- const backend = this.backend;
- const { utils, device } = backend;
- const depthStencilFormat = utils.getCurrentDepthStencilFormat( renderContext );
- const colorFormats = utils.getCurrentColorFormats( renderContext );
- const sampleCount = this._getSampleCount( renderContext );
- const descriptor = {
- label,
- colorFormats,
- depthStencilFormat,
- sampleCount
- };
- return device.createRenderBundleEncoder( descriptor );
- }
- /**
- * Creates a compute pipeline for the given compute node.
- *
- * @param {ComputePipeline} pipeline - The compute pipeline.
- * @param {Array<BindGroup>} bindings - The bindings.
- */
- createComputePipeline( pipeline, bindings ) {
- const backend = this.backend;
- const device = backend.device;
- const computeProgram = backend.get( pipeline.computeProgram ).module;
- const pipelineGPU = backend.get( pipeline );
- // bind group layouts
- const bindGroupLayouts = [];
- for ( const bindingsGroup of bindings ) {
- const bindingsData = backend.get( bindingsGroup );
- const { layoutGPU } = bindingsData.layout;
- bindGroupLayouts.push( layoutGPU );
- }
- pipelineGPU.pipeline = device.createComputePipeline( {
- compute: computeProgram,
- layout: device.createPipelineLayout( {
- bindGroupLayouts
- } )
- } );
- }
- /**
- * Returns the blending state as a descriptor object required
- * for the pipeline creation.
- *
- * @private
- * @param {Material} material - The material.
- * @return {Object} The blending state.
- */
- _getBlending( material ) {
- let color, alpha;
- const blending = material.blending;
- const blendSrc = material.blendSrc;
- const blendDst = material.blendDst;
- const blendEquation = material.blendEquation;
- if ( blending === CustomBlending ) {
- const blendSrcAlpha = material.blendSrcAlpha !== null ? material.blendSrcAlpha : blendSrc;
- const blendDstAlpha = material.blendDstAlpha !== null ? material.blendDstAlpha : blendDst;
- const blendEquationAlpha = material.blendEquationAlpha !== null ? material.blendEquationAlpha : blendEquation;
- color = {
- srcFactor: this._getBlendFactor( blendSrc ),
- dstFactor: this._getBlendFactor( blendDst ),
- operation: this._getBlendOperation( blendEquation )
- };
- alpha = {
- srcFactor: this._getBlendFactor( blendSrcAlpha ),
- dstFactor: this._getBlendFactor( blendDstAlpha ),
- operation: this._getBlendOperation( blendEquationAlpha )
- };
- } else {
- const premultipliedAlpha = material.premultipliedAlpha;
- const setBlend = ( srcRGB, dstRGB, srcAlpha, dstAlpha ) => {
- color = {
- srcFactor: srcRGB,
- dstFactor: dstRGB,
- operation: GPUBlendOperation.Add
- };
- alpha = {
- srcFactor: srcAlpha,
- dstFactor: dstAlpha,
- operation: GPUBlendOperation.Add
- };
- };
- if ( premultipliedAlpha ) {
- switch ( blending ) {
- case NormalBlending:
- setBlend( GPUBlendFactor.One, GPUBlendFactor.OneMinusSrcAlpha, GPUBlendFactor.One, GPUBlendFactor.OneMinusSrcAlpha );
- break;
- case AdditiveBlending:
- setBlend( GPUBlendFactor.One, GPUBlendFactor.One, GPUBlendFactor.One, GPUBlendFactor.One );
- break;
- case SubtractiveBlending:
- setBlend( GPUBlendFactor.Zero, GPUBlendFactor.OneMinusSrc, GPUBlendFactor.Zero, GPUBlendFactor.One );
- break;
- case MultiplyBlending:
- setBlend( GPUBlendFactor.Dst, GPUBlendFactor.OneMinusSrcAlpha, GPUBlendFactor.Zero, GPUBlendFactor.One );
- break;
- }
- } else {
- switch ( blending ) {
- case NormalBlending:
- setBlend( GPUBlendFactor.SrcAlpha, GPUBlendFactor.OneMinusSrcAlpha, GPUBlendFactor.One, GPUBlendFactor.OneMinusSrcAlpha );
- break;
- case AdditiveBlending:
- setBlend( GPUBlendFactor.SrcAlpha, GPUBlendFactor.One, GPUBlendFactor.One, GPUBlendFactor.One );
- break;
- case SubtractiveBlending:
- error( 'WebGPURenderer: SubtractiveBlending requires material.premultipliedAlpha = true' );
- break;
- case MultiplyBlending:
- error( 'WebGPURenderer: MultiplyBlending requires material.premultipliedAlpha = true' );
- break;
- }
- }
- }
- if ( color !== undefined && alpha !== undefined ) {
- return { color, alpha };
- } else {
- error( 'WebGPURenderer: Invalid blending: ', blending );
- }
- }
- /**
- * Returns the GPU blend factor which is required for the pipeline creation.
- *
- * @private
- * @param {number} blend - The blend factor as a three.js constant.
- * @return {string} The GPU blend factor.
- */
- _getBlendFactor( blend ) {
- let blendFactor;
- switch ( blend ) {
- case ZeroFactor:
- blendFactor = GPUBlendFactor.Zero;
- break;
- case OneFactor:
- blendFactor = GPUBlendFactor.One;
- break;
- case SrcColorFactor:
- blendFactor = GPUBlendFactor.Src;
- break;
- case OneMinusSrcColorFactor:
- blendFactor = GPUBlendFactor.OneMinusSrc;
- break;
- case SrcAlphaFactor:
- blendFactor = GPUBlendFactor.SrcAlpha;
- break;
- case OneMinusSrcAlphaFactor:
- blendFactor = GPUBlendFactor.OneMinusSrcAlpha;
- break;
- case DstColorFactor:
- blendFactor = GPUBlendFactor.Dst;
- break;
- case OneMinusDstColorFactor:
- blendFactor = GPUBlendFactor.OneMinusDst;
- break;
- case DstAlphaFactor:
- blendFactor = GPUBlendFactor.DstAlpha;
- break;
- case OneMinusDstAlphaFactor:
- blendFactor = GPUBlendFactor.OneMinusDstAlpha;
- break;
- case SrcAlphaSaturateFactor:
- blendFactor = GPUBlendFactor.SrcAlphaSaturated;
- break;
- case BlendColorFactor:
- blendFactor = GPUBlendFactor.Constant;
- break;
- case OneMinusBlendColorFactor:
- blendFactor = GPUBlendFactor.OneMinusConstant;
- break;
- default:
- error( 'WebGPURenderer: Blend factor not supported.', blend );
- }
- return blendFactor;
- }
- /**
- * Returns the GPU stencil compare function which is required for the pipeline creation.
- *
- * @private
- * @param {Material} material - The material.
- * @return {string} The GPU stencil compare function.
- */
- _getStencilCompare( material ) {
- let stencilCompare;
- const stencilFunc = material.stencilFunc;
- switch ( stencilFunc ) {
- case NeverStencilFunc:
- stencilCompare = GPUCompareFunction.Never;
- break;
- case AlwaysStencilFunc:
- stencilCompare = GPUCompareFunction.Always;
- break;
- case LessStencilFunc:
- stencilCompare = GPUCompareFunction.Less;
- break;
- case LessEqualStencilFunc:
- stencilCompare = GPUCompareFunction.LessEqual;
- break;
- case EqualStencilFunc:
- stencilCompare = GPUCompareFunction.Equal;
- break;
- case GreaterEqualStencilFunc:
- stencilCompare = GPUCompareFunction.GreaterEqual;
- break;
- case GreaterStencilFunc:
- stencilCompare = GPUCompareFunction.Greater;
- break;
- case NotEqualStencilFunc:
- stencilCompare = GPUCompareFunction.NotEqual;
- break;
- default:
- error( 'WebGPURenderer: Invalid stencil function.', stencilFunc );
- }
- return stencilCompare;
- }
- /**
- * Returns the GPU stencil operation which is required for the pipeline creation.
- *
- * @private
- * @param {number} op - A three.js constant defining the stencil operation.
- * @return {string} The GPU stencil operation.
- */
- _getStencilOperation( op ) {
- let stencilOperation;
- switch ( op ) {
- case KeepStencilOp:
- stencilOperation = GPUStencilOperation.Keep;
- break;
- case ZeroStencilOp:
- stencilOperation = GPUStencilOperation.Zero;
- break;
- case ReplaceStencilOp:
- stencilOperation = GPUStencilOperation.Replace;
- break;
- case InvertStencilOp:
- stencilOperation = GPUStencilOperation.Invert;
- break;
- case IncrementStencilOp:
- stencilOperation = GPUStencilOperation.IncrementClamp;
- break;
- case DecrementStencilOp:
- stencilOperation = GPUStencilOperation.DecrementClamp;
- break;
- case IncrementWrapStencilOp:
- stencilOperation = GPUStencilOperation.IncrementWrap;
- break;
- case DecrementWrapStencilOp:
- stencilOperation = GPUStencilOperation.DecrementWrap;
- break;
- default:
- error( 'WebGPURenderer: Invalid stencil operation.', stencilOperation );
- }
- return stencilOperation;
- }
- /**
- * Returns the GPU blend operation which is required for the pipeline creation.
- *
- * @private
- * @param {number} blendEquation - A three.js constant defining the blend equation.
- * @return {string} The GPU blend operation.
- */
- _getBlendOperation( blendEquation ) {
- let blendOperation;
- switch ( blendEquation ) {
- case AddEquation:
- blendOperation = GPUBlendOperation.Add;
- break;
- case SubtractEquation:
- blendOperation = GPUBlendOperation.Subtract;
- break;
- case ReverseSubtractEquation:
- blendOperation = GPUBlendOperation.ReverseSubtract;
- break;
- case MinEquation:
- blendOperation = GPUBlendOperation.Min;
- break;
- case MaxEquation:
- blendOperation = GPUBlendOperation.Max;
- break;
- default:
- error( 'WebGPUPipelineUtils: Blend equation not supported.', blendEquation );
- }
- return blendOperation;
- }
- /**
- * Returns the primitive state as a descriptor object required
- * for the pipeline creation.
- *
- * @private
- * @param {Object3D} object - The 3D object.
- * @param {BufferGeometry} geometry - The geometry.
- * @param {Material} material - The material.
- * @return {Object} The primitive state.
- */
- _getPrimitiveState( object, geometry, material ) {
- const descriptor = {};
- const utils = this.backend.utils;
- //
- descriptor.topology = utils.getPrimitiveTopology( object, material );
- if ( geometry.index !== null && object.isLine === true && object.isLineSegments !== true ) {
- descriptor.stripIndexFormat = ( geometry.index.array instanceof Uint16Array ) ? GPUIndexFormat.Uint16 : GPUIndexFormat.Uint32;
- }
- //
- let flipSided = ( material.side === BackSide );
- if ( object.isMesh && object.matrixWorld.determinant() < 0 ) flipSided = ! flipSided;
- descriptor.frontFace = ( flipSided === true ) ? GPUFrontFace.CW : GPUFrontFace.CCW;
- //
- descriptor.cullMode = ( material.side === DoubleSide ) ? GPUCullMode.None : GPUCullMode.Back;
- return descriptor;
- }
- /**
- * Returns the GPU color write mask which is required for the pipeline creation.
- *
- * @private
- * @param {Material} material - The material.
- * @return {number} The GPU color write mask.
- */
- _getColorWriteMask( material ) {
- return ( material.colorWrite === true ) ? GPUColorWriteFlags.All : GPUColorWriteFlags.None;
- }
- /**
- * Returns the GPU depth compare function which is required for the pipeline creation.
- *
- * @private
- * @param {Material} material - The material.
- * @return {string} The GPU depth compare function.
- */
- _getDepthCompare( material ) {
- let depthCompare;
- if ( material.depthTest === false ) {
- depthCompare = GPUCompareFunction.Always;
- } else {
- const depthFunc = material.depthFunc;
- switch ( depthFunc ) {
- case NeverDepth:
- depthCompare = GPUCompareFunction.Never;
- break;
- case AlwaysDepth:
- depthCompare = GPUCompareFunction.Always;
- break;
- case LessDepth:
- depthCompare = GPUCompareFunction.Less;
- break;
- case LessEqualDepth:
- depthCompare = GPUCompareFunction.LessEqual;
- break;
- case EqualDepth:
- depthCompare = GPUCompareFunction.Equal;
- break;
- case GreaterEqualDepth:
- depthCompare = GPUCompareFunction.GreaterEqual;
- break;
- case GreaterDepth:
- depthCompare = GPUCompareFunction.Greater;
- break;
- case NotEqualDepth:
- depthCompare = GPUCompareFunction.NotEqual;
- break;
- default:
- error( 'WebGPUPipelineUtils: Invalid depth function.', depthFunc );
- }
- }
- return depthCompare;
- }
- }
- /**
- * Manages a pool of WebGPU timestamp queries for performance measurement.
- * Extends the base TimestampQueryPool to provide WebGPU-specific implementation.
- *
- * @augments TimestampQueryPool
- */
- class WebGPUTimestampQueryPool extends TimestampQueryPool {
- /**
- * Creates a new WebGPU timestamp query pool.
- *
- * @param {GPUDevice} device - The WebGPU device to create queries on.
- * @param {string} type - The type identifier for this query pool.
- * @param {number} [maxQueries=2048] - Maximum number of queries this pool can hold.
- */
- constructor( device, type, maxQueries = 2048 ) {
- super( maxQueries );
- this.device = device;
- this.type = type;
- this.querySet = this.device.createQuerySet( {
- type: 'timestamp',
- count: this.maxQueries,
- label: `queryset_global_timestamp_${type}`
- } );
- const bufferSize = this.maxQueries * 8;
- this.resolveBuffer = this.device.createBuffer( {
- label: `buffer_timestamp_resolve_${type}`,
- size: bufferSize,
- usage: GPUBufferUsage.QUERY_RESOLVE | GPUBufferUsage.COPY_SRC
- } );
- this.resultBuffer = this.device.createBuffer( {
- label: `buffer_timestamp_result_${type}`,
- size: bufferSize,
- usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ
- } );
- }
- /**
- * Allocates a pair of queries for a given render context.
- *
- * @param {string} uid - A unique identifier for the render context.
- * @returns {?number} The base offset for the allocated queries, or null if allocation failed.
- */
- allocateQueriesForContext( uid ) {
- if ( ! this.trackTimestamp || this.isDisposed ) return null;
- if ( this.currentQueryIndex + 2 > this.maxQueries ) {
- warnOnce( `WebGPUTimestampQueryPool [${ this.type }]: Maximum number of queries exceeded, when using trackTimestamp it is necessary to resolves the queries via renderer.resolveTimestampsAsync( THREE.TimestampQuery.${ this.type.toUpperCase() } ).` );
- return null;
- }
- const baseOffset = this.currentQueryIndex;
- this.currentQueryIndex += 2;
- this.queryOffsets.set( uid, baseOffset );
- return baseOffset;
- }
- /**
- * Asynchronously resolves all pending queries and returns the total duration.
- * If there's already a pending resolve operation, returns that promise instead.
- *
- * @async
- * @returns {Promise<number>} The total duration in milliseconds, or the last valid value if resolution fails.
- */
- async resolveQueriesAsync() {
- if ( ! this.trackTimestamp || this.currentQueryIndex === 0 || this.isDisposed ) {
- return this.lastValue;
- }
- if ( this.pendingResolve ) {
- return this.pendingResolve;
- }
- this.pendingResolve = this._resolveQueries();
- try {
- const result = await this.pendingResolve;
- return result;
- } finally {
- this.pendingResolve = null;
- }
- }
- /**
- * Internal method to resolve queries and calculate total duration.
- *
- * @async
- * @private
- * @returns {Promise<number>} The total duration in milliseconds.
- */
- async _resolveQueries() {
- if ( this.isDisposed ) {
- return this.lastValue;
- }
- try {
- if ( this.resultBuffer.mapState !== 'unmapped' ) {
- return this.lastValue;
- }
- const currentOffsets = new Map( this.queryOffsets );
- const queryCount = this.currentQueryIndex;
- const bytesUsed = queryCount * 8;
- // Reset state before GPU work
- this.currentQueryIndex = 0;
- this.queryOffsets.clear();
- const commandEncoder = this.device.createCommandEncoder();
- commandEncoder.resolveQuerySet(
- this.querySet,
- 0,
- queryCount,
- this.resolveBuffer,
- 0
- );
- commandEncoder.copyBufferToBuffer(
- this.resolveBuffer,
- 0,
- this.resultBuffer,
- 0,
- bytesUsed
- );
- const commandBuffer = commandEncoder.finish();
- this.device.queue.submit( [ commandBuffer ] );
- if ( this.resultBuffer.mapState !== 'unmapped' ) {
- return this.lastValue;
- }
- // Create and track the mapping operation
- await this.resultBuffer.mapAsync( GPUMapMode.READ, 0, bytesUsed );
- if ( this.isDisposed ) {
- if ( this.resultBuffer.mapState === 'mapped' ) {
- this.resultBuffer.unmap();
- }
- return this.lastValue;
- }
- //
- const times = new BigUint64Array( this.resultBuffer.getMappedRange( 0, bytesUsed ) );
- const framesDuration = {};
- const frames = [];
- for ( const [ uid, baseOffset ] of currentOffsets ) {
- const match = uid.match( /^(.*):f(\d+)$/ );
- const frame = parseInt( match[ 2 ] );
- if ( frames.includes( frame ) === false ) {
- frames.push( frame );
- }
- if ( framesDuration[ frame ] === undefined ) framesDuration[ frame ] = 0;
- const startTime = times[ baseOffset ];
- const endTime = times[ baseOffset + 1 ];
- const duration = Number( endTime - startTime ) / 1e6;
- this.timestamps.set( uid, duration );
- framesDuration[ frame ] += duration;
- }
- // Return the total duration of the last frame
- const totalDuration = framesDuration[ frames[ frames.length - 1 ] ];
- this.resultBuffer.unmap();
- this.lastValue = totalDuration;
- this.frames = frames;
- return totalDuration;
- } catch ( e ) {
- error( 'Error resolving queries:', e );
- if ( this.resultBuffer.mapState === 'mapped' ) {
- this.resultBuffer.unmap();
- }
- return this.lastValue;
- }
- }
- /**
- * Dispose of the query pool.
- *
- * @async
- * @returns {Promise} A Promise that resolves when the dispose has been executed.
- */
- async dispose() {
- if ( this.isDisposed ) {
- return;
- }
- this.isDisposed = true;
- // Wait for pending resolve operation
- if ( this.pendingResolve ) {
- try {
- await this.pendingResolve;
- } catch ( e ) {
- error( 'Error waiting for pending resolve:', e );
- }
- }
- // Ensure buffer is unmapped before destroying
- if ( this.resultBuffer && this.resultBuffer.mapState === 'mapped' ) {
- try {
- this.resultBuffer.unmap();
- } catch ( e ) {
- error( 'Error unmapping buffer:', e );
- }
- }
- // Destroy resources
- if ( this.querySet ) {
- this.querySet.destroy();
- this.querySet = null;
- }
- if ( this.resolveBuffer ) {
- this.resolveBuffer.destroy();
- this.resolveBuffer = null;
- }
- if ( this.resultBuffer ) {
- this.resultBuffer.destroy();
- this.resultBuffer = null;
- }
- this.queryOffsets.clear();
- this.pendingResolve = null;
- }
- }
- /*// debugger tools
- import 'https://greggman.github.io/webgpu-avoid-redundant-state-setting/webgpu-check-redundant-state-setting.js';
- //*/
- /**
- * A backend implementation targeting WebGPU.
- *
- * @private
- * @augments Backend
- */
- class WebGPUBackend extends Backend {
- /**
- * WebGPUBackend options.
- *
- * @typedef {Object} WebGPUBackend~Options
- * @property {boolean} [logarithmicDepthBuffer=false] - Whether logarithmic depth buffer is enabled or not.
- * @property {boolean} [alpha=true] - Whether the default framebuffer (which represents the final contents of the canvas) should be transparent or opaque.
- * @property {boolean} [compatibilityMode=false] - Whether the backend should be in compatibility mode or not.
- * @property {boolean} [depth=true] - Whether the default framebuffer should have a depth buffer or not.
- * @property {boolean} [stencil=false] - Whether the default framebuffer should have a stencil buffer or not.
- * @property {boolean} [antialias=false] - Whether MSAA as the default anti-aliasing should be enabled or not.
- * @property {number} [samples=0] - When `antialias` is `true`, `4` samples are used by default. Set this parameter to any other integer value than 0 to overwrite the default.
- * @property {boolean} [forceWebGL=false] - If set to `true`, the renderer uses a WebGL 2 backend no matter if WebGPU is supported or not.
- * @property {boolean} [trackTimestamp=false] - Whether to track timestamps with a Timestamp Query API or not.
- * @property {string} [powerPreference=undefined] - The power preference.
- * @property {Object} [requiredLimits=undefined] - Specifies the limits that are required by the device request. The request will fail if the adapter cannot provide these limits.
- * @property {GPUDevice} [device=undefined] - If there is an existing GPU device on app level, it can be passed to the renderer as a parameter.
- * @property {number} [outputType=undefined] - Texture type for output to canvas. By default, device's preferred format is used; other formats may incur overhead.
- */
- /**
- * Constructs a new WebGPU backend.
- *
- * @param {WebGPUBackend~Options} [parameters] - The configuration parameter.
- */
- constructor( parameters = {} ) {
- super( parameters );
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isWebGPUBackend = true;
- // some parameters require default values other than "undefined"
- this.parameters.alpha = ( parameters.alpha === undefined ) ? true : parameters.alpha;
- this.parameters.compatibilityMode = ( parameters.compatibilityMode === undefined ) ? false : parameters.compatibilityMode;
- this.parameters.requiredLimits = ( parameters.requiredLimits === undefined ) ? {} : parameters.requiredLimits;
- /**
- * Indicates whether the backend is in compatibility mode or not.
- * @type {boolean}
- * @default false
- */
- this.compatibilityMode = this.parameters.compatibilityMode;
- /**
- * A reference to the device.
- *
- * @type {?GPUDevice}
- * @default null
- */
- this.device = null;
- /**
- * A reference to the default render pass descriptor.
- *
- * @type {?Object}
- * @default null
- */
- this.defaultRenderPassdescriptor = null;
- /**
- * A reference to a backend module holding common utility functions.
- *
- * @type {WebGPUUtils}
- */
- this.utils = new WebGPUUtils( this );
- /**
- * A reference to a backend module holding shader attribute-related
- * utility functions.
- *
- * @type {WebGPUAttributeUtils}
- */
- this.attributeUtils = new WebGPUAttributeUtils( this );
- /**
- * A reference to a backend module holding shader binding-related
- * utility functions.
- *
- * @type {WebGPUBindingUtils}
- */
- this.bindingUtils = new WebGPUBindingUtils( this );
- /**
- * A reference to a backend module holding shader pipeline-related
- * utility functions.
- *
- * @type {WebGPUPipelineUtils}
- */
- this.pipelineUtils = new WebGPUPipelineUtils( this );
- /**
- * A reference to a backend module holding shader texture-related
- * utility functions.
- *
- * @type {WebGPUTextureUtils}
- */
- this.textureUtils = new WebGPUTextureUtils( this );
- /**
- * A map that manages the resolve buffers for occlusion queries.
- *
- * @type {Map<number,GPUBuffer>}
- */
- this.occludedResolveCache = new Map();
- }
- /**
- * Initializes the backend so it is ready for usage.
- *
- * @async
- * @param {Renderer} renderer - The renderer.
- * @return {Promise} A Promise that resolves when the backend has been initialized.
- */
- async init( renderer ) {
- await super.init( renderer );
- //
- const parameters = this.parameters;
- // create the device if it is not passed with parameters
- let device;
- if ( parameters.device === undefined ) {
- const adapterOptions = {
- powerPreference: parameters.powerPreference,
- featureLevel: parameters.compatibilityMode ? 'compatibility' : undefined
- };
- const adapter = ( typeof navigator !== 'undefined' ) ? await navigator.gpu.requestAdapter( adapterOptions ) : null;
- if ( adapter === null ) {
- throw new Error( 'WebGPUBackend: Unable to create WebGPU adapter.' );
- }
- // feature support
- const features = Object.values( GPUFeatureName );
- const supportedFeatures = [];
- for ( const name of features ) {
- if ( adapter.features.has( name ) ) {
- supportedFeatures.push( name );
- }
- }
- const deviceDescriptor = {
- requiredFeatures: supportedFeatures,
- requiredLimits: parameters.requiredLimits
- };
- device = await adapter.requestDevice( deviceDescriptor );
- } else {
- device = parameters.device;
- }
- device.lost.then( ( info ) => {
- if ( info.reason === 'destroyed' ) return;
- const deviceLossInfo = {
- api: 'WebGPU',
- message: info.message || 'Unknown reason',
- reason: info.reason || null,
- originalEvent: info
- };
- renderer.onDeviceLost( deviceLossInfo );
- } );
- this.device = device;
- this.trackTimestamp = this.trackTimestamp && this.hasFeature( GPUFeatureName.TimestampQuery );
- this.updateSize();
- }
- /**
- * A reference to the context.
- *
- * @type {?GPUCanvasContext}
- * @default null
- */
- get context() {
- const canvasTarget = this.renderer.getCanvasTarget();
- const canvasData = this.get( canvasTarget );
- let context = canvasData.context;
- if ( context === undefined ) {
- const parameters = this.parameters;
- if ( canvasTarget.isDefaultCanvasTarget === true && parameters.context !== undefined ) {
- context = parameters.context;
- } else {
- context = canvasTarget.domElement.getContext( 'webgpu' );
- }
- // OffscreenCanvas does not have setAttribute, see #22811
- if ( 'setAttribute' in canvasTarget.domElement ) canvasTarget.domElement.setAttribute( 'data-engine', `three.js r${ REVISION } webgpu` );
- const alphaMode = parameters.alpha ? 'premultiplied' : 'opaque';
- const toneMappingMode = parameters.outputType === HalfFloatType ? 'extended' : 'standard';
- context.configure( {
- device: this.device,
- format: this.utils.getPreferredCanvasFormat(),
- usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC,
- alphaMode: alphaMode,
- toneMapping: {
- mode: toneMappingMode
- }
- } );
- canvasData.context = context;
- }
- return context;
- }
- /**
- * The coordinate system of the backend.
- *
- * @type {number}
- * @readonly
- */
- get coordinateSystem() {
- return WebGPUCoordinateSystem;
- }
- /**
- * This method performs a readback operation by moving buffer data from
- * a storage buffer attribute from the GPU to the CPU.
- *
- * @async
- * @param {StorageBufferAttribute} attribute - The storage buffer attribute.
- * @return {Promise<ArrayBuffer>} A promise that resolves with the buffer data when the data are ready.
- */
- async getArrayBufferAsync( attribute ) {
- return await this.attributeUtils.getArrayBufferAsync( attribute );
- }
- /**
- * Returns the backend's rendering context.
- *
- * @return {GPUCanvasContext} The rendering context.
- */
- getContext() {
- return this.context;
- }
- /**
- * Returns the default render pass descriptor.
- *
- * In WebGPU, the default framebuffer must be configured
- * like custom framebuffers so the backend needs a render
- * pass descriptor even when rendering directly to screen.
- *
- * @private
- * @return {Object} The render pass descriptor.
- */
- _getDefaultRenderPassDescriptor() {
- const renderer = this.renderer;
- const canvasTarget = renderer.getCanvasTarget();
- const canvasData = this.get( canvasTarget );
- const samples = renderer.currentSamples;
- let descriptor = canvasData.descriptor;
- if ( descriptor === undefined || canvasData.samples !== samples ) {
- descriptor = {
- colorAttachments: [ {
- view: null
- } ]
- };
- if ( renderer.depth === true || renderer.stencil === true ) {
- descriptor.depthStencilAttachment = {
- view: this.textureUtils.getDepthBuffer( renderer.depth, renderer.stencil ).createView()
- };
- }
- const colorAttachment = descriptor.colorAttachments[ 0 ];
- if ( samples > 0 ) {
- colorAttachment.view = this.textureUtils.getColorBuffer().createView();
- } else {
- colorAttachment.resolveTarget = undefined;
- }
- canvasData.descriptor = descriptor;
- canvasData.samples = samples;
- }
- const colorAttachment = descriptor.colorAttachments[ 0 ];
- if ( samples > 0 ) {
- colorAttachment.resolveTarget = this.context.getCurrentTexture().createView();
- } else {
- colorAttachment.view = this.context.getCurrentTexture().createView();
- }
- return descriptor;
- }
- /**
- * Internal to determine if the current render target is a render target array with depth 2D array texture.
- *
- * @param {RenderContext} renderContext - The render context.
- * @return {boolean} Whether the render target is a render target array with depth 2D array texture.
- *
- * @private
- */
- _isRenderCameraDepthArray( renderContext ) {
- return renderContext.depthTexture && renderContext.depthTexture.image.depth > 1 && renderContext.camera.isArrayCamera;
- }
- /**
- * Returns the render pass descriptor for the given render context.
- *
- * @private
- * @param {RenderContext} renderContext - The render context.
- * @param {Object} colorAttachmentsConfig - Configuration object for the color attachments.
- * @return {Object} The render pass descriptor.
- */
- _getRenderPassDescriptor( renderContext, colorAttachmentsConfig = {} ) {
- const renderTarget = renderContext.renderTarget;
- const renderTargetData = this.get( renderTarget );
- let descriptors = renderTargetData.descriptors;
- if ( descriptors === undefined ||
- renderTargetData.width !== renderTarget.width ||
- renderTargetData.height !== renderTarget.height ||
- renderTargetData.samples !== renderTarget.samples
- ) {
- descriptors = {};
- renderTargetData.descriptors = descriptors;
- }
- const cacheKey = renderContext.getCacheKey();
- let descriptorBase = descriptors[ cacheKey ];
- if ( descriptorBase === undefined ) {
- const textures = renderContext.textures;
- const textureViews = [];
- let sliceIndex;
- const isRenderCameraDepthArray = this._isRenderCameraDepthArray( renderContext );
- for ( let i = 0; i < textures.length; i ++ ) {
- const textureData = this.get( textures[ i ] );
- const viewDescriptor = {
- label: `colorAttachment_${ i }`,
- baseMipLevel: renderContext.activeMipmapLevel,
- mipLevelCount: 1,
- baseArrayLayer: renderContext.activeCubeFace,
- arrayLayerCount: 1,
- dimension: GPUTextureViewDimension.TwoD
- };
- if ( renderTarget.isRenderTarget3D ) {
- sliceIndex = renderContext.activeCubeFace;
- viewDescriptor.baseArrayLayer = 0;
- viewDescriptor.dimension = GPUTextureViewDimension.ThreeD;
- viewDescriptor.depthOrArrayLayers = textures[ i ].image.depth;
- } else if ( renderTarget.isRenderTarget && textures[ i ].image.depth > 1 ) {
- if ( isRenderCameraDepthArray === true ) {
- const cameras = renderContext.camera.cameras;
- for ( let layer = 0; layer < cameras.length; layer ++ ) {
- const layerViewDescriptor = {
- ...viewDescriptor,
- baseArrayLayer: layer,
- arrayLayerCount: 1,
- dimension: GPUTextureViewDimension.TwoD
- };
- const textureView = textureData.texture.createView( layerViewDescriptor );
- textureViews.push( {
- view: textureView,
- resolveTarget: undefined,
- depthSlice: undefined
- } );
- }
- } else {
- viewDescriptor.dimension = GPUTextureViewDimension.TwoDArray;
- viewDescriptor.depthOrArrayLayers = textures[ i ].image.depth;
- }
- }
- if ( isRenderCameraDepthArray !== true ) {
- const textureView = textureData.texture.createView( viewDescriptor );
- let view, resolveTarget;
- if ( textureData.msaaTexture !== undefined ) {
- view = textureData.msaaTexture.createView();
- resolveTarget = textureView;
- } else {
- view = textureView;
- resolveTarget = undefined;
- }
- textureViews.push( {
- view,
- resolveTarget,
- depthSlice: sliceIndex
- } );
- }
- }
- descriptorBase = { textureViews };
- if ( renderContext.depth ) {
- const depthTextureData = this.get( renderContext.depthTexture );
- const options = {};
- if ( renderContext.depthTexture.isArrayTexture || renderContext.depthTexture.isCubeTexture ) {
- options.dimension = GPUTextureViewDimension.TwoD;
- options.arrayLayerCount = 1;
- options.baseArrayLayer = renderContext.activeCubeFace;
- }
- descriptorBase.depthStencilView = depthTextureData.texture.createView( options );
- }
- descriptors[ cacheKey ] = descriptorBase;
- renderTargetData.width = renderTarget.width;
- renderTargetData.height = renderTarget.height;
- renderTargetData.samples = renderTarget.samples;
- renderTargetData.activeMipmapLevel = renderContext.activeMipmapLevel;
- renderTargetData.activeCubeFace = renderContext.activeCubeFace;
- }
- const descriptor = {
- colorAttachments: []
- };
- // Apply dynamic properties to cached views
- for ( let i = 0; i < descriptorBase.textureViews.length; i ++ ) {
- const viewInfo = descriptorBase.textureViews[ i ];
- let clearValue = { r: 0, g: 0, b: 0, a: 1 };
- if ( i === 0 && colorAttachmentsConfig.clearValue ) {
- clearValue = colorAttachmentsConfig.clearValue;
- }
- descriptor.colorAttachments.push( {
- view: viewInfo.view,
- depthSlice: viewInfo.depthSlice,
- resolveTarget: viewInfo.resolveTarget,
- loadOp: colorAttachmentsConfig.loadOp || GPULoadOp.Load,
- storeOp: colorAttachmentsConfig.storeOp || GPUStoreOp.Store,
- clearValue: clearValue
- } );
- }
- if ( descriptorBase.depthStencilView ) {
- descriptor.depthStencilAttachment = {
- view: descriptorBase.depthStencilView
- };
- }
- return descriptor;
- }
- /**
- * This method is executed at the beginning of a render call and prepares
- * the WebGPU state for upcoming render calls
- *
- * @param {RenderContext} renderContext - The render context.
- */
- beginRender( renderContext ) {
- const renderContextData = this.get( renderContext );
- //
- const device = this.device;
- const occlusionQueryCount = renderContext.occlusionQueryCount;
- let occlusionQuerySet;
- if ( occlusionQueryCount > 0 ) {
- if ( renderContextData.currentOcclusionQuerySet ) renderContextData.currentOcclusionQuerySet.destroy();
- if ( renderContextData.currentOcclusionQueryBuffer ) renderContextData.currentOcclusionQueryBuffer.destroy();
- // Get a reference to the array of objects with queries. The renderContextData property
- // can be changed by another render pass before the buffer.mapAsyc() completes.
- renderContextData.currentOcclusionQuerySet = renderContextData.occlusionQuerySet;
- renderContextData.currentOcclusionQueryBuffer = renderContextData.occlusionQueryBuffer;
- renderContextData.currentOcclusionQueryObjects = renderContextData.occlusionQueryObjects;
- //
- occlusionQuerySet = device.createQuerySet( { type: 'occlusion', count: occlusionQueryCount, label: `occlusionQuerySet_${ renderContext.id }` } );
- renderContextData.occlusionQuerySet = occlusionQuerySet;
- renderContextData.occlusionQueryIndex = 0;
- renderContextData.occlusionQueryObjects = new Array( occlusionQueryCount );
- renderContextData.lastOcclusionObject = null;
- }
- let descriptor;
- if ( renderContext.textures === null ) {
- descriptor = this._getDefaultRenderPassDescriptor();
- } else {
- descriptor = this._getRenderPassDescriptor( renderContext, { loadOp: GPULoadOp.Load } );
- }
- this.initTimestampQuery( TimestampQuery.RENDER, this.getTimestampUID( renderContext ), descriptor );
- descriptor.occlusionQuerySet = occlusionQuerySet;
- const depthStencilAttachment = descriptor.depthStencilAttachment;
- if ( renderContext.textures !== null ) {
- const colorAttachments = descriptor.colorAttachments;
- for ( let i = 0; i < colorAttachments.length; i ++ ) {
- const colorAttachment = colorAttachments[ i ];
- if ( renderContext.clearColor ) {
- colorAttachment.clearValue = i === 0 ? renderContext.clearColorValue : { r: 0, g: 0, b: 0, a: 1 };
- colorAttachment.loadOp = GPULoadOp.Clear;
- } else {
- colorAttachment.loadOp = GPULoadOp.Load;
- }
- colorAttachment.storeOp = GPUStoreOp.Store;
- }
- } else {
- const colorAttachment = descriptor.colorAttachments[ 0 ];
- if ( renderContext.clearColor ) {
- colorAttachment.clearValue = renderContext.clearColorValue;
- colorAttachment.loadOp = GPULoadOp.Clear;
- } else {
- colorAttachment.loadOp = GPULoadOp.Load;
- }
- colorAttachment.storeOp = GPUStoreOp.Store;
- }
- //
- if ( renderContext.depth ) {
- if ( renderContext.clearDepth ) {
- depthStencilAttachment.depthClearValue = renderContext.clearDepthValue;
- depthStencilAttachment.depthLoadOp = GPULoadOp.Clear;
- } else {
- depthStencilAttachment.depthLoadOp = GPULoadOp.Load;
- }
- depthStencilAttachment.depthStoreOp = GPUStoreOp.Store;
- }
- if ( renderContext.stencil ) {
- if ( renderContext.clearStencil ) {
- depthStencilAttachment.stencilClearValue = renderContext.clearStencilValue;
- depthStencilAttachment.stencilLoadOp = GPULoadOp.Clear;
- } else {
- depthStencilAttachment.stencilLoadOp = GPULoadOp.Load;
- }
- depthStencilAttachment.stencilStoreOp = GPUStoreOp.Store;
- }
- //
- const encoder = device.createCommandEncoder( { label: 'renderContext_' + renderContext.id } );
- // shadow arrays - prepare bundle encoders for each camera in an array camera
- if ( this._isRenderCameraDepthArray( renderContext ) === true ) {
- const cameras = renderContext.camera.cameras;
- if ( ! renderContextData.layerDescriptors || renderContextData.layerDescriptors.length !== cameras.length ) {
- this._createDepthLayerDescriptors( renderContext, renderContextData, descriptor, cameras );
- } else {
- this._updateDepthLayerDescriptors( renderContext, renderContextData, cameras );
- }
- // Create bundle encoders for each layer
- renderContextData.bundleEncoders = [];
- renderContextData.bundleSets = [];
- // Create separate bundle encoders for each camera in the array
- for ( let i = 0; i < cameras.length; i ++ ) {
- const bundleEncoder = this.pipelineUtils.createBundleEncoder(
- renderContext,
- 'renderBundleArrayCamera_' + i
- );
- // Initialize state tracking for this bundle
- const bundleSets = {
- attributes: {},
- bindingGroups: [],
- pipeline: null,
- index: null
- };
- renderContextData.bundleEncoders.push( bundleEncoder );
- renderContextData.bundleSets.push( bundleSets );
- }
- // We'll complete the bundles in finishRender
- renderContextData.currentPass = null;
- } else {
- const currentPass = encoder.beginRenderPass( descriptor );
- renderContextData.currentPass = currentPass;
- if ( renderContext.viewport ) {
- this.updateViewport( renderContext );
- }
- if ( renderContext.scissor ) {
- this.updateScissor( renderContext );
- }
- }
- //
- renderContextData.descriptor = descriptor;
- renderContextData.encoder = encoder;
- renderContextData.currentSets = { attributes: {}, bindingGroups: [], pipeline: null, index: null };
- renderContextData.renderBundles = [];
- }
- /**
- * This method creates layer descriptors for each camera in an array camera
- * to prepare for rendering to a depth array texture.
- *
- * @param {RenderContext} renderContext - The render context.
- * @param {Object} renderContextData - The render context data.
- * @param {Object} descriptor - The render pass descriptor.
- * @param {ArrayCamera} cameras - The array camera.
- *
- * @private
- */
- _createDepthLayerDescriptors( renderContext, renderContextData, descriptor, cameras ) {
- const depthStencilAttachment = descriptor.depthStencilAttachment;
- renderContextData.layerDescriptors = [];
- const depthTextureData = this.get( renderContext.depthTexture );
- if ( ! depthTextureData.viewCache ) {
- depthTextureData.viewCache = [];
- }
- for ( let i = 0; i < cameras.length; i ++ ) {
- const layerDescriptor = {
- ...descriptor,
- colorAttachments: [ {
- ...descriptor.colorAttachments[ 0 ],
- view: descriptor.colorAttachments[ i ].view
- } ]
- };
- if ( descriptor.depthStencilAttachment ) {
- const layerIndex = i;
- if ( ! depthTextureData.viewCache[ layerIndex ] ) {
- depthTextureData.viewCache[ layerIndex ] = depthTextureData.texture.createView( {
- dimension: GPUTextureViewDimension.TwoD,
- baseArrayLayer: i,
- arrayLayerCount: 1
- } );
- }
- layerDescriptor.depthStencilAttachment = {
- view: depthTextureData.viewCache[ layerIndex ],
- depthLoadOp: depthStencilAttachment.depthLoadOp || GPULoadOp.Clear,
- depthStoreOp: depthStencilAttachment.depthStoreOp || GPUStoreOp.Store,
- depthClearValue: depthStencilAttachment.depthClearValue || 1.0
- };
- if ( renderContext.stencil ) {
- layerDescriptor.depthStencilAttachment.stencilLoadOp = depthStencilAttachment.stencilLoadOp;
- layerDescriptor.depthStencilAttachment.stencilStoreOp = depthStencilAttachment.stencilStoreOp;
- layerDescriptor.depthStencilAttachment.stencilClearValue = depthStencilAttachment.stencilClearValue;
- }
- } else {
- layerDescriptor.depthStencilAttachment = { ...depthStencilAttachment };
- }
- renderContextData.layerDescriptors.push( layerDescriptor );
- }
- }
- /**
- * This method updates the layer descriptors for each camera in an array camera
- * to prepare for rendering to a depth array texture.
- *
- * @param {RenderContext} renderContext - The render context.
- * @param {Object} renderContextData - The render context data.
- * @param {ArrayCamera} cameras - The array camera.
- *
- */
- _updateDepthLayerDescriptors( renderContext, renderContextData, cameras ) {
- for ( let i = 0; i < cameras.length; i ++ ) {
- const layerDescriptor = renderContextData.layerDescriptors[ i ];
- if ( layerDescriptor.depthStencilAttachment ) {
- const depthAttachment = layerDescriptor.depthStencilAttachment;
- if ( renderContext.depth ) {
- if ( renderContext.clearDepth ) {
- depthAttachment.depthClearValue = renderContext.clearDepthValue;
- depthAttachment.depthLoadOp = GPULoadOp.Clear;
- } else {
- depthAttachment.depthLoadOp = GPULoadOp.Load;
- }
- }
- if ( renderContext.stencil ) {
- if ( renderContext.clearStencil ) {
- depthAttachment.stencilClearValue = renderContext.clearStencilValue;
- depthAttachment.stencilLoadOp = GPULoadOp.Clear;
- } else {
- depthAttachment.stencilLoadOp = GPULoadOp.Load;
- }
- }
- }
- }
- }
- /**
- * This method is executed at the end of a render call and finalizes work
- * after draw calls.
- *
- * @param {RenderContext} renderContext - The render context.
- */
- finishRender( renderContext ) {
- const renderContextData = this.get( renderContext );
- const occlusionQueryCount = renderContext.occlusionQueryCount;
- if ( renderContextData.renderBundles.length > 0 ) {
- renderContextData.currentPass.executeBundles( renderContextData.renderBundles );
- }
- if ( occlusionQueryCount > renderContextData.occlusionQueryIndex ) {
- renderContextData.currentPass.endOcclusionQuery();
- }
- // shadow arrays - Execute bundles for each layer
- const encoder = renderContextData.encoder;
- if ( this._isRenderCameraDepthArray( renderContext ) === true ) {
- const bundles = [];
- for ( let i = 0; i < renderContextData.bundleEncoders.length; i ++ ) {
- const bundleEncoder = renderContextData.bundleEncoders[ i ];
- bundles.push( bundleEncoder.finish() );
- }
- for ( let i = 0; i < renderContextData.layerDescriptors.length; i ++ ) {
- if ( i < bundles.length ) {
- const layerDescriptor = renderContextData.layerDescriptors[ i ];
- const renderPass = encoder.beginRenderPass( layerDescriptor );
- if ( renderContext.viewport ) {
- const { x, y, width, height, minDepth, maxDepth } = renderContext.viewportValue;
- renderPass.setViewport( x, y, width, height, minDepth, maxDepth );
- }
- if ( renderContext.scissor ) {
- const { x, y, width, height } = renderContext.scissorValue;
- renderPass.setScissorRect( x, y, width, height );
- }
- renderPass.executeBundles( [ bundles[ i ] ] );
- renderPass.end();
- }
- }
- } else if ( renderContextData.currentPass ) {
- renderContextData.currentPass.end();
- }
- if ( occlusionQueryCount > 0 ) {
- const bufferSize = occlusionQueryCount * 8; // 8 byte entries for query results
- //
- let queryResolveBuffer = this.occludedResolveCache.get( bufferSize );
- if ( queryResolveBuffer === undefined ) {
- queryResolveBuffer = this.device.createBuffer(
- {
- size: bufferSize,
- usage: GPUBufferUsage.QUERY_RESOLVE | GPUBufferUsage.COPY_SRC
- }
- );
- this.occludedResolveCache.set( bufferSize, queryResolveBuffer );
- }
- //
- const readBuffer = this.device.createBuffer(
- {
- size: bufferSize,
- usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ
- }
- );
- // two buffers required here - WebGPU doesn't allow usage of QUERY_RESOLVE & MAP_READ to be combined
- renderContextData.encoder.resolveQuerySet( renderContextData.occlusionQuerySet, 0, occlusionQueryCount, queryResolveBuffer, 0 );
- renderContextData.encoder.copyBufferToBuffer( queryResolveBuffer, 0, readBuffer, 0, bufferSize );
- renderContextData.occlusionQueryBuffer = readBuffer;
- //
- this.resolveOccludedAsync( renderContext );
- }
- this.device.queue.submit( [ renderContextData.encoder.finish() ] );
- //
- if ( renderContext.textures !== null ) {
- const textures = renderContext.textures;
- for ( let i = 0; i < textures.length; i ++ ) {
- const texture = textures[ i ];
- if ( texture.generateMipmaps === true ) {
- this.textureUtils.generateMipmaps( texture );
- }
- }
- }
- }
- /**
- * Returns `true` if the given 3D object is fully occluded by other
- * 3D objects in the scene.
- *
- * @param {RenderContext} renderContext - The render context.
- * @param {Object3D} object - The 3D object to test.
- * @return {boolean} Whether the 3D object is fully occluded or not.
- */
- isOccluded( renderContext, object ) {
- const renderContextData = this.get( renderContext );
- return renderContextData.occluded && renderContextData.occluded.has( object );
- }
- /**
- * This method processes the result of occlusion queries and writes it
- * into render context data.
- *
- * @async
- * @param {RenderContext} renderContext - The render context.
- * @return {Promise} A Promise that resolves when the occlusion query results have been processed.
- */
- async resolveOccludedAsync( renderContext ) {
- const renderContextData = this.get( renderContext );
- // handle occlusion query results
- const { currentOcclusionQueryBuffer, currentOcclusionQueryObjects } = renderContextData;
- if ( currentOcclusionQueryBuffer && currentOcclusionQueryObjects ) {
- const occluded = new WeakSet();
- renderContextData.currentOcclusionQueryObjects = null;
- renderContextData.currentOcclusionQueryBuffer = null;
- await currentOcclusionQueryBuffer.mapAsync( GPUMapMode.READ );
- const buffer = currentOcclusionQueryBuffer.getMappedRange();
- const results = new BigUint64Array( buffer );
- for ( let i = 0; i < currentOcclusionQueryObjects.length; i ++ ) {
- if ( results[ i ] === BigInt( 0 ) ) {
- occluded.add( currentOcclusionQueryObjects[ i ] );
- }
- }
- currentOcclusionQueryBuffer.destroy();
- renderContextData.occluded = occluded;
- }
- }
- /**
- * Updates the viewport with the values from the given render context.
- *
- * @param {RenderContext} renderContext - The render context.
- */
- updateViewport( renderContext ) {
- const { currentPass } = this.get( renderContext );
- const { x, y, width, height, minDepth, maxDepth } = renderContext.viewportValue;
- currentPass.setViewport( x, y, width, height, minDepth, maxDepth );
- }
- /**
- * Updates the scissor with the values from the given render context.
- *
- * @param {RenderContext} renderContext - The render context.
- */
- updateScissor( renderContext ) {
- const { currentPass } = this.get( renderContext );
- const { x, y, width, height } = renderContext.scissorValue;
- currentPass.setScissorRect( x, y, width, height );
- }
- /**
- * Returns the clear color and alpha into a single
- * color object.
- *
- * @return {Color4} The clear color.
- */
- getClearColor() {
- const clearColor = super.getClearColor();
- // only premultiply alpha when alphaMode is "premultiplied"
- if ( this.renderer.alpha === true ) {
- clearColor.r *= clearColor.a;
- clearColor.g *= clearColor.a;
- clearColor.b *= clearColor.a;
- }
- return clearColor;
- }
- /**
- * Performs a clear operation.
- *
- * @param {boolean} color - Whether the color buffer should be cleared or not.
- * @param {boolean} depth - Whether the depth buffer should be cleared or not.
- * @param {boolean} stencil - Whether the stencil buffer should be cleared or not.
- * @param {?RenderContext} [renderTargetContext=null] - The render context of the current set render target.
- */
- clear( color, depth, stencil, renderTargetContext = null ) {
- const device = this.device;
- const renderer = this.renderer;
- let colorAttachments = [];
- let depthStencilAttachment;
- let clearValue;
- let supportsDepth;
- let supportsStencil;
- if ( color ) {
- const clearColor = this.getClearColor();
- clearValue = { r: clearColor.r, g: clearColor.g, b: clearColor.b, a: clearColor.a };
- }
- if ( renderTargetContext === null ) {
- supportsDepth = renderer.depth;
- supportsStencil = renderer.stencil;
- const descriptor = this._getDefaultRenderPassDescriptor();
- if ( color ) {
- colorAttachments = descriptor.colorAttachments;
- const colorAttachment = colorAttachments[ 0 ];
- colorAttachment.clearValue = clearValue;
- colorAttachment.loadOp = GPULoadOp.Clear;
- colorAttachment.storeOp = GPUStoreOp.Store;
- }
- if ( supportsDepth || supportsStencil ) {
- depthStencilAttachment = descriptor.depthStencilAttachment;
- }
- } else {
- supportsDepth = renderTargetContext.depth;
- supportsStencil = renderTargetContext.stencil;
- const clearConfig = {
- loadOp: color ? GPULoadOp.Clear : GPULoadOp.Load,
- clearValue: color ? clearValue : undefined
- };
- if ( supportsDepth ) {
- clearConfig.depthLoadOp = depth ? GPULoadOp.Clear : GPULoadOp.Load;
- clearConfig.depthClearValue = depth ? renderer.getClearDepth() : undefined;
- clearConfig.depthStoreOp = GPUStoreOp.Store;
- }
- if ( supportsStencil ) {
- clearConfig.stencilLoadOp = stencil ? GPULoadOp.Clear : GPULoadOp.Load;
- clearConfig.stencilClearValue = stencil ? renderer.getClearStencil() : undefined;
- clearConfig.stencilStoreOp = GPUStoreOp.Store;
- }
- const descriptor = this._getRenderPassDescriptor( renderTargetContext, clearConfig );
- colorAttachments = descriptor.colorAttachments;
- depthStencilAttachment = descriptor.depthStencilAttachment;
- }
- if ( supportsDepth && depthStencilAttachment ) {
- if ( depth ) {
- depthStencilAttachment.depthLoadOp = GPULoadOp.Clear;
- depthStencilAttachment.depthClearValue = renderer.getClearDepth();
- depthStencilAttachment.depthStoreOp = GPUStoreOp.Store;
- } else {
- depthStencilAttachment.depthLoadOp = GPULoadOp.Load;
- depthStencilAttachment.depthStoreOp = GPUStoreOp.Store;
- }
- }
- //
- if ( supportsStencil && depthStencilAttachment ) {
- if ( stencil ) {
- depthStencilAttachment.stencilLoadOp = GPULoadOp.Clear;
- depthStencilAttachment.stencilClearValue = renderer.getClearStencil();
- depthStencilAttachment.stencilStoreOp = GPUStoreOp.Store;
- } else {
- depthStencilAttachment.stencilLoadOp = GPULoadOp.Load;
- depthStencilAttachment.stencilStoreOp = GPUStoreOp.Store;
- }
- }
- //
- const encoder = device.createCommandEncoder( { label: 'clear' } );
- const currentPass = encoder.beginRenderPass( {
- colorAttachments,
- depthStencilAttachment
- } );
- currentPass.end();
- device.queue.submit( [ encoder.finish() ] );
- }
- // compute
- /**
- * This method is executed at the beginning of a compute call and
- * prepares the state for upcoming compute tasks.
- *
- * @param {Node|Array<Node>} computeGroup - The compute node(s).
- */
- beginCompute( computeGroup ) {
- const groupGPU = this.get( computeGroup );
- //
- const descriptor = {
- label: 'computeGroup_' + computeGroup.id
- };
- this.initTimestampQuery( TimestampQuery.COMPUTE, this.getTimestampUID( computeGroup ), descriptor );
- groupGPU.cmdEncoderGPU = this.device.createCommandEncoder( { label: 'computeGroup_' + computeGroup.id } );
- groupGPU.passEncoderGPU = groupGPU.cmdEncoderGPU.beginComputePass( descriptor );
- }
- /**
- * Executes a compute command for the given compute node.
- *
- * @param {Node|Array<Node>} computeGroup - The group of compute nodes of a compute call. Can be a single compute node.
- * @param {Node} computeNode - The compute node.
- * @param {Array<BindGroup>} bindings - The bindings.
- * @param {ComputePipeline} pipeline - The compute pipeline.
- * @param {number|Array<number>|IndirectStorageBufferAttribute} [dispatchSize=null]
- * - A single number representing count, or
- * - An array [x, y, z] representing dispatch size, or
- * - A IndirectStorageBufferAttribute for indirect dispatch size.
- */
- compute( computeGroup, computeNode, bindings, pipeline, dispatchSize = null ) {
- const computeNodeData = this.get( computeNode );
- const { passEncoderGPU } = this.get( computeGroup );
- // pipeline
- const pipelineGPU = this.get( pipeline ).pipeline;
- this.pipelineUtils.setPipeline( passEncoderGPU, pipelineGPU );
- // bind groups
- for ( let i = 0, l = bindings.length; i < l; i ++ ) {
- const bindGroup = bindings[ i ];
- const bindingsData = this.get( bindGroup );
- passEncoderGPU.setBindGroup( i, bindingsData.group );
- }
- if ( dispatchSize === null ) {
- dispatchSize = computeNode.count;
- }
- // When the dispatchSize is set with a StorageBuffer from the GPU.
- if ( dispatchSize && typeof dispatchSize === 'object' && dispatchSize.isIndirectStorageBufferAttribute ) {
- const dispatchBuffer = this.get( dispatchSize ).buffer;
- passEncoderGPU.dispatchWorkgroupsIndirect( dispatchBuffer, 0 );
- return;
- }
- if ( typeof dispatchSize === 'number' ) {
- // If a single number is given, we calculate the dispatch size based on the workgroup size
- const count = dispatchSize;
- if ( computeNodeData.dispatchSize === undefined || computeNodeData.count !== count ) {
- // cache dispatch size to avoid recalculating it every time
- computeNodeData.dispatchSize = [ 0, 1, 1 ];
- computeNodeData.count = count;
- const workgroupSize = computeNode.workgroupSize;
- let size = workgroupSize[ 0 ];
- for ( let i = 1; i < workgroupSize.length; i ++ )
- size *= workgroupSize[ i ];
- const dispatchCount = Math.ceil( count / size );
- //
- const maxComputeWorkgroupsPerDimension = this.device.limits.maxComputeWorkgroupsPerDimension;
- dispatchSize = [ dispatchCount, 1, 1 ];
- if ( dispatchCount > maxComputeWorkgroupsPerDimension ) {
- dispatchSize[ 0 ] = Math.min( dispatchCount, maxComputeWorkgroupsPerDimension );
- dispatchSize[ 1 ] = Math.ceil( dispatchCount / maxComputeWorkgroupsPerDimension );
- }
- computeNodeData.dispatchSize = dispatchSize;
- }
- dispatchSize = computeNodeData.dispatchSize;
- }
- //
- passEncoderGPU.dispatchWorkgroups(
- dispatchSize[ 0 ],
- dispatchSize[ 1 ] || 1,
- dispatchSize[ 2 ] || 1
- );
- }
- /**
- * This method is executed at the end of a compute call and
- * finalizes work after compute tasks.
- *
- * @param {Node|Array<Node>} computeGroup - The compute node(s).
- */
- finishCompute( computeGroup ) {
- const groupData = this.get( computeGroup );
- groupData.passEncoderGPU.end();
- this.device.queue.submit( [ groupData.cmdEncoderGPU.finish() ] );
- }
- // render object
- /**
- * Executes a draw command for the given render object.
- *
- * @param {RenderObject} renderObject - The render object to draw.
- * @param {Info} info - Holds a series of statistical information about the GPU memory and the rendering process.
- */
- draw( renderObject, info ) {
- const { object, material, context, pipeline } = renderObject;
- const bindings = renderObject.getBindings();
- const renderContextData = this.get( context );
- const pipelineData = this.get( pipeline );
- const pipelineGPU = pipelineData.pipeline;
- if ( pipelineData.error === true ) return;
- const index = renderObject.getIndex();
- const hasIndex = ( index !== null );
- const drawParams = renderObject.getDrawParameters();
- if ( drawParams === null ) return;
- // pipeline
- const setPipelineAndBindings = ( passEncoderGPU, currentSets ) => {
- // pipeline
- this.pipelineUtils.setPipeline( passEncoderGPU, pipelineGPU );
- currentSets.pipeline = pipelineGPU;
- // bind groups
- const currentBindingGroups = currentSets.bindingGroups;
- for ( let i = 0, l = bindings.length; i < l; i ++ ) {
- const bindGroup = bindings[ i ];
- const bindingsData = this.get( bindGroup );
- if ( currentBindingGroups[ bindGroup.index ] !== bindGroup.id ) {
- passEncoderGPU.setBindGroup( bindGroup.index, bindingsData.group );
- currentBindingGroups[ bindGroup.index ] = bindGroup.id;
- }
- }
- // attributes
- // index
- if ( hasIndex === true ) {
- if ( currentSets.index !== index ) {
- const buffer = this.get( index ).buffer;
- const indexFormat = ( index.array instanceof Uint16Array ) ? GPUIndexFormat.Uint16 : GPUIndexFormat.Uint32;
- passEncoderGPU.setIndexBuffer( buffer, indexFormat );
- currentSets.index = index;
- }
- }
- // vertex buffers
- const vertexBuffers = renderObject.getVertexBuffers();
- for ( let i = 0, l = vertexBuffers.length; i < l; i ++ ) {
- const vertexBuffer = vertexBuffers[ i ];
- if ( currentSets.attributes[ i ] !== vertexBuffer ) {
- const buffer = this.get( vertexBuffer ).buffer;
- passEncoderGPU.setVertexBuffer( i, buffer );
- currentSets.attributes[ i ] = vertexBuffer;
- }
- }
- // stencil
- if ( context.stencil === true && material.stencilWrite === true && renderContextData.currentStencilRef !== material.stencilRef ) {
- passEncoderGPU.setStencilReference( material.stencilRef );
- renderContextData.currentStencilRef = material.stencilRef;
- }
- };
- // Define draw function
- const draw = ( passEncoderGPU, currentSets ) => {
- setPipelineAndBindings( passEncoderGPU, currentSets );
- if ( object.isBatchedMesh === true ) {
- const starts = object._multiDrawStarts;
- const counts = object._multiDrawCounts;
- const drawCount = object._multiDrawCount;
- const drawInstances = object._multiDrawInstances;
- if ( drawInstances !== null ) {
- // @deprecated, r174
- warnOnce( 'WebGPUBackend: renderMultiDrawInstances has been deprecated and will be removed in r184. Append to renderMultiDraw arguments and use indirection.' );
- }
- for ( let i = 0; i < drawCount; i ++ ) {
- const count = drawInstances ? drawInstances[ i ] : 1;
- const firstInstance = count > 1 ? 0 : i;
- if ( hasIndex === true ) {
- passEncoderGPU.drawIndexed( counts[ i ], count, starts[ i ] / index.array.BYTES_PER_ELEMENT, 0, firstInstance );
- } else {
- passEncoderGPU.draw( counts[ i ], count, starts[ i ], firstInstance );
- }
- info.update( object, counts[ i ], count );
- }
- } else if ( hasIndex === true ) {
- const { vertexCount: indexCount, instanceCount, firstVertex: firstIndex } = drawParams;
- const indirect = renderObject.getIndirect();
- if ( indirect !== null ) {
- const buffer = this.get( indirect ).buffer;
- const indirectOffset = renderObject.getIndirectOffset();
- const indirectOffsets = Array.isArray( indirectOffset ) ? indirectOffset : [ indirectOffset ];
- for ( let i = 0; i < indirectOffsets.length; i ++ ) {
- passEncoderGPU.drawIndexedIndirect( buffer, indirectOffsets[ i ] );
- }
- } else {
- passEncoderGPU.drawIndexed( indexCount, instanceCount, firstIndex, 0, 0 );
- }
- info.update( object, indexCount, instanceCount );
- } else {
- const { vertexCount, instanceCount, firstVertex } = drawParams;
- const indirect = renderObject.getIndirect();
- if ( indirect !== null ) {
- const buffer = this.get( indirect ).buffer;
- const indirectOffset = renderObject.getIndirectOffset();
- const indirectOffsets = Array.isArray( indirectOffset ) ? indirectOffset : [ indirectOffset ];
- for ( let i = 0; i < indirectOffsets.length; i ++ ) {
- passEncoderGPU.drawIndirect( buffer, indirectOffsets[ i ] );
- }
- } else {
- passEncoderGPU.draw( vertexCount, instanceCount, firstVertex, 0 );
- }
- info.update( object, vertexCount, instanceCount );
- }
- };
- if ( renderObject.camera.isArrayCamera && renderObject.camera.cameras.length > 0 ) {
- const cameraData = this.get( renderObject.camera );
- const cameras = renderObject.camera.cameras;
- const cameraIndex = renderObject.getBindingGroup( 'cameraIndex' );
- if ( cameraData.indexesGPU === undefined || cameraData.indexesGPU.length !== cameras.length ) {
- const bindingsData = this.get( cameraIndex );
- const indexesGPU = [];
- const data = new Uint32Array( [ 0, 0, 0, 0 ] );
- for ( let i = 0, len = cameras.length; i < len; i ++ ) {
- data[ 0 ] = i;
- const { layoutGPU } = bindingsData.layout;
- const bindGroupIndex = this.bindingUtils.createBindGroupIndex( data, layoutGPU );
- indexesGPU.push( bindGroupIndex );
- }
- cameraData.indexesGPU = indexesGPU; // TODO: Create a global library for this
- }
- const pixelRatio = this.renderer.getPixelRatio();
- for ( let i = 0, len = cameras.length; i < len; i ++ ) {
- const subCamera = cameras[ i ];
- if ( object.layers.test( subCamera.layers ) ) {
- const vp = subCamera.viewport;
- let pass = renderContextData.currentPass;
- let sets = renderContextData.currentSets;
- if ( renderContextData.bundleEncoders ) {
- const bundleEncoder = renderContextData.bundleEncoders[ i ];
- const bundleSets = renderContextData.bundleSets[ i ];
- pass = bundleEncoder;
- sets = bundleSets;
- }
- if ( vp ) {
- pass.setViewport(
- Math.floor( vp.x * pixelRatio ),
- Math.floor( vp.y * pixelRatio ),
- Math.floor( vp.width * pixelRatio ),
- Math.floor( vp.height * pixelRatio ),
- context.viewportValue.minDepth,
- context.viewportValue.maxDepth
- );
- }
- // Set camera index binding for this layer
- if ( cameraIndex && cameraData.indexesGPU ) {
- pass.setBindGroup( cameraIndex.index, cameraData.indexesGPU[ i ] );
- sets.bindingGroups[ cameraIndex.index ] = cameraIndex.id;
- }
- draw( pass, sets );
- }
- }
- } else {
- // Regular single camera rendering
- if ( renderContextData.currentPass ) {
- // Handle occlusion queries
- if ( renderContextData.occlusionQuerySet !== undefined ) {
- const lastObject = renderContextData.lastOcclusionObject;
- if ( lastObject !== object ) {
- if ( lastObject !== null && lastObject.occlusionTest === true ) {
- renderContextData.currentPass.endOcclusionQuery();
- renderContextData.occlusionQueryIndex ++;
- }
- if ( object.occlusionTest === true ) {
- renderContextData.currentPass.beginOcclusionQuery( renderContextData.occlusionQueryIndex );
- renderContextData.occlusionQueryObjects[ renderContextData.occlusionQueryIndex ] = object;
- }
- renderContextData.lastOcclusionObject = object;
- }
- }
- draw( renderContextData.currentPass, renderContextData.currentSets );
- }
- }
- }
- // cache key
- /**
- * Returns `true` if the render pipeline requires an update.
- *
- * @param {RenderObject} renderObject - The render object.
- * @return {boolean} Whether the render pipeline requires an update or not.
- */
- needsRenderUpdate( renderObject ) {
- const data = this.get( renderObject );
- const { object, material } = renderObject;
- const utils = this.utils;
- const sampleCount = utils.getSampleCountRenderContext( renderObject.context );
- const colorSpace = utils.getCurrentColorSpace( renderObject.context );
- const colorFormat = utils.getCurrentColorFormat( renderObject.context );
- const depthStencilFormat = utils.getCurrentDepthStencilFormat( renderObject.context );
- const primitiveTopology = utils.getPrimitiveTopology( object, material );
- let needsUpdate = false;
- if ( data.material !== material || data.materialVersion !== material.version ||
- data.transparent !== material.transparent || data.blending !== material.blending || data.premultipliedAlpha !== material.premultipliedAlpha ||
- data.blendSrc !== material.blendSrc || data.blendDst !== material.blendDst || data.blendEquation !== material.blendEquation ||
- data.blendSrcAlpha !== material.blendSrcAlpha || data.blendDstAlpha !== material.blendDstAlpha || data.blendEquationAlpha !== material.blendEquationAlpha ||
- data.colorWrite !== material.colorWrite || data.depthWrite !== material.depthWrite || data.depthTest !== material.depthTest || data.depthFunc !== material.depthFunc ||
- data.stencilWrite !== material.stencilWrite || data.stencilFunc !== material.stencilFunc ||
- data.stencilFail !== material.stencilFail || data.stencilZFail !== material.stencilZFail || data.stencilZPass !== material.stencilZPass ||
- data.stencilFuncMask !== material.stencilFuncMask || data.stencilWriteMask !== material.stencilWriteMask ||
- data.side !== material.side || data.alphaToCoverage !== material.alphaToCoverage ||
- data.sampleCount !== sampleCount || data.colorSpace !== colorSpace ||
- data.colorFormat !== colorFormat || data.depthStencilFormat !== depthStencilFormat ||
- data.primitiveTopology !== primitiveTopology ||
- data.clippingContextCacheKey !== renderObject.clippingContextCacheKey
- ) {
- data.material = material; data.materialVersion = material.version;
- data.transparent = material.transparent; data.blending = material.blending; data.premultipliedAlpha = material.premultipliedAlpha;
- data.blendSrc = material.blendSrc; data.blendDst = material.blendDst; data.blendEquation = material.blendEquation;
- data.blendSrcAlpha = material.blendSrcAlpha; data.blendDstAlpha = material.blendDstAlpha; data.blendEquationAlpha = material.blendEquationAlpha;
- data.colorWrite = material.colorWrite;
- data.depthWrite = material.depthWrite; data.depthTest = material.depthTest; data.depthFunc = material.depthFunc;
- data.stencilWrite = material.stencilWrite; data.stencilFunc = material.stencilFunc;
- data.stencilFail = material.stencilFail; data.stencilZFail = material.stencilZFail; data.stencilZPass = material.stencilZPass;
- data.stencilFuncMask = material.stencilFuncMask; data.stencilWriteMask = material.stencilWriteMask;
- data.side = material.side; data.alphaToCoverage = material.alphaToCoverage;
- data.sampleCount = sampleCount;
- data.colorSpace = colorSpace;
- data.colorFormat = colorFormat;
- data.depthStencilFormat = depthStencilFormat;
- data.primitiveTopology = primitiveTopology;
- data.clippingContextCacheKey = renderObject.clippingContextCacheKey;
- needsUpdate = true;
- }
- return needsUpdate;
- }
- /**
- * Returns a cache key that is used to identify render pipelines.
- *
- * @param {RenderObject} renderObject - The render object.
- * @return {string} The cache key.
- */
- getRenderCacheKey( renderObject ) {
- const { object, material } = renderObject;
- const utils = this.utils;
- const renderContext = renderObject.context;
- // meshes with negative scale have a different frontFace render pipeline
- // descriptor value so the following must be honored in the cache key
- const frontFaceCW = ( object.isMesh && object.matrixWorld.determinant() < 0 );
- return [
- material.transparent, material.blending, material.premultipliedAlpha,
- material.blendSrc, material.blendDst, material.blendEquation,
- material.blendSrcAlpha, material.blendDstAlpha, material.blendEquationAlpha,
- material.colorWrite,
- material.depthWrite, material.depthTest, material.depthFunc,
- material.stencilWrite, material.stencilFunc,
- material.stencilFail, material.stencilZFail, material.stencilZPass,
- material.stencilFuncMask, material.stencilWriteMask,
- material.side,
- frontFaceCW,
- utils.getSampleCountRenderContext( renderContext ),
- utils.getCurrentColorSpace( renderContext ), utils.getCurrentColorFormat( renderContext ), utils.getCurrentDepthStencilFormat( renderContext ),
- utils.getPrimitiveTopology( object, material ),
- renderObject.getGeometryCacheKey(),
- renderObject.clippingContextCacheKey
- ].join();
- }
- // textures
- /**
- * Updates a GPU sampler for the given texture.
- *
- * @param {Texture} texture - The texture to update the sampler for.
- * @return {string} The current sampler key.
- */
- updateSampler( texture ) {
- return this.textureUtils.updateSampler( texture );
- }
- /**
- * Creates a default texture for the given texture that can be used
- * as a placeholder until the actual texture is ready for usage.
- *
- * @param {Texture} texture - The texture to create a default texture for.
- * @return {boolean} Whether the sampler has been updated or not.
- */
- createDefaultTexture( texture ) {
- return this.textureUtils.createDefaultTexture( texture );
- }
- /**
- * Defines a texture on the GPU for the given texture object.
- *
- * @param {Texture} texture - The texture.
- * @param {Object} [options={}] - Optional configuration parameter.
- */
- createTexture( texture, options ) {
- this.textureUtils.createTexture( texture, options );
- }
- /**
- * Uploads the updated texture data to the GPU.
- *
- * @param {Texture} texture - The texture.
- * @param {Object} [options={}] - Optional configuration parameter.
- */
- updateTexture( texture, options ) {
- this.textureUtils.updateTexture( texture, options );
- }
- /**
- * Generates mipmaps for the given texture.
- *
- * @param {Texture} texture - The texture.
- */
- generateMipmaps( texture ) {
- this.textureUtils.generateMipmaps( texture );
- }
- /**
- * Destroys the GPU data for the given texture object.
- *
- * @param {Texture} texture - The texture.
- * @param {boolean} [isDefaultTexture=false] - Whether the texture uses a default GPU texture or not.
- */
- destroyTexture( texture, isDefaultTexture = false ) {
- this.textureUtils.destroyTexture( texture, isDefaultTexture );
- }
- /**
- * Returns texture data as a typed array.
- *
- * @async
- * @param {Texture} texture - The texture to copy.
- * @param {number} x - The x coordinate of the copy origin.
- * @param {number} y - The y coordinate of the copy origin.
- * @param {number} width - The width of the copy.
- * @param {number} height - The height of the copy.
- * @param {number} faceIndex - The face index.
- * @return {Promise<TypedArray>} A Promise that resolves with a typed array when the copy operation has finished.
- */
- async copyTextureToBuffer( texture, x, y, width, height, faceIndex ) {
- return this.textureUtils.copyTextureToBuffer( texture, x, y, width, height, faceIndex );
- }
- /**
- * Inits a time stamp query for the given render context.
- *
- * @param {string} type - The type of the timestamp query (e.g. 'render', 'compute').
- * @param {number} uid - Unique id for the context (e.g. render context id).
- * @param {Object} descriptor - The query descriptor.
- */
- initTimestampQuery( type, uid, descriptor ) {
- if ( ! this.trackTimestamp ) return;
- if ( ! this.timestampQueryPool[ type ] ) {
- // TODO: Variable maxQueries?
- this.timestampQueryPool[ type ] = new WebGPUTimestampQueryPool( this.device, type, 2048 );
- }
- const timestampQueryPool = this.timestampQueryPool[ type ];
- const baseOffset = timestampQueryPool.allocateQueriesForContext( uid );
- descriptor.timestampWrites = {
- querySet: timestampQueryPool.querySet,
- beginningOfPassWriteIndex: baseOffset,
- endOfPassWriteIndex: baseOffset + 1,
- };
- }
- // node builder
- /**
- * Returns a node builder for the given render object.
- *
- * @param {RenderObject} object - The render object.
- * @param {Renderer} renderer - The renderer.
- * @return {WGSLNodeBuilder} The node builder.
- */
- createNodeBuilder( object, renderer ) {
- return new WGSLNodeBuilder( object, renderer );
- }
- // program
- /**
- * Creates a shader program from the given programmable stage.
- *
- * @param {ProgrammableStage} program - The programmable stage.
- */
- createProgram( program ) {
- const programGPU = this.get( program );
- programGPU.module = {
- module: this.device.createShaderModule( { code: program.code, label: program.stage + ( program.name !== '' ? `_${ program.name }` : '' ) } ),
- entryPoint: 'main'
- };
- }
- /**
- * Destroys the shader program of the given programmable stage.
- *
- * @param {ProgrammableStage} program - The programmable stage.
- */
- destroyProgram( program ) {
- this.delete( program );
- }
- // pipelines
- /**
- * Creates a render pipeline for the given render object.
- *
- * @param {RenderObject} renderObject - The render object.
- * @param {Array<Promise>} promises - An array of compilation promises which are used in `compileAsync()`.
- */
- createRenderPipeline( renderObject, promises ) {
- this.pipelineUtils.createRenderPipeline( renderObject, promises );
- }
- /**
- * Creates a compute pipeline for the given compute node.
- *
- * @param {ComputePipeline} computePipeline - The compute pipeline.
- * @param {Array<BindGroup>} bindings - The bindings.
- */
- createComputePipeline( computePipeline, bindings ) {
- this.pipelineUtils.createComputePipeline( computePipeline, bindings );
- }
- /**
- * Prepares the state for encoding render bundles.
- *
- * @param {RenderContext} renderContext - The render context.
- */
- beginBundle( renderContext ) {
- const renderContextData = this.get( renderContext );
- renderContextData._currentPass = renderContextData.currentPass;
- renderContextData._currentSets = renderContextData.currentSets;
- renderContextData.currentSets = { attributes: {}, bindingGroups: [], pipeline: null, index: null };
- renderContextData.currentPass = this.pipelineUtils.createBundleEncoder( renderContext );
- }
- /**
- * After processing render bundles this method finalizes related work.
- *
- * @param {RenderContext} renderContext - The render context.
- * @param {RenderBundle} bundle - The render bundle.
- */
- finishBundle( renderContext, bundle ) {
- const renderContextData = this.get( renderContext );
- const bundleEncoder = renderContextData.currentPass;
- const bundleGPU = bundleEncoder.finish();
- this.get( bundle ).bundleGPU = bundleGPU;
- // restore render pass state
- renderContextData.currentSets = renderContextData._currentSets;
- renderContextData.currentPass = renderContextData._currentPass;
- }
- /**
- * Adds a render bundle to the render context data.
- *
- * @param {RenderContext} renderContext - The render context.
- * @param {RenderBundle} bundle - The render bundle to add.
- */
- addBundle( renderContext, bundle ) {
- const renderContextData = this.get( renderContext );
- renderContextData.renderBundles.push( this.get( bundle ).bundleGPU );
- }
- // bindings
- /**
- * Creates bindings from the given bind group definition.
- *
- * @param {BindGroup} bindGroup - The bind group.
- * @param {Array<BindGroup>} bindings - Array of bind groups.
- * @param {number} cacheIndex - The cache index.
- * @param {number} version - The version.
- */
- createBindings( bindGroup, bindings, cacheIndex, version ) {
- this.bindingUtils.createBindings( bindGroup, bindings, cacheIndex, version );
- }
- /**
- * Updates the given bind group definition.
- *
- * @param {BindGroup} bindGroup - The bind group.
- * @param {Array<BindGroup>} bindings - Array of bind groups.
- * @param {number} cacheIndex - The cache index.
- * @param {number} version - The version.
- */
- updateBindings( bindGroup, bindings, cacheIndex, version ) {
- this.bindingUtils.createBindings( bindGroup, bindings, cacheIndex, version );
- }
- /**
- * Updates a buffer binding.
- *
- * @param {Buffer} binding - The buffer binding to update.
- */
- updateBinding( binding ) {
- this.bindingUtils.updateBinding( binding );
- }
- /**
- * Delete data associated with the current bind group.
- *
- * @param {BindGroup} bindGroup - The bind group.
- */
- deleteBindGroupData( bindGroup ) {
- this.bindingUtils.deleteBindGroupData( bindGroup );
- }
- // attributes
- /**
- * Creates the buffer of an indexed shader attribute.
- *
- * @param {BufferAttribute} attribute - The indexed buffer attribute.
- */
- createIndexAttribute( attribute ) {
- let usage = GPUBufferUsage.INDEX | GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST;
- if ( attribute.isStorageBufferAttribute || attribute.isStorageInstancedBufferAttribute ) {
- usage |= GPUBufferUsage.STORAGE;
- }
- this.attributeUtils.createAttribute( attribute, usage );
- }
- /**
- * Creates the GPU buffer of a shader attribute.
- *
- * @param {BufferAttribute} attribute - The buffer attribute.
- */
- createAttribute( attribute ) {
- this.attributeUtils.createAttribute( attribute, GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST );
- }
- /**
- * Creates the GPU buffer of a storage attribute.
- *
- * @param {BufferAttribute} attribute - The buffer attribute.
- */
- createStorageAttribute( attribute ) {
- this.attributeUtils.createAttribute( attribute, GPUBufferUsage.STORAGE | GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST );
- }
- /**
- * Creates the GPU buffer of an indirect storage attribute.
- *
- * @param {BufferAttribute} attribute - The buffer attribute.
- */
- createIndirectStorageAttribute( attribute ) {
- this.attributeUtils.createAttribute( attribute, GPUBufferUsage.STORAGE | GPUBufferUsage.INDIRECT | GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST );
- }
- /**
- * Updates the GPU buffer of a shader attribute.
- *
- * @param {BufferAttribute} attribute - The buffer attribute to update.
- */
- updateAttribute( attribute ) {
- this.attributeUtils.updateAttribute( attribute );
- }
- /**
- * Destroys the GPU buffer of a shader attribute.
- *
- * @param {BufferAttribute} attribute - The buffer attribute to destroy.
- */
- destroyAttribute( attribute ) {
- this.attributeUtils.destroyAttribute( attribute );
- }
- // canvas
- /**
- * Triggers an update of the default render pass descriptor.
- */
- updateSize() {
- this.delete( this.renderer.getCanvasTarget() );
- }
- // utils public
- /**
- * Returns the maximum anisotropy texture filtering value.
- *
- * @return {number} The maximum anisotropy texture filtering value.
- */
- getMaxAnisotropy() {
- return 16;
- }
- /**
- * Checks if the given feature is supported by the backend.
- *
- * @param {string} name - The feature's name.
- * @return {boolean} Whether the feature is supported or not.
- */
- hasFeature( name ) {
- if ( GPUFeatureMap[ name ] !== undefined ) name = GPUFeatureMap[ name ];
- return this.device.features.has( name );
- }
- /**
- * Copies data of the given source texture to the given destination texture.
- *
- * @param {Texture} srcTexture - The source texture.
- * @param {Texture} dstTexture - The destination texture.
- * @param {?(Box3|Box2)} [srcRegion=null] - The region of the source texture to copy.
- * @param {?(Vector2|Vector3)} [dstPosition=null] - The destination position of the copy.
- * @param {number} [srcLevel=0] - The mipmap level to copy.
- * @param {number} [dstLevel=0] - The destination mip level to copy to.
- */
- copyTextureToTexture( srcTexture, dstTexture, srcRegion = null, dstPosition = null, srcLevel = 0, dstLevel = 0 ) {
- let dstX = 0;
- let dstY = 0;
- let dstZ = 0;
- let srcX = 0;
- let srcY = 0;
- let srcZ = 0;
- let srcWidth = srcTexture.image.width;
- let srcHeight = srcTexture.image.height;
- let srcDepth = 1;
- if ( srcRegion !== null ) {
- if ( srcRegion.isBox3 === true ) {
- srcX = srcRegion.min.x;
- srcY = srcRegion.min.y;
- srcZ = srcRegion.min.z;
- srcWidth = srcRegion.max.x - srcRegion.min.x;
- srcHeight = srcRegion.max.y - srcRegion.min.y;
- srcDepth = srcRegion.max.z - srcRegion.min.z;
- } else {
- // Assume it's a Box2
- srcX = srcRegion.min.x;
- srcY = srcRegion.min.y;
- srcWidth = srcRegion.max.x - srcRegion.min.x;
- srcHeight = srcRegion.max.y - srcRegion.min.y;
- srcDepth = 1;
- }
- }
- if ( dstPosition !== null ) {
- dstX = dstPosition.x;
- dstY = dstPosition.y;
- dstZ = dstPosition.z || 0;
- }
- const encoder = this.device.createCommandEncoder( { label: 'copyTextureToTexture_' + srcTexture.id + '_' + dstTexture.id } );
- const sourceGPU = this.get( srcTexture ).texture;
- const destinationGPU = this.get( dstTexture ).texture;
- encoder.copyTextureToTexture(
- {
- texture: sourceGPU,
- mipLevel: srcLevel,
- origin: { x: srcX, y: srcY, z: srcZ }
- },
- {
- texture: destinationGPU,
- mipLevel: dstLevel,
- origin: { x: dstX, y: dstY, z: dstZ }
- },
- [
- srcWidth,
- srcHeight,
- srcDepth
- ]
- );
- this.device.queue.submit( [ encoder.finish() ] );
- if ( dstLevel === 0 && dstTexture.generateMipmaps ) {
- this.textureUtils.generateMipmaps( dstTexture );
- }
- }
- /**
- * Copies the current bound framebuffer to the given texture.
- *
- * @param {Texture} texture - The destination texture.
- * @param {RenderContext} renderContext - The render context.
- * @param {Vector4} rectangle - A four dimensional vector defining the origin and dimension of the copy.
- */
- copyFramebufferToTexture( texture, renderContext, rectangle ) {
- const renderContextData = this.get( renderContext );
- let sourceGPU = null;
- if ( renderContext.renderTarget ) {
- if ( texture.isDepthTexture ) {
- sourceGPU = this.get( renderContext.depthTexture ).texture;
- } else {
- sourceGPU = this.get( renderContext.textures[ 0 ] ).texture;
- }
- } else {
- if ( texture.isDepthTexture ) {
- sourceGPU = this.textureUtils.getDepthBuffer( renderContext.depth, renderContext.stencil );
- } else {
- sourceGPU = this.context.getCurrentTexture();
- }
- }
- const destinationGPU = this.get( texture ).texture;
- if ( sourceGPU.format !== destinationGPU.format ) {
- error( 'WebGPUBackend: copyFramebufferToTexture: Source and destination formats do not match.', sourceGPU.format, destinationGPU.format );
- return;
- }
- let encoder;
- if ( renderContextData.currentPass ) {
- renderContextData.currentPass.end();
- encoder = renderContextData.encoder;
- } else {
- encoder = this.device.createCommandEncoder( { label: 'copyFramebufferToTexture_' + texture.id } );
- }
- encoder.copyTextureToTexture(
- {
- texture: sourceGPU,
- origin: [ rectangle.x, rectangle.y, 0 ],
- },
- {
- texture: destinationGPU
- },
- [
- rectangle.z,
- rectangle.w
- ]
- );
- // mipmaps must be genereated with the same encoder otherwise the copied texture data
- // might be out-of-sync, see #31768
- if ( texture.generateMipmaps ) {
- this.textureUtils.generateMipmaps( texture, encoder );
- }
- if ( renderContextData.currentPass ) {
- const { descriptor } = renderContextData;
- for ( let i = 0; i < descriptor.colorAttachments.length; i ++ ) {
- descriptor.colorAttachments[ i ].loadOp = GPULoadOp.Load;
- }
- if ( renderContext.depth ) descriptor.depthStencilAttachment.depthLoadOp = GPULoadOp.Load;
- if ( renderContext.stencil ) descriptor.depthStencilAttachment.stencilLoadOp = GPULoadOp.Load;
- renderContextData.currentPass = encoder.beginRenderPass( descriptor );
- renderContextData.currentSets = { attributes: {}, bindingGroups: [], pipeline: null, index: null };
- if ( renderContext.viewport ) {
- this.updateViewport( renderContext );
- }
- if ( renderContext.scissor ) {
- this.updateScissor( renderContext );
- }
- } else {
- this.device.queue.submit( [ encoder.finish() ] );
- }
- }
- dispose() {
- this.bindingUtils.dispose();
- this.textureUtils.dispose();
- if ( this.occludedResolveCache ) {
- for ( const buffer of this.occludedResolveCache.values() ) {
- buffer.destroy();
- }
- this.occludedResolveCache.clear();
- }
- if ( this.timestampQueryPool ) {
- for ( const queryPool of Object.values( this.timestampQueryPool ) ) {
- if ( queryPool !== null ) queryPool.dispose();
- }
- }
- if ( this.parameters.device === undefined && this.device !== null ) {
- this.device.destroy();
- }
- }
- }
- /**
- * A IES version of {@link SpotLight}. Can only be used with {@link WebGPURenderer}.
- *
- * @augments SpotLight
- */
- class IESSpotLight extends SpotLight {
- /**
- * Constructs a new IES spot light.
- *
- * @param {(number|Color|string)} [color=0xffffff] - The light's color.
- * @param {number} [intensity=1] - The light's strength/intensity measured in candela (cd).
- * @param {number} [distance=0] - Maximum range of the light. `0` means no limit.
- * @param {number} [angle=Math.PI/3] - Maximum angle of light dispersion from its direction whose upper bound is `Math.PI/2`.
- * @param {number} [penumbra=0] - Percent of the spotlight cone that is attenuated due to penumbra. Value range is `[0,1]`.
- * @param {number} [decay=2] - The amount the light dims along the distance of the light.
- */
- constructor( color, intensity, distance, angle, penumbra, decay ) {
- super( color, intensity, distance, angle, penumbra, decay );
- /**
- * TODO
- *
- * @type {?Texture}
- * @default null
- */
- this.iesMap = null;
- }
- copy( source, recursive ) {
- super.copy( source, recursive );
- this.iesMap = source.iesMap;
- return this;
- }
- }
- /**
- * A projector light version of {@link SpotLight}. Can only be used with {@link WebGPURenderer}.
- *
- * @augments SpotLight
- */
- class ProjectorLight extends SpotLight {
- /**
- * Constructs a new projector light.
- *
- * @param {(number|Color|string)} [color=0xffffff] - The light's color.
- * @param {number} [intensity=1] - The light's strength/intensity measured in candela (cd).
- * @param {number} [distance=0] - Maximum range of the light. `0` means no limit.
- * @param {number} [angle=Math.PI/3] - Maximum angle of light dispersion from its direction whose upper bound is `Math.PI/2`.
- * @param {number} [penumbra=0] - Percent of the spotlight cone that is attenuated due to penumbra. Value range is `[0,1]`.
- * @param {number} [decay=2] - The amount the light dims along the distance of the light.
- */
- constructor( color, intensity, distance, angle, penumbra, decay ) {
- super( color, intensity, distance, angle, penumbra, decay );
- /**
- * Aspect ratio of the light. Set to `null` to use the texture aspect ratio.
- *
- * @type {?number}
- * @default null
- */
- this.aspect = null;
- }
- copy( source, recursive ) {
- super.copy( source, recursive );
- this.aspect = source.aspect;
- return this;
- }
- }
- /**
- * This version of a node library represents a basic version
- * just focusing on lights and tone mapping techniques.
- *
- * @private
- * @augments NodeLibrary
- */
- class BasicNodeLibrary extends NodeLibrary {
- /**
- * Constructs a new basic node library.
- */
- constructor() {
- super();
- this.addLight( PointLightNode, PointLight );
- this.addLight( DirectionalLightNode, DirectionalLight );
- this.addLight( RectAreaLightNode, RectAreaLight );
- this.addLight( SpotLightNode, SpotLight );
- this.addLight( AmbientLightNode, AmbientLight );
- this.addLight( HemisphereLightNode, HemisphereLight );
- this.addLight( LightProbeNode, LightProbe );
- this.addLight( IESSpotLightNode, IESSpotLight );
- this.addLight( ProjectorLightNode, ProjectorLight );
- this.addToneMapping( linearToneMapping, LinearToneMapping );
- this.addToneMapping( reinhardToneMapping, ReinhardToneMapping );
- this.addToneMapping( cineonToneMapping, CineonToneMapping );
- this.addToneMapping( acesFilmicToneMapping, ACESFilmicToneMapping );
- this.addToneMapping( agxToneMapping, AgXToneMapping );
- this.addToneMapping( neutralToneMapping, NeutralToneMapping );
- }
- }
- /**
- * This alternative version of {@link WebGPURenderer} only supports node materials.
- * So classes like `MeshBasicMaterial` are not compatible.
- *
- * @private
- * @augments Renderer
- */
- class WebGPURenderer extends Renderer {
- /**
- * Constructs a new WebGPU renderer.
- *
- * @param {WebGPURenderer~Options} [parameters] - The configuration parameter.
- */
- constructor( parameters = {} ) {
- let BackendClass;
- if ( parameters.forceWebGL ) {
- BackendClass = WebGLBackend;
- } else {
- BackendClass = WebGPUBackend;
- parameters.getFallback = () => {
- warn( 'WebGPURenderer: WebGPU is not available, running under WebGL2 backend.' );
- return new WebGLBackend( parameters );
- };
- }
- const backend = new BackendClass( parameters );
- super( backend, parameters );
- /**
- * The generic default value is overwritten with the
- * standard node library for type mapping. Material
- * mapping is not supported with this version.
- *
- * @type {BasicNodeLibrary}
- */
- this.library = new BasicNodeLibrary();
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isWebGPURenderer = true;
- }
- }
- /**
- * A specialized group which enables applications access to the
- * Render Bundle API of WebGPU. The group with all its descendant nodes
- * are considered as one render bundle and processed as such by
- * the renderer.
- *
- * This module is only fully supported by `WebGPURenderer` with a WebGPU backend.
- * With a WebGL backend, the group can technically be rendered but without
- * any performance improvements.
- *
- * @augments Group
- */
- class BundleGroup extends Group {
- /**
- * Constructs a new bundle group.
- */
- constructor() {
- super();
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isBundleGroup = true;
- /**
- * This property is only relevant for detecting types
- * during serialization/deserialization. It should always
- * match the class name.
- *
- * @type {string}
- * @readonly
- * @default 'BundleGroup'
- */
- this.type = 'BundleGroup';
- /**
- * Whether the bundle is static or not. When set to `true`, the structure
- * is assumed to be static and does not change. E.g. no new objects are
- * added to the group
- *
- * If a change is required, an update can still be forced by setting the
- * `needsUpdate` flag to `true`.
- *
- * @type {boolean}
- * @default true
- */
- this.static = true;
- /**
- * The bundle group's version.
- *
- * @type {number}
- * @readonly
- * @default 0
- */
- this.version = 0;
- }
- /**
- * Set this property to `true` when the bundle group has changed.
- *
- * @type {boolean}
- * @default false
- * @param {boolean} value
- */
- set needsUpdate( value ) {
- if ( value === true ) this.version ++;
- }
- }
- /**
- * This module is responsible to manage the post processing setups in apps.
- * You usually create a single instance of this class and use it to define
- * the output of your post processing effect chain.
- * ```js
- * const postProcessing = new PostProcessing( renderer );
- *
- * const scenePass = pass( scene, camera );
- *
- * postProcessing.outputNode = scenePass;
- * ```
- *
- * Note: This module can only be used with `WebGPURenderer`.
- */
- class PostProcessing {
- /**
- * Constructs a new post processing management module.
- *
- * @param {Renderer} renderer - A reference to the renderer.
- * @param {Node<vec4>} outputNode - An optional output node.
- */
- constructor( renderer, outputNode = vec4( 0, 0, 1, 1 ) ) {
- /**
- * A reference to the renderer.
- *
- * @type {Renderer}
- */
- this.renderer = renderer;
- /**
- * A node which defines the final output of the post
- * processing. This is usually the last node in a chain
- * of effect nodes.
- *
- * @type {Node<vec4>}
- */
- this.outputNode = outputNode;
- /**
- * Whether the default output tone mapping and color
- * space transformation should be enabled or not.
- *
- * It is enabled by default by it must be disabled when
- * effects must be executed after tone mapping and color
- * space conversion. A typical example is FXAA which
- * requires sRGB input.
- *
- * When set to `false`, the app must control the output
- * transformation with `RenderOutputNode`.
- *
- * ```js
- * const outputPass = renderOutput( scenePass );
- * ```
- *
- * @type {boolean}
- */
- this.outputColorTransform = true;
- /**
- * Must be set to `true` when the output node changes.
- *
- * @type {Node<vec4>}
- */
- this.needsUpdate = true;
- const material = new NodeMaterial();
- material.name = 'PostProcessing';
- /**
- * The full screen quad that is used to render
- * the effects.
- *
- * @private
- * @type {QuadMesh}
- */
- this._quadMesh = new QuadMesh( material );
- this._quadMesh.name = 'Post-Processing';
- /**
- * The context of the post processing stack.
- *
- * @private
- * @type {?Object}
- * @default null
- */
- this._context = null;
- }
- /**
- * When `PostProcessing` is used to apply post processing effects,
- * the application must use this version of `render()` inside
- * its animation loop (not the one from the renderer).
- */
- render() {
- const renderer = this.renderer;
- this._update();
- if ( this._context.onBeforePostProcessing !== null ) this._context.onBeforePostProcessing();
- const toneMapping = renderer.toneMapping;
- const outputColorSpace = renderer.outputColorSpace;
- renderer.toneMapping = NoToneMapping;
- renderer.outputColorSpace = ColorManagement.workingColorSpace;
- //
- const currentXR = renderer.xr.enabled;
- renderer.xr.enabled = false;
- this._quadMesh.render( renderer );
- renderer.xr.enabled = currentXR;
- //
- renderer.toneMapping = toneMapping;
- renderer.outputColorSpace = outputColorSpace;
- if ( this._context.onAfterPostProcessing !== null ) this._context.onAfterPostProcessing();
- }
- /**
- * Returns the current context of the post processing stack.
- *
- * @readonly
- * @type {?Object}
- */
- get context() {
- return this._context;
- }
- /**
- * Frees internal resources.
- */
- dispose() {
- this._quadMesh.material.dispose();
- }
- /**
- * Updates the state of the module.
- *
- * @private
- */
- _update() {
- if ( this.needsUpdate === true ) {
- const renderer = this.renderer;
- const toneMapping = renderer.toneMapping;
- const outputColorSpace = renderer.outputColorSpace;
- const context = {
- postProcessing: this,
- onBeforePostProcessing: null,
- onAfterPostProcessing: null
- };
- let outputNode = this.outputNode;
- if ( this.outputColorTransform === true ) {
- outputNode = outputNode.context( context );
- outputNode = renderOutput( outputNode, toneMapping, outputColorSpace );
- } else {
- context.toneMapping = toneMapping;
- context.outputColorSpace = outputColorSpace;
- outputNode = outputNode.context( context );
- }
- this._context = context;
- this._quadMesh.material.fragmentNode = outputNode;
- this._quadMesh.material.needsUpdate = true;
- this.needsUpdate = false;
- }
- }
- /**
- * When `PostProcessing` is used to apply post processing effects,
- * the application must use this version of `renderAsync()` inside
- * its animation loop (not the one from the renderer).
- *
- * @async
- * @deprecated
- * @return {Promise} A Promise that resolves when the render has been finished.
- */
- async renderAsync() {
- warnOnce( 'PostProcessing: "renderAsync()" has been deprecated. Use "render()" and "await renderer.init();" when creating the renderer.' ); // @deprecated r181
- await this.renderer.init();
- this.render();
- }
- }
- /**
- * This special type of texture is intended for compute shaders.
- * It can be used to compute the data of a texture with a compute shader.
- *
- * Note: This type of texture can only be used with `WebGPURenderer`
- * and a WebGPU backend.
- *
- * @augments Texture
- */
- class StorageTexture extends Texture {
- /**
- * Constructs a new storage texture.
- *
- * @param {number} [width=1] - The storage texture's width.
- * @param {number} [height=1] - The storage texture's height.
- */
- constructor( width = 1, height = 1 ) {
- super();
- /**
- * The image object which just represents the texture's dimension.
- *
- * @type {{width: number, height: number}}
- */
- this.image = { width, height };
- /**
- * The default `magFilter` for storage textures is `THREE.LinearFilter`.
- *
- * @type {number}
- */
- this.magFilter = LinearFilter;
- /**
- * The default `minFilter` for storage textures is `THREE.LinearFilter`.
- *
- * @type {number}
- */
- this.minFilter = LinearFilter;
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isStorageTexture = true;
- /**
- * When `true`, mipmaps will be auto-generated after compute writes.
- * When `false`, mipmaps must be written manually via compute shaders.
- *
- * @type {boolean}
- * @default true
- */
- this.mipmapsAutoUpdate = true;
- }
- /**
- * Sets the size of the storage texture.
- *
- * @param {number} width - The new width of the storage texture.
- * @param {number} height - The new height of the storage texture.
- */
- setSize( width, height ) {
- if ( this.image.width !== width || this.image.height !== height ) {
- this.image.width = width;
- this.image.height = height;
- this.dispose();
- }
- }
- }
- /**
- * This special type of buffer attribute is intended for compute shaders.
- * It can be used to encode draw parameters for indirect draw calls.
- *
- * Note: This type of buffer attribute can only be used with `WebGPURenderer`
- * and a WebGPU backend.
- *
- * @augments StorageBufferAttribute
- */
- class IndirectStorageBufferAttribute extends StorageBufferAttribute {
- /**
- * Constructs a new storage buffer attribute.
- *
- * @param {number|Uint32Array} count - The item count. It is also valid to pass a `Uint32Array` as an argument.
- * The subsequent parameter is then obsolete.
- * @param {number} itemSize - The item size.
- */
- constructor( count, itemSize ) {
- super( count, itemSize, Uint32Array );
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isIndirectStorageBufferAttribute = true;
- }
- }
- /**
- * A loader for loading node objects in the three.js JSON Object/Scene format.
- *
- * @augments Loader
- */
- class NodeLoader extends Loader {
- /**
- * Constructs a new node loader.
- *
- * @param {LoadingManager} [manager] - A reference to a loading manager.
- */
- constructor( manager ) {
- super( manager );
- /**
- * Represents a dictionary of textures.
- *
- * @type {Object<string,Texture>}
- */
- this.textures = {};
- /**
- * Represents a dictionary of node types.
- *
- * @type {Object<string,Node.constructor>}
- */
- this.nodes = {};
- }
- /**
- * Loads the node definitions from the given URL.
- *
- * @param {string} url - The path/URL of the file to be loaded.
- * @param {Function} onLoad - Will be called when load completes.
- * @param {Function} onProgress - Will be called while load progresses.
- * @param {Function} onError - Will be called when errors are thrown during the loading process.
- */
- load( url, onLoad, onProgress, onError ) {
- const loader = new FileLoader( this.manager );
- loader.setPath( this.path );
- loader.setRequestHeader( this.requestHeader );
- loader.setWithCredentials( this.withCredentials );
- loader.load( url, ( text ) => {
- try {
- onLoad( this.parse( JSON.parse( text ) ) );
- } catch ( e ) {
- if ( onError ) {
- onError( e );
- } else {
- error( e );
- }
- this.manager.itemError( url );
- }
- }, onProgress, onError );
- }
- /**
- * Parse the node dependencies for the loaded node.
- *
- * @param {Array<Object>} [json] - The JSON definition
- * @return {Object<string,Node>} A dictionary with node dependencies.
- */
- parseNodes( json ) {
- const nodes = {};
- if ( json !== undefined ) {
- for ( const nodeJSON of json ) {
- const { uuid, type } = nodeJSON;
- nodes[ uuid ] = this.createNodeFromType( type );
- nodes[ uuid ].uuid = uuid;
- }
- const meta = { nodes, textures: this.textures };
- for ( const nodeJSON of json ) {
- nodeJSON.meta = meta;
- const node = nodes[ nodeJSON.uuid ];
- node.deserialize( nodeJSON );
- delete nodeJSON.meta;
- }
- }
- return nodes;
- }
- /**
- * Parses the node from the given JSON.
- *
- * @param {Object} json - The JSON definition
- * @param {string} json.type - The node type.
- * @param {string} json.uuid - The node UUID.
- * @param {Array<Object>} [json.nodes] - The node dependencies.
- * @param {Object} [json.meta] - The meta data.
- * @return {Node} The parsed node.
- */
- parse( json ) {
- const node = this.createNodeFromType( json.type );
- node.uuid = json.uuid;
- const nodes = this.parseNodes( json.nodes );
- const meta = { nodes, textures: this.textures };
- json.meta = meta;
- node.deserialize( json );
- delete json.meta;
- return node;
- }
- /**
- * Defines the dictionary of textures.
- *
- * @param {Object<string,Texture>} value - The texture library defines as `<uuid,texture>`.
- * @return {NodeLoader} A reference to this loader.
- */
- setTextures( value ) {
- this.textures = value;
- return this;
- }
- /**
- * Defines the dictionary of node types.
- *
- * @param {Object<string,Node.constructor>} value - The node library defined as `<classname,class>`.
- * @return {NodeLoader} A reference to this loader.
- */
- setNodes( value ) {
- this.nodes = value;
- return this;
- }
- /**
- * Creates a node object from the given type.
- *
- * @param {string} type - The node type.
- * @return {Node} The created node instance.
- */
- createNodeFromType( type ) {
- if ( this.nodes[ type ] === undefined ) {
- error( 'NodeLoader: Node type not found:', type );
- return float();
- }
- return nodeObject( new this.nodes[ type ]() );
- }
- }
- /**
- * A special type of material loader for loading node materials.
- *
- * @augments MaterialLoader
- */
- class NodeMaterialLoader extends MaterialLoader {
- /**
- * Constructs a new node material loader.
- *
- * @param {LoadingManager} [manager] - A reference to a loading manager.
- */
- constructor( manager ) {
- super( manager );
- /**
- * Represents a dictionary of node types.
- *
- * @type {Object<string,Node.constructor>}
- */
- this.nodes = {};
- /**
- * Represents a dictionary of node material types.
- *
- * @type {Object<string,NodeMaterial.constructor>}
- */
- this.nodeMaterials = {};
- }
- /**
- * Parses the node material from the given JSON.
- *
- * @param {Object} json - The JSON definition
- * @return {NodeMaterial}. The parsed material.
- */
- parse( json ) {
- const material = super.parse( json );
- const nodes = this.nodes;
- const inputNodes = json.inputNodes;
- for ( const property in inputNodes ) {
- const uuid = inputNodes[ property ];
- material[ property ] = nodes[ uuid ];
- }
- return material;
- }
- /**
- * Defines the dictionary of node types.
- *
- * @param {Object<string,Node.constructor>} value - The node library defined as `<classname,class>`.
- * @return {NodeLoader} A reference to this loader.
- */
- setNodes( value ) {
- this.nodes = value;
- return this;
- }
- /**
- * Defines the dictionary of node material types.
- *
- * @param {Object<string,NodeMaterial.constructor>} value - The node material library defined as `<classname,class>`.
- * @return {NodeLoader} A reference to this loader.
- */
- setNodeMaterials( value ) {
- this.nodeMaterials = value;
- return this;
- }
- /**
- * Creates a node material from the given type.
- *
- * @param {string} type - The node material type.
- * @return {Node} The created node material instance.
- */
- createMaterialFromType( type ) {
- const materialClass = this.nodeMaterials[ type ];
- if ( materialClass !== undefined ) {
- return new materialClass();
- }
- return super.createMaterialFromType( type );
- }
- }
- /**
- * A special type of object loader for loading 3D objects using
- * node materials.
- *
- * @augments ObjectLoader
- */
- class NodeObjectLoader extends ObjectLoader {
- /**
- * Constructs a new node object loader.
- *
- * @param {LoadingManager} [manager] - A reference to a loading manager.
- */
- constructor( manager ) {
- super( manager );
- /**
- * Represents a dictionary of node types.
- *
- * @type {Object<string,Node.constructor>}
- */
- this.nodes = {};
- /**
- * Represents a dictionary of node material types.
- *
- * @type {Object<string,NodeMaterial.constructor>}
- */
- this.nodeMaterials = {};
- /**
- * A reference to hold the `nodes` JSON property.
- *
- * @private
- * @type {?Object[]}
- */
- this._nodesJSON = null;
- }
- /**
- * Defines the dictionary of node types.
- *
- * @param {Object<string,Node.constructor>} value - The node library defined as `<classname,class>`.
- * @return {NodeObjectLoader} A reference to this loader.
- */
- setNodes( value ) {
- this.nodes = value;
- return this;
- }
- /**
- * Defines the dictionary of node material types.
- *
- * @param {Object<string,NodeMaterial.constructor>} value - The node material library defined as `<classname,class>`.
- * @return {NodeObjectLoader} A reference to this loader.
- */
- setNodeMaterials( value ) {
- this.nodeMaterials = value;
- return this;
- }
- /**
- * Parses the node objects from the given JSON.
- *
- * @param {Object} json - The JSON definition
- * @param {Function} onLoad - The onLoad callback function.
- * @return {Object3D}. The parsed 3D object.
- */
- parse( json, onLoad ) {
- this._nodesJSON = json.nodes;
- const data = super.parse( json, onLoad );
- this._nodesJSON = null; // dispose
- return data;
- }
- /**
- * Parses the node objects from the given JSON and textures.
- *
- * @param {Object[]} json - The JSON definition
- * @param {Object<string,Texture>} textures - The texture library.
- * @return {Object<string,Node>}. The parsed nodes.
- */
- parseNodes( json, textures ) {
- if ( json !== undefined ) {
- const loader = new NodeLoader();
- loader.setNodes( this.nodes );
- loader.setTextures( textures );
- return loader.parseNodes( json );
- }
- return {};
- }
- /**
- * Parses the node objects from the given JSON and textures.
- *
- * @param {Object} json - The JSON definition
- * @param {Object<string,Texture>} textures - The texture library.
- * @return {Object<string,NodeMaterial>}. The parsed materials.
- */
- parseMaterials( json, textures ) {
- const materials = {};
- if ( json !== undefined ) {
- const nodes = this.parseNodes( this._nodesJSON, textures );
- const loader = new NodeMaterialLoader();
- loader.setTextures( textures );
- loader.setNodes( nodes );
- loader.setNodeMaterials( this.nodeMaterials );
- for ( let i = 0, l = json.length; i < l; i ++ ) {
- const data = json[ i ];
- materials[ data.uuid ] = loader.parse( data );
- }
- }
- return materials;
- }
- }
- /**
- * In earlier three.js versions, clipping was defined globally
- * on the renderer or on material level. This special version of
- * `THREE.Group` allows to encode the clipping state into the scene
- * graph. Meaning if you create an instance of this group, all
- * descendant 3D objects will be affected by the respective clipping
- * planes.
- *
- * Note: `ClippingGroup` can only be used with `WebGPURenderer`.
- *
- * @augments Group
- */
- class ClippingGroup extends Group {
- /**
- * Constructs a new clipping group.
- */
- constructor() {
- super();
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isClippingGroup = true;
- /**
- * An array with clipping planes.
- *
- * @type {Array<Plane>}
- */
- this.clippingPlanes = [];
- /**
- * Whether clipping should be enabled or not.
- *
- * @type {boolean}
- * @default true
- */
- this.enabled = true;
- /**
- * Whether the intersection of the clipping planes is used to clip objects, rather than their union.
- *
- * @type {boolean}
- * @default false
- */
- this.clipIntersection = false;
- /**
- * Whether shadows should be clipped or not.
- *
- * @type {boolean}
- * @default false
- */
- this.clipShadows = false;
- }
- }
- export { ACESFilmicToneMapping, AONode, AddEquation, AddOperation, AdditiveBlending, AgXToneMapping, AlphaFormat, AlwaysCompare, AlwaysDepth, AlwaysStencilFunc, AmbientLight, AmbientLightNode, AnalyticLightNode, ArrayCamera, ArrayElementNode, ArrayNode, AssignNode, AttributeNode, BackSide, BasicEnvironmentNode, BasicShadowMap, BatchNode, BitcastNode, BoxGeometry, BufferAttribute, BufferAttributeNode, BufferGeometry, BufferNode, BumpMapNode, BundleGroup, BypassNode, ByteType, Camera, CanvasTarget, CineonToneMapping, ClampToEdgeWrapping, ClippingGroup, CodeNode, Color, ColorManagement, ColorSpaceNode, ComputeNode, ConstNode, ContextNode, ConvertNode, CubeCamera, CubeDepthTexture, CubeReflectionMapping, CubeRefractionMapping, CubeTexture, CubeTextureNode, CubeUVReflectionMapping, CullFaceBack, CullFaceFront, CullFaceNone, CustomBlending, CylinderGeometry, DataArrayTexture, DataTexture, DebugNode, DecrementStencilOp, DecrementWrapStencilOp, DepthFormat, DepthStencilFormat, DepthTexture, DirectionalLight, DirectionalLightNode, DoubleSide, DstAlphaFactor, DstColorFactor, DynamicDrawUsage, EnvironmentNode, EqualCompare, EqualDepth, EqualStencilFunc, EquirectangularReflectionMapping, EquirectangularRefractionMapping, Euler, EventDispatcher, EventNode, ExpressionNode, FileLoader, Float16BufferAttribute, Float32BufferAttribute, FloatType, FramebufferTexture, FrontFacingNode, FrontSide, Frustum, FrustumArray, FunctionCallNode, FunctionNode, FunctionOverloadingNode, GLSLNodeParser, GreaterCompare, GreaterDepth, GreaterEqualCompare, GreaterEqualDepth, GreaterEqualStencilFunc, GreaterStencilFunc, Group, HalfFloatType, HemisphereLight, HemisphereLightNode, IESSpotLight, IESSpotLightNode, IncrementStencilOp, IncrementWrapStencilOp, IndexNode, IndirectStorageBufferAttribute, InspectorBase, InstanceNode, InstancedBufferAttribute, InstancedInterleavedBuffer, InstancedMeshNode, IntType, InterleavedBuffer, InterleavedBufferAttribute, InvertStencilOp, IrradianceNode, IsolateNode, JoinNode, KeepStencilOp, LessCompare, LessDepth, LessEqualCompare, LessEqualDepth, LessEqualStencilFunc, LessStencilFunc, LightProbe, LightProbeNode, Lighting, LightingContextNode, LightingModel, LightingNode, LightsNode, Line2NodeMaterial, LineBasicMaterial, LineBasicNodeMaterial, LineDashedMaterial, LineDashedNodeMaterial, LinearFilter, LinearMipMapLinearFilter, LinearMipmapLinearFilter, LinearMipmapNearestFilter, LinearSRGBColorSpace, LinearToneMapping, LinearTransfer, Loader, LoopNode, MRTNode, Material, MaterialLoader, MaterialNode, MaterialReferenceNode, MathUtils, Matrix2, Matrix3, Matrix4, MaxEquation, MaxMipLevelNode, MemberNode, Mesh, MeshBasicMaterial, MeshBasicNodeMaterial, MeshLambertMaterial, MeshLambertNodeMaterial, MeshMatcapMaterial, MeshMatcapNodeMaterial, MeshNormalMaterial, MeshNormalNodeMaterial, MeshPhongMaterial, MeshPhongNodeMaterial, MeshPhysicalMaterial, MeshPhysicalNodeMaterial, MeshSSSNodeMaterial, MeshStandardMaterial, MeshStandardNodeMaterial, MeshToonMaterial, MeshToonNodeMaterial, MinEquation, MirroredRepeatWrapping, MixOperation, ModelNode, MorphNode, MultiplyBlending, MultiplyOperation, NearestFilter, NearestMipmapLinearFilter, NearestMipmapNearestFilter, NeutralToneMapping, NeverCompare, NeverDepth, NeverStencilFunc, NoBlending, NoColorSpace, NoNormalPacking, NoToneMapping, Node, NodeAccess, NodeAttribute, NodeBuilder, NodeCache, NodeCode, NodeFrame, NodeFunctionInput, NodeLoader, NodeMaterial, NodeMaterialLoader, NodeMaterialObserver, NodeObjectLoader, NodeShaderStage, NodeType, NodeUniform, NodeUpdateType, NodeUtils, NodeVar, NodeVarying, NormalBlending, NormalGAPacking, NormalMapNode, NormalRGPacking, NotEqualCompare, NotEqualDepth, NotEqualStencilFunc, Object3D, Object3DNode, ObjectLoader, ObjectSpaceNormalMap, OneFactor, OneMinusDstAlphaFactor, OneMinusDstColorFactor, OneMinusSrcAlphaFactor, OneMinusSrcColorFactor, OrthographicCamera, OutputStructNode, PCFShadowMap, PMREMGenerator, PMREMNode, ParameterNode, PassNode, PerspectiveCamera, PhongLightingModel, PhysicalLightingModel, Plane, PlaneGeometry, PointLight, PointLightNode, PointUVNode, PointsMaterial, PointsNodeMaterial, PostProcessing, PosterizeNode, ProjectorLight, ProjectorLightNode, PropertyNode, QuadMesh, Quaternion, R11_EAC_Format, RED_GREEN_RGTC2_Format, RED_RGTC1_Format, REVISION, RG11_EAC_Format, RGBAFormat, RGBAIntegerFormat, RGBA_ASTC_10x10_Format, RGBA_ASTC_10x5_Format, RGBA_ASTC_10x6_Format, RGBA_ASTC_10x8_Format, RGBA_ASTC_12x10_Format, RGBA_ASTC_12x12_Format, RGBA_ASTC_4x4_Format, RGBA_ASTC_5x4_Format, RGBA_ASTC_5x5_Format, RGBA_ASTC_6x5_Format, RGBA_ASTC_6x6_Format, RGBA_ASTC_8x5_Format, RGBA_ASTC_8x6_Format, RGBA_ASTC_8x8_Format, RGBA_BPTC_Format, RGBA_ETC2_EAC_Format, RGBA_PVRTC_2BPPV1_Format, RGBA_PVRTC_4BPPV1_Format, RGBA_S3TC_DXT1_Format, RGBA_S3TC_DXT3_Format, RGBA_S3TC_DXT5_Format, RGBFormat, RGBIntegerFormat, RGB_ETC1_Format, RGB_ETC2_Format, RGB_PVRTC_2BPPV1_Format, RGB_PVRTC_4BPPV1_Format, RGB_S3TC_DXT1_Format, RGFormat, RGIntegerFormat, RTTNode, RangeNode, RectAreaLight, RectAreaLightNode, RedFormat, RedIntegerFormat, ReferenceNode, ReflectorNode, ReinhardToneMapping, RemapNode, RenderOutputNode, RenderTarget, RendererReferenceNode, RendererUtils, RepeatWrapping, ReplaceStencilOp, ReverseSubtractEquation, RotateNode, SIGNED_R11_EAC_Format, SIGNED_RED_GREEN_RGTC2_Format, SIGNED_RED_RGTC1_Format, SIGNED_RG11_EAC_Format, SRGBColorSpace, SRGBTransfer, Scene, SceneNode, ScreenNode, ScriptableNode, ScriptableValueNode, SetNode, ShadowBaseNode, ShadowMaterial, ShadowNode, ShadowNodeMaterial, ShortType, SkinningNode, Sphere, SphereGeometry, SplitNode, SpotLight, SpotLightNode, SpriteMaterial, SpriteNodeMaterial, SpriteSheetUVNode, SrcAlphaFactor, SrcAlphaSaturateFactor, SrcColorFactor, StackNode, StaticDrawUsage, StorageArrayElementNode, StorageBufferAttribute, StorageBufferNode, StorageInstancedBufferAttribute, StorageTexture, StorageTextureNode, StructNode, StructTypeNode, SubBuildNode, SubtractEquation, SubtractiveBlending, TSL, TangentSpaceNormalMap, TempNode, Texture, Texture3DNode, TextureNode, TextureSizeNode, TimestampQuery, ToneMappingNode, ToonOutlinePassNode, UVMapping, Uint16BufferAttribute, Uint32BufferAttribute, UniformArrayNode, UniformGroupNode, UniformNode, UnsignedByteType, UnsignedInt101111Type, UnsignedInt248Type, UnsignedInt5999Type, UnsignedIntType, UnsignedShort4444Type, UnsignedShort5551Type, UnsignedShortType, UserDataNode, VSMShadowMap, VarNode, VaryingNode, Vector2, Vector3, Vector4, VertexColorNode, ViewportDepthNode, ViewportDepthTextureNode, ViewportSharedTextureNode, ViewportTextureNode, VolumeNodeMaterial, WebGLCoordinateSystem, WebGLCubeRenderTarget, WebGPUCoordinateSystem, WebGPURenderer, WebXRController, ZeroFactor, ZeroStencilOp, createCanvasElement, defaultBuildStages, defaultShaderStages, error, log$1 as log, shaderStages, vectorComponents, warn, warnOnce };
|