123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644464546464647464846494650465146524653465446554656465746584659466046614662466346644665466646674668466946704671467246734674467546764677467846794680468146824683468446854686468746884689469046914692469346944695469646974698469947004701470247034704470547064707470847094710471147124713471447154716471747184719472047214722472347244725472647274728472947304731473247334734473547364737473847394740474147424743474447454746474747484749475047514752475347544755475647574758475947604761476247634764476547664767476847694770477147724773477447754776477747784779478047814782478347844785478647874788478947904791479247934794479547964797479847994800480148024803480448054806480748084809481048114812481348144815481648174818481948204821482248234824482548264827482848294830483148324833483448354836483748384839484048414842484348444845484648474848484948504851485248534854485548564857485848594860486148624863486448654866486748684869487048714872487348744875487648774878487948804881488248834884488548864887488848894890489148924893489448954896489748984899490049014902490349044905490649074908490949104911491249134914491549164917491849194920492149224923492449254926492749284929493049314932493349344935493649374938493949404941494249434944494549464947494849494950495149524953495449554956495749584959496049614962496349644965496649674968496949704971497249734974497549764977497849794980498149824983498449854986498749884989499049914992499349944995499649974998499950005001500250035004500550065007500850095010501150125013501450155016501750185019502050215022502350245025502650275028502950305031503250335034503550365037503850395040504150425043504450455046504750485049505050515052505350545055505650575058505950605061506250635064506550665067506850695070507150725073507450755076507750785079508050815082508350845085508650875088508950905091509250935094509550965097509850995100510151025103510451055106510751085109511051115112511351145115511651175118511951205121512251235124512551265127512851295130513151325133513451355136513751385139514051415142514351445145514651475148514951505151515251535154515551565157515851595160516151625163516451655166516751685169517051715172517351745175517651775178517951805181518251835184518551865187518851895190519151925193519451955196519751985199520052015202520352045205520652075208520952105211521252135214521552165217521852195220522152225223522452255226522752285229523052315232523352345235523652375238523952405241524252435244524552465247524852495250525152525253525452555256525752585259526052615262526352645265526652675268526952705271527252735274527552765277527852795280528152825283528452855286528752885289529052915292529352945295529652975298529953005301530253035304530553065307530853095310531153125313531453155316531753185319532053215322532353245325532653275328532953305331533253335334533553365337533853395340534153425343534453455346534753485349535053515352535353545355535653575358535953605361536253635364536553665367536853695370537153725373537453755376537753785379538053815382538353845385538653875388538953905391539253935394539553965397539853995400540154025403540454055406540754085409541054115412541354145415541654175418541954205421542254235424542554265427542854295430543154325433543454355436543754385439544054415442544354445445544654475448544954505451545254535454545554565457545854595460546154625463546454655466546754685469547054715472547354745475547654775478547954805481548254835484548554865487548854895490549154925493549454955496549754985499550055015502550355045505550655075508550955105511551255135514551555165517551855195520552155225523552455255526552755285529553055315532553355345535553655375538553955405541554255435544554555465547554855495550555155525553555455555556555755585559556055615562556355645565556655675568556955705571557255735574557555765577557855795580558155825583558455855586558755885589559055915592559355945595559655975598559956005601560256035604560556065607560856095610561156125613561456155616561756185619562056215622562356245625562656275628562956305631563256335634563556365637563856395640564156425643564456455646564756485649565056515652565356545655565656575658565956605661566256635664566556665667566856695670567156725673567456755676567756785679568056815682568356845685568656875688568956905691569256935694569556965697569856995700570157025703570457055706570757085709571057115712571357145715571657175718571957205721572257235724572557265727572857295730573157325733573457355736573757385739574057415742574357445745574657475748574957505751575257535754575557565757575857595760576157625763576457655766576757685769577057715772577357745775577657775778577957805781578257835784578557865787578857895790579157925793579457955796579757985799580058015802580358045805580658075808580958105811581258135814581558165817581858195820582158225823582458255826582758285829583058315832583358345835583658375838583958405841584258435844584558465847584858495850585158525853585458555856585758585859586058615862586358645865586658675868586958705871587258735874587558765877587858795880588158825883588458855886588758885889589058915892589358945895589658975898589959005901590259035904590559065907590859095910591159125913591459155916591759185919592059215922592359245925592659275928592959305931593259335934593559365937593859395940594159425943594459455946594759485949595059515952595359545955595659575958595959605961596259635964596559665967596859695970597159725973597459755976597759785979598059815982598359845985598659875988598959905991599259935994599559965997599859996000600160026003600460056006600760086009601060116012601360146015601660176018601960206021602260236024602560266027602860296030603160326033603460356036603760386039604060416042604360446045604660476048604960506051605260536054605560566057605860596060606160626063606460656066606760686069607060716072607360746075607660776078607960806081608260836084608560866087608860896090609160926093609460956096609760986099610061016102610361046105610661076108610961106111611261136114611561166117611861196120612161226123612461256126612761286129613061316132613361346135613661376138613961406141614261436144614561466147614861496150615161526153615461556156615761586159616061616162616361646165616661676168616961706171617261736174617561766177617861796180618161826183618461856186618761886189619061916192619361946195619661976198619962006201620262036204620562066207620862096210621162126213621462156216621762186219622062216222622362246225622662276228622962306231623262336234623562366237623862396240624162426243624462456246624762486249625062516252625362546255625662576258625962606261626262636264626562666267626862696270627162726273627462756276627762786279628062816282628362846285628662876288628962906291629262936294629562966297629862996300630163026303630463056306630763086309631063116312631363146315631663176318631963206321632263236324632563266327632863296330633163326333633463356336633763386339634063416342634363446345634663476348634963506351635263536354635563566357635863596360636163626363636463656366636763686369637063716372637363746375637663776378637963806381638263836384638563866387638863896390639163926393639463956396639763986399640064016402640364046405640664076408640964106411641264136414641564166417641864196420642164226423642464256426642764286429643064316432643364346435643664376438643964406441644264436444644564466447644864496450645164526453645464556456645764586459646064616462646364646465646664676468646964706471647264736474647564766477647864796480648164826483648464856486648764886489649064916492649364946495649664976498649965006501650265036504650565066507650865096510651165126513651465156516651765186519652065216522652365246525652665276528652965306531653265336534653565366537653865396540654165426543654465456546654765486549655065516552655365546555655665576558655965606561656265636564656565666567656865696570657165726573657465756576657765786579658065816582658365846585658665876588658965906591659265936594659565966597659865996600660166026603660466056606660766086609661066116612661366146615661666176618661966206621662266236624662566266627662866296630663166326633663466356636663766386639664066416642664366446645664666476648664966506651665266536654665566566657665866596660666166626663666466656666666766686669667066716672667366746675667666776678667966806681668266836684668566866687668866896690669166926693669466956696669766986699670067016702670367046705670667076708670967106711671267136714671567166717671867196720672167226723672467256726672767286729673067316732673367346735673667376738673967406741674267436744674567466747674867496750675167526753675467556756675767586759676067616762676367646765676667676768676967706771677267736774677567766777677867796780678167826783678467856786678767886789679067916792679367946795679667976798679968006801680268036804680568066807680868096810681168126813681468156816681768186819682068216822682368246825682668276828682968306831683268336834683568366837683868396840684168426843684468456846684768486849685068516852685368546855685668576858685968606861686268636864686568666867686868696870687168726873687468756876687768786879688068816882688368846885688668876888688968906891689268936894689568966897689868996900690169026903690469056906690769086909691069116912691369146915691669176918691969206921692269236924692569266927692869296930693169326933693469356936693769386939694069416942694369446945694669476948694969506951695269536954695569566957695869596960696169626963696469656966696769686969697069716972697369746975697669776978697969806981698269836984698569866987698869896990699169926993699469956996699769986999700070017002700370047005700670077008700970107011701270137014701570167017701870197020702170227023702470257026702770287029703070317032703370347035703670377038703970407041704270437044704570467047704870497050705170527053705470557056705770587059706070617062706370647065706670677068706970707071707270737074707570767077707870797080708170827083708470857086708770887089709070917092709370947095709670977098709971007101710271037104710571067107710871097110711171127113711471157116711771187119712071217122712371247125712671277128712971307131713271337134713571367137713871397140714171427143714471457146714771487149715071517152715371547155715671577158715971607161716271637164716571667167716871697170717171727173717471757176717771787179718071817182718371847185718671877188718971907191719271937194719571967197719871997200720172027203720472057206720772087209721072117212721372147215721672177218721972207221722272237224722572267227722872297230723172327233723472357236723772387239724072417242724372447245724672477248724972507251725272537254725572567257725872597260726172627263726472657266726772687269727072717272727372747275727672777278727972807281728272837284728572867287728872897290729172927293729472957296729772987299730073017302730373047305730673077308730973107311731273137314731573167317731873197320732173227323732473257326732773287329733073317332733373347335733673377338733973407341734273437344734573467347734873497350735173527353735473557356735773587359736073617362736373647365736673677368736973707371737273737374737573767377737873797380738173827383738473857386738773887389739073917392739373947395739673977398739974007401740274037404740574067407740874097410741174127413741474157416741774187419742074217422742374247425742674277428742974307431743274337434743574367437743874397440744174427443744474457446744774487449745074517452745374547455745674577458745974607461746274637464746574667467746874697470747174727473747474757476747774787479748074817482748374847485748674877488748974907491749274937494749574967497749874997500750175027503750475057506750775087509751075117512751375147515751675177518751975207521752275237524752575267527752875297530753175327533753475357536753775387539754075417542754375447545754675477548754975507551755275537554755575567557755875597560756175627563756475657566756775687569757075717572757375747575757675777578757975807581758275837584758575867587758875897590759175927593759475957596759775987599760076017602760376047605760676077608760976107611761276137614761576167617761876197620762176227623762476257626762776287629763076317632763376347635763676377638763976407641764276437644764576467647764876497650765176527653765476557656765776587659766076617662766376647665766676677668766976707671767276737674767576767677767876797680768176827683768476857686768776887689769076917692769376947695769676977698769977007701770277037704770577067707770877097710771177127713771477157716771777187719772077217722772377247725772677277728772977307731773277337734773577367737773877397740774177427743774477457746774777487749775077517752775377547755775677577758775977607761776277637764776577667767776877697770777177727773777477757776777777787779778077817782778377847785778677877788778977907791779277937794779577967797779877997800780178027803780478057806780778087809781078117812781378147815781678177818781978207821782278237824782578267827782878297830783178327833783478357836783778387839784078417842784378447845784678477848784978507851785278537854785578567857785878597860786178627863786478657866786778687869787078717872787378747875787678777878787978807881788278837884788578867887788878897890789178927893789478957896789778987899790079017902790379047905790679077908790979107911791279137914791579167917791879197920792179227923792479257926792779287929793079317932793379347935793679377938793979407941794279437944794579467947794879497950795179527953795479557956795779587959796079617962796379647965796679677968796979707971797279737974797579767977797879797980798179827983798479857986798779887989799079917992799379947995799679977998799980008001800280038004800580068007800880098010801180128013801480158016801780188019802080218022802380248025802680278028802980308031803280338034803580368037803880398040804180428043804480458046804780488049805080518052805380548055805680578058805980608061806280638064806580668067806880698070807180728073807480758076807780788079808080818082808380848085808680878088808980908091809280938094809580968097809880998100810181028103810481058106810781088109811081118112811381148115811681178118811981208121812281238124812581268127812881298130813181328133813481358136813781388139814081418142814381448145814681478148814981508151815281538154815581568157815881598160816181628163816481658166816781688169817081718172817381748175817681778178817981808181818281838184818581868187818881898190819181928193819481958196819781988199820082018202820382048205820682078208820982108211821282138214821582168217821882198220822182228223822482258226822782288229823082318232823382348235823682378238823982408241824282438244824582468247824882498250825182528253825482558256825782588259826082618262826382648265826682678268826982708271827282738274827582768277827882798280828182828283828482858286828782888289829082918292829382948295829682978298829983008301830283038304830583068307830883098310831183128313831483158316831783188319832083218322832383248325832683278328832983308331833283338334833583368337833883398340834183428343834483458346834783488349835083518352835383548355835683578358835983608361836283638364836583668367836883698370837183728373837483758376837783788379838083818382838383848385838683878388838983908391839283938394839583968397839883998400840184028403840484058406840784088409841084118412841384148415841684178418841984208421842284238424842584268427842884298430843184328433843484358436843784388439844084418442844384448445844684478448844984508451845284538454845584568457845884598460846184628463846484658466846784688469847084718472847384748475847684778478847984808481848284838484848584868487848884898490849184928493849484958496849784988499850085018502850385048505850685078508850985108511851285138514851585168517851885198520852185228523852485258526852785288529853085318532853385348535853685378538853985408541854285438544854585468547854885498550855185528553855485558556855785588559856085618562856385648565856685678568856985708571857285738574857585768577857885798580858185828583858485858586858785888589859085918592859385948595859685978598859986008601860286038604860586068607860886098610861186128613861486158616861786188619862086218622862386248625862686278628862986308631863286338634863586368637863886398640864186428643864486458646864786488649865086518652865386548655865686578658865986608661866286638664866586668667866886698670867186728673867486758676867786788679868086818682868386848685868686878688868986908691869286938694869586968697869886998700870187028703870487058706870787088709871087118712871387148715871687178718871987208721872287238724872587268727872887298730873187328733873487358736873787388739874087418742874387448745874687478748874987508751875287538754875587568757875887598760876187628763876487658766876787688769877087718772877387748775877687778778877987808781878287838784878587868787878887898790879187928793879487958796879787988799880088018802880388048805880688078808880988108811881288138814881588168817881888198820882188228823882488258826882788288829883088318832883388348835883688378838883988408841884288438844884588468847884888498850885188528853885488558856885788588859886088618862886388648865886688678868886988708871887288738874887588768877887888798880888188828883888488858886888788888889889088918892889388948895889688978898889989008901890289038904890589068907890889098910891189128913891489158916891789188919892089218922892389248925892689278928892989308931893289338934893589368937893889398940894189428943894489458946894789488949895089518952895389548955895689578958895989608961896289638964896589668967896889698970897189728973897489758976897789788979898089818982898389848985898689878988898989908991899289938994899589968997899889999000900190029003900490059006900790089009901090119012901390149015901690179018901990209021902290239024902590269027902890299030903190329033903490359036903790389039904090419042904390449045904690479048904990509051905290539054905590569057905890599060906190629063906490659066906790689069907090719072907390749075907690779078907990809081908290839084908590869087908890899090909190929093909490959096909790989099910091019102910391049105910691079108910991109111911291139114911591169117911891199120912191229123912491259126912791289129913091319132913391349135913691379138913991409141914291439144914591469147914891499150915191529153915491559156915791589159916091619162916391649165916691679168916991709171917291739174917591769177917891799180918191829183918491859186918791889189919091919192919391949195919691979198919992009201920292039204920592069207920892099210921192129213921492159216921792189219922092219222922392249225922692279228922992309231923292339234923592369237923892399240924192429243924492459246924792489249925092519252925392549255925692579258925992609261926292639264926592669267926892699270927192729273927492759276927792789279928092819282928392849285928692879288928992909291929292939294929592969297929892999300930193029303930493059306930793089309931093119312931393149315931693179318931993209321932293239324932593269327932893299330933193329333933493359336933793389339934093419342934393449345934693479348934993509351935293539354935593569357935893599360936193629363936493659366936793689369937093719372937393749375937693779378937993809381938293839384938593869387938893899390939193929393939493959396939793989399940094019402940394049405940694079408940994109411941294139414941594169417941894199420942194229423942494259426942794289429943094319432943394349435943694379438943994409441944294439444944594469447944894499450945194529453945494559456945794589459946094619462946394649465946694679468946994709471947294739474947594769477947894799480948194829483948494859486948794889489949094919492949394949495949694979498949995009501950295039504950595069507950895099510951195129513951495159516951795189519952095219522952395249525952695279528952995309531953295339534953595369537953895399540954195429543954495459546954795489549955095519552955395549555955695579558955995609561956295639564956595669567956895699570957195729573957495759576957795789579958095819582958395849585958695879588958995909591959295939594959595969597959895999600960196029603960496059606960796089609961096119612961396149615961696179618961996209621962296239624962596269627962896299630963196329633963496359636963796389639964096419642964396449645964696479648964996509651965296539654965596569657965896599660966196629663966496659666966796689669967096719672967396749675967696779678967996809681968296839684968596869687968896899690969196929693969496959696969796989699970097019702970397049705970697079708970997109711971297139714971597169717971897199720972197229723972497259726972797289729973097319732973397349735973697379738973997409741974297439744974597469747974897499750975197529753975497559756975797589759976097619762976397649765976697679768976997709771977297739774977597769777977897799780978197829783978497859786978797889789979097919792979397949795979697979798979998009801980298039804980598069807980898099810981198129813981498159816981798189819982098219822982398249825982698279828982998309831983298339834983598369837983898399840984198429843984498459846984798489849985098519852985398549855985698579858985998609861986298639864986598669867986898699870987198729873987498759876987798789879988098819882988398849885988698879888988998909891989298939894989598969897989898999900990199029903990499059906990799089909991099119912991399149915991699179918991999209921992299239924992599269927992899299930993199329933993499359936993799389939994099419942994399449945994699479948994999509951995299539954995599569957995899599960996199629963996499659966996799689969997099719972997399749975997699779978997999809981998299839984998599869987998899899990999199929993999499959996999799989999100001000110002100031000410005100061000710008100091001010011100121001310014100151001610017100181001910020100211002210023100241002510026100271002810029100301003110032100331003410035100361003710038100391004010041100421004310044100451004610047100481004910050100511005210053100541005510056100571005810059100601006110062100631006410065100661006710068100691007010071100721007310074100751007610077100781007910080100811008210083100841008510086100871008810089100901009110092100931009410095100961009710098100991010010101101021010310104101051010610107101081010910110101111011210113101141011510116101171011810119101201012110122101231012410125101261012710128101291013010131101321013310134101351013610137101381013910140101411014210143101441014510146101471014810149101501015110152101531015410155101561015710158101591016010161101621016310164101651016610167101681016910170101711017210173101741017510176101771017810179101801018110182101831018410185101861018710188101891019010191101921019310194101951019610197101981019910200102011020210203102041020510206102071020810209102101021110212102131021410215102161021710218102191022010221102221022310224102251022610227102281022910230102311023210233102341023510236102371023810239102401024110242102431024410245102461024710248102491025010251102521025310254102551025610257102581025910260102611026210263102641026510266102671026810269102701027110272102731027410275102761027710278102791028010281102821028310284102851028610287102881028910290102911029210293102941029510296102971029810299103001030110302103031030410305103061030710308103091031010311103121031310314103151031610317103181031910320103211032210323103241032510326103271032810329103301033110332103331033410335103361033710338103391034010341103421034310344103451034610347103481034910350103511035210353103541035510356103571035810359103601036110362103631036410365103661036710368103691037010371103721037310374103751037610377103781037910380103811038210383103841038510386103871038810389103901039110392103931039410395103961039710398103991040010401104021040310404104051040610407104081040910410104111041210413104141041510416104171041810419104201042110422104231042410425104261042710428104291043010431104321043310434104351043610437104381043910440104411044210443104441044510446104471044810449104501045110452104531045410455104561045710458104591046010461104621046310464104651046610467104681046910470104711047210473104741047510476104771047810479104801048110482104831048410485104861048710488104891049010491104921049310494104951049610497104981049910500105011050210503105041050510506105071050810509105101051110512105131051410515105161051710518105191052010521105221052310524105251052610527105281052910530105311053210533105341053510536105371053810539105401054110542105431054410545105461054710548105491055010551105521055310554105551055610557105581055910560105611056210563105641056510566105671056810569105701057110572105731057410575105761057710578105791058010581105821058310584105851058610587105881058910590105911059210593105941059510596105971059810599106001060110602106031060410605106061060710608106091061010611106121061310614106151061610617106181061910620106211062210623106241062510626106271062810629106301063110632106331063410635106361063710638106391064010641106421064310644106451064610647106481064910650106511065210653106541065510656106571065810659106601066110662106631066410665106661066710668106691067010671106721067310674106751067610677106781067910680106811068210683106841068510686106871068810689106901069110692106931069410695106961069710698106991070010701107021070310704107051070610707107081070910710107111071210713107141071510716107171071810719107201072110722107231072410725107261072710728107291073010731107321073310734107351073610737107381073910740107411074210743107441074510746107471074810749107501075110752107531075410755107561075710758107591076010761107621076310764107651076610767107681076910770107711077210773107741077510776107771077810779107801078110782107831078410785107861078710788107891079010791107921079310794107951079610797107981079910800108011080210803108041080510806108071080810809108101081110812108131081410815108161081710818108191082010821108221082310824108251082610827108281082910830108311083210833108341083510836108371083810839108401084110842108431084410845108461084710848108491085010851108521085310854108551085610857108581085910860108611086210863108641086510866108671086810869108701087110872108731087410875108761087710878108791088010881108821088310884108851088610887108881088910890108911089210893108941089510896108971089810899109001090110902109031090410905109061090710908109091091010911109121091310914109151091610917109181091910920109211092210923109241092510926109271092810929109301093110932109331093410935109361093710938109391094010941109421094310944109451094610947109481094910950109511095210953109541095510956109571095810959109601096110962109631096410965109661096710968109691097010971109721097310974109751097610977109781097910980109811098210983109841098510986109871098810989109901099110992109931099410995109961099710998109991100011001110021100311004110051100611007110081100911010110111101211013110141101511016110171101811019110201102111022110231102411025110261102711028110291103011031110321103311034110351103611037110381103911040110411104211043110441104511046110471104811049110501105111052110531105411055110561105711058110591106011061110621106311064110651106611067110681106911070110711107211073110741107511076110771107811079110801108111082110831108411085110861108711088110891109011091110921109311094110951109611097110981109911100111011110211103111041110511106111071110811109111101111111112111131111411115111161111711118111191112011121111221112311124111251112611127111281112911130111311113211133111341113511136111371113811139111401114111142111431114411145111461114711148111491115011151111521115311154111551115611157111581115911160111611116211163111641116511166111671116811169111701117111172111731117411175111761117711178111791118011181111821118311184111851118611187111881118911190111911119211193111941119511196111971119811199112001120111202112031120411205112061120711208112091121011211112121121311214112151121611217112181121911220112211122211223112241122511226112271122811229112301123111232112331123411235112361123711238112391124011241112421124311244112451124611247112481124911250112511125211253112541125511256112571125811259112601126111262112631126411265112661126711268112691127011271112721127311274112751127611277112781127911280112811128211283112841128511286112871128811289112901129111292112931129411295112961129711298112991130011301113021130311304113051130611307113081130911310113111131211313113141131511316113171131811319113201132111322113231132411325113261132711328113291133011331113321133311334113351133611337113381133911340113411134211343113441134511346113471134811349113501135111352113531135411355113561135711358113591136011361113621136311364113651136611367113681136911370113711137211373113741137511376113771137811379113801138111382113831138411385113861138711388113891139011391113921139311394113951139611397113981139911400114011140211403114041140511406114071140811409114101141111412114131141411415114161141711418114191142011421114221142311424114251142611427114281142911430114311143211433114341143511436114371143811439114401144111442114431144411445114461144711448114491145011451114521145311454114551145611457114581145911460114611146211463114641146511466114671146811469114701147111472114731147411475114761147711478114791148011481114821148311484114851148611487114881148911490114911149211493114941149511496114971149811499115001150111502115031150411505115061150711508115091151011511115121151311514115151151611517115181151911520115211152211523115241152511526115271152811529115301153111532115331153411535115361153711538115391154011541115421154311544115451154611547115481154911550115511155211553115541155511556115571155811559115601156111562115631156411565115661156711568115691157011571115721157311574115751157611577115781157911580115811158211583115841158511586115871158811589115901159111592115931159411595115961159711598115991160011601116021160311604116051160611607116081160911610116111161211613116141161511616116171161811619116201162111622116231162411625116261162711628116291163011631116321163311634116351163611637116381163911640116411164211643116441164511646116471164811649116501165111652116531165411655116561165711658116591166011661116621166311664116651166611667116681166911670116711167211673116741167511676116771167811679116801168111682116831168411685116861168711688116891169011691116921169311694116951169611697116981169911700117011170211703117041170511706117071170811709117101171111712117131171411715117161171711718117191172011721117221172311724117251172611727117281172911730117311173211733117341173511736117371173811739117401174111742117431174411745117461174711748117491175011751117521175311754117551175611757117581175911760117611176211763117641176511766117671176811769117701177111772117731177411775117761177711778117791178011781117821178311784117851178611787117881178911790117911179211793117941179511796117971179811799118001180111802118031180411805118061180711808118091181011811118121181311814118151181611817118181181911820118211182211823118241182511826118271182811829118301183111832118331183411835118361183711838118391184011841118421184311844118451184611847118481184911850118511185211853118541185511856118571185811859118601186111862118631186411865118661186711868118691187011871118721187311874118751187611877118781187911880118811188211883118841188511886118871188811889118901189111892118931189411895118961189711898118991190011901119021190311904119051190611907119081190911910119111191211913119141191511916119171191811919119201192111922119231192411925119261192711928119291193011931119321193311934119351193611937119381193911940119411194211943119441194511946119471194811949119501195111952119531195411955119561195711958119591196011961119621196311964119651196611967119681196911970119711197211973119741197511976119771197811979119801198111982119831198411985119861198711988119891199011991119921199311994119951199611997119981199912000120011200212003120041200512006120071200812009120101201112012120131201412015120161201712018120191202012021120221202312024120251202612027120281202912030120311203212033120341203512036120371203812039120401204112042120431204412045120461204712048120491205012051120521205312054120551205612057120581205912060120611206212063120641206512066120671206812069120701207112072120731207412075120761207712078120791208012081120821208312084120851208612087120881208912090120911209212093120941209512096120971209812099121001210112102121031210412105121061210712108121091211012111121121211312114121151211612117121181211912120121211212212123121241212512126121271212812129121301213112132121331213412135121361213712138121391214012141121421214312144121451214612147121481214912150121511215212153121541215512156121571215812159121601216112162121631216412165121661216712168121691217012171121721217312174121751217612177121781217912180121811218212183121841218512186121871218812189121901219112192121931219412195121961219712198121991220012201122021220312204122051220612207122081220912210122111221212213122141221512216122171221812219122201222112222122231222412225122261222712228122291223012231122321223312234122351223612237122381223912240122411224212243122441224512246122471224812249122501225112252122531225412255122561225712258122591226012261122621226312264122651226612267122681226912270122711227212273122741227512276122771227812279122801228112282122831228412285122861228712288122891229012291122921229312294122951229612297122981229912300123011230212303123041230512306123071230812309123101231112312123131231412315123161231712318123191232012321123221232312324123251232612327123281232912330123311233212333123341233512336123371233812339123401234112342123431234412345123461234712348123491235012351123521235312354123551235612357123581235912360123611236212363123641236512366123671236812369123701237112372123731237412375123761237712378123791238012381123821238312384123851238612387123881238912390123911239212393123941239512396123971239812399124001240112402124031240412405124061240712408124091241012411124121241312414124151241612417124181241912420124211242212423124241242512426124271242812429124301243112432124331243412435124361243712438124391244012441124421244312444124451244612447124481244912450124511245212453124541245512456124571245812459124601246112462124631246412465124661246712468124691247012471124721247312474124751247612477124781247912480124811248212483124841248512486124871248812489124901249112492124931249412495124961249712498124991250012501125021250312504125051250612507125081250912510125111251212513125141251512516125171251812519125201252112522125231252412525125261252712528125291253012531125321253312534125351253612537125381253912540125411254212543125441254512546125471254812549125501255112552125531255412555125561255712558125591256012561125621256312564125651256612567125681256912570125711257212573125741257512576125771257812579125801258112582125831258412585125861258712588125891259012591125921259312594125951259612597125981259912600126011260212603126041260512606126071260812609126101261112612126131261412615126161261712618126191262012621126221262312624126251262612627126281262912630126311263212633126341263512636126371263812639126401264112642126431264412645126461264712648126491265012651126521265312654126551265612657126581265912660126611266212663126641266512666126671266812669126701267112672126731267412675126761267712678126791268012681126821268312684126851268612687126881268912690126911269212693126941269512696126971269812699127001270112702127031270412705127061270712708127091271012711127121271312714127151271612717127181271912720127211272212723127241272512726127271272812729127301273112732127331273412735127361273712738127391274012741127421274312744127451274612747127481274912750127511275212753127541275512756127571275812759127601276112762127631276412765127661276712768127691277012771127721277312774127751277612777127781277912780127811278212783127841278512786127871278812789127901279112792127931279412795127961279712798127991280012801128021280312804128051280612807128081280912810128111281212813128141281512816128171281812819128201282112822128231282412825128261282712828128291283012831128321283312834128351283612837128381283912840128411284212843128441284512846128471284812849128501285112852128531285412855128561285712858128591286012861128621286312864128651286612867128681286912870128711287212873128741287512876128771287812879128801288112882128831288412885128861288712888128891289012891128921289312894128951289612897128981289912900129011290212903129041290512906129071290812909129101291112912129131291412915129161291712918129191292012921129221292312924129251292612927129281292912930129311293212933129341293512936129371293812939129401294112942129431294412945129461294712948129491295012951129521295312954129551295612957129581295912960129611296212963129641296512966129671296812969129701297112972129731297412975129761297712978129791298012981129821298312984129851298612987129881298912990129911299212993129941299512996129971299812999130001300113002130031300413005130061300713008130091301013011130121301313014130151301613017130181301913020130211302213023130241302513026130271302813029130301303113032130331303413035130361303713038130391304013041130421304313044130451304613047130481304913050130511305213053130541305513056130571305813059130601306113062130631306413065130661306713068130691307013071130721307313074130751307613077130781307913080130811308213083130841308513086130871308813089130901309113092130931309413095130961309713098130991310013101131021310313104131051310613107131081310913110131111311213113131141311513116131171311813119131201312113122131231312413125131261312713128131291313013131131321313313134131351313613137131381313913140131411314213143131441314513146131471314813149131501315113152131531315413155131561315713158131591316013161131621316313164131651316613167131681316913170131711317213173131741317513176131771317813179131801318113182131831318413185131861318713188131891319013191131921319313194131951319613197131981319913200132011320213203132041320513206132071320813209132101321113212132131321413215132161321713218132191322013221132221322313224132251322613227132281322913230132311323213233132341323513236132371323813239132401324113242132431324413245132461324713248132491325013251132521325313254132551325613257132581325913260132611326213263132641326513266132671326813269132701327113272132731327413275132761327713278132791328013281132821328313284132851328613287132881328913290132911329213293132941329513296132971329813299133001330113302133031330413305133061330713308133091331013311133121331313314133151331613317133181331913320133211332213323133241332513326133271332813329133301333113332133331333413335133361333713338133391334013341133421334313344133451334613347133481334913350133511335213353133541335513356133571335813359133601336113362133631336413365133661336713368133691337013371133721337313374133751337613377133781337913380133811338213383133841338513386133871338813389133901339113392133931339413395133961339713398133991340013401134021340313404134051340613407134081340913410134111341213413134141341513416134171341813419134201342113422134231342413425134261342713428134291343013431134321343313434134351343613437134381343913440134411344213443134441344513446134471344813449134501345113452134531345413455134561345713458134591346013461134621346313464134651346613467134681346913470134711347213473134741347513476134771347813479134801348113482134831348413485134861348713488134891349013491134921349313494134951349613497134981349913500135011350213503135041350513506135071350813509135101351113512135131351413515135161351713518135191352013521135221352313524135251352613527135281352913530135311353213533135341353513536135371353813539135401354113542135431354413545135461354713548135491355013551135521355313554135551355613557135581355913560135611356213563135641356513566135671356813569135701357113572135731357413575135761357713578135791358013581135821358313584135851358613587135881358913590135911359213593135941359513596135971359813599136001360113602136031360413605136061360713608136091361013611136121361313614136151361613617136181361913620136211362213623136241362513626136271362813629136301363113632136331363413635136361363713638136391364013641136421364313644136451364613647136481364913650136511365213653136541365513656136571365813659136601366113662136631366413665136661366713668136691367013671136721367313674136751367613677136781367913680136811368213683136841368513686136871368813689136901369113692 |
- /* import * as THREE from 'three';
- import { Ray as Ray$1, Plane, MathUtils, EventDispatcher, Vector3, MOUSE, TOUCH, Quaternion, Spherical, Vector2 } from 'three';
- */
-
- import * as THREE from '../three.js/build/three.module.js';
- import { Ray as Ray$1, Plane, MathUtils, EventDispatcher, Vector3, MOUSE, TOUCH, Quaternion, Spherical, Vector2 } from '../three.js/build/three.module.js';
- //加
- {
-
-
- const _tables = /*@__PURE__*/ _generateTables();
-
- function _generateTables() {
- // float32 to float16 helpers
- const buffer = new ArrayBuffer( 4 );
- const floatView = new Float32Array( buffer );
- const uint32View = new Uint32Array( buffer );
- const baseTable = new Uint32Array( 512 );
- const shiftTable = new Uint32Array( 512 );
- for ( let i = 0; i < 256; ++ i ) {
- const e = i - 127;
- // very small number (0, -0)
- if ( e < - 27 ) {
- baseTable[ i ] = 0x0000;
- baseTable[ i | 0x100 ] = 0x8000;
- shiftTable[ i ] = 24;
- shiftTable[ i | 0x100 ] = 24;
- // small number (denorm)
- } else if ( e < - 14 ) {
- baseTable[ i ] = 0x0400 >> ( - e - 14 );
- baseTable[ i | 0x100 ] = ( 0x0400 >> ( - e - 14 ) ) | 0x8000;
- shiftTable[ i ] = - e - 1;
- shiftTable[ i | 0x100 ] = - e - 1;
- // normal number
- } else if ( e <= 15 ) {
- baseTable[ i ] = ( e + 15 ) << 10;
- baseTable[ i | 0x100 ] = ( ( e + 15 ) << 10 ) | 0x8000;
- shiftTable[ i ] = 13;
- shiftTable[ i | 0x100 ] = 13;
- // large number (Infinity, -Infinity)
- } else if ( e < 128 ) {
- baseTable[ i ] = 0x7c00;
- baseTable[ i | 0x100 ] = 0xfc00;
- shiftTable[ i ] = 24;
- shiftTable[ i | 0x100 ] = 24;
- // stay (NaN, Infinity, -Infinity)
- } else {
- baseTable[ i ] = 0x7c00;
- baseTable[ i | 0x100 ] = 0xfc00;
- shiftTable[ i ] = 13;
- shiftTable[ i | 0x100 ] = 13;
- }
- }
- // float16 to float32 helpers
- const mantissaTable = new Uint32Array( 2048 );
- const exponentTable = new Uint32Array( 64 );
- const offsetTable = new Uint32Array( 64 );
- for ( let i = 1; i < 1024; ++ i ) {
- let m = i << 13; // zero pad mantissa bits
- let e = 0; // zero exponent
- // normalized
- while ( ( m & 0x00800000 ) === 0 ) {
- m <<= 1;
- e -= 0x00800000; // decrement exponent
- }
- m &= ~ 0x00800000; // clear leading 1 bit
- e += 0x38800000; // adjust bias
- mantissaTable[ i ] = m | e;
- }
- for ( let i = 1024; i < 2048; ++ i ) {
- mantissaTable[ i ] = 0x38000000 + ( ( i - 1024 ) << 13 );
- }
- for ( let i = 1; i < 31; ++ i ) {
- exponentTable[ i ] = i << 23;
- }
- exponentTable[ 31 ] = 0x47800000;
- exponentTable[ 32 ] = 0x80000000;
- for ( let i = 33; i < 63; ++ i ) {
- exponentTable[ i ] = 0x80000000 + ( ( i - 32 ) << 23 );
- }
- exponentTable[ 63 ] = 0xc7800000;
- for ( let i = 1; i < 64; ++ i ) {
- if ( i !== 32 ) {
- offsetTable[ i ] = 1024;
- }
- }
- return {
- floatView: floatView,
- uint32View: uint32View,
- baseTable: baseTable,
- shiftTable: shiftTable,
- mantissaTable: mantissaTable,
- exponentTable: exponentTable,
- offsetTable: offsetTable
- };
- }
- // float32 to float16
- /* function toHalfFloat( val ) {
- if ( Math.abs( val ) > 65504 ) console.warn( 'THREE.DataUtils.toHalfFloat(): Value out of range.' );
- val = clamp( val, - 65504, 65504 );
- _tables.floatView[ 0 ] = val;
- const f = _tables.uint32View[ 0 ];
- const e = ( f >> 23 ) & 0x1ff;
- return _tables.baseTable[ e ] + ( ( f & 0x007fffff ) >> _tables.shiftTable[ e ] );
- } */
- // float16 to float32
- function fromHalfFloat( val ) {
- const m = val >> 10;
- _tables.uint32View[ 0 ] = _tables.mantissaTable[ _tables.offsetTable[ m ] + ( val & 0x3ff ) ] + _tables.exponentTable[ m ];
- return _tables.floatView[ 0 ];
- }
- /*
- THREE.DataUtils = {
- toHalfFloat: toHalfFloat,
- fromHalfFloat: fromHalfFloat,
- };
- */
- THREE.DataUtils.fromHalfFloat = fromHalfFloat
-
- }
- //---------------------------------------------------
-
- class AbortablePromise {
- static idGen = 0;
- constructor(promiseFunc, abortHandler) {
- let resolver;
- let rejecter;
- this.promise = new Promise((resolve, reject) => {
- resolver = resolve;
- rejecter = reject;
- });
- const promiseResolve = resolver.bind(this);
- const promiseReject = rejecter.bind(this);
- const resolve = (...args) => {
- promiseResolve(...args);
- };
- const reject = (error) => {
- promiseReject(error);
- };
- promiseFunc(resolve.bind(this), reject.bind(this));
- this.abortHandler = abortHandler;
- this.id = AbortablePromise.idGen++;
- }
- then(onResolve) {
- return new AbortablePromise((resolve, reject) => {
- this.promise = this.promise
- .then((...args) => {
- const onResolveResult = onResolve(...args);
- if (onResolveResult instanceof Promise || onResolveResult instanceof AbortablePromise) {
- onResolveResult.then((...args2) => {
- resolve(...args2);
- });
- } else {
- resolve(onResolveResult);
- }
- })
- .catch((error) => {
- reject(error);
- });
- }, this.abortHandler);
- }
- catch(onFail) {
- return new AbortablePromise((resolve) => {
- this.promise = this.promise.then((...args) => {
- resolve(...args);
- })
- .catch(onFail);
- }, this.abortHandler);
- }
- abort(reason) {
- if (this.abortHandler) this.abortHandler(reason);
- }
- }
- class AbortedPromiseError extends Error {
- constructor(msg) {
- super(msg);
- }
- }
- const floatToHalf = function() {
- const floatView = new Float32Array(1);
- const int32View = new Int32Array(floatView.buffer);
- return function(val) {
- floatView[0] = val;
- const x = int32View[0];
- let bits = (x >> 16) & 0x8000;
- let m = (x >> 12) & 0x07ff;
- const e = (x >> 23) & 0xff;
- if (e < 103) return bits;
- if (e > 142) {
- bits |= 0x7c00;
- bits |= ((e == 255) ? 0 : 1) && (x & 0x007fffff);
- return bits;
- }
- if (e < 113) {
- m |= 0x0800;
- bits |= (m >> (114 - e)) + ((m >> (113 - e)) & 1);
- return bits;
- }
- bits |= (( e - 112) << 10) | (m >> 1);
- bits += m & 1;
- return bits;
- };
- }();
- const uintEncodedFloat = function() {
- const floatView = new Float32Array(1);
- const int32View = new Int32Array(floatView.buffer);
- return function(f) {
- floatView[0] = f;
- return int32View[0];
- };
- }();
- const rgbaToInteger = function(r, g, b, a) {
- return r + (g << 8) + (b << 16) + (a << 24);
- };
- const rgbaArrayToInteger = function(arr, offset) {
- return arr[offset] + (arr[offset + 1] << 8) + (arr[offset + 2] << 16) + (arr[offset + 3] << 24);
- };
- const fetchWithProgress = function(path, onProgress, saveChunks = true) {
- const abortController = new AbortController();
- const signal = abortController.signal;
- let aborted = false;
- const abortHandler = (reason) => {
- abortController.abort(new AbortedPromiseError(reason));
- aborted = true;
- };
- return new AbortablePromise((resolve, reject) => {
- fetch(path, { signal })
- .then(async (data) => {
- const reader = data.body.getReader();
- let bytesDownloaded = 0;
- let _fileSize = data.headers.get('Content-Length');
- let fileSize = _fileSize ? parseInt(_fileSize) : undefined;
- const chunks = [];
- while (!aborted) {
- try {
- const { value: chunk, done } = await reader.read();
- if (done) {
- if (onProgress) {
- onProgress(100, '100%', chunk, fileSize);
- }
- if (saveChunks) {
- const buffer = new Blob(chunks).arrayBuffer();
- resolve(buffer);
- } else {
- resolve();
- }
- break;
- }
- bytesDownloaded += chunk.length;
- let percent;
- let percentLabel;
- if (fileSize !== undefined) {
- percent = bytesDownloaded / fileSize * 100;
- percentLabel = `${percent.toFixed(2)}%`;
- }
- if (saveChunks) chunks.push(chunk);
- if (onProgress) {
- const cancelSaveChucnks = onProgress(percent, percentLabel, chunk, fileSize);
- if (cancelSaveChucnks) saveChunks = false;
- }
- } catch (error) {
- reject(error);
- break;
- }
- }
- })
- .catch((error) => {
- reject(error);
- });
- }, abortHandler);
- };
- const clamp = function(val, min, max) {
- return Math.max(Math.min(val, max), min);
- };
- const getCurrentTime = function() {
- return performance.now() / 1000;
- };
- const disposeAllMeshes = (object3D) => {
- if (object3D.geometry) {
- object3D.geometry.dispose();
- object3D.geometry = null;
- }
- if (object3D.material) {
- object3D.material.dispose();
- object3D.material = null;
- }
- if (object3D.children) {
- for (let child of object3D.children) {
- disposeAllMeshes(child);
- }
- }
- };
- const delayedExecute = (func, fast) => {
- return new Promise((resolve) => {
- window.setTimeout(() => {
- resolve(func());
- }, fast ? 1 : 50);
- });
- };
- const getSphericalHarmonicsComponentCountForDegree = (sphericalHarmonicsDegree = 0) => {
- switch (sphericalHarmonicsDegree) {
- case 1:
- return 9;
- case 2:
- return 24;
- }
- return 0;
- };
- const nativePromiseWithExtractedComponents = () => {
- let resolver;
- let rejecter;
- const promise = new Promise((resolve, reject) => {
- resolver = resolve;
- rejecter = reject;
- });
- return {
- 'promise': promise,
- 'resolve': resolver,
- 'reject': rejecter
- };
- };
- const abortablePromiseWithExtractedComponents = (abortHandler) => {
- let resolver;
- let rejecter;
- if (!abortHandler) {
- abortHandler = () => {};
- }
- const promise = new AbortablePromise((resolve, reject) => {
- resolver = resolve;
- rejecter = reject;
- }, abortHandler);
- return {
- 'promise': promise,
- 'resolve': resolver,
- 'reject': rejecter
- };
- };
- class Semver {
- constructor(major, minor, patch) {
- this.major = major;
- this.minor = minor;
- this.patch = patch;
- }
- toString() {
- return `${this.major}_${this.minor}_${this.patch}`;
- }
- }
- function isIOS() {
- const ua = navigator.userAgent;
- return ua.indexOf('iPhone') > 0 || ua.indexOf('iPad') > 0;
- }
- function getIOSSemever() {
- if (isIOS()) {
- const extract = navigator.userAgent.match(/OS (\d+)_(\d+)_?(\d+)?/);
- return new Semver(
- parseInt(extract[1] || 0, 10),
- parseInt(extract[2] || 0, 10),
- parseInt(extract[3] || 0, 10)
- );
- } else {
- return null; // or [0,0,0]
- }
- }
- const BASE_COMPONENT_COUNT = 14;
- class UncompressedSplatArray {
- static OFFSET = {
- X: 0,
- Y: 1,
- Z: 2,
- SCALE0: 3,
- SCALE1: 4,
- SCALE2: 5,
- ROTATION0: 6,
- ROTATION1: 7,
- ROTATION2: 8,
- ROTATION3: 9,
- FDC0: 10,
- FDC1: 11,
- FDC2: 12,
- OPACITY: 13,
- FRC0: 14,
- FRC1: 15,
- FRC2: 16,
- FRC3: 17,
- FRC4: 18,
- FRC5: 19,
- FRC6: 20,
- FRC7: 21,
- FRC8: 22,
- FRC9: 23,
- FRC10: 24,
- FRC11: 25,
- FRC12: 26,
- FRC13: 27,
- FRC14: 28,
- FRC15: 29,
- FRC16: 30,
- FRC17: 31,
- FRC18: 32,
- FRC19: 33,
- FRC20: 34,
- FRC21: 35,
- FRC22: 36,
- FRC23: 37
- };
- constructor(sphericalHarmonicsDegree = 0) {
- this.sphericalHarmonicsDegree = sphericalHarmonicsDegree;
- this.sphericalHarmonicsCount = getSphericalHarmonicsComponentCountForDegree(this.sphericalHarmonicsDegree);
- this.componentCount = this.sphericalHarmonicsCount + BASE_COMPONENT_COUNT;
- this.defaultSphericalHarmonics = new Array(this.sphericalHarmonicsCount).fill(0);
- this.splats = [];
- this.splatCount = 0;
- }
- static createSplat(sphericalHarmonicsDegree = 0) {
- const baseSplat = [0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0];
- let shEntries = getSphericalHarmonicsComponentCountForDegree(sphericalHarmonicsDegree);
- for (let i = 0; i < shEntries; i++) baseSplat.push(0);
- return baseSplat;
- }
- addSplat(splat) {
- this.splats.push(splat);
- this.splatCount++;
- }
- getSplat(index) {
- return this.splats[index];
- }
- addDefaultSplat() {
- const newSplat = UncompressedSplatArray.createSplat(this.sphericalHarmonicsDegree);
- this.addSplat(newSplat);
- return newSplat;
- }
- addSplatFromComonents(x, y, z, scale0, scale1, scale2, rot0, rot1, rot2, rot3, r, g, b, opacity, ...rest) {
- const newSplat = [x, y, z, scale0, scale1, scale2, rot0, rot1, rot2, rot3, r, g, b, opacity, ...this.defaultSphericalHarmonics];
- for (let i = 0; i < rest.length && i < this.sphericalHarmonicsCount; i++) {
- newSplat[i] = rest[i];
- }
- this.addSplat(newSplat);
- return newSplat;
- }
- addSplatFromArray(src, srcIndex) {
- const srcSplat = src.splats[srcIndex];
- const newSplat = UncompressedSplatArray.createSplat(this.sphericalHarmonicsDegree);
- for (let i = 0; i < this.componentCount && i < srcSplat.length; i++) {
- newSplat[i] = srcSplat[i];
- }
- this.addSplat(newSplat);
- }
- }
- class Constants {
- static DepthMapRange = 1 << 16;
- static MemoryPageSize = 65536;
- static BytesPerFloat = 4;
- static BytesPerInt = 4;
- static MaxScenes = 32;
- static ProgressiveLoadSectionSize = 262144;
- static ProgressiveLoadSectionDelayDuration = 15;
- static SphericalHarmonics8BitCompressionRange = 3;
- }
- const DefaultSphericalHarmonics8BitCompressionRange = Constants.SphericalHarmonics8BitCompressionRange;
- const DefaultSphericalHarmonics8BitCompressionHalfRange = DefaultSphericalHarmonics8BitCompressionRange / 2.0;
- const toHalfFloat = THREE.DataUtils.toHalfFloat.bind(THREE.DataUtils);
- const fromHalfFloat$1 = THREE.DataUtils.fromHalfFloat.bind(THREE.DataUtils);
- const toUncompressedFloat = (f, compressionLevel, isSH = false, range8BitMin, range8BitMax) => {
- if (compressionLevel === 0) {
- return f;
- } else if (compressionLevel === 1 || compressionLevel === 2 && !isSH) {
- return THREE.DataUtils.fromHalfFloat(f);
- } else if (compressionLevel === 2) {
- return fromUint8(f, range8BitMin, range8BitMax);
- }
- };
- const toUint8 = (v, rangeMin, rangeMax) => {
- v = clamp(v, rangeMin, rangeMax);
- const range = (rangeMax - rangeMin);
- return clamp(Math.floor((v - rangeMin) / range * 255), 0, 255);
- };
- const fromUint8 = (v, rangeMin, rangeMax) => {
- const range = (rangeMax - rangeMin);
- return (v / 255 * range + rangeMin);
- };
-
- const fromHalfFloatToUint8 = (v, rangeMin, rangeMax) => {
- return toUint8(fromHalfFloat$1(v, rangeMin, rangeMax));
- };
- const fromUint8ToHalfFloat = (v, rangeMin, rangeMax) => {
- return toHalfFloat(fromUint8(v, rangeMin, rangeMax));
- };
- const dataViewFloatForCompressionLevel = (dataView, floatIndex, compressionLevel, isSH = false) => {
- if (compressionLevel === 0) {
- return dataView.getFloat32(floatIndex * 4, true);
- } else if (compressionLevel === 1 || compressionLevel === 2 && !isSH) {
- return dataView.getUint16(floatIndex * 2, true);
- } else {
- return dataView.getUint8(floatIndex, true);
- }
- };
- const convertBetweenCompressionLevels = function() {
- const noop = (v) => v;
- return function(val, fromLevel, toLevel, isSH = false) {
- if (fromLevel === toLevel) return val;
- let outputConversionFunc = noop;
- if (fromLevel === 2 && isSH) {
- if (toLevel === 1) outputConversionFunc = fromUint8ToHalfFloat;
- else if (toLevel == 0) {
- outputConversionFunc = fromUint8;
- }
- } else if (fromLevel === 2 || fromLevel === 1) {
- if (toLevel === 0) outputConversionFunc = fromHalfFloat$1;
- else if (toLevel == 2) {
- if (!isSH) outputConversionFunc = noop;
- else outputConversionFunc = fromHalfFloatToUint8;
- }
- } else if (fromLevel === 0) {
- if (toLevel === 1) outputConversionFunc = toHalfFloat;
- else if (toLevel == 2) {
- if (!isSH) outputConversionFunc = toHalfFloat;
- else outputConversionFunc = toUint8;
- }
- }
- return outputConversionFunc(val);
- };
- }();
- const copyBetweenBuffers = (srcBuffer, srcOffset, destBuffer, destOffset, byteCount = 0) => {
- const src = new Uint8Array(srcBuffer, srcOffset);
- const dest = new Uint8Array(destBuffer, destOffset);
- for (let i = 0; i < byteCount; i++) {
- dest[i] = src[i];
- }
- };
- /**
- * SplatBuffer: Container for splat data from a single scene/file and capable of (mediocre) compression.
- */
- class SplatBuffer {
- static CurrentMajorVersion = 0;
- static CurrentMinorVersion = 1;
- static CenterComponentCount = 3;
- static ScaleComponentCount = 3;
- static RotationComponentCount = 4;
- static ColorComponentCount = 4;
- static CovarianceComponentCount = 6;
- static SplatScaleOffsetFloat = 3;
- static SplatRotationOffsetFloat = 6;
- static CompressionLevels = {
- 0: {
- BytesPerCenter: 12,
- BytesPerScale: 12,
- BytesPerRotation: 16,
- BytesPerColor: 4,
- ScaleOffsetBytes: 12,
- RotationffsetBytes: 24,
- ColorOffsetBytes: 40,
- SphericalHarmonicsOffsetBytes: 44,
- ScaleRange: 1,
- BytesPerSphericalHarmonicsComponent: 4,
- SphericalHarmonicsOffsetFloat: 11,
- SphericalHarmonicsDegrees: {
- 0: { BytesPerSplat: 44 },
- 1: { BytesPerSplat: 80 },
- 2: { BytesPerSplat: 140 }
- },
- },
- 1: {
- BytesPerCenter: 6,
- BytesPerScale: 6,
- BytesPerRotation: 8,
- BytesPerColor: 4,
- ScaleOffsetBytes: 6,
- RotationffsetBytes: 12,
- ColorOffsetBytes: 20,
- SphericalHarmonicsOffsetBytes: 24,
- ScaleRange: 32767,
- BytesPerSphericalHarmonicsComponent: 2,
- SphericalHarmonicsOffsetFloat: 12,
- SphericalHarmonicsDegrees: {
- 0: { BytesPerSplat: 24 },
- 1: { BytesPerSplat: 42 },
- 2: { BytesPerSplat: 72 }
- },
- },
- 2: {
- BytesPerCenter: 6,
- BytesPerScale: 6,
- BytesPerRotation: 8,
- BytesPerColor: 4,
- ScaleOffsetBytes: 6,
- RotationffsetBytes: 12,
- ColorOffsetBytes: 20,
- SphericalHarmonicsOffsetBytes: 24,
- ScaleRange: 32767,
- BytesPerSphericalHarmonicsComponent: 1,
- SphericalHarmonicsOffsetFloat: 12,
- SphericalHarmonicsDegrees: {
- 0: { BytesPerSplat: 24 },
- 1: { BytesPerSplat: 33 },
- 2: { BytesPerSplat: 48 }
- },
- }
- };
- static CovarianceSizeFloats = 6;
- static HeaderSizeBytes = 4096;
- static SectionHeaderSizeBytes = 1024;
- static BucketStorageSizeBytes = 12;
- static BucketStorageSizeFloats = 3;
- static BucketBlockSize = 5.0;
- static BucketSize = 256;
- constructor(bufferData, secLoadedCountsToMax = true) {
- this.constructFromBuffer(bufferData, secLoadedCountsToMax);
- }
- getSplatCount() {
- return this.splatCount;
- }
- getMaxSplatCount() {
- return this.maxSplatCount;
- }
- getMinSphericalHarmonicsDegree() {
- let minSphericalHarmonicsDegree = 0;
- for (let i = 0; i < this.sections.length; i++) {
- const section = this.sections[i];
- if (i === 0 || section.sphericalHarmonicsDegree < minSphericalHarmonicsDegree) {
- minSphericalHarmonicsDegree = section.sphericalHarmonicsDegree;
- }
- }
- return minSphericalHarmonicsDegree;
- }
- getBucketIndex(section, localSplatIndex) {
- let bucketIndex;
- const maxSplatIndexInFullBuckets = section.fullBucketCount * section.bucketSize;
- if (localSplatIndex < maxSplatIndexInFullBuckets) {
- bucketIndex = Math.floor(localSplatIndex / section.bucketSize);
- } else {
- let bucketSplatIndex = maxSplatIndexInFullBuckets;
- bucketIndex = section.fullBucketCount;
- let partiallyFullBucketIndex = 0;
- while (bucketSplatIndex < section.splatCount) {
- let currentPartiallyFilledBucketSize = section.partiallyFilledBucketLengths[partiallyFullBucketIndex];
- if (localSplatIndex >= bucketSplatIndex && localSplatIndex < bucketSplatIndex + currentPartiallyFilledBucketSize) {
- break;
- }
- bucketSplatIndex += currentPartiallyFilledBucketSize;
- bucketIndex++;
- partiallyFullBucketIndex++;
- }
- }
- return bucketIndex;
- }
- getSplatCenter(globalSplatIndex, outCenter, transform) {
- const sectionIndex = this.globalSplatIndexToSectionMap[globalSplatIndex];
- const section = this.sections[sectionIndex];
- const localSplatIndex = globalSplatIndex - section.splatCountOffset;
- const srcSplatCentersBase = section.bytesPerSplat * localSplatIndex;
- const dataView = new DataView(this.bufferData, section.dataBase + srcSplatCentersBase);
- const x = dataViewFloatForCompressionLevel(dataView, 0, this.compressionLevel);
- const y = dataViewFloatForCompressionLevel(dataView, 1, this.compressionLevel);
- const z = dataViewFloatForCompressionLevel(dataView, 2, this.compressionLevel);
- if (this.compressionLevel >= 1) {
- const bucketIndex = this.getBucketIndex(section, localSplatIndex);
- const bucketBase = bucketIndex * SplatBuffer.BucketStorageSizeFloats;
- const sf = section.compressionScaleFactor;
- const sr = section.compressionScaleRange;
- outCenter.x = (x - sr) * sf + section.bucketArray[bucketBase];
- outCenter.y = (y - sr) * sf + section.bucketArray[bucketBase + 1];
- outCenter.z = (z - sr) * sf + section.bucketArray[bucketBase + 2];
- } else {
- outCenter.x = x;
- outCenter.y = y;
- outCenter.z = z;
- }
- if (transform) outCenter.applyMatrix4(transform);
- }
- getSplatScaleAndRotation = function() {
- const scaleMatrix = new THREE.Matrix4();
- const rotationMatrix = new THREE.Matrix4();
- const tempMatrix = new THREE.Matrix4();
- const tempPosition = new THREE.Vector3();
- const scale = new THREE.Vector3();
- const rotation = new THREE.Quaternion();
- return function(index, outScale, outRotation, transform, scaleOverride) {
- const sectionIndex = this.globalSplatIndexToSectionMap[index];
- const section = this.sections[sectionIndex];
- const localSplatIndex = index - section.splatCountOffset;
- const srcSplatScalesBase = section.bytesPerSplat * localSplatIndex +
- SplatBuffer.CompressionLevels[this.compressionLevel].ScaleOffsetBytes;
- const dataView = new DataView(this.bufferData, section.dataBase + srcSplatScalesBase);
- scale.set(toUncompressedFloat(dataViewFloatForCompressionLevel(dataView, 0, this.compressionLevel), this.compressionLevel),
- toUncompressedFloat(dataViewFloatForCompressionLevel(dataView, 1, this.compressionLevel), this.compressionLevel),
- toUncompressedFloat(dataViewFloatForCompressionLevel(dataView, 2, this.compressionLevel), this.compressionLevel));
- if (scaleOverride) {
- if (scaleOverride.x !== undefined) scale.x = scaleOverride.x;
- if (scaleOverride.y !== undefined) scale.y = scaleOverride.y;
- if (scaleOverride.z !== undefined) scale.z = scaleOverride.z;
- }
- rotation.set(toUncompressedFloat(dataViewFloatForCompressionLevel(dataView, 4, this.compressionLevel), this.compressionLevel),
- toUncompressedFloat(dataViewFloatForCompressionLevel(dataView, 5, this.compressionLevel), this.compressionLevel),
- toUncompressedFloat(dataViewFloatForCompressionLevel(dataView, 6, this.compressionLevel), this.compressionLevel),
- toUncompressedFloat(dataViewFloatForCompressionLevel(dataView, 3, this.compressionLevel), this.compressionLevel));
- if (transform) {
- scaleMatrix.makeScale(scale.x, scale.y, scale.z);
- rotationMatrix.makeRotationFromQuaternion(rotation);
- tempMatrix.copy(scaleMatrix).multiply(rotationMatrix).multiply(transform);
- tempMatrix.decompose(tempPosition, outRotation, outScale);
- } else {
- outScale.copy(scale);
- outRotation.copy(rotation);
- }
- };
- }();
- getSplatColor(globalSplatIndex, outColor) {
- const sectionIndex = this.globalSplatIndexToSectionMap[globalSplatIndex];
- const section = this.sections[sectionIndex];
- const localSplatIndex = globalSplatIndex - section.splatCountOffset;
- const srcSplatColorsBase = section.bytesPerSplat * localSplatIndex +
- SplatBuffer.CompressionLevels[this.compressionLevel].ColorOffsetBytes;
- const splatColorsArray = new Uint8Array(this.bufferData, section.dataBase + srcSplatColorsBase, 4);
- outColor.set(splatColorsArray[0], splatColorsArray[1],
- splatColorsArray[2], splatColorsArray[3]);
- }
- fillSplatCenterArray(outCenterArray, transform, srcFrom, srcTo, destFrom) {
- const splatCount = this.splatCount;
- srcFrom = srcFrom || 0;
- srcTo = srcTo || splatCount - 1;
- if (destFrom === undefined) destFrom = srcFrom;
- const center = new THREE.Vector3();
- for (let i = srcFrom; i <= srcTo; i++) {
- const sectionIndex = this.globalSplatIndexToSectionMap[i];
- const section = this.sections[sectionIndex];
- const localSplatIndex = i - section.splatCountOffset;
- const centerDestBase = (i - srcFrom + destFrom) * SplatBuffer.CenterComponentCount;
- const srcSplatCentersBase = section.bytesPerSplat * localSplatIndex;
- const dataView = new DataView(this.bufferData, section.dataBase + srcSplatCentersBase);
- const x = dataViewFloatForCompressionLevel(dataView, 0, this.compressionLevel);
- const y = dataViewFloatForCompressionLevel(dataView, 1, this.compressionLevel);
- const z = dataViewFloatForCompressionLevel(dataView, 2, this.compressionLevel);
- if (this.compressionLevel >= 1) {
- const bucketIndex = this.getBucketIndex(section, localSplatIndex);
- const bucketBase = bucketIndex * SplatBuffer.BucketStorageSizeFloats;
- const sf = section.compressionScaleFactor;
- const sr = section.compressionScaleRange;
- center.x = (x - sr) * sf + section.bucketArray[bucketBase];
- center.y = (y - sr) * sf + section.bucketArray[bucketBase + 1];
- center.z = (z - sr) * sf + section.bucketArray[bucketBase + 2];
- } else {
- center.x = x;
- center.y = y;
- center.z = z;
- }
- if (transform) {
- center.applyMatrix4(transform);
- }
- outCenterArray[centerDestBase] = center.x;
- outCenterArray[centerDestBase + 1] = center.y;
- outCenterArray[centerDestBase + 2] = center.z;
- }
- }
- fillSplatScaleRotationArray = function() {
- const scaleMatrix = new THREE.Matrix4();
- const rotationMatrix = new THREE.Matrix4();
- const tempMatrix = new THREE.Matrix4();
- const scale = new THREE.Vector3();
- const rotation = new THREE.Quaternion();
- const tempPosition = new THREE.Vector3();
- const ensurePositiveW = (quaternion) => {
- const flip = quaternion.w < 0 ? -1 : 1;
- quaternion.x *= flip;
- quaternion.y *= flip;
- quaternion.z *= flip;
- quaternion.w *= flip;
- };
- return function(outScaleArray, outRotationArray, transform, srcFrom, srcTo, destFrom,
- desiredOutputCompressionLevel, scaleOverride) {
- const splatCount = this.splatCount;
- srcFrom = srcFrom || 0;
- srcTo = srcTo || splatCount - 1;
- if (destFrom === undefined) destFrom = srcFrom;
- const outputConversion = (value, srcCompressionLevel) => {
- if (srcCompressionLevel === undefined) srcCompressionLevel = this.compressionLevel;
- return convertBetweenCompressionLevels(value, srcCompressionLevel, desiredOutputCompressionLevel);
- };
- for (let i = srcFrom; i <= srcTo; i++) {
- const sectionIndex = this.globalSplatIndexToSectionMap[i];
- const section = this.sections[sectionIndex];
- const localSplatIndex = i - section.splatCountOffset;
- const srcSplatScalesBase = section.bytesPerSplat * localSplatIndex +
- SplatBuffer.CompressionLevels[this.compressionLevel].ScaleOffsetBytes;
- const scaleDestBase = (i - srcFrom + destFrom) * SplatBuffer.ScaleComponentCount;
- const rotationDestBase = (i - srcFrom + destFrom) * SplatBuffer.RotationComponentCount;
- const dataView = new DataView(this.bufferData, section.dataBase + srcSplatScalesBase);
- const srcScaleX = (scaleOverride && scaleOverride.x !== undefined) ? scaleOverride.x :
- dataViewFloatForCompressionLevel(dataView, 0, this.compressionLevel);
- const srcScaleY = (scaleOverride && scaleOverride.y !== undefined) ? scaleOverride.y :
- dataViewFloatForCompressionLevel(dataView, 1, this.compressionLevel);
- const srcScaleZ = (scaleOverride && scaleOverride.z !== undefined) ? scaleOverride.z :
- dataViewFloatForCompressionLevel(dataView, 2, this.compressionLevel);
- const srcRotationW = dataViewFloatForCompressionLevel(dataView, 3, this.compressionLevel);
- const srcRotationX = dataViewFloatForCompressionLevel(dataView, 4, this.compressionLevel);
- const srcRotationY = dataViewFloatForCompressionLevel(dataView, 5, this.compressionLevel);
- const srcRotationZ = dataViewFloatForCompressionLevel(dataView, 6, this.compressionLevel);
- scale.set(toUncompressedFloat(srcScaleX, this.compressionLevel),
- toUncompressedFloat(srcScaleY, this.compressionLevel),
- toUncompressedFloat(srcScaleZ, this.compressionLevel));
- rotation.set(toUncompressedFloat(srcRotationX, this.compressionLevel),
- toUncompressedFloat(srcRotationY, this.compressionLevel),
- toUncompressedFloat(srcRotationZ, this.compressionLevel),
- toUncompressedFloat(srcRotationW, this.compressionLevel)).normalize();
- if (transform) {
- tempPosition.set(0, 0, 0);
- scaleMatrix.makeScale(scale.x, scale.y, scale.z);
- rotationMatrix.makeRotationFromQuaternion(rotation);
- tempMatrix.identity().premultiply(scaleMatrix).premultiply(rotationMatrix);
- tempMatrix.premultiply(transform);
- tempMatrix.decompose(tempPosition, rotation, scale);
- rotation.normalize();
- }
- ensurePositiveW(rotation);
- if (outScaleArray) {
- outScaleArray[scaleDestBase] = outputConversion(scale.x, 0);
- outScaleArray[scaleDestBase + 1] = outputConversion(scale.y, 0);
- outScaleArray[scaleDestBase + 2] = outputConversion(scale.z, 0);
- }
- if (outRotationArray) {
- outRotationArray[rotationDestBase] = outputConversion(rotation.x, 0);
- outRotationArray[rotationDestBase + 1] = outputConversion(rotation.y, 0);
- outRotationArray[rotationDestBase + 2] = outputConversion(rotation.z, 0);
- outRotationArray[rotationDestBase + 3] = outputConversion(rotation.w, 0);
- }
- }
- };
- }();
- static computeCovariance = function() {
- const tempMatrix4 = new THREE.Matrix4();
- const scaleMatrix = new THREE.Matrix3();
- const rotationMatrix = new THREE.Matrix3();
- const covarianceMatrix = new THREE.Matrix3();
- const transformedCovariance = new THREE.Matrix3();
- const transform3x3 = new THREE.Matrix3();
- const transform3x3Transpose = new THREE.Matrix3();
- return function(scale, rotation, transform, outCovariance, outOffset = 0, desiredOutputCompressionLevel) {
- tempMatrix4.makeScale(scale.x, scale.y, scale.z);
- scaleMatrix.setFromMatrix4(tempMatrix4);
- tempMatrix4.makeRotationFromQuaternion(rotation);
- rotationMatrix.setFromMatrix4(tempMatrix4);
- covarianceMatrix.copy(rotationMatrix).multiply(scaleMatrix);
- transformedCovariance.copy(covarianceMatrix).transpose().premultiply(covarianceMatrix);
- if (transform) {
- transform3x3.setFromMatrix4(transform);
- transform3x3Transpose.copy(transform3x3).transpose();
- transformedCovariance.multiply(transform3x3Transpose);
- transformedCovariance.premultiply(transform3x3);
- }
- if (desiredOutputCompressionLevel >= 1) {
- outCovariance[outOffset] = toHalfFloat(transformedCovariance.elements[0]);
- outCovariance[outOffset + 1] = toHalfFloat(transformedCovariance.elements[3]);
- outCovariance[outOffset + 2] = toHalfFloat(transformedCovariance.elements[6]);
- outCovariance[outOffset + 3] = toHalfFloat(transformedCovariance.elements[4]);
- outCovariance[outOffset + 4] = toHalfFloat(transformedCovariance.elements[7]);
- outCovariance[outOffset + 5] = toHalfFloat(transformedCovariance.elements[8]);
- } else {
- outCovariance[outOffset] = transformedCovariance.elements[0];
- outCovariance[outOffset + 1] = transformedCovariance.elements[3];
- outCovariance[outOffset + 2] = transformedCovariance.elements[6];
- outCovariance[outOffset + 3] = transformedCovariance.elements[4];
- outCovariance[outOffset + 4] = transformedCovariance.elements[7];
- outCovariance[outOffset + 5] = transformedCovariance.elements[8];
- }
- };
- }();
- fillSplatCovarianceArray(covarianceArray, transform, srcFrom, srcTo, destFrom, desiredOutputCompressionLevel) {
- const splatCount = this.splatCount;
- const scale = new THREE.Vector3();
- const rotation = new THREE.Quaternion();
- srcFrom = srcFrom || 0;
- srcTo = srcTo || splatCount - 1;
- if (destFrom === undefined) destFrom = srcFrom;
- for (let i = srcFrom; i <= srcTo; i++) {
- const sectionIndex = this.globalSplatIndexToSectionMap[i];
- const section = this.sections[sectionIndex];
- const localSplatIndex = i - section.splatCountOffset;
- const covarianceDestBase = (i - srcFrom + destFrom) * SplatBuffer.CovarianceComponentCount;
- const srcSplatScalesBase = section.bytesPerSplat * localSplatIndex +
- SplatBuffer.CompressionLevels[this.compressionLevel].ScaleOffsetBytes;
- const dataView = new DataView(this.bufferData, section.dataBase + srcSplatScalesBase);
- scale.set(toUncompressedFloat(dataViewFloatForCompressionLevel(dataView, 0, this.compressionLevel), this.compressionLevel),
- toUncompressedFloat(dataViewFloatForCompressionLevel(dataView, 1, this.compressionLevel), this.compressionLevel),
- toUncompressedFloat(dataViewFloatForCompressionLevel(dataView, 2, this.compressionLevel), this.compressionLevel));
- rotation.set(toUncompressedFloat(dataViewFloatForCompressionLevel(dataView, 4, this.compressionLevel), this.compressionLevel),
- toUncompressedFloat(dataViewFloatForCompressionLevel(dataView, 5, this.compressionLevel), this.compressionLevel),
- toUncompressedFloat(dataViewFloatForCompressionLevel(dataView, 6, this.compressionLevel), this.compressionLevel),
- toUncompressedFloat(dataViewFloatForCompressionLevel(dataView, 3, this.compressionLevel), this.compressionLevel));
- SplatBuffer.computeCovariance(scale, rotation, transform, covarianceArray, covarianceDestBase, desiredOutputCompressionLevel);
- }
- }
- fillSplatColorArray(outColorArray, minimumAlpha, srcFrom, srcTo, destFrom) {
- const splatCount = this.splatCount;
- srcFrom = srcFrom || 0;
- srcTo = srcTo || splatCount - 1;
- if (destFrom === undefined) destFrom = srcFrom;
- for (let i = srcFrom; i <= srcTo; i++) {
- const sectionIndex = this.globalSplatIndexToSectionMap[i];
- const section = this.sections[sectionIndex];
- const localSplatIndex = i - section.splatCountOffset;
- const colorDestBase = (i - srcFrom + destFrom) * SplatBuffer.ColorComponentCount;
- const srcSplatColorsBase = section.bytesPerSplat * localSplatIndex +
- SplatBuffer.CompressionLevels[this.compressionLevel].ColorOffsetBytes;
- const dataView = new Uint8Array(this.bufferData, section.dataBase + srcSplatColorsBase);
- let alpha = dataView[3];
- alpha = (alpha >= minimumAlpha) ? alpha : 0;
- outColorArray[colorDestBase] = dataView[0];
- outColorArray[colorDestBase + 1] = dataView[1];
- outColorArray[colorDestBase + 2] = dataView[2];
- outColorArray[colorDestBase + 3] = alpha;
- }
- }
- fillSphericalHarmonicsArray = function() {
- const sphericalHarmonicVectors = [];
- for (let i = 0; i < 15; i++) {
- sphericalHarmonicVectors[i] = new THREE.Vector3();
- }
- const tempMatrix3 = new THREE.Matrix3();
- const tempMatrix4 = new THREE.Matrix4();
- const tempTranslation = new THREE.Vector3();
- const tempScale = new THREE.Vector3();
- const tempRotation = new THREE.Quaternion();
- const sh11 = [];
- const sh12 = [];
- const sh13 = [];
- const sh21 = [];
- const sh22 = [];
- const sh23 = [];
- const sh24 = [];
- const sh25 = [];
- const shIn1 = [];
- const shIn2 = [];
- const shIn3 = [];
- const shIn4 = [];
- const shIn5 = [];
- const shOut1 = [];
- const shOut2 = [];
- const shOut3 = [];
- const shOut4 = [];
- const shOut5 = [];
- const noop = (v) => v;
- const set3 = (array, val1, val2, val3) => {
- array[0] = val1;
- array[1] = val2;
- array[2] = val3;
- };
- const set3FromArray = (array, srcDestView, stride, srcBase, compressionLevel) => {
- array[0] = dataViewFloatForCompressionLevel(srcDestView, srcBase, compressionLevel, true);
- array[1] = dataViewFloatForCompressionLevel(srcDestView, srcBase + stride, compressionLevel, true);
- array[2] = dataViewFloatForCompressionLevel(srcDestView, srcBase + stride + stride, compressionLevel, true);
- };
- const copy3 = (srcArray, destArray) => {
- destArray[0] = srcArray[0];
- destArray[1] = srcArray[1];
- destArray[2] = srcArray[2];
- };
- const setOutput3 = (srcArray, destArray, destBase, conversionFunc) => {
- destArray[destBase] = conversionFunc(srcArray[0]);
- destArray[destBase + 1] = conversionFunc(srcArray[1]);
- destArray[destBase + 2] = conversionFunc(srcArray[2]);
- };
- const toUncompressedFloatArray3 = (src, dest, compressionLevel, range8BitMin, range8BitMax) => {
- dest[0] = toUncompressedFloat(src[0], compressionLevel, true, range8BitMin, range8BitMax);
- dest[1] = toUncompressedFloat(src[1], compressionLevel, true, range8BitMin, range8BitMax);
- dest[2] = toUncompressedFloat(src[2], compressionLevel, true, range8BitMin, range8BitMax);
- return dest;
- };
- return function(outSphericalHarmonicsArray, outSphericalHarmonicsDegree, transform,
- srcFrom, srcTo, destFrom, desiredOutputCompressionLevel) {
- const splatCount = this.splatCount;
- srcFrom = srcFrom || 0;
- srcTo = srcTo || splatCount - 1;
- if (destFrom === undefined) destFrom = srcFrom;
- if (transform && outSphericalHarmonicsDegree >= 1) {
- tempMatrix4.copy(transform);
- tempMatrix4.decompose(tempTranslation, tempRotation, tempScale);
- tempRotation.normalize();
- tempMatrix4.makeRotationFromQuaternion(tempRotation);
- tempMatrix3.setFromMatrix4(tempMatrix4);
- set3(sh11, tempMatrix3.elements[4], -tempMatrix3.elements[7], tempMatrix3.elements[1]);
- set3(sh12, -tempMatrix3.elements[5], tempMatrix3.elements[8], -tempMatrix3.elements[2]);
- set3(sh13, tempMatrix3.elements[3], -tempMatrix3.elements[6], tempMatrix3.elements[0]);
- }
- const localFromHalfFloatToUint8 = (v) => {
- return fromHalfFloatToUint8(v, this.minSphericalHarmonicsCoeff, this.maxSphericalHarmonicsCoeff);
- };
- const localToUint8 = (v) => {
- return toUint8(v, this.minSphericalHarmonicsCoeff, this.maxSphericalHarmonicsCoeff);
- };
- for (let i = srcFrom; i <= srcTo; i++) {
- const sectionIndex = this.globalSplatIndexToSectionMap[i];
- const section = this.sections[sectionIndex];
- outSphericalHarmonicsDegree = Math.min(outSphericalHarmonicsDegree, section.sphericalHarmonicsDegree);
- const outSphericalHarmonicsComponentsCount = getSphericalHarmonicsComponentCountForDegree(outSphericalHarmonicsDegree);
- const localSplatIndex = i - section.splatCountOffset;
- const srcSplatSHBase = section.bytesPerSplat * localSplatIndex +
- SplatBuffer.CompressionLevels[this.compressionLevel].SphericalHarmonicsOffsetBytes;
- const dataView = new DataView(this.bufferData, section.dataBase + srcSplatSHBase);
- const shDestBase = (i - srcFrom + destFrom) * outSphericalHarmonicsComponentsCount;
- let compressionLevelForOutputConversion = transform ? 0 : this.compressionLevel;
- let outputConversionFunc = noop;
- if (compressionLevelForOutputConversion !== desiredOutputCompressionLevel) {
- if (compressionLevelForOutputConversion === 1) {
- if (desiredOutputCompressionLevel === 0) outputConversionFunc = fromHalfFloat$1;
- else if (desiredOutputCompressionLevel == 2) outputConversionFunc = localFromHalfFloatToUint8;
- } else if (compressionLevelForOutputConversion === 0) {
- if (desiredOutputCompressionLevel === 1) outputConversionFunc = toHalfFloat;
- else if (desiredOutputCompressionLevel == 2) outputConversionFunc = localToUint8;
- }
- }
- const minShCoeff = this.minSphericalHarmonicsCoeff;
- const maxShCoeff = this.maxSphericalHarmonicsCoeff;
- if (outSphericalHarmonicsDegree >= 1) {
- set3FromArray(shIn1, dataView, 3, 0, this.compressionLevel);
- set3FromArray(shIn2, dataView, 3, 1, this.compressionLevel);
- set3FromArray(shIn3, dataView, 3, 2, this.compressionLevel);
- if (transform) {
- toUncompressedFloatArray3(shIn1, shIn1, this.compressionLevel, minShCoeff, maxShCoeff);
- toUncompressedFloatArray3(shIn2, shIn2, this.compressionLevel, minShCoeff, maxShCoeff);
- toUncompressedFloatArray3(shIn3, shIn3, this.compressionLevel, minShCoeff, maxShCoeff);
- SplatBuffer.rotateSphericalHarmonics3(shIn1, shIn2, shIn3, sh11, sh12, sh13, shOut1, shOut2, shOut3);
- } else {
- copy3(shIn1, shOut1);
- copy3(shIn2, shOut2);
- copy3(shIn3, shOut3);
- }
- setOutput3(shOut1, outSphericalHarmonicsArray, shDestBase, outputConversionFunc);
- setOutput3(shOut2, outSphericalHarmonicsArray, shDestBase + 3, outputConversionFunc);
- setOutput3(shOut3, outSphericalHarmonicsArray, shDestBase + 6, outputConversionFunc);
- if (outSphericalHarmonicsDegree >= 2) {
- set3FromArray(shIn1, dataView, 5, 9, this.compressionLevel);
- set3FromArray(shIn2, dataView, 5, 10, this.compressionLevel);
- set3FromArray(shIn3, dataView, 5, 11, this.compressionLevel);
- set3FromArray(shIn4, dataView, 5, 12, this.compressionLevel);
- set3FromArray(shIn5, dataView, 5, 13, this.compressionLevel);
- if (transform) {
- toUncompressedFloatArray3(shIn1, shIn1, this.compressionLevel, minShCoeff, maxShCoeff);
- toUncompressedFloatArray3(shIn2, shIn2, this.compressionLevel, minShCoeff, maxShCoeff);
- toUncompressedFloatArray3(shIn3, shIn3, this.compressionLevel, minShCoeff, maxShCoeff);
- toUncompressedFloatArray3(shIn4, shIn4, this.compressionLevel, minShCoeff, maxShCoeff);
- toUncompressedFloatArray3(shIn5, shIn5, this.compressionLevel, minShCoeff, maxShCoeff);
- SplatBuffer.rotateSphericalHarmonics5(shIn1, shIn2, shIn3, shIn4, shIn5,
- sh11, sh12, sh13, sh21, sh22, sh23, sh24, sh25,
- shOut1, shOut2, shOut3, shOut4, shOut5);
- } else {
- copy3(shIn1, shOut1);
- copy3(shIn2, shOut2);
- copy3(shIn3, shOut3);
- copy3(shIn4, shOut4);
- copy3(shIn5, shOut5);
- }
- setOutput3(shOut1, outSphericalHarmonicsArray, shDestBase + 9, outputConversionFunc);
- setOutput3(shOut2, outSphericalHarmonicsArray, shDestBase + 12, outputConversionFunc);
- setOutput3(shOut3, outSphericalHarmonicsArray, shDestBase + 15, outputConversionFunc);
- setOutput3(shOut4, outSphericalHarmonicsArray, shDestBase + 18, outputConversionFunc);
- setOutput3(shOut5, outSphericalHarmonicsArray, shDestBase + 21, outputConversionFunc);
- }
- }
- }
- };
- }();
- static dot3 = (v1, v2, v3, transformRow, outArray) => {
- outArray[0] = outArray[1] = outArray[2] = 0;
- const t0 = transformRow[0];
- const t1 = transformRow[1];
- const t2 = transformRow[2];
- SplatBuffer.addInto3(v1[0] * t0, v1[1] * t0, v1[2] * t0, outArray);
- SplatBuffer.addInto3(v2[0] * t1, v2[1] * t1, v2[2] * t1, outArray);
- SplatBuffer.addInto3(v3[0] * t2, v3[1] * t2, v3[2] * t2, outArray);
- };
- static addInto3 = (val1, val2, val3, destArray) => {
- destArray[0] = destArray[0] + val1;
- destArray[1] = destArray[1] + val2;
- destArray[2] = destArray[2] + val3;
- };
- static dot5 = (v1, v2, v3, v4, v5, transformRow, outArray) => {
- outArray[0] = outArray[1] = outArray[2] = 0;
- const t0 = transformRow[0];
- const t1 = transformRow[1];
- const t2 = transformRow[2];
- const t3 = transformRow[3];
- const t4 = transformRow[4];
- SplatBuffer.addInto3(v1[0] * t0, v1[1] * t0, v1[2] * t0, outArray);
- SplatBuffer.addInto3(v2[0] * t1, v2[1] * t1, v2[2] * t1, outArray);
- SplatBuffer.addInto3(v3[0] * t2, v3[1] * t2, v3[2] * t2, outArray);
- SplatBuffer.addInto3(v4[0] * t3, v4[1] * t3, v4[2] * t3, outArray);
- SplatBuffer.addInto3(v5[0] * t4, v5[1] * t4, v5[2] * t4, outArray);
- };
- static rotateSphericalHarmonics3 = (in1, in2, in3, tsh11, tsh12, tsh13, out1, out2, out3) => {
- SplatBuffer.dot3(in1, in2, in3, tsh11, out1);
- SplatBuffer.dot3(in1, in2, in3, tsh12, out2);
- SplatBuffer.dot3(in1, in2, in3, tsh13, out3);
- };
- static rotateSphericalHarmonics5 = (in1, in2, in3, in4, in5, tsh11, tsh12, tsh13,
- tsh21, tsh22, tsh23, tsh24, tsh25, out1, out2, out3, out4, out5) => {
- const kSqrt0104 = Math.sqrt(1.0 / 4.0);
- const kSqrt0304 = Math.sqrt(3.0 / 4.0);
- const kSqrt0103 = Math.sqrt(1.0 / 3.0);
- const kSqrt0403 = Math.sqrt(4.0 / 3.0);
- const kSqrt0112 = Math.sqrt(1.0 / 12.0);
- tsh21[0] = kSqrt0104 * ((tsh13[2] * tsh11[0] + tsh13[0] * tsh11[2]) + (tsh11[2] * tsh13[0] + tsh11[0] * tsh13[2]));
- tsh21[1] = (tsh13[1] * tsh11[0] + tsh11[1] * tsh13[0]);
- tsh21[2] = kSqrt0304 * (tsh13[1] * tsh11[1] + tsh11[1] * tsh13[1]);
- tsh21[3] = (tsh13[1] * tsh11[2] + tsh11[1] * tsh13[2]);
- tsh21[4] = kSqrt0104 * ((tsh13[2] * tsh11[2] - tsh13[0] * tsh11[0]) + (tsh11[2] * tsh13[2] - tsh11[0] * tsh13[0]));
- SplatBuffer.dot5(in1, in2, in3, in4, in5, tsh21, out1);
- tsh22[0] = kSqrt0104 * ((tsh12[2] * tsh11[0] + tsh12[0] * tsh11[2]) + (tsh11[2] * tsh12[0] + tsh11[0] * tsh12[2]));
- tsh22[1] = tsh12[1] * tsh11[0] + tsh11[1] * tsh12[0];
- tsh22[2] = kSqrt0304 * (tsh12[1] * tsh11[1] + tsh11[1] * tsh12[1]);
- tsh22[3] = tsh12[1] * tsh11[2] + tsh11[1] * tsh12[2];
- tsh22[4] = kSqrt0104 * ((tsh12[2] * tsh11[2] - tsh12[0] * tsh11[0]) + (tsh11[2] * tsh12[2] - tsh11[0] * tsh12[0]));
- SplatBuffer.dot5(in1, in2, in3, in4, in5, tsh22, out2);
- tsh23[0] = kSqrt0103 * (tsh12[2] * tsh12[0] + tsh12[0] * tsh12[2]) + -kSqrt0112 *
- ((tsh13[2] * tsh13[0] + tsh13[0] * tsh13[2]) + (tsh11[2] * tsh11[0] + tsh11[0] * tsh11[2]));
- tsh23[1] = kSqrt0403 * tsh12[1] * tsh12[0] + -kSqrt0103 * (tsh13[1] * tsh13[0] + tsh11[1] * tsh11[0]);
- tsh23[2] = tsh12[1] * tsh12[1] + -kSqrt0104 * (tsh13[1] * tsh13[1] + tsh11[1] * tsh11[1]);
- tsh23[3] = kSqrt0403 * tsh12[1] * tsh12[2] + -kSqrt0103 * (tsh13[1] * tsh13[2] + tsh11[1] * tsh11[2]);
- tsh23[4] = kSqrt0103 * (tsh12[2] * tsh12[2] - tsh12[0] * tsh12[0]) + -kSqrt0112 *
- ((tsh13[2] * tsh13[2] - tsh13[0] * tsh13[0]) + (tsh11[2] * tsh11[2] - tsh11[0] * tsh11[0]));
- SplatBuffer.dot5(in1, in2, in3, in4, in5, tsh23, out3);
- tsh24[0] = kSqrt0104 * ((tsh12[2] * tsh13[0] + tsh12[0] * tsh13[2]) + (tsh13[2] * tsh12[0] + tsh13[0] * tsh12[2]));
- tsh24[1] = tsh12[1] * tsh13[0] + tsh13[1] * tsh12[0];
- tsh24[2] = kSqrt0304 * (tsh12[1] * tsh13[1] + tsh13[1] * tsh12[1]);
- tsh24[3] = tsh12[1] * tsh13[2] + tsh13[1] * tsh12[2];
- tsh24[4] = kSqrt0104 * ((tsh12[2] * tsh13[2] - tsh12[0] * tsh13[0]) + (tsh13[2] * tsh12[2] - tsh13[0] * tsh12[0]));
- SplatBuffer.dot5(in1, in2, in3, in4, in5, tsh24, out4);
- tsh25[0] = kSqrt0104 * ((tsh13[2] * tsh13[0] + tsh13[0] * tsh13[2]) - (tsh11[2] * tsh11[0] + tsh11[0] * tsh11[2]));
- tsh25[1] = (tsh13[1] * tsh13[0] - tsh11[1] * tsh11[0]);
- tsh25[2] = kSqrt0304 * (tsh13[1] * tsh13[1] - tsh11[1] * tsh11[1]);
- tsh25[3] = (tsh13[1] * tsh13[2] - tsh11[1] * tsh11[2]);
- tsh25[4] = kSqrt0104 * ((tsh13[2] * tsh13[2] - tsh13[0] * tsh13[0]) - (tsh11[2] * tsh11[2] - tsh11[0] * tsh11[0]));
- SplatBuffer.dot5(in1, in2, in3, in4, in5, tsh25, out5);
- };
- static parseHeader(buffer) {
- const headerArrayUint8 = new Uint8Array(buffer, 0, SplatBuffer.HeaderSizeBytes);
- const headerArrayUint16 = new Uint16Array(buffer, 0, SplatBuffer.HeaderSizeBytes / 2);
- const headerArrayUint32 = new Uint32Array(buffer, 0, SplatBuffer.HeaderSizeBytes / 4);
- const headerArrayFloat32 = new Float32Array(buffer, 0, SplatBuffer.HeaderSizeBytes / 4);
- const versionMajor = headerArrayUint8[0];
- const versionMinor = headerArrayUint8[1];
- const maxSectionCount = headerArrayUint32[1];
- const sectionCount = headerArrayUint32[2];
- const maxSplatCount = headerArrayUint32[3];
- const splatCount = headerArrayUint32[4];
- const compressionLevel = headerArrayUint16[10];
- const sceneCenter = new THREE.Vector3(headerArrayFloat32[6], headerArrayFloat32[7], headerArrayFloat32[8]);
- const minSphericalHarmonicsCoeff = headerArrayFloat32[9] || -DefaultSphericalHarmonics8BitCompressionHalfRange;
- const maxSphericalHarmonicsCoeff = headerArrayFloat32[10] || DefaultSphericalHarmonics8BitCompressionHalfRange;
- return {
- versionMajor,
- versionMinor,
- maxSectionCount,
- sectionCount,
- maxSplatCount,
- splatCount,
- compressionLevel,
- sceneCenter,
- minSphericalHarmonicsCoeff,
- maxSphericalHarmonicsCoeff
- };
- }
- static writeHeaderCountsToBuffer(sectionCount, splatCount, buffer) {
- const headerArrayUint32 = new Uint32Array(buffer, 0, SplatBuffer.HeaderSizeBytes / 4);
- headerArrayUint32[2] = sectionCount;
- headerArrayUint32[4] = splatCount;
- }
- static writeHeaderToBuffer(header, buffer) {
- const headerArrayUint8 = new Uint8Array(buffer, 0, SplatBuffer.HeaderSizeBytes);
- const headerArrayUint16 = new Uint16Array(buffer, 0, SplatBuffer.HeaderSizeBytes / 2);
- const headerArrayUint32 = new Uint32Array(buffer, 0, SplatBuffer.HeaderSizeBytes / 4);
- const headerArrayFloat32 = new Float32Array(buffer, 0, SplatBuffer.HeaderSizeBytes / 4);
- headerArrayUint8[0] = header.versionMajor;
- headerArrayUint8[1] = header.versionMinor;
- headerArrayUint8[2] = 0; // unused for now
- headerArrayUint8[3] = 0; // unused for now
- headerArrayUint32[1] = header.maxSectionCount;
- headerArrayUint32[2] = header.sectionCount;
- headerArrayUint32[3] = header.maxSplatCount;
- headerArrayUint32[4] = header.splatCount;
- headerArrayUint16[10] = header.compressionLevel;
- headerArrayFloat32[6] = header.sceneCenter.x;
- headerArrayFloat32[7] = header.sceneCenter.y;
- headerArrayFloat32[8] = header.sceneCenter.z;
- headerArrayFloat32[9] = header.minSphericalHarmonicsCoeff || -DefaultSphericalHarmonics8BitCompressionHalfRange;
- headerArrayFloat32[10] = header.maxSphericalHarmonicsCoeff || DefaultSphericalHarmonics8BitCompressionHalfRange;
- }
- static parseSectionHeaders(header, buffer, offset = 0, secLoadedCountsToMax) {
- const compressionLevel = header.compressionLevel;
- const maxSectionCount = header.maxSectionCount;
- const sectionHeaderArrayUint16 = new Uint16Array(buffer, offset, maxSectionCount * SplatBuffer.SectionHeaderSizeBytes / 2);
- const sectionHeaderArrayUint32 = new Uint32Array(buffer, offset, maxSectionCount * SplatBuffer.SectionHeaderSizeBytes / 4);
- const sectionHeaderArrayFloat32 = new Float32Array(buffer, offset, maxSectionCount * SplatBuffer.SectionHeaderSizeBytes / 4);
- const sectionHeaders = [];
- let sectionHeaderBase = 0;
- let sectionHeaderBaseUint16 = sectionHeaderBase / 2;
- let sectionHeaderBaseUint32 = sectionHeaderBase / 4;
- let sectionBase = SplatBuffer.HeaderSizeBytes + header.maxSectionCount * SplatBuffer.SectionHeaderSizeBytes;
- let splatCountOffset = 0;
- for (let i = 0; i < maxSectionCount; i++) {
- const maxSplatCount = sectionHeaderArrayUint32[sectionHeaderBaseUint32 + 1];
- const bucketSize = sectionHeaderArrayUint32[sectionHeaderBaseUint32 + 2];
- const bucketCount = sectionHeaderArrayUint32[sectionHeaderBaseUint32 + 3];
- const bucketBlockSize = sectionHeaderArrayFloat32[sectionHeaderBaseUint32 + 4];
- const halfBucketBlockSize = bucketBlockSize / 2.0;
- const bucketStorageSizeBytes = sectionHeaderArrayUint16[sectionHeaderBaseUint16 + 10];
- const compressionScaleRange = sectionHeaderArrayUint32[sectionHeaderBaseUint32 + 6] ||
- SplatBuffer.CompressionLevels[compressionLevel].ScaleRange;
- const fullBucketCount = sectionHeaderArrayUint32[sectionHeaderBaseUint32 + 8];
- const partiallyFilledBucketCount = sectionHeaderArrayUint32[sectionHeaderBaseUint32 + 9];
- const bucketsMetaDataSizeBytes = partiallyFilledBucketCount * 4;
- const bucketsStorageSizeBytes = bucketStorageSizeBytes * bucketCount + bucketsMetaDataSizeBytes;
- const sphericalHarmonicsDegree = sectionHeaderArrayUint16[sectionHeaderBaseUint16 + 20];
- const { bytesPerSplat } = SplatBuffer.calculateComponentStorage(compressionLevel, sphericalHarmonicsDegree);
- const splatDataStorageSizeBytes = bytesPerSplat * maxSplatCount;
- const storageSizeBytes = splatDataStorageSizeBytes + bucketsStorageSizeBytes;
- const sectionHeader = {
- bytesPerSplat: bytesPerSplat,
- splatCountOffset: splatCountOffset,
- splatCount: secLoadedCountsToMax ? maxSplatCount : 0,
- maxSplatCount: maxSplatCount,
- bucketSize: bucketSize,
- bucketCount: bucketCount,
- bucketBlockSize: bucketBlockSize,
- halfBucketBlockSize: halfBucketBlockSize,
- bucketStorageSizeBytes: bucketStorageSizeBytes,
- bucketsStorageSizeBytes: bucketsStorageSizeBytes,
- splatDataStorageSizeBytes: splatDataStorageSizeBytes,
- storageSizeBytes: storageSizeBytes,
- compressionScaleRange: compressionScaleRange,
- compressionScaleFactor: halfBucketBlockSize / compressionScaleRange,
- base: sectionBase,
- bucketsBase: sectionBase + bucketsMetaDataSizeBytes,
- dataBase: sectionBase + bucketsStorageSizeBytes,
- fullBucketCount: fullBucketCount,
- partiallyFilledBucketCount: partiallyFilledBucketCount,
- sphericalHarmonicsDegree: sphericalHarmonicsDegree
- };
- sectionHeaders[i] = sectionHeader;
- sectionBase += storageSizeBytes;
- sectionHeaderBase += SplatBuffer.SectionHeaderSizeBytes;
- sectionHeaderBaseUint16 = sectionHeaderBase / 2;
- sectionHeaderBaseUint32 = sectionHeaderBase / 4;
- splatCountOffset += maxSplatCount;
- }
- return sectionHeaders;
- }
- static writeSectionHeaderToBuffer(sectionHeader, compressionLevel, buffer, offset = 0) {
- const sectionHeadeArrayUint16 = new Uint16Array(buffer, offset, SplatBuffer.SectionHeaderSizeBytes / 2);
- const sectionHeadeArrayUint32 = new Uint32Array(buffer, offset, SplatBuffer.SectionHeaderSizeBytes / 4);
- const sectionHeadeArrayFloat32 = new Float32Array(buffer, offset, SplatBuffer.SectionHeaderSizeBytes / 4);
- sectionHeadeArrayUint32[0] = sectionHeader.splatCount;
- sectionHeadeArrayUint32[1] = sectionHeader.maxSplatCount;
- sectionHeadeArrayUint32[2] = compressionLevel >= 1 ? sectionHeader.bucketSize : 0;
- sectionHeadeArrayUint32[3] = compressionLevel >= 1 ? sectionHeader.bucketCount : 0;
- sectionHeadeArrayFloat32[4] = compressionLevel >= 1 ? sectionHeader.bucketBlockSize : 0.0;
- sectionHeadeArrayUint16[10] = compressionLevel >= 1 ? SplatBuffer.BucketStorageSizeBytes : 0;
- sectionHeadeArrayUint32[6] = compressionLevel >= 1 ? sectionHeader.compressionScaleRange : 0;
- sectionHeadeArrayUint32[7] = sectionHeader.storageSizeBytes;
- sectionHeadeArrayUint32[8] = compressionLevel >= 1 ? sectionHeader.fullBucketCount : 0;
- sectionHeadeArrayUint32[9] = compressionLevel >= 1 ? sectionHeader.partiallyFilledBucketCount : 0;
- sectionHeadeArrayUint16[20] = sectionHeader.sphericalHarmonicsDegree;
- }
- static writeSectionHeaderSplatCountToBuffer(splatCount, buffer, offset = 0) {
- const sectionHeadeArrayUint32 = new Uint32Array(buffer, offset, SplatBuffer.SectionHeaderSizeBytes / 4);
- sectionHeadeArrayUint32[0] = splatCount;
- }
- constructFromBuffer(bufferData, secLoadedCountsToMax) {
- this.bufferData = bufferData;
- this.globalSplatIndexToLocalSplatIndexMap = [];
- this.globalSplatIndexToSectionMap = [];
- const header = SplatBuffer.parseHeader(this.bufferData);
- this.versionMajor = header.versionMajor;
- this.versionMinor = header.versionMinor;
- this.maxSectionCount = header.maxSectionCount;
- this.sectionCount = secLoadedCountsToMax ? header.maxSectionCount : 0;
- this.maxSplatCount = header.maxSplatCount;
- this.splatCount = secLoadedCountsToMax ? header.maxSplatCount : 0;
- this.compressionLevel = header.compressionLevel;
- this.sceneCenter = new THREE.Vector3().copy(header.sceneCenter);
- this.minSphericalHarmonicsCoeff = header.minSphericalHarmonicsCoeff;
- this.maxSphericalHarmonicsCoeff = header.maxSphericalHarmonicsCoeff;
- this.sections = SplatBuffer.parseSectionHeaders(header, this.bufferData, SplatBuffer.HeaderSizeBytes, secLoadedCountsToMax);
- this.linkBufferArrays();
- this.buildMaps();
- }
- static calculateComponentStorage(compressionLevel, sphericalHarmonicsDegree) {
- const bytesPerCenter = SplatBuffer.CompressionLevels[compressionLevel].BytesPerCenter;
- const bytesPerScale = SplatBuffer.CompressionLevels[compressionLevel].BytesPerScale;
- const bytesPerRotation = SplatBuffer.CompressionLevels[compressionLevel].BytesPerRotation;
- const bytesPerColor = SplatBuffer.CompressionLevels[compressionLevel].BytesPerColor;
- const sphericalHarmonicsComponentsPerSplat = getSphericalHarmonicsComponentCountForDegree(sphericalHarmonicsDegree);
- const sphericalHarmonicsBytesPerSplat = SplatBuffer.CompressionLevels[compressionLevel].BytesPerSphericalHarmonicsComponent *
- sphericalHarmonicsComponentsPerSplat;
- const bytesPerSplat = bytesPerCenter + bytesPerScale + bytesPerRotation +
- bytesPerColor + sphericalHarmonicsBytesPerSplat;
- return {
- bytesPerCenter,
- bytesPerScale,
- bytesPerRotation,
- bytesPerColor,
- sphericalHarmonicsComponentsPerSplat,
- sphericalHarmonicsBytesPerSplat,
- bytesPerSplat
- };
- }
- linkBufferArrays() {
- for (let i = 0; i < this.maxSectionCount; i++) {
- const section = this.sections[i];
- section.bucketArray = new Float32Array(this.bufferData, section.bucketsBase,
- section.bucketCount * SplatBuffer.BucketStorageSizeFloats);
- if (section.partiallyFilledBucketCount > 0) {
- section.partiallyFilledBucketLengths = new Uint32Array(this.bufferData, section.base,
- section.partiallyFilledBucketCount);
- }
- }
- }
- buildMaps() {
- let cumulativeSplatCount = 0;
- for (let i = 0; i < this.maxSectionCount; i++) {
- const section = this.sections[i];
- for (let j = 0; j < section.maxSplatCount; j++) {
- const globalSplatIndex = cumulativeSplatCount + j;
- this.globalSplatIndexToLocalSplatIndexMap[globalSplatIndex] = j;
- this.globalSplatIndexToSectionMap[globalSplatIndex] = i;
- }
- cumulativeSplatCount += section.maxSplatCount;
- }
- }
- updateLoadedCounts(newSectionCount, newSplatCount) {
- SplatBuffer.writeHeaderCountsToBuffer(newSectionCount, newSplatCount, this.bufferData);
- this.sectionCount = newSectionCount;
- this.splatCount = newSplatCount;
- }
- updateSectionLoadedCounts(sectionIndex, newSplatCount) {
- const sectionHeaderOffset = SplatBuffer.HeaderSizeBytes + SplatBuffer.SectionHeaderSizeBytes * sectionIndex;
- SplatBuffer.writeSectionHeaderSplatCountToBuffer(newSplatCount, this.bufferData, sectionHeaderOffset);
- this.sections[sectionIndex].splatCount = newSplatCount;
- }
- static writeSplatDataToSectionBuffer = function() {
- const tempCenterBuffer = new ArrayBuffer(12);
- const tempScaleBuffer = new ArrayBuffer(12);
- const tempRotationBuffer = new ArrayBuffer(16);
- const tempColorBuffer = new ArrayBuffer(4);
- const tempSHBuffer = new ArrayBuffer(256);
- const tempRot = new THREE.Quaternion();
- const tempScale = new THREE.Vector3();
- const bucketCenterDelta = new THREE.Vector3();
- const {
- X: OFFSET_X, Y: OFFSET_Y, Z: OFFSET_Z,
- SCALE0: OFFSET_SCALE0, SCALE1: OFFSET_SCALE1, SCALE2: OFFSET_SCALE2,
- ROTATION0: OFFSET_ROT0, ROTATION1: OFFSET_ROT1, ROTATION2: OFFSET_ROT2, ROTATION3: OFFSET_ROT3,
- FDC0: OFFSET_FDC0, FDC1: OFFSET_FDC1, FDC2: OFFSET_FDC2, OPACITY: OFFSET_OPACITY,
- FRC0: OFFSET_FRC0, FRC9: OFFSET_FRC9,
- } = UncompressedSplatArray.OFFSET;
- const compressPositionOffset = (v, compressionScaleFactor, compressionScaleRange) => {
- const doubleCompressionScaleRange = compressionScaleRange * 2 + 1;
- v = Math.round(v * compressionScaleFactor) + compressionScaleRange;
- return clamp(v, 0, doubleCompressionScaleRange);
- };
- return function(targetSplat, sectionBuffer, bufferOffset, compressionLevel, sphericalHarmonicsDegree,
- bucketCenter, compressionScaleFactor, compressionScaleRange,
- minSphericalHarmonicsCoeff = -DefaultSphericalHarmonics8BitCompressionHalfRange,
- maxSphericalHarmonicsCoeff = DefaultSphericalHarmonics8BitCompressionHalfRange) {
- const sphericalHarmonicsComponentsPerSplat = getSphericalHarmonicsComponentCountForDegree(sphericalHarmonicsDegree);
- const bytesPerCenter = SplatBuffer.CompressionLevels[compressionLevel].BytesPerCenter;
- const bytesPerScale = SplatBuffer.CompressionLevels[compressionLevel].BytesPerScale;
- const bytesPerRotation = SplatBuffer.CompressionLevels[compressionLevel].BytesPerRotation;
- const bytesPerColor = SplatBuffer.CompressionLevels[compressionLevel].BytesPerColor;
- const centerBase = bufferOffset;
- const scaleBase = centerBase + bytesPerCenter;
- const rotationBase = scaleBase + bytesPerScale;
- const colorBase = rotationBase + bytesPerRotation;
- const sphericalHarmonicsBase = colorBase + bytesPerColor;
- if (targetSplat[OFFSET_ROT0] !== undefined) {
- tempRot.set(targetSplat[OFFSET_ROT0], targetSplat[OFFSET_ROT1], targetSplat[OFFSET_ROT2], targetSplat[OFFSET_ROT3]);
- tempRot.normalize();
- } else {
- tempRot.set(1.0, 0.0, 0.0, 0.0);
- }
- if (targetSplat[OFFSET_SCALE0] !== undefined) {
- tempScale.set(targetSplat[OFFSET_SCALE0] || 0,
- targetSplat[OFFSET_SCALE1] || 0,
- targetSplat[OFFSET_SCALE2] || 0);
- } else {
- tempScale.set(0, 0, 0);
- }
- if (compressionLevel === 0) {
- const center = new Float32Array(sectionBuffer, centerBase, SplatBuffer.CenterComponentCount);
- const rot = new Float32Array(sectionBuffer, rotationBase, SplatBuffer.RotationComponentCount);
- const scale = new Float32Array(sectionBuffer, scaleBase, SplatBuffer.ScaleComponentCount);
- rot.set([tempRot.x, tempRot.y, tempRot.z, tempRot.w]);
- scale.set([tempScale.x, tempScale.y, tempScale.z]);
- center.set([targetSplat[OFFSET_X], targetSplat[OFFSET_Y], targetSplat[OFFSET_Z]]);
- if (sphericalHarmonicsDegree > 0) {
- const shOut = new Float32Array(sectionBuffer, sphericalHarmonicsBase, sphericalHarmonicsComponentsPerSplat);
- if (sphericalHarmonicsDegree >= 1) {
- for (let s = 0; s < 9; s++) shOut[s] = targetSplat[OFFSET_FRC0 + s] || 0;
- if (sphericalHarmonicsDegree >= 2) {
- for (let s = 0; s < 15; s++) shOut[s + 9] = targetSplat[OFFSET_FRC9 + s] || 0;
- }
- }
- }
- } else {
- const center = new Uint16Array(tempCenterBuffer, 0, SplatBuffer.CenterComponentCount);
- const rot = new Uint16Array(tempRotationBuffer, 0, SplatBuffer.RotationComponentCount);
- const scale = new Uint16Array(tempScaleBuffer, 0, SplatBuffer.ScaleComponentCount);
- rot.set([toHalfFloat(tempRot.x), toHalfFloat(tempRot.y), toHalfFloat(tempRot.z), toHalfFloat(tempRot.w)]);
- scale.set([toHalfFloat(tempScale.x), toHalfFloat(tempScale.y), toHalfFloat(tempScale.z)]);
- bucketCenterDelta.set(targetSplat[OFFSET_X], targetSplat[OFFSET_Y], targetSplat[OFFSET_Z]).sub(bucketCenter);
- bucketCenterDelta.x = compressPositionOffset(bucketCenterDelta.x, compressionScaleFactor, compressionScaleRange);
- bucketCenterDelta.y = compressPositionOffset(bucketCenterDelta.y, compressionScaleFactor, compressionScaleRange);
- bucketCenterDelta.z = compressPositionOffset(bucketCenterDelta.z, compressionScaleFactor, compressionScaleRange);
- center.set([bucketCenterDelta.x, bucketCenterDelta.y, bucketCenterDelta.z]);
- if (sphericalHarmonicsDegree > 0) {
- const SHArrayType = compressionLevel === 1 ? Uint16Array : Uint8Array;
- const bytesPerSHComponent = compressionLevel === 1 ? 2 : 1;
- const shOut = new SHArrayType(tempSHBuffer, 0, sphericalHarmonicsComponentsPerSplat);
- if (sphericalHarmonicsDegree >= 1) {
- for (let s = 0; s < 9; s++) {
- const srcVal = targetSplat[OFFSET_FRC0 + s] || 0;
- shOut[s] = compressionLevel === 1 ? toHalfFloat(srcVal) :
- toUint8(srcVal, minSphericalHarmonicsCoeff, maxSphericalHarmonicsCoeff);
- }
- const degree1ByteCount = 9 * bytesPerSHComponent;
- copyBetweenBuffers(shOut.buffer, 0, sectionBuffer, sphericalHarmonicsBase, degree1ByteCount);
- if (sphericalHarmonicsDegree >= 2) {
- for (let s = 0; s < 15; s++) {
- const srcVal = targetSplat[OFFSET_FRC9 + s] || 0;
- shOut[s + 9] = compressionLevel === 1 ? toHalfFloat(srcVal) :
- toUint8(srcVal, minSphericalHarmonicsCoeff, maxSphericalHarmonicsCoeff);
- }
- copyBetweenBuffers(shOut.buffer, degree1ByteCount, sectionBuffer,
- sphericalHarmonicsBase + degree1ByteCount, 15 * bytesPerSHComponent);
- }
- }
- }
- copyBetweenBuffers(center.buffer, 0, sectionBuffer, centerBase, 6);
- copyBetweenBuffers(scale.buffer, 0, sectionBuffer, scaleBase, 6);
- copyBetweenBuffers(rot.buffer, 0, sectionBuffer, rotationBase, 8);
- }
- const rgba = new Uint8ClampedArray(tempColorBuffer, 0, 4);
- rgba.set([targetSplat[OFFSET_FDC0] || 0, targetSplat[OFFSET_FDC1] || 0, targetSplat[OFFSET_FDC2] || 0]);
- rgba[3] = targetSplat[OFFSET_OPACITY] || 0;
- copyBetweenBuffers(rgba.buffer, 0, sectionBuffer, colorBase, 4);
- };
- }();
- static generateFromUncompressedSplatArrays(splatArrays, minimumAlpha, compressionLevel,
- sceneCenter, blockSize, bucketSize, options = []) {
- let shDegree = 0;
- for (let sa = 0; sa < splatArrays.length; sa ++) {
- const splatArray = splatArrays[sa];
- shDegree = Math.max(splatArray.sphericalHarmonicsDegree, shDegree);
- }
- let minSphericalHarmonicsCoeff;
- let maxSphericalHarmonicsCoeff;
- for (let sa = 0; sa < splatArrays.length; sa ++) {
- const splatArray = splatArrays[sa];
- for (let i = 0; i < splatArray.splats.length; i++) {
- const splat = splatArray.splats[i];
- for (let sc = UncompressedSplatArray.OFFSET.FRC0; sc < UncompressedSplatArray.OFFSET.FRC23 && sc < splat.length; sc++) {
- if (!minSphericalHarmonicsCoeff || splat[sc] < minSphericalHarmonicsCoeff) {
- minSphericalHarmonicsCoeff = splat[sc];
- }
- if (!maxSphericalHarmonicsCoeff || splat[sc] > maxSphericalHarmonicsCoeff) {
- maxSphericalHarmonicsCoeff = splat[sc];
- }
- }
- }
- }
- minSphericalHarmonicsCoeff = minSphericalHarmonicsCoeff || -DefaultSphericalHarmonics8BitCompressionHalfRange;
- maxSphericalHarmonicsCoeff = maxSphericalHarmonicsCoeff || DefaultSphericalHarmonics8BitCompressionHalfRange;
- const { bytesPerSplat } = SplatBuffer.calculateComponentStorage(compressionLevel, shDegree);
- const compressionScaleRange = SplatBuffer.CompressionLevels[compressionLevel].ScaleRange;
- const sectionBuffers = [];
- const sectionHeaderBuffers = [];
- let totalSplatCount = 0;
- for (let sa = 0; sa < splatArrays.length; sa ++) {
- const splatArray = splatArrays[sa];
- const validSplats = new UncompressedSplatArray(shDegree);
- for (let i = 0; i < splatArray.splatCount; i++) {
- const targetSplat = splatArray.splats[i];
- if ((targetSplat[UncompressedSplatArray.OFFSET.OPACITY] || 0) >= minimumAlpha) {
- validSplats.addSplat(targetSplat);
- }
- }
- const sectionOptions = options[sa] || {};
- const sectionBlockSize = (sectionOptions.blockSizeFactor || 1) * (blockSize || SplatBuffer.BucketBlockSize);
- const sectionBucketSize = Math.ceil((sectionOptions.bucketSizeFactor || 1) * (bucketSize || SplatBuffer.BucketSize));
- const bucketInfo = SplatBuffer.computeBucketsForUncompressedSplatArray(validSplats, sectionBlockSize, sectionBucketSize);
- const fullBucketCount = bucketInfo.fullBuckets.length;
- const partiallyFullBucketLengths = bucketInfo.partiallyFullBuckets.map((bucket) => bucket.splats.length);
- const partiallyFilledBucketCount = partiallyFullBucketLengths.length;
- const buckets = [...bucketInfo.fullBuckets, ...bucketInfo.partiallyFullBuckets];
- const sectionDataSizeBytes = validSplats.splats.length * bytesPerSplat;
- const bucketMetaDataSizeBytes = partiallyFilledBucketCount * 4;
- const bucketDataBytes = compressionLevel >= 1 ? buckets.length *
- SplatBuffer.BucketStorageSizeBytes + bucketMetaDataSizeBytes : 0;
- const sectionSizeBytes = sectionDataSizeBytes + bucketDataBytes;
- const sectionBuffer = new ArrayBuffer(sectionSizeBytes);
- const compressionScaleFactor = compressionScaleRange / (sectionBlockSize * 0.5);
- const bucketCenter = new THREE.Vector3();
- let outSplatCount = 0;
- for (let b = 0; b < buckets.length; b++) {
- const bucket = buckets[b];
- bucketCenter.fromArray(bucket.center);
- for (let i = 0; i < bucket.splats.length; i++) {
- let row = bucket.splats[i];
- const targetSplat = validSplats.splats[row];
- const bufferOffset = bucketDataBytes + outSplatCount * bytesPerSplat;
- SplatBuffer.writeSplatDataToSectionBuffer(targetSplat, sectionBuffer, bufferOffset, compressionLevel, shDegree,
- bucketCenter, compressionScaleFactor, compressionScaleRange,
- minSphericalHarmonicsCoeff, maxSphericalHarmonicsCoeff);
- outSplatCount++;
- }
- }
- totalSplatCount += outSplatCount;
- if (compressionLevel >= 1) {
- const bucketMetaDataArray = new Uint32Array(sectionBuffer, 0, partiallyFullBucketLengths.length * 4);
- for (let pfb = 0; pfb < partiallyFullBucketLengths.length; pfb ++) {
- bucketMetaDataArray[pfb] = partiallyFullBucketLengths[pfb];
- }
- const bucketArray = new Float32Array(sectionBuffer, bucketMetaDataSizeBytes,
- buckets.length * SplatBuffer.BucketStorageSizeFloats);
- for (let b = 0; b < buckets.length; b++) {
- const bucket = buckets[b];
- const base = b * 3;
- bucketArray[base] = bucket.center[0];
- bucketArray[base + 1] = bucket.center[1];
- bucketArray[base + 2] = bucket.center[2];
- }
- }
- sectionBuffers.push(sectionBuffer);
- const sectionHeaderBuffer = new ArrayBuffer(SplatBuffer.SectionHeaderSizeBytes);
- SplatBuffer.writeSectionHeaderToBuffer({
- maxSplatCount: outSplatCount,
- splatCount: outSplatCount,
- bucketSize: sectionBucketSize,
- bucketCount: buckets.length,
- bucketBlockSize: sectionBlockSize,
- compressionScaleRange: compressionScaleRange,
- storageSizeBytes: sectionSizeBytes,
- fullBucketCount: fullBucketCount,
- partiallyFilledBucketCount: partiallyFilledBucketCount,
- sphericalHarmonicsDegree: shDegree
- }, compressionLevel, sectionHeaderBuffer, 0);
- sectionHeaderBuffers.push(sectionHeaderBuffer);
- }
- let sectionsCumulativeSizeBytes = 0;
- for (let sectionBuffer of sectionBuffers) sectionsCumulativeSizeBytes += sectionBuffer.byteLength;
- const unifiedBufferSize = SplatBuffer.HeaderSizeBytes +
- SplatBuffer.SectionHeaderSizeBytes * sectionBuffers.length + sectionsCumulativeSizeBytes;
- const unifiedBuffer = new ArrayBuffer(unifiedBufferSize);
- SplatBuffer.writeHeaderToBuffer({
- versionMajor: 0,
- versionMinor: 1,
- maxSectionCount: sectionBuffers.length,
- sectionCount: sectionBuffers.length,
- maxSplatCount: totalSplatCount,
- splatCount: totalSplatCount,
- compressionLevel: compressionLevel,
- sceneCenter: sceneCenter,
- minSphericalHarmonicsCoeff: minSphericalHarmonicsCoeff,
- maxSphericalHarmonicsCoeff: maxSphericalHarmonicsCoeff
- }, unifiedBuffer);
- let currentUnifiedBase = SplatBuffer.HeaderSizeBytes;
- for (let sectionHeaderBuffer of sectionHeaderBuffers) {
- new Uint8Array(unifiedBuffer, currentUnifiedBase, SplatBuffer.SectionHeaderSizeBytes).set(new Uint8Array(sectionHeaderBuffer));
- currentUnifiedBase += SplatBuffer.SectionHeaderSizeBytes;
- }
- for (let sectionBuffer of sectionBuffers) {
- new Uint8Array(unifiedBuffer, currentUnifiedBase, sectionBuffer.byteLength).set(new Uint8Array(sectionBuffer));
- currentUnifiedBase += sectionBuffer.byteLength;
- }
- const splatBuffer = new SplatBuffer(unifiedBuffer);
- return splatBuffer;
- }
- static computeBucketsForUncompressedSplatArray(splatArray, blockSize, bucketSize) {
- let splatCount = splatArray.splatCount;
- const halfBlockSize = blockSize / 2.0;
- const min = new THREE.Vector3();
- const max = new THREE.Vector3();
- for (let i = 0; i < splatCount; i++) {
- const targetSplat = splatArray.splats[i];
- const center = [targetSplat[UncompressedSplatArray.OFFSET.X],
- targetSplat[UncompressedSplatArray.OFFSET.Y],
- targetSplat[UncompressedSplatArray.OFFSET.Z]];
- if (i === 0 || center[0] < min.x) min.x = center[0];
- if (i === 0 || center[0] > max.x) max.x = center[0];
- if (i === 0 || center[1] < min.y) min.y = center[1];
- if (i === 0 || center[1] > max.y) max.y = center[1];
- if (i === 0 || center[2] < min.z) min.z = center[2];
- if (i === 0 || center[2] > max.z) max.z = center[2];
- }
- const dimensions = new THREE.Vector3().copy(max).sub(min);
- const yBlocks = Math.ceil(dimensions.y / blockSize);
- const zBlocks = Math.ceil(dimensions.z / blockSize);
- const blockCenter = new THREE.Vector3();
- const fullBuckets = [];
- const partiallyFullBuckets = {};
- for (let i = 0; i < splatCount; i++) {
- const targetSplat = splatArray.splats[i];
- const center = [targetSplat[UncompressedSplatArray.OFFSET.X],
- targetSplat[UncompressedSplatArray.OFFSET.Y],
- targetSplat[UncompressedSplatArray.OFFSET.Z]];
- const xBlock = Math.floor((center[0] - min.x) / blockSize);
- const yBlock = Math.floor((center[1] - min.y) / blockSize);
- const zBlock = Math.floor((center[2] - min.z) / blockSize);
- blockCenter.x = xBlock * blockSize + min.x + halfBlockSize;
- blockCenter.y = yBlock * blockSize + min.y + halfBlockSize;
- blockCenter.z = zBlock * blockSize + min.z + halfBlockSize;
- const bucketId = xBlock * (yBlocks * zBlocks) + yBlock * zBlocks + zBlock;
- let bucket = partiallyFullBuckets[bucketId];
- if (!bucket) {
- partiallyFullBuckets[bucketId] = bucket = {
- 'splats': [],
- 'center': blockCenter.toArray()
- };
- }
- bucket.splats.push(i);
- if (bucket.splats.length >= bucketSize) {
- fullBuckets.push(bucket);
- partiallyFullBuckets[bucketId] = null;
- }
- }
- const partiallyFullBucketArray = [];
- for (let bucketId in partiallyFullBuckets) {
- if (partiallyFullBuckets.hasOwnProperty(bucketId)) {
- const bucket = partiallyFullBuckets[bucketId];
- if (bucket) {
- partiallyFullBucketArray.push(bucket);
- }
- }
- }
- return {
- 'fullBuckets': fullBuckets,
- 'partiallyFullBuckets': partiallyFullBucketArray,
- };
- }
- }
- const HeaderMagicBytes = new Uint8Array([112, 108, 121, 10]);
- const HeaderEndTokenBytes = new Uint8Array([10, 101, 110, 100, 95, 104, 101, 97, 100, 101, 114, 10]);
- const HeaderEndToken = 'end_header';
- const DataTypeMap = new Map([
- ['char', Int8Array],
- ['uchar', Uint8Array],
- ['short', Int16Array],
- ['ushort', Uint16Array],
- ['int', Int32Array],
- ['uint', Uint32Array],
- ['float', Float32Array],
- ['double', Float64Array],
- ]);
- const unpackUnorm = (value, bits) => {
- const t = (1 << bits) - 1;
- return (value & t) / t;
- };
- const unpack111011 = (result, value) => {
- result.x = unpackUnorm(value >>> 21, 11);
- result.y = unpackUnorm(value >>> 11, 10);
- result.z = unpackUnorm(value, 11);
- };
- const unpack8888 = (result, value) => {
- result.x = unpackUnorm(value >>> 24, 8);
- result.y = unpackUnorm(value >>> 16, 8);
- result.z = unpackUnorm(value >>> 8, 8);
- result.w = unpackUnorm(value, 8);
- };
- // unpack quaternion with 2,10,10,10 format (largest element, 3x10bit element)
- const unpackRot = (result, value) => {
- const norm = 1.0 / (Math.sqrt(2) * 0.5);
- const a = (unpackUnorm(value >>> 20, 10) - 0.5) * norm;
- const b = (unpackUnorm(value >>> 10, 10) - 0.5) * norm;
- const c = (unpackUnorm(value, 10) - 0.5) * norm;
- const m = Math.sqrt(1.0 - (a * a + b * b + c * c));
- switch (value >>> 30) {
- case 0:
- result.set(m, a, b, c);
- break;
- case 1:
- result.set(a, m, b, c);
- break;
- case 2:
- result.set(a, b, m, c);
- break;
- case 3:
- result.set(a, b, c, m);
- break;
- }
- };
- const lerp = (a, b, t) => {
- return a * (1 - t) + b * t;
- };
- const getElementPropStorage = (element, name) => {
- return element.properties.find((p) => p.name === name && p.storage)
- ?.storage;
- };
- class PlayCanvasCompressedPlyParser {
- static decodeHeaderText(headerText) {
- let element;
- let chunkElement;
- let vertexElement;
- const headerLines = headerText.split('\n').filter((line) => !line.startsWith('comment '));
- let bytesPerSplat = 0;
- let done = false;
- for (let i = 1; i < headerLines.length; ++i) {
- const words = headerLines[i].split(' ');
- switch (words[0]) {
- case 'format':
- if (words[1] !== 'binary_little_endian') {
- throw new Error('Unsupported ply format');
- }
- break;
- case 'element':
- element = {
- name: words[1],
- count: parseInt(words[2], 10),
- properties: [],
- storageSizeBytes: 0
- };
- if (element.name === 'chunk') chunkElement = element;
- else if (element.name === 'vertex') vertexElement = element;
- break;
- case 'property': {
- if (!DataTypeMap.has(words[1])) {
- throw new Error(
- `Unrecognized property data type '${words[1]}' in ply header`
- );
- }
- const StorageType = DataTypeMap.get(words[1]);
- const storageSizeByes = StorageType.BYTES_PER_ELEMENT * element.count;
- if (element.name === 'vertex') bytesPerSplat += StorageType.BYTES_PER_ELEMENT;
- element.properties.push({
- type: words[1],
- name: words[2],
- storage: null,
- byteSize: StorageType.BYTES_PER_ELEMENT,
- storageSizeByes: storageSizeByes
- });
- element.storageSizeBytes += storageSizeByes;
- break;
- }
- case HeaderEndToken:
- done = true;
- break;
- default:
- throw new Error(
- `Unrecognized header value '${words[0]}' in ply header`
- );
- }
- if (done) break;
- }
- return {
- 'chunkElement': chunkElement,
- 'vertexElement': vertexElement,
- 'bytesPerSplat': bytesPerSplat,
- 'headerSizeBytes': headerText.indexOf(HeaderEndToken) + HeaderEndToken.length + 1,
- 'sphericalHarmonicsDegree': 0
- };
- }
- static decodeHeader(plyBuffer) {
- /**
- * Searches for the first occurrence of a sequence within a buffer.
- * @example
- * find(new Uint8Array([1, 2, 3, 4]), new Uint8Array([3, 4])); // 2
- * @param {Uint8Array} buf - The buffer in which to search.
- * @param {Uint8Array} search - The sequence to search for.
- * @return {number} The index of the first occurrence of the search sequence in the buffer, or -1 if not found.
- */
- const find = (buf, search) => {
- const endIndex = buf.length - search.length;
- let i;
- let j;
- for (i = 0; i <= endIndex; ++i) {
- for (j = 0; j < search.length; ++j) {
- if (buf[i + j] !== search[j]) {
- break;
- }
- }
- if (j === search.length) {
- return i;
- }
- }
- return -1;
- };
- /**
- * Checks if array 'a' starts with the same elements as array 'b'.
- * @example
- * startsWith(new Uint8Array([1, 2, 3, 4]), new Uint8Array([1, 2])); // true
- * @param {Uint8Array} a - The array to check against.
- * @param {Uint8Array} b - The array of elements to look for at the start of 'a'.
- * @return {boolean} - True if 'a' starts with all elements of 'b', otherwise false.
- */
- const startsWith = (a, b) => {
- if (a.length < b.length) {
- return false;
- }
- for (let i = 0; i < b.length; ++i) {
- if (a[i] !== b[i]) {
- return false;
- }
- }
- return true;
- };
- let buf = new Uint8Array(plyBuffer);
- let endHeaderTokenOffset;
- if (buf.length >= HeaderMagicBytes.length && !startsWith(buf, HeaderMagicBytes)) {
- throw new Error('Invalid PLY header');
- }
- endHeaderTokenOffset = find(buf, HeaderEndTokenBytes);
- if (endHeaderTokenOffset === -1) {
- throw new Error('End of PLY header not found');
- }
- const headerText = new TextDecoder('ascii').decode(
- buf.slice(0, endHeaderTokenOffset)
- );
- const {chunkElement, vertexElement, bytesPerSplat} = PlayCanvasCompressedPlyParser.decodeHeaderText(headerText);
- return {
- 'headerSizeBytes': endHeaderTokenOffset + HeaderEndTokenBytes.length,
- 'bytesPerSplat': bytesPerSplat,
- 'chunkElement': chunkElement,
- 'vertexElement': vertexElement
- };
- }
- static readElementData(element, readBuffer, readOffset, fromIndex, toIndex, propertyFilter = null) {
- let dataView = readBuffer instanceof DataView ? readBuffer : new DataView(readBuffer);
- fromIndex = fromIndex || 0;
- toIndex = toIndex || element.count - 1;
- for (let e = fromIndex; e <= toIndex; ++e) {
- for (let j = 0; j < element.properties.length; ++j) {
- const property = element.properties[j];
- const StorageType = DataTypeMap.get(property.type);
- const requiredStorageSizeBytes = StorageType.BYTES_PER_ELEMENT * element.count;
- if ((!property.storage || property.storage.byteLength < requiredStorageSizeBytes) &&
- (!propertyFilter || propertyFilter(property.name))) {
- property.storage = new StorageType(element.count);
- }
- if (property.storage) {
- switch (property.type) {
- case 'char':
- property.storage[e] = dataView.getInt8(readOffset);
- break;
- case 'uchar':
- property.storage[e] = dataView.getUint8(readOffset);
- break;
- case 'short':
- property.storage[e] = dataView.getInt16(readOffset, true);
- break;
- case 'ushort':
- property.storage[e] = dataView.getUint16(readOffset, true);
- break;
- case 'int':
- property.storage[e] = dataView.getInt32(readOffset, true);
- break;
- case 'uint':
- property.storage[e] = dataView.getUint32(readOffset, true);
- break;
- case 'float':
- property.storage[e] = dataView.getFloat32(readOffset, true);
- break;
- case 'double':
- property.storage[e] = dataView.getFloat64(readOffset, true);
- break;
- }
- }
- readOffset += property.byteSize;
- }
- }
- return readOffset;
- }
- static readPly(plyBuffer, propertyFilter = null) {
- const header = PlayCanvasCompressedPlyParser.decodeHeader(plyBuffer);
- let readIndex = PlayCanvasCompressedPlyParser.readElementData(header.chunkElement, plyBuffer,
- header.headerSizeBytes, null, null, propertyFilter);
- PlayCanvasCompressedPlyParser.readElementData(header.vertexElement, plyBuffer, readIndex, null, null, propertyFilter);
- return {
- 'chunkElement': header.chunkElement,
- 'vertexElement': header.vertexElement
- };
- }
- static getElementStorageArrays(chunkElement, vertexElement) {
- const minX = getElementPropStorage(chunkElement, 'min_x');
- const minY = getElementPropStorage(chunkElement, 'min_y');
- const minZ = getElementPropStorage(chunkElement, 'min_z');
- const maxX = getElementPropStorage(chunkElement, 'max_x');
- const maxY = getElementPropStorage(chunkElement, 'max_y');
- const maxZ = getElementPropStorage(chunkElement, 'max_z');
- const minScaleX = getElementPropStorage(chunkElement, 'min_scale_x');
- const minScaleY = getElementPropStorage(chunkElement, 'min_scale_y');
- const minScaleZ = getElementPropStorage(chunkElement, 'min_scale_z');
- const maxScaleX = getElementPropStorage(chunkElement, 'max_scale_x');
- const maxScaleY = getElementPropStorage(chunkElement, 'max_scale_y');
- const maxScaleZ = getElementPropStorage(chunkElement, 'max_scale_z');
- const position = getElementPropStorage(vertexElement, 'packed_position');
- const rotation = getElementPropStorage(vertexElement, 'packed_rotation');
- const scale = getElementPropStorage(vertexElement, 'packed_scale');
- const color = getElementPropStorage(vertexElement, 'packed_color');
- return {
- positionExtremes: {
- minX, maxX,
- minY, maxY,
- minZ, maxZ
- },
- scaleExtremes: {
- minScaleX, maxScaleX, minScaleY,
- maxScaleY, minScaleZ, maxScaleZ
- },
- position,
- rotation,
- scale,
- color
- };
- }
- static decompressSplat = function() {
- const p = new THREE.Vector3();
- const r = new THREE.Quaternion();
- const s = new THREE.Vector3();
- const c = new THREE.Vector4();
- const OFFSET = UncompressedSplatArray.OFFSET;
- return function(index, chunkSplatIndexOffset, positionArray, positionExtremes, scaleArray, scaleExtremes,
- rotationArray, colorArray, outSplat) {
- outSplat = outSplat || UncompressedSplatArray.createSplat();
- const chunkIndex = Math.floor((chunkSplatIndexOffset + index) / 256);
- unpack111011(p, positionArray[index]);
- unpackRot(r, rotationArray[index]);
- unpack111011(s, scaleArray[index]);
- unpack8888(c, colorArray[index]);
- outSplat[OFFSET.X] = lerp(positionExtremes.minX[chunkIndex], positionExtremes.maxX[chunkIndex], p.x);
- outSplat[OFFSET.Y] = lerp(positionExtremes.minY[chunkIndex], positionExtremes.maxY[chunkIndex], p.y);
- outSplat[OFFSET.Z] = lerp(positionExtremes.minZ[chunkIndex], positionExtremes.maxZ[chunkIndex], p.z);
- outSplat[OFFSET.ROTATION0] = r.x;
- outSplat[OFFSET.ROTATION1] = r.y;
- outSplat[OFFSET.ROTATION2] = r.z;
- outSplat[OFFSET.ROTATION3] = r.w;
- outSplat[OFFSET.SCALE0] = Math.exp(lerp(scaleExtremes.minScaleX[chunkIndex], scaleExtremes.maxScaleX[chunkIndex], s.x));
- outSplat[OFFSET.SCALE1] = Math.exp(lerp(scaleExtremes.minScaleY[chunkIndex], scaleExtremes.maxScaleY[chunkIndex], s.y));
- outSplat[OFFSET.SCALE2] = Math.exp(lerp(scaleExtremes.minScaleZ[chunkIndex], scaleExtremes.maxScaleZ[chunkIndex], s.z));
- outSplat[OFFSET.FDC0] = clamp(Math.floor(c.x * 255), 0, 255);
- outSplat[OFFSET.FDC1] = clamp(Math.floor(c.y * 255), 0, 255);
- outSplat[OFFSET.FDC2] = clamp(Math.floor(c.z * 255), 0, 255);
- outSplat[OFFSET.OPACITY] = clamp(Math.floor(c.w * 255), 0, 255);
- return outSplat;
- };
- }();
- static parseToUncompressedSplatBufferSection(chunkElement, vertexElement, fromIndex, toIndex, chunkSplatIndexOffset,
- vertexDataBuffer, veretxReadOffset, outBuffer, outOffset, propertyFilter = null) {
- PlayCanvasCompressedPlyParser.readElementData(vertexElement, vertexDataBuffer, veretxReadOffset, fromIndex, toIndex, propertyFilter);
- const outBytesPerSplat = SplatBuffer.CompressionLevels[0].SphericalHarmonicsDegrees[0].BytesPerSplat;
- const { positionExtremes, scaleExtremes, position, rotation, scale, color } =
- PlayCanvasCompressedPlyParser.getElementStorageArrays(chunkElement, vertexElement);
- const tempSplat = UncompressedSplatArray.createSplat();
- for (let i = fromIndex; i <= toIndex; ++i) {
- PlayCanvasCompressedPlyParser.decompressSplat(i, chunkSplatIndexOffset, position, positionExtremes,
- scale, scaleExtremes, rotation, color, tempSplat);
- const outBase = i * outBytesPerSplat + outOffset;
- SplatBuffer.writeSplatDataToSectionBuffer(tempSplat, outBuffer, outBase, 0, 0);
- }
- }
- static parseToUncompressedSplatArray(plyBuffer) {
- const { chunkElement, vertexElement } = PlayCanvasCompressedPlyParser.readPly(plyBuffer);
- const splatArray = new UncompressedSplatArray();
- const { positionExtremes, scaleExtremes, position, rotation, scale, color } =
- PlayCanvasCompressedPlyParser.getElementStorageArrays(chunkElement, vertexElement);
- for (let i = 0; i < vertexElement.count; ++i) {
- splatArray.addDefaultSplat();
- const newSplat = splatArray.getSplat(splatArray.splatCount - 1);
- PlayCanvasCompressedPlyParser.decompressSplat(i, 0, position, positionExtremes, scale, scaleExtremes, rotation, color, newSplat);
- }
- const mat = new THREE.Matrix4();
- mat.identity();
- return splatArray;
- }
- }
- const PlyFormat = {
- 'INRIAV1': 0,
- 'INRIAV2': 1,
- 'PlayCanvasCompressed': 2
- };
- const [
- FieldSizeIdDouble, FieldSizeIdInt, FieldSizeIdUInt, FieldSizeIdFloat, FieldSizeIdShort, FieldSizeIdUShort, FieldSizeIdUChar
- ] = [0, 1, 2, 3, 4, 5, 6];
- const FieldSizeStringMap = {
- 'double': FieldSizeIdDouble,
- 'int': FieldSizeIdInt,
- 'uint': FieldSizeIdUInt,
- 'float': FieldSizeIdFloat,
- 'short': FieldSizeIdShort,
- 'ushort': FieldSizeIdUShort,
- 'uchar': FieldSizeIdUChar,
- };
- const FieldSize = {
- [FieldSizeIdDouble]: 8,
- [FieldSizeIdInt]: 4,
- [FieldSizeIdUInt]: 4,
- [FieldSizeIdFloat]: 4,
- [FieldSizeIdShort]: 2,
- [FieldSizeIdUShort]: 2,
- [FieldSizeIdUChar]: 1,
- };
- class PlyParserUtils {
- static HeaderEndToken = 'end_header';
- constructor() {
- }
- decodeSectionHeader(headerLines, fieldNameIdMap, headerStartLine = 0) {
- const extractedLines = [];
- let processingSection = false;
- let headerEndLine = -1;
- let vertexCount = 0;
- let endOfHeader = false;
- let sectionName = null;
- const fieldIds = [];
- const fieldTypes = [];
- const allFieldNames = [];
- const usedFieldNames = [];
- const fieldTypesByName = {};
- for (let i = headerStartLine; i < headerLines.length; i++) {
- const line = headerLines[i].trim();
- if (line.startsWith('element')) {
- if (processingSection) {
- headerEndLine--;
- break;
- } else {
- processingSection = true;
- headerStartLine = i;
- headerEndLine = i;
- const lineComponents = line.split(' ');
- let validComponents = 0;
- for (let lineComponent of lineComponents) {
- const trimmedComponent = lineComponent.trim();
- if (trimmedComponent.length > 0) {
- validComponents++;
- if (validComponents === 2) {
- sectionName = trimmedComponent;
- } else if (validComponents === 3) {
- vertexCount = parseInt(trimmedComponent);
- }
- }
- }
- }
- } else if (line.startsWith('property')) {
- const fieldMatch = line.match(/(\w+)\s+(\w+)\s+(\w+)/);
- if (fieldMatch) {
- const fieldTypeStr = fieldMatch[2];
- const fieldName = fieldMatch[3];
- allFieldNames.push(fieldName);
- const fieldId = fieldNameIdMap[fieldName];
- fieldTypesByName[fieldName] = fieldTypeStr;
- const fieldType = FieldSizeStringMap[fieldTypeStr];
- if (fieldId !== undefined) {
- usedFieldNames.push(fieldName);
- fieldIds.push(fieldId);
- fieldTypes[fieldId] = fieldType;
- }
- }
- }
- if (line === PlyParserUtils.HeaderEndToken) {
- endOfHeader = true;
- break;
- }
- if (processingSection) {
- extractedLines.push(line);
- headerEndLine++;
- }
- }
- const fieldOffsets = [];
- let bytesPerVertex = 0;
- for (let fieldName of allFieldNames) {
- const fieldType = fieldTypesByName[fieldName];
- if (fieldTypesByName.hasOwnProperty(fieldName)) {
- const fieldId = fieldNameIdMap[fieldName];
- if (fieldId !== undefined) {
- fieldOffsets[fieldId] = bytesPerVertex;
- }
- }
- bytesPerVertex += FieldSize[FieldSizeStringMap[fieldType]];
- }
- const sphericalHarmonics = this.decodeSphericalHarmonicsFromSectionHeader(allFieldNames, fieldNameIdMap);
- return {
- 'headerLines': extractedLines,
- 'headerStartLine': headerStartLine,
- 'headerEndLine': headerEndLine,
- 'fieldTypes': fieldTypes,
- 'fieldIds': fieldIds,
- 'fieldOffsets': fieldOffsets,
- 'bytesPerVertex': bytesPerVertex,
- 'vertexCount': vertexCount,
- 'dataSizeBytes': bytesPerVertex * vertexCount,
- 'endOfHeader': endOfHeader,
- 'sectionName': sectionName,
- 'sphericalHarmonicsDegree': sphericalHarmonics.degree,
- 'sphericalHarmonicsCoefficientsPerChannel': sphericalHarmonics.coefficientsPerChannel,
- 'sphericalHarmonicsDegree1Fields': sphericalHarmonics.degree1Fields,
- 'sphericalHarmonicsDegree2Fields': sphericalHarmonics.degree2Fields
- };
- }
- decodeSphericalHarmonicsFromSectionHeader(fieldNames, fieldNameIdMap) {
- let sphericalHarmonicsFieldCount = 0;
- let coefficientsPerChannel = 0;
- for (let fieldName of fieldNames) {
- if (fieldName.startsWith('f_rest')) sphericalHarmonicsFieldCount++;
- }
- coefficientsPerChannel = sphericalHarmonicsFieldCount / 3;
- let degree = 0;
- if (coefficientsPerChannel >= 3) degree = 1;
- if (coefficientsPerChannel >= 8) degree = 2;
- let degree1Fields = [];
- let degree2Fields = [];
- for (let rgb = 0; rgb < 3; rgb++) {
- if (degree >= 1) {
- for (let i = 0; i < 3; i++) {
- degree1Fields.push(fieldNameIdMap['f_rest_' + (i + coefficientsPerChannel * rgb)]);
- }
- }
- if (degree >= 2) {
- for (let i = 0; i < 5; i++) {
- degree2Fields.push(fieldNameIdMap['f_rest_' + (i + coefficientsPerChannel * rgb + 3)]);
- }
- }
- }
- return {
- 'degree': degree,
- 'coefficientsPerChannel': coefficientsPerChannel,
- 'degree1Fields': degree1Fields,
- 'degree2Fields': degree2Fields
- };
- }
- static getHeaderSectionNames(headerLines) {
- const sectionNames = [];
- for (let headerLine of headerLines) {
- if (headerLine.startsWith('element')) {
- const lineComponents = headerLine.split(' ');
- let validComponents = 0;
- for (let lineComponent of lineComponents) {
- const trimmedComponent = lineComponent.trim();
- if (trimmedComponent.length > 0) {
- validComponents++;
- if (validComponents === 2) {
- sectionNames.push(trimmedComponent);
- }
- }
- }
- }
- }
- return sectionNames;
- }
- static checkTextForEndHeader(endHeaderTestText) {
- if (endHeaderTestText.includes(PlyParserUtils.HeaderEndToken)) {
- return true;
- }
- return false;
- }
- static checkBufferForEndHeader(buffer, searchOfset, chunkSize, decoder) {
- const endHeaderTestChunk = new Uint8Array(buffer, Math.max(0, searchOfset - chunkSize), chunkSize);
- const endHeaderTestText = decoder.decode(endHeaderTestChunk);
- return PlyParserUtils.checkTextForEndHeader(endHeaderTestText);
- }
- static extractHeaderFromBufferToText(plyBuffer) {
- const decoder = new TextDecoder();
- let headerOffset = 0;
- let headerText = '';
- const readChunkSize = 100;
- while (true) {
- if (headerOffset + readChunkSize >= plyBuffer.byteLength) {
- throw new Error('End of file reached while searching for end of header');
- }
- const headerChunk = new Uint8Array(plyBuffer, headerOffset, readChunkSize);
- headerText += decoder.decode(headerChunk);
- headerOffset += readChunkSize;
- if (PlyParserUtils.checkBufferForEndHeader(plyBuffer, headerOffset, readChunkSize * 2, decoder)) {
- break;
- }
- }
- return headerText;
- }
- readHeaderFromBuffer(plyBuffer) {
- const decoder = new TextDecoder();
- let headerOffset = 0;
- let headerText = '';
- const readChunkSize = 100;
- while (true) {
- if (headerOffset + readChunkSize >= plyBuffer.byteLength) {
- throw new Error('End of file reached while searching for end of header');
- }
- const headerChunk = new Uint8Array(plyBuffer, headerOffset, readChunkSize);
- headerText += decoder.decode(headerChunk);
- headerOffset += readChunkSize;
- if (PlyParserUtils.checkBufferForEndHeader(plyBuffer, headerOffset, readChunkSize * 2, decoder)) {
- break;
- }
- }
- return headerText;
- }
- static convertHeaderTextToLines(headerText) {
- const headerLines = headerText.split('\n');
- const prunedLines = [];
- for (let i = 0; i < headerLines.length; i++) {
- const line = headerLines[i].trim();
- prunedLines.push(line);
- if (line === PlyParserUtils.HeaderEndToken) {
- break;
- }
- }
- return prunedLines;
- }
- static determineHeaderFormatFromHeaderText(headertText) {
- const headerLines = PlyParserUtils.convertHeaderTextToLines(headertText);
- let format = PlyFormat.INRIAV1;
- for (let i = 0; i < headerLines.length; i++) {
- const line = headerLines[i].trim();
- if (line.startsWith('element chunk') || line.match(/[A-Za-z]*packed_[A-Za-z]*/)) {
- format = PlyFormat.PlayCanvasCompressed;
- } else if (line.startsWith('element codebook_centers')) {
- format = PlyFormat.INRIAV2;
- } else if (line === PlyParserUtils.HeaderEndToken) {
- break;
- }
- }
- return format;
- }
- static determineHeaderFormatFromPlyBuffer(plyBuffer) {
- const headertText = PlyParserUtils.extractHeaderFromBufferToText(plyBuffer);
- return PlyParserUtils.determineHeaderFormatFromHeaderText(headertText);
- }
- static readVertex(vertexData, header, row, dataOffset, fieldsToRead, rawVertex, normalize = true) {
- const offset = row * header.bytesPerVertex + dataOffset;
- const fieldOffsets = header.fieldOffsets;
- const fieldTypes = header.fieldTypes;
- for (let fieldId of fieldsToRead) {
- const fieldType = fieldTypes[fieldId];
- if (fieldType === FieldSizeIdFloat) {
- rawVertex[fieldId] = vertexData.getFloat32(offset + fieldOffsets[fieldId], true);
- } else if (fieldType === FieldSizeIdShort) {
- rawVertex[fieldId] = vertexData.getInt16(offset + fieldOffsets[fieldId], true);
- } else if (fieldType === FieldSizeIdUShort) {
- rawVertex[fieldId] = vertexData.getUint16(offset + fieldOffsets[fieldId], true);
- } else if (fieldType === FieldSizeIdInt) {
- rawVertex[fieldId] = vertexData.getInt32(offset + fieldOffsets[fieldId], true);
- } else if (fieldType === FieldSizeIdUInt) {
- rawVertex[fieldId] = vertexData.getUint32(offset + fieldOffsets[fieldId], true);
- } else if (fieldType === FieldSizeIdUChar) {
- if (normalize) {
- rawVertex[fieldId] = vertexData.getUint8(offset + fieldOffsets[fieldId]) / 255.0;
- } else {
- rawVertex[fieldId] = vertexData.getUint8(offset + fieldOffsets[fieldId]);
- }
- }
- }
- }
- }
- const BaseFieldNamesToRead = ['scale_0', 'scale_1', 'scale_2', 'rot_0', 'rot_1', 'rot_2', 'rot_3', 'x', 'y', 'z',
- 'f_dc_0', 'f_dc_1', 'f_dc_2', 'opacity', 'red', 'green', 'blue', 'f_rest_0', 'filter_3D'];
- const BaseFieldsToReadIndexes = BaseFieldNamesToRead.map((e, i) => i);
- const [
- // eslint-disable-next-line max-len
- SCALE_0, SCALE_1, SCALE_2, ROT_0, ROT_1, ROT_2, ROT_3, X, Y, Z, F_DC_0, F_DC_1, F_DC_2, OPACITY, RED, GREEN, BLUE, F_REST_0, FILTER_3D
- ] = BaseFieldsToReadIndexes;
- class INRIAV1PlyParser {
- constructor() {
- this.plyParserutils = new PlyParserUtils();
- }
- decodeHeaderLines(headerLines) {
- let shLineCount = 0;
- headerLines.forEach((line) => {
- if (line.includes('f_rest_')) shLineCount++;
- });
- let shFieldsToReadCount = 0;
- if (shLineCount >= 45) {
- shFieldsToReadCount = 45;
- } else if (shLineCount >= 24) {
- shFieldsToReadCount = 24;
- } else if (shLineCount >= 9) {
- shFieldsToReadCount = 9;
- }
- const shFieldIndexesToMap = Array.from(Array(Math.max(shFieldsToReadCount - 1, 0)));
- let shRemainingFieldNamesToRead = shFieldIndexesToMap.map((element, index) => `f_rest_${index + 1}`);
- const fieldNamesToRead = [...BaseFieldNamesToRead, ...shRemainingFieldNamesToRead];
- const fieldsToReadIndexes = fieldNamesToRead.map((e, i) => i);
- const fieldNameIdMap = fieldsToReadIndexes.reduce((acc, element) => {
- acc[fieldNamesToRead[element]] = element;
- return acc;
- }, {});
- const header = this.plyParserutils.decodeSectionHeader(headerLines, fieldNameIdMap, 0);
- header.splatCount = header.vertexCount;
- header.bytesPerSplat = header.bytesPerVertex;
- header.fieldsToReadIndexes = fieldsToReadIndexes;
- return header;
- }
- decodeHeaderText(headerText) {
- const headerLines = PlyParserUtils.convertHeaderTextToLines(headerText);
- const header = this.decodeHeaderLines(headerLines);
- header.headerText = headerText;
- header.headerSizeBytes = headerText.indexOf(PlyParserUtils.HeaderEndToken) + PlyParserUtils.HeaderEndToken.length + 1;
- return header;
- }
- decodeHeaderFromBuffer(plyBuffer) {
- const headerText = this.plyParserutils.readHeaderFromBuffer(plyBuffer);
- return this.decodeHeaderText(headerText);
- }
- findSplatData(plyBuffer, header) {
- return new DataView(plyBuffer, header.headerSizeBytes);
- }
- parseToUncompressedSplatBufferSection(header, fromSplat, toSplat, splatData, splatDataOffset,
- toBuffer, toOffset, outSphericalHarmonicsDegree = 0) {
- outSphericalHarmonicsDegree = Math.min(outSphericalHarmonicsDegree, header.sphericalHarmonicsDegree);
- const outBytesPerSplat = SplatBuffer.CompressionLevels[0].SphericalHarmonicsDegrees[outSphericalHarmonicsDegree].BytesPerSplat;
- for (let i = fromSplat; i <= toSplat; i++) {
- const parsedSplat = INRIAV1PlyParser.parseToUncompressedSplat(splatData, i, header,
- splatDataOffset, outSphericalHarmonicsDegree);
- const outBase = i * outBytesPerSplat + toOffset;
- SplatBuffer.writeSplatDataToSectionBuffer(parsedSplat, toBuffer, outBase, 0, outSphericalHarmonicsDegree);
- }
- }
- decodeSectionSplatData(sectionSplatData, splatCount, sectionHeader, outSphericalHarmonicsDegree) {
- outSphericalHarmonicsDegree = Math.min(outSphericalHarmonicsDegree, sectionHeader.sphericalHarmonicsDegree);
- const splatArray = new UncompressedSplatArray(outSphericalHarmonicsDegree);
- for (let row = 0; row < splatCount; row++) {
- const newSplat = INRIAV1PlyParser.parseToUncompressedSplat(sectionSplatData, row, sectionHeader,
- 0, outSphericalHarmonicsDegree);
- splatArray.addSplat(newSplat);
- }
- return splatArray;
- }
- static parseToUncompressedSplat = function() {
- let rawSplat = [];
- const tempRotation = new THREE.Quaternion();
- const OFFSET_X = UncompressedSplatArray.OFFSET.X;
- const OFFSET_Y = UncompressedSplatArray.OFFSET.Y;
- const OFFSET_Z = UncompressedSplatArray.OFFSET.Z;
- const OFFSET_SCALE0 = UncompressedSplatArray.OFFSET.SCALE0;
- const OFFSET_SCALE1 = UncompressedSplatArray.OFFSET.SCALE1;
- const OFFSET_SCALE2 = UncompressedSplatArray.OFFSET.SCALE2;
- const OFFSET_ROTATION0 = UncompressedSplatArray.OFFSET.ROTATION0;
- const OFFSET_ROTATION1 = UncompressedSplatArray.OFFSET.ROTATION1;
- const OFFSET_ROTATION2 = UncompressedSplatArray.OFFSET.ROTATION2;
- const OFFSET_ROTATION3 = UncompressedSplatArray.OFFSET.ROTATION3;
- const OFFSET_FDC0 = UncompressedSplatArray.OFFSET.FDC0;
- const OFFSET_FDC1 = UncompressedSplatArray.OFFSET.FDC1;
- const OFFSET_FDC2 = UncompressedSplatArray.OFFSET.FDC2;
- const OFFSET_OPACITY = UncompressedSplatArray.OFFSET.OPACITY;
- const OFFSET_FRC = [];
- for (let i = 0; i < 45; i++) {
- OFFSET_FRC[i] = UncompressedSplatArray.OFFSET.FRC0 + i;
- }
- return function(splatData, row, header, splatDataOffset = 0, outSphericalHarmonicsDegree = 0) {
- outSphericalHarmonicsDegree = Math.min(outSphericalHarmonicsDegree, header.sphericalHarmonicsDegree);
- INRIAV1PlyParser.readSplat(splatData, header, row, splatDataOffset, rawSplat);
- const newSplat = UncompressedSplatArray.createSplat(outSphericalHarmonicsDegree);
- if (rawSplat[SCALE_0] !== undefined) {
- newSplat[OFFSET_SCALE0] = Math.exp(rawSplat[SCALE_0]);
- newSplat[OFFSET_SCALE1] = Math.exp(rawSplat[SCALE_1]);
- newSplat[OFFSET_SCALE2] = Math.exp(rawSplat[SCALE_2]);
- } else {
- newSplat[OFFSET_SCALE0] = 0.01;
- newSplat[OFFSET_SCALE1] = 0.01;
- newSplat[OFFSET_SCALE2] = 0.01;
- }
- if (rawSplat[F_DC_0] !== undefined) {
- const SH_C0 = 0.28209479177387814;
- newSplat[OFFSET_FDC0] = (0.5 + SH_C0 * rawSplat[F_DC_0]) * 255;
- newSplat[OFFSET_FDC1] = (0.5 + SH_C0 * rawSplat[F_DC_1]) * 255;
- newSplat[OFFSET_FDC2] = (0.5 + SH_C0 * rawSplat[F_DC_2]) * 255;
- } else if (rawSplat[RED] !== undefined) {
- newSplat[OFFSET_FDC0] = rawSplat[RED] * 255;
- newSplat[OFFSET_FDC1] = rawSplat[GREEN] * 255;
- newSplat[OFFSET_FDC2] = rawSplat[BLUE] * 255;
- } else {
- newSplat[OFFSET_FDC0] = 0;
- newSplat[OFFSET_FDC1] = 0;
- newSplat[OFFSET_FDC2] = 0;
- }
- if (rawSplat[OPACITY] !== undefined) {
- newSplat[OFFSET_OPACITY] = (1 / (1 + Math.exp(-rawSplat[OPACITY]))) * 255;
- }
- if (rawSplat[FILTER_3D] !== undefined) {
- const filter3d = rawSplat[FILTER_3D];
- const filter3dSquare = filter3d*filter3d;
- // x2,y2,z2, 2代表平方
- const scaleXSquare = newSplat[OFFSET_SCALE0]*newSplat[OFFSET_SCALE0];
- const scaleYSquare = newSplat[OFFSET_SCALE1]*newSplat[OFFSET_SCALE1];
- const scaleZSquare = newSplat[OFFSET_SCALE2]*newSplat[OFFSET_SCALE2];
- const det1 = scaleXSquare*scaleYSquare*scaleZSquare; // 计算所有列元素乘积之和 x2*y2*z2
- // x2+d2, y2+d2, z2+d2
- const scaleXAfterSquare = scaleXSquare + filter3dSquare;
- const scaleYAfterSquare = scaleYSquare + filter3dSquare;
- const scaleZAfterSquare = scaleZSquare + filter3dSquare;
- // (x2+d2)*(y2+d2)*(z2+d2)
- const det2 = scaleXAfterSquare*scaleYAfterSquare*scaleZAfterSquare;
- const coef = Math.sqrt(det1 / det2); // sqrt( (x2*y2*z2)/{(x2+d2)*(y2+d2)*(z2+d2)} )
- // sqrt(x2+d2),sqrt(y2+d2), sqrt(z2+d2)
- newSplat[OFFSET_SCALE0] = Math.sqrt(scaleXAfterSquare);
- newSplat[OFFSET_SCALE1] = Math.sqrt(scaleYAfterSquare);
- newSplat[OFFSET_SCALE2] = Math.sqrt(scaleZAfterSquare);
- newSplat[OFFSET_OPACITY] = newSplat[OFFSET_OPACITY] * coef;
- }
- newSplat[OFFSET_FDC0] = clamp(Math.floor(newSplat[OFFSET_FDC0]), 0, 255);
- newSplat[OFFSET_FDC1] = clamp(Math.floor(newSplat[OFFSET_FDC1]), 0, 255);
- newSplat[OFFSET_FDC2] = clamp(Math.floor(newSplat[OFFSET_FDC2]), 0, 255);
- newSplat[OFFSET_OPACITY] = clamp(Math.floor(newSplat[OFFSET_OPACITY]), 0, 255);
- if (outSphericalHarmonicsDegree >= 1) {
- if (rawSplat[F_REST_0] !== undefined) {
- for (let i = 0; i < 9; i++) {
- newSplat[OFFSET_FRC[i]] = rawSplat[header.sphericalHarmonicsDegree1Fields[i]];
- }
- if (outSphericalHarmonicsDegree >= 2) {
- for (let i = 0; i < 15; i++) {
- newSplat[OFFSET_FRC[9 + i]] = rawSplat[header.sphericalHarmonicsDegree2Fields[i]];
- }
- }
- }
- }
- tempRotation.set(rawSplat[ROT_0], rawSplat[ROT_1], rawSplat[ROT_2], rawSplat[ROT_3]);
- tempRotation.normalize();
- newSplat[OFFSET_ROTATION0] = tempRotation.x;
- newSplat[OFFSET_ROTATION1] = tempRotation.y;
- newSplat[OFFSET_ROTATION2] = tempRotation.z;
- newSplat[OFFSET_ROTATION3] = tempRotation.w;
- newSplat[OFFSET_X] = rawSplat[X];
- newSplat[OFFSET_Y] = rawSplat[Y];
- newSplat[OFFSET_Z] = rawSplat[Z];
- return newSplat;
- };
- }();
- static readSplat(splatData, header, row, dataOffset, rawSplat) {
- return PlyParserUtils.readVertex(splatData, header, row, dataOffset, header.fieldsToReadIndexes, rawSplat, true);
- }
- parseToUncompressedSplatArray(plyBuffer, outSphericalHarmonicsDegree = 0) {
- const header = this.decodeHeaderFromBuffer(plyBuffer);
- const splatCount = header.splatCount;
- const splatData = this.findSplatData(plyBuffer, header);
- const splatArray = this.decodeSectionSplatData(splatData, splatCount, header, outSphericalHarmonicsDegree);
- return splatArray;
- }
- }
- const CodeBookEntryNamesToRead = [
- 'features_dc', 'features_rest_0', 'features_rest_1', 'features_rest_2', 'features_rest_3', 'features_rest_4', 'features_rest_5',
- 'features_rest_6', 'features_rest_7', 'features_rest_8', 'features_rest_9', 'features_rest_10', 'features_rest_11', 'features_rest_12',
- 'features_rest_13', 'features_rest_14', 'opacity', 'scaling', 'rotation_re', 'rotation_im'
- ];
- const CodeBookEntriesToReadIndexes = CodeBookEntryNamesToRead.map((e, i) => i);
- const [
- CB_FEATURES_DC, CB_FEATURES_REST_0, CB_FEATURES_REST_3, CB_OPACITY, CB_SCALING, CB_ROTATION_RE, CB_ROTATION_IM
- ] = [0, 1, 4, 16, 17, 18, 19];
- const FieldNamesToRead = ['scale_0', 'scale_1', 'scale_2', 'rot_0', 'rot_1', 'rot_2', 'rot_3',
- 'x', 'y', 'z', 'f_dc_0', 'f_dc_1', 'f_dc_2', 'opacity', 'red', 'green', 'blue',
- 'f_rest_0', 'f_rest_1', 'f_rest_2', 'f_rest_3', 'f_rest_4', 'f_rest_5', 'f_rest_6', 'f_rest_7', 'f_rest_8',
- 'f_rest_9', 'f_rest_10', 'f_rest_11', 'f_rest_12', 'f_rest_13', 'f_rest_14', 'f_rest_15', 'f_rest_16',
- 'f_rest_17', 'f_rest_18', 'f_rest_19', 'f_rest_20', 'f_rest_21', 'f_rest_22', 'f_rest_23', 'f_rest_24',
- 'f_rest_25', 'f_rest_26', 'f_rest_27', 'f_rest_28', 'f_rest_29', 'f_rest_30', 'f_rest_31', 'f_rest_32',
- 'f_rest_33', 'f_rest_34', 'f_rest_35', 'f_rest_36', 'f_rest_37', 'f_rest_38', 'f_rest_39', 'f_rest_40',
- 'f_rest_41', 'f_rest_42', 'f_rest_43', 'f_rest_44', 'f_rest_45'
- ];
- const FieldsToReadIndexes = FieldNamesToRead.map((e, i) => i);
- const [
- PLY_SCALE_0, PLY_SCALE_1, PLY_SCALE_2, PLY_ROT_0, PLY_ROT_1, PLY_ROT_2, PLY_ROT_3, PLY_X, PLY_Y, PLY_Z,
- PLY_F_DC_0, PLY_F_DC_1, PLY_F_DC_2, PLY_OPACITY,
- ] = FieldsToReadIndexes;
- const PLY_RED = PLY_F_DC_0;
- const PLY_GREEN = PLY_F_DC_1;
- const PLY_BLUE = PLY_F_DC_2;
- const fromHalfFloat = (hf) =>{
- const t = (31744 & hf) >> 10;
- const a = 1023 & hf;
- return (hf >> 15 ? -1 : 1)*(t ? t === 31 ? a ? NaN : 1/0 : Math.pow(2, t - 15) *( 1 + a / 1024) : a / 1024*6103515625e-14);
- };
- class INRIAV2PlyParser {
- constructor() {
- this.plyParserutils = new PlyParserUtils();
- }
- decodeSectionHeadersFromHeaderLines(headerLines) {
- const fieldNameIdMap = FieldsToReadIndexes.reduce((acc, element) => {
- acc[FieldNamesToRead[element]] = element;
- return acc;
- }, {});
- const codeBookEntriesToReadIdMap = CodeBookEntriesToReadIndexes.reduce((acc, element) => {
- acc[CodeBookEntryNamesToRead[element]] = element;
- return acc;
- }, {});
- const sectionNames = PlyParserUtils.getHeaderSectionNames(headerLines);
- let codeBookSectionIndex;
- for (let s = 0; s < sectionNames.length; s++) {
- const sectionName = sectionNames[s];
- if (sectionName === 'codebook_centers') {
- codeBookSectionIndex = s;
- }
- }
- let currentStartLine = 0;
- let lastSectionFound = false;
- const sectionHeaders = [];
- let sectionIndex = 0;
- while (!lastSectionFound) {
- let sectionHeader;
- if (sectionIndex === codeBookSectionIndex) {
- sectionHeader = this.plyParserutils.decodeSectionHeader(headerLines, codeBookEntriesToReadIdMap, currentStartLine);
- } else {
- sectionHeader = this.plyParserutils.decodeSectionHeader(headerLines, fieldNameIdMap, currentStartLine);
- }
- lastSectionFound = sectionHeader.endOfHeader;
- currentStartLine = sectionHeader.headerEndLine + 1;
- if (!lastSectionFound) {
- sectionHeader.splatCount = sectionHeader.vertexCount;
- sectionHeader.bytesPerSplat = sectionHeader.bytesPerVertex;
- }
- sectionHeaders.push(sectionHeader);
- sectionIndex++;
- }
- return sectionHeaders;
- }
- decodeSectionHeadersFromHeaderText(headerText) {
- const headerLines = PlyParserUtils.convertHeaderTextToLines(headerText);
- return this.decodeSectionHeadersFromHeaderLines(headerLines);
- }
- getSplatCountFromSectionHeaders(sectionHeaders) {
- let splatCount = 0;
- for (let sectionHeader of sectionHeaders) {
- if (sectionHeader.sectionName !== 'codebook_centers') {
- splatCount += sectionHeader.vertexCount;
- }
- }
- return splatCount;
- }
- decodeHeaderFromHeaderText(headerText) {
- const headerSizeBytes = headerText.indexOf(PlyParserUtils.HeaderEndToken) + PlyParserUtils.HeaderEndToken.length + 1;
- const sectionHeaders = this.decodeSectionHeadersFromHeaderText(headerText);
- const splatCount = this.getSplatCountFromSectionHeaders(sectionHeaders);
- return {
- 'headerSizeBytes': headerSizeBytes,
- 'sectionHeaders': sectionHeaders,
- 'splatCount': splatCount
- };
- }
- decodeHeaderFromBuffer(plyBuffer) {
- const headerText = this.plyParserutils.readHeaderFromBuffer(plyBuffer);
- return this.decodeHeaderFromHeaderText(headerText);
- }
- findVertexData(plyBuffer, header, targetSection) {
- let byteOffset = header.headerSizeBytes;
- for (let s = 0; s < targetSection && s < header.sectionHeaders.length; s++) {
- const sectionHeader = header.sectionHeaders[s];
- byteOffset += sectionHeader.dataSizeBytes;
- }
- return new DataView(plyBuffer, byteOffset, header.sectionHeaders[targetSection].dataSizeBytes);
- }
- decodeCodeBook(codeBookData, sectionHeader) {
- const rawVertex = [];
- const codeBook = [];
- for (let row = 0; row < sectionHeader.vertexCount; row++) {
- PlyParserUtils.readVertex(codeBookData, sectionHeader, row, 0, CodeBookEntriesToReadIndexes, rawVertex);
- for (let index of CodeBookEntriesToReadIndexes) {
- const codeBookElementOffset = CodeBookEntriesToReadIndexes[index];
- let codeBookPage = codeBook[codeBookElementOffset];
- if (!codeBookPage) {
- codeBook[codeBookElementOffset] = codeBookPage = [];
- }
- codeBookPage.push(rawVertex[index]);
- }
- }
- for (let page = 0; page < codeBook.length; page++) {
- const codeBookPage = codeBook[page];
- const SH_C0 = 0.28209479177387814;
- for (let i = 0; i < codeBookPage.length; i++) {
- const baseValue = fromHalfFloat(codeBookPage[i]);
- if (page === CB_OPACITY) {
- codeBookPage[i] = Math.round((1 / (1 + Math.exp(-baseValue))) * 255);
- } else if (page === CB_FEATURES_DC) {
- codeBookPage[i] = Math.round((0.5 + SH_C0 * baseValue) * 255);
- } else if (page === CB_SCALING) {
- codeBookPage[i] = Math.exp(baseValue);
- } else {
- codeBookPage[i] = baseValue;
- }
- }
- }
- return codeBook;
- }
- decodeSectionSplatData(sectionSplatData, splatCount, sectionHeader, codeBook, outSphericalHarmonicsDegree) {
- outSphericalHarmonicsDegree = Math.min(outSphericalHarmonicsDegree, sectionHeader.sphericalHarmonicsDegree);
- const splatArray = new UncompressedSplatArray(outSphericalHarmonicsDegree);
- for (let row = 0; row < splatCount; row++) {
- const newSplat = INRIAV2PlyParser.parseToUncompressedSplat(sectionSplatData, row, sectionHeader, codeBook,
- 0, outSphericalHarmonicsDegree);
- splatArray.addSplat(newSplat);
- }
- return splatArray;
- }
- static parseToUncompressedSplat = function() {
- let rawSplat = [];
- const tempRotation = new THREE.Quaternion();
- const OFFSET_X = UncompressedSplatArray.OFFSET.X;
- const OFFSET_Y = UncompressedSplatArray.OFFSET.Y;
- const OFFSET_Z = UncompressedSplatArray.OFFSET.Z;
- const OFFSET_SCALE0 = UncompressedSplatArray.OFFSET.SCALE0;
- const OFFSET_SCALE1 = UncompressedSplatArray.OFFSET.SCALE1;
- const OFFSET_SCALE2 = UncompressedSplatArray.OFFSET.SCALE2;
- const OFFSET_ROTATION0 = UncompressedSplatArray.OFFSET.ROTATION0;
- const OFFSET_ROTATION1 = UncompressedSplatArray.OFFSET.ROTATION1;
- const OFFSET_ROTATION2 = UncompressedSplatArray.OFFSET.ROTATION2;
- const OFFSET_ROTATION3 = UncompressedSplatArray.OFFSET.ROTATION3;
- const OFFSET_FDC0 = UncompressedSplatArray.OFFSET.FDC0;
- const OFFSET_FDC1 = UncompressedSplatArray.OFFSET.FDC1;
- const OFFSET_FDC2 = UncompressedSplatArray.OFFSET.FDC2;
- const OFFSET_OPACITY = UncompressedSplatArray.OFFSET.OPACITY;
- const OFFSET_FRC = [];
- for (let i = 0; i < 45; i++) {
- OFFSET_FRC[i] = UncompressedSplatArray.OFFSET.FRC0 + i;
- }
- return function(splatData, row, header, codeBook, splatDataOffset = 0, outSphericalHarmonicsDegree = 0) {
- outSphericalHarmonicsDegree = Math.min(outSphericalHarmonicsDegree, header.sphericalHarmonicsDegree);
- INRIAV2PlyParser.readSplat(splatData, header, row, splatDataOffset, rawSplat);
- const newSplat = UncompressedSplatArray.createSplat(outSphericalHarmonicsDegree);
- if (rawSplat[PLY_SCALE_0] !== undefined) {
- newSplat[OFFSET_SCALE0] = codeBook[CB_SCALING][rawSplat[PLY_SCALE_0]];
- newSplat[OFFSET_SCALE1] = codeBook[CB_SCALING][rawSplat[PLY_SCALE_1]];
- newSplat[OFFSET_SCALE2] = codeBook[CB_SCALING][rawSplat[PLY_SCALE_2]];
- } else {
- newSplat[OFFSET_SCALE0] = 0.01;
- newSplat[OFFSET_SCALE1] = 0.01;
- newSplat[OFFSET_SCALE2] = 0.01;
- }
- if (rawSplat[PLY_F_DC_0] !== undefined) {
- newSplat[OFFSET_FDC0] = codeBook[CB_FEATURES_DC][rawSplat[PLY_F_DC_0]];
- newSplat[OFFSET_FDC1] = codeBook[CB_FEATURES_DC][rawSplat[PLY_F_DC_1]];
- newSplat[OFFSET_FDC2] = codeBook[CB_FEATURES_DC][rawSplat[PLY_F_DC_2]];
- } else if (rawSplat[PLY_RED] !== undefined) {
- newSplat[OFFSET_FDC0] = rawSplat[PLY_RED] * 255;
- newSplat[OFFSET_FDC1] = rawSplat[PLY_GREEN] * 255;
- newSplat[OFFSET_FDC2] = rawSplat[PLY_BLUE] * 255;
- } else {
- newSplat[OFFSET_FDC0] = 0;
- newSplat[OFFSET_FDC1] = 0;
- newSplat[OFFSET_FDC2] = 0;
- }
- if (rawSplat[PLY_OPACITY] !== undefined) {
- newSplat[OFFSET_OPACITY] = codeBook[CB_OPACITY][rawSplat[PLY_OPACITY]];
- }
- newSplat[OFFSET_FDC0] = clamp(Math.floor(newSplat[OFFSET_FDC0]), 0, 255);
- newSplat[OFFSET_FDC1] = clamp(Math.floor(newSplat[OFFSET_FDC1]), 0, 255);
- newSplat[OFFSET_FDC2] = clamp(Math.floor(newSplat[OFFSET_FDC2]), 0, 255);
- newSplat[OFFSET_OPACITY] = clamp(Math.floor(newSplat[OFFSET_OPACITY]), 0, 255);
- if (outSphericalHarmonicsDegree >= 1 && header.sphericalHarmonicsDegree >= 1) {
- for (let i = 0; i < 9; i++) {
- const codeBookPage = codeBook[CB_FEATURES_REST_0 + i % 3];
- newSplat[OFFSET_FRC[i]] = codeBookPage[rawSplat[header.sphericalHarmonicsDegree1Fields[i]]];
- }
- if (outSphericalHarmonicsDegree >= 2 && header.sphericalHarmonicsDegree >= 2) {
- for (let i = 0; i < 15; i++) {
- const codeBookPage = codeBook[CB_FEATURES_REST_3 + i % 5];
- newSplat[OFFSET_FRC[9 + i]] = codeBookPage[rawSplat[header.sphericalHarmonicsDegree2Fields[i]]];
- }
- }
- }
- const rot0 = codeBook[CB_ROTATION_RE][rawSplat[PLY_ROT_0]];
- const rot1 = codeBook[CB_ROTATION_IM][rawSplat[PLY_ROT_1]];
- const rot2 = codeBook[CB_ROTATION_IM][rawSplat[PLY_ROT_2]];
- const rot3 = codeBook[CB_ROTATION_IM][rawSplat[PLY_ROT_3]];
- tempRotation.set(rot0, rot1, rot2, rot3);
- tempRotation.normalize();
- newSplat[OFFSET_ROTATION0] = tempRotation.x;
- newSplat[OFFSET_ROTATION1] = tempRotation.y;
- newSplat[OFFSET_ROTATION2] = tempRotation.z;
- newSplat[OFFSET_ROTATION3] = tempRotation.w;
- newSplat[OFFSET_X] = fromHalfFloat(rawSplat[PLY_X]);
- newSplat[OFFSET_Y] = fromHalfFloat(rawSplat[PLY_Y]);
- newSplat[OFFSET_Z] = fromHalfFloat(rawSplat[PLY_Z]);
- return newSplat;
- };
- }();
- static readSplat(splatData, header, row, dataOffset, rawSplat) {
- return PlyParserUtils.readVertex(splatData, header, row, dataOffset, FieldsToReadIndexes, rawSplat, false);
- }
- parseToUncompressedSplatArray(plyBuffer, outSphericalHarmonicsDegree = 0) {
- const splatArrays = [];
- const header = this.decodeHeaderFromBuffer(plyBuffer, outSphericalHarmonicsDegree);
- let codeBook;
- for (let s = 0; s < header.sectionHeaders.length; s++) {
- const sectionHeader = header.sectionHeaders[s];
- if (sectionHeader.sectionName === 'codebook_centers') {
- const codeBookData = this.findVertexData(plyBuffer, header, s);
- codeBook = this.decodeCodeBook(codeBookData, sectionHeader);
- }
- }
- for (let s = 0; s < header.sectionHeaders.length; s++) {
- const sectionHeader = header.sectionHeaders[s];
- if (sectionHeader.sectionName !== 'codebook_centers') {
- const splatCount = sectionHeader.vertexCount;
- const vertexData = this.findVertexData(plyBuffer, header, s);
- const splatArray = this.decodeSectionSplatData(vertexData, splatCount, sectionHeader,
- codeBook, outSphericalHarmonicsDegree);
- splatArrays.push(splatArray);
- }
- }
- const unified = new UncompressedSplatArray(outSphericalHarmonicsDegree);
- for (let splatArray of splatArrays) {
- for (let splat of splatArray.splats) {
- unified.addSplat(splat);
- }
- }
- return unified;
- }
- }
- class PlyParser {
- static parseToUncompressedSplatArray(plyBuffer, outSphericalHarmonicsDegree = 0) {
- const plyFormat = PlyParserUtils.determineHeaderFormatFromPlyBuffer(plyBuffer);
- if (plyFormat === PlyFormat.PlayCanvasCompressed) {
- return PlayCanvasCompressedPlyParser.parseToUncompressedSplatArray(plyBuffer);
- } else if (plyFormat === PlyFormat.INRIAV1) {
- return new INRIAV1PlyParser().parseToUncompressedSplatArray(plyBuffer, outSphericalHarmonicsDegree);
- } else if (plyFormat === PlyFormat.INRIAV2) {
- return new INRIAV2PlyParser().parseToUncompressedSplatArray(plyBuffer, outSphericalHarmonicsDegree);
- }
- }
- }
- class SplatPartitioner {
- constructor(sectionCount, sectionFilters, groupingParameters, partitionGenerator) {
- this.sectionCount = sectionCount;
- this.sectionFilters = sectionFilters;
- this.groupingParameters = groupingParameters;
- this.partitionGenerator = partitionGenerator;
- }
- partitionUncompressedSplatArray(splatArray) {
- let groupingParameters;
- let sectionCount;
- let sectionFilters;
- if (this.partitionGenerator) {
- const results = this.partitionGenerator(splatArray);
- groupingParameters = results.groupingParameters;
- sectionCount = results.sectionCount;
- sectionFilters = results.sectionFilters;
- } else {
- groupingParameters = this.groupingParameters;
- sectionCount = this.sectionCount;
- sectionFilters = this.sectionFilters;
- }
- const newArrays = [];
- for (let s = 0; s < sectionCount; s++) {
- const sectionSplats = new UncompressedSplatArray(splatArray.sphericalHarmonicsDegree);
- const sectionFilter = sectionFilters[s];
- for (let i = 0; i < splatArray.splatCount; i++) {
- if (sectionFilter(i)) {
- sectionSplats.addSplatFromArray(splatArray, i);
- }
- }
- newArrays.push(sectionSplats);
- }
- return {
- splatArrays: newArrays,
- parameters: groupingParameters
- };
- }
- static getStandardPartitioner(partitionSize = 0, sceneCenter = new THREE.Vector3(),
- blockSize = SplatBuffer.BucketBlockSize, bucketSize = SplatBuffer.BucketSize) {
- const partitionGenerator = (splatArray) => {
- const OFFSET_X = UncompressedSplatArray.OFFSET.X;
- const OFFSET_Y = UncompressedSplatArray.OFFSET.Y;
- const OFFSET_Z = UncompressedSplatArray.OFFSET.Z;
- if (partitionSize <= 0) partitionSize = splatArray.splatCount;
- const center = new THREE.Vector3();
- const clampDistance = 0.5;
- const clampPoint = (point) => {
- point.x = Math.floor(point.x / clampDistance) * clampDistance;
- point.y = Math.floor(point.y / clampDistance) * clampDistance;
- point.z = Math.floor(point.z / clampDistance) * clampDistance;
- };
- splatArray.splats.forEach((splat) => {
- center.set(splat[OFFSET_X], splat[OFFSET_Y], splat[OFFSET_Z]).sub(sceneCenter);
- clampPoint(center);
- splat.centerDist = center.lengthSq();
- });
- splatArray.splats.sort((a, b) => {
- let centerADist = a.centerDist;
- let centerBDist = b.centerDist;
- if (centerADist > centerBDist) return 1;
- else return -1;
- });
- const sectionFilters = [];
- const groupingParameters = [];
- partitionSize = Math.min(splatArray.splatCount, partitionSize);
- const patitionCount = Math.ceil(splatArray.splatCount / partitionSize);
- let currentStartSplat = 0;
- for (let i = 0; i < patitionCount; i ++) {
- let startSplat = currentStartSplat;
- sectionFilters.push((splatIndex) => {
- return splatIndex >= startSplat && splatIndex < startSplat + partitionSize;
- });
- groupingParameters.push({
- 'blocksSize': blockSize,
- 'bucketSize': bucketSize,
- });
- currentStartSplat += partitionSize;
- }
- return {
- 'sectionCount': sectionFilters.length,
- sectionFilters,
- groupingParameters
- };
- };
- return new SplatPartitioner(undefined, undefined, undefined, partitionGenerator);
- }
- }
- class SplatBufferGenerator {
- constructor(splatPartitioner, alphaRemovalThreshold, compressionLevel, sectionSize, sceneCenter, blockSize, bucketSize) {
- this.splatPartitioner = splatPartitioner;
- this.alphaRemovalThreshold = alphaRemovalThreshold;
- this.compressionLevel = compressionLevel;
- this.sectionSize = sectionSize;
- this.sceneCenter = sceneCenter ? new THREE.Vector3().copy(sceneCenter) : undefined;
- this.blockSize = blockSize;
- this.bucketSize = bucketSize;
- }
- generateFromUncompressedSplatArray(splatArray) {
- const partitionResults = this.splatPartitioner.partitionUncompressedSplatArray(splatArray);
- return SplatBuffer.generateFromUncompressedSplatArrays(partitionResults.splatArrays,
- this.alphaRemovalThreshold, this.compressionLevel,
- this.sceneCenter, this.blockSize, this.bucketSize,
- partitionResults.parameters);
- }
- static getStandardGenerator(alphaRemovalThreshold = 1, compressionLevel = 1, sectionSize = 0, sceneCenter = new THREE.Vector3(),
- blockSize = SplatBuffer.BucketBlockSize, bucketSize = SplatBuffer.BucketSize) {
- const splatPartitioner = SplatPartitioner.getStandardPartitioner(sectionSize, sceneCenter, blockSize, bucketSize);
- return new SplatBufferGenerator(splatPartitioner, alphaRemovalThreshold, compressionLevel,
- sectionSize, sceneCenter, blockSize, bucketSize);
- }
- }
- const LoaderStatus = {
- 'Downloading': 0,
- 'Processing': 1,
- 'Done': 2
- };
- function storeChunksInBuffer(chunks, buffer) {
- let inBytes = 0;
- for (let chunk of chunks) inBytes += chunk.sizeBytes;
- if (!buffer || buffer.byteLength < inBytes) {
- buffer = new ArrayBuffer(inBytes);
- }
- let offset = 0;
- for (let chunk of chunks) {
- new Uint8Array(buffer, offset, chunk.sizeBytes).set(chunk.data);
- offset += chunk.sizeBytes;
- }
- return buffer;
- }
- class PlyLoader {
- static loadFromURL(fileName, onProgress, progressiveLoad, onStreamedSectionProgress, minimumAlpha, compressionLevel,
- outSphericalHarmonicsDegree = 0, sectionSize, sceneCenter, blockSize, bucketSize) {
- const progressiveLoadSectionSizeBytes = Constants.ProgressiveLoadSectionSize;
- const splatDataOffsetBytes = SplatBuffer.HeaderSizeBytes + SplatBuffer.SectionHeaderSizeBytes;
- const sectionCount = 1;
- let progressiveLoadBufferIn;
- let progressiveLoadBufferOut;
- let progressiveLoadSplatBuffer;
- let compressedPlyHeaderChunksBuffer;
- let maxSplatCount = 0;
- let splatCount = 0;
- let headerLoaded = false;
- let readyToLoadSplatData = false;
- let compressed = false;
- const progressiveLoadPromise = nativePromiseWithExtractedComponents();
- let numBytesStreamed = 0;
- let numBytesParsed = 0;
- let numBytesDownloaded = 0;
- let headerText = '';
- let header = null;
- let chunks = [];
- const textDecoder = new TextDecoder();
- const inriaV1PlyParser = new INRIAV1PlyParser();
- const localOnProgress = (percent, percentLabel, chunkData) => {
- const loadComplete = percent >= 100;
- if (progressiveLoad) {
- if (chunkData) {
- chunks.push({
- 'data': chunkData,
- 'sizeBytes': chunkData.byteLength,
- 'startBytes': numBytesDownloaded,
- 'endBytes': numBytesDownloaded + chunkData.byteLength
- });
- numBytesDownloaded += chunkData.byteLength;
- }
- if (!headerLoaded) {
- headerText += textDecoder.decode(chunkData);
- if (PlyParserUtils.checkTextForEndHeader(headerText)) {
- const plyFormat = PlyParserUtils.determineHeaderFormatFromHeaderText(headerText);
- if (plyFormat === PlyFormat.INRIAV1) {
- header = inriaV1PlyParser.decodeHeaderText(headerText);
- maxSplatCount = header.splatCount;
- readyToLoadSplatData = true;
- compressed = false;
- } else if (plyFormat === PlyFormat.PlayCanvasCompressed) {
- header = PlayCanvasCompressedPlyParser.decodeHeaderText(headerText);
- maxSplatCount = header.vertexElement.count;
- compressed = true;
- } else {
- throw new Error('PlyLoader.loadFromURL() -> Selected Ply format cannot be progressively loaded.');
- }
- outSphericalHarmonicsDegree = Math.min(outSphericalHarmonicsDegree, header.sphericalHarmonicsDegree);
- const shDescriptor = SplatBuffer.CompressionLevels[0].SphericalHarmonicsDegrees[outSphericalHarmonicsDegree];
- const splatBufferSizeBytes = splatDataOffsetBytes + shDescriptor.BytesPerSplat * maxSplatCount;
- progressiveLoadBufferOut = new ArrayBuffer(splatBufferSizeBytes);
- SplatBuffer.writeHeaderToBuffer({
- versionMajor: SplatBuffer.CurrentMajorVersion,
- versionMinor: SplatBuffer.CurrentMinorVersion,
- maxSectionCount: sectionCount,
- sectionCount: sectionCount,
- maxSplatCount: maxSplatCount,
- splatCount: splatCount,
- compressionLevel: 0,
- sceneCenter: new THREE.Vector3()
- }, progressiveLoadBufferOut);
- numBytesStreamed = header.headerSizeBytes;
- numBytesParsed = header.headerSizeBytes;
- headerLoaded = true;
- }
- } else if (compressed && !readyToLoadSplatData) {
- const sizeRequiredForHeaderAndChunks = header.headerSizeBytes + header.chunkElement.storageSizeBytes;
- compressedPlyHeaderChunksBuffer = storeChunksInBuffer(chunks, compressedPlyHeaderChunksBuffer);
- if (compressedPlyHeaderChunksBuffer.byteLength >= sizeRequiredForHeaderAndChunks) {
- PlayCanvasCompressedPlyParser.readElementData(header.chunkElement, compressedPlyHeaderChunksBuffer,
- header.headerSizeBytes);
- numBytesStreamed = sizeRequiredForHeaderAndChunks;
- numBytesParsed = sizeRequiredForHeaderAndChunks;
- readyToLoadSplatData = true;
- }
- }
- if (headerLoaded && readyToLoadSplatData) {
- if (chunks.length > 0) {
- progressiveLoadBufferIn = storeChunksInBuffer(chunks, progressiveLoadBufferIn);
- const bytesLoadedSinceLastStreamedSection = numBytesDownloaded - numBytesStreamed;
- if (bytesLoadedSinceLastStreamedSection > progressiveLoadSectionSizeBytes || loadComplete) {
- const numBytesToProcess = numBytesDownloaded - numBytesParsed;
- const addedSplatCount = Math.floor(numBytesToProcess / header.bytesPerSplat);
- const numBytesToParse = addedSplatCount * header.bytesPerSplat;
- const numBytesLeftOver = numBytesToProcess - numBytesToParse;
- const newSplatCount = splatCount + addedSplatCount;
- const parsedDataViewOffset = numBytesParsed - chunks[0].startBytes;
- const dataToParse = new DataView(progressiveLoadBufferIn, parsedDataViewOffset, numBytesToParse);
- const shDescriptor = SplatBuffer.CompressionLevels[0].SphericalHarmonicsDegrees[outSphericalHarmonicsDegree];
- const outOffset = splatCount * shDescriptor.BytesPerSplat + splatDataOffsetBytes;
- if (compressed) {
- PlayCanvasCompressedPlyParser.parseToUncompressedSplatBufferSection(header.chunkElement,
- header.vertexElement, 0,
- addedSplatCount - 1, splatCount,
- dataToParse, 0,
- progressiveLoadBufferOut, outOffset);
- } else {
- inriaV1PlyParser.parseToUncompressedSplatBufferSection(header, 0, addedSplatCount - 1, dataToParse,
- 0, progressiveLoadBufferOut, outOffset,
- outSphericalHarmonicsDegree);
- }
- splatCount = newSplatCount;
- if (!progressiveLoadSplatBuffer) {
- SplatBuffer.writeSectionHeaderToBuffer({
- maxSplatCount: maxSplatCount,
- splatCount: splatCount,
- bucketSize: 0,
- bucketCount: 0,
- bucketBlockSize: 0,
- compressionScaleRange: 0,
- storageSizeBytes: 0,
- fullBucketCount: 0,
- partiallyFilledBucketCount: 0,
- sphericalHarmonicsDegree: outSphericalHarmonicsDegree
- }, 0, progressiveLoadBufferOut, SplatBuffer.HeaderSizeBytes);
- progressiveLoadSplatBuffer = new SplatBuffer(progressiveLoadBufferOut, false);
- }
- progressiveLoadSplatBuffer.updateLoadedCounts(1, splatCount);
- onStreamedSectionProgress(progressiveLoadSplatBuffer, loadComplete);
- numBytesStreamed += progressiveLoadSectionSizeBytes;
- numBytesParsed += numBytesToParse;
- if (numBytesLeftOver === 0) {
- chunks = [];
- } else {
- let keepChunks = [];
- let keepSize = 0;
- for (let i = chunks.length - 1; i >= 0; i--) {
- const chunk = chunks[i];
- keepSize += chunk.sizeBytes;
- keepChunks.unshift(chunk);
- if (keepSize >= numBytesLeftOver) break;
- }
- chunks = keepChunks;
- }
- }
- }
- if (loadComplete) {
- progressiveLoadPromise.resolve(progressiveLoadSplatBuffer);
- }
- }
- }
- if (onProgress) onProgress(percent, percentLabel, LoaderStatus.Downloading);
- };
- return fetchWithProgress(fileName, localOnProgress, !progressiveLoad).then((plyFileData) => {
- if (onProgress) onProgress(0, '0%', LoaderStatus.Processing);
- const loadPromise = progressiveLoad ? progressiveLoadPromise.promise :
- PlyLoader.loadFromFileData(plyFileData, minimumAlpha, compressionLevel, outSphericalHarmonicsDegree,
- sectionSize, sceneCenter, blockSize, bucketSize);
- return loadPromise.then((splatBuffer) => {
- if (onProgress) onProgress(100, '100%', LoaderStatus.Done);
- return splatBuffer;
- });
- });
- }
- static loadFromFileData(plyFileData, minimumAlpha, compressionLevel, outSphericalHarmonicsDegree = 0,
- sectionSize, sceneCenter, blockSize, bucketSize) {
- return delayedExecute(() => {
- return PlyParser.parseToUncompressedSplatArray(plyFileData, outSphericalHarmonicsDegree);
- })
- .then((splatArray) => {
- const splatBufferGenerator = SplatBufferGenerator.getStandardGenerator(minimumAlpha, compressionLevel, sectionSize,
- sceneCenter, blockSize, bucketSize);
- return splatBufferGenerator.generateFromUncompressedSplatArray(splatArray);
- });
- }
- }
- class SplatParser {
- static RowSizeBytes = 32;
- static CenterSizeBytes = 12;
- static ScaleSizeBytes = 12;
- static RotationSizeBytes = 4;
- static ColorSizeBytes = 4;
- static parseToUncompressedSplatBufferSection(fromSplat, toSplat, fromBuffer, fromOffset, toBuffer, toOffset) {
- const outBytesPerCenter = SplatBuffer.CompressionLevels[0].BytesPerCenter;
- const outBytesPerScale = SplatBuffer.CompressionLevels[0].BytesPerScale;
- const outBytesPerRotation = SplatBuffer.CompressionLevels[0].BytesPerRotation;
- const outBytesPerSplat = SplatBuffer.CompressionLevels[0].SphericalHarmonicsDegrees[0].BytesPerSplat;
- for (let i = fromSplat; i <= toSplat; i++) {
- const inBase = i * SplatParser.RowSizeBytes + fromOffset;
- const inCenter = new Float32Array(fromBuffer, inBase, 3);
- const inScale = new Float32Array(fromBuffer, inBase + SplatParser.CenterSizeBytes, 3);
- const inColor = new Uint8Array(fromBuffer, inBase + SplatParser.CenterSizeBytes + SplatParser.ScaleSizeBytes, 4);
- const inRotation = new Uint8Array(fromBuffer, inBase + SplatParser.CenterSizeBytes + SplatParser.ScaleSizeBytes +
- SplatParser.RotationSizeBytes, 4);
- const quat = new THREE.Quaternion((inRotation[1] - 128) / 128, (inRotation[2] - 128) / 128,
- (inRotation[3] - 128) / 128, (inRotation[0] - 128) / 128);
- quat.normalize();
- const outBase = i * outBytesPerSplat + toOffset;
- const outCenter = new Float32Array(toBuffer, outBase, 3);
- const outScale = new Float32Array(toBuffer, outBase + outBytesPerCenter, 3);
- const outRotation = new Float32Array(toBuffer, outBase + outBytesPerCenter + outBytesPerScale, 4);
- const outColor = new Uint8Array(toBuffer, outBase + outBytesPerCenter + outBytesPerScale + outBytesPerRotation, 4);
- outCenter[0] = inCenter[0];
- outCenter[1] = inCenter[1];
- outCenter[2] = inCenter[2];
- outScale[0] = inScale[0];
- outScale[1] = inScale[1];
- outScale[2] = inScale[2];
- outRotation[0] = quat.w;
- outRotation[1] = quat.x;
- outRotation[2] = quat.y;
- outRotation[3] = quat.z;
- outColor[0] = inColor[0];
- outColor[1] = inColor[1];
- outColor[2] = inColor[2];
- outColor[3] = inColor[3];
- }
- }
- static parseStandardSplatToUncompressedSplatArray(inBuffer) {
- // Standard .splat row layout:
- // XYZ - Position (Float32)
- // XYZ - Scale (Float32)
- // RGBA - colors (uint8)
- // IJKL - quaternion/rot (uint8)
- const splatCount = inBuffer.byteLength / SplatParser.RowSizeBytes;
- const splatArray = new UncompressedSplatArray();
- for (let i = 0; i < splatCount; i++) {
- const inBase = i * SplatParser.RowSizeBytes;
- const inCenter = new Float32Array(inBuffer, inBase, 3);
- const inScale = new Float32Array(inBuffer, inBase + SplatParser.CenterSizeBytes, 3);
- const inColor = new Uint8Array(inBuffer, inBase + SplatParser.CenterSizeBytes + SplatParser.ScaleSizeBytes, 4);
- const inRotation = new Uint8Array(inBuffer, inBase + SplatParser.CenterSizeBytes +
- SplatParser.ScaleSizeBytes + SplatParser.ColorSizeBytes, 4);
- const quat = new THREE.Quaternion((inRotation[1] - 128) / 128, (inRotation[2] - 128) / 128,
- (inRotation[3] - 128) / 128, (inRotation[0] - 128) / 128);
- quat.normalize();
- splatArray.addSplatFromComonents(inCenter[0], inCenter[1], inCenter[2], inScale[0], inScale[1], inScale[2],
- quat.w, quat.x, quat.y, quat.z, inColor[0], inColor[1], inColor[2], inColor[3]);
- }
- return splatArray;
- }
- }
- class SplatLoader {
- static loadFromURL(fileName, onProgress, progressiveLoad, onStreamedSectionProgress, minimumAlpha, compressionLevel,
- optimizeSplatData, sectionSize, sceneCenter, blockSize, bucketSize) {
- const splatDataOffsetBytes = SplatBuffer.HeaderSizeBytes + SplatBuffer.SectionHeaderSizeBytes;
- const progressiveLoadSectionSizeBytes = Constants.ProgressiveLoadSectionSize;
- const sectionCount = 1;
- let progressiveLoadBufferIn;
- let progressiveLoadBufferOut;
- let progressiveLoadSplatBuffer;
- let maxSplatCount = 0;
- let splatCount = 0;
- const progressiveLoadPromise = nativePromiseWithExtractedComponents();
- let numBytesStreamed = 0;
- let numBytesLoaded = 0;
- let chunks = [];
- const localOnProgress = (percent, percentStr, chunk, fileSize) => {
- const loadComplete = percent >= 100;
- if (!fileSize) progressiveLoad = false;
- if (progressiveLoad) {
- if (!progressiveLoadBufferIn) {
- maxSplatCount = fileSize / SplatParser.RowSizeBytes;
- progressiveLoadBufferIn = new ArrayBuffer(fileSize);
- const bytesPerSplat = SplatBuffer.CompressionLevels[0].SphericalHarmonicsDegrees[0].BytesPerSplat;
- const splatBufferSizeBytes = splatDataOffsetBytes + bytesPerSplat * maxSplatCount;
- progressiveLoadBufferOut = new ArrayBuffer(splatBufferSizeBytes);
- SplatBuffer.writeHeaderToBuffer({
- versionMajor: SplatBuffer.CurrentMajorVersion,
- versionMinor: SplatBuffer.CurrentMinorVersion,
- maxSectionCount: sectionCount,
- sectionCount: sectionCount,
- maxSplatCount: maxSplatCount,
- splatCount: splatCount,
- compressionLevel: 0,
- sceneCenter: new THREE.Vector3()
- }, progressiveLoadBufferOut);
- }
- if (chunk) {
- chunks.push(chunk);
- new Uint8Array(progressiveLoadBufferIn, numBytesLoaded, chunk.byteLength).set(new Uint8Array(chunk));
- numBytesLoaded += chunk.byteLength;
- const bytesLoadedSinceLastSection = numBytesLoaded - numBytesStreamed;
- if (bytesLoadedSinceLastSection > progressiveLoadSectionSizeBytes || loadComplete) {
- const bytesToUpdate = loadComplete ? bytesLoadedSinceLastSection : progressiveLoadSectionSizeBytes;
- const addedSplatCount = bytesToUpdate / SplatParser.RowSizeBytes;
- const newSplatCount = splatCount + addedSplatCount;
- SplatParser.parseToUncompressedSplatBufferSection(splatCount, newSplatCount - 1, progressiveLoadBufferIn, 0,
- progressiveLoadBufferOut, splatDataOffsetBytes);
- splatCount = newSplatCount;
- if (!progressiveLoadSplatBuffer) {
- SplatBuffer.writeSectionHeaderToBuffer({
- maxSplatCount: maxSplatCount,
- splatCount: splatCount,
- bucketSize: 0,
- bucketCount: 0,
- bucketBlockSize: 0,
- compressionScaleRange: 0,
- storageSizeBytes: 0,
- fullBucketCount: 0,
- partiallyFilledBucketCount: 0
- }, 0, progressiveLoadBufferOut, SplatBuffer.HeaderSizeBytes);
- progressiveLoadSplatBuffer = new SplatBuffer(progressiveLoadBufferOut, false);
- }
- progressiveLoadSplatBuffer.updateLoadedCounts(1, splatCount);
- onStreamedSectionProgress(progressiveLoadSplatBuffer, loadComplete);
- numBytesStreamed += progressiveLoadSectionSizeBytes;
- }
- }
- if (loadComplete) {
- progressiveLoadPromise.resolve(progressiveLoadSplatBuffer);
- }
- }
- if (onProgress) onProgress(percent, percentStr, LoaderStatus.Downloading);
- return progressiveLoad;
- };
- return fetchWithProgress(fileName, localOnProgress, true).then((fullBuffer) => {
- if (onProgress) onProgress(0, '0%', LoaderStatus.Processing);
- const loadPromise = progressiveLoad ? progressiveLoadPromise.promise :
- SplatLoader.loadFromFileData(fullBuffer, minimumAlpha, compressionLevel, optimizeSplatData,
- sectionSize, sceneCenter, blockSize, bucketSize);
- return loadPromise.then((splatBuffer) => {
- if (onProgress) onProgress(100, '100%', LoaderStatus.Done);
- return splatBuffer;
- });
- });
- }
- static loadFromFileData(splatFileData, minimumAlpha, compressionLevel, optimizeSplatData,
- sectionSize, sceneCenter, blockSize, bucketSize) {
- return delayedExecute(() => {
- const splatArray = SplatParser.parseStandardSplatToUncompressedSplatArray(splatFileData);
- if (optimizeSplatData) {
- const splatBufferGenerator = SplatBufferGenerator.getStandardGenerator(minimumAlpha, compressionLevel,
- sectionSize, sceneCenter, blockSize,
- bucketSize);
- return splatBufferGenerator.generateFromUncompressedSplatArray(splatArray);
- } else {
- return SplatBuffer.generateFromUncompressedSplatArrays([splatArray], minimumAlpha, 0, new THREE.Vector3());
- }
- });
- }
- }
- class KSplatLoader {
- static checkVersion(buffer) {
- const minVersionMajor = SplatBuffer.CurrentMajorVersion;
- const minVersionMinor = SplatBuffer.CurrentMinorVersion;
- const header = SplatBuffer.parseHeader(buffer);
- if (header.versionMajor === minVersionMajor &&
- header.versionMinor >= minVersionMinor ||
- header.versionMajor > minVersionMajor) {
- return true;
- } else {
- throw new Error(`KSplat version not supported: v${header.versionMajor}.${header.versionMinor}. ` +
- `Minimum required: v${minVersionMajor}.${minVersionMinor}`);
- }
- };
- static loadFromURL(fileName, externalOnProgress, progressiveLoad, onSectionBuilt) {
- let progressiveLoadBuffer;
- let progressiveLoadSplatBuffer;
- let headerBuffer;
- let header;
- let headerLoaded = false;
- let headerLoading = false;
- let sectionHeadersBuffer;
- let sectionHeaders = [];
- let sectionHeadersLoaded = false;
- let sectionHeadersLoading = false;
- let numBytesLoaded = 0;
- let numBytesProgressivelyLoaded = 0;
- let totalBytesToDownload = 0;
- let downloadComplete = false;
- let loadComplete = false;
- let loadSectionQueued = false;
- let chunks = [];
- const progressiveLoadPromise = nativePromiseWithExtractedComponents();
- const checkAndLoadHeader = () => {
- if (!headerLoaded && !headerLoading && numBytesLoaded >= SplatBuffer.HeaderSizeBytes) {
- headerLoading = true;
- const headerAssemblyPromise = new Blob(chunks).arrayBuffer();
- headerAssemblyPromise.then((bufferData) => {
- headerBuffer = new ArrayBuffer(SplatBuffer.HeaderSizeBytes);
- new Uint8Array(headerBuffer).set(new Uint8Array(bufferData, 0, SplatBuffer.HeaderSizeBytes));
- KSplatLoader.checkVersion(headerBuffer);
- headerLoading = false;
- headerLoaded = true;
- header = SplatBuffer.parseHeader(headerBuffer);
- window.setTimeout(() => {
- checkAndLoadSectionHeaders();
- }, 1);
- });
- }
- };
- let queuedCheckAndLoadSectionsCount = 0;
- const queueCheckAndLoadSections = () => {
- if (queuedCheckAndLoadSectionsCount === 0) {
- queuedCheckAndLoadSectionsCount++;
- window.setTimeout(() => {
- queuedCheckAndLoadSectionsCount--;
- checkAndLoadSections();
- }, 1);
- }
- };
- const checkAndLoadSectionHeaders = () => {
- const performLoad = () => {
- sectionHeadersLoading = true;
- const sectionHeadersAssemblyPromise = new Blob(chunks).arrayBuffer();
- sectionHeadersAssemblyPromise.then((bufferData) => {
- sectionHeadersLoading = false;
- sectionHeadersLoaded = true;
- sectionHeadersBuffer = new ArrayBuffer(header.maxSectionCount * SplatBuffer.SectionHeaderSizeBytes);
- new Uint8Array(sectionHeadersBuffer).set(new Uint8Array(bufferData, SplatBuffer.HeaderSizeBytes,
- header.maxSectionCount * SplatBuffer.SectionHeaderSizeBytes));
- sectionHeaders = SplatBuffer.parseSectionHeaders(header, sectionHeadersBuffer, 0, false);
- let totalSectionStorageStorageByes = 0;
- for (let i = 0; i < header.maxSectionCount; i++) {
- totalSectionStorageStorageByes += sectionHeaders[i].storageSizeBytes;
- }
- const totalStorageSizeBytes = SplatBuffer.HeaderSizeBytes + header.maxSectionCount *
- SplatBuffer.SectionHeaderSizeBytes + totalSectionStorageStorageByes;
- if (!progressiveLoadBuffer) {
- progressiveLoadBuffer = new ArrayBuffer(totalStorageSizeBytes);
- let offset = 0;
- for (let i = 0; i < chunks.length; i++) {
- const chunk = chunks[i];
- new Uint8Array(progressiveLoadBuffer, offset, chunk.byteLength).set(new Uint8Array(chunk));
- offset += chunk.byteLength;
- }
- }
- totalBytesToDownload = SplatBuffer.HeaderSizeBytes + SplatBuffer.SectionHeaderSizeBytes * header.maxSectionCount;
- for (let i = 0; i <= sectionHeaders.length && i < header.maxSectionCount; i++) {
- totalBytesToDownload += sectionHeaders[i].storageSizeBytes;
- }
- queueCheckAndLoadSections();
- });
- };
- if (!sectionHeadersLoading && !sectionHeadersLoaded && headerLoaded &&
- numBytesLoaded >= SplatBuffer.HeaderSizeBytes + SplatBuffer.SectionHeaderSizeBytes * header.maxSectionCount) {
- performLoad();
- }
- };
- const checkAndLoadSections = () => {
- if (loadSectionQueued) return;
- loadSectionQueued = true;
- const checkAndLoadFunc = () => {
- loadSectionQueued = false;
- if (sectionHeadersLoaded) {
- if (loadComplete) return;
- downloadComplete = numBytesLoaded >= totalBytesToDownload;
- let bytesLoadedSinceLastSection = numBytesLoaded - numBytesProgressivelyLoaded;
- if (bytesLoadedSinceLastSection > Constants.ProgressiveLoadSectionSize || downloadComplete) {
- numBytesProgressivelyLoaded += Constants.ProgressiveLoadSectionSize;
- loadComplete = numBytesProgressivelyLoaded >= totalBytesToDownload;
- if (!progressiveLoadSplatBuffer) progressiveLoadSplatBuffer = new SplatBuffer(progressiveLoadBuffer, false);
- const baseDataOffset = SplatBuffer.HeaderSizeBytes + SplatBuffer.SectionHeaderSizeBytes * header.maxSectionCount;
- let sectionBase = 0;
- let reachedSections = 0;
- let loadedSplatCount = 0;
- for (let i = 0; i < header.maxSectionCount; i++) {
- const sectionHeader = sectionHeaders[i];
- const bucketsDataOffset = sectionBase + sectionHeader.partiallyFilledBucketCount * 4 +
- sectionHeader.bucketStorageSizeBytes * sectionHeader.bucketCount;
- const bytesRequiredToReachSectionSplatData = baseDataOffset + bucketsDataOffset;
- if (numBytesProgressivelyLoaded >= bytesRequiredToReachSectionSplatData) {
- reachedSections++;
- const bytesPastSSectionSplatDataStart = numBytesProgressivelyLoaded - bytesRequiredToReachSectionSplatData;
- const baseDescriptor = SplatBuffer.CompressionLevels[header.compressionLevel];
- const shDesc = baseDescriptor.SphericalHarmonicsDegrees[sectionHeader.sphericalHarmonicsDegree];
- const bytesPerSplat = shDesc.BytesPerSplat;
- let loadedSplatsForSection = Math.floor(bytesPastSSectionSplatDataStart / bytesPerSplat);
- loadedSplatsForSection = Math.min(loadedSplatsForSection, sectionHeader.maxSplatCount);
- loadedSplatCount += loadedSplatsForSection;
- progressiveLoadSplatBuffer.updateLoadedCounts(reachedSections, loadedSplatCount);
- progressiveLoadSplatBuffer.updateSectionLoadedCounts(i, loadedSplatsForSection);
- } else {
- break;
- }
- sectionBase += sectionHeader.storageSizeBytes;
- }
- onSectionBuilt(progressiveLoadSplatBuffer, loadComplete);
- const percentComplete = numBytesProgressivelyLoaded / totalBytesToDownload * 100;
- const percentLabel = (percentComplete).toFixed(2) + '%';
- if (externalOnProgress) externalOnProgress(percentComplete, percentLabel, LoaderStatus.Downloading);
- if (loadComplete) {
- progressiveLoadPromise.resolve(progressiveLoadSplatBuffer);
- } else {
- checkAndLoadSections();
- }
- }
- }
- };
- window.setTimeout(checkAndLoadFunc, Constants.ProgressiveLoadSectionDelayDuration);
- };
- const localOnProgress = (percent, percentStr, chunk) => {
- if (chunk) {
- chunks.push(chunk);
- if (progressiveLoadBuffer) {
- new Uint8Array(progressiveLoadBuffer, numBytesLoaded, chunk.byteLength).set(new Uint8Array(chunk));
- }
- numBytesLoaded += chunk.byteLength;
- }
- if (progressiveLoad) {
- checkAndLoadHeader();
- checkAndLoadSectionHeaders();
- checkAndLoadSections();
- } else {
- if (externalOnProgress) externalOnProgress(percent, percentStr, LoaderStatus.Downloading);
- }
- };
- return fetchWithProgress(fileName, localOnProgress, !progressiveLoad).then((fullBuffer) => {
- if (externalOnProgress) externalOnProgress(0, '0%', LoaderStatus.Processing);
- const loadPromise = progressiveLoad ? progressiveLoadPromise.promise : KSplatLoader.loadFromFileData(fullBuffer);
- return loadPromise.then((splatBuffer) => {
- if (externalOnProgress) externalOnProgress(100, '100%', LoaderStatus.Done);
- return splatBuffer;
- });
- });
- }
- static loadFromFileData(fileData) {
- return delayedExecute(() => {
- KSplatLoader.checkVersion(fileData);
- return new SplatBuffer(fileData);
- });
- }
- static downloadFile = function() {
- let downLoadLink;
- return function(splatBuffer, fileName) {
- const blob = new Blob([splatBuffer.bufferData], {
- type: 'application/octet-stream',
- });
- if (!downLoadLink) {
- downLoadLink = document.createElement('a');
- document.body.appendChild(downLoadLink);
- }
- downLoadLink.download = fileName;
- downLoadLink.href = URL.createObjectURL(blob);
- downLoadLink.click();
- };
- }();
- }
- const SceneFormat = {
- 'Splat': 0,
- 'KSplat': 1,
- 'Ply': 2
- };
- const sceneFormatFromPath = (path) => {
- if (path.endsWith('.ply')) return SceneFormat.Ply;
- else if (path.endsWith('.splat')) return SceneFormat.Splat;
- else if (path.endsWith('.ksplat')) return SceneFormat.KSplat;
- return null;
- };
- var Utils = /*#__PURE__*/Object.freeze({
- __proto__: null,
- sceneFormatFromPath: sceneFormatFromPath
- });
- /*
- Copyright © 2010-2024 three.js authors & Mark Kellogg
- Permission is hereby granted, free of charge, to any person obtaining a copy
- of this software and associated documentation files (the "Software"), to deal
- in the Software without restriction, including without limitation the rights
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- copies of the Software, and to permit persons to whom the Software is
- furnished to do so, subject to the following conditions:
- The above copyright notice and this permission notice shall be included in
- all copies or substantial portions of the Software.
- */
- // OrbitControls performs orbiting, dollying (zooming), and panning.
- // Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default).
- //
- // Orbit - left mouse / touch: one-finger move
- // Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish
- // Pan - right mouse, or left mouse + ctrl/meta/shiftKey, or arrow keys / touch: two-finger move
- const _changeEvent = { type: 'change' };
- const _startEvent = { type: 'start' };
- const _endEvent = { type: 'end' };
- const _ray = new Ray$1();
- const _plane = new Plane();
- const TILT_LIMIT = Math.cos( 70 * MathUtils.DEG2RAD );
- class OrbitControls extends EventDispatcher {
- constructor( object, domElement ) {
- super();
- this.object = object;
- this.domElement = domElement;
- this.domElement.style.touchAction = 'none'; // disable touch scroll
- // Set to false to disable this control
- this.enabled = true;
- // "target" sets the location of focus, where the object orbits around
- this.target = new Vector3();
- // How far you can dolly in and out ( PerspectiveCamera only )
- this.minDistance = 0;
- this.maxDistance = Infinity;
- // How far you can zoom in and out ( OrthographicCamera only )
- this.minZoom = 0;
- this.maxZoom = Infinity;
- // How far you can orbit vertically, upper and lower limits.
- // Range is 0 to Math.PI radians.
- this.minPolarAngle = 0; // radians
- this.maxPolarAngle = Math.PI; // radians
- // How far you can orbit horizontally, upper and lower limits.
- // If set, the interval [min, max] must be a sub-interval of [- 2 PI, 2 PI], with ( max - min < 2 PI )
- this.minAzimuthAngle = - Infinity; // radians
- this.maxAzimuthAngle = Infinity; // radians
- // Set to true to enable damping (inertia)
- // If damping is enabled, you must call controls.update() in your animation loop
- this.enableDamping = false;
- this.dampingFactor = 0.05;
- // This option actually enables dollying in and out; left as "zoom" for backwards compatibility.
- // Set to false to disable zooming
- this.enableZoom = true;
- this.zoomSpeed = 1.0;
- // Set to false to disable rotating
- this.enableRotate = true;
- this.rotateSpeed = 1.0;
- // Set to false to disable panning
- this.enablePan = true;
- this.panSpeed = 1.0;
- this.screenSpacePanning = true; // if false, pan orthogonal to world-space direction camera.up
- this.keyPanSpeed = 7.0; // pixels moved per arrow key push
- this.zoomToCursor = false;
- // Set to true to automatically rotate around the target
- // If auto-rotate is enabled, you must call controls.update() in your animation loop
- this.autoRotate = false;
- this.autoRotateSpeed = 2.0; // 30 seconds per orbit when fps is 60
- // The four arrow keys
- //this.keys = { LEFT: 'KeyA', UP: 'KeyW', RIGHT: 'KeyD', BOTTOM: 'KeyS' };
- this.keys = { LEFT: 'KeyA', UP: 'KeyE', RIGHT: 'KeyD', BOTTOM: 'KeyQ', Forward:'KeyW', Backward:'KeyS' };
- // Mouse buttons
- this.mouseButtons = { LEFT: MOUSE.ROTATE, MIDDLE: MOUSE.DOLLY, RIGHT: MOUSE.PAN };
- // Touch fingers
- this.touches = { ONE: TOUCH.ROTATE, TWO: TOUCH.DOLLY_PAN };
- // for reset
- this.target0 = this.target.clone();
- this.position0 = this.object.position.clone();
- this.zoom0 = this.object.zoom;
- // the target DOM element for key events
- this._domElementKeyEvents = null;
- //
- // public methods
- //
- this.getPolarAngle = function() {
- return spherical.phi;
- };
- this.getAzimuthalAngle = function() {
- return spherical.theta;
- };
- this.getDistance = function() {
- return this.object.position.distanceTo( this.target );
- };
- this.listenToKeyEvents = function( domElement ) {
- domElement.addEventListener( 'keydown', onKeyDown );
- this._domElementKeyEvents = domElement;
- };
- this.stopListenToKeyEvents = function() {
- this._domElementKeyEvents.removeEventListener( 'keydown', onKeyDown );
- this._domElementKeyEvents = null;
- };
- this.saveState = function() {
- scope.target0.copy( scope.target );
- scope.position0.copy( scope.object.position );
- scope.zoom0 = scope.object.zoom;
- };
- this.reset = function() {
- scope.target.copy( scope.target0 );
- scope.object.position.copy( scope.position0 );
- scope.object.zoom = scope.zoom0;
- this.clearDampedRotation();
- this.clearDampedPan();
- scope.object.updateProjectionMatrix();
- scope.dispatchEvent( _changeEvent );
- scope.update();
- state = STATE.NONE;
- };
- this.clearDampedRotation = function() {
- sphericalDelta.theta = 0.0;
- sphericalDelta.phi = 0.0;
- };
- this.clearDampedPan = function() {
- panOffset.set(0, 0, 0);
- };
- // this method is exposed, but perhaps it would be better if we can make it private...
- this.update = function() {
- const offset = new Vector3();
- // so camera.up is the orbit axis
- const quat = new Quaternion().setFromUnitVectors( object.up, new Vector3( 0, 1, 0 ) );
- const quatInverse = quat.clone().invert();
- const lastPosition = new Vector3();
- const lastQuaternion = new Quaternion();
- const lastTargetPosition = new Vector3();
- const twoPI = 2 * Math.PI;
- return function update() {
- quat.setFromUnitVectors( object.up, new Vector3( 0, 1, 0 ) );
- quatInverse.copy(quat).invert();
- const position = scope.object.position;
- offset.copy( position ).sub( scope.target );
- // rotate offset to "y-axis-is-up" space
- offset.applyQuaternion( quat );
- // angle from z-axis around y-axis
- spherical.setFromVector3( offset );
- if ( scope.autoRotate && state === STATE.NONE ) {
- rotateLeft( getAutoRotationAngle() );
- }
- if ( scope.enableDamping ) {
- spherical.theta += sphericalDelta.theta * scope.dampingFactor;
- spherical.phi += sphericalDelta.phi * scope.dampingFactor;
- } else {
- spherical.theta += sphericalDelta.theta;
- spherical.phi += sphericalDelta.phi;
- }
- // restrict theta to be between desired limits
- let min = scope.minAzimuthAngle;
- let max = scope.maxAzimuthAngle;
- if ( isFinite( min ) && isFinite( max ) ) {
- if ( min < - Math.PI ) min += twoPI; else if ( min > Math.PI ) min -= twoPI;
- if ( max < - Math.PI ) max += twoPI; else if ( max > Math.PI ) max -= twoPI;
- if ( min <= max ) {
- spherical.theta = Math.max( min, Math.min( max, spherical.theta ) );
- } else {
- spherical.theta = ( spherical.theta > ( min + max ) / 2 ) ?
- Math.max( min, spherical.theta ) :
- Math.min( max, spherical.theta );
- }
- }
- // restrict phi to be between desired limits
- spherical.phi = Math.max( scope.minPolarAngle, Math.min( scope.maxPolarAngle, spherical.phi ) );
- spherical.makeSafe();
- // move target to panned location
- if ( scope.enableDamping === true ) {
- scope.target.addScaledVector( panOffset, scope.dampingFactor );
- } else {
- scope.target.add( panOffset );
- }
- // adjust the camera position based on zoom only if we're not zooming to the cursor or if it's an ortho camera
- // we adjust zoom later in these cases
- if ( scope.zoomToCursor && performCursorZoom || scope.object.isOrthographicCamera ) {
- spherical.radius = clampDistance( spherical.radius );
- } else {
- spherical.radius = clampDistance( spherical.radius * scale );
- }
- offset.setFromSpherical( spherical );
- // rotate offset back to "camera-up-vector-is-up" space
- offset.applyQuaternion( quatInverse );
-
- position.copy( scope.target ).add( offset );
-
-
- //xzw 到达中心点后还能继续向前移动,也就是能推进中心点
- const minRadius = 2;
- if(offset.length() < minRadius){
- let offsetNew = offset.clone().normalize().multiplyScalar(minRadius);
- scope.target.copy(position).sub(offsetNew);
- }
-
- scope.object.lookAt( scope.target );
- if ( scope.enableDamping === true ) {
- sphericalDelta.theta *= ( 1 - scope.dampingFactor );
- sphericalDelta.phi *= ( 1 - scope.dampingFactor );
- panOffset.multiplyScalar( 1 - scope.dampingFactor );
- } else {
- sphericalDelta.set( 0, 0, 0 );
- panOffset.set( 0, 0, 0 );
- }
- // adjust camera position
- let zoomChanged = false;
- if ( scope.zoomToCursor && performCursorZoom ) {
- let newRadius = null;
- if ( scope.object.isPerspectiveCamera ) {
- // move the camera down the pointer ray
- // this method avoids floating point error
- const prevRadius = offset.length();
- newRadius = clampDistance( prevRadius * scale );
- const radiusDelta = prevRadius - newRadius;
- scope.object.position.addScaledVector( dollyDirection, radiusDelta );
- scope.object.updateMatrixWorld();
- } else if ( scope.object.isOrthographicCamera ) {
- // adjust the ortho camera position based on zoom changes
- const mouseBefore = new Vector3( mouse.x, mouse.y, 0 );
- mouseBefore.unproject( scope.object );
- scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom / scale ) );
- scope.object.updateProjectionMatrix();
- zoomChanged = true;
- const mouseAfter = new Vector3( mouse.x, mouse.y, 0 );
- mouseAfter.unproject( scope.object );
- scope.object.position.sub( mouseAfter ).add( mouseBefore );
- scope.object.updateMatrixWorld();
- newRadius = offset.length();
- } else {
- console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - zoom to cursor disabled.' );
- scope.zoomToCursor = false;
- }
- // handle the placement of the target
- if ( newRadius !== null ) {
- if ( this.screenSpacePanning ) {
- // position the orbit target in front of the new camera position
- scope.target.set( 0, 0, - 1 )
- .transformDirection( scope.object.matrix )
- .multiplyScalar( newRadius )
- .add( scope.object.position );
- } else {
- // get the ray and translation plane to compute target
- _ray.origin.copy( scope.object.position );
- _ray.direction.set( 0, 0, - 1 ).transformDirection( scope.object.matrix );
- // if the camera is 20 degrees above the horizon then don't adjust the focus target to avoid
- // extremely large values
- if ( Math.abs( scope.object.up.dot( _ray.direction ) ) < TILT_LIMIT ) {
- object.lookAt( scope.target );
- } else {
- _plane.setFromNormalAndCoplanarPoint( scope.object.up, scope.target );
- _ray.intersectPlane( _plane, scope.target );
- }
- }
- }
- } else if ( scope.object.isOrthographicCamera ) {
- scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom / scale ) );
- scope.object.updateProjectionMatrix();
- zoomChanged = true;
- }
- scale = 1;
- performCursorZoom = false;
- // update condition is:
- // min(camera displacement, camera rotation in radians)^2 > EPS
- // using small-angle approximation cos(x/2) = 1 - x^2 / 8
- if ( zoomChanged ||
- lastPosition.distanceToSquared( scope.object.position ) > EPS ||
- 8 * ( 1 - lastQuaternion.dot( scope.object.quaternion ) ) > EPS ||
- lastTargetPosition.distanceToSquared( scope.target ) > 0 ) {
- scope.dispatchEvent( _changeEvent );
- lastPosition.copy( scope.object.position );
- lastQuaternion.copy( scope.object.quaternion );
- lastTargetPosition.copy( scope.target );
- zoomChanged = false;
- return true;
- }
- return false;
- };
- }();
- this.dispose = function() {
- scope.domElement.removeEventListener( 'contextmenu', onContextMenu );
- scope.domElement.removeEventListener( 'pointerdown', onPointerDown );
- scope.domElement.removeEventListener( 'pointercancel', onPointerUp );
- scope.domElement.removeEventListener( 'wheel', onMouseWheel );
- scope.domElement.removeEventListener( 'pointermove', onPointerMove );
- scope.domElement.removeEventListener( 'pointerup', onPointerUp );
- if ( scope._domElementKeyEvents !== null ) {
- scope._domElementKeyEvents.removeEventListener( 'keydown', onKeyDown );
- scope._domElementKeyEvents = null;
- }
- };
- //
- // internals
- //
- const scope = this;
- const STATE = {
- NONE: - 1,
- ROTATE: 0,
- DOLLY: 1,
- PAN: 2,
- TOUCH_ROTATE: 3,
- TOUCH_PAN: 4,
- TOUCH_DOLLY_PAN: 5,
- TOUCH_DOLLY_ROTATE: 6
- };
- let state = STATE.NONE;
- const EPS = 0.000001;
- // current position in spherical coordinates
- const spherical = new Spherical();
- const sphericalDelta = new Spherical();
- let scale = 1;
- const panOffset = new Vector3();
- const rotateStart = new Vector2();
- const rotateEnd = new Vector2();
- const rotateDelta = new Vector2();
- const panStart = new Vector2();
- const panEnd = new Vector2();
- const panDelta = new Vector2();
- const dollyStart = new Vector2();
- const dollyEnd = new Vector2();
- const dollyDelta = new Vector2();
- const dollyDirection = new Vector3();
- const mouse = new Vector2();
- let performCursorZoom = false;
- const pointers = [];
- const pointerPositions = {};
- function getAutoRotationAngle() {
- return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed;
- }
- function getZoomScale() {
- return Math.pow( 0.95, scope.zoomSpeed );
- }
- function rotateLeft( angle ) {
- sphericalDelta.theta -= angle;
- }
- function rotateUp( angle ) {
- sphericalDelta.phi -= angle;
- }
- const panLeft = function() {
- const v = new Vector3();
- return function panLeft( distance, objectMatrix ) {
- v.setFromMatrixColumn( objectMatrix, 0 ); // get X column of objectMatrix
- v.multiplyScalar( - distance );
- panOffset.add( v );
- };
- }();
- const panUp = function() {
- const v = new Vector3();
- return function panUp( distance, objectMatrix ) {
- if ( scope.screenSpacePanning === true ) {
- v.setFromMatrixColumn( objectMatrix, 1 );
- } else {
- v.setFromMatrixColumn( objectMatrix, 0 );
- v.crossVectors( scope.object.up, v );
- }
- v.multiplyScalar( distance );
- panOffset.add( v );
- };
- }();
- // deltaX and deltaY are in pixels; right and down are positive
- const pan = function() {
- const offset = new Vector3();
- return function pan( deltaX, deltaY ) {
- const element = scope.domElement;
- if ( scope.object.isPerspectiveCamera ) {
- // perspective
- const position = scope.object.position;
- offset.copy( position ).sub( scope.target );
- let targetDistance = offset.length();
- // half of the fov is center to top of screen
- targetDistance *= Math.tan( ( scope.object.fov / 2 ) * Math.PI / 180.0 );
- // we use only clientHeight here so aspect ratio does not distort speed
- panLeft( 2 * deltaX * targetDistance / element.clientHeight, scope.object.matrix );
- panUp( 2 * deltaY * targetDistance / element.clientHeight, scope.object.matrix );
- } else if ( scope.object.isOrthographicCamera ) {
- // orthographic
- panLeft( deltaX * ( scope.object.right - scope.object.left ) /
- scope.object.zoom / element.clientWidth, scope.object.matrix );
- panUp( deltaY * ( scope.object.top - scope.object.bottom ) / scope.object.zoom /
- element.clientHeight, scope.object.matrix );
- } else {
- // camera neither orthographic nor perspective
- console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' );
- scope.enablePan = false;
- }
- };
- }();
- function dollyOut( dollyScale ) {
- if ( scope.object.isPerspectiveCamera || scope.object.isOrthographicCamera ) {
- scale /= dollyScale;
- } else {
- console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' );
- scope.enableZoom = false;
- }
- }
- function dollyIn( dollyScale ) {
- if ( scope.object.isPerspectiveCamera || scope.object.isOrthographicCamera ) {
- scale *= dollyScale;
- } else {
- console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' );
- scope.enableZoom = false;
- }
- }
- function updateMouseParameters( event ) {
- if ( ! scope.zoomToCursor ) {
- return;
- }
- performCursorZoom = true;
- const rect = scope.domElement.getBoundingClientRect();
- const x = event.clientX - rect.left;
- const y = event.clientY - rect.top;
- const w = rect.width;
- const h = rect.height;
- mouse.x = ( x / w ) * 2 - 1;
- mouse.y = - ( y / h ) * 2 + 1;
- dollyDirection.set( mouse.x, mouse.y, 1 ).unproject( object ).sub( object.position ).normalize();
- }
- function clampDistance( dist ) {
- return Math.max( scope.minDistance, Math.min( scope.maxDistance, dist ) );
- }
- //
- // event callbacks - update the object state
- //
- function handleMouseDownRotate( event ) {
- rotateStart.set( event.clientX, event.clientY );
- }
- function handleMouseDownDolly( event ) {
- updateMouseParameters( event );
- dollyStart.set( event.clientX, event.clientY );
- }
- function handleMouseDownPan( event ) {
- panStart.set( event.clientX, event.clientY );
- }
- function handleMouseMoveRotate( event ) {
- rotateEnd.set( event.clientX, event.clientY );
- rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed );
- const element = scope.domElement;
- rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height
- rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight );
- rotateStart.copy( rotateEnd );
- scope.update();
- }
- function handleMouseMoveDolly( event ) {
- dollyEnd.set( event.clientX, event.clientY );
- dollyDelta.subVectors( dollyEnd, dollyStart );
- if ( dollyDelta.y > 0 ) {
- dollyOut( getZoomScale() );
- } else if ( dollyDelta.y < 0 ) {
- dollyIn( getZoomScale() );
- }
- dollyStart.copy( dollyEnd );
- scope.update();
- }
- function handleMouseMovePan( event ) {
- panEnd.set( event.clientX, event.clientY );
- panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed );
- pan( panDelta.x, panDelta.y );
- panStart.copy( panEnd );
- scope.update();
- }
- function handleMouseWheel( event ) {
- updateMouseParameters( event );
- if ( event.deltaY < 0 ) {
- dollyIn( getZoomScale() );
- } else if ( event.deltaY > 0 ) {
- dollyOut( getZoomScale() );
- }
- scope.update();
- }
- function handleKeyDown( event ) {
- let needsUpdate = false;
- switch ( event.code ) {
- case scope.keys.UP:
- if ( event.ctrlKey || event.metaKey || event.shiftKey ) {
- rotateUp( 2 * Math.PI * scope.rotateSpeed / scope.domElement.clientHeight );
- } else {
- pan( 0, scope.keyPanSpeed );
- }
- needsUpdate = true;
- break;
- case scope.keys.BOTTOM:
- if ( event.ctrlKey || event.metaKey || event.shiftKey ) {
- rotateUp( - 2 * Math.PI * scope.rotateSpeed / scope.domElement.clientHeight );
- } else {
- pan( 0, - scope.keyPanSpeed );
- }
- needsUpdate = true;
- break;
-
- //xzw add: 应要求把WS改为前进后退------
-
- case scope.keys.Forward:
- dollyIn( getZoomScale() );
- //state = STATE.DOLLY;
- break;
-
- case scope.keys.Backward:
- dollyOut( getZoomScale() );
- //state = STATE.DOLLY;
- break
- //--------------------------------------
-
-
- case scope.keys.LEFT:
- if ( event.ctrlKey || event.metaKey || event.shiftKey ) {
- rotateLeft( 2 * Math.PI * scope.rotateSpeed / scope.domElement.clientHeight );
- } else {
- pan( scope.keyPanSpeed, 0 );
- }
- needsUpdate = true;
- break;
- case scope.keys.RIGHT:
- if ( event.ctrlKey || event.metaKey || event.shiftKey ) {
- rotateLeft( - 2 * Math.PI * scope.rotateSpeed / scope.domElement.clientHeight );
- } else {
- pan( - scope.keyPanSpeed, 0 );
- }
- needsUpdate = true;
- break;
- }
- if ( needsUpdate ) {
- // prevent the browser from scrolling on cursor keys
- event.preventDefault();
- scope.update();
- }
- }
- function handleTouchStartRotate() {
- if ( pointers.length === 1 ) {
- rotateStart.set( pointers[0].pageX, pointers[0].pageY );
- } else {
- const x = 0.5 * ( pointers[0].pageX + pointers[1].pageX );
- const y = 0.5 * ( pointers[0].pageY + pointers[1].pageY );
- rotateStart.set( x, y );
- }
- }
- function handleTouchStartPan() {
- if ( pointers.length === 1 ) {
- panStart.set( pointers[0].pageX, pointers[0].pageY );
- } else {
- const x = 0.5 * ( pointers[0].pageX + pointers[1].pageX );
- const y = 0.5 * ( pointers[0].pageY + pointers[1].pageY );
- panStart.set( x, y );
- }
- }
- function handleTouchStartDolly() {
- const dx = pointers[0].pageX - pointers[1].pageX;
- const dy = pointers[0].pageY - pointers[1].pageY;
- const distance = Math.sqrt( dx * dx + dy * dy );
- dollyStart.set( 0, distance );
- }
- function handleTouchStartDollyPan() {
- if ( scope.enableZoom ) handleTouchStartDolly();
- if ( scope.enablePan ) handleTouchStartPan();
- }
- function handleTouchStartDollyRotate() {
- if ( scope.enableZoom ) handleTouchStartDolly();
- if ( scope.enableRotate ) handleTouchStartRotate();
- }
- function handleTouchMoveRotate( event ) {
- if ( pointers.length == 1 ) {
- rotateEnd.set( event.pageX, event.pageY );
- } else {
- const position = getSecondPointerPosition( event );
- const x = 0.5 * ( event.pageX + position.x );
- const y = 0.5 * ( event.pageY + position.y );
- rotateEnd.set( x, y );
- }
- rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed );
- const element = scope.domElement;
- rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height
- rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight );
- rotateStart.copy( rotateEnd );
- }
- function handleTouchMovePan( event ) {
- if ( pointers.length === 1 ) {
- panEnd.set( event.pageX, event.pageY );
- } else {
- const position = getSecondPointerPosition( event );
- const x = 0.5 * ( event.pageX + position.x );
- const y = 0.5 * ( event.pageY + position.y );
- panEnd.set( x, y );
- }
- panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed );
- pan( panDelta.x, panDelta.y );
- panStart.copy( panEnd );
- }
- function handleTouchMoveDolly( event ) {
- const position = getSecondPointerPosition( event );
- const dx = event.pageX - position.x;
- const dy = event.pageY - position.y;
- const distance = Math.sqrt( dx * dx + dy * dy );
- dollyEnd.set( 0, distance );
- dollyDelta.set( 0, Math.pow( dollyEnd.y / dollyStart.y, scope.zoomSpeed ) );
- dollyOut( dollyDelta.y );
- dollyStart.copy( dollyEnd );
- }
- function handleTouchMoveDollyPan( event ) {
- if ( scope.enableZoom ) handleTouchMoveDolly( event );
- if ( scope.enablePan ) handleTouchMovePan( event );
- }
- function handleTouchMoveDollyRotate( event ) {
- if ( scope.enableZoom ) handleTouchMoveDolly( event );
- if ( scope.enableRotate ) handleTouchMoveRotate( event );
- }
- //
- // event handlers - FSM: listen for events and reset state
- //
- function onPointerDown( event ) {
- if ( scope.enabled === false ) return;
- if ( pointers.length === 0 ) {
- scope.domElement.setPointerCapture( event.pointerId );
- scope.domElement.addEventListener( 'pointermove', onPointerMove );
- scope.domElement.addEventListener( 'pointerup', onPointerUp );
- }
- //
- addPointer( event );
- if ( event.pointerType === 'touch' ) {
- onTouchStart( event );
- } else {
- onMouseDown( event );
- }
- }
- function onPointerMove( event ) {
- if ( scope.enabled === false ) return;
- if ( event.pointerType === 'touch' ) {
- onTouchMove( event );
- } else {
- onMouseMove( event );
- }
- }
- function onPointerUp( event ) {
- removePointer( event );
- if ( pointers.length === 0 ) {
- scope.domElement.releasePointerCapture( event.pointerId );
- scope.domElement.removeEventListener( 'pointermove', onPointerMove );
- scope.domElement.removeEventListener( 'pointerup', onPointerUp );
- }
- scope.dispatchEvent( _endEvent );
- state = STATE.NONE;
- }
- function onMouseDown( event ) {
- let mouseAction;
- switch ( event.button ) {
- case 0:
- mouseAction = scope.mouseButtons.LEFT;
- break;
- case 1:
- mouseAction = scope.mouseButtons.MIDDLE;
- break;
- case 2:
- mouseAction = scope.mouseButtons.RIGHT;
- break;
- default:
- mouseAction = - 1;
- }
- switch ( mouseAction ) {
- case MOUSE.DOLLY:
- if ( scope.enableZoom === false ) return;
- handleMouseDownDolly( event );
- state = STATE.DOLLY;
- break;
- case MOUSE.ROTATE:
- if ( event.ctrlKey || event.metaKey || event.shiftKey ) {
- if ( scope.enablePan === false ) return;
- handleMouseDownPan( event );
- state = STATE.PAN;
- } else {
- if ( scope.enableRotate === false ) return;
- handleMouseDownRotate( event );
- state = STATE.ROTATE;
- }
- break;
- case MOUSE.PAN:
- if ( event.ctrlKey || event.metaKey || event.shiftKey ) {
- if ( scope.enableRotate === false ) return;
- handleMouseDownRotate( event );
- state = STATE.ROTATE;
- } else {
- if ( scope.enablePan === false ) return;
- handleMouseDownPan( event );
- state = STATE.PAN;
- }
- break;
- default:
- state = STATE.NONE;
- }
- if ( state !== STATE.NONE ) {
- scope.dispatchEvent( _startEvent );
- }
- }
- function onMouseMove( event ) {
- switch ( state ) {
- case STATE.ROTATE:
- if ( scope.enableRotate === false ) return;
- handleMouseMoveRotate( event );
- break;
- case STATE.DOLLY:
- if ( scope.enableZoom === false ) return;
- handleMouseMoveDolly( event );
- break;
- case STATE.PAN:
- if ( scope.enablePan === false ) return;
- handleMouseMovePan( event );
- break;
- }
- }
- function onMouseWheel( event ) {
- if ( scope.enabled === false || scope.enableZoom === false || state !== STATE.NONE ) return;
- event.preventDefault();
- scope.dispatchEvent( _startEvent );
- handleMouseWheel( event );
- scope.dispatchEvent( _endEvent );
- }
- function onKeyDown( event ) {
- if ( scope.enabled === false || scope.enablePan === false ) return;
- handleKeyDown( event );
- }
- function onTouchStart( event ) {
- trackPointer( event );
- switch ( pointers.length ) {
- case 1:
- switch ( scope.touches.ONE ) {
- case TOUCH.ROTATE:
- if ( scope.enableRotate === false ) return;
- handleTouchStartRotate();
- state = STATE.TOUCH_ROTATE;
- break;
- case TOUCH.PAN:
- if ( scope.enablePan === false ) return;
- handleTouchStartPan();
- state = STATE.TOUCH_PAN;
- break;
- default:
- state = STATE.NONE;
- }
- break;
- case 2:
- switch ( scope.touches.TWO ) {
- case TOUCH.DOLLY_PAN:
- if ( scope.enableZoom === false && scope.enablePan === false ) return;
- handleTouchStartDollyPan();
- state = STATE.TOUCH_DOLLY_PAN;
- break;
- case TOUCH.DOLLY_ROTATE:
- if ( scope.enableZoom === false && scope.enableRotate === false ) return;
- handleTouchStartDollyRotate();
- state = STATE.TOUCH_DOLLY_ROTATE;
- break;
- default:
- state = STATE.NONE;
- }
- break;
- default:
- state = STATE.NONE;
- }
- if ( state !== STATE.NONE ) {
- scope.dispatchEvent( _startEvent );
- }
- }
- function onTouchMove( event ) {
- trackPointer( event );
- switch ( state ) {
- case STATE.TOUCH_ROTATE:
- if ( scope.enableRotate === false ) return;
- handleTouchMoveRotate( event );
- scope.update();
- break;
- case STATE.TOUCH_PAN:
- if ( scope.enablePan === false ) return;
- handleTouchMovePan( event );
- scope.update();
- break;
- case STATE.TOUCH_DOLLY_PAN:
- if ( scope.enableZoom === false && scope.enablePan === false ) return;
- handleTouchMoveDollyPan( event );
- scope.update();
- break;
- case STATE.TOUCH_DOLLY_ROTATE:
- if ( scope.enableZoom === false && scope.enableRotate === false ) return;
- handleTouchMoveDollyRotate( event );
- scope.update();
- break;
- default:
- state = STATE.NONE;
- }
- }
- function onContextMenu( event ) {
- if ( scope.enabled === false ) return;
- event.preventDefault();
- }
- function addPointer( event ) {
- pointers.push( event );
- }
- function removePointer( event ) {
- delete pointerPositions[event.pointerId];
- for ( let i = 0; i < pointers.length; i ++ ) {
- if ( pointers[i].pointerId == event.pointerId ) {
- pointers.splice( i, 1 );
- return;
- }
- }
- }
- function trackPointer( event ) {
- let position = pointerPositions[event.pointerId];
- if ( position === undefined ) {
- position = new Vector2();
- pointerPositions[event.pointerId] = position;
- }
- position.set( event.pageX, event.pageY );
- }
- function getSecondPointerPosition( event ) {
- const pointer = ( event.pointerId === pointers[0].pointerId ) ? pointers[1] : pointers[0];
- return pointerPositions[pointer.pointerId];
- }
- //
- scope.domElement.addEventListener( 'contextmenu', onContextMenu );
- scope.domElement.addEventListener( 'pointerdown', onPointerDown );
- scope.domElement.addEventListener( 'pointercancel', onPointerUp );
- scope.domElement.addEventListener( 'wheel', onMouseWheel, { passive: false } );
- // force an update at start
- this.update();
- }
- }
- const fadeElement = (element, out, displayStyle, duration, onComplete) => {
- const startTime = performance.now();
- let startOpacity = element.style.display === 'none' ? 0 : parseFloat(element.style.opacity);
- if (isNaN(startOpacity)) startOpacity = 1;
- const interval = window.setInterval(() => {
- const currentTime = performance.now();
- const elapsed = currentTime - startTime;
- let t = Math.min(elapsed / duration, 1.0);
- if (t > 0.999) t = 1;
- let opacity;
- if (out) {
- opacity = (1.0 - t) * startOpacity;
- if (opacity < 0.0001) opacity = 0;
- } else {
- opacity = (1.0 - startOpacity) * t + startOpacity;
- }
- if (opacity > 0) {
- element.style.display = displayStyle;
- element.style.opacity = opacity;
- } else {
- element.style.display = 'none';
- }
- if (t >= 1) {
- if (onComplete) onComplete();
- window.clearInterval(interval);
- }
- }, 16);
- return interval;
- };
- const cancelFade = (interval) => {
- window.clearInterval(interval);
- };
- const STANDARD_FADE_DURATION = 500;
- class LoadingSpinner {
- static elementIDGen = 0;
- constructor(message, container) {
- this.taskIDGen = 0;
- this.elementID = LoadingSpinner.elementIDGen++;
- this.tasks = [];
- this.message = message || 'Loading...';
- this.container = container || document.body;
- this.spinnerContainerOuter = document.createElement('div');
- this.spinnerContainerOuter.className = `spinnerOuterContainer${this.elementID}`;
- this.spinnerContainerOuter.style.display = 'none';
- this.spinnerContainerPrimary = document.createElement('div');
- this.spinnerContainerPrimary.className = `spinnerContainerPrimary${this.elementID}`;
- this.spinnerPrimary = document.createElement('div');
- this.spinnerPrimary.classList.add(`spinner${this.elementID}`, `spinnerPrimary${this.elementID}`);
- this.messageContainerPrimary = document.createElement('div');
- this.messageContainerPrimary.classList.add(`messageContainer${this.elementID}`, `messageContainerPrimary${this.elementID}`);
- this.messageContainerPrimary.innerHTML = this.message;
- this.spinnerContainerMin = document.createElement('div');
- this.spinnerContainerMin.className = `spinnerContainerMin${this.elementID}`;
- this.spinnerMin = document.createElement('div');
- this.spinnerMin.classList.add(`spinner${this.elementID}`, `spinnerMin${this.elementID}`);
- this.messageContainerMin = document.createElement('div');
- this.messageContainerMin.classList.add(`messageContainer${this.elementID}`, `messageContainerMin${this.elementID}`);
- this.messageContainerMin.innerHTML = this.message;
- this.spinnerContainerPrimary.appendChild(this.spinnerPrimary);
- this.spinnerContainerPrimary.appendChild(this.messageContainerPrimary);
- this.spinnerContainerOuter.appendChild(this.spinnerContainerPrimary);
- this.spinnerContainerMin.appendChild(this.spinnerMin);
- this.spinnerContainerMin.appendChild(this.messageContainerMin);
- this.spinnerContainerOuter.appendChild(this.spinnerContainerMin);
- const style = document.createElement('style');
- style.innerHTML = `
- .spinnerOuterContainer${this.elementID} {
- width: 100%;
- height: 100%;
- margin: 0;
- top: 0;
- left: 0;
- position: absolute;
- pointer-events: none;
- }
- .messageContainer${this.elementID} {
- height: 20px;
- font-family: arial;
- font-size: 12pt;
- color: #ffffff;
- text-align: center;
- vertical-align: middle;
- }
- .spinner${this.elementID} {
- padding: 15px;
- background: #07e8d6;
- z-index:99999;
-
- aspect-ratio: 1;
- border-radius: 50%;
- --_m:
- conic-gradient(#0000,#000),
- linear-gradient(#000 0 0) content-box;
- -webkit-mask: var(--_m);
- mask: var(--_m);
- -webkit-mask-composite: source-out;
- mask-composite: subtract;
- box-sizing: border-box;
- animation: load 1s linear infinite;
- }
- .spinnerContainerPrimary${this.elementID} {
- z-index:99999;
- background-color: rgba(128, 128, 128, 0.75);
- border: #666666 1px solid;
- border-radius: 5px;
- padding-top: 20px;
- padding-bottom: 10px;
- margin: 0;
- position: absolute;
- top: 50%;
- left: 50%;
- transform: translate(-80px, -80px);
- width: 180px;
- pointer-events: auto;
- }
- .spinnerPrimary${this.elementID} {
- width: 120px;
- margin-left: 30px;
- }
- .messageContainerPrimary${this.elementID} {
- padding-top: 15px;
- }
- .spinnerContainerMin${this.elementID} {
- z-index:99999;
- background-color: rgba(128, 128, 128, 0.75);
- border: #666666 1px solid;
- border-radius: 5px;
- padding-top: 20px;
- padding-bottom: 15px;
- margin: 0;
- position: absolute;
- bottom: 50px;
- left: 50%;
- transform: translate(-50%, 0);
- display: flex;
- flex-direction: left;
- pointer-events: auto;
- min-width: 250px;
- }
- .messageContainerMin${this.elementID} {
- margin-right: 15px;
- }
- .spinnerMin${this.elementID} {
- width: 50px;
- height: 50px;
- margin-left: 15px;
- margin-right: 25px;
- }
- .messageContainerMin${this.elementID} {
- padding-top: 15px;
- }
-
- @keyframes load {
- to{transform: rotate(1turn)}
- }
- `;
- this.spinnerContainerOuter.appendChild(style);
- this.container.appendChild(this.spinnerContainerOuter);
- this.setMinimized(false, true);
- this.fadeTransitions = [];
- }
- addTask(message) {
- const newTask = {
- 'message': message,
- 'id': this.taskIDGen++
- };
- this.tasks.push(newTask);
- this.update();
- return newTask.id;
- }
- removeTask(id) {
- let index = 0;
- for (let task of this.tasks) {
- if (task.id === id) {
- this.tasks.splice(index, 1);
- break;
- }
- index++;
- }
- this.update();
- }
- removeAllTasks() {
- this.tasks = [];
- this.update();
- }
- setMessageForTask(id, message) {
- for (let task of this.tasks) {
- if (task.id === id) {
- task.message = message;
- break;
- }
- }
- this.update();
- }
- update() {
- if (this.tasks.length > 0) {
- this.show();
- this.setMessage(this.tasks[this.tasks.length - 1].message);
- } else {
- this.hide();
- }
- }
- show() {
- this.spinnerContainerOuter.style.display = 'block';
- this.visible = true;
- }
- hide() {
- this.spinnerContainerOuter.style.display = 'none';
- this.visible = false;
- }
- setContainer(container) {
- if (this.container && this.spinnerContainerOuter.parentElement === this.container) {
- this.container.removeChild(this.spinnerContainerOuter);
- }
- if (container) {
- this.container = container;
- this.container.appendChild(this.spinnerContainerOuter);
- this.spinnerContainerOuter.style.zIndex = this.container.style.zIndex + 1;
- }
- }
- setMinimized(minimized, instant) {
- const showHideSpinner = (element, show, instant, displayStyle, fadeTransitionsIndex) => {
- if (instant) {
- element.style.display = show ? displayStyle : 'none';
- } else {
- this.fadeTransitions[fadeTransitionsIndex] = fadeElement(element, !show, displayStyle, STANDARD_FADE_DURATION, () => {
- this.fadeTransitions[fadeTransitionsIndex] = null;
- });
- }
- };
- showHideSpinner(this.spinnerContainerPrimary, !minimized, instant, 'block', 0);
- showHideSpinner(this.spinnerContainerMin, minimized, instant, 'flex', 1);
- this.minimized = minimized;
- }
- setMessage(msg) {
- this.messageContainerPrimary.innerHTML = msg;
- this.messageContainerMin.innerHTML = msg;
- }
- }
- class LoadingProgressBar {
- constructor(container) {
- this.idGen = 0;
- this.tasks = [];
- this.container = container || document.body;
- this.progressBarContainerOuter = document.createElement('div');
- this.progressBarContainerOuter.className = 'progressBarOuterContainer';
- this.progressBarContainerOuter.style.display = 'none';
- this.progressBarBox = document.createElement('div');
- this.progressBarBox.className = 'progressBarBox';
- this.progressBarBackground = document.createElement('div');
- this.progressBarBackground.className = 'progressBarBackground';
- this.progressBar = document.createElement('div');
- this.progressBar.className = 'progressBar';
- this.progressBarBackground.appendChild(this.progressBar);
- this.progressBarBox.appendChild(this.progressBarBackground);
- this.progressBarContainerOuter.appendChild(this.progressBarBox);
- const style = document.createElement('style');
- style.innerHTML = `
- .progressBarOuterContainer {
- width: 100%;
- height: 100%;
- margin: 0;
- top: 0;
- left: 0;
- position: absolute;
- pointer-events: none;
- }
- .progressBarBox {
- z-index:99999;
- padding: 7px 9px 5px 7px;
- background-color: rgba(190, 190, 190, 0.75);
- border: #555555 1px solid;
- border-radius: 15px;
- margin: 0;
- position: absolute;
- bottom: 50px;
- left: 50%;
- transform: translate(-50%, 0);
- width: 180px;
- height: 30px;
- pointer-events: auto;
- }
- .progressBarBackground {
- width: 100%;
- height: 25px;
- border-radius:10px;
- background-color: rgba(128, 128, 128, 0.75);
- border: #444444 1px solid;
- box-shadow: inset 0 0 10px #333333;
- }
- .progressBar {
- height: 25px;
- width: 0px;
- border-radius:10px;
- background-color: rgba(0, 200, 0, 0.75);
- box-shadow: inset 0 0 10px #003300;
- }
- `;
- this.progressBarContainerOuter.appendChild(style);
- this.container.appendChild(this.progressBarContainerOuter);
- }
- show() {
- this.progressBarContainerOuter.style.display = 'block';
- }
- hide() {
- this.progressBarContainerOuter.style.display = 'none';
- }
- setProgress(progress) {
- this.progressBar.style.width = progress + '%';
- }
- setContainer(container) {
- if (this.container && this.progressBarContainerOuter.parentElement === this.container) {
- this.container.removeChild(this.progressBarContainerOuter);
- }
- if (container) {
- this.container = container;
- this.container.appendChild(this.progressBarContainerOuter);
- this.progressBarContainerOuter.style.zIndex = this.container.style.zIndex + 1;
- }
- }
- }
- class InfoPanel {
- constructor(container) {
- this.container = container || document.body;
- this.infoCells = {};
- const layout = [
- ['Camera position', 'cameraPosition'],
- ['Camera look-at', 'cameraLookAt'],
- ['Camera up', 'cameraUp'],
- ['Camera mode', 'orthographicCamera'],
- ['Cursor position', 'cursorPosition'],
- ['FPS', 'fps'],
- ['Rendering:', 'renderSplatCount'],
- ['Sort time', 'sortTime'],
- ['Render window', 'renderWindow'],
- ['Focal adjustment', 'focalAdjustment'],
- ['Splat scale', 'splatScale'],
- ['Point cloud mode', 'pointCloudMode']
- ];
- this.infoPanelContainer = document.createElement('div');
- const style = document.createElement('style');
- style.innerHTML = `
- .infoPanel {
- width: 430px;
- padding: 10px;
- background-color: rgba(50, 50, 50, 0.85);
- border: #555555 2px solid;
- color: #dddddd;
- border-radius: 10px;
- z-index: 9999;
- font-family: arial;
- font-size: 11pt;
- text-align: left;
- margin: 0;
- top: 10px;
- left:10px;
- position: absolute;
- pointer-events: auto;
- }
- .info-panel-cell {
- margin-bottom: 5px;
- padding-bottom: 2px;
- }
- .label-cell {
- font-weight: bold;
- font-size: 12pt;
- width: 140px;
- }
- `;
- this.infoPanelContainer.append(style);
- this.infoPanel = document.createElement('div');
- this.infoPanel.className = 'infoPanel';
- const infoTable = document.createElement('div');
- infoTable.style.display = 'table';
- for (let layoutEntry of layout) {
- const row = document.createElement('div');
- row.style.display = 'table-row';
- row.className = 'info-panel-row';
- const labelCell = document.createElement('div');
- labelCell.style.display = 'table-cell';
- labelCell.innerHTML = `${layoutEntry[0]}: `;
- labelCell.classList.add('info-panel-cell', 'label-cell');
- const spacerCell = document.createElement('div');
- spacerCell.style.display = 'table-cell';
- spacerCell.style.width = '10px';
- spacerCell.innerHTML = ' ';
- spacerCell.className = 'info-panel-cell';
- const infoCell = document.createElement('div');
- infoCell.style.display = 'table-cell';
- infoCell.innerHTML = '';
- infoCell.className = 'info-panel-cell';
- this.infoCells[layoutEntry[1]] = infoCell;
- row.appendChild(labelCell);
- row.appendChild(spacerCell);
- row.appendChild(infoCell);
- infoTable.appendChild(row);
- }
- this.infoPanel.appendChild(infoTable);
- this.infoPanelContainer.append(this.infoPanel);
- this.infoPanelContainer.style.display = 'none';
- this.container.appendChild(this.infoPanelContainer);
- this.visible = false;
- }
- update = function(renderDimensions, cameraPosition, cameraLookAtPosition, cameraUp, orthographicCamera,
- meshCursorPosition, currentFPS, splatCount, splatRenderCount,
- splatRenderCountPct, lastSortTime, focalAdjustment, splatScale, pointCloudMode) {
- const cameraPosString = `${cameraPosition.x.toFixed(5)}, ${cameraPosition.y.toFixed(5)}, ${cameraPosition.z.toFixed(5)}`;
- if (this.infoCells.cameraPosition.innerHTML !== cameraPosString) {
- this.infoCells.cameraPosition.innerHTML = cameraPosString;
- }
- if (cameraLookAtPosition) {
- const cla = cameraLookAtPosition;
- const cameraLookAtString = `${cla.x.toFixed(5)}, ${cla.y.toFixed(5)}, ${cla.z.toFixed(5)}`;
- if (this.infoCells.cameraLookAt.innerHTML !== cameraLookAtString) {
- this.infoCells.cameraLookAt.innerHTML = cameraLookAtString;
- }
- }
- const cameraUpString = `${cameraUp.x.toFixed(5)}, ${cameraUp.y.toFixed(5)}, ${cameraUp.z.toFixed(5)}`;
- if (this.infoCells.cameraUp.innerHTML !== cameraUpString) {
- this.infoCells.cameraUp.innerHTML = cameraUpString;
- }
- this.infoCells.orthographicCamera.innerHTML = orthographicCamera ? 'Orthographic' : 'Perspective';
- if (meshCursorPosition) {
- const cursPos = meshCursorPosition;
- const cursorPosString = `${cursPos.x.toFixed(5)}, ${cursPos.y.toFixed(5)}, ${cursPos.z.toFixed(5)}`;
- this.infoCells.cursorPosition.innerHTML = cursorPosString;
- } else {
- this.infoCells.cursorPosition.innerHTML = 'N/A';
- }
- this.infoCells.fps.innerHTML = currentFPS;
- this.infoCells.renderWindow.innerHTML = `${renderDimensions.x} x ${renderDimensions.y}`;
- this.infoCells.renderSplatCount.innerHTML =
- `${splatRenderCount} splats out of ${splatCount} (${splatRenderCountPct.toFixed(2)}%)`;
- this.infoCells.sortTime.innerHTML = `${lastSortTime.toFixed(3)} ms`;
- this.infoCells.focalAdjustment.innerHTML = `${focalAdjustment.toFixed(3)}`;
- this.infoCells.splatScale.innerHTML = `${splatScale.toFixed(3)}`;
- this.infoCells.pointCloudMode.innerHTML = `${pointCloudMode}`;
- };
- setContainer(container) {
- if (this.container && this.infoPanelContainer.parentElement === this.container) {
- this.container.removeChild(this.infoPanelContainer);
- }
- if (container) {
- this.container = container;
- this.container.appendChild(this.infoPanelContainer);
- this.infoPanelContainer.style.zIndex = this.container.style.zIndex + 1;
- }
- }
- show() {
- this.infoPanelContainer.style.display = 'block';
- this.visible = true;
- }
- hide() {
- this.infoPanelContainer.style.display = 'none';
- this.visible = false;
- }
- }
- class ArrowHelper extends THREE.Object3D {
- constructor(dir = new THREE.Vector3(0, 0, 1), origin = new THREE.Vector3(0, 0, 0), length = 1,
- radius = 0.1, color = 0xffff00, headLength = length * 0.2, headRadius = headLength * 0.2) {
- super();
- this.type = 'ArrowHelper';
- const lineGeometry = new THREE.CylinderGeometry(radius, radius, length, 32);
- lineGeometry.translate(0, length / 2.0, 0);
- const coneGeometry = new THREE.CylinderGeometry( 0, headRadius, headLength, 32);
- coneGeometry.translate(0, length, 0);
- this.position.copy( origin );
- this.line = new THREE.Mesh(lineGeometry, new THREE.MeshBasicMaterial({color: color, toneMapped: false}));
- this.line.matrixAutoUpdate = false;
- this.add(this.line);
- this.cone = new THREE.Mesh(coneGeometry, new THREE.MeshBasicMaterial({color: color, toneMapped: false}));
- this.cone.matrixAutoUpdate = false;
- this.add(this.cone);
- this.setDirection(dir);
- }
- setDirection( dir ) {
- if (dir.y > 0.99999) {
- this.quaternion.set(0, 0, 0, 1);
- } else if (dir.y < - 0.99999) {
- this.quaternion.set(1, 0, 0, 0);
- } else {
- _axis.set(dir.z, 0, -dir.x).normalize();
- const radians = Math.acos(dir.y);
- this.quaternion.setFromAxisAngle(_axis, radians);
- }
- }
- setColor( color ) {
- this.line.material.color.set(color);
- this.cone.material.color.set(color);
- }
- copy(source) {
- super.copy(source, false);
- this.line.copy(source.line);
- this.cone.copy(source.cone);
- return this;
- }
- dispose() {
- this.line.geometry.dispose();
- this.line.material.dispose();
- this.cone.geometry.dispose();
- this.cone.material.dispose();
- }
- }
- class SceneHelper {
- constructor(threeScene) {
- this.threeScene = threeScene;
- this.splatRenderTarget = null;
- this.renderTargetCopyQuad = null;
- this.renderTargetCopyCamera = null;
- this.meshCursor = null;
- this.focusMarker = null;
- this.controlPlane = null;
- this.debugRoot = null;
- this.secondaryDebugRoot = null;
- }
- updateSplatRenderTargetForRenderDimensions(width, height) {
- this.destroySplatRendertarget();
- this.splatRenderTarget = new THREE.WebGLRenderTarget(width, height, {
- format: THREE.RGBAFormat,
- stencilBuffer: false,
- depthBuffer: true,
- });
- this.splatRenderTarget.depthTexture = new THREE.DepthTexture(width, height);
- this.splatRenderTarget.depthTexture.format = THREE.DepthFormat;
- this.splatRenderTarget.depthTexture.type = THREE.UnsignedIntType;
- }
- destroySplatRendertarget() {
- if (this.splatRenderTarget) {
- this.splatRenderTarget = null;
- }
- }
- setupRenderTargetCopyObjects() {
- const uniforms = {
- 'sourceColorTexture': {
- 'type': 't',
- 'value': null
- },
- 'sourceDepthTexture': {
- 'type': 't',
- 'value': null
- },
- };
- const renderTargetCopyMaterial = new THREE.ShaderMaterial({
- vertexShader: `
- varying vec2 vUv;
- void main() {
- vUv = uv;
- gl_Position = vec4( position.xy, 0.0, 1.0 );
- }
- `,
- fragmentShader: `
- #include <common>
- #include <packing>
- varying vec2 vUv;
- uniform sampler2D sourceColorTexture;
- uniform sampler2D sourceDepthTexture;
- void main() {
- vec4 color = texture2D(sourceColorTexture, vUv);
- float fragDepth = texture2D(sourceDepthTexture, vUv).x;
- gl_FragDepth = fragDepth;
- gl_FragColor = vec4(color.rgb, color.a * 2.0);
- }
- `,
- uniforms: uniforms,
- depthWrite: false,
- depthTest: false,
- transparent: true,
- blending: THREE.CustomBlending,
- blendSrc: THREE.SrcAlphaFactor,
- blendSrcAlpha: THREE.SrcAlphaFactor,
- blendDst: THREE.OneMinusSrcAlphaFactor,
- blendDstAlpha: THREE.OneMinusSrcAlphaFactor
- });
- renderTargetCopyMaterial.extensions.fragDepth = true;
- this.renderTargetCopyQuad = new THREE.Mesh(new THREE.PlaneGeometry(2, 2), renderTargetCopyMaterial);
- this.renderTargetCopyCamera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);
- }
- destroyRenderTargetCopyObjects() {
- if (this.renderTargetCopyQuad) {
- disposeAllMeshes(this.renderTargetCopyQuad);
- this.renderTargetCopyQuad = null;
- }
- }
- setupMeshCursor() {
- if (!this.meshCursor) {
- const coneGeometry = new THREE.ConeGeometry(0.5, 1.5, 32);
- const coneMaterial = new THREE.MeshBasicMaterial({color: 0xFFFFFF});
- const downArrow = new THREE.Mesh(coneGeometry, coneMaterial);
- downArrow.rotation.set(0, 0, Math.PI);
- downArrow.position.set(0, 1, 0);
- const upArrow = new THREE.Mesh(coneGeometry, coneMaterial);
- upArrow.position.set(0, -1, 0);
- const leftArrow = new THREE.Mesh(coneGeometry, coneMaterial);
- leftArrow.rotation.set(0, 0, Math.PI / 2.0);
- leftArrow.position.set(1, 0, 0);
- const rightArrow = new THREE.Mesh(coneGeometry, coneMaterial);
- rightArrow.rotation.set(0, 0, -Math.PI / 2.0);
- rightArrow.position.set(-1, 0, 0);
- this.meshCursor = new THREE.Object3D();
- this.meshCursor.add(downArrow);
- this.meshCursor.add(upArrow);
- this.meshCursor.add(leftArrow);
- this.meshCursor.add(rightArrow);
- this.meshCursor.scale.set(0.1, 0.1, 0.1);
- this.threeScene.add(this.meshCursor);
- this.meshCursor.visible = false;
- }
- }
- destroyMeshCursor() {
- if (this.meshCursor) {
- disposeAllMeshes(this.meshCursor);
- this.threeScene.remove(this.meshCursor);
- this.meshCursor = null;
- }
- }
- setMeshCursorVisibility(visible) {
- this.meshCursor.visible = visible;
- }
- getMeschCursorVisibility() {
- return this.meshCursor.visible;
- }
- setMeshCursorPosition(position) {
- this.meshCursor.position.copy(position);
- }
- positionAndOrientMeshCursor(position, camera) {
- this.meshCursor.position.copy(position);
- this.meshCursor.up.copy(camera.up);
- this.meshCursor.lookAt(camera.position);
- }
- setupFocusMarker() {
- if (!this.focusMarker) {
- const sphereGeometry = new THREE.SphereGeometry(.5, 32, 32);
- const focusMarkerMaterial = SceneHelper.buildFocusMarkerMaterial();
- focusMarkerMaterial.depthTest = false;
- focusMarkerMaterial.depthWrite = false;
- focusMarkerMaterial.transparent = true;
- this.focusMarker = new THREE.Mesh(sphereGeometry, focusMarkerMaterial);
- }
- }
- destroyFocusMarker() {
- if (this.focusMarker) {
- disposeAllMeshes(this.focusMarker);
- this.focusMarker = null;
- }
- }
- updateFocusMarker = function() {
- const tempPosition = new THREE.Vector3();
- const tempMatrix = new THREE.Matrix4();
- const toCamera = new THREE.Vector3();
- return function(position, camera, viewport) {
- tempMatrix.copy(camera.matrixWorld).invert();
- tempPosition.copy(position).applyMatrix4(tempMatrix);
- tempPosition.normalize().multiplyScalar(10);
- tempPosition.applyMatrix4(camera.matrixWorld);
- toCamera.copy(camera.position).sub(position);
- const toCameraDistance = toCamera.length();
- this.focusMarker.position.copy(position);
- this.focusMarker.scale.set(toCameraDistance, toCameraDistance, toCameraDistance);
- this.focusMarker.material.uniforms.realFocusPosition.value.copy(position);
- this.focusMarker.material.uniforms.viewport.value.copy(viewport);
- this.focusMarker.material.uniformsNeedUpdate = true;
- };
- }();
- setFocusMarkerVisibility(visible) {
- this.focusMarker.visible = visible;
- }
- setFocusMarkerOpacity(opacity) {
- this.focusMarker.material.uniforms.opacity.value = opacity;
- this.focusMarker.material.uniformsNeedUpdate = true;
- }
- getFocusMarkerOpacity() {
- return this.focusMarker.material.uniforms.opacity.value;
- }
- setupControlPlane() {
- if (!this.controlPlane) {
- const planeGeometry = new THREE.PlaneGeometry(1, 1);
- planeGeometry.rotateX(-Math.PI / 2);
- const planeMaterial = new THREE.MeshBasicMaterial({color: 0xffffff});
- planeMaterial.transparent = true;
- planeMaterial.opacity = 0.6;
- planeMaterial.depthTest = false;
- planeMaterial.depthWrite = false;
- planeMaterial.side = THREE.DoubleSide;
- const planeMesh = new THREE.Mesh(planeGeometry, planeMaterial);
- const arrowDir = new THREE.Vector3(0, 1, 0);
- arrowDir.normalize();
- const arrowOrigin = new THREE.Vector3(0, 0, 0);
- const arrowLength = 0.5;
- const arrowRadius = 0.01;
- const arrowColor = 0x00dd00;
- const arrowHelper = new ArrowHelper(arrowDir, arrowOrigin, arrowLength, arrowRadius, arrowColor, 0.1, 0.03);
- this.controlPlane = new THREE.Object3D();
- this.controlPlane.add(planeMesh);
- this.controlPlane.add(arrowHelper);
- }
- }
- destroyControlPlane() {
- if (this.controlPlane) {
- disposeAllMeshes(this.controlPlane);
- this.controlPlane = null;
- }
- }
- setControlPlaneVisibility(visible) {
- this.controlPlane.visible = visible;
- }
- positionAndOrientControlPlane = function() {
- const tempQuaternion = new THREE.Quaternion();
- const defaultUp = new THREE.Vector3(0, 1, 0);
- return function(position, up) {
- tempQuaternion.setFromUnitVectors(defaultUp, up);
- this.controlPlane.position.copy(position);
- this.controlPlane.quaternion.copy(tempQuaternion);
- };
- }();
- addDebugMeshes() {
- this.debugRoot = this.createDebugMeshes();
- this.secondaryDebugRoot = this.createSecondaryDebugMeshes();
- this.threeScene.add(this.debugRoot);
- this.threeScene.add(this.secondaryDebugRoot);
- }
- destroyDebugMeshes() {
- for (let debugRoot of [this.debugRoot, this.secondaryDebugRoot]) {
- if (debugRoot) {
- disposeAllMeshes(debugRoot);
- this.threeScene.remove(debugRoot);
- }
- }
- this.debugRoot = null;
- this.secondaryDebugRoot = null;
- }
- createDebugMeshes(renderOrder) {
- const sphereGeometry = new THREE.SphereGeometry(1, 32, 32);
- const debugMeshRoot = new THREE.Object3D();
- const createMesh = (color, position) => {
- let sphereMesh = new THREE.Mesh(sphereGeometry, SceneHelper.buildDebugMaterial(color));
- sphereMesh.renderOrder = renderOrder;
- debugMeshRoot.add(sphereMesh);
- sphereMesh.position.fromArray(position);
- };
- createMesh(0xff0000, [-50, 0, 0]);
- createMesh(0xff0000, [50, 0, 0]);
- createMesh(0x00ff00, [0, 0, -50]);
- createMesh(0x00ff00, [0, 0, 50]);
- createMesh(0xffaa00, [5, 0, 5]);
- return debugMeshRoot;
- }
- createSecondaryDebugMeshes(renderOrder) {
- const boxGeometry = new THREE.BoxGeometry(3, 3, 3);
- const debugMeshRoot = new THREE.Object3D();
- let boxColor = 0xBBBBBB;
- const createMesh = (position) => {
- let boxMesh = new THREE.Mesh(boxGeometry, SceneHelper.buildDebugMaterial(boxColor));
- boxMesh.renderOrder = renderOrder;
- debugMeshRoot.add(boxMesh);
- boxMesh.position.fromArray(position);
- };
- let separation = 10;
- createMesh([-separation, 0, -separation]);
- createMesh([-separation, 0, separation]);
- createMesh([separation, 0, -separation]);
- createMesh([separation, 0, separation]);
- return debugMeshRoot;
- }
- static buildDebugMaterial(color) {
- const vertexShaderSource = `
- #include <common>
- varying float ndcDepth;
- void main() {
- gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(position.xyz, 1.0);
- ndcDepth = gl_Position.z / gl_Position.w;
- gl_Position.x = gl_Position.x / gl_Position.w;
- gl_Position.y = gl_Position.y / gl_Position.w;
- gl_Position.z = 0.0;
- gl_Position.w = 1.0;
-
- }
- `;
- const fragmentShaderSource = `
- #include <common>
- uniform vec3 color;
- varying float ndcDepth;
- void main() {
- gl_FragDepth = (ndcDepth + 1.0) / 2.0;
- gl_FragColor = vec4(color.rgb, 0.0);
- }
- `;
- const uniforms = {
- 'color': {
- 'type': 'v3',
- 'value': new THREE.Color(color)
- },
- };
- const material = new THREE.ShaderMaterial({
- uniforms: uniforms,
- vertexShader: vertexShaderSource,
- fragmentShader: fragmentShaderSource,
- transparent: false,
- depthTest: true,
- depthWrite: true,
- side: THREE.FrontSide
- });
- material.extensions.fragDepth = true;
- return material;
- }
- static buildFocusMarkerMaterial(color) {
- const vertexShaderSource = `
- #include <common>
- uniform vec2 viewport;
- uniform vec3 realFocusPosition;
- varying vec4 ndcPosition;
- varying vec4 ndcCenter;
- varying vec4 ndcFocusPosition;
- void main() {
- float radius = 0.01;
- vec4 viewPosition = modelViewMatrix * vec4(position.xyz, 1.0);
- vec4 viewCenter = modelViewMatrix * vec4(0.0, 0.0, 0.0, 1.0);
- vec4 viewFocusPosition = modelViewMatrix * vec4(realFocusPosition, 1.0);
- ndcPosition = projectionMatrix * viewPosition;
- ndcPosition = ndcPosition * vec4(1.0 / ndcPosition.w);
- ndcCenter = projectionMatrix * viewCenter;
- ndcCenter = ndcCenter * vec4(1.0 / ndcCenter.w);
- ndcFocusPosition = projectionMatrix * viewFocusPosition;
- ndcFocusPosition = ndcFocusPosition * vec4(1.0 / ndcFocusPosition.w);
- gl_Position = projectionMatrix * viewPosition;
- }
- `;
- const fragmentShaderSource = `
- #include <common>
- uniform vec3 color;
- uniform vec2 viewport;
- uniform float opacity;
- varying vec4 ndcPosition;
- varying vec4 ndcCenter;
- varying vec4 ndcFocusPosition;
- void main() {
- vec2 screenPosition = vec2(ndcPosition) * viewport;
- vec2 screenCenter = vec2(ndcCenter) * viewport;
- vec2 screenVec = screenPosition - screenCenter;
- float projectedRadius = length(screenVec);
- float lineWidth = 0.0005 * viewport.y;
- float aaRange = 0.0025 * viewport.y;
- float radius = 0.06 * viewport.y;
- float radDiff = abs(projectedRadius - radius) - lineWidth;
- float alpha = 1.0 - clamp(radDiff / 5.0, 0.0, 1.0);
- gl_FragColor = vec4(color.rgb, alpha * opacity);
- }
- `;
- const uniforms = {
- 'color': {
- 'type': 'v3',
- 'value': new THREE.Color(color)
- },
- 'realFocusPosition': {
- 'type': 'v3',
- 'value': new THREE.Vector3()
- },
- 'viewport': {
- 'type': 'v2',
- 'value': new THREE.Vector2()
- },
- 'opacity': {
- 'value': 0.0
- }
- };
- const material = new THREE.ShaderMaterial({
- uniforms: uniforms,
- vertexShader: vertexShaderSource,
- fragmentShader: fragmentShaderSource,
- transparent: true,
- depthTest: false,
- depthWrite: false,
- side: THREE.FrontSide
- });
- return material;
- }
- dispose() {
- this.destroyMeshCursor();
- this.destroyFocusMarker();
- this.destroyDebugMeshes();
- this.destroyControlPlane();
- this.destroyRenderTargetCopyObjects();
- this.destroySplatRendertarget();
- }
- }
- const VectorRight = new THREE.Vector3(1, 0, 0);
- const VectorUp = new THREE.Vector3(0, 1, 0);
- const VectorBackward = new THREE.Vector3(0, 0, 1);
- class Ray {
- constructor(origin = new THREE.Vector3(), direction = new THREE.Vector3()) {
- this.origin = new THREE.Vector3();
- this.direction = new THREE.Vector3();
- this.setParameters(origin, direction);
- }
- setParameters(origin, direction) {
- this.origin.copy(origin);
- this.direction.copy(direction).normalize();
- }
- boxContainsPoint(box, point, epsilon) {
- return point.x < box.min.x - epsilon || point.x > box.max.x + epsilon ||
- point.y < box.min.y - epsilon || point.y > box.max.y + epsilon ||
- point.z < box.min.z - epsilon || point.z > box.max.z + epsilon ? false : true;
- }
- intersectBox = function() {
- const planeIntersectionPoint = new THREE.Vector3();
- const planeIntersectionPointArray = [];
- const originArray = [];
- const directionArray = [];
- return function(box, outHit) {
- originArray[0] = this.origin.x;
- originArray[1] = this.origin.y;
- originArray[2] = this.origin.z;
- directionArray[0] = this.direction.x;
- directionArray[1] = this.direction.y;
- directionArray[2] = this.direction.z;
- if (this.boxContainsPoint(box, this.origin, 0.0001)) {
- if (outHit) {
- outHit.origin.copy(this.origin);
- outHit.normal.set(0, 0, 0);
- outHit.distance = -1;
- }
- return true;
- }
- for (let i = 0; i < 3; i++) {
- if (directionArray[i] == 0.0) continue;
- const hitNormal = i == 0 ? VectorRight : i == 1 ? VectorUp : VectorBackward;
- const extremeVec = directionArray[i] < 0 ? box.max : box.min;
- let multiplier = -Math.sign(directionArray[i]);
- planeIntersectionPointArray[0] = i == 0 ? extremeVec.x : i == 1 ? extremeVec.y : extremeVec.z;
- let toSide = planeIntersectionPointArray[0] - originArray[i];
- if (toSide * multiplier < 0) {
- const idx1 = (i + 1) % 3;
- const idx2 = (i + 2) % 3;
- planeIntersectionPointArray[2] = directionArray[idx1] / directionArray[i] * toSide + originArray[idx1];
- planeIntersectionPointArray[1] = directionArray[idx2] / directionArray[i] * toSide + originArray[idx2];
- planeIntersectionPoint.set(planeIntersectionPointArray[i],
- planeIntersectionPointArray[idx2],
- planeIntersectionPointArray[idx1]);
- if (this.boxContainsPoint(box, planeIntersectionPoint, 0.0001)) {
- if (outHit) {
- outHit.origin.copy(planeIntersectionPoint);
- outHit.normal.copy(hitNormal).multiplyScalar(multiplier);
- outHit.distance = planeIntersectionPoint.sub(this.origin).length();
- }
- return true;
- }
- }
- }
- return false;
- };
- }();
- intersectSphere = function() {
- const toSphereCenterVec = new THREE.Vector3();
- return function(center, radius, outHit) {
- toSphereCenterVec.copy(center).sub(this.origin);
- const toClosestApproach = toSphereCenterVec.dot(this.direction);
- const toClosestApproachSq = toClosestApproach * toClosestApproach;
- const toSphereCenterSq = toSphereCenterVec.dot(toSphereCenterVec);
- const diffSq = toSphereCenterSq - toClosestApproachSq;
- const radiusSq = radius * radius;
- if (diffSq > radiusSq) return false;
- const thc = Math.sqrt(radiusSq - diffSq);
- const t0 = toClosestApproach - thc;
- const t1 = toClosestApproach + thc;
- if (t1 < 0) return false;
- let t = t0 < 0 ? t1 : t0;
- if (outHit) {
- outHit.origin.copy(this.origin).addScaledVector(this.direction, t);
- outHit.normal.copy(outHit.origin).sub(center).normalize();
- outHit.distance = t;
- }
- return true;
- };
- }();
- }
- class Hit {
- constructor() {
- this.origin = new THREE.Vector3();
- this.normal = new THREE.Vector3();
- this.distance = 0;
- this.splatIndex = 0;
- }
- set(origin, normal, distance, splatIndex) {
- this.origin.copy(origin);
- this.normal.copy(normal);
- this.distance = distance;
- this.splatIndex = splatIndex;
- }
- clone() {
- const hitClone = new Hit();
- hitClone.origin.copy(this.origin);
- hitClone.normal.copy(this.normal);
- hitClone.distance = this.distance;
- hitClone.splatIndex = this.splatIndex;
- return hitClone;
- }
- }
- const SplatRenderMode = {
- ThreeD: 0,
- TwoD: 1
- };
- class Raycaster {
- constructor(origin, direction, raycastAgainstTrueSplatEllipsoid = false) {
- this.ray = new Ray(origin, direction);
- this.raycastAgainstTrueSplatEllipsoid = raycastAgainstTrueSplatEllipsoid;
- }
- setFromCameraAndScreenPosition = function() {
- const ndcCoords = new THREE.Vector2();
- return function(camera, screenPosition, screenDimensions) {
- ndcCoords.x = screenPosition.x / screenDimensions.x * 2.0 - 1.0;
- ndcCoords.y = (screenDimensions.y - screenPosition.y) / screenDimensions.y * 2.0 - 1.0;
- if (camera.isPerspectiveCamera) {
- this.ray.origin.setFromMatrixPosition(camera.matrixWorld);
- this.ray.direction.set(ndcCoords.x, ndcCoords.y, 0.5 ).unproject(camera).sub(this.ray.origin).normalize();
- this.camera = camera;
- } else if (camera.isOrthographicCamera) {
- this.ray.origin.set(ndcCoords.x, ndcCoords.y,
- (camera.near + camera.far) / (camera.near - camera.far)).unproject(camera);
- this.ray.direction.set(0, 0, -1).transformDirection(camera.matrixWorld);
- this.camera = camera;
- } else {
- throw new Error('Raycaster::setFromCameraAndScreenPosition() -> Unsupported camera type');
- }
- };
- }();
- intersectSplatMesh = function() {
- const toLocal = new THREE.Matrix4();
- const fromLocal = new THREE.Matrix4();
- const sceneTransform = new THREE.Matrix4();
- const localRay = new Ray();
- const tempPoint = new THREE.Vector3();
- return function(splatMesh, outHits = []) {
- const splatTree = splatMesh.getSplatTree();
- if (!splatTree) return;
- for (let s = 0; s < splatTree.subTrees.length; s++) {
- const subTree = splatTree.subTrees[s];
- fromLocal.copy(splatMesh.matrixWorld);
- if (splatMesh.dynamicMode) {
- splatMesh.getSceneTransform(s, sceneTransform);
- fromLocal.multiply(sceneTransform);
- }
- toLocal.copy(fromLocal).invert();
- localRay.origin.copy(this.ray.origin).applyMatrix4(toLocal);
- localRay.direction.copy(this.ray.origin).add(this.ray.direction);
- localRay.direction.applyMatrix4(toLocal).sub(localRay.origin).normalize();
- const outHitsForSubTree = [];
- if (subTree.rootNode) {
- this.castRayAtSplatTreeNode(localRay, splatTree, subTree.rootNode, outHitsForSubTree);
- }
- outHitsForSubTree.forEach((hit) => {
- hit.origin.applyMatrix4(fromLocal);
- hit.normal.applyMatrix4(fromLocal).normalize();
- hit.distance = tempPoint.copy(hit.origin).sub(this.ray.origin).length();
- });
- outHits.push(...outHitsForSubTree);
- }
- outHits.sort((a, b) => {
- if (a.distance > b.distance) return 1;
- else return -1;
- });
- return outHits;
- };
- }();
- castRayAtSplatTreeNode = function() {
- const tempColor = new THREE.Vector4();
- const tempCenter = new THREE.Vector3();
- const tempScale = new THREE.Vector3();
- const tempRotation = new THREE.Quaternion();
- const tempHit = new Hit();
- const scaleEpsilon = 0.0000001;
- const origin = new THREE.Vector3(0, 0, 0);
- const uniformScaleMatrix = new THREE.Matrix4();
- const scaleMatrix = new THREE.Matrix4();
- const rotationMatrix = new THREE.Matrix4();
- const toSphereSpace = new THREE.Matrix4();
- const fromSphereSpace = new THREE.Matrix4();
- const tempRay = new Ray();
- return function(ray, splatTree, node, outHits = []) {
- if (!ray.intersectBox(node.boundingBox)) {
- return;
- }
- if (node.data && node.data.indexes && node.data.indexes.length > 0) {
- for (let i = 0; i < node.data.indexes.length; i++) {
- const splatGlobalIndex = node.data.indexes[i];
- const splatSceneIndex = splatTree.splatMesh.getSceneIndexForSplat(splatGlobalIndex);
- const splatScene = splatTree.splatMesh.getScene(splatSceneIndex);
- if (!splatScene.visible) continue;
- splatTree.splatMesh.getSplatColor(splatGlobalIndex, tempColor);
- splatTree.splatMesh.getSplatCenter(splatGlobalIndex, tempCenter);
- splatTree.splatMesh.getSplatScaleAndRotation(splatGlobalIndex, tempScale, tempRotation);
- /* if (tempScale.x <= scaleEpsilon || tempScale.y <= scaleEpsilon ||
- splatTree.splatMesh.splatRenderMode === SplatRenderMode.ThreeD && tempScale.z <= scaleEpsilon) {
- continue;
- } */ //xzw改。 不知道为何有一个模型dynamic时tempScale.是0。导致无法pick。所以这段先注释 (0829)
- if (!this.raycastAgainstTrueSplatEllipsoid) {
- let radius = (tempScale.x + tempScale.y);
- let componentCount = 2;
- if (splatTree.splatMesh.splatRenderMode === SplatRenderMode.ThreeD) {
- radius += tempScale.z;
- componentCount = 3;
- }
- radius = radius / componentCount;
- if (ray.intersectSphere(tempCenter, radius, tempHit)) {
- const hitClone = tempHit.clone();
- hitClone.splatIndex = splatGlobalIndex;
- outHits.push(hitClone);
- }
- } else {
- scaleMatrix.makeScale(tempScale.x, tempScale.y, tempScale.z);
- rotationMatrix.makeRotationFromQuaternion(tempRotation);
- const uniformScale = Math.log10(tempColor.w) * 2.0;
- uniformScaleMatrix.makeScale(uniformScale, uniformScale, uniformScale);
- fromSphereSpace.copy(uniformScaleMatrix).multiply(rotationMatrix).multiply(scaleMatrix);
- toSphereSpace.copy(fromSphereSpace).invert();
- tempRay.origin.copy(ray.origin).sub(tempCenter).applyMatrix4(toSphereSpace);
- tempRay.direction.copy(ray.origin).add(ray.direction).sub(tempCenter);
- tempRay.direction.applyMatrix4(toSphereSpace).sub(tempRay.origin).normalize();
- if (tempRay.intersectSphere(origin, 1.0, tempHit)) {
- const hitClone = tempHit.clone();
- hitClone.splatIndex = splatGlobalIndex;
- hitClone.origin.applyMatrix4(fromSphereSpace).add(tempCenter);
- outHits.push(hitClone);
- }
- }
- }
- }
- if (node.children && node.children.length > 0) {
- for (let child of node.children) {
- this.castRayAtSplatTreeNode(ray, splatTree, child, outHits);
- }
- }
- return outHits;
- };
- }();
- }
- class SplatMaterial {
- static buildVertexShaderBase(dynamicMode = false, enableOptionalEffects = false, maxSphericalHarmonicsDegree = 0, customVars = '') {
-
- let vertexShaderSource = `
- precision highp float;
- #include <common>
- attribute uint splatIndex;
- uniform highp usampler2D centersColorsTexture;
- uniform highp sampler2D sphericalHarmonicsTexture;
- uniform highp sampler2D sphericalHarmonicsTextureR;
- uniform highp sampler2D sphericalHarmonicsTextureG;
- uniform highp sampler2D sphericalHarmonicsTextureB;
- uniform highp usampler2D sceneIndexesTexture;
- uniform vec2 sceneIndexesTextureSize;
- uniform int sceneCount;
- `;
- if (enableOptionalEffects) {
- vertexShaderSource += `
- uniform float sceneOpacity[${Constants.MaxScenes}];
- uniform int sceneVisibility[${Constants.MaxScenes}];
- `;
- }
- if (dynamicMode) {
- vertexShaderSource += `
- uniform highp mat4 transforms[${Constants.MaxScenes}];
- `;
- }
- vertexShaderSource += `
- ${customVars}
- uniform vec2 focal;
- uniform float orthoZoom;
- uniform int orthographicMode;
- uniform int pointCloudModeEnabled;
- uniform float inverseFocalAdjustment;
- uniform vec2 viewport;
- uniform vec2 basisViewport;
- uniform vec2 centersColorsTextureSize;
- uniform int sphericalHarmonicsDegree;
- uniform vec2 sphericalHarmonicsTextureSize;
- uniform int sphericalHarmonics8BitMode;
- uniform int sphericalHarmonicsMultiTextureMode;
- uniform float visibleRegionRadius;
- uniform float visibleRegionFadeStartRadius;
- uniform float firstRenderTime;
- uniform float currentTime;
- uniform int fadeInComplete;
- uniform vec3 sceneCenter;
- uniform float splatScale;
- uniform float sphericalHarmonics8BitCompressionRangeMin[${Constants.MaxScenes}];
- uniform float sphericalHarmonics8BitCompressionRangeMax[${Constants.MaxScenes}];
- varying vec4 vColor;
- varying vec2 vUv;
- varying vec2 vPosition;
- mat3 quaternionToRotationMatrix(float x, float y, float z, float w) {
- float s = 1.0 / sqrt(w * w + x * x + y * y + z * z);
-
- return mat3(
- 1. - 2. * (y * y + z * z),
- 2. * (x * y + w * z),
- 2. * (x * z - w * y),
- 2. * (x * y - w * z),
- 1. - 2. * (x * x + z * z),
- 2. * (y * z + w * x),
- 2. * (x * z + w * y),
- 2. * (y * z - w * x),
- 1. - 2. * (x * x + y * y)
- );
- }
- const float sqrt8 = sqrt(8.0);
- const float minAlpha = 1.0 / 255.0;
- const vec4 encodeNorm4 = vec4(1.0 / 255.0, 1.0 / 255.0, 1.0 / 255.0, 1.0 / 255.0);
- const uvec4 mask4 = uvec4(uint(0x000000FF), uint(0x0000FF00), uint(0x00FF0000), uint(0xFF000000));
- const uvec4 shift4 = uvec4(0, 8, 16, 24);
- vec4 uintToRGBAVec (uint u) {
- uvec4 urgba = mask4 & u;
- urgba = urgba >> shift4;
- vec4 rgba = vec4(urgba) * encodeNorm4;
- return rgba;
- }
- vec2 getDataUV(in int stride, in int offset, in vec2 dimensions) {
- vec2 samplerUV = vec2(0.0, 0.0);
- float d = float(splatIndex * uint(stride) + uint(offset)) / dimensions.x;
- samplerUV.y = float(floor(d)) / dimensions.y;
- samplerUV.x = fract(d);
- return samplerUV;
- }
- vec2 getDataUVF(in uint sIndex, in float stride, in uint offset, in vec2 dimensions) {
- vec2 samplerUV = vec2(0.0, 0.0);
- float d = float(uint(float(sIndex) * stride) + offset) / dimensions.x;
- samplerUV.y = float(floor(d)) / dimensions.y;
- samplerUV.x = fract(d);
- return samplerUV;
- }
- const float SH_C1 = 0.4886025119029199f;
- const float[5] SH_C2 = float[](1.0925484, -1.0925484, 0.3153916, -1.0925484, 0.5462742);
- void main () {
- uint oddOffset = splatIndex & uint(0x00000001);
- uint doubleOddOffset = oddOffset * uint(2);
- bool isEven = oddOffset == uint(0);
- uint nearestEvenIndex = splatIndex - oddOffset;
- float fOddOffset = float(oddOffset);
- uvec4 sampledCenterColor = texture(centersColorsTexture, getDataUV(1, 0, centersColorsTextureSize));
- vec3 splatCenter = uintBitsToFloat(uvec3(sampledCenterColor.gba));
- uint sceneIndex = uint(0);
- if (sceneCount > 1) {
- sceneIndex = texture(sceneIndexesTexture, getDataUV(1, 0, sceneIndexesTextureSize)).r;
- }
- `;
- if (enableOptionalEffects) {
- vertexShaderSource += `
- float splatOpacityFromScene = sceneOpacity[sceneIndex];
- int sceneVisible = sceneVisibility[sceneIndex];
- if (splatOpacityFromScene <= 0.01 || sceneVisible == 0) {
- gl_Position = vec4(0.0, 0.0, 2.0, 1.0);
- return;
- }
- `;
- }
- if (dynamicMode) {
- vertexShaderSource += `
- mat4 transform = transforms[sceneIndex];
- //mat4 transformModelViewMatrix = modelViewMatrix * transform;
-
- mat4 transformModelViewMatrix = modelViewMatrix; //xzw改 直接用最外层的matrixWorld
-
- `;
- } else {
- vertexShaderSource += `mat4 transformModelViewMatrix = modelViewMatrix;`;
- }
- vertexShaderSource += `
- float sh8BitCompressionRangeMinForScene = sphericalHarmonics8BitCompressionRangeMin[sceneIndex];
- float sh8BitCompressionRangeMaxForScene = sphericalHarmonics8BitCompressionRangeMax[sceneIndex];
- float sh8BitCompressionRangeForScene = sh8BitCompressionRangeMaxForScene - sh8BitCompressionRangeMinForScene;
- float sh8BitCompressionHalfRangeForScene = sh8BitCompressionRangeForScene / 2.0;
- vec3 vec8BitSHShift = vec3(sh8BitCompressionRangeMinForScene);
- vec4 viewCenter = transformModelViewMatrix * vec4(splatCenter, 1.0);
- vec4 clipCenter = projectionMatrix * viewCenter;
- float clip = 1.2 * clipCenter.w;
- if (clipCenter.z < -clip || clipCenter.x < -clip || clipCenter.x > clip || clipCenter.y < -clip || clipCenter.y > clip) {
- gl_Position = vec4(0.0, 0.0, 2.0, 1.0);
- return;
- }
- vec3 ndcCenter = clipCenter.xyz / clipCenter.w;
- vPosition = position.xy;
- vColor = uintToRGBAVec(sampledCenterColor.r);
- `;
- // Proceed to sampling and rendering 1st degree spherical harmonics
- if (maxSphericalHarmonicsDegree >= 1) {
- vertexShaderSource += `
- if (sphericalHarmonicsDegree >= 1) {
- `;
- if (dynamicMode) {
- vertexShaderSource += `
- //vec3 worldViewDir = normalize(splatCenter - vec3(inverse(transform) * vec4(cameraPosition, 1.0)));
- vec3 worldViewDir = normalize(splatCenter - vec3(inverse(modelMatrix) * vec4(cameraPosition, 1.0))); //xzw改 直接用最外层的matrixWorld
- `;
- } else {
- vertexShaderSource += `
- vec3 worldViewDir = normalize(splatCenter - cameraPosition);
- `;
- }
- vertexShaderSource += `
- vec3 sh1;
- vec3 sh2;
- vec3 sh3;
- `;
- if (maxSphericalHarmonicsDegree >= 2) {
- vertexShaderSource += `
- vec3 sh4;
- vec3 sh5;
- vec3 sh6;
- vec3 sh7;
- vec3 sh8;
- `;
- }
- // Determining how to sample spherical harmonics textures to get the coefficients for calculations for a given degree
- // depends on how many total degrees (maxSphericalHarmonicsDegree) are present in the textures. This is because that
- // number affects how they are packed in the textures, and therefore the offset & stride required to access them.
- // Sample spherical harmonics textures with 1 degree worth of data for 1st degree calculations, and store in sh1, sh2, and sh3
- if (maxSphericalHarmonicsDegree === 1) {
- vertexShaderSource += `
- if (sphericalHarmonicsMultiTextureMode == 0) {
- vec2 shUV = getDataUVF(nearestEvenIndex, 2.5, doubleOddOffset, sphericalHarmonicsTextureSize);
- vec4 sampledSH0123 = texture(sphericalHarmonicsTexture, shUV);
- shUV = getDataUVF(nearestEvenIndex, 2.5, doubleOddOffset + uint(1), sphericalHarmonicsTextureSize);
- vec4 sampledSH4567 = texture(sphericalHarmonicsTexture, shUV);
- shUV = getDataUVF(nearestEvenIndex, 2.5, doubleOddOffset + uint(2), sphericalHarmonicsTextureSize);
- vec4 sampledSH891011 = texture(sphericalHarmonicsTexture, shUV);
- sh1 = vec3(sampledSH0123.rgb) * (1.0 - fOddOffset) + vec3(sampledSH0123.ba, sampledSH4567.r) * fOddOffset;
- sh2 = vec3(sampledSH0123.a, sampledSH4567.rg) * (1.0 - fOddOffset) + vec3(sampledSH4567.gba) * fOddOffset;
- sh3 = vec3(sampledSH4567.ba, sampledSH891011.r) * (1.0 - fOddOffset) + vec3(sampledSH891011.rgb) * fOddOffset;
- } else {
- vec2 sampledSH01R = texture(sphericalHarmonicsTextureR, getDataUV(2, 0, sphericalHarmonicsTextureSize)).rg;
- vec2 sampledSH23R = texture(sphericalHarmonicsTextureR, getDataUV(2, 1, sphericalHarmonicsTextureSize)).rg;
- vec2 sampledSH01G = texture(sphericalHarmonicsTextureG, getDataUV(2, 0, sphericalHarmonicsTextureSize)).rg;
- vec2 sampledSH23G = texture(sphericalHarmonicsTextureG, getDataUV(2, 1, sphericalHarmonicsTextureSize)).rg;
- vec2 sampledSH01B = texture(sphericalHarmonicsTextureB, getDataUV(2, 0, sphericalHarmonicsTextureSize)).rg;
- vec2 sampledSH23B = texture(sphericalHarmonicsTextureB, getDataUV(2, 1, sphericalHarmonicsTextureSize)).rg;
- sh1 = vec3(sampledSH01R.rg, sampledSH23R.r);
- sh2 = vec3(sampledSH01G.rg, sampledSH23G.r);
- sh3 = vec3(sampledSH01B.rg, sampledSH23B.r);
- }
- `;
- // Sample spherical harmonics textures with 2 degrees worth of data for 1st degree calculations, and store in sh1, sh2, and sh3
- } else if (maxSphericalHarmonicsDegree === 2) {
- vertexShaderSource += `
- vec4 sampledSH0123;
- vec4 sampledSH4567;
- vec4 sampledSH891011;
- vec4 sampledSH0123R;
- vec4 sampledSH0123G;
- vec4 sampledSH0123B;
- if (sphericalHarmonicsMultiTextureMode == 0) {
- sampledSH0123 = texture(sphericalHarmonicsTexture, getDataUV(6, 0, sphericalHarmonicsTextureSize));
- sampledSH4567 = texture(sphericalHarmonicsTexture, getDataUV(6, 1, sphericalHarmonicsTextureSize));
- sampledSH891011 = texture(sphericalHarmonicsTexture, getDataUV(6, 2, sphericalHarmonicsTextureSize));
- sh1 = sampledSH0123.rgb;
- sh2 = vec3(sampledSH0123.a, sampledSH4567.rg);
- sh3 = vec3(sampledSH4567.ba, sampledSH891011.r);
- } else {
- sampledSH0123R = texture(sphericalHarmonicsTextureR, getDataUV(2, 0, sphericalHarmonicsTextureSize));
- sampledSH0123G = texture(sphericalHarmonicsTextureG, getDataUV(2, 0, sphericalHarmonicsTextureSize));
- sampledSH0123B = texture(sphericalHarmonicsTextureB, getDataUV(2, 0, sphericalHarmonicsTextureSize));
- sh1 = vec3(sampledSH0123R.rgb);
- sh2 = vec3(sampledSH0123G.rgb);
- sh3 = vec3(sampledSH0123B.rgb);
- }
- `;
- }
- // Perform 1st degree spherical harmonics calculations
- vertexShaderSource += `
- if (sphericalHarmonics8BitMode == 1) {
- sh1 = sh1 * sh8BitCompressionRangeForScene + vec8BitSHShift;
- sh2 = sh2 * sh8BitCompressionRangeForScene + vec8BitSHShift;
- sh3 = sh3 * sh8BitCompressionRangeForScene + vec8BitSHShift;
- }
- float x = worldViewDir.x;
- float y = worldViewDir.y;
- float z = worldViewDir.z;
- vColor.rgb += SH_C1 * (-sh1 * y + sh2 * z - sh3 * x);
- `;
- // Proceed to sampling and rendering 2nd degree spherical harmonics
- if (maxSphericalHarmonicsDegree >= 2) {
- vertexShaderSource += `
- if (sphericalHarmonicsDegree >= 2) {
- float xx = x * x;
- float yy = y * y;
- float zz = z * z;
- float xy = x * y;
- float yz = y * z;
- float xz = x * z;
- `;
- // Sample spherical harmonics textures with 2 degrees worth of data for 2nd degree calculations,
- // and store in sh4, sh5, sh6, sh7, and sh8
- if (maxSphericalHarmonicsDegree === 2) {
- vertexShaderSource += `
- if (sphericalHarmonicsMultiTextureMode == 0) {
- vec4 sampledSH12131415 = texture(sphericalHarmonicsTexture, getDataUV(6, 3, sphericalHarmonicsTextureSize));
- vec4 sampledSH16171819 = texture(sphericalHarmonicsTexture, getDataUV(6, 4, sphericalHarmonicsTextureSize));
- vec4 sampledSH20212223 = texture(sphericalHarmonicsTexture, getDataUV(6, 5, sphericalHarmonicsTextureSize));
- sh4 = sampledSH891011.gba;
- sh5 = sampledSH12131415.rgb;
- sh6 = vec3(sampledSH12131415.a, sampledSH16171819.rg);
- sh7 = vec3(sampledSH16171819.ba, sampledSH20212223.r);
- sh8 = sampledSH20212223.gba;
- } else {
- vec4 sampledSH4567R = texture(sphericalHarmonicsTextureR, getDataUV(2, 1, sphericalHarmonicsTextureSize));
- vec4 sampledSH4567G = texture(sphericalHarmonicsTextureG, getDataUV(2, 1, sphericalHarmonicsTextureSize));
- vec4 sampledSH4567B = texture(sphericalHarmonicsTextureB, getDataUV(2, 1, sphericalHarmonicsTextureSize));
- sh4 = vec3(sampledSH0123R.a, sampledSH4567R.rg);
- sh5 = vec3(sampledSH4567R.ba, sampledSH0123G.a);
- sh6 = vec3(sampledSH4567G.rgb);
- sh7 = vec3(sampledSH4567G.a, sampledSH0123B.a, sampledSH4567B.r);
- sh8 = vec3(sampledSH4567B.gba);
- }
- `;
- }
- // Perform 2nd degree spherical harmonics calculations
- vertexShaderSource += `
- if (sphericalHarmonics8BitMode == 1) {
- sh4 = sh4 * sh8BitCompressionRangeForScene + vec8BitSHShift;
- sh5 = sh5 * sh8BitCompressionRangeForScene + vec8BitSHShift;
- sh6 = sh6 * sh8BitCompressionRangeForScene + vec8BitSHShift;
- sh7 = sh7 * sh8BitCompressionRangeForScene + vec8BitSHShift;
- sh8 = sh8 * sh8BitCompressionRangeForScene + vec8BitSHShift;
- }
- vColor.rgb +=
- (SH_C2[0] * xy) * sh4 +
- (SH_C2[1] * yz) * sh5 +
- (SH_C2[2] * (2.0 * zz - xx - yy)) * sh6 +
- (SH_C2[3] * xz) * sh7 +
- (SH_C2[4] * (xx - yy)) * sh8;
- }
- `;
- }
- vertexShaderSource += `
- vColor.rgb = clamp(vColor.rgb, vec3(0.), vec3(1.));
- }
- `;
- }
- return vertexShaderSource;
- }
- static getVertexShaderFadeIn() {
- return `
- if (fadeInComplete == 0) {
- float opacityAdjust = 1.0;
- float centerDist = length(splatCenter - sceneCenter);
- float renderTime = max(currentTime - firstRenderTime, 0.0);
- float fadeDistance = 0.75;
- float distanceLoadFadeInFactor = step(visibleRegionFadeStartRadius, centerDist);
- distanceLoadFadeInFactor = (1.0 - distanceLoadFadeInFactor) +
- (1.0 - clamp((centerDist - visibleRegionFadeStartRadius) / fadeDistance, 0.0, 1.0)) *
- distanceLoadFadeInFactor;
- opacityAdjust *= distanceLoadFadeInFactor;
- vColor.a *= opacityAdjust;
- }
- `;
- }
- static getUniforms(dynamicMode = false, enableOptionalEffects = false, maxSphericalHarmonicsDegree = 0,
- splatScale = 1.0, pointCloudModeEnabled = false) {
- const uniforms = {
- 'sceneCenter': {
- 'type': 'v3',
- 'value': new THREE.Vector3()
- },
- 'fadeInComplete': {
- 'type': 'i',
- 'value': 0
- },
- 'orthographicMode': {
- 'type': 'i',
- 'value': 0
- },
- 'visibleRegionFadeStartRadius': {
- 'type': 'f',
- 'value': 0.0
- },
- 'visibleRegionRadius': {
- 'type': 'f',
- 'value': 0.0
- },
- 'currentTime': {
- 'type': 'f',
- 'value': 0.0
- },
- 'firstRenderTime': {
- 'type': 'f',
- 'value': 0.0
- },
- 'centersColorsTexture': {
- 'type': 't',
- 'value': null
- },
- 'sphericalHarmonicsTexture': {
- 'type': 't',
- 'value': null
- },
- 'sphericalHarmonicsTextureR': {
- 'type': 't',
- 'value': null
- },
- 'sphericalHarmonicsTextureG': {
- 'type': 't',
- 'value': null
- },
- 'sphericalHarmonicsTextureB': {
- 'type': 't',
- 'value': null
- },
- 'sphericalHarmonics8BitCompressionRangeMin': {
- 'type': 'f',
- 'value': []
- },
- 'sphericalHarmonics8BitCompressionRangeMax': {
- 'type': 'f',
- 'value': []
- },
- 'focal': {
- 'type': 'v2',
- 'value': new THREE.Vector2()
- },
- 'orthoZoom': {
- 'type': 'f',
- 'value': 1.0
- },
- 'inverseFocalAdjustment': {
- 'type': 'f',
- 'value': 1.0
- },
- 'viewport': {
- 'type': 'v2',
- 'value': new THREE.Vector2()
- },
- 'basisViewport': {
- 'type': 'v2',
- 'value': new THREE.Vector2()
- },
- 'debugColor': {
- 'type': 'v3',
- 'value': new THREE.Color()
- },
- 'centersColorsTextureSize': {
- 'type': 'v2',
- 'value': new THREE.Vector2(1024, 1024)
- },
- 'sphericalHarmonicsDegree': {
- 'type': 'i',
- 'value': maxSphericalHarmonicsDegree
- },
- 'sphericalHarmonicsTextureSize': {
- 'type': 'v2',
- 'value': new THREE.Vector2(1024, 1024)
- },
- 'sphericalHarmonics8BitMode': {
- 'type': 'i',
- 'value': 0
- },
- 'sphericalHarmonicsMultiTextureMode': {
- 'type': 'i',
- 'value': 0
- },
- 'splatScale': {
- 'type': 'f',
- 'value': splatScale
- },
- 'pointCloudModeEnabled': {
- 'type': 'i',
- 'value': pointCloudModeEnabled ? 1 : 0
- },
- 'sceneIndexesTexture': {
- 'type': 't',
- 'value': null
- },
- 'sceneIndexesTextureSize': {
- 'type': 'v2',
- 'value': new THREE.Vector2(1024, 1024)
- },
- 'sceneCount': {
- 'type': 'i',
- 'value': 1
- }
- };
- for (let i = 0; i < Constants.MaxScenes; i++) {
- uniforms.sphericalHarmonics8BitCompressionRangeMin.value.push(-Constants.SphericalHarmonics8BitCompressionRange / 2.0);
- uniforms.sphericalHarmonics8BitCompressionRangeMax.value.push(Constants.SphericalHarmonics8BitCompressionRange / 2.0);
- }
- if (enableOptionalEffects) {
- const sceneOpacity = [];
- for (let i = 0; i < Constants.MaxScenes; i++) {
- sceneOpacity.push(1.0);
- }
- uniforms['sceneOpacity'] ={
- 'type': 'f',
- 'value': sceneOpacity
- };
- const sceneVisibility = [];
- for (let i = 0; i < Constants.MaxScenes; i++) {
- sceneVisibility.push(1);
- }
- uniforms['sceneVisibility'] ={
- 'type': 'i',
- 'value': sceneVisibility
- };
- }
- if (dynamicMode) {
- const transformMatrices = [];
- for (let i = 0; i < Constants.MaxScenes; i++) {
- transformMatrices.push(new THREE.Matrix4());
- }
- uniforms['transforms'] = {
- 'type': 'mat4',
- 'value': transformMatrices
- };
- }
- return uniforms;
- }
- }
- class SplatMaterial3D {
- /**
- * Build the Three.js material that is used to render the splats.
- * @param {number} dynamicMode If true, it means the scene geometry represented by this splat mesh is not stationary or
- * that the splat count might change
- * @param {boolean} enableOptionalEffects When true, allows for usage of extra properties and attributes in the shader for effects
- * such as opacity adjustment. Default is false for performance reasons.
- * @param {boolean} antialiased If true, calculate compensation factor to deal with gaussians being rendered at a significantly
- * different resolution than that of their training
- * @param {number} maxScreenSpaceSplatSize The maximum clip space splat size
- * @param {number} splatScale Value by which all splats are scaled in screen-space (default is 1.0)
- * @param {number} pointCloudModeEnabled Render all splats as screen-space circles
- * @param {number} maxSphericalHarmonicsDegree Degree of spherical harmonics to utilize in rendering splats
- * @return {THREE.ShaderMaterial}
- */
- static build(dynamicMode = false, enableOptionalEffects = false, antialiased = false,
- maxScreenSpaceSplatSize = 2048, splatScale = 1.0, pointCloudModeEnabled = false, maxSphericalHarmonicsDegree = 0) {
- const customVertexVars = `
- uniform vec2 covariancesTextureSize;
- uniform highp sampler2D covariancesTexture;
- uniform highp usampler2D covariancesTextureHalfFloat;
- uniform int covariancesAreHalfFloat;
- void fromCovarianceHalfFloatV4(uvec4 val, out vec4 first, out vec4 second) {
- vec2 r = unpackHalf2x16(val.r);
- vec2 g = unpackHalf2x16(val.g);
- vec2 b = unpackHalf2x16(val.b);
- first = vec4(r.x, r.y, g.x, g.y);
- second = vec4(b.x, b.y, 0.0, 0.0);
- }
- `;
- let vertexShaderSource = SplatMaterial.buildVertexShaderBase(dynamicMode, enableOptionalEffects,
- maxSphericalHarmonicsDegree, customVertexVars);
- vertexShaderSource += SplatMaterial3D.buildVertexShaderProjection(antialiased, enableOptionalEffects, maxScreenSpaceSplatSize);
- const fragmentShaderSource = SplatMaterial3D.buildFragmentShader();
- const uniforms = SplatMaterial.getUniforms(dynamicMode, enableOptionalEffects,
- maxSphericalHarmonicsDegree, splatScale, pointCloudModeEnabled);
- uniforms['covariancesTextureSize'] = {
- 'type': 'v2',
- 'value': new THREE.Vector2(1024, 1024)
- };
- uniforms['covariancesTexture'] = {
- 'type': 't',
- 'value': null
- };
- uniforms['covariancesTextureHalfFloat'] = {
- 'type': 't',
- 'value': null
- };
- uniforms['covariancesAreHalfFloat'] = {
- 'type': 'i',
- 'value': 0
- };
- const material = new THREE.ShaderMaterial({
- uniforms: uniforms,
- vertexShader: vertexShaderSource,
- fragmentShader: fragmentShaderSource,
- transparent: true,
- alphaTest: 1.0,
- blending: THREE.NormalBlending,
- depthTest: true,
- depthWrite: false,
- side: THREE.DoubleSide
- });
- return material;
- }
- static buildVertexShaderProjection(antialiased, enableOptionalEffects, maxScreenSpaceSplatSize) {
- let vertexShaderSource = `
- vec4 sampledCovarianceA;
- vec4 sampledCovarianceB;
- vec3 cov3D_M11_M12_M13;
- vec3 cov3D_M22_M23_M33;
- if (covariancesAreHalfFloat == 0) {
- sampledCovarianceA = texture(covariancesTexture, getDataUVF(nearestEvenIndex, 1.5, oddOffset,
- covariancesTextureSize));
- sampledCovarianceB = texture(covariancesTexture, getDataUVF(nearestEvenIndex, 1.5, oddOffset + uint(1),
- covariancesTextureSize));
- cov3D_M11_M12_M13 = vec3(sampledCovarianceA.rgb) * (1.0 - fOddOffset) +
- vec3(sampledCovarianceA.ba, sampledCovarianceB.r) * fOddOffset;
- cov3D_M22_M23_M33 = vec3(sampledCovarianceA.a, sampledCovarianceB.rg) * (1.0 - fOddOffset) +
- vec3(sampledCovarianceB.gba) * fOddOffset;
- } else {
- uvec4 sampledCovarianceU = texture(covariancesTextureHalfFloat, getDataUV(1, 0, covariancesTextureSize));
- fromCovarianceHalfFloatV4(sampledCovarianceU, sampledCovarianceA, sampledCovarianceB);
- cov3D_M11_M12_M13 = sampledCovarianceA.rgb;
- cov3D_M22_M23_M33 = vec3(sampledCovarianceA.a, sampledCovarianceB.rg);
- }
-
- // Construct the 3D covariance matrix
- mat3 Vrk = mat3(
- cov3D_M11_M12_M13.x, cov3D_M11_M12_M13.y, cov3D_M11_M12_M13.z,
- cov3D_M11_M12_M13.y, cov3D_M22_M23_M33.x, cov3D_M22_M23_M33.y,
- cov3D_M11_M12_M13.z, cov3D_M22_M23_M33.y, cov3D_M22_M23_M33.z
- );
- mat3 J;
- if (orthographicMode == 1) {
- // Since the projection is linear, we don't need an approximation
- J = transpose(mat3(orthoZoom, 0.0, 0.0,
- 0.0, orthoZoom, 0.0,
- 0.0, 0.0, 0.0));
- } else {
- // Construct the Jacobian of the affine approximation of the projection matrix. It will be used to transform the
- // 3D covariance matrix instead of using the actual projection matrix because that transformation would
- // require a non-linear component (perspective division) which would yield a non-gaussian result.
- float s = 1.0 / (viewCenter.z * viewCenter.z);
- J = mat3(
- focal.x / viewCenter.z, 0., -(focal.x * viewCenter.x) * s,
- 0., focal.y / viewCenter.z, -(focal.y * viewCenter.y) * s,
- 0., 0., 0.
- );
- }
- // Concatenate the projection approximation with the model-view transformation
- mat3 W = transpose(mat3(transformModelViewMatrix));
- mat3 T = W * J;
- // Transform the 3D covariance matrix (Vrk) to compute the 2D covariance matrix
- mat3 cov2Dm = transpose(T) * Vrk * T;
- `;
- if (antialiased) {
- vertexShaderSource += `
- // (mip-slpatting:https://niujinshuchong.github.io/mip-splatting/)
- // compute the coef of alpha based on the detemintant
- // float detOrig = cov2Dm[0][0] * cov2Dm[1][1] - cov2Dm[0][1] * cov2Dm[0][1];
- // cov2Dm[0][0] += 0.3;
- // cov2Dm[1][1] += 0.3;
- // float detBlur = cov2Dm[0][0] * cov2Dm[1][1] - cov2Dm[0][1] * cov2Dm[0][1];
- // vColor.a *= sqrt(max(detOrig / detBlur, 0.0));
- // if (vColor.a < minAlpha) return;
- float kernel_size_2D_mip_filter = 0.1;
- float det_0 = max(1e-6, cov2Dm[0][0] * cov2Dm[1][1] - cov2Dm[0][1] * cov2Dm[0][1]);
- cov2Dm[0][0] += kernel_size_2D_mip_filter;
- cov2Dm[1][1] += kernel_size_2D_mip_filter;
- float det_1 = max(1e-6, cov2Dm[0][0] * cov2Dm[1][1] - cov2Dm[0][1] * cov2Dm[0][1]);
- float coef = sqrt(det_0 / (det_1+1e-6) + 1e-6);
- if (det_0 <= 1e-6 || det_1 <= 1e-6){
- coef = 0.0f;
- }
- vColor.a *= coef;
- if (vColor.a < minAlpha) return;
- `;
- } else {
- vertexShaderSource += `
- cov2Dm[0][0] += 0.3;
- cov2Dm[1][1] += 0.3;
- `;
- }
- vertexShaderSource += `
- // We are interested in the upper-left 2x2 portion of the projected 3D covariance matrix because
- // we only care about the X and Y values. We want the X-diagonal, cov2Dm[0][0],
- // the Y-diagonal, cov2Dm[1][1], and the correlation between the two cov2Dm[0][1]. We don't
- // need cov2Dm[1][0] because it is a symetric matrix.
- vec3 cov2Dv = vec3(cov2Dm[0][0], cov2Dm[0][1], cov2Dm[1][1]);
- // We now need to solve for the eigen-values and eigen vectors of the 2D covariance matrix
- // so that we can determine the 2D basis for the splat. This is done using the method described
- // here: https://people.math.harvard.edu/~knill/teaching/math21b2004/exhibits/2dmatrices/index.html
- // After calculating the eigen-values and eigen-vectors, we calculate the basis for rendering the splat
- // by normalizing the eigen-vectors and then multiplying them by (sqrt(8) * sqrt(eigen-value)), which is
- // equal to scaling them by sqrt(8) standard deviations.
- //
- // This is a different approach than in the original work at INRIA. In that work they compute the
- // max extents of the projected splat in screen space to form a screen-space aligned bounding rectangle
- // which forms the geometry that is actually rasterized. The dimensions of that bounding box are 3.0
- // times the square root of the maximum eigen-value, or 3 standard deviations. They then use the inverse
- // 2D covariance matrix (called 'conic') in the CUDA rendering thread to determine fragment opacity by
- // calculating the full gaussian: exp(-0.5 * (X - mean) * conic * (X - mean)) * splat opacity
- float a = cov2Dv.x;
- float d = cov2Dv.z;
- float b = cov2Dv.y;
- float D = a * d - b * b;
- float trace = a + d;
- float traceOver2 = 0.5 * trace;
- float term2 = sqrt(max(0.1f, traceOver2 * traceOver2 - D));
- float eigenValue1 = traceOver2 + term2;
- float eigenValue2 = traceOver2 - term2;
- if (pointCloudModeEnabled == 1) {
- eigenValue1 = eigenValue2 = 0.2;
- }
- if (eigenValue2 <= 0.0) return;
- vec2 eigenVector1 = normalize(vec2(b, eigenValue1 - a));
- // since the eigen vectors are orthogonal, we derive the second one from the first
- vec2 eigenVector2 = vec2(eigenVector1.y, -eigenVector1.x);
- // We use sqrt(8) standard deviations instead of 3 to eliminate more of the splat with a very low opacity.
- vec2 basisVector1 = eigenVector1 * splatScale * min(sqrt8 * sqrt(eigenValue1), ${parseInt(maxScreenSpaceSplatSize)}.0);
- vec2 basisVector2 = eigenVector2 * splatScale * min(sqrt8 * sqrt(eigenValue2), ${parseInt(maxScreenSpaceSplatSize)}.0);
- `;
- if (enableOptionalEffects) {
- vertexShaderSource += `
- vColor.a *= splatOpacityFromScene;
- `;
- }
- vertexShaderSource += `
- vec2 ndcOffset = vec2(vPosition.x * basisVector1 + vPosition.y * basisVector2) *
- basisViewport * 2.0 * inverseFocalAdjustment;
- vec4 quadPos = vec4(ndcCenter.xy + ndcOffset, ndcCenter.z, 1.0);
- gl_Position = quadPos;
- // Scale the position data we send to the fragment shader
- vPosition *= sqrt8;
- `;
- vertexShaderSource += SplatMaterial.getVertexShaderFadeIn();
- vertexShaderSource += `}`;
- return vertexShaderSource;
- }
- static buildFragmentShader() {
- let fragmentShaderSource = `
- precision highp float;
- #include <common>
-
- uniform vec3 debugColor;
- varying vec4 vColor;
- varying vec2 vUv;
- varying vec2 vPosition;
- `;
- fragmentShaderSource += `
- void main () {
- // Compute the positional squared distance from the center of the splat to the current fragment.
- float A = dot(vPosition, vPosition);
- // Since the positional data in vPosition has been scaled by sqrt(8), the squared result will be
- // scaled by a factor of 8. If the squared result is larger than 8, it means it is outside the ellipse
- // defined by the rectangle formed by vPosition. It also means it's farther
- // away than sqrt(8) standard deviations from the mean.
- if (A > 8.0) discard;
- vec3 color = vColor.rgb;
- // Since the rendered splat is scaled by sqrt(8), the inverse covariance matrix that is part of
- // the gaussian formula becomes the identity matrix. We're then left with (X - mean) * (X - mean),
- // and since 'mean' is zero, we have X * X, which is the same as A:
- float opacity = exp(-0.5 * A) * vColor.a;
- gl_FragColor = vec4(color.rgb, opacity);
- }
- `;
- return fragmentShaderSource;
- }
- }
- class SplatMaterial2D {
- /**
- * Build the Three.js material that is used to render the splats.
- * @param {number} dynamicMode If true, it means the scene geometry represented by this splat mesh is not stationary or
- * that the splat count might change
- * @param {boolean} enableOptionalEffects When true, allows for usage of extra properties and attributes in the shader for effects
- * such as opacity adjustment. Default is false for performance reasons.
- * @param {number} splatScale Value by which all splats are scaled in screen-space (default is 1.0)
- * @param {number} pointCloudModeEnabled Render all splats as screen-space circles
- * @param {number} maxSphericalHarmonicsDegree Degree of spherical harmonics to utilize in rendering splats
- * @return {THREE.ShaderMaterial}
- */
- static build(dynamicMode = false, enableOptionalEffects = false, splatScale = 1.0,
- pointCloudModeEnabled = false, maxSphericalHarmonicsDegree = 0) {
- const customVertexVars = `
- uniform vec2 scaleRotationsTextureSize;
- uniform highp sampler2D scaleRotationsTexture;
- varying mat3 vT;
- varying vec2 vQuadCenter;
- varying vec2 vFragCoord;
- `;
- let vertexShaderSource = SplatMaterial.buildVertexShaderBase(dynamicMode, enableOptionalEffects,
- maxSphericalHarmonicsDegree, customVertexVars);
- vertexShaderSource += SplatMaterial2D.buildVertexShaderProjection();
- const fragmentShaderSource = SplatMaterial2D.buildFragmentShader();
- const uniforms = SplatMaterial.getUniforms(dynamicMode, enableOptionalEffects,
- maxSphericalHarmonicsDegree, splatScale, pointCloudModeEnabled);
- uniforms['scaleRotationsTexture'] = {
- 'type': 't',
- 'value': null
- };
- uniforms['scaleRotationsTextureSize'] = {
- 'type': 'v2',
- 'value': new THREE.Vector2(1024, 1024)
- };
- const material = new THREE.ShaderMaterial({
- uniforms: uniforms,
- vertexShader: vertexShaderSource,
- fragmentShader: fragmentShaderSource,
- transparent: true,
- alphaTest: 1.0,
- blending: THREE.NormalBlending,
- depthTest: true,
- depthWrite: false,
- side: THREE.DoubleSide
- });
- return material;
- }
- static buildVertexShaderProjection() {
- // Original CUDA code for calculating splat-to-screen transformation, for reference
- /*
- glm::mat3 R = quat_to_rotmat(rot);
- glm::mat3 S = scale_to_mat(scale, mod);
- glm::mat3 L = R * S;
- // center of Gaussians in the camera coordinate
- glm::mat3x4 splat2world = glm::mat3x4(
- glm::vec4(L[0], 0.0),
- glm::vec4(L[1], 0.0),
- glm::vec4(p_orig.x, p_orig.y, p_orig.z, 1)
- );
- glm::mat4 world2ndc = glm::mat4(
- projmatrix[0], projmatrix[4], projmatrix[8], projmatrix[12],
- projmatrix[1], projmatrix[5], projmatrix[9], projmatrix[13],
- projmatrix[2], projmatrix[6], projmatrix[10], projmatrix[14],
- projmatrix[3], projmatrix[7], projmatrix[11], projmatrix[15]
- );
- glm::mat3x4 ndc2pix = glm::mat3x4(
- glm::vec4(float(W) / 2.0, 0.0, 0.0, float(W-1) / 2.0),
- glm::vec4(0.0, float(H) / 2.0, 0.0, float(H-1) / 2.0),
- glm::vec4(0.0, 0.0, 0.0, 1.0)
- );
- T = glm::transpose(splat2world) * world2ndc * ndc2pix;
- normal = transformVec4x3({L[2].x, L[2].y, L[2].z}, viewmatrix);
- */
- // Compute a 2D-to-2D mapping matrix from a tangent plane into a image plane
- // given a 2D gaussian parameters. T = WH (from the paper: https://arxiv.org/pdf/2403.17888)
- let vertexShaderSource = `
- vec4 scaleRotationA = texture(scaleRotationsTexture, getDataUVF(nearestEvenIndex, 1.5,
- oddOffset, scaleRotationsTextureSize));
- vec4 scaleRotationB = texture(scaleRotationsTexture, getDataUVF(nearestEvenIndex, 1.5,
- oddOffset + uint(1), scaleRotationsTextureSize));
- vec3 scaleRotation123 = vec3(scaleRotationA.rgb) * (1.0 - fOddOffset) +
- vec3(scaleRotationA.ba, scaleRotationB.r) * fOddOffset;
- vec3 scaleRotation456 = vec3(scaleRotationA.a, scaleRotationB.rg) * (1.0 - fOddOffset) +
- vec3(scaleRotationB.gba) * fOddOffset;
- float missingW = sqrt(1.0 - scaleRotation456.x * scaleRotation456.x - scaleRotation456.y *
- scaleRotation456.y - scaleRotation456.z * scaleRotation456.z);
- mat3 R = quaternionToRotationMatrix(scaleRotation456.r, scaleRotation456.g, scaleRotation456.b, missingW);
- mat3 S = mat3(scaleRotation123.r, 0.0, 0.0,
- 0.0, scaleRotation123.g, 0.0,
- 0.0, 0.0, scaleRotation123.b);
-
- mat3 L = R * S;
- mat3x4 splat2World = mat3x4(vec4(L[0], 0.0),
- vec4(L[1], 0.0),
- vec4(splatCenter.x, splatCenter.y, splatCenter.z, 1.0));
- mat4 world2ndc = transpose(projectionMatrix * transformModelViewMatrix);
- mat3x4 ndc2pix = mat3x4(vec4(viewport.x / 2.0, 0.0, 0.0, (viewport.x - 1.0) / 2.0),
- vec4(0.0, viewport.y / 2.0, 0.0, (viewport.y - 1.0) / 2.0),
- vec4(0.0, 0.0, 0.0, 1.0));
- mat3 T = transpose(splat2World) * world2ndc * ndc2pix;
- vec3 normal = vec3(viewMatrix * vec4(L[0][2], L[1][2], L[2][2], 0.0));
- `;
- // Original CUDA code for projection to 2D, for reference
- /*
- float3 T0 = {T[0][0], T[0][1], T[0][2]};
- float3 T1 = {T[1][0], T[1][1], T[1][2]};
- float3 T3 = {T[2][0], T[2][1], T[2][2]};
- // Compute AABB
- float3 temp_point = {1.0f, 1.0f, -1.0f};
- float distance = sumf3(T3 * T3 * temp_point);
- float3 f = (1 / distance) * temp_point;
- if (distance == 0.0) return false;
- point_image = {
- sumf3(f * T0 * T3),
- sumf3(f * T1 * T3)
- };
- float2 temp = {
- sumf3(f * T0 * T0),
- sumf3(f * T1 * T1)
- };
- float2 half_extend = point_image * point_image - temp;
- extent = sqrtf2(maxf2(1e-4, half_extend));
- return true;
- */
- // Computing the bounding box of the 2D Gaussian and its center
- // The center of the bounding box is used to create a low pass filter.
- // This code is based off the reference implementation and creates an AABB aligned
- // with the screen for the quad to be rendered.
- const referenceQuadGeneration = `
- vec3 T0 = vec3(T[0][0], T[0][1], T[0][2]);
- vec3 T1 = vec3(T[1][0], T[1][1], T[1][2]);
- vec3 T3 = vec3(T[2][0], T[2][1], T[2][2]);
- vec3 tempPoint = vec3(1.0, 1.0, -1.0);
- float distance = (T3.x * T3.x * tempPoint.x) + (T3.y * T3.y * tempPoint.y) + (T3.z * T3.z * tempPoint.z);
- vec3 f = (1.0 / distance) * tempPoint;
- if (abs(distance) < 0.00001) return;
- float pointImageX = (T0.x * T3.x * f.x) + (T0.y * T3.y * f.y) + (T0.z * T3.z * f.z);
- float pointImageY = (T1.x * T3.x * f.x) + (T1.y * T3.y * f.y) + (T1.z * T3.z * f.z);
- vec2 pointImage = vec2(pointImageX, pointImageY);
- float tempX = (T0.x * T0.x * f.x) + (T0.y * T0.y * f.y) + (T0.z * T0.z * f.z);
- float tempY = (T1.x * T1.x * f.x) + (T1.y * T1.y * f.y) + (T1.z * T1.z * f.z);
- vec2 temp = vec2(tempX, tempY);
- vec2 halfExtend = pointImage * pointImage - temp;
- vec2 extent = sqrt(max(vec2(0.0001), halfExtend));
- float radius = max(extent.x, extent.y);
- vec2 ndcOffset = ((position.xy * radius * 3.0) * basisViewport * 2.0);
- vec4 quadPos = vec4(ndcCenter.xy + ndcOffset, ndcCenter.z, 1.0);
- gl_Position = quadPos;
- vT = T;
- vQuadCenter = pointImage;
- vFragCoord = (quadPos.xy * 0.5 + 0.5) * viewport;
- `;
- const useRefImplementation = false;
- if (useRefImplementation) {
- vertexShaderSource += referenceQuadGeneration;
- } else {
- // Create a quad that is aligned with the eigen vectors of the projected gaussian for rendering.
- // This is a different approach than the reference implementation, similar to how the rendering of
- // 3D gaussians in this viewer differs from the reference implementation. If the quad is too small
- // (smaller than a pixel), then revert to the reference implementation.
- vertexShaderSource += `
- mat4 splat2World4 = mat4(vec4(L[0], 0.0),
- vec4(L[1], 0.0),
- vec4(L[2], 0.0),
- vec4(splatCenter.x, splatCenter.y, splatCenter.z, 1.0));
- mat4 Tt = transpose(transpose(splat2World4) * world2ndc);
- vec4 tempPoint1 = Tt * vec4(1.0, 0.0, 0.0, 1.0);
- tempPoint1 /= tempPoint1.w;
- vec4 tempPoint2 = Tt * vec4(0.0, 1.0, 0.0, 1.0);
- tempPoint2 /= tempPoint2.w;
- vec4 center = Tt * vec4(0.0, 0.0, 0.0, 1.0);
- center /= center.w;
- vec2 basisVector1 = tempPoint1.xy - center.xy;
- vec2 basisVector2 = tempPoint2.xy - center.xy;
- vec2 basisVector1Screen = basisVector1 * 0.5 * viewport;
- vec2 basisVector2Screen = basisVector2 * 0.5 * viewport;
- const float minPix = 1.;
- if (length(basisVector1Screen) < minPix || length(basisVector2Screen) < minPix) {
- ${referenceQuadGeneration}
- } else {
- vec2 ndcOffset = vec2(position.x * basisVector1 + position.y * basisVector2) * 3.0 * inverseFocalAdjustment;
- vec4 quadPos = vec4(ndcCenter.xy + ndcOffset, ndcCenter.z, 1.0);
- gl_Position = quadPos;
- vT = T;
- vQuadCenter = center.xy;
- vFragCoord = (quadPos.xy * 0.5 + 0.5) * viewport;
- }
- `;
- }
- vertexShaderSource += SplatMaterial.getVertexShaderFadeIn();
- vertexShaderSource += `}`;
- return vertexShaderSource;
- }
- static buildFragmentShader() {
- // Original CUDA code for splat intersection, for reference
- /*
- const float2 xy = collected_xy[j];
- const float3 Tu = collected_Tu[j];
- const float3 Tv = collected_Tv[j];
- const float3 Tw = collected_Tw[j];
- float3 k = pix.x * Tw - Tu;
- float3 l = pix.y * Tw - Tv;
- float3 p = cross(k, l);
- if (p.z == 0.0) continue;
- float2 s = {p.x / p.z, p.y / p.z};
- float rho3d = (s.x * s.x + s.y * s.y);
- float2 d = {xy.x - pixf.x, xy.y - pixf.y};
- float rho2d = FilterInvSquare * (d.x * d.x + d.y * d.y);
- // compute intersection and depth
- float rho = min(rho3d, rho2d);
- float depth = (rho3d <= rho2d) ? (s.x * Tw.x + s.y * Tw.y) + Tw.z : Tw.z;
- if (depth < near_n) continue;
- float4 nor_o = collected_normal_opacity[j];
- float normal[3] = {nor_o.x, nor_o.y, nor_o.z};
- float opa = nor_o.w;
- float power = -0.5f * rho;
- if (power > 0.0f)
- continue;
- // Eq. (2) from 3D Gaussian splatting paper.
- // Obtain alpha by multiplying with Gaussian opacity
- // and its exponential falloff from mean.
- // Avoid numerical instabilities (see paper appendix).
- float alpha = min(0.99f, opa * exp(power));
- if (alpha < 1.0f / 255.0f)
- continue;
- float test_T = T * (1 - alpha);
- if (test_T < 0.0001f)
- {
- done = true;
- continue;
- }
- float w = alpha * T;
- */
- let fragmentShaderSource = `
- precision highp float;
- #include <common>
- uniform vec3 debugColor;
- varying vec4 vColor;
- varying vec2 vUv;
- varying vec2 vPosition;
- varying mat3 vT;
- varying vec2 vQuadCenter;
- varying vec2 vFragCoord;
- void main () {
- const float FilterInvSquare = 2.0;
- const float near_n = 0.2;
- const float T = 1.0;
- vec2 xy = vQuadCenter;
- vec3 Tu = vT[0];
- vec3 Tv = vT[1];
- vec3 Tw = vT[2];
- vec3 k = vFragCoord.x * Tw - Tu;
- vec3 l = vFragCoord.y * Tw - Tv;
- vec3 p = cross(k, l);
- if (p.z == 0.0) discard;
- vec2 s = vec2(p.x / p.z, p.y / p.z);
- float rho3d = (s.x * s.x + s.y * s.y);
- vec2 d = vec2(xy.x - vFragCoord.x, xy.y - vFragCoord.y);
- float rho2d = FilterInvSquare * (d.x * d.x + d.y * d.y);
- // compute intersection and depth
- float rho = min(rho3d, rho2d);
- float depth = (rho3d <= rho2d) ? (s.x * Tw.x + s.y * Tw.y) + Tw.z : Tw.z;
- if (depth < near_n) discard;
- // vec4 nor_o = collected_normal_opacity[j];
- // float normal[3] = {nor_o.x, nor_o.y, nor_o.z};
- float opa = vColor.a;
- float power = -0.5f * rho;
- if (power > 0.0f) discard;
- // Eq. (2) from 3D Gaussian splatting paper.
- // Obtain alpha by multiplying with Gaussian opacity
- // and its exponential falloff from mean.
- // Avoid numerical instabilities (see paper appendix).
- float alpha = min(0.99f, opa * exp(power));
- if (alpha < 1.0f / 255.0f) discard;
- float test_T = T * (1.0 - alpha);
- if (test_T < 0.0001)discard;
- float w = alpha * T;
- gl_FragColor = vec4(vColor.rgb, w);
- }
- `;
- return fragmentShaderSource;
- }
- }
- class SplatGeometry {
- /**
- * Build the Three.js geometry that will be used to render the splats. The geometry is instanced and is made up of
- * vertices for a single quad as well as an attribute buffer for the splat indexes.
- * @param {number} maxSplatCount The maximum number of splats that the geometry will need to accomodate
- * @return {THREE.InstancedBufferGeometry}
- */
- static build(maxSplatCount) {
- const baseGeometry = new THREE.BufferGeometry();
- baseGeometry.setIndex([0, 1, 2, 0, 2, 3]);
- // Vertices for the instanced quad
- const positionsArray = new Float32Array(4 * 3);
- const positions = new THREE.BufferAttribute(positionsArray, 3);
- baseGeometry.setAttribute('position', positions);
- positions.setXYZ(0, -1.0, -1.0, 0.0);
- positions.setXYZ(1, -1.0, 1.0, 0.0);
- positions.setXYZ(2, 1.0, 1.0, 0.0);
- positions.setXYZ(3, 1.0, -1.0, 0.0);
- positions.needsUpdate = true;
- const geometry = new THREE.InstancedBufferGeometry().copy(baseGeometry);
- // Splat index buffer
- const splatIndexArray = new Uint32Array(maxSplatCount);
- const splatIndexes = new THREE.InstancedBufferAttribute(splatIndexArray, 1, false);
- splatIndexes.setUsage(THREE.DynamicDrawUsage);
- geometry.setAttribute('splatIndex', splatIndexes);
- geometry.instanceCount = 0;
- return geometry;
- }
- }
- /**
- * SplatScene: Descriptor for a single splat scene managed by an instance of SplatMesh.
- */
- class SplatScene extends THREE.Object3D {
- constructor(splatBuffer, position = new THREE.Vector3(), quaternion = new THREE.Quaternion(),
- scale = new THREE.Vector3(1, 1, 1), minimumAlpha = 1, opacity = 1.0, visible = true) {
- super();
- this.splatBuffer = splatBuffer;
- this.position.copy(position);
- this.quaternion.copy(quaternion);
- this.scale.copy(scale);
- this.transform = new THREE.Matrix4();
- this.minimumAlpha = minimumAlpha;
- this.opacity = opacity;
- this.visible = visible;
- }
- copyTransformData(otherScene) {
- this.position.copy(otherScene.position);
- this.quaternion.copy(otherScene.quaternion);
- this.scale.copy(otherScene.scale);
- this.transform.copy(otherScene.transform);
- }
- updateTransform(dynamicMode) {
- /* if (dynamicMode) {
- if (this.matrixWorldAutoUpdate) this.updateWorldMatrix(true, false);
- this.transform.copy(this.matrixWorld);
- } else { */
- if (this.matrixAutoUpdate) this.updateMatrix();
- this.transform.copy(this.matrix);
- //}
- }
- }
- class SplatTreeNode {
- static idGen = 0;
- constructor(min, max, depth, id) {
- this.min = new THREE.Vector3().copy(min);
- this.max = new THREE.Vector3().copy(max);
- this.boundingBox = new THREE.Box3(this.min, this.max);
- this.center = new THREE.Vector3().copy(this.max).sub(this.min).multiplyScalar(0.5).add(this.min);
- this.depth = depth;
- this.children = [];
- this.data = null;
- this.id = id || SplatTreeNode.idGen++;
- }
- }
- class SplatSubTree {
- constructor(maxDepth, maxCentersPerNode) {
- this.maxDepth = maxDepth;
- this.maxCentersPerNode = maxCentersPerNode;
- this.sceneDimensions = new THREE.Vector3();
- this.sceneMin = new THREE.Vector3();
- this.sceneMax = new THREE.Vector3();
- this.rootNode = null;
- this.nodesWithIndexes = [];
- this.splatMesh = null;
- }
- static convertWorkerSubTreeNode(workerSubTreeNode) {
- const minVector = new THREE.Vector3().fromArray(workerSubTreeNode.min);
- const maxVector = new THREE.Vector3().fromArray(workerSubTreeNode.max);
- const convertedNode = new SplatTreeNode(minVector, maxVector, workerSubTreeNode.depth, workerSubTreeNode.id);
- if (workerSubTreeNode.data.indexes) {
- convertedNode.data = {
- 'indexes': []
- };
- for (let index of workerSubTreeNode.data.indexes) {
- convertedNode.data.indexes.push(index);
- }
- }
- if (workerSubTreeNode.children) {
- for (let child of workerSubTreeNode.children) {
- convertedNode.children.push(SplatSubTree.convertWorkerSubTreeNode(child));
- }
- }
- return convertedNode;
- }
- static convertWorkerSubTree(workerSubTree, splatMesh) {
- const convertedSubTree = new SplatSubTree(workerSubTree.maxDepth, workerSubTree.maxCentersPerNode);
- convertedSubTree.sceneMin = new THREE.Vector3().fromArray(workerSubTree.sceneMin);
- convertedSubTree.sceneMax = new THREE.Vector3().fromArray(workerSubTree.sceneMax);
- convertedSubTree.splatMesh = splatMesh;
- convertedSubTree.rootNode = SplatSubTree.convertWorkerSubTreeNode(workerSubTree.rootNode);
- const visitLeavesFromNode = (node, visitFunc) => {
- if (node.children.length === 0) visitFunc(node);
- for (let child of node.children) {
- visitLeavesFromNode(child, visitFunc);
- }
- };
- convertedSubTree.nodesWithIndexes = [];
- visitLeavesFromNode(convertedSubTree.rootNode, (node) => {
- if (node.data && node.data.indexes && node.data.indexes.length > 0) {
- convertedSubTree.nodesWithIndexes.push(node);
- }
- });
- return convertedSubTree;
- }
- }
- function createSplatTreeWorker(self) {
- let WorkerSplatTreeNodeIDGen = 0;
- class WorkerBox3 {
- constructor(min, max) {
- this.min = [min[0], min[1], min[2]];
- this.max = [max[0], max[1], max[2]];
- }
- containsPoint(point) {
- return point[0] >= this.min[0] && point[0] <= this.max[0] &&
- point[1] >= this.min[1] && point[1] <= this.max[1] &&
- point[2] >= this.min[2] && point[2] <= this.max[2];
- }
- }
- class WorkerSplatSubTree {
- constructor(maxDepth, maxCentersPerNode) {
- this.maxDepth = maxDepth;
- this.maxCentersPerNode = maxCentersPerNode;
- this.sceneDimensions = [];
- this.sceneMin = [];
- this.sceneMax = [];
- this.rootNode = null;
- this.addedIndexes = {};
- this.nodesWithIndexes = [];
- this.splatMesh = null;
- this.disposed = false;
- }
- }
- class WorkerSplatTreeNode {
- constructor(min, max, depth, id) {
- this.min = [min[0], min[1], min[2]];
- this.max = [max[0], max[1], max[2]];
- this.center = [(max[0] - min[0]) * 0.5 + min[0],
- (max[1] - min[1]) * 0.5 + min[1],
- (max[2] - min[2]) * 0.5 + min[2]];
- this.depth = depth;
- this.children = [];
- this.data = null;
- this.id = id || WorkerSplatTreeNodeIDGen++;
- }
- }
- processSplatTreeNode = function(tree, node, indexToCenter, sceneCenters) {
- const splatCount = node.data.indexes.length;
- if (splatCount < tree.maxCentersPerNode || node.depth > tree.maxDepth) {
- const newIndexes = [];
- for (let i = 0; i < node.data.indexes.length; i++) {
- if (!tree.addedIndexes[node.data.indexes[i]]) {
- newIndexes.push(node.data.indexes[i]);
- tree.addedIndexes[node.data.indexes[i]] = true;
- }
- }
- node.data.indexes = newIndexes;
- node.data.indexes.sort((a, b) => {
- if (a > b) return 1;
- else return -1;
- });
- tree.nodesWithIndexes.push(node);
- return;
- }
- const nodeDimensions = [node.max[0] - node.min[0],
- node.max[1] - node.min[1],
- node.max[2] - node.min[2]];
- const halfDimensions = [nodeDimensions[0] * 0.5,
- nodeDimensions[1] * 0.5,
- nodeDimensions[2] * 0.5];
- const nodeCenter = [node.min[0] + halfDimensions[0],
- node.min[1] + halfDimensions[1],
- node.min[2] + halfDimensions[2]];
- const childrenBounds = [
- // top section, clockwise from upper-left (looking from above, +Y)
- new WorkerBox3([nodeCenter[0] - halfDimensions[0], nodeCenter[1], nodeCenter[2] - halfDimensions[2]],
- [nodeCenter[0], nodeCenter[1] + halfDimensions[1], nodeCenter[2]]),
- new WorkerBox3([nodeCenter[0], nodeCenter[1], nodeCenter[2] - halfDimensions[2]],
- [nodeCenter[0] + halfDimensions[0], nodeCenter[1] + halfDimensions[1], nodeCenter[2]]),
- new WorkerBox3([nodeCenter[0], nodeCenter[1], nodeCenter[2]],
- [nodeCenter[0] + halfDimensions[0], nodeCenter[1] + halfDimensions[1], nodeCenter[2] + halfDimensions[2]]),
- new WorkerBox3([nodeCenter[0] - halfDimensions[0], nodeCenter[1], nodeCenter[2]],
- [nodeCenter[0], nodeCenter[1] + halfDimensions[1], nodeCenter[2] + halfDimensions[2]]),
- // bottom section, clockwise from lower-left (looking from above, +Y)
- new WorkerBox3([nodeCenter[0] - halfDimensions[0], nodeCenter[1] - halfDimensions[1], nodeCenter[2] - halfDimensions[2]],
- [nodeCenter[0], nodeCenter[1], nodeCenter[2]]),
- new WorkerBox3([nodeCenter[0], nodeCenter[1] - halfDimensions[1], nodeCenter[2] - halfDimensions[2]],
- [nodeCenter[0] + halfDimensions[0], nodeCenter[1], nodeCenter[2]]),
- new WorkerBox3([nodeCenter[0], nodeCenter[1] - halfDimensions[1], nodeCenter[2]],
- [nodeCenter[0] + halfDimensions[0], nodeCenter[1], nodeCenter[2] + halfDimensions[2]]),
- new WorkerBox3([nodeCenter[0] - halfDimensions[0], nodeCenter[1] - halfDimensions[1], nodeCenter[2]],
- [nodeCenter[0], nodeCenter[1], nodeCenter[2] + halfDimensions[2]]),
- ];
- const splatCounts = [];
- const baseIndexes = [];
- for (let i = 0; i < childrenBounds.length; i++) {
- splatCounts[i] = 0;
- baseIndexes[i] = [];
- }
- const center = [0, 0, 0];
- for (let i = 0; i < splatCount; i++) {
- const splatGlobalIndex = node.data.indexes[i];
- const centerBase = indexToCenter[splatGlobalIndex];
- center[0] = sceneCenters[centerBase];
- center[1] = sceneCenters[centerBase + 1];
- center[2] = sceneCenters[centerBase + 2];
- for (let j = 0; j < childrenBounds.length; j++) {
- if (childrenBounds[j].containsPoint(center)) {
- splatCounts[j]++;
- baseIndexes[j].push(splatGlobalIndex);
- }
- }
- }
- for (let i = 0; i < childrenBounds.length; i++) {
- const childNode = new WorkerSplatTreeNode(childrenBounds[i].min, childrenBounds[i].max, node.depth + 1);
- childNode.data = {
- 'indexes': baseIndexes[i]
- };
- node.children.push(childNode);
- }
- node.data = {};
- for (let child of node.children) {
- processSplatTreeNode(tree, child, indexToCenter, sceneCenters);
- }
- return;
- };
- const buildSubTree = (sceneCenters, maxDepth, maxCentersPerNode) => {
- const sceneMin = [0, 0, 0];
- const sceneMax = [0, 0, 0];
- const indexes = [];
- const centerCount = Math.floor(sceneCenters.length / 4);
- for ( let i = 0; i < centerCount; i ++) {
- const base = i * 4;
- const x = sceneCenters[base];
- const y = sceneCenters[base + 1];
- const z = sceneCenters[base + 2];
- const index = Math.round(sceneCenters[base + 3]);
- if (i === 0 || x < sceneMin[0]) sceneMin[0] = x;
- if (i === 0 || x > sceneMax[0]) sceneMax[0] = x;
- if (i === 0 || y < sceneMin[1]) sceneMin[1] = y;
- if (i === 0 || y > sceneMax[1]) sceneMax[1] = y;
- if (i === 0 || z < sceneMin[2]) sceneMin[2] = z;
- if (i === 0 || z > sceneMax[2]) sceneMax[2] = z;
- indexes.push(index);
- }
- const subTree = new WorkerSplatSubTree(maxDepth, maxCentersPerNode);
- subTree.sceneMin = sceneMin;
- subTree.sceneMax = sceneMax;
- subTree.rootNode = new WorkerSplatTreeNode(subTree.sceneMin, subTree.sceneMax, 0);
- subTree.rootNode.data = {
- 'indexes': indexes
- };
- return subTree;
- };
- function createSplatTree(allCenters, maxDepth, maxCentersPerNode) {
- const indexToCenter = [];
- for (let sceneCenters of allCenters) {
- const centerCount = Math.floor(sceneCenters.length / 4);
- for ( let i = 0; i < centerCount; i ++) {
- const base = i * 4;
- const index = Math.round(sceneCenters[base + 3]);
- indexToCenter[index] = base;
- }
- }
- const subTrees = [];
- for (let sceneCenters of allCenters) {
- const subTree = buildSubTree(sceneCenters, maxDepth, maxCentersPerNode);
- subTrees.push(subTree);
- processSplatTreeNode(subTree, subTree.rootNode, indexToCenter, sceneCenters);
- }
- self.postMessage({
- 'subTrees': subTrees
- });
- }
- self.onmessage = (e) => {
- if (e.data.process) {
- createSplatTree(e.data.process.centers, e.data.process.maxDepth, e.data.process.maxCentersPerNode);
- }
- };
- }
- function workerProcessCenters(splatTreeWorker, centers, transferBuffers, maxDepth, maxCentersPerNode) {
- splatTreeWorker.postMessage({
- 'process': {
- 'centers': centers,
- 'maxDepth': maxDepth,
- 'maxCentersPerNode': maxCentersPerNode
- }
- }, transferBuffers);
- }
- function checkAndCreateWorker() {
- const splatTreeWorker = new Worker(
- URL.createObjectURL(
- new Blob(['(', createSplatTreeWorker.toString(), ')(self)'], {
- type: 'application/javascript',
- }),
- ),
- );
- return splatTreeWorker;
- }
- /**
- * SplatTree: Octree tailored to splat data from a SplatMesh instance
- */
- class SplatTree {
- constructor(maxDepth, maxCentersPerNode) {
- this.maxDepth = maxDepth;
- this.maxCentersPerNode = maxCentersPerNode;
- this.subTrees = [];
- this.splatMesh = null;
- }
- dispose() {
- this.diposeSplatTreeWorker();
- this.disposed = true;
- }
- diposeSplatTreeWorker() {
- if (this.splatTreeWorker) this.splatTreeWorker.terminate();
- this.splatTreeWorker = null;
- };
- /**
- * Construct this instance of SplatTree from an instance of SplatMesh.
- *
- * @param {SplatMesh} splatMesh The instance of SplatMesh from which to construct this splat tree.
- * @param {function} filterFunc Optional function to filter out unwanted splats.
- * @param {function} onIndexesUpload Function to be called when the upload of splat centers to the splat tree
- * builder worker starts and finishes.
- * @param {function} onSplatTreeConstruction Function to be called when the conversion of the local splat tree from
- * the format produced by the splat tree builder worker starts and ends.
- * @return {undefined}
- */
- processSplatMesh = function(splatMesh, filterFunc = () => true, onIndexesUpload, onSplatTreeConstruction) {
- if (!this.splatTreeWorker) this.splatTreeWorker = checkAndCreateWorker();
- this.splatMesh = splatMesh;
- this.subTrees = [];
- const center = new THREE.Vector3();
- const addCentersForScene = (splatOffset, splatCount) => {
- const sceneCenters = new Float32Array(splatCount * 4);
- let addedCount = 0;
- for (let i = 0; i < splatCount; i++) {
- const globalSplatIndex = i + splatOffset;
- if (filterFunc(globalSplatIndex)) {
- splatMesh.getSplatCenter(globalSplatIndex, center);
- const addBase = addedCount * 4;
- sceneCenters[addBase] = center.x;
- sceneCenters[addBase + 1] = center.y;
- sceneCenters[addBase + 2] = center.z;
- sceneCenters[addBase + 3] = globalSplatIndex;
- addedCount++;
- }
- }
- return sceneCenters;
- };
- return new Promise((resolve) => {
- const checkForEarlyExit = () => {
- if (this.disposed) {
- this.diposeSplatTreeWorker();
- resolve();
- return true;
- }
- return false;
- };
- if (onIndexesUpload) onIndexesUpload(false);
- delayedExecute(() => {
- if (checkForEarlyExit()) return;
- const allCenters = [];
- if (splatMesh.dynamicMode) {
- let splatOffset = 0;
- for (let s = 0; s < splatMesh.scenes.length; s++) {
- const scene = splatMesh.getScene(s);
- const splatCount = scene.splatBuffer.getSplatCount();
- const sceneCenters = addCentersForScene(splatOffset, splatCount);
- allCenters.push(sceneCenters);
- splatOffset += splatCount;
- }
- } else {
- const sceneCenters = addCentersForScene(0, splatMesh.getSplatCount());
- allCenters.push(sceneCenters);
- }
- this.splatTreeWorker.onmessage = (e) => {
- if (checkForEarlyExit()) return;
- if (e.data.subTrees) {
- if (onSplatTreeConstruction) onSplatTreeConstruction(false);
- delayedExecute(() => {
- if (checkForEarlyExit()) return;
- for (let workerSubTree of e.data.subTrees) {
- const convertedSubTree = SplatSubTree.convertWorkerSubTree(workerSubTree, splatMesh);
- this.subTrees.push(convertedSubTree);
- }
- this.diposeSplatTreeWorker();
- if (onSplatTreeConstruction) onSplatTreeConstruction(true);
- delayedExecute(() => {
- resolve();
- });
- });
- }
- };
- delayedExecute(() => {
- if (checkForEarlyExit()) return;
- if (onIndexesUpload) onIndexesUpload(true);
- const transferBuffers = allCenters.map((array) => array.buffer);
- workerProcessCenters(this.splatTreeWorker, allCenters, transferBuffers, this.maxDepth, this.maxCentersPerNode);
- });
- });
- });
- };
- countLeaves() {
- let leafCount = 0;
- this.visitLeaves(() => {
- leafCount++;
- });
- return leafCount;
- }
- visitLeaves(visitFunc) {
- const visitLeavesFromNode = (node, visitFunc) => {
- if (node.children.length === 0) visitFunc(node);
- for (let child of node.children) {
- visitLeavesFromNode(child, visitFunc);
- }
- };
- for (let subTree of this.subTrees) {
- visitLeavesFromNode(subTree.rootNode, visitFunc);
- }
- }
- }
- function WebGLExtensions( gl ) {
- const extensions = {};
- function getExtension( name ) {
- if ( extensions[name] !== undefined ) {
- return extensions[name];
- }
- let extension;
- switch ( name ) {
- case 'WEBGL_depth_texture':
- extension = gl.getExtension( 'WEBGL_depth_texture' ) || gl.getExtension( 'MOZ_WEBGL_depth_texture' ) ||
- gl.getExtension( 'WEBKIT_WEBGL_depth_texture' );
- break;
- case 'EXT_texture_filter_anisotropic':
- extension = gl.getExtension( 'EXT_texture_filter_anisotropic' ) ||
- gl.getExtension( 'MOZ_EXT_texture_filter_anisotropic' ) ||
- gl.getExtension( 'WEBKIT_EXT_texture_filter_anisotropic' );
- break;
- case 'WEBGL_compressed_texture_s3tc':
- extension = gl.getExtension( 'WEBGL_compressed_texture_s3tc' ) ||
- gl.getExtension( 'MOZ_WEBGL_compressed_texture_s3tc' ) ||
- gl.getExtension( 'WEBKIT_WEBGL_compressed_texture_s3tc' );
- break;
- case 'WEBGL_compressed_texture_pvrtc':
- extension = gl.getExtension( 'WEBGL_compressed_texture_pvrtc' ) ||
- gl.getExtension( 'WEBKIT_WEBGL_compressed_texture_pvrtc' );
- break;
- default:
- extension = gl.getExtension( name );
- }
- extensions[name] = extension;
- return extension;
- }
- return {
- has: function( name ) {
- return getExtension( name ) !== null;
- },
- init: function( capabilities ) {
- if ( capabilities.isWebGL2 ) {
- getExtension( 'EXT_color_buffer_float' );
- getExtension( 'WEBGL_clip_cull_distance' );
- } else {
- getExtension( 'WEBGL_depth_texture' );
- getExtension( 'OES_texture_float' );
- getExtension( 'OES_texture_half_float' );
- getExtension( 'OES_texture_half_float_linear' );
- getExtension( 'OES_standard_derivatives' );
- getExtension( 'OES_element_index_uint' );
- getExtension( 'OES_vertex_array_object' );
- getExtension( 'ANGLE_instanced_arrays' );
- }
- getExtension( 'OES_texture_float_linear' );
- getExtension( 'EXT_color_buffer_half_float' );
- getExtension( 'WEBGL_multisampled_render_to_texture' );
- },
- get: function( name ) {
- const extension = getExtension( name );
- if ( extension === null ) {
- console.warn( 'THREE.WebGLRenderer: ' + name + ' extension not supported.' );
- }
- return extension;
- }
- };
- }
- function WebGLCapabilities( gl, extensions, parameters ) {
- let maxAnisotropy;
- function getMaxAnisotropy() {
- if ( maxAnisotropy !== undefined ) return maxAnisotropy;
- if ( extensions.has( 'EXT_texture_filter_anisotropic' ) === true ) {
- const extension = extensions.get( 'EXT_texture_filter_anisotropic' );
- maxAnisotropy = gl.getParameter( extension.MAX_TEXTURE_MAX_ANISOTROPY_EXT );
- } else {
- maxAnisotropy = 0;
- }
- return maxAnisotropy;
- }
- function getMaxPrecision( precision ) {
- if ( precision === 'highp' ) {
- if ( gl.getShaderPrecisionFormat( gl.VERTEX_SHADER, gl.HIGH_FLOAT ).precision > 0 &&
- gl.getShaderPrecisionFormat( gl.FRAGMENT_SHADER, gl.HIGH_FLOAT ).precision > 0 ) {
- return 'highp';
- }
- precision = 'mediump';
- }
- if ( precision === 'mediump' ) {
- if ( gl.getShaderPrecisionFormat( gl.VERTEX_SHADER, gl.MEDIUM_FLOAT ).precision > 0 &&
- gl.getShaderPrecisionFormat( gl.FRAGMENT_SHADER, gl.MEDIUM_FLOAT ).precision > 0 ) {
- return 'mediump';
- }
- }
- return 'lowp';
- }
- const isWebGL2 = typeof WebGL2RenderingContext !== 'undefined' && gl.constructor.name === 'WebGL2RenderingContext';
- let precision = parameters.precision !== undefined ? parameters.precision : 'highp';
- const maxPrecision = getMaxPrecision( precision );
- if ( maxPrecision !== precision ) {
- console.warn( 'THREE.WebGLRenderer:', precision, 'not supported, using', maxPrecision, 'instead.' );
- precision = maxPrecision;
- }
- const drawBuffers = isWebGL2 || extensions.has( 'WEBGL_draw_buffers' );
- const logarithmicDepthBuffer = parameters.logarithmicDepthBuffer === true;
- const maxTextures = gl.getParameter( gl.MAX_TEXTURE_IMAGE_UNITS );
- const maxVertexTextures = gl.getParameter( gl.MAX_VERTEX_TEXTURE_IMAGE_UNITS );
- const maxTextureSize = gl.getParameter( gl.MAX_TEXTURE_SIZE );
- const maxCubemapSize = gl.getParameter( gl.MAX_CUBE_MAP_TEXTURE_SIZE );
- const maxAttributes = gl.getParameter( gl.MAX_VERTEX_ATTRIBS );
- const maxVertexUniforms = gl.getParameter( gl.MAX_VERTEX_UNIFORM_VECTORS );
- const maxVaryings = gl.getParameter( gl.MAX_VARYING_VECTORS );
- const maxFragmentUniforms = gl.getParameter( gl.MAX_FRAGMENT_UNIFORM_VECTORS );
- const vertexTextures = maxVertexTextures > 0;
- const floatFragmentTextures = isWebGL2 || extensions.has( 'OES_texture_float' );
- const floatVertexTextures = vertexTextures && floatFragmentTextures;
- const maxSamples = isWebGL2 ? gl.getParameter( gl.MAX_SAMPLES ) : 0;
- return {
- isWebGL2: isWebGL2,
- drawBuffers: drawBuffers,
- getMaxAnisotropy: getMaxAnisotropy,
- getMaxPrecision: getMaxPrecision,
- precision: precision,
- logarithmicDepthBuffer: logarithmicDepthBuffer,
- maxTextures: maxTextures,
- maxVertexTextures: maxVertexTextures,
- maxTextureSize: maxTextureSize,
- maxCubemapSize: maxCubemapSize,
- maxAttributes: maxAttributes,
- maxVertexUniforms: maxVertexUniforms,
- maxVaryings: maxVaryings,
- maxFragmentUniforms: maxFragmentUniforms,
- vertexTextures: vertexTextures,
- floatFragmentTextures: floatFragmentTextures,
- floatVertexTextures: floatVertexTextures,
- maxSamples: maxSamples
- };
- }
- const SceneRevealMode = {
- Default: 0,
- Gradual: 1,
- Instant: 2
- };
- const LogLevel = {
- None: 0,
- Error: 1,
- Warning: 2,
- Info: 3,
- Debug: 4
- };
- const dummyGeometry = new THREE.BufferGeometry();
- const dummyMaterial = new THREE.MeshBasicMaterial();
- const COVARIANCES_ELEMENTS_PER_SPLAT = 6;
- const CENTER_COLORS_ELEMENTS_PER_SPLAT = 4;
- const COVARIANCES_ELEMENTS_PER_TEXEL_STORED = 4;
- const COVARIANCES_ELEMENTS_PER_TEXEL_ALLOCATED = 4;
- const COVARIANCES_ELEMENTS_PER_TEXEL_COMPRESSED_STORED = 6;
- const COVARIANCES_ELEMENTS_PER_TEXEL_COMPRESSED_ALLOCATED = 8;
- const SCALES_ROTATIONS_ELEMENTS_PER_TEXEL = 4;
- const CENTER_COLORS_ELEMENTS_PER_TEXEL = 4;
- const SCENE_INDEXES_ELEMENTS_PER_TEXEL = 1;
- const SCENE_FADEIN_RATE_FAST = 0.012;
- const SCENE_FADEIN_RATE_GRADUAL = 0.003;
- const VISIBLE_REGION_EXPANSION_DELTA = 1;
- // Based on my own observations across multiple devices, OSes and browsers, using textures that have one dimension
- // greater than 4096 while the other is greater than or equal to 4096 causes issues (Essentially any texture larger
- // than 4096 x 4096 (16777216) texels). Specifically it seems all texture data beyond the 4096 x 4096 texel boundary
- // is corrupted, while data below that boundary is usable. In these cases the texture has been valid in the eyes of
- // both Three.js and WebGL, and the texel format (RG, RGBA, etc.) has not mattered. More investigation will be needed,
- // but for now the work-around is to split the spherical harmonics into three textures (one for each color channel).
- const MAX_TEXTURE_TEXELS = 16777216;
- /**
- * SplatMesh: Container for one or more splat scenes, abstracting them into a single unified container for
- * splat data. Additionally contains data structures and code to make the splat data renderable as a Three.js mesh.
- */
- class SplatMesh extends THREE.Mesh {
- constructor(splatRenderMode = SplatRenderMode.ThreeD, dynamicMode = false, enableOptionalEffects = false,
- halfPrecisionCovariancesOnGPU = false, devicePixelRatio = 1, enableDistancesComputationOnGPU = true,
- integerBasedDistancesComputation = false, antialiased = false, maxScreenSpaceSplatSize = 1024, logLevel = LogLevel.None,
- sphericalHarmonicsDegree = 0) {
- super(dummyGeometry, dummyMaterial);
- // Reference to a Three.js renderer
- this.renderer = undefined;
- // Determine how the splats are rendered
- this.splatRenderMode = splatRenderMode;
- // When 'dynamicMode' is true, scenes are assumed to be non-static. Dynamic scenes are handled differently
- // and certain optimizations cannot be made for them. Additionally, by default, all splat data retrieved from
- // this splat mesh will not have their scene transform applied to them if the splat mesh is dynamic. That
- // can be overriden via parameters to the individual functions that are used to retrieve splat data.
- this.dynamicMode = dynamicMode;
- // When true, allows for usage of extra properties and attributes during rendering for effects such as opacity adjustment.
- // Default is false for performance reasons. These properties are separate from transform properties (scale, rotation, position)
- // that are enabled by the 'dynamicScene' parameter.
- this.enableOptionalEffects = enableOptionalEffects;
- // Use 16-bit floating point values when storing splat covariance data in textures, instead of 32-bit
- this.halfPrecisionCovariancesOnGPU = halfPrecisionCovariancesOnGPU;
- // Ratio of the resolution in physical pixels to the resolution in CSS pixels for the current display device
- this.devicePixelRatio = devicePixelRatio;
- // Use a transform feedback to calculate splat distances from the camera
- this.enableDistancesComputationOnGPU = enableDistancesComputationOnGPU;
- // Use a faster integer-based approach for calculating splat distances from the camera
- this.integerBasedDistancesComputation = integerBasedDistancesComputation;
- // When true, will perform additional steps during rendering to address artifacts caused by the rendering of gaussians at a
- // substantially different resolution than that at which they were rendered during training. This will only work correctly
- // for models that were trained using a process that utilizes this compensation calculation. For more details:
- // https://github.com/nerfstudio-project/gsplat/pull/117
- // https://github.com/graphdeco-inria/gaussian-splatting/issues/294#issuecomment-1772688093
- this.antialiased = antialiased;
- // Specify the maximum clip space splat size, can help deal with large splats that get too unwieldy
- this.maxScreenSpaceSplatSize = maxScreenSpaceSplatSize;
- // The verbosity of console logging
- this.logLevel = logLevel;
- // Degree 0 means no spherical harmonics
- this.sphericalHarmonicsDegree = sphericalHarmonicsDegree;
- this.minSphericalHarmonicsDegree = 0;
- // The individual splat scenes stored in this splat mesh, each containing their own transform
- this.scenes = [];
- // Special octree tailored to SplatMesh instances
- this.splatTree = null;
- this.baseSplatTree = null;
- // Cache textures and the intermediate data used to populate them
- this.splatDataTextures = {};
- this.distancesTransformFeedback = {
- 'id': null,
- 'vertexShader': null,
- 'fragmentShader': null,
- 'program': null,
- 'centersBuffer': null,
- 'sceneIndexesBuffer': null,
- 'outDistancesBuffer': null,
- 'centersLoc': -1,
- 'modelViewProjLoc': -1,
- 'sceneIndexesLoc': -1,
- 'transformsLocs': []
- };
- this.globalSplatIndexToLocalSplatIndexMap = [];
- this.globalSplatIndexToSceneIndexMap = [];
- this.lastBuildSplatCount = 0;
- this.lastBuildScenes = [];
- this.lastBuildMaxSplatCount = 0;
- this.lastBuildSceneCount = 0;
- this.firstRenderTime = -1;
- this.finalBuild = false;
- this.webGLUtils = null;
- this.boundingBox = new THREE.Box3();
- this.calculatedSceneCenter = new THREE.Vector3();
- this.maxSplatDistanceFromSceneCenter = 0;
- this.visibleRegionBufferRadius = 0;
- this.visibleRegionRadius = 0;
- this.visibleRegionFadeStartRadius = 0;
- this.visibleRegionChanging = false;
- this.splatScale = 1.0;
- this.pointCloudModeEnabled = false;
- this.disposed = false;
- this.lastRenderer = null;
- this.visible = false;
- }
- /**
- * Build a container for each scene managed by this splat mesh based on an instance of SplatBuffer, along with optional
- * transform data (position, scale, rotation) passed to the splat mesh during the build process.
- * @param {Array<THREE.Matrix4>} splatBuffers SplatBuffer instances containing splats for each scene
- * @param {Array<object>} sceneOptions Array of options objects: {
- *
- * position (Array<number>): Position of the scene, acts as an offset from its default position, defaults to [0, 0, 0]
- *
- * rotation (Array<number>): Rotation of the scene represented as a quaternion, defaults to [0, 0, 0, 1]
- *
- * scale (Array<number>): Scene's scale, defaults to [1, 1, 1]
- * }
- * @return {Array<THREE.Matrix4>}
- */
- static buildScenes(parentObject, splatBuffers, sceneOptions) {
- const scenes = [];
- scenes.length = splatBuffers.length;
- for (let i = 0; i < splatBuffers.length; i++) {
- const splatBuffer = splatBuffers[i];
- const options = sceneOptions[i] || {};
- let positionArray = options['position'] || [0, 0, 0];
- let rotationArray = options['rotation'] || [0, 0, 0, 1];
- let scaleArray = options['scale'] || [1, 1, 1];
- const position = new THREE.Vector3().fromArray(positionArray);
- const rotation = new THREE.Quaternion().fromArray(rotationArray);
- const scale = new THREE.Vector3().fromArray(scaleArray);
- const scene = SplatMesh.createScene(splatBuffer, position, rotation, scale,
- options.splatAlphaRemovalThreshold || 1, options.opacity, options.visible);
- parentObject.add(scene);
- scenes[i] = scene;
- }
- return scenes;
- }
- static createScene(splatBuffer, position, rotation, scale, minimumAlpha, opacity = 1.0, visible = true) {
- return new SplatScene(splatBuffer, position, rotation, scale, minimumAlpha, opacity, visible);
- }
- /**
- * Build data structures that map global splat indexes (based on a unified index across all splat buffers) to
- * local data within a single scene.
- * @param {Array<SplatBuffer>} splatBuffers Instances of SplatBuffer off which to build the maps
- * @return {object}
- */
- static buildSplatIndexMaps(splatBuffers) {
- const localSplatIndexMap = [];
- const sceneIndexMap = [];
- let totalSplatCount = 0;
- for (let s = 0; s < splatBuffers.length; s++) {
- const splatBuffer = splatBuffers[s];
- const maxSplatCount = splatBuffer.getMaxSplatCount();
- for (let i = 0; i < maxSplatCount; i++) {
- localSplatIndexMap[totalSplatCount] = i;
- sceneIndexMap[totalSplatCount] = s;
- totalSplatCount++;
- }
- }
- return {
- localSplatIndexMap,
- sceneIndexMap
- };
- }
- /**
- * Build an instance of SplatTree (a specialized octree) for the given splat mesh.
- * @param {Array<number>} minAlphas Array of minimum splat slphas for each scene
- * @param {function} onSplatTreeIndexesUpload Function to be called when the upload of splat centers to the splat tree
- * builder worker starts and finishes.
- * @param {function} onSplatTreeConstruction Function to be called when the conversion of the local splat tree from
- * the format produced by the splat tree builder worker starts and ends.
- * @return {SplatTree}
- */
- buildSplatTree = function(minAlphas = [], onSplatTreeIndexesUpload, onSplatTreeConstruction) {
- return new Promise((resolve) => {
- this.disposeSplatTree();
- // TODO: expose SplatTree constructor parameters (maximumDepth and maxCentersPerNode) so that they can
- // be configured on a per-scene basis
- this.baseSplatTree = new SplatTree(8, 1000);
- const buildStartTime = performance.now();
- const splatColor = new THREE.Vector4();
- this.baseSplatTree.processSplatMesh(this, (splatIndex) => {
- this.getSplatColor(splatIndex, splatColor);
- const sceneIndex = this.getSceneIndexForSplat(splatIndex);
- const minAlpha = minAlphas[sceneIndex] || 1;
- return splatColor.w >= minAlpha;
- }, onSplatTreeIndexesUpload, onSplatTreeConstruction)
- .then(() => {
- const buildTime = performance.now() - buildStartTime;
- if (this.logLevel >= LogLevel.Info) console.log('SplatTree build: ' + buildTime + ' ms');
- if (this.disposed) {
- resolve();
- } else {
- this.splatTree = this.baseSplatTree;
- this.baseSplatTree = null;
- let leavesWithVertices = 0;
- let avgSplatCount = 0;
- let maxSplatCount = 0;
- let nodeCount = 0;
- this.splatTree.visitLeaves((node) => {
- const nodeSplatCount = node.data.indexes.length;
- if (nodeSplatCount > 0) {
- avgSplatCount += nodeSplatCount;
- maxSplatCount = Math.max(maxSplatCount, nodeSplatCount);
- nodeCount++;
- leavesWithVertices++;
- }
- });
- if (this.logLevel >= LogLevel.Info) {
- console.log(`SplatTree leaves: ${this.splatTree.countLeaves()}`);
- console.log(`SplatTree leaves with splats:${leavesWithVertices}`);
- avgSplatCount = avgSplatCount / nodeCount;
- console.log(`Avg splat count per node: ${avgSplatCount}`);
- console.log(`Total splat count: ${this.getSplatCount()}`);
- }
- resolve();
- }
- });
- });
- };
- /**
- * Construct this instance of SplatMesh.
- * @param {Array<SplatBuffer>} splatBuffers The base splat data, instances of SplatBuffer
- * @param {Array<object>} sceneOptions Dynamic options for each scene {
- *
- * splatAlphaRemovalThreshold: Ignore any splats with an alpha less than the specified
- * value (valid range: 0 - 255), defaults to 1
- *
- * position (Array<number>): Position of the scene, acts as an offset from its default position, defaults to [0, 0, 0]
- *
- * rotation (Array<number>): Rotation of the scene represented as a quaternion, defaults to [0, 0, 0, 1]
- *
- * scale (Array<number>): Scene's scale, defaults to [1, 1, 1]
- *
- * }
- * @param {boolean} keepSceneTransforms For a scene that already exists and is being overwritten, this flag
- * says to keep the transform from the existing scene.
- * @param {boolean} finalBuild Will the splat mesh be in its final state after this build?
- * @param {function} onSplatTreeIndexesUpload Function to be called when the upload of splat centers to the splat tree
- * builder worker starts and finishes.
- * @param {function} onSplatTreeConstruction Function to be called when the conversion of the local splat tree from
- * the format produced by the splat tree builder worker starts and ends.
- * @return {object} Object containing info about the splats that are updated
- */
- build(splatBuffers, sceneOptions, keepSceneTransforms = true, finalBuild = false,
- onSplatTreeIndexesUpload, onSplatTreeConstruction, preserveVisibleRegion = true) {
- this.sceneOptions = sceneOptions;
- this.finalBuild = finalBuild;
- const maxSplatCount = SplatMesh.getTotalMaxSplatCountForSplatBuffers(splatBuffers);
- const newScenes = SplatMesh.buildScenes(this, splatBuffers, sceneOptions);
- if (keepSceneTransforms) {
- for (let i = 0; i < this.scenes.length && i < newScenes.length; i++) {
- const newScene = newScenes[i];
- const existingScene = this.getScene(i);
- newScene.copyTransformData(existingScene);
- }
- }
- this.scenes = newScenes;
- let minSphericalHarmonicsDegree = 3;
- for (let splatBuffer of splatBuffers) {
- const splatBufferSphericalHarmonicsDegree = splatBuffer.getMinSphericalHarmonicsDegree();
- if (splatBufferSphericalHarmonicsDegree < minSphericalHarmonicsDegree) {
- minSphericalHarmonicsDegree = splatBufferSphericalHarmonicsDegree;
- }
- }
- this.minSphericalHarmonicsDegree = Math.min(minSphericalHarmonicsDegree, this.sphericalHarmonicsDegree);
- let splatBuffersChanged = false;
- if (splatBuffers.length !== this.lastBuildScenes.length) {
- splatBuffersChanged = true;
- } else {
- for (let i = 0; i < splatBuffers.length; i++) {
- const splatBuffer = splatBuffers[i];
- if (splatBuffer !== this.lastBuildScenes[i].splatBuffer) {
- splatBuffersChanged = true;
- break;
- }
- }
- }
- let isUpdateBuild = true;
- if (this.scenes.length !== 1 ||
- this.lastBuildSceneCount !== this.scenes.length ||
- this.lastBuildMaxSplatCount !== maxSplatCount ||
- splatBuffersChanged) {
- isUpdateBuild = false;
- }
- if (!isUpdateBuild) {
- this.boundingBox = new THREE.Box3();
- if (!preserveVisibleRegion) {
- this.maxSplatDistanceFromSceneCenter = 0;
- this.visibleRegionBufferRadius = 0;
- this.visibleRegionRadius = 0;
- this.visibleRegionFadeStartRadius = 0;
- this.firstRenderTime = -1;
- }
- this.lastBuildScenes = [];
- this.lastBuildSplatCount = 0;
- this.lastBuildMaxSplatCount = 0;
- this.disposeMeshData();
- this.geometry = SplatGeometry.build(maxSplatCount);
- if (this.splatRenderMode === SplatRenderMode.ThreeD) {
- this.material = SplatMaterial3D.build(this.dynamicMode, this.enableOptionalEffects, this.antialiased,
- this.maxScreenSpaceSplatSize, this.splatScale, this.pointCloudModeEnabled,
- this.minSphericalHarmonicsDegree);
- } else {
- this.material = SplatMaterial2D.build(this.dynamicMode, this.enableOptionalEffects,
- this.splatScale, this.pointCloudModeEnabled, this.minSphericalHarmonicsDegree);
- }
- const indexMaps = SplatMesh.buildSplatIndexMaps(splatBuffers);
- this.globalSplatIndexToLocalSplatIndexMap = indexMaps.localSplatIndexMap;
- this.globalSplatIndexToSceneIndexMap = indexMaps.sceneIndexMap;
- }
- const splatCount = this.getSplatCount();
- if (this.enableDistancesComputationOnGPU) this.setupDistancesComputationTransformFeedback();
- const dataUpdateResults = this.refreshGPUDataFromSplatBuffers(isUpdateBuild);
- for (let i = 0; i < this.scenes.length; i++) {
- this.lastBuildScenes[i] = this.scenes[i];
- }
- this.lastBuildSplatCount = splatCount;
- this.lastBuildMaxSplatCount = this.getMaxSplatCount();
- this.lastBuildSceneCount = this.scenes.length;
- if (finalBuild && this.scenes.length > 0) {
- this.buildSplatTree(sceneOptions.map(options => options.splatAlphaRemovalThreshold || 1),
- onSplatTreeIndexesUpload, onSplatTreeConstruction)
- .then(() => {
- if (this.onSplatTreeReadyCallback) this.onSplatTreeReadyCallback(this.splatTree);
- this.onSplatTreeReadyCallback = null;
- });
- }
- this.visible = (this.scenes.length > 0);
- return dataUpdateResults;
- }
- freeIntermediateSplatData() {
- const deleteTextureData = (texture) => {
- delete texture.source.data;
- delete texture.image;
- texture.onUpdate = null;
- };
- delete this.splatDataTextures.baseData.covariances;
- delete this.splatDataTextures.baseData.centers;
- delete this.splatDataTextures.baseData.colors;
- delete this.splatDataTextures.baseData.sphericalHarmonics;
- delete this.splatDataTextures.centerColors.data;
- delete this.splatDataTextures.covariances.data;
- if (this.splatDataTextures.sphericalHarmonics) {
- delete this.splatDataTextures.sphericalHarmonics.data;
- }
- if (this.splatDataTextures.sceneIndexes) {
- delete this.splatDataTextures.sceneIndexes.data;
- }
- this.splatDataTextures.centerColors.texture.needsUpdate = true;
- this.splatDataTextures.centerColors.texture.onUpdate = () => {
- deleteTextureData(this.splatDataTextures.centerColors.texture);
- };
- this.splatDataTextures.covariances.texture.needsUpdate = true;
- this.splatDataTextures.covariances.texture.onUpdate = () => {
- deleteTextureData(this.splatDataTextures.covariances.texture);
- };
- if (this.splatDataTextures.sphericalHarmonics) {
- if (this.splatDataTextures.sphericalHarmonics.texture) {
- this.splatDataTextures.sphericalHarmonics.texture.needsUpdate = true;
- this.splatDataTextures.sphericalHarmonics.texture.onUpdate = () => {
- deleteTextureData(this.splatDataTextures.sphericalHarmonics.texture);
- };
- } else {
- this.splatDataTextures.sphericalHarmonics.textures.forEach((texture) => {
- texture.needsUpdate = true;
- texture.onUpdate = () => {
- deleteTextureData(texture);
- };
- });
- }
- }
- if (this.splatDataTextures.sceneIndexes) {
- this.splatDataTextures.sceneIndexes.texture.needsUpdate = true;
- this.splatDataTextures.sceneIndexes.texture.onUpdate = () => {
- deleteTextureData(this.splatDataTextures.sceneIndexes.texture);
- };
- }
- }
- /**
- * Dispose all resources held by the splat mesh
- */
- dispose() {
- this.disposeMeshData();
- this.disposeTextures();
- this.disposeSplatTree();
- if (this.enableDistancesComputationOnGPU) {
- if (this.computeDistancesOnGPUSyncTimeout) {
- clearTimeout(this.computeDistancesOnGPUSyncTimeout);
- this.computeDistancesOnGPUSyncTimeout = null;
- }
- this.disposeDistancesComputationGPUResources();
- }
- this.scenes = [];
- this.distancesTransformFeedback = {
- 'id': null,
- 'vertexShader': null,
- 'fragmentShader': null,
- 'program': null,
- 'centersBuffer': null,
- 'sceneIndexesBuffer': null,
- 'outDistancesBuffer': null,
- 'centersLoc': -1,
- 'modelViewProjLoc': -1,
- 'sceneIndexesLoc': -1,
- 'transformsLocs': []
- };
- this.renderer = null;
- this.globalSplatIndexToLocalSplatIndexMap = [];
- this.globalSplatIndexToSceneIndexMap = [];
- this.lastBuildSplatCount = 0;
- this.lastBuildScenes = [];
- this.lastBuildMaxSplatCount = 0;
- this.lastBuildSceneCount = 0;
- this.firstRenderTime = -1;
- this.finalBuild = false;
- this.webGLUtils = null;
- this.boundingBox = new THREE.Box3();
- this.calculatedSceneCenter = new THREE.Vector3();
- this.maxSplatDistanceFromSceneCenter = 0;
- this.visibleRegionBufferRadius = 0;
- this.visibleRegionRadius = 0;
- this.visibleRegionFadeStartRadius = 0;
- this.visibleRegionChanging = false;
- this.splatScale = 1.0;
- this.pointCloudModeEnabled = false;
- this.disposed = true;
- this.lastRenderer = null;
- this.visible = false;
- }
- /**
- * Dispose of only the Three.js mesh resources (geometry, material, and texture)
- */
- disposeMeshData() {
- if (this.geometry && this.geometry !== dummyGeometry) {
- this.geometry.dispose();
- this.geometry = null;
- }
- if (this.material) {
- this.material.dispose();
- this.material = null;
- }
- }
- disposeTextures() {
- for (let textureKey in this.splatDataTextures) {
- if (this.splatDataTextures.hasOwnProperty(textureKey)) {
- const textureContainer = this.splatDataTextures[textureKey];
- if (textureContainer.texture) {
- textureContainer.texture.dispose();
- textureContainer.texture = null;
- }
- }
- }
- this.splatDataTextures = null;
- }
- disposeSplatTree() {
- if (this.splatTree) {
- this.splatTree.dispose();
- this.splatTree = null;
- }
- if (this.baseSplatTree) {
- this.baseSplatTree.dispose();
- this.baseSplatTree = null;
- }
- }
- getSplatTree() {
- return this.splatTree;
- }
- onSplatTreeReady(callback) {
- this.onSplatTreeReadyCallback = callback;
- }
- /**
- * Get copies of data that are necessary for splat distance computation: splat center positions and splat
- * scene indexes (necessary for applying dynamic scene transformations during distance computation)
- * @param {*} start The index at which to start copying data
- * @param {*} end The index at which to stop copying data
- * @return {object}
- */
- getDataForDistancesComputation(start, end) {
- const centers = this.integerBasedDistancesComputation ?
- this.getIntegerCenters(start, end, true) :
- this.getFloatCenters(start, end, true);
- const sceneIndexes = this.getSceneIndexes(start, end);
- return {
- centers,
- sceneIndexes
- };
- }
- /**
- * Refresh data textures and GPU buffers with splat data from the splat buffers belonging to this mesh.
- * @param {boolean} sinceLastBuildOnly Specify whether or not to only update for splats that have been added since the last build.
- * @return {object}
- */
- refreshGPUDataFromSplatBuffers(sinceLastBuildOnly) {
- const splatCount = this.getSplatCount();
- this.refreshDataTexturesFromSplatBuffers(sinceLastBuildOnly);
- const updateStart = sinceLastBuildOnly ? this.lastBuildSplatCount : 0;
- const { centers, sceneIndexes } = this.getDataForDistancesComputation(updateStart, splatCount - 1);
- if (this.enableDistancesComputationOnGPU) {
- this.refreshGPUBuffersForDistancesComputation(centers, sceneIndexes, sinceLastBuildOnly);
- }
- return {
- 'from': updateStart,
- 'to': splatCount - 1,
- 'count': splatCount - updateStart,
- 'centers': centers,
- 'sceneIndexes': sceneIndexes
- };
- }
- /**
- * Update the GPU buffers that are used for computing splat distances on the GPU.
- * @param {Array<number>} centers Splat center positions
- * @param {Array<number>} sceneIndexes Indexes of the scene to which each splat belongs
- * @param {boolean} sinceLastBuildOnly Specify whether or not to only update for splats that have been added since the last build.
- */
- refreshGPUBuffersForDistancesComputation(centers, sceneIndexes, sinceLastBuildOnly = false) {
- const offset = sinceLastBuildOnly ? this.lastBuildSplatCount : 0;
- this.updateGPUCentersBufferForDistancesComputation(sinceLastBuildOnly, centers, offset);
- this.updateGPUTransformIndexesBufferForDistancesComputation(sinceLastBuildOnly, sceneIndexes, offset);
- }
- /**
- * Refresh data textures with data from the splat buffers for this mesh.
- * @param {boolean} sinceLastBuildOnly Specify whether or not to only update for splats that have been added since the last build.
- */
- refreshDataTexturesFromSplatBuffers(sinceLastBuildOnly) {
- const splatCount = this.getSplatCount();
- const fromSplat = this.lastBuildSplatCount;
- const toSplat = splatCount - 1;
- if (!sinceLastBuildOnly) {
- this.setupDataTextures();
- this.updateBaseDataFromSplatBuffers();
- } else {
- this.updateBaseDataFromSplatBuffers(fromSplat, toSplat);
- }
- this.updateDataTexturesFromBaseData(fromSplat, toSplat);
- this.updateVisibleRegion(sinceLastBuildOnly);
- }
- setupDataTextures() {
- const maxSplatCount = this.getMaxSplatCount();
- const splatCount = this.getSplatCount();
- this.disposeTextures();
- const computeDataTextureSize = (elementsPerTexel, elementsPerSplat) => {
- const texSize = new THREE.Vector2(4096, 1024);
- while (texSize.x * texSize.y * elementsPerTexel < maxSplatCount * elementsPerSplat) texSize.y *= 2;
- return texSize;
- };
- const getCovariancesElementsPertexelStored = (compressionLevel) => {
- return compressionLevel >= 1 ? COVARIANCES_ELEMENTS_PER_TEXEL_COMPRESSED_STORED : COVARIANCES_ELEMENTS_PER_TEXEL_STORED;
- };
- const getCovariancesInitialTextureSpecs = (compressionLevel) => {
- const elementsPerTexelStored = getCovariancesElementsPertexelStored(compressionLevel);
- const texSize = computeDataTextureSize(elementsPerTexelStored, 6);
- return {elementsPerTexelStored, texSize};
- };
- let covarianceCompressionLevel = this.getTargetCovarianceCompressionLevel();
- const scaleRotationCompressionLevel = 0;
- const shCompressionLevel = this.getTargetSphericalHarmonicsCompressionLevel();
- let covariances;
- let scales;
- let rotations;
- if (this.splatRenderMode === SplatRenderMode.ThreeD) {
- const initialCovTexSpecs = getCovariancesInitialTextureSpecs(covarianceCompressionLevel);
- if (initialCovTexSpecs.texSize.x * initialCovTexSpecs.texSize.y > MAX_TEXTURE_TEXELS && covarianceCompressionLevel === 0) {
- covarianceCompressionLevel = 1;
- }
- covariances = new Float32Array(maxSplatCount * COVARIANCES_ELEMENTS_PER_SPLAT);
- } else {
- scales = new Float32Array(maxSplatCount * 3);
- rotations = new Float32Array(maxSplatCount * 4);
- }
- const centers = new Float32Array(maxSplatCount * 3);
- const colors = new Uint8Array(maxSplatCount * 4);
- let SphericalHarmonicsArrayType = Float32Array;
- if (shCompressionLevel === 1) SphericalHarmonicsArrayType = Uint16Array;
- else if (shCompressionLevel === 2) SphericalHarmonicsArrayType = Uint8Array;
- const shComponentCount = getSphericalHarmonicsComponentCountForDegree(this.minSphericalHarmonicsDegree);
- const shData = this.minSphericalHarmonicsDegree ? new SphericalHarmonicsArrayType(maxSplatCount * shComponentCount) : undefined;
- // set up centers/colors data texture
- const centersColsTexSize = computeDataTextureSize(CENTER_COLORS_ELEMENTS_PER_TEXEL, 4);
- const paddedCentersCols = new Uint32Array(centersColsTexSize.x * centersColsTexSize.y * CENTER_COLORS_ELEMENTS_PER_TEXEL);
- SplatMesh.updateCenterColorsPaddedData(0, splatCount - 1, centers, colors, paddedCentersCols);
- const centersColsTex = new THREE.DataTexture(paddedCentersCols, centersColsTexSize.x, centersColsTexSize.y,
- THREE.RGBAIntegerFormat, THREE.UnsignedIntType);
- centersColsTex.internalFormat = 'RGBA32UI';
- centersColsTex.needsUpdate = true;
- this.material.uniforms.centersColorsTexture.value = centersColsTex;
- this.material.uniforms.centersColorsTextureSize.value.copy(centersColsTexSize);
- this.material.uniformsNeedUpdate = true;
- this.splatDataTextures = {
- 'baseData': {
- 'covariances': covariances,
- 'scales': scales,
- 'rotations': rotations,
- 'centers': centers,
- 'colors': colors,
- 'sphericalHarmonics': shData
- },
- 'centerColors': {
- 'data': paddedCentersCols,
- 'texture': centersColsTex,
- 'size': centersColsTexSize
- }
- };
- if (this.splatRenderMode === SplatRenderMode.ThreeD) {
- // set up covariances data texture
- const covTexSpecs = getCovariancesInitialTextureSpecs(covarianceCompressionLevel);
- const covariancesElementsPerTexelStored = covTexSpecs.elementsPerTexelStored;
- const covTexSize = covTexSpecs.texSize;
- let CovariancesDataType = covarianceCompressionLevel >= 1 ? Uint32Array : Float32Array;
- const covariancesElementsPerTexelAllocated = covarianceCompressionLevel >= 1 ?
- COVARIANCES_ELEMENTS_PER_TEXEL_COMPRESSED_ALLOCATED :
- COVARIANCES_ELEMENTS_PER_TEXEL_ALLOCATED;
- const covariancesTextureData = new CovariancesDataType(covTexSize.x * covTexSize.y * covariancesElementsPerTexelAllocated);
- if (covarianceCompressionLevel === 0) {
- covariancesTextureData.set(covariances);
- } else {
- SplatMesh.updatePaddedCompressedCovariancesTextureData(covariances, covariancesTextureData, 0, 0, covariances.length);
- }
- let covTex;
- if (covarianceCompressionLevel >= 1) {
- covTex = new THREE.DataTexture(covariancesTextureData, covTexSize.x, covTexSize.y,
- THREE.RGBAIntegerFormat, THREE.UnsignedIntType);
- covTex.internalFormat = 'RGBA32UI';
- this.material.uniforms.covariancesTextureHalfFloat.value = covTex;
- } else {
- covTex = new THREE.DataTexture(covariancesTextureData, covTexSize.x, covTexSize.y, THREE.RGBAFormat, THREE.FloatType);
- this.material.uniforms.covariancesTexture.value = covTex;
- // For some reason a usampler2D needs to have a valid texture attached or WebGL complains
- const dummyTex = new THREE.DataTexture(new Uint32Array(32), 2, 2, THREE.RGBAIntegerFormat, THREE.UnsignedIntType);
- dummyTex.internalFormat = 'RGBA32UI';
- this.material.uniforms.covariancesTextureHalfFloat.value = dummyTex;
- dummyTex.needsUpdate = true;
- }
- covTex.needsUpdate = true;
- this.material.uniforms.covariancesAreHalfFloat.value = (covarianceCompressionLevel >= 1) ? 1 : 0;
- this.material.uniforms.covariancesTextureSize.value.copy(covTexSize);
- this.splatDataTextures['covariances'] = {
- 'data': covariancesTextureData,
- 'texture': covTex,
- 'size': covTexSize,
- 'compressionLevel': covarianceCompressionLevel,
- 'elementsPerTexelStored': covariancesElementsPerTexelStored,
- 'elementsPerTexelAllocated': covariancesElementsPerTexelAllocated
- };
- } else {
- // set up scale & rotations data texture
- const elementsPerSplat = 6;
- const scaleRotationsTexSize = computeDataTextureSize(SCALES_ROTATIONS_ELEMENTS_PER_TEXEL, elementsPerSplat);
- let ScaleRotationsDataType = scaleRotationCompressionLevel >= 1 ? Uint16Array : Float32Array;
- let scaleRotationsTextureType = scaleRotationCompressionLevel >= 1 ? THREE.HalfFloatType : THREE.FloatType;
- const paddedScaleRotations = new ScaleRotationsDataType(scaleRotationsTexSize.x * scaleRotationsTexSize.y *
- SCALES_ROTATIONS_ELEMENTS_PER_TEXEL);
- SplatMesh.updateScaleRotationsPaddedData(0, splatCount - 1, scales, rotations, paddedScaleRotations);
- const scaleRotationsTex = new THREE.DataTexture(paddedScaleRotations, scaleRotationsTexSize.x, scaleRotationsTexSize.y,
- THREE.RGBAFormat, scaleRotationsTextureType);
- scaleRotationsTex.needsUpdate = true;
- this.material.uniforms.scaleRotationsTexture.value = scaleRotationsTex;
- this.material.uniforms.scaleRotationsTextureSize.value.copy(scaleRotationsTexSize);
- this.splatDataTextures['scaleRotations'] = {
- 'data': paddedScaleRotations,
- 'texture': scaleRotationsTex,
- 'size': scaleRotationsTexSize,
- 'compressionLevel': scaleRotationCompressionLevel
- };
- }
- if (shData) {
- const shTextureType = shCompressionLevel === 2 ? THREE.UnsignedByteType : THREE.HalfFloatType;
- let paddedSHComponentCount = shComponentCount;
- if (paddedSHComponentCount % 2 !== 0) paddedSHComponentCount++;
- const shElementsPerTexel = this.minSphericalHarmonicsDegree === 2 ? 4 : 2;
- const texelFormat = shElementsPerTexel === 4 ? THREE.RGBAFormat : THREE.RGFormat;
- let shTexSize = computeDataTextureSize(shElementsPerTexel, paddedSHComponentCount);
- // Use one texture for all spherical harmonics data
- if (shTexSize.x * shTexSize.y <= MAX_TEXTURE_TEXELS) {
- const paddedSHArraySize = shTexSize.x * shTexSize.y * shElementsPerTexel;
- const paddedSHArray = new SphericalHarmonicsArrayType(paddedSHArraySize);
- for (let c = 0; c < splatCount; c++) {
- const srcBase = shComponentCount * c;
- const destBase = paddedSHComponentCount * c;
- for (let i = 0; i < shComponentCount; i++) {
- paddedSHArray[destBase + i] = shData[srcBase + i];
- }
- }
- const shTexture = new THREE.DataTexture(paddedSHArray, shTexSize.x, shTexSize.y, texelFormat, shTextureType);
- shTexture.needsUpdate = true;
- this.material.uniforms.sphericalHarmonicsTexture.value = shTexture;
- this.splatDataTextures['sphericalHarmonics'] = {
- 'componentCount': shComponentCount,
- 'paddedComponentCount': paddedSHComponentCount,
- 'data': paddedSHArray,
- 'textureCount': 1,
- 'texture': shTexture,
- 'size': shTexSize,
- 'compressionLevel': shCompressionLevel,
- 'elementsPerTexel': shElementsPerTexel
- };
- // Use three textures for spherical harmonics data, one per color channel
- } else {
- const shComponentCountPerChannel = shComponentCount / 3;
- paddedSHComponentCount = shComponentCountPerChannel;
- if (paddedSHComponentCount % 2 !== 0) paddedSHComponentCount++;
- shTexSize = computeDataTextureSize(shElementsPerTexel, paddedSHComponentCount);
- const paddedSHArraySize = shTexSize.x * shTexSize.y * shElementsPerTexel;
- const textureUniforms = [this.material.uniforms.sphericalHarmonicsTextureR,
- this.material.uniforms.sphericalHarmonicsTextureG,
- this.material.uniforms.sphericalHarmonicsTextureB];
- const paddedSHArrays = [];
- const shTextures = [];
- for (let t = 0; t < 3; t++) {
- const paddedSHArray = new SphericalHarmonicsArrayType(paddedSHArraySize);
- paddedSHArrays.push(paddedSHArray);
- for (let c = 0; c < splatCount; c++) {
- const srcBase = shComponentCount * c;
- const destBase = paddedSHComponentCount * c;
- if (shComponentCountPerChannel >= 3) {
- for (let i = 0; i < 3; i++) paddedSHArray[destBase + i] = shData[srcBase + t * 3 + i];
- if (shComponentCountPerChannel >= 8) {
- for (let i = 0; i < 5; i++) paddedSHArray[destBase + 3 + i] = shData[srcBase + 9 + t * 5 + i];
- }
- }
- }
- const shTexture = new THREE.DataTexture(paddedSHArray, shTexSize.x, shTexSize.y, texelFormat, shTextureType);
- shTextures.push(shTexture);
- shTexture.needsUpdate = true;
- textureUniforms[t].value = shTexture;
- }
- this.material.uniforms.sphericalHarmonicsMultiTextureMode.value = 1;
- this.splatDataTextures['sphericalHarmonics'] = {
- 'componentCount': shComponentCount,
- 'componentCountPerChannel': shComponentCountPerChannel,
- 'paddedComponentCount': paddedSHComponentCount,
- 'data': paddedSHArrays,
- 'textureCount': 3,
- 'textures': shTextures,
- 'size': shTexSize,
- 'compressionLevel': shCompressionLevel,
- 'elementsPerTexel': shElementsPerTexel
- };
- }
- this.material.uniforms.sphericalHarmonicsTextureSize.value.copy(shTexSize);
- this.material.uniforms.sphericalHarmonics8BitMode.value = shCompressionLevel === 2 ? 1 : 0;
- for (let s = 0; s < this.scenes.length; s++) {
- const splatBuffer = this.scenes[s].splatBuffer;
- this.material.uniforms.sphericalHarmonics8BitCompressionRangeMin.value[s] =
- splatBuffer.minSphericalHarmonicsCoeff;
- this.material.uniforms.sphericalHarmonics8BitCompressionRangeMax.value[s] =
- splatBuffer.maxSphericalHarmonicsCoeff;
- }
- this.material.uniformsNeedUpdate = true;
- }
- const sceneIndexesTexSize = computeDataTextureSize(SCENE_INDEXES_ELEMENTS_PER_TEXEL, 4);
- const paddedTransformIndexes = new Uint32Array(sceneIndexesTexSize.x *
- sceneIndexesTexSize.y * SCENE_INDEXES_ELEMENTS_PER_TEXEL);
- for (let c = 0; c < splatCount; c++) paddedTransformIndexes[c] = this.globalSplatIndexToSceneIndexMap[c];
- const sceneIndexesTexture = new THREE.DataTexture(paddedTransformIndexes, sceneIndexesTexSize.x, sceneIndexesTexSize.y,
- THREE.RedIntegerFormat, THREE.UnsignedIntType);
- sceneIndexesTexture.internalFormat = 'R32UI';
- sceneIndexesTexture.needsUpdate = true;
- this.material.uniforms.sceneIndexesTexture.value = sceneIndexesTexture;
- this.material.uniforms.sceneIndexesTextureSize.value.copy(sceneIndexesTexSize);
- this.material.uniformsNeedUpdate = true;
- this.splatDataTextures['sceneIndexes'] = {
- 'data': paddedTransformIndexes,
- 'texture': sceneIndexesTexture,
- 'size': sceneIndexesTexSize
- };
- this.material.uniforms.sceneCount.value = this.scenes.length;
- }
- updateBaseDataFromSplatBuffers(fromSplat, toSplat) {
- const covarancesTextureDesc = this.splatDataTextures['covariances'];
- const covarianceCompressionLevel = covarancesTextureDesc ? covarancesTextureDesc.compressionLevel : undefined;
- const scaleRotationsTextureDesc = this.splatDataTextures['scaleRotations'];
- const scaleRotationCompressionLevel = scaleRotationsTextureDesc ? scaleRotationsTextureDesc.compressionLevel : undefined;
- const shITextureDesc = this.splatDataTextures['sphericalHarmonics'];
- const shCompressionLevel = shITextureDesc ? shITextureDesc.compressionLevel : 0;
- this.fillSplatDataArrays(this.splatDataTextures.baseData.covariances, this.splatDataTextures.baseData.scales,
- this.splatDataTextures.baseData.rotations, this.splatDataTextures.baseData.centers,
- this.splatDataTextures.baseData.colors, this.splatDataTextures.baseData.sphericalHarmonics, undefined,
- covarianceCompressionLevel, scaleRotationCompressionLevel, shCompressionLevel,
- fromSplat, toSplat, fromSplat);
- }
- updateDataTexturesFromBaseData(fromSplat, toSplat) {
- const covarancesTextureDesc = this.splatDataTextures['covariances'];
- const covarianceCompressionLevel = covarancesTextureDesc ? covarancesTextureDesc.compressionLevel : undefined;
- const scaleRotationsTextureDesc = this.splatDataTextures['scaleRotations'];
- const scaleRotationCompressionLevel = scaleRotationsTextureDesc ? scaleRotationsTextureDesc.compressionLevel : undefined;
- const shTextureDesc = this.splatDataTextures['sphericalHarmonics'];
- const shCompressionLevel = shTextureDesc ? shTextureDesc.compressionLevel : 0;
- // Update center & color data texture
- const centerColorsTextureDescriptor = this.splatDataTextures['centerColors'];
- const paddedCenterColors = centerColorsTextureDescriptor.data;
- const centerColorsTexture = centerColorsTextureDescriptor.texture;
- SplatMesh.updateCenterColorsPaddedData(fromSplat, toSplat, this.splatDataTextures.baseData.centers,
- this.splatDataTextures.baseData.colors, paddedCenterColors);
- const centerColorsTextureProps = this.renderer ? this.renderer.properties.get(centerColorsTexture) : null;
- if (!centerColorsTextureProps || !centerColorsTextureProps.__webglTexture) {
- centerColorsTexture.needsUpdate = true;
- } else {
- this.updateDataTexture(paddedCenterColors, centerColorsTextureDescriptor.texture, centerColorsTextureDescriptor.size,
- centerColorsTextureProps, CENTER_COLORS_ELEMENTS_PER_TEXEL, CENTER_COLORS_ELEMENTS_PER_SPLAT, 4,
- fromSplat, toSplat);
- }
- // update covariance data texture
- if (covarancesTextureDesc) {
- const covariancesTexture = covarancesTextureDesc.texture;
- const covarancesStartElement = fromSplat * COVARIANCES_ELEMENTS_PER_SPLAT;
- const covariancesEndElement = toSplat * COVARIANCES_ELEMENTS_PER_SPLAT;
- if (covarianceCompressionLevel === 0) {
- for (let i = covarancesStartElement; i <= covariancesEndElement; i++) {
- const covariance = this.splatDataTextures.baseData.covariances[i];
- covarancesTextureDesc.data[i] = covariance;
- }
- } else {
- SplatMesh.updatePaddedCompressedCovariancesTextureData(this.splatDataTextures.baseData.covariances,
- covarancesTextureDesc.data,
- fromSplat * covarancesTextureDesc.elementsPerTexelAllocated,
- covarancesStartElement, covariancesEndElement);
- }
- const covariancesTextureProps = this.renderer ? this.renderer.properties.get(covariancesTexture) : null;
- if (!covariancesTextureProps || !covariancesTextureProps.__webglTexture) {
- covariancesTexture.needsUpdate = true;
- } else {
- if (covarianceCompressionLevel === 0) {
- this.updateDataTexture(covarancesTextureDesc.data, covarancesTextureDesc.texture, covarancesTextureDesc.size,
- covariancesTextureProps, covarancesTextureDesc.elementsPerTexelStored,
- COVARIANCES_ELEMENTS_PER_SPLAT, 4, fromSplat, toSplat);
- } else {
- this.updateDataTexture(covarancesTextureDesc.data, covarancesTextureDesc.texture, covarancesTextureDesc.size,
- covariancesTextureProps, covarancesTextureDesc.elementsPerTexelAllocated,
- covarancesTextureDesc.elementsPerTexelAllocated, 2, fromSplat, toSplat);
- }
- }
- }
- // update scale and rotation data texture
- if (scaleRotationsTextureDesc) {
- const paddedScaleRotations = scaleRotationsTextureDesc.data;
- const scaleRotationsTexture = scaleRotationsTextureDesc.texture;
- const elementsPerSplat = 6;
- const bytesPerElement = scaleRotationCompressionLevel === 0 ? 4 : 2;
- SplatMesh.updateScaleRotationsPaddedData(fromSplat, toSplat, this.splatDataTextures.baseData.scales,
- this.splatDataTextures.baseData.rotations, paddedScaleRotations);
- const scaleRotationsTextureProps = this.renderer ? this.renderer.properties.get(scaleRotationsTexture) : null;
- if (!scaleRotationsTextureProps || !scaleRotationsTextureProps.__webglTexture) {
- scaleRotationsTexture.needsUpdate = true;
- } else {
- this.updateDataTexture(paddedScaleRotations, scaleRotationsTextureDesc.texture, scaleRotationsTextureDesc.size,
- scaleRotationsTextureProps, SCALES_ROTATIONS_ELEMENTS_PER_TEXEL, elementsPerSplat, bytesPerElement,
- fromSplat, toSplat);
- }
- }
- // update spherical harmonics data texture
- const shData = this.splatDataTextures.baseData.sphericalHarmonics;
- if (shData) {
- let shBytesPerElement = 4;
- if (shCompressionLevel === 1) shBytesPerElement = 2;
- else if (shCompressionLevel === 2) shBytesPerElement = 1;
- const updateTexture = (shTexture, shTextureSize, elementsPerTexel, paddedSHArray, paddedSHComponentCount) => {
- const shTextureProps = this.renderer ? this.renderer.properties.get(shTexture) : null;
- if (!shTextureProps || !shTextureProps.__webglTexture) {
- shTexture.needsUpdate = true;
- } else {
- this.updateDataTexture(paddedSHArray, shTexture, shTextureSize, shTextureProps, elementsPerTexel,
- paddedSHComponentCount, shBytesPerElement, fromSplat, toSplat);
- }
- };
- const shComponentCount = shTextureDesc.componentCount;
- const paddedSHComponentCount = shTextureDesc.paddedComponentCount;
- // Update for the case of a single texture for all spherical harmonics data
- if (shTextureDesc.textureCount === 1) {
- const paddedSHArray = shTextureDesc.data;
- for (let c = fromSplat; c <= toSplat; c++) {
- const srcBase = shComponentCount * c;
- const destBase = paddedSHComponentCount * c;
- for (let i = 0; i < shComponentCount; i++) {
- paddedSHArray[destBase + i] = shData[srcBase + i];
- }
- }
- updateTexture(shTextureDesc.texture, shTextureDesc.size,
- shTextureDesc.elementsPerTexel, paddedSHArray, paddedSHComponentCount);
- // Update for the case of spherical harmonics data split among three textures, one for each color channel
- } else {
- const shComponentCountPerChannel = shTextureDesc.componentCountPerChannel;
- for (let t = 0; t < 3; t++) {
- const paddedSHArray = shTextureDesc.data[t];
- for (let c = fromSplat; c <= toSplat; c++) {
- const srcBase = shComponentCount * c;
- const destBase = paddedSHComponentCount * c;
- if (shComponentCountPerChannel >= 3) {
- for (let i = 0; i < 3; i++) paddedSHArray[destBase + i] = shData[srcBase + t * 3 + i];
- if (shComponentCountPerChannel >= 8) {
- for (let i = 0; i < 5; i++) paddedSHArray[destBase + 3 + i] = shData[srcBase + 9 + t * 5 + i];
- }
- }
- }
- updateTexture(shTextureDesc.textures[t], shTextureDesc.size,
- shTextureDesc.elementsPerTexel, paddedSHArray, paddedSHComponentCount);
- }
- }
- }
- // update scene index & transform data
- const sceneIndexesTexDesc = this.splatDataTextures['sceneIndexes'];
- const paddedSceneIndexes = sceneIndexesTexDesc.data;
- for (let c = this.lastBuildSplatCount; c <= toSplat; c++) {
- paddedSceneIndexes[c] = this.globalSplatIndexToSceneIndexMap[c];
- }
- const sceneIndexesTexture = sceneIndexesTexDesc.texture;
- const sceneIndexesTextureProps = this.renderer ? this.renderer.properties.get(sceneIndexesTexture) : null;
- if (!sceneIndexesTextureProps || !sceneIndexesTextureProps.__webglTexture) {
- sceneIndexesTexture.needsUpdate = true;
- } else {
- this.updateDataTexture(paddedSceneIndexes, sceneIndexesTexDesc.texture, sceneIndexesTexDesc.size,
- sceneIndexesTextureProps, 1, 1, 1, this.lastBuildSplatCount, toSplat);
- }
- }
- getTargetCovarianceCompressionLevel() {
- return this.halfPrecisionCovariancesOnGPU ? 1 : 0;
- }
- getTargetSphericalHarmonicsCompressionLevel() {
- return Math.max(1, this.getMaximumSplatBufferCompressionLevel());
- }
- getMaximumSplatBufferCompressionLevel() {
- let maxCompressionLevel;
- for (let i = 0; i < this.scenes.length; i++) {
- const scene = this.getScene(i);
- const splatBuffer = scene.splatBuffer;
- if (i === 0 || splatBuffer.compressionLevel > maxCompressionLevel) {
- maxCompressionLevel = splatBuffer.compressionLevel;
- }
- }
- return maxCompressionLevel;
- }
- getMinimumSplatBufferCompressionLevel() {
- let minCompressionLevel;
- for (let i = 0; i < this.scenes.length; i++) {
- const scene = this.getScene(i);
- const splatBuffer = scene.splatBuffer;
- if (i === 0 || splatBuffer.compressionLevel < minCompressionLevel) {
- minCompressionLevel = splatBuffer.compressionLevel;
- }
- }
- return minCompressionLevel;
- }
- static computeTextureUpdateRegion(startSplat, endSplat, textureWidth, elementsPerTexel, elementsPerSplat) {
- const texelsPerSplat = elementsPerSplat / elementsPerTexel;
- const startSplatTexels = startSplat * texelsPerSplat;
- const startRow = Math.floor(startSplatTexels / textureWidth);
- const startRowElement = startRow * textureWidth * elementsPerTexel;
- const endSplatTexels = endSplat * texelsPerSplat;
- const endRow = Math.floor(endSplatTexels / textureWidth);
- const endRowEndElement = endRow * textureWidth * elementsPerTexel + (textureWidth * elementsPerTexel);
- return {
- 'dataStart': startRowElement,
- 'dataEnd': endRowEndElement,
- 'startRow': startRow,
- 'endRow': endRow
- };
- }
- updateDataTexture(paddedData, texture, textureSize, textureProps, elementsPerTexel, elementsPerSplat, bytesPerElement, from, to) {
- const gl = this.renderer.getContext();
- const updateRegion = SplatMesh.computeTextureUpdateRegion(from, to, textureSize.x, elementsPerTexel, elementsPerSplat);
- const updateElementCount = updateRegion.dataEnd - updateRegion.dataStart;
- const updateDataView = new paddedData.constructor(paddedData.buffer,
- updateRegion.dataStart * bytesPerElement, updateElementCount);
- const updateHeight = updateRegion.endRow - updateRegion.startRow + 1;
- const glType = this.webGLUtils.convert(texture.type);
- const glFormat = this.webGLUtils.convert(texture.format, texture.colorSpace);
- const currentTexture = gl.getParameter(gl.TEXTURE_BINDING_2D);
- gl.bindTexture(gl.TEXTURE_2D, textureProps.__webglTexture);
- gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, updateRegion.startRow,
- textureSize.x, updateHeight, glFormat, glType, updateDataView);
- gl.bindTexture(gl.TEXTURE_2D, currentTexture);
- }
- static updatePaddedCompressedCovariancesTextureData(sourceData, textureData, textureDataStartIndex, fromElement, toElement) {
- let textureDataView = new DataView(textureData.buffer);
- let textureDataIndex = textureDataStartIndex;
- let sequentialCount = 0;
- for (let i = fromElement; i <= toElement; i+=2) {
- textureDataView.setUint16(textureDataIndex * 2, sourceData[i], true);
- textureDataView.setUint16(textureDataIndex * 2 + 2, sourceData[i + 1], true);
- textureDataIndex += 2;
- sequentialCount++;
- if (sequentialCount >= 3) {
- textureDataIndex += 2;
- sequentialCount = 0;
- }
- }
- }
- static updateCenterColorsPaddedData(from, to, centers, colors, paddedCenterColors) {
- for (let c = from; c <= to; c++) {
- const colorsBase = c * 4;
- const centersBase = c * 3;
- const centerColorsBase = c * 4;
- paddedCenterColors[centerColorsBase] = rgbaArrayToInteger(colors, colorsBase);
- paddedCenterColors[centerColorsBase + 1] = uintEncodedFloat(centers[centersBase]);
- paddedCenterColors[centerColorsBase + 2] = uintEncodedFloat(centers[centersBase + 1]);
- paddedCenterColors[centerColorsBase + 3] = uintEncodedFloat(centers[centersBase + 2]);
- }
- }
- static updateScaleRotationsPaddedData(from, to, scales, rotations, paddedScaleRotations) {
- const combinedSize = 6;
- for (let c = from; c <= to; c++) {
- const scaleBase = c * 3;
- const rotationBase = c * 4;
- const scaleRotationsBase = c * combinedSize;
- paddedScaleRotations[scaleRotationsBase] = scales[scaleBase];
- paddedScaleRotations[scaleRotationsBase + 1] = scales[scaleBase + 1];
- paddedScaleRotations[scaleRotationsBase + 2] = scales[scaleBase + 2];
- paddedScaleRotations[scaleRotationsBase + 3] = rotations[rotationBase];
- paddedScaleRotations[scaleRotationsBase + 4] = rotations[rotationBase + 1];
- paddedScaleRotations[scaleRotationsBase + 5] = rotations[rotationBase + 2];
- }
- }
- updateVisibleRegion(sinceLastBuildOnly) {
- const splatCount = this.getSplatCount();
- const tempCenter = new THREE.Vector3();
- if (!sinceLastBuildOnly) {
- const avgCenter = new THREE.Vector3();
- this.scenes.forEach((scene) => {
- avgCenter.add(scene.splatBuffer.sceneCenter);
- });
- avgCenter.multiplyScalar(1.0 / this.scenes.length);
- this.calculatedSceneCenter.copy(avgCenter);
- this.material.uniforms.sceneCenter.value.copy(this.calculatedSceneCenter);
- this.material.uniformsNeedUpdate = true;
- }
- const startSplatFormMaxDistanceCalc = sinceLastBuildOnly ? this.lastBuildSplatCount : 0;
- for (let i = startSplatFormMaxDistanceCalc; i < splatCount; i++) {
- this.getSplatCenter(i, tempCenter, true);
- const distFromCSceneCenter = tempCenter.sub(this.calculatedSceneCenter).length();
- if (distFromCSceneCenter > this.maxSplatDistanceFromSceneCenter) this.maxSplatDistanceFromSceneCenter = distFromCSceneCenter;
- }
- if (this.maxSplatDistanceFromSceneCenter - this.visibleRegionBufferRadius > VISIBLE_REGION_EXPANSION_DELTA) {
- this.visibleRegionBufferRadius = this.maxSplatDistanceFromSceneCenter;
- this.visibleRegionRadius = Math.max(this.visibleRegionBufferRadius - VISIBLE_REGION_EXPANSION_DELTA, 0.0);
- }
- if (this.finalBuild) this.visibleRegionRadius = this.visibleRegionBufferRadius = this.maxSplatDistanceFromSceneCenter;
- this.updateVisibleRegionFadeDistance();
- }
- updateVisibleRegionFadeDistance(sceneRevealMode = SceneRevealMode.Default) {
- const fastFadeRate = SCENE_FADEIN_RATE_FAST;
- const gradualFadeRate = SCENE_FADEIN_RATE_GRADUAL;
- const defaultFadeInRate = this.finalBuild ? fastFadeRate : gradualFadeRate;
- const fadeInRate = sceneRevealMode === SceneRevealMode.Default ? defaultFadeInRate : gradualFadeRate;
- this.visibleRegionFadeStartRadius = (this.visibleRegionRadius - this.visibleRegionFadeStartRadius) *
- fadeInRate + this.visibleRegionFadeStartRadius;
- const fadeInPercentage = (this.visibleRegionBufferRadius > 0) ?
- (this.visibleRegionFadeStartRadius / this.visibleRegionBufferRadius) : 0;
- const fadeInComplete = fadeInPercentage > 0.99;
- const shaderFadeInComplete = (fadeInComplete || sceneRevealMode === SceneRevealMode.Instant) ? 1 : 0;
- this.material.uniforms.visibleRegionFadeStartRadius.value = this.visibleRegionFadeStartRadius;
- this.material.uniforms.visibleRegionRadius.value = this.visibleRegionRadius;
- this.material.uniforms.firstRenderTime.value = this.firstRenderTime;
- this.material.uniforms.currentTime.value = performance.now();
- this.material.uniforms.fadeInComplete.value = shaderFadeInComplete;
- this.material.uniformsNeedUpdate = true;
- this.visibleRegionChanging = !fadeInComplete;
- }
- /**
- * Set the indexes of splats that should be rendered; should be sorted in desired render order.
- * @param {Uint32Array} globalIndexes Sorted index list of splats to be rendered
- * @param {number} renderSplatCount Total number of splats to be rendered. Necessary because we may not want to render
- * every splat.
- */
- updateRenderIndexes(globalIndexes, renderSplatCount) {
- const geometry = this.geometry;
- geometry.attributes.splatIndex.set(globalIndexes);
- geometry.attributes.splatIndex.needsUpdate = true;
- if (renderSplatCount > 0 && this.firstRenderTime === -1) this.firstRenderTime = performance.now();
- geometry.instanceCount = renderSplatCount;
- //console.log('updateRenderIndexes')
- }
- /**
- * Update the transforms for each scene in this splat mesh from their individual components (position,
- * quaternion, and scale)
- */
- updateTransforms() {
- for (let i = 0; i < this.scenes.length; i++) {
- const scene = this.getScene(i);
- scene.updateTransform(this.dynamicMode);
- }
- }
- updateUniforms = function() {
- const viewport = new THREE.Vector2();
- return function(renderDimensions, cameraFocalLengthX, cameraFocalLengthY,
- orthographicMode, orthographicZoom, inverseFocalAdjustment) {
- const splatCount = this.getSplatCount();
- if (splatCount > 0) {
- viewport.set(renderDimensions.x * this.devicePixelRatio,
- renderDimensions.y * this.devicePixelRatio);
- this.material.uniforms.viewport.value.copy(viewport);
- this.material.uniforms.basisViewport.value.set(1.0 / viewport.x, 1.0 / viewport.y);
- this.material.uniforms.focal.value.set(cameraFocalLengthX, cameraFocalLengthY);
- this.material.uniforms.orthographicMode.value = orthographicMode ? 1 : 0;
- this.material.uniforms.orthoZoom.value = orthographicZoom;
- this.material.uniforms.inverseFocalAdjustment.value = inverseFocalAdjustment;
- if (this.dynamicMode) {
- for (let i = 0; i < this.scenes.length; i++) {
- this.material.uniforms.transforms.value[i].copy(this.getScene(i).transform);
- }
- }
- if (this.enableOptionalEffects) {
- for (let i = 0; i < this.scenes.length; i++) {
- this.material.uniforms.sceneOpacity.value[i] = clamp(this.getScene(i).opacity, 0.0, 1.0);
- this.material.uniforms.sceneVisibility.value[i] = this.getScene(i).visible ? 1 : 0;
- this.material.uniformsNeedUpdate = true;
- }
- }
- this.material.uniformsNeedUpdate = true;
- }
- };
- }();
- setSplatScale(splatScale = 1) {
- this.splatScale = splatScale;
- this.material.uniforms.splatScale.value = splatScale;
- this.material.uniformsNeedUpdate = true;
- }
- getSplatScale() {
- return this.splatScale;
- }
- setPointCloudModeEnabled(enabled) {
- this.pointCloudModeEnabled = enabled;
- this.material.uniforms.pointCloudModeEnabled.value = enabled ? 1 : 0;
- this.material.uniformsNeedUpdate = true;
- }
- getPointCloudModeEnabled() {
- return this.pointCloudModeEnabled;
- }
- getSplatDataTextures() {
- return this.splatDataTextures;
- }
- getSplatCount() {
- return SplatMesh.getTotalSplatCountForScenes(this.scenes);
- }
- static getTotalSplatCountForScenes(scenes) {
- let totalSplatCount = 0;
- for (let scene of scenes) {
- if (scene && scene.splatBuffer) totalSplatCount += scene.splatBuffer.getSplatCount();
- }
- return totalSplatCount;
- }
- static getTotalSplatCountForSplatBuffers(splatBuffers) {
- let totalSplatCount = 0;
- for (let splatBuffer of splatBuffers) totalSplatCount += splatBuffer.getSplatCount();
- return totalSplatCount;
- }
- getMaxSplatCount() {
- return SplatMesh.getTotalMaxSplatCountForScenes(this.scenes);
- }
- static getTotalMaxSplatCountForScenes(scenes) {
- let totalSplatCount = 0;
- for (let scene of scenes) {
- if (scene && scene.splatBuffer) totalSplatCount += scene.splatBuffer.getMaxSplatCount();
- }
- return totalSplatCount;
- }
- static getTotalMaxSplatCountForSplatBuffers(splatBuffers) {
- let totalSplatCount = 0;
- for (let splatBuffer of splatBuffers) totalSplatCount += splatBuffer.getMaxSplatCount();
- return totalSplatCount;
- }
- disposeDistancesComputationGPUResources() {
- if (!this.renderer) return;
- const gl = this.renderer.getContext();
- if (this.distancesTransformFeedback.vao) {
- gl.deleteVertexArray(this.distancesTransformFeedback.vao);
- this.distancesTransformFeedback.vao = null;
- }
- if (this.distancesTransformFeedback.program) {
- gl.deleteProgram(this.distancesTransformFeedback.program);
- gl.deleteShader(this.distancesTransformFeedback.vertexShader);
- gl.deleteShader(this.distancesTransformFeedback.fragmentShader);
- this.distancesTransformFeedback.program = null;
- this.distancesTransformFeedback.vertexShader = null;
- this.distancesTransformFeedback.fragmentShader = null;
- }
- this.disposeDistancesComputationGPUBufferResources();
- if (this.distancesTransformFeedback.id) {
- gl.deleteTransformFeedback(this.distancesTransformFeedback.id);
- this.distancesTransformFeedback.id = null;
- }
- }
- disposeDistancesComputationGPUBufferResources() {
- if (!this.renderer) return;
- const gl = this.renderer.getContext();
- if (this.distancesTransformFeedback.centersBuffer) {
- this.distancesTransformFeedback.centersBuffer = null;
- gl.deleteBuffer(this.distancesTransformFeedback.centersBuffer);
- }
- if (this.distancesTransformFeedback.outDistancesBuffer) {
- gl.deleteBuffer(this.distancesTransformFeedback.outDistancesBuffer);
- this.distancesTransformFeedback.outDistancesBuffer = null;
- }
- }
- /**
- * Set the Three.js renderer used by this splat mesh
- * @param {THREE.WebGLRenderer} renderer Instance of THREE.WebGLRenderer
- */
- setRenderer(renderer) {
- if (renderer !== this.renderer) {
- this.renderer = renderer;
- const gl = this.renderer.getContext();
- const extensions = new WebGLExtensions(gl);
- const capabilities = new WebGLCapabilities(gl, extensions, {});
- extensions.init(capabilities);
- this.webGLUtils = new THREE.WebGLUtils(gl, extensions, capabilities);
- if (this.enableDistancesComputationOnGPU && this.getSplatCount() > 0) {
- this.setupDistancesComputationTransformFeedback();
- const { centers, sceneIndexes } = this.getDataForDistancesComputation(0, this.getSplatCount() - 1);
- this.refreshGPUBuffersForDistancesComputation(centers, sceneIndexes);
- }
- }
- }
- setupDistancesComputationTransformFeedback = function() {
- let currentMaxSplatCount;
- return function() {
- const maxSplatCount = this.getMaxSplatCount();
- if (!this.renderer) return;
- const rebuildGPUObjects = (this.lastRenderer !== this.renderer);
- const rebuildBuffers = currentMaxSplatCount !== maxSplatCount;
- if (!rebuildGPUObjects && !rebuildBuffers) return;
- if (rebuildGPUObjects) {
- this.disposeDistancesComputationGPUResources();
- } else if (rebuildBuffers) {
- this.disposeDistancesComputationGPUBufferResources();
- }
- const gl = this.renderer.getContext();
- const createShader = (gl, type, source) => {
- const shader = gl.createShader(type);
- if (!shader) {
- console.error('Fatal error: gl could not create a shader object.');
- return null;
- }
- gl.shaderSource(shader, source);
- gl.compileShader(shader);
- const compiled = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
- if (!compiled) {
- let typeName = 'unknown';
- if (type === gl.VERTEX_SHADER) typeName = 'vertex shader';
- else if (type === gl.FRAGMENT_SHADER) typeName = 'fragement shader';
- const errors = gl.getShaderInfoLog(shader);
- console.error('Failed to compile ' + typeName + ' with these errors:' + errors);
- gl.deleteShader(shader);
- return null;
- }
- return shader;
- };
- let vsSource;
- if (this.integerBasedDistancesComputation) {
- vsSource =
- `#version 300 es
- in ivec4 center;
- flat out int distance;`;
- if (this.dynamicMode) {
- vsSource += `
- in uint sceneIndex;
- uniform ivec4 transforms[${Constants.MaxScenes}];
- void main(void) {
- ivec4 transform = transforms[sceneIndex];
- distance = center.x * transform.x + center.y * transform.y + center.z * transform.z + transform.w * center.w;
- }
- `;
- } else {
- vsSource += `
- uniform ivec3 modelViewProj;
- void main(void) {
- distance = center.x * modelViewProj.x + center.y * modelViewProj.y + center.z * modelViewProj.z;
- }
- `;
- }
- } else {
- vsSource =
- `#version 300 es
- in vec4 center;
- flat out float distance;`;
- if (this.dynamicMode) {
- vsSource += `
- in uint sceneIndex;
- uniform mat4 transforms[${Constants.MaxScenes}];
- void main(void) {
- vec4 transformedCenter = transforms[sceneIndex] * vec4(center.xyz, 1.0);
- distance = transformedCenter.z;
- }
- `;
- } else {
- vsSource += `
- uniform vec3 modelViewProj;
- void main(void) {
- distance = center.x * modelViewProj.x + center.y * modelViewProj.y + center.z * modelViewProj.z;
- }
- `;
- }
- }
- const fsSource =
- `#version 300 es
- precision lowp float;
- out vec4 fragColor;
- void main(){}
- `;
- const currentVao = gl.getParameter(gl.VERTEX_ARRAY_BINDING);
- const currentProgram = gl.getParameter(gl.CURRENT_PROGRAM);
- const currentProgramDeleted = currentProgram ? gl.getProgramParameter(currentProgram, gl.DELETE_STATUS) : false;
- if (rebuildGPUObjects) {
- this.distancesTransformFeedback.vao = gl.createVertexArray();
- }
- gl.bindVertexArray(this.distancesTransformFeedback.vao);
- if (rebuildGPUObjects) {
- const program = gl.createProgram();
- const vertexShader = createShader(gl, gl.VERTEX_SHADER, vsSource);
- const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fsSource);
- if (!vertexShader || !fragmentShader) {
- throw new Error('Could not compile shaders for distances computation on GPU.');
- }
- gl.attachShader(program, vertexShader);
- gl.attachShader(program, fragmentShader);
- gl.transformFeedbackVaryings(program, ['distance'], gl.SEPARATE_ATTRIBS);
- gl.linkProgram(program);
- const linked = gl.getProgramParameter(program, gl.LINK_STATUS);
- if (!linked) {
- const error = gl.getProgramInfoLog(program);
- console.error('Fatal error: Failed to link program: ' + error);
- gl.deleteProgram(program);
- gl.deleteShader(fragmentShader);
- gl.deleteShader(vertexShader);
- throw new Error('Could not link shaders for distances computation on GPU.');
- }
- this.distancesTransformFeedback.program = program;
- this.distancesTransformFeedback.vertexShader = vertexShader;
- this.distancesTransformFeedback.vertexShader = fragmentShader;
- }
- gl.useProgram(this.distancesTransformFeedback.program);
- this.distancesTransformFeedback.centersLoc =
- gl.getAttribLocation(this.distancesTransformFeedback.program, 'center');
- if (this.dynamicMode) {
- this.distancesTransformFeedback.sceneIndexesLoc =
- gl.getAttribLocation(this.distancesTransformFeedback.program, 'sceneIndex');
- for (let i = 0; i < this.scenes.length; i++) {
- this.distancesTransformFeedback.transformsLocs[i] =
- gl.getUniformLocation(this.distancesTransformFeedback.program, `transforms[${i}]`);
- }
- } else {
- this.distancesTransformFeedback.modelViewProjLoc =
- gl.getUniformLocation(this.distancesTransformFeedback.program, 'modelViewProj');
- }
- if (rebuildGPUObjects || rebuildBuffers) {
- this.distancesTransformFeedback.centersBuffer = gl.createBuffer();
- gl.bindBuffer(gl.ARRAY_BUFFER, this.distancesTransformFeedback.centersBuffer);
- gl.enableVertexAttribArray(this.distancesTransformFeedback.centersLoc);
- if (this.integerBasedDistancesComputation) {
- gl.vertexAttribIPointer(this.distancesTransformFeedback.centersLoc, 4, gl.INT, 0, 0);
- } else {
- gl.vertexAttribPointer(this.distancesTransformFeedback.centersLoc, 4, gl.FLOAT, false, 0, 0);
- }
- if (this.dynamicMode) {
- this.distancesTransformFeedback.sceneIndexesBuffer = gl.createBuffer();
- gl.bindBuffer(gl.ARRAY_BUFFER, this.distancesTransformFeedback.sceneIndexesBuffer);
- gl.enableVertexAttribArray(this.distancesTransformFeedback.sceneIndexesLoc);
- gl.vertexAttribIPointer(this.distancesTransformFeedback.sceneIndexesLoc, 1, gl.UNSIGNED_INT, 0, 0);
- }
- }
- if (rebuildGPUObjects || rebuildBuffers) {
- this.distancesTransformFeedback.outDistancesBuffer = gl.createBuffer();
- }
- gl.bindBuffer(gl.ARRAY_BUFFER, this.distancesTransformFeedback.outDistancesBuffer);
- gl.bufferData(gl.ARRAY_BUFFER, maxSplatCount * 4, gl.STATIC_READ);
- if (rebuildGPUObjects) {
- this.distancesTransformFeedback.id = gl.createTransformFeedback();
- }
- gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, this.distancesTransformFeedback.id);
- gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, 0, this.distancesTransformFeedback.outDistancesBuffer);
- if (currentProgram && currentProgramDeleted !== true) gl.useProgram(currentProgram);
- if (currentVao) gl.bindVertexArray(currentVao);
- this.lastRenderer = this.renderer;
- currentMaxSplatCount = maxSplatCount;
- };
- }();
- /**
- * Refresh GPU buffers used for computing splat distances with centers data from the scenes for this mesh.
- * @param {boolean} isUpdate Specify whether or not to update the GPU buffer or to initialize & fill
- * @param {Array<number>} centers The splat centers data
- * @param {number} offsetSplats Offset in the GPU buffer at which to start updating data, specified in splats
- */
- updateGPUCentersBufferForDistancesComputation(isUpdate, centers, offsetSplats) {
- if (!this.renderer) return;
- const gl = this.renderer.getContext();
- const currentVao = gl.getParameter(gl.VERTEX_ARRAY_BINDING);
- gl.bindVertexArray(this.distancesTransformFeedback.vao);
- const ArrayType = this.integerBasedDistancesComputation ? Uint32Array : Float32Array;
- const attributeBytesPerCenter = 16;
- const subBufferOffset = offsetSplats * attributeBytesPerCenter;
- gl.bindBuffer(gl.ARRAY_BUFFER, this.distancesTransformFeedback.centersBuffer);
- if (isUpdate) {
- gl.bufferSubData(gl.ARRAY_BUFFER, subBufferOffset, centers);
- } else {
- const maxArray = new ArrayType(this.getMaxSplatCount() * attributeBytesPerCenter);
- maxArray.set(centers);
- gl.bufferData(gl.ARRAY_BUFFER, maxArray, gl.STATIC_DRAW);
- }
- gl.bindBuffer(gl.ARRAY_BUFFER, null);
- if (currentVao) gl.bindVertexArray(currentVao);
- }
- /**
- * Refresh GPU buffers used for pre-computing splat distances with centers data from the scenes for this mesh.
- * @param {boolean} isUpdate Specify whether or not to update the GPU buffer or to initialize & fill
- * @param {Array<number>} sceneIndexes The splat scene indexes
- * @param {number} offsetSplats Offset in the GPU buffer at which to start updating data, specified in splats
- */
- updateGPUTransformIndexesBufferForDistancesComputation(isUpdate, sceneIndexes, offsetSplats) {
- if (!this.renderer || !this.dynamicMode) return;
- const gl = this.renderer.getContext();
- const currentVao = gl.getParameter(gl.VERTEX_ARRAY_BINDING);
- gl.bindVertexArray(this.distancesTransformFeedback.vao);
- const subBufferOffset = offsetSplats * 4;
- gl.bindBuffer(gl.ARRAY_BUFFER, this.distancesTransformFeedback.sceneIndexesBuffer);
- if (isUpdate) {
- gl.bufferSubData(gl.ARRAY_BUFFER, subBufferOffset, sceneIndexes);
- } else {
- const maxArray = new Uint32Array(this.getMaxSplatCount() * 4);
- maxArray.set(sceneIndexes);
- gl.bufferData(gl.ARRAY_BUFFER, maxArray, gl.STATIC_DRAW);
- }
- gl.bindBuffer(gl.ARRAY_BUFFER, null);
- if (currentVao) gl.bindVertexArray(currentVao);
- }
- /**
- * Get a typed array containing a mapping from global splat indexes to their scene index.
- * @param {number} start Starting splat index to store
- * @param {number} end Ending splat index to store
- * @return {Uint32Array}
- */
- getSceneIndexes(start, end) {
- let sceneIndexes;
- const fillCount = end - start + 1;
- sceneIndexes = new Uint32Array(fillCount);
- for (let i = start; i <= end; i++) {
- sceneIndexes[i] = this.globalSplatIndexToSceneIndexMap[i];
- }
- return sceneIndexes;
- }
- /**
- * Fill 'array' with the transforms for each scene in this splat mesh.
- * @param {Array} array Empty array to be filled with scene transforms. If not empty, contents will be overwritten.
- */
- fillTransformsArray = function() {
- const tempArray = [];
- return function(array) {
- if (tempArray.length !== array.length) tempArray.length = array.length;
- for (let i = 0; i < this.scenes.length; i++) {
- const sceneTransform = this.getScene(i).transform;
- const sceneTransformElements = sceneTransform.elements;
- for (let j = 0; j < 16; j++) {
- tempArray[i * 16 + j] = sceneTransformElements[j];
- }
- }
- array.set(tempArray);
- };
- }();
- computeDistancesOnGPU = function() {
- const tempMatrix = new THREE.Matrix4();
- return function(modelViewProjMatrix, outComputedDistances) {
- if (!this.renderer) return;
- // console.time("gpu_compute_distances");
- const gl = this.renderer.getContext();
- const currentVao = gl.getParameter(gl.VERTEX_ARRAY_BINDING);
- const currentProgram = gl.getParameter(gl.CURRENT_PROGRAM);
- const currentProgramDeleted = currentProgram ? gl.getProgramParameter(currentProgram, gl.DELETE_STATUS) : false;
- gl.bindVertexArray(this.distancesTransformFeedback.vao);
- gl.useProgram(this.distancesTransformFeedback.program);
- gl.enable(gl.RASTERIZER_DISCARD);
- if (this.dynamicMode) {
- for (let i = 0; i < this.scenes.length; i++) {
- tempMatrix.copy(this.getScene(i).transform);
- tempMatrix.premultiply(modelViewProjMatrix);
- if (this.integerBasedDistancesComputation) {
- const iTempMatrix = SplatMesh.getIntegerMatrixArray(tempMatrix);
- const iTransform = [iTempMatrix[2], iTempMatrix[6], iTempMatrix[10], iTempMatrix[14]];
- gl.uniform4i(this.distancesTransformFeedback.transformsLocs[i], iTransform[0], iTransform[1],
- iTransform[2], iTransform[3]);
- } else {
- gl.uniformMatrix4fv(this.distancesTransformFeedback.transformsLocs[i], false, tempMatrix.elements);
- }
- }
- } else {
- if (this.integerBasedDistancesComputation) {
- const iViewProjMatrix = SplatMesh.getIntegerMatrixArray(modelViewProjMatrix);
- const iViewProj = [iViewProjMatrix[2], iViewProjMatrix[6], iViewProjMatrix[10]];
- gl.uniform3i(this.distancesTransformFeedback.modelViewProjLoc, iViewProj[0], iViewProj[1], iViewProj[2]);
- } else {
- const viewProj = [modelViewProjMatrix.elements[2], modelViewProjMatrix.elements[6], modelViewProjMatrix.elements[10]];
- gl.uniform3f(this.distancesTransformFeedback.modelViewProjLoc, viewProj[0], viewProj[1], viewProj[2]);
- }
- }
- gl.bindBuffer(gl.ARRAY_BUFFER, this.distancesTransformFeedback.centersBuffer);
- gl.enableVertexAttribArray(this.distancesTransformFeedback.centersLoc);
- if (this.integerBasedDistancesComputation) {
- gl.vertexAttribIPointer(this.distancesTransformFeedback.centersLoc, 4, gl.INT, 0, 0);
- } else {
- gl.vertexAttribPointer(this.distancesTransformFeedback.centersLoc, 4, gl.FLOAT, false, 0, 0);
- }
- if (this.dynamicMode) {
- gl.bindBuffer(gl.ARRAY_BUFFER, this.distancesTransformFeedback.sceneIndexesBuffer);
- gl.enableVertexAttribArray(this.distancesTransformFeedback.sceneIndexesLoc);
- gl.vertexAttribIPointer(this.distancesTransformFeedback.sceneIndexesLoc, 1, gl.UNSIGNED_INT, 0, 0);
- }
- gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, this.distancesTransformFeedback.id);
- gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, 0, this.distancesTransformFeedback.outDistancesBuffer);
- gl.beginTransformFeedback(gl.POINTS);
- gl.drawArrays(gl.POINTS, 0, this.getSplatCount());
- gl.endTransformFeedback();
- gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, 0, null);
- gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, null);
- gl.disable(gl.RASTERIZER_DISCARD);
- const sync = gl.fenceSync(gl.SYNC_GPU_COMMANDS_COMPLETE, 0);
- gl.flush();
- const promise = new Promise((resolve) => {
- const checkSync = () => {
- if (this.disposed) {
- resolve();
- } else {
- const timeout = 0;
- const bitflags = 0;
- const status = gl.clientWaitSync(sync, bitflags, timeout);
- switch (status) {
- case gl.TIMEOUT_EXPIRED:
- this.computeDistancesOnGPUSyncTimeout = setTimeout(checkSync);
- return this.computeDistancesOnGPUSyncTimeout;
- case gl.WAIT_FAILED:
- throw new Error('should never get here');
- default:
- this.computeDistancesOnGPUSyncTimeout = null;
- gl.deleteSync(sync);
- const currentVao = gl.getParameter(gl.VERTEX_ARRAY_BINDING);
- gl.bindVertexArray(this.distancesTransformFeedback.vao);
- gl.bindBuffer(gl.ARRAY_BUFFER, this.distancesTransformFeedback.outDistancesBuffer);
- gl.getBufferSubData(gl.ARRAY_BUFFER, 0, outComputedDistances);
- gl.bindBuffer(gl.ARRAY_BUFFER, null);
- if (currentVao) gl.bindVertexArray(currentVao);
- // console.timeEnd("gpu_compute_distances");
- resolve();
- }
- }
- };
- this.computeDistancesOnGPUSyncTimeout = setTimeout(checkSync);
- });
- if (currentProgram && currentProgramDeleted !== true) gl.useProgram(currentProgram);
- if (currentVao) gl.bindVertexArray(currentVao);
- return promise;
- };
- }();
- /**
- * Given a global splat index, return corresponding local data (splat buffer, index of splat in that splat
- * buffer, and the corresponding transform)
- * @param {number} globalIndex Global splat index
- * @param {object} paramsObj Object in which to store local data
- * @param {boolean} returnSceneTransform By default, the transform of the scene to which the splat at 'globalIndex' belongs will be
- * returned via the 'sceneTransform' property of 'paramsObj' only if the splat mesh is static.
- * If 'returnSceneTransform' is true, the 'sceneTransform' property will always contain the scene
- * transform, and if 'returnSceneTransform' is false, the 'sceneTransform' property will always
- * be null.
- */
- getLocalSplatParameters(globalIndex, paramsObj, returnSceneTransform) {
- if (returnSceneTransform === undefined || returnSceneTransform === null) {
- returnSceneTransform = this.dynamicMode ? false : true;
- }
- paramsObj.splatBuffer = this.getSplatBufferForSplat(globalIndex);
- paramsObj.localIndex = this.getSplatLocalIndex(globalIndex);
- paramsObj.sceneTransform = returnSceneTransform ? this.getSceneTransformForSplat(globalIndex) : null;
- }
- /**
- * Fill arrays with splat data and apply transforms if appropriate. Each array is optional.
- * @param {Float32Array} covariances Target storage for splat covariances
- * @param {Float32Array} scales Target storage for splat scales
- * @param {Float32Array} rotations Target storage for splat rotations
- * @param {Float32Array} centers Target storage for splat centers
- * @param {Uint8Array} colors Target storage for splat colors
- * @param {Float32Array} sphericalHarmonics Target storage for spherical harmonics
- * @param {boolean} applySceneTransform By default, scene transforms are applied to relevant splat data only if the splat mesh is
- * static. If 'applySceneTransform' is true, scene transforms will always be applied and if
- * it is false, they will never be applied. If undefined, the default behavior will apply.
- * @param {number} covarianceCompressionLevel The compression level for covariances in the destination array
- * @param {number} sphericalHarmonicsCompressionLevel The compression level for spherical harmonics in the destination array
- * @param {number} srcStart The start location from which to pull source data
- * @param {number} srcEnd The end location from which to pull source data
- * @param {number} destStart The start location from which to write data
- */
- fillSplatDataArrays(covariances, scales, rotations, centers, colors, sphericalHarmonics, applySceneTransform,
- covarianceCompressionLevel = 0, scaleRotationCompressionLevel = 0, sphericalHarmonicsCompressionLevel = 1,
- srcStart, srcEnd, destStart = 0) {
- const scaleOverride = new THREE.Vector3();
- scaleOverride.x = undefined;
- scaleOverride.y = undefined;
- if (this.splatRenderMode === SplatRenderMode.ThreeD) {
- scaleOverride.z = undefined;
- } else {
- scaleOverride.z = 1;
- }
- const tempTransform = new THREE.Matrix4();
- for (let i = 0; i < this.scenes.length; i++) {
- if (applySceneTransform === undefined || applySceneTransform === null) {
- applySceneTransform = this.dynamicMode ? false : true;
- }
- const scene = this.getScene(i);
- const splatBuffer = scene.splatBuffer;
- let sceneTransform;
- if (applySceneTransform) {
- this.getSceneTransform(i, tempTransform);
- sceneTransform = tempTransform;
- }
- if (covariances) {
- splatBuffer.fillSplatCovarianceArray(covariances, sceneTransform, srcStart, srcEnd, destStart, covarianceCompressionLevel);
- }
- if (scales || rotations) {
- if (!scales || !rotations) {
- throw new Error('SplatMesh::fillSplatDataArrays() -> "scales" and "rotations" must both be valid.');
- }
- splatBuffer.fillSplatScaleRotationArray(scales, rotations, sceneTransform,
- srcStart, srcEnd, destStart, scaleRotationCompressionLevel, scaleOverride);
- }
- if (centers) splatBuffer.fillSplatCenterArray(centers, sceneTransform, srcStart, srcEnd, destStart);
- if (colors) splatBuffer.fillSplatColorArray(colors, scene.minimumAlpha, srcStart, srcEnd, destStart);
- if (sphericalHarmonics) {
- splatBuffer.fillSphericalHarmonicsArray(sphericalHarmonics, this.minSphericalHarmonicsDegree,
- sceneTransform, srcStart, srcEnd, destStart, sphericalHarmonicsCompressionLevel);
- }
- destStart += splatBuffer.getSplatCount();
- }
- }
- /**
- * Convert splat centers, which are floating point values, to an array of integers and multiply
- * each by 1000. Centers will get transformed as appropriate before conversion to integer.
- * @param {number} start The index at which to start retrieving data
- * @param {number} end The index at which to stop retrieving data
- * @param {boolean} padFour Enforce alignment of 4 by inserting a 1 after every 3 values
- * @return {Int32Array}
- */
- getIntegerCenters(start, end, padFour = false) {
- const splatCount = end - start + 1;
- const floatCenters = new Float32Array(splatCount * 3);
- this.fillSplatDataArrays(null, null, null, floatCenters, null, null, undefined, undefined, undefined, undefined, start);
- let intCenters;
- let componentCount = padFour ? 4 : 3;
- intCenters = new Int32Array(splatCount * componentCount);
- for (let i = 0; i < splatCount; i++) {
- for (let t = 0; t < 3; t++) {
- intCenters[i * componentCount + t] = Math.round(floatCenters[i * 3 + t] * 1000.0);
- }
- if (padFour) intCenters[i * componentCount + 3] = 1000;
- }
- return intCenters;
- }
- /**
- * Returns an array of splat centers, transformed as appropriate, optionally padded.
- * @param {number} start The index at which to start retrieving data
- * @param {number} end The index at which to stop retrieving data
- * @param {boolean} padFour Enforce alignment of 4 by inserting a 1 after every 3 values
- * @return {Float32Array}
- */
- getFloatCenters(start, end, padFour = false) {
- const splatCount = end - start + 1;
- const floatCenters = new Float32Array(splatCount * 3);
- this.fillSplatDataArrays(null, null, null, floatCenters, null, null, undefined, undefined, undefined, undefined, start);
- if (!padFour) return floatCenters;
- let paddedFloatCenters = new Float32Array(splatCount * 4);
- for (let i = 0; i < splatCount; i++) {
- for (let t = 0; t < 3; t++) {
- paddedFloatCenters[i * 4 + t] = floatCenters[i * 3 + t];
- }
- paddedFloatCenters[i * 4 + 3] = 1.0;
- }
- return paddedFloatCenters;
- }
- /**
- * Get the center for a splat, transformed as appropriate.
- * @param {number} globalIndex Global index of splat
- * @param {THREE.Vector3} outCenter THREE.Vector3 instance in which to store splat center
- * @param {boolean} applySceneTransform By default, if the splat mesh is static, the transform of the scene to which the splat at
- * 'globalIndex' belongs will be applied to the splat center. If 'applySceneTransform' is true,
- * the scene transform will always be applied and if 'applySceneTransform' is false, the
- * scene transform will never be applied. If undefined, the default behavior will apply.
- */
- getSplatCenter = function() {
- const paramsObj = {};
- return function(globalIndex, outCenter, applySceneTransform) {
- this.getLocalSplatParameters(globalIndex, paramsObj, applySceneTransform);
- paramsObj.splatBuffer.getSplatCenter(paramsObj.localIndex, outCenter, paramsObj.sceneTransform);
- };
- }();
- /**
- * Get the scale and rotation for a splat, transformed as appropriate.
- * @param {number} globalIndex Global index of splat
- * @param {THREE.Vector3} outScale THREE.Vector3 instance in which to store splat scale
- * @param {THREE.Quaternion} outRotation THREE.Quaternion instance in which to store splat rotation
- * @param {boolean} applySceneTransform By default, if the splat mesh is static, the transform of the scene to which the splat at
- * 'globalIndex' belongs will be applied to the splat scale and rotation. If
- * 'applySceneTransform' is true, the scene transform will always be applied and if
- * 'applySceneTransform' is false, the scene transform will never be applied. If undefined,
- * the default behavior will apply.
- */
- getSplatScaleAndRotation = function() {
- const paramsObj = {};
- const scaleOverride = new THREE.Vector3();
- return function(globalIndex, outScale, outRotation, applySceneTransform) {
- this.getLocalSplatParameters(globalIndex, paramsObj, applySceneTransform);
- scaleOverride.x = undefined;
- scaleOverride.y = undefined;
- scaleOverride.z = undefined;
- if (this.splatRenderMode === SplatRenderMode.TwoD) scaleOverride.z = 0;
- paramsObj.splatBuffer.getSplatScaleAndRotation(paramsObj.localIndex, outScale, outRotation,
- paramsObj.sceneTransform, scaleOverride);
- };
- }();
- /**
- * Get the color for a splat.
- * @param {number} globalIndex Global index of splat
- * @param {THREE.Vector4} outColor THREE.Vector4 instance in which to store splat color
- */
- getSplatColor = function() {
- const paramsObj = {};
- return function(globalIndex, outColor) {
- this.getLocalSplatParameters(globalIndex, paramsObj);
- paramsObj.splatBuffer.getSplatColor(paramsObj.localIndex, outColor);
- };
- }();
- /**
- * Store the transform of the scene at 'sceneIndex' in 'outTransform'.
- * @param {number} sceneIndex Index of the desired scene
- * @param {THREE.Matrix4} outTransform Instance of THREE.Matrix4 in which to store the scene's transform
- */
- getSceneTransform(sceneIndex, outTransform) {
- const scene = this.getScene(sceneIndex);
- scene.updateTransform(this.dynamicMode);
- outTransform.copy(scene.transform);
- }
- /**
- * Get the scene at 'sceneIndex'.
- * @param {number} sceneIndex Index of the desired scene
- * @return {SplatScene}
- */
- getScene(sceneIndex) {
- if (sceneIndex < 0 || sceneIndex >= this.scenes.length) {
- throw new Error('SplatMesh::getScene() -> Invalid scene index.');
- }
- return this.scenes[sceneIndex];
- }
- getSceneCount() {
- return this.scenes.length;
- }
- getSplatBufferForSplat(globalIndex) {
- return this.getScene(this.globalSplatIndexToSceneIndexMap[globalIndex]).splatBuffer;
- }
- getSceneIndexForSplat(globalIndex) {
- return this.globalSplatIndexToSceneIndexMap[globalIndex];
- }
- getSceneTransformForSplat(globalIndex) {
- return this.getScene(this.globalSplatIndexToSceneIndexMap[globalIndex]).transform;
- }
- getSplatLocalIndex(globalIndex) {
- return this.globalSplatIndexToLocalSplatIndexMap[globalIndex];
- }
- static getIntegerMatrixArray(matrix) {
- const matrixElements = matrix.elements;
- const intMatrixArray = [];
- for (let i = 0; i < 16; i++) {
- intMatrixArray[i] = Math.round(matrixElements[i] * 1000.0);
- }
- return intMatrixArray;
- }
- }
- var SorterWasm = "AGFzbQEAAAAADwhkeWxpbmsuMAEEAAAAAAEbA2AAAGAQf39/f39/f39/f39/f39/fwBgAAF/AhIBA2VudgZtZW1vcnkCAwCAgAQDBAMAAQIHVAQRX193YXNtX2NhbGxfY3RvcnMAABhfX3dhc21fYXBwbHlfZGF0YV9yZWxvY3MAAAtzb3J0SW5kZXhlcwABE2Vtc2NyaXB0ZW5fdGxzX2luaXQAAgqWEAMDAAELihAEAXwDewN/A30gCyAKayEMAkACQCAOBEAgDQRAQfj///8HIQpBiICAgHghDSALIAxNDQMgDCEBA0AgAyABQQJ0IgVqIAIgACAFaigCAEECdGooAgAiBTYCACAFIAogBSAKSBshCiAFIA0gBSANShshDSABQQFqIgEgC0cNAAsMAwsgDwRAIAsgDE0NAkF/IQ9B+P///wchCkGIgICAeCENIAwhAgNAIA8gByAAIAJBAnQiFWooAgAiFkECdGooAgAiFEcEQAJ/IAX9CQI4IAggFEEGdGoiDv0JAgwgDioCHP0gASAOKgIs/SACIA4qAjz9IAP95gEgBf0JAiggDv0JAgggDioCGP0gASAOKgIo/SACIA4qAjj9IAP95gEgBf0JAgggDv0JAgAgDioCEP0gASAOKgIg/SACIA4qAjD9IAP95gEgBf0JAhggDv0JAgQgDioCFP0gASAOKgIk/SACIA4qAjT9IAP95gH95AH95AH95AEiEf1f/QwAAAAAAECPQAAAAAAAQI9AIhL98gEiE/0hASIQmUQAAAAAAADgQWMEQCAQqgwBC0GAgICAeAshDgJ/IBP9IQAiEJlEAAAAAAAA4EFjBEAgEKoMAQtBgICAgHgL/REgDv0cAQJ/IBEgEf0NCAkKCwwNDg8AAAAAAAAAAP1fIBL98gEiEf0hACIQmUQAAAAAAADgQWMEQCAQqgwBC0GAgICAeAv9HAICfyAR/SEBIhCZRAAAAAAAAOBBYwRAIBCqDAELQYCAgIB4C/0cAyESIBQhDwsgAyAVaiABIBZBBHRq/QAAACAS/bUBIhH9GwAgEf0bAWogEf0bAmogEf0bA2oiDjYCACAOIAogCiAOShshCiAOIA0gDSAOSBshDSACQQFqIgIgC0cNAAsMAwsCfyAFKgIIu/0UIAUqAhi7/SIB/QwAAAAAAECPQAAAAAAAQI9A/fIBIhH9IQEiEJlEAAAAAAAA4EFjBEAgEKoMAQtBgICAgHgLIQ4CfyAR/SEAIhCZRAAAAAAAAOBBYwRAIBCqDAELQYCAgIB4CyECAn8gBSoCKLtEAAAAAABAj0CiIhCZRAAAAAAAAOBBYwRAIBCqDAELQYCAgIB4CyEFQfj///8HIQpBiICAgHghDSALIAxNDQIgAv0RIA79HAEgBf0cAiESIAwhBQNAIAMgBUECdCICaiABIAAgAmooAgBBBHRq/QAAACAS/bUBIhH9GwAgEf0bAWogEf0bAmoiAjYCACACIAogAiAKSBshCiACIA0gAiANShshDSAFQQFqIgUgC0cNAAsMAgsgDQRAQfj///8HIQpBiICAgHghDSALIAxNDQIgDCEBA0AgAyABQQJ0IgVqAn8gAiAAIAVqKAIAQQJ0aioCALtEAAAAAAAAsECiIhCZRAAAAAAAAOBBYwRAIBCqDAELQYCAgIB4CyIONgIAIAogDiAKIA5IGyEKIA0gDiANIA5KGyENIAFBAWoiASALRw0ACwwCCyAPRQRAIAsgDE0NASAFKgIoIRcgBSoCGCEYIAUqAgghGUH4////ByEKQYiAgIB4IQ0gDCEFA0ACfyAXIAEgACAFQQJ0IgdqKAIAQQR0aiICKgIIlCAZIAIqAgCUIBggAioCBJSSkrtEAAAAAAAAsECiIhCZRAAAAAAAAOBBYwRAIBCqDAELQYCAgIB4CyEOIAMgB2ogDjYCACAKIA4gCiAOSBshCiANIA4gDSAOShshDSAFQQFqIgUgC0cNAAsMAgsgCyAMTQ0AQX8hD0H4////ByEKQYiAgIB4IQ0gDCECA0AgDyAHIAAgAkECdCIUaigCAEECdCIVaigCACIORwRAIAX9CQI4IAggDkEGdGoiD/0JAgwgDyoCHP0gASAPKgIs/SACIA8qAjz9IAP95gEgBf0JAiggD/0JAgggDyoCGP0gASAPKgIo/SACIA8qAjj9IAP95gEgBf0JAgggD/0JAgAgDyoCEP0gASAPKgIg/SACIA8qAjD9IAP95gEgBf0JAhggD/0JAgQgDyoCFP0gASAPKgIk/SACIA8qAjT9IAP95gH95AH95AH95AEhESAOIQ8LIAMgFGoCfyAR/R8DIAEgFUECdCIOQQxyaioCAJQgEf0fAiABIA5BCHJqKgIAlCAR/R8AIAEgDmoqAgCUIBH9HwEgASAOQQRyaioCAJSSkpK7RAAAAAAAALBAoiIQmUQAAAAAAADgQWMEQCAQqgwBC0GAgICAeAsiDjYCACAKIA4gCiAOSBshCiANIA4gDSAOShshDSACQQFqIgIgC0cNAAsMAQtBiICAgHghDUH4////ByEKCyALIAxLBEAgCUEBa7MgDbIgCrKTlSEXIAwhDQNAAn8gFyADIA1BAnRqIgEoAgAgCmuylCIYi0MAAABPXQRAIBioDAELQYCAgIB4CyEOIAEgDjYCACAEIA5BAnRqIgEgASgCAEEBajYCACANQQFqIg0gC0cNAAsLIAlBAk8EQCAEKAIAIQ1BASEKA0AgBCAKQQJ0aiIBIAEoAgAgDWoiDTYCACAKQQFqIgogCUcNAAsLIAxBAEoEQCAMIQoDQCAGIApBAWsiAUECdCICaiAAIAJqKAIANgIAIApBAUshAiABIQogAg0ACwsgCyAMSgRAIAshCgNAIAYgCyAEIAMgCkEBayIKQQJ0IgFqKAIAQQJ0aiICKAIAIgVrQQJ0aiAAIAFqKAIANgIAIAIgBUEBazYCACAKIAxKDQALCwsEAEEACw==";
- var SorterWasmNoSIMD = "AGFzbQEAAAAADwhkeWxpbmsuMAEEAAAAAAEXAmAAAGAQf39/f39/f39/f39/f39/fwACEgEDZW52Bm1lbW9yeQIDAICABAMDAgABBz4DEV9fd2FzbV9jYWxsX2N0b3JzAAAYX193YXNtX2FwcGx5X2RhdGFfcmVsb2NzAAALc29ydEluZGV4ZXMAAQqiDwICAAucDwMBfAd9Bn8gCyAKayEMAkACQCAOBEAgDQRAQfj///8HIQpBiICAgHghDSALIAxNDQMgDCEFA0AgAyAFQQJ0IgFqIAIgACABaigCAEECdGooAgAiATYCACABIAogASAKSBshCiABIA0gASANShshDSAFQQFqIgUgC0cNAAsMAwsgDwRAIAsgDE0NAkF/IQ9B+P///wchCkGIgICAeCENIAwhAgNAIA8gByAAIAJBAnQiGmooAgBBAnQiG2ooAgAiDkcEQAJ/IAUqAjgiESAIIA5BBnRqIg8qAjyUIAUqAigiEiAPKgI4lCAFKgIIIhMgDyoCMJQgBSoCGCIUIA8qAjSUkpKSu0QAAAAAAECPQKIiEJlEAAAAAAAA4EFjBEAgEKoMAQtBgICAgHgLIRgCfyARIA8qAiyUIBIgDyoCKJQgEyAPKgIglCAUIA8qAiSUkpKSu0QAAAAAAECPQKIiEJlEAAAAAAAA4EFjBEAgEKoMAQtBgICAgHgLIRkCfyARIA8qAhyUIBIgDyoCGJQgEyAPKgIQlCAUIA8qAhSUkpKSu0QAAAAAAECPQKIiEJlEAAAAAAAA4EFjBEAgEKoMAQtBgICAgHgLIRwCfyARIA8qAgyUIBIgDyoCCJQgEyAPKgIAlCAUIA8qAgSUkpKSu0QAAAAAAECPQKIiEJlEAAAAAAAA4EFjBEAgEKoMAQtBgICAgHgLIR0gDiEPCyADIBpqIAEgG0ECdGoiDigCBCAcbCAOKAIAIB1saiAOKAIIIBlsaiAOKAIMIBhsaiIONgIAIA4gCiAKIA5KGyEKIA4gDSANIA5IGyENIAJBAWoiAiALRw0ACwwDCwJ/IAUqAii7RAAAAAAAQI9AoiIQmUQAAAAAAADgQWMEQCAQqgwBC0GAgICAeAshAgJ/IAUqAhi7RAAAAAAAQI9AoiIQmUQAAAAAAADgQWMEQCAQqgwBC0GAgICAeAshByALIAxNAn8gBSoCCLtEAAAAAABAj0CiIhCZRAAAAAAAAOBBYwRAIBCqDAELQYCAgIB4CyEPQfj///8HIQpBiICAgHghDQ0CIAwhBQNAIAMgBUECdCIIaiABIAAgCGooAgBBBHRqIggoAgQgB2wgCCgCACAPbGogCCgCCCACbGoiCDYCACAIIAogCCAKSBshCiAIIA0gCCANShshDSAFQQFqIgUgC0cNAAsMAgsgDQRAQfj///8HIQpBiICAgHghDSALIAxNDQIgDCEFA0AgAyAFQQJ0IgFqAn8gAiAAIAFqKAIAQQJ0aioCALtEAAAAAAAAsECiIhCZRAAAAAAAAOBBYwRAIBCqDAELQYCAgIB4CyIONgIAIAogDiAKIA5IGyEKIA0gDiANIA5KGyENIAVBAWoiBSALRw0ACwwCCyAPRQRAIAsgDE0NASAFKgIoIREgBSoCGCESIAUqAgghE0H4////ByEKQYiAgIB4IQ0gDCEFA0ACfyARIAEgACAFQQJ0IgdqKAIAQQR0aiICKgIIlCATIAIqAgCUIBIgAioCBJSSkrtEAAAAAAAAsECiIhCZRAAAAAAAAOBBYwRAIBCqDAELQYCAgIB4CyEOIAMgB2ogDjYCACAKIA4gCiAOSBshCiANIA4gDSAOShshDSAFQQFqIgUgC0cNAAsMAgsgCyAMTQ0AQX8hD0H4////ByEKQYiAgIB4IQ0gDCECA0AgDyAHIAAgAkECdCIYaigCAEECdCIZaigCACIORwRAIAUqAjgiESAIIA5BBnRqIg8qAjyUIAUqAigiEiAPKgI4lCAFKgIIIhMgDyoCMJQgBSoCGCIUIA8qAjSUkpKSIRUgESAPKgIslCASIA8qAiiUIBMgDyoCIJQgFCAPKgIklJKSkiEWIBEgDyoCHJQgEiAPKgIYlCATIA8qAhCUIBQgDyoCFJSSkpIhFyARIA8qAgyUIBIgDyoCCJQgEyAPKgIAlCAUIA8qAgSUkpKSIREgDiEPCyADIBhqAn8gFSABIBlBAnRqIg4qAgyUIBYgDioCCJQgESAOKgIAlCAXIA4qAgSUkpKSu0QAAAAAAACwQKIiEJlEAAAAAAAA4EFjBEAgEKoMAQtBgICAgHgLIg42AgAgCiAOIAogDkgbIQogDSAOIA0gDkobIQ0gAkEBaiICIAtHDQALDAELQYiAgIB4IQ1B+P///wchCgsgCyAMSwRAIAlBAWuzIA2yIAqyk5UhESAMIQ0DQAJ/IBEgAyANQQJ0aiIBKAIAIAprspQiEotDAAAAT10EQCASqAwBC0GAgICAeAshDiABIA42AgAgBCAOQQJ0aiIBIAEoAgBBAWo2AgAgDUEBaiINIAtHDQALCyAJQQJPBEAgBCgCACENQQEhCgNAIAQgCkECdGoiASABKAIAIA1qIg02AgAgCkEBaiIKIAlHDQALCyAMQQBKBEAgDCEKA0AgBiAKQQFrIgFBAnQiAmogACACaigCADYCACAKQQFLIAEhCg0ACwsgCyAMSgRAIAshCgNAIAYgCyAEIAMgCkEBayIKQQJ0IgFqKAIAQQJ0aiICKAIAIgVrQQJ0aiAAIAFqKAIANgIAIAIgBUEBazYCACAKIAxKDQALCws=";
- var SorterWasmNonShared = "AGFzbQEAAAAADwhkeWxpbmsuMAEEAAAAAAEXAmAAAGAQf39/f39/f39/f39/f39/fwACDwEDZW52Bm1lbW9yeQIAAAMDAgABBz4DEV9fd2FzbV9jYWxsX2N0b3JzAAAYX193YXNtX2FwcGx5X2RhdGFfcmVsb2NzAAALc29ydEluZGV4ZXMAAQrrDwICAAvlDwQBfAN7B30DfyALIAprIQwCQAJAIA4EQCANBEBB+P///wchCkGIgICAeCENIAsgDE0NAyAMIQUDQCADIAVBAnQiAWogAiAAIAFqKAIAQQJ0aigCACIBNgIAIAEgCiABIApIGyEKIAEgDSABIA1KGyENIAVBAWoiBSALRw0ACwwDCyAPBEAgCyAMTQ0CQX8hD0H4////ByEKQYiAgIB4IQ0gDCECA0AgDyAHIAAgAkECdCIcaigCACIdQQJ0aigCACIbRwRAAn8gBf0JAjggCCAbQQZ0aiIO/QkCDCAOKgIc/SABIA4qAiz9IAIgDioCPP0gA/3mASAF/QkCKCAO/QkCCCAOKgIY/SABIA4qAij9IAIgDioCOP0gA/3mASAF/QkCCCAO/QkCACAOKgIQ/SABIA4qAiD9IAIgDioCMP0gA/3mASAF/QkCGCAO/QkCBCAOKgIU/SABIA4qAiT9IAIgDioCNP0gA/3mAf3kAf3kAf3kASIR/V/9DAAAAAAAQI9AAAAAAABAj0AiEv3yASIT/SEBIhCZRAAAAAAAAOBBYwRAIBCqDAELQYCAgIB4CyEOAn8gE/0hACIQmUQAAAAAAADgQWMEQCAQqgwBC0GAgICAeAv9ESAO/RwBAn8gESAR/Q0ICQoLDA0ODwABAgMAAQID/V8gEv3yASIR/SEAIhCZRAAAAAAAAOBBYwRAIBCqDAELQYCAgIB4C/0cAgJ/IBH9IQEiEJlEAAAAAAAA4EFjBEAgEKoMAQtBgICAgHgL/RwDIRIgGyEPCyADIBxqIAEgHUEEdGr9AAAAIBL9tQEiEf0bACAR/RsBaiAR/RsCaiAR/RsDaiIONgIAIA4gCiAKIA5KGyEKIA4gDSANIA5IGyENIAJBAWoiAiALRw0ACwwDCwJ/IAUqAgi7/RQgBSoCGLv9IgH9DAAAAAAAQI9AAAAAAABAj0D98gEiEf0hASIQmUQAAAAAAADgQWMEQCAQqgwBC0GAgICAeAshDgJ/IBH9IQAiEJlEAAAAAAAA4EFjBEAgEKoMAQtBgICAgHgLAn8gBSoCKLtEAAAAAABAj0CiIhCZRAAAAAAAAOBBYwRAIBCqDAELQYCAgIB4CyEFQfj///8HIQpBiICAgHghDSALIAxNDQL9ESAO/RwBIAX9HAIhEiAMIQUDQCADIAVBAnQiAmogASAAIAJqKAIAQQR0av0AAAAgEv21ASIR/RsAIBH9GwFqIBH9GwJqIgI2AgAgAiAKIAIgCkgbIQogAiANIAIgDUobIQ0gBUEBaiIFIAtHDQALDAILIA0EQEH4////ByEKQYiAgIB4IQ0gCyAMTQ0CIAwhBQNAIAMgBUECdCIBagJ/IAIgACABaigCAEECdGoqAgC7RAAAAAAAALBAoiIQmUQAAAAAAADgQWMEQCAQqgwBC0GAgICAeAsiDjYCACAKIA4gCiAOSBshCiANIA4gDSAOShshDSAFQQFqIgUgC0cNAAsMAgsgD0UEQCALIAxNDQEgBSoCKCEUIAUqAhghFSAFKgIIIRZB+P///wchCkGIgICAeCENIAwhBQNAAn8gFCABIAAgBUECdCIHaigCAEEEdGoiAioCCJQgFiACKgIAlCAVIAIqAgSUkpK7RAAAAAAAALBAoiIQmUQAAAAAAADgQWMEQCAQqgwBC0GAgICAeAshDiADIAdqIA42AgAgCiAOIAogDkgbIQogDSAOIA0gDkobIQ0gBUEBaiIFIAtHDQALDAILIAsgDE0NAEF/IQ9B+P///wchCkGIgICAeCENIAwhAgNAIA8gByAAIAJBAnQiG2ooAgBBAnQiHGooAgAiDkcEQCAFKgI4IhQgCCAOQQZ0aiIPKgI8lCAFKgIoIhUgDyoCOJQgBSoCCCIWIA8qAjCUIAUqAhgiFyAPKgI0lJKSkiEYIBQgDyoCLJQgFSAPKgIolCAWIA8qAiCUIBcgDyoCJJSSkpIhGSAUIA8qAhyUIBUgDyoCGJQgFiAPKgIQlCAXIA8qAhSUkpKSIRogFCAPKgIMlCAVIA8qAgiUIBYgDyoCAJQgFyAPKgIElJKSkiEUIA4hDwsgAyAbagJ/IBggASAcQQJ0aiIOKgIMlCAZIA4qAgiUIBQgDioCAJQgGiAOKgIElJKSkrtEAAAAAAAAsECiIhCZRAAAAAAAAOBBYwRAIBCqDAELQYCAgIB4CyIONgIAIAogDiAKIA5IGyEKIA0gDiANIA5KGyENIAJBAWoiAiALRw0ACwwBC0GIgICAeCENQfj///8HIQoLIAsgDEsEQCAJQQFrsyANsiAKspOVIRQgDCENA0ACfyAUIAMgDUECdGoiASgCACAKa7KUIhWLQwAAAE9dBEAgFagMAQtBgICAgHgLIQ4gASAONgIAIAQgDkECdGoiASABKAIAQQFqNgIAIA1BAWoiDSALRw0ACwsgCUECTwRAIAQoAgAhDUEBIQoDQCAEIApBAnRqIgEgASgCACANaiINNgIAIApBAWoiCiAJRw0ACwsgDEEASgRAIAwhCgNAIAYgCkEBayIBQQJ0IgJqIAAgAmooAgA2AgAgCkEBSyABIQoNAAsLIAsgDEoEQCALIQoDQCAGIAsgBCADIApBAWsiCkECdCIBaigCAEECdGoiAigCACIFa0ECdGogACABaigCADYCACACIAVBAWs2AgAgCiAMSg0ACwsL";
- var SorterWasmNoSIMDNonShared = "AGFzbQEAAAAADwhkeWxpbmsuMAEEAAAAAAEXAmAAAGAQf39/f39/f39/f39/f39/fwACDwEDZW52Bm1lbW9yeQIAAAMDAgABBz4DEV9fd2FzbV9jYWxsX2N0b3JzAAAYX193YXNtX2FwcGx5X2RhdGFfcmVsb2NzAAALc29ydEluZGV4ZXMAAQqiDwICAAucDwMBfAd9Bn8gCyAKayEMAkACQCAOBEAgDQRAQfj///8HIQpBiICAgHghDSALIAxNDQMgDCEFA0AgAyAFQQJ0IgFqIAIgACABaigCAEECdGooAgAiATYCACABIAogASAKSBshCiABIA0gASANShshDSAFQQFqIgUgC0cNAAsMAwsgDwRAIAsgDE0NAkF/IQ9B+P///wchCkGIgICAeCENIAwhAgNAIA8gByAAIAJBAnQiGmooAgBBAnQiG2ooAgAiDkcEQAJ/IAUqAjgiESAIIA5BBnRqIg8qAjyUIAUqAigiEiAPKgI4lCAFKgIIIhMgDyoCMJQgBSoCGCIUIA8qAjSUkpKSu0QAAAAAAECPQKIiEJlEAAAAAAAA4EFjBEAgEKoMAQtBgICAgHgLIRgCfyARIA8qAiyUIBIgDyoCKJQgEyAPKgIglCAUIA8qAiSUkpKSu0QAAAAAAECPQKIiEJlEAAAAAAAA4EFjBEAgEKoMAQtBgICAgHgLIRkCfyARIA8qAhyUIBIgDyoCGJQgEyAPKgIQlCAUIA8qAhSUkpKSu0QAAAAAAECPQKIiEJlEAAAAAAAA4EFjBEAgEKoMAQtBgICAgHgLIRwCfyARIA8qAgyUIBIgDyoCCJQgEyAPKgIAlCAUIA8qAgSUkpKSu0QAAAAAAECPQKIiEJlEAAAAAAAA4EFjBEAgEKoMAQtBgICAgHgLIR0gDiEPCyADIBpqIAEgG0ECdGoiDigCBCAcbCAOKAIAIB1saiAOKAIIIBlsaiAOKAIMIBhsaiIONgIAIA4gCiAKIA5KGyEKIA4gDSANIA5IGyENIAJBAWoiAiALRw0ACwwDCwJ/IAUqAii7RAAAAAAAQI9AoiIQmUQAAAAAAADgQWMEQCAQqgwBC0GAgICAeAshAgJ/IAUqAhi7RAAAAAAAQI9AoiIQmUQAAAAAAADgQWMEQCAQqgwBC0GAgICAeAshByALIAxNAn8gBSoCCLtEAAAAAABAj0CiIhCZRAAAAAAAAOBBYwRAIBCqDAELQYCAgIB4CyEPQfj///8HIQpBiICAgHghDQ0CIAwhBQNAIAMgBUECdCIIaiABIAAgCGooAgBBBHRqIggoAgQgB2wgCCgCACAPbGogCCgCCCACbGoiCDYCACAIIAogCCAKSBshCiAIIA0gCCANShshDSAFQQFqIgUgC0cNAAsMAgsgDQRAQfj///8HIQpBiICAgHghDSALIAxNDQIgDCEFA0AgAyAFQQJ0IgFqAn8gAiAAIAFqKAIAQQJ0aioCALtEAAAAAAAAsECiIhCZRAAAAAAAAOBBYwRAIBCqDAELQYCAgIB4CyIONgIAIAogDiAKIA5IGyEKIA0gDiANIA5KGyENIAVBAWoiBSALRw0ACwwCCyAPRQRAIAsgDE0NASAFKgIoIREgBSoCGCESIAUqAgghE0H4////ByEKQYiAgIB4IQ0gDCEFA0ACfyARIAEgACAFQQJ0IgdqKAIAQQR0aiICKgIIlCATIAIqAgCUIBIgAioCBJSSkrtEAAAAAAAAsECiIhCZRAAAAAAAAOBBYwRAIBCqDAELQYCAgIB4CyEOIAMgB2ogDjYCACAKIA4gCiAOSBshCiANIA4gDSAOShshDSAFQQFqIgUgC0cNAAsMAgsgCyAMTQ0AQX8hD0H4////ByEKQYiAgIB4IQ0gDCECA0AgDyAHIAAgAkECdCIYaigCAEECdCIZaigCACIORwRAIAUqAjgiESAIIA5BBnRqIg8qAjyUIAUqAigiEiAPKgI4lCAFKgIIIhMgDyoCMJQgBSoCGCIUIA8qAjSUkpKSIRUgESAPKgIslCASIA8qAiiUIBMgDyoCIJQgFCAPKgIklJKSkiEWIBEgDyoCHJQgEiAPKgIYlCATIA8qAhCUIBQgDyoCFJSSkpIhFyARIA8qAgyUIBIgDyoCCJQgEyAPKgIAlCAUIA8qAgSUkpKSIREgDiEPCyADIBhqAn8gFSABIBlBAnRqIg4qAgyUIBYgDioCCJQgESAOKgIAlCAXIA4qAgSUkpKSu0QAAAAAAACwQKIiEJlEAAAAAAAA4EFjBEAgEKoMAQtBgICAgHgLIg42AgAgCiAOIAogDkgbIQogDSAOIA0gDkobIQ0gAkEBaiICIAtHDQALDAELQYiAgIB4IQ1B+P///wchCgsgCyAMSwRAIAlBAWuzIA2yIAqyk5UhESAMIQ0DQAJ/IBEgAyANQQJ0aiIBKAIAIAprspQiEotDAAAAT10EQCASqAwBC0GAgICAeAshDiABIA42AgAgBCAOQQJ0aiIBIAEoAgBBAWo2AgAgDUEBaiINIAtHDQALCyAJQQJPBEAgBCgCACENQQEhCgNAIAQgCkECdGoiASABKAIAIA1qIg02AgAgCkEBaiIKIAlHDQALCyAMQQBKBEAgDCEKA0AgBiAKQQFrIgFBAnQiAmogACACaigCADYCACAKQQFLIAEhCg0ACwsgCyAMSgRAIAshCgNAIAYgCyAEIAMgCkEBayIKQQJ0IgFqKAIAQQJ0aiICKAIAIgVrQQJ0aiAAIAFqKAIANgIAIAIgBUEBazYCACAKIAxKDQALCws=";
- function sortWorker(self) {
- let wasmInstance;
- let wasmMemory;
- let useSharedMemory;
- let integerBasedSort;
- let dynamicMode;
- let splatCount;
- let indexesToSortOffset;
- let sortedIndexesOffset;
- let sceneIndexesOffset;
- let transformsOffset;
- let precomputedDistancesOffset;
- let mappedDistancesOffset;
- let frequenciesOffset;
- let centersOffset;
- let modelViewProjOffset;
- let countsZero;
- let sortedIndexesOut;
- let Constants;
- function sort(splatSortCount, splatRenderCount, modelViewProj,
- usePrecomputedDistances, copyIndexesToSort, copyPrecomputedDistances, copyTransforms) {
- const sortStartTime = performance.now();
- if (!useSharedMemory) {
- const indexesToSort = new Uint32Array(wasmMemory, indexesToSortOffset, copyIndexesToSort.byteLength / Constants.BytesPerInt);
- indexesToSort.set(copyIndexesToSort);
- const transforms = new Float32Array(wasmMemory, transformsOffset, copyTransforms.byteLength / Constants.BytesPerFloat);
- transforms.set(copyTransforms);
- if (usePrecomputedDistances) {
- let precomputedDistances;
- if (integerBasedSort) {
- precomputedDistances = new Int32Array(wasmMemory, precomputedDistancesOffset,
- copyPrecomputedDistances.byteLength / Constants.BytesPerInt);
- } else {
- precomputedDistances = new Float32Array(wasmMemory, precomputedDistancesOffset,
- copyPrecomputedDistances.byteLength / Constants.BytesPerFloat);
- }
- precomputedDistances.set(copyPrecomputedDistances);
- }
- }
- if (!countsZero) countsZero = new Uint32Array(Constants.DepthMapRange);
- new Float32Array(wasmMemory, modelViewProjOffset, 16).set(modelViewProj);
- new Uint32Array(wasmMemory, frequenciesOffset, Constants.DepthMapRange).set(countsZero);
- wasmInstance.exports.sortIndexes(indexesToSortOffset, centersOffset, precomputedDistancesOffset,
- mappedDistancesOffset, frequenciesOffset, modelViewProjOffset,
- sortedIndexesOffset, sceneIndexesOffset, transformsOffset, Constants.DepthMapRange,
- splatSortCount, splatRenderCount, splatCount, usePrecomputedDistances, integerBasedSort,
- dynamicMode);
- const sortMessage = {
- 'sortDone': true,
- 'splatSortCount': splatSortCount,
- 'splatRenderCount': splatRenderCount,
- 'sortTime': 0
- };
- if (!useSharedMemory) {
- const sortedIndexes = new Uint32Array(wasmMemory, sortedIndexesOffset, splatRenderCount);
- if (!sortedIndexesOut || sortedIndexesOut.length < splatRenderCount) {
- sortedIndexesOut = new Uint32Array(splatRenderCount);
- }
- sortedIndexesOut.set(sortedIndexes);
- sortMessage.sortedIndexes = sortedIndexesOut;
- }
- const sortEndTime = performance.now();
- sortMessage.sortTime = sortEndTime - sortStartTime;
- self.postMessage(sortMessage);
- }
- self.onmessage = (e) => {
- if (e.data.centers) {
- centers = e.data.centers;
- sceneIndexes = e.data.sceneIndexes;
- if (integerBasedSort) {
- new Int32Array(wasmMemory, centersOffset + e.data.range.from * Constants.BytesPerInt * 4,
- e.data.range.count * 4).set(new Int32Array(centers));
- } else {
- new Float32Array(wasmMemory, centersOffset + e.data.range.from * Constants.BytesPerFloat * 4,
- e.data.range.count * 4).set(new Float32Array(centers));
- }
- if (dynamicMode) {
- new Uint32Array(wasmMemory, sceneIndexesOffset + e.data.range.from * 4,
- e.data.range.count).set(new Uint32Array(sceneIndexes));
- }
- self.postMessage({
- 'centerDataSet': true,
- });
- } else if (e.data.sort) {
- const renderCount = e.data.sort.splatRenderCount || 0;
- const sortCount = e.data.sort.splatSortCount || 0;
- const usePrecomputedDistances = e.data.sort.usePrecomputedDistances;
- let copyIndexesToSort;
- let copyPrecomputedDistances;
- let copyTransforms;
- if (!useSharedMemory) {
- copyIndexesToSort = e.data.sort.indexesToSort;
- copyTransforms = e.data.sort.transforms;
- if (usePrecomputedDistances) copyPrecomputedDistances = e.data.sort.precomputedDistances;
- }
- sort(sortCount, renderCount, e.data.sort.modelViewProj, usePrecomputedDistances,
- copyIndexesToSort, copyPrecomputedDistances, copyTransforms);
- } else if (e.data.init) {
- // Yep, this is super hacky and gross :(
- Constants = e.data.init.Constants;
- splatCount = e.data.init.splatCount;
- useSharedMemory = e.data.init.useSharedMemory;
- integerBasedSort = e.data.init.integerBasedSort;
- dynamicMode = e.data.init.dynamicMode;
- const CENTERS_BYTES_PER_ENTRY = integerBasedSort ? (Constants.BytesPerInt * 4) : (Constants.BytesPerFloat * 4);
- const sorterWasmBytes = new Uint8Array(e.data.init.sorterWasmBytes);
- const matrixSize = 16 * Constants.BytesPerFloat;
- const memoryRequiredForIndexesToSort = splatCount * Constants.BytesPerInt;
- const memoryRequiredForCenters = splatCount * CENTERS_BYTES_PER_ENTRY;
- const memoryRequiredForModelViewProjectionMatrix = matrixSize;
- const memoryRequiredForPrecomputedDistances = integerBasedSort ?
- (splatCount * Constants.BytesPerInt) : (splatCount * Constants.BytesPerFloat);
- const memoryRequiredForMappedDistances = splatCount * Constants.BytesPerInt;
- const memoryRequiredForSortedIndexes = splatCount * Constants.BytesPerInt;
- const memoryRequiredForIntermediateSortBuffers = Constants.DepthMapRange * Constants.BytesPerInt * 2;
- const memoryRequiredforTransformIndexes = dynamicMode ? (splatCount * Constants.BytesPerInt) : 0;
- const memoryRequiredforTransforms = dynamicMode ? (Constants.MaxScenes * matrixSize) : 0;
- const extraMemory = Constants.MemoryPageSize * 32;
- const totalRequiredMemory = memoryRequiredForIndexesToSort +
- memoryRequiredForCenters +
- memoryRequiredForModelViewProjectionMatrix +
- memoryRequiredForPrecomputedDistances +
- memoryRequiredForMappedDistances +
- memoryRequiredForIntermediateSortBuffers +
- memoryRequiredForSortedIndexes +
- memoryRequiredforTransformIndexes +
- memoryRequiredforTransforms +
- extraMemory;
- const totalPagesRequired = Math.floor(totalRequiredMemory / Constants.MemoryPageSize ) + 1;
- const sorterWasmImport = {
- module: {},
- env: {
- memory: new WebAssembly.Memory({
- initial: totalPagesRequired,
- maximum: totalPagesRequired,
- shared: true,
- }),
- }
- };
- WebAssembly.compile(sorterWasmBytes)
- .then((wasmModule) => {
- return WebAssembly.instantiate(wasmModule, sorterWasmImport);
- })
- .then((instance) => {
- wasmInstance = instance;
- indexesToSortOffset = 0;
- centersOffset = indexesToSortOffset + memoryRequiredForIndexesToSort;
- modelViewProjOffset = centersOffset + memoryRequiredForCenters;
- precomputedDistancesOffset = modelViewProjOffset + memoryRequiredForModelViewProjectionMatrix;
- mappedDistancesOffset = precomputedDistancesOffset + memoryRequiredForPrecomputedDistances;
- frequenciesOffset = mappedDistancesOffset + memoryRequiredForMappedDistances;
- sortedIndexesOffset = frequenciesOffset + memoryRequiredForIntermediateSortBuffers;
- sceneIndexesOffset = sortedIndexesOffset + memoryRequiredForSortedIndexes;
- transformsOffset = sceneIndexesOffset + memoryRequiredforTransformIndexes;
- wasmMemory = sorterWasmImport.env.memory.buffer;
- if (useSharedMemory) {
- self.postMessage({
- 'sortSetupPhase1Complete': true,
- 'indexesToSortBuffer': wasmMemory,
- 'indexesToSortOffset': indexesToSortOffset,
- 'sortedIndexesBuffer': wasmMemory,
- 'sortedIndexesOffset': sortedIndexesOffset,
- 'precomputedDistancesBuffer': wasmMemory,
- 'precomputedDistancesOffset': precomputedDistancesOffset,
- 'transformsBuffer': wasmMemory,
- 'transformsOffset': transformsOffset
- });
- } else {
- self.postMessage({
- 'sortSetupPhase1Complete': true
- });
- }
- });
- }
- };
- }
- function createSortWorker(splatCount, useSharedMemory, enableSIMDInSort, integerBasedSort, dynamicMode) {
- const worker = new Worker(
- URL.createObjectURL(
- new Blob(['(', sortWorker.toString(), ')(self)'], {
- type: 'application/javascript',
- }),
- ),
- );
- let sourceWasm = SorterWasm;
- // iOS makes choosing the right WebAssembly configuration tricky :(
- let iOSSemVer = isIOS() ? getIOSSemever() : null;
- if (!enableSIMDInSort && !useSharedMemory) {
- sourceWasm = SorterWasmNoSIMD;
- if (iOSSemVer && iOSSemVer.major < 16 && iOSSemVer.minor < 4) {
- sourceWasm = SorterWasmNoSIMDNonShared;
- }
- } else if (!enableSIMDInSort) {
- sourceWasm = SorterWasmNoSIMD;
- } else if (!useSharedMemory) {
- if (iOSSemVer && iOSSemVer.major < 16 && iOSSemVer.minor < 4) {
- sourceWasm = SorterWasmNonShared;
- }
- }
- const sorterWasmBinaryString = atob(sourceWasm);
- const sorterWasmBytes = new Uint8Array(sorterWasmBinaryString.length);
- for (let i = 0; i < sorterWasmBinaryString.length; i++) {
- sorterWasmBytes[i] = sorterWasmBinaryString.charCodeAt(i);
- }
- worker.postMessage({
- 'init': {
- 'sorterWasmBytes': sorterWasmBytes.buffer,
- 'splatCount': splatCount,
- 'useSharedMemory': useSharedMemory,
- 'integerBasedSort': integerBasedSort,
- 'dynamicMode': dynamicMode,
- // Super hacky
- 'Constants': {
- 'BytesPerFloat': Constants.BytesPerFloat,
- 'BytesPerInt': Constants.BytesPerInt,
- 'DepthMapRange': Constants.DepthMapRange,
- 'MemoryPageSize': Constants.MemoryPageSize,
- 'MaxScenes': Constants.MaxScenes
- }
- }
- });
- return worker;
- }
- const WebXRMode = {
- None: 0,
- VR: 1,
- AR: 2
- };
- /*
- Copyright © 2010-2024 three.js authors & Mark Kellogg
- Permission is hereby granted, free of charge, to any person obtaining a copy
- of this software and associated documentation files (the "Software"), to deal
- in the Software without restriction, including without limitation the rights
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- copies of the Software, and to permit persons to whom the Software is
- furnished to do so, subject to the following conditions:
- The above copyright notice and this permission notice shall be included in
- all copies or substantial portions of the Software.
- */
- class VRButton {
- static createButton( renderer ) {
- const button = document.createElement( 'button' );
- function showEnterVR( /* device */ ) {
- let currentSession = null;
- async function onSessionStarted( session ) {
- session.addEventListener( 'end', onSessionEnded );
- await renderer.xr.setSession( session );
- button.textContent = 'EXIT VR';
- currentSession = session;
- }
- function onSessionEnded( /* event */ ) {
- currentSession.removeEventListener( 'end', onSessionEnded );
- button.textContent = 'ENTER VR';
- currentSession = null;
- }
- //
- button.style.display = '';
- button.style.cursor = 'pointer';
- button.style.left = 'calc(50% - 50px)';
- button.style.width = '100px';
- button.textContent = 'ENTER VR';
- // WebXR's requestReferenceSpace only works if the corresponding feature
- // was requested at session creation time. For simplicity, just ask for
- // the interesting ones as optional features, but be aware that the
- // requestReferenceSpace call will fail if it turns out to be unavailable.
- // ('local' is always available for immersive sessions and doesn't need to
- // be requested separately.)
- const sessionInit = { optionalFeatures: ['local-floor', 'bounded-floor', 'hand-tracking', 'layers'] };
- button.onmouseenter = function() {
- button.style.opacity = '1.0';
- };
- button.onmouseleave = function() {
- button.style.opacity = '0.5';
- };
- button.onclick = function() {
- if ( currentSession === null ) {
- navigator.xr.requestSession( 'immersive-vr', sessionInit ).then( onSessionStarted );
- } else {
- currentSession.end();
- if ( navigator.xr.offerSession !== undefined ) {
- navigator.xr.offerSession( 'immersive-vr', sessionInit )
- .then( onSessionStarted )
- .catch( ( err ) => {
- console.warn( err );
- } );
- }
- }
- };
- if ( navigator.xr.offerSession !== undefined ) {
- navigator.xr.offerSession( 'immersive-vr', sessionInit )
- .then( onSessionStarted )
- .catch( ( err ) => {
- console.warn( err );
- } );
- }
- }
- function disableButton() {
- button.style.display = '';
- button.style.cursor = 'auto';
- button.style.left = 'calc(50% - 75px)';
- button.style.width = '150px';
- button.onmouseenter = null;
- button.onmouseleave = null;
- button.onclick = null;
- }
- function showWebXRNotFound() {
- disableButton();
- button.textContent = 'VR NOT SUPPORTED';
- }
- function showVRNotAllowed( exception ) {
- disableButton();
- console.warn( 'Exception when trying to call xr.isSessionSupported', exception );
- button.textContent = 'VR NOT ALLOWED';
- }
- function stylizeElement( element ) {
- element.style.position = 'absolute';
- element.style.bottom = '20px';
- element.style.padding = '12px 6px';
- element.style.border = '1px solid #fff';
- element.style.borderRadius = '4px';
- element.style.background = 'rgba(0,0,0,0.1)';
- element.style.color = '#fff';
- element.style.font = 'normal 13px sans-serif';
- element.style.textAlign = 'center';
- element.style.opacity = '0.5';
- element.style.outline = 'none';
- element.style.zIndex = '999';
- }
- if ( 'xr' in navigator ) {
- button.id = 'VRButton';
- button.style.display = 'none';
- stylizeElement( button );
- navigator.xr.isSessionSupported( 'immersive-vr' ).then( function( supported ) {
- supported ? showEnterVR() : showWebXRNotFound();
- if ( supported && VRButton.xrSessionIsGranted ) {
- button.click();
- }
- } ).catch( showVRNotAllowed );
- return button;
- } else {
- const message = document.createElement( 'a' );
- if ( window.isSecureContext === false ) {
- message.href = document.location.href.replace( /^http:/, 'https:' );
- message.innerHTML = 'WEBXR NEEDS HTTPS'; // TODO Improve message
- } else {
- message.href = 'https://immersiveweb.dev/';
- message.innerHTML = 'WEBXR NOT AVAILABLE';
- }
- message.style.left = 'calc(50% - 90px)';
- message.style.width = '180px';
- message.style.textDecoration = 'none';
- stylizeElement( message );
- return message;
- }
- }
- static registerSessionGrantedListener() {
- if ( typeof navigator !== 'undefined' && 'xr' in navigator ) {
- // WebXRViewer (based on Firefox) has a bug where addEventListener
- // throws a silent exception and aborts execution entirely.
- if ( /WebXRViewer\//i.test( navigator.userAgent ) ) return;
- navigator.xr.addEventListener( 'sessiongranted', () => {
- VRButton.xrSessionIsGranted = true;
- } );
- }
- }
- }
- VRButton.xrSessionIsGranted = false;
- VRButton.registerSessionGrantedListener();
- /*
- Copyright © 2010-2024 three.js authors & Mark Kellogg
- Permission is hereby granted, free of charge, to any person obtaining a copy
- of this software and associated documentation files (the "Software"), to deal
- in the Software without restriction, including without limitation the rights
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- copies of the Software, and to permit persons to whom the Software is
- furnished to do so, subject to the following conditions:
- The above copyright notice and this permission notice shall be included in
- all copies or substantial portions of the Software.
- */
- class ARButton {
- static createButton( renderer, sessionInit = {} ) {
- const button = document.createElement( 'button' );
- function showStartAR( /* device */ ) {
- if ( sessionInit.domOverlay === undefined ) {
- const overlay = document.createElement( 'div' );
- overlay.style.display = 'none';
- document.body.appendChild( overlay );
- const svg = document.createElementNS( 'http://www.w3.org/2000/svg', 'svg' );
- svg.setAttribute( 'width', 38 );
- svg.setAttribute( 'height', 38 );
- svg.style.position = 'absolute';
- svg.style.right = '20px';
- svg.style.top = '20px';
- svg.addEventListener( 'click', function() {
- currentSession.end();
- } );
- overlay.appendChild( svg );
- const path = document.createElementNS( 'http://www.w3.org/2000/svg', 'path' );
- path.setAttribute( 'd', 'M 12,12 L 28,28 M 28,12 12,28' );
- path.setAttribute( 'stroke', '#fff' );
- path.setAttribute( 'stroke-width', 2 );
- svg.appendChild( path );
- if ( sessionInit.optionalFeatures === undefined ) {
- sessionInit.optionalFeatures = [];
- }
- sessionInit.optionalFeatures.push( 'dom-overlay' );
- sessionInit.domOverlay = { root: overlay };
- }
- //
- let currentSession = null;
- async function onSessionStarted( session ) {
- session.addEventListener( 'end', onSessionEnded );
- renderer.xr.setReferenceSpaceType( 'local' );
- await renderer.xr.setSession( session );
- button.textContent = 'STOP AR';
- sessionInit.domOverlay.root.style.display = '';
- currentSession = session;
- }
- function onSessionEnded( /* event */ ) {
- currentSession.removeEventListener( 'end', onSessionEnded );
- button.textContent = 'START AR';
- sessionInit.domOverlay.root.style.display = 'none';
- currentSession = null;
- }
- //
- button.style.display = '';
- button.style.cursor = 'pointer';
- button.style.left = 'calc(50% - 50px)';
- button.style.width = '100px';
- button.textContent = 'START AR';
- button.onmouseenter = function() {
- button.style.opacity = '1.0';
- };
- button.onmouseleave = function() {
- button.style.opacity = '0.5';
- };
- button.onclick = function() {
- if ( currentSession === null ) {
- navigator.xr.requestSession( 'immersive-ar', sessionInit ).then( onSessionStarted );
- } else {
- currentSession.end();
- if ( navigator.xr.offerSession !== undefined ) {
- navigator.xr.offerSession( 'immersive-ar', sessionInit )
- .then( onSessionStarted )
- .catch( ( err ) => {
- console.warn( err );
- } );
- }
- }
- };
- if ( navigator.xr.offerSession !== undefined ) {
- navigator.xr.offerSession( 'immersive-ar', sessionInit )
- .then( onSessionStarted )
- .catch( ( err ) => {
- console.warn( err );
- } );
- }
- }
- function disableButton() {
- button.style.display = '';
- button.style.cursor = 'auto';
- button.style.left = 'calc(50% - 75px)';
- button.style.width = '150px';
- button.onmouseenter = null;
- button.onmouseleave = null;
- button.onclick = null;
- }
- function showARNotSupported() {
- disableButton();
- button.textContent = 'AR NOT SUPPORTED';
- }
- function showARNotAllowed( exception ) {
- disableButton();
- console.warn( 'Exception when trying to call xr.isSessionSupported', exception );
- button.textContent = 'AR NOT ALLOWED';
- }
- function stylizeElement( element ) {
- element.style.position = 'absolute';
- element.style.bottom = '20px';
- element.style.padding = '12px 6px';
- element.style.border = '1px solid #fff';
- element.style.borderRadius = '4px';
- element.style.background = 'rgba(0,0,0,0.1)';
- element.style.color = '#fff';
- element.style.font = 'normal 13px sans-serif';
- element.style.textAlign = 'center';
- element.style.opacity = '0.5';
- element.style.outline = 'none';
- element.style.zIndex = '999';
- }
- if ( 'xr' in navigator ) {
- button.id = 'ARButton';
- button.style.display = 'none';
- stylizeElement( button );
- navigator.xr.isSessionSupported( 'immersive-ar' ).then( function( supported ) {
- supported ? showStartAR() : showARNotSupported();
- } ).catch( showARNotAllowed );
- return button;
- } else {
- const message = document.createElement( 'a' );
- if ( window.isSecureContext === false ) {
- message.href = document.location.href.replace( /^http:/, 'https:' );
- message.innerHTML = 'WEBXR NEEDS HTTPS'; // TODO Improve message
- } else {
- message.href = 'https://immersiveweb.dev/';
- message.innerHTML = 'WEBXR NOT AVAILABLE';
- }
- message.style.left = 'calc(50% - 90px)';
- message.style.width = '180px';
- message.style.textDecoration = 'none';
- stylizeElement( message );
- return message;
- }
- }
- }
- const RenderMode = {
- Always: 0,
- OnChange: 1,
- Never: 2
- };
- const THREE_CAMERA_FOV = 50;
- const MINIMUM_DISTANCE_TO_NEW_FOCAL_POINT = .75;
- const MIN_SPLAT_COUNT_TO_SHOW_SPLAT_TREE_LOADING_SPINNER = 1500000;
- const FOCUS_MARKER_FADE_IN_SPEED = 10.0;
- const FOCUS_MARKER_FADE_OUT_SPEED = 2.5;
- const CONSECUTIVE_RENDERED_FRAMES_FOR_FPS_CALCULATION = 60;
- /**
- * Viewer: Manages the rendering of splat scenes. Manages an instance of SplatMesh as well as a web worker
- * that performs the sort for its splats.
- */
- class Viewer {
- constructor(options = {}) {
- // The natural 'up' vector for viewing the scene (only has an effect when used with orbit controls and
- // when the viewer uses its own camera).
- if (!options.cameraUp) options.cameraUp = [0, 1, 0];
- this.cameraUp = new THREE.Vector3().fromArray(options.cameraUp);
- // The camera's initial position (only used when the viewer uses its own camera).
- if (!options.initialCameraPosition) options.initialCameraPosition = [0, 10, 15];
- this.initialCameraPosition = new THREE.Vector3().fromArray(options.initialCameraPosition);
- // The initial focal point of the camera and center of the camera's orbit (only used when the viewer uses its own camera).
- if (!options.initialCameraLookAt) options.initialCameraLookAt = [0, 0, 0];
- this.initialCameraLookAt = new THREE.Vector3().fromArray(options.initialCameraLookAt);
- // 'dropInMode' is a flag that is used internally to support the usage of the viewer as a Three.js scene object
- this.dropInMode = options.dropInMode || false;
- // If 'selfDrivenMode' is true, the viewer manages its own update/animation loop via requestAnimationFrame()
- if (options.selfDrivenMode === undefined || options.selfDrivenMode === null) options.selfDrivenMode = true;
- this.selfDrivenMode = options.selfDrivenMode && !this.dropInMode;
- this.selfDrivenUpdateFunc = this.selfDrivenUpdate.bind(this);
- // If 'useBuiltInControls' is true, the viewer will create its own instance of OrbitControls and attach to the camera
- if (options.useBuiltInControls === undefined) options.useBuiltInControls = true;
- this.useBuiltInControls = options.useBuiltInControls;
- // parent element of the Three.js renderer canvas
- this.rootElement = options.rootElement;
- // Tells the viewer to pretend the device pixel ratio is 1, which can boost performance on devices where it is larger,
- // at a small cost to visual quality
- this.ignoreDevicePixelRatio = options.ignoreDevicePixelRatio || false;
- this.devicePixelRatio = this.ignoreDevicePixelRatio ? 1 : window.devicePixelRatio;
- // Tells the viewer to use 16-bit floating point values when storing splat covariance data in textures, instead of 32-bit
- this.halfPrecisionCovariancesOnGPU = options.halfPrecisionCovariancesOnGPU || false;
- // If 'threeScene' is valid, it will be rendered by the viewer along with the splat mesh
- this.threeScene = options.threeScene;
- // Allows for usage of an external Three.js renderer
- this.renderer = options.renderer;
- // Allows for usage of an external Three.js camera
- this.camera = options.camera;
- // If 'gpuAcceleratedSort' is true, a partially GPU-accelerated approach to sorting splats will be used.
- // Currently this means pre-computing splat distances from the camera on the GPU
- this.gpuAcceleratedSort = options.gpuAcceleratedSort || false;
- // if 'integerBasedSort' is true, the integer version of splat centers as well as other values used to calculate
- // splat distances are used instead of the float version. This speeds up computation, but introduces the possibility of
- // overflow in larger scenes.
- if (options.integerBasedSort === undefined || options.integerBasedSort === null) {
- options.integerBasedSort = true;
- }
- this.integerBasedSort = options.integerBasedSort;
- // If 'sharedMemoryForWorkers' is true, a SharedArrayBuffer will be used to communicate with web workers. This method
- // is faster than copying memory to or from web workers, but comes with security implications as outlined here:
- // https://web.dev/articles/cross-origin-isolation-guide
- // If enabled, it requires specific CORS headers to be present in the response from the server that is sent when
- // loading the application. More information is available in the README.
- if (options.sharedMemoryForWorkers === undefined || options.sharedMemoryForWorkers === null) options.sharedMemoryForWorkers = true;
- this.sharedMemoryForWorkers = options.sharedMemoryForWorkers;
- // if 'dynamicScene' is true, it tells the viewer to assume scene elements are not stationary or that the number of splats in the
- // scene may change. This prevents optimizations that depend on a static scene from being made. Additionally, if 'dynamicScene' is
- // true it tells the splat mesh to not apply scene tranforms to splat data that is returned by functions like
- // SplatMesh.getSplatCenter() by default.
- this.dynamicScene = !!options.dynamicScene;
- // When true, will perform additional steps during rendering to address artifacts caused by the rendering of gaussians at a
- // substantially different resolution than that at which they were rendered during training. This will only work correctly
- // for models that were trained using a process that utilizes this compensation calculation. For more details:
- // https://github.com/nerfstudio-project/gsplat/pull/117
- // https://github.com/graphdeco-inria/gaussian-splatting/issues/294#issuecomment-1772688093
- this.antialiased = options.antialiased || false;
- this.webXRMode = options.webXRMode || WebXRMode.None;
- if (this.webXRMode !== WebXRMode.None) {
- this.gpuAcceleratedSort = false;
- }
- this.webXRActive = false;
- // if 'renderMode' is RenderMode.Always, then the viewer will rrender the scene on every update. If it is RenderMode.OnChange,
- // it will only render when something in the scene has changed.
- this.renderMode = options.renderMode || RenderMode.Always;
- // SceneRevealMode.Default results in a nice, slow fade-in effect for progressively loaded scenes,
- // and a fast fade-in for non progressively loaded scenes.
- // SceneRevealMode.Gradual will force a slow fade-in for all scenes.
- // SceneRevealMode.Instant will force all loaded scene data to be immediately visible.
- this.sceneRevealMode = options.sceneRevealMode || SceneRevealMode.Default;
- // Hacky, experimental, non-scientific parameter for tweaking focal length related calculations. For scenes with very
- // small gaussians, small details, and small dimensions -- increasing this value can help improve visual quality.
- this.focalAdjustment = options.focalAdjustment || 1.0;
- // Specify the maximum screen-space splat size, can help deal with large splats that get too unwieldy
- this.maxScreenSpaceSplatSize = options.maxScreenSpaceSplatSize || 1024;
- // The verbosity of console logging
- this.logLevel = options.logLevel || LogLevel.None;
- // Degree of spherical harmonics to utilize in rendering splats (assuming the data is present in the splat scene).
- // Valid values are 0 - 2. Default value is 0.
- this.sphericalHarmonicsDegree = options.sphericalHarmonicsDegree || 0;
- // When true, allows for usage of extra properties and attributes during rendering for effects such as opacity adjustment.
- // Default is false for performance reasons. These properties are separate from transform properties (scale, rotation, position)
- // that are enabled by the 'dynamicScene' parameter.
- this.enableOptionalEffects = options.enableOptionalEffects || false;
- // Enable the usage of SIMD WebAssembly instructions for the splat sort
- if (options.enableSIMDInSort === undefined || options.enableSIMDInSort === null) options.enableSIMDInSort = true;
- this.enableSIMDInSort = options.enableSIMDInSort;
- // Level to compress PLY files when loading them for direct rendering (not exporting to .ksplat)
- if (options.plyInMemoryCompressionLevel === undefined || options.plyInMemoryCompressionLevel === null) {
- options.plyInMemoryCompressionLevel = 2;
- }
- this.plyInMemoryCompressionLevel = options.plyInMemoryCompressionLevel;
- // When true, the intermediate splat data that is the result of decompressing splat bufffer(s) and is used to
- // populate the data textures will be freed. This will reduces memory usage, but if that data needs to be modified
- // it will need to be re-populated from the splat buffer(s). Default is false.
- if (options.freeIntermediateSplatData === undefined || options.freeIntermediateSplatData === null) {
- options.freeIntermediateSplatData = false;
- }
- this.freeIntermediateSplatData = options.freeIntermediateSplatData;
- // It appears that for certain iOS versions, special actions need to be taken with the
- // usage of SIMD instructions and shared memory
- if (isIOS()) {
- const semver = getIOSSemever();
- if (semver.major < 17) {
- this.enableSIMDInSort = false;
- }
- if (semver.major < 16) {
- this.sharedMemoryForWorkers = false;
- }
- }
- // Tell the viewer how to render the splats
- if (options.splatRenderMode === undefined || options.splatRenderMode === null) {
- options.splatRenderMode = SplatRenderMode.ThreeD;
- }
- this.splatRenderMode = options.splatRenderMode;
- this.onSplatMeshChangedCallback = null;
- this.createSplatMesh();
- this.controls = null;
- this.perspectiveControls = null;
- this.orthographicControls = null;
- this.orthographicCamera = null;
- this.perspectiveCamera = null;
- this.showMeshCursor = false;
- this.showControlPlane = false;
- this.showInfo = false;
- this.sceneHelper = null;
- this.sortWorker = null;
- this.sortRunning = false;
- this.splatRenderCount = 0;
- this.sortWorkerIndexesToSort = null;
- this.sortWorkerSortedIndexes = null;
- this.sortWorkerPrecomputedDistances = null;
- this.sortWorkerTransforms = null;
- this.runAfterNextSort = [];
- this.selfDrivenModeRunning = false;
- this.splatRenderReady = false;
- this.raycaster = new Raycaster();
- this.infoPanel = null;
- this.startInOrthographicMode = false;
- this.currentFPS = 0;
- this.lastSortTime = 0;
- this.consecutiveRenderFrames = 0;
- this.previousCameraTarget = new THREE.Vector3();
- this.nextCameraTarget = new THREE.Vector3();
- this.mousePosition = new THREE.Vector2();
- this.mouseDownPosition = new THREE.Vector2();
- this.mouseDownTime = null;
- this.resizeObserver = null;
- this.mouseMoveListener = null;
- this.mouseDownListener = null;
- this.mouseUpListener = null;
- this.keyDownListener = null;
- this.sortPromise = null;
- this.sortPromiseResolver = null;
- this.splatSceneDownloadPromises = {};
- this.splatSceneDownloadAndBuildPromise = null;
- this.splatSceneRemovalPromise = null;
- this.loadingSpinner = new LoadingSpinner(null, this.rootElement || document.body);
- this.loadingSpinner.hide();
- this.loadingProgressBar = new LoadingProgressBar(this.rootElement || document.body);
- this.loadingProgressBar.hide();
- this.infoPanel = new InfoPanel(this.rootElement || document.body);
- this.infoPanel.hide();
- this.usingExternalCamera = (this.dropInMode || this.camera) ? true : false;
- this.usingExternalRenderer = (this.dropInMode || this.renderer) ? true : false;
- this.initialized = false;
- this.disposing = false;
- this.disposed = false;
- this.disposePromise = null;
- if (!this.dropInMode) this.init();
- }
- createSplatMesh() {
- this.splatMesh = new SplatMesh(this.splatRenderMode, this.dynamicScene, this.enableOptionalEffects,
- this.halfPrecisionCovariancesOnGPU, this.devicePixelRatio, this.gpuAcceleratedSort,
- this.integerBasedSort, this.antialiased, this.maxScreenSpaceSplatSize, this.logLevel,
- this.sphericalHarmonicsDegree);
- this.splatMesh.frustumCulled = false;
- if (this.onSplatMeshChangedCallback) this.onSplatMeshChangedCallback();
- }
- init() {
- if (this.initialized) return;
- if (!this.rootElement) {
- if (!this.usingExternalRenderer) {
- this.rootElement = document.createElement('div');
- this.rootElement.style.width = '100%';
- this.rootElement.style.height = '100%';
- this.rootElement.style.position = 'absolute';
- document.body.appendChild(this.rootElement);
- } else {
- this.rootElement = this.renderer.domElement.parentElement || document.body;
- }
- }
- this.setupCamera();
- this.setupRenderer();
- this.setupWebXR();
- this.setupControls();
- this.setupEventHandlers();
- this.threeScene = this.threeScene || new THREE.Scene();
- this.sceneHelper = new SceneHelper(this.threeScene);
- this.sceneHelper.setupMeshCursor();
- this.sceneHelper.setupFocusMarker();
- this.sceneHelper.setupControlPlane();
- this.loadingProgressBar.setContainer(this.rootElement);
- this.loadingSpinner.setContainer(this.rootElement);
- this.infoPanel.setContainer(this.rootElement);
- this.initialized = true;
- }
- setupCamera() {
- if (!this.usingExternalCamera) {
- const renderDimensions = new THREE.Vector2();
- this.getRenderDimensions(renderDimensions);
- this.perspectiveCamera = new THREE.PerspectiveCamera(THREE_CAMERA_FOV, renderDimensions.x / renderDimensions.y, 0.1, 1000);
- this.orthographicCamera = new THREE.OrthographicCamera(renderDimensions.x / -2, renderDimensions.x / 2,
- renderDimensions.y / 2, renderDimensions.y / -2, 0.1, 1000 );
- this.camera = this.startInOrthographicMode ? this.orthographicCamera : this.perspectiveCamera;
- this.camera.position.copy(this.initialCameraPosition);
- this.camera.up.copy(this.cameraUp).normalize();
- this.camera.lookAt(this.initialCameraLookAt);
- }
- }
- setupRenderer() {
- if (!this.usingExternalRenderer) {
- const renderDimensions = new THREE.Vector2();
- this.getRenderDimensions(renderDimensions);
- this.renderer = new THREE.WebGLRenderer({
- antialias: false,
- precision: 'highp'
- });
- this.renderer.setPixelRatio(this.devicePixelRatio);
- this.renderer.autoClear = true;
- this.renderer.setClearColor(new THREE.Color( 0x000000 ), 0.0);
- this.renderer.setSize(renderDimensions.x, renderDimensions.y);
- this.resizeObserver = new ResizeObserver(() => {
- this.getRenderDimensions(renderDimensions);
- this.renderer.setSize(renderDimensions.x, renderDimensions.y);
- this.forceRenderNextFrame();
- });
- this.resizeObserver.observe(this.rootElement);
- this.rootElement.appendChild(this.renderer.domElement);
- }
- }
- setupWebXR() {
- if (this.webXRMode) {
- if (this.webXRMode === WebXRMode.VR) {
- this.rootElement.appendChild(VRButton.createButton(this.renderer));
- } else if (this.webXRMode === WebXRMode.AR) {
- this.rootElement.appendChild(ARButton.createButton(this.renderer));
- }
- this.renderer.xr.addEventListener('sessionstart', (e) => {
- this.webXRActive = true;
- });
- this.renderer.xr.addEventListener('sessionend', (e) => {
- this.webXRActive = false;
- });
- this.renderer.xr.enabled = true;
- this.camera.position.copy(this.initialCameraPosition);
- this.camera.up.copy(this.cameraUp).normalize();
- this.camera.lookAt(this.initialCameraLookAt);
- }
- }
- setupControls() {
- if (this.useBuiltInControls && this.webXRMode === WebXRMode.None) {
- if (!this.usingExternalCamera) {
- this.perspectiveControls = new OrbitControls(this.perspectiveCamera, this.renderer.domElement);
- this.orthographicControls = new OrbitControls(this.orthographicCamera, this.renderer.domElement);
- } else {
- if (this.camera.isOrthographicCamera) {
- this.orthographicControls = new OrbitControls(this.camera, this.renderer.domElement);
- } else {
- this.perspectiveControls = new OrbitControls(this.camera, this.renderer.domElement);
- }
- }
- for (let controls of [this.orthographicControls, this.perspectiveControls,]) {
- if (controls) {
- controls.listenToKeyEvents(window);
- controls.rotateSpeed = 0.5;
- controls.maxPolarAngle = Math.PI * .75;
- controls.minPolarAngle = 0.1;
- controls.enableDamping = true;
- controls.dampingFactor = 0.05;
- controls.target.copy(this.initialCameraLookAt);
- controls.update();
- }
- }
- this.controls = this.camera.isOrthographicCamera ? this.orthographicControls : this.perspectiveControls;
- this.controls.update();
- }
- }
- setupEventHandlers() {
- if (this.useBuiltInControls && this.webXRMode === WebXRMode.None) {
- this.mouseMoveListener = this.onMouseMove.bind(this);
- this.renderer.domElement.addEventListener('pointermove', this.mouseMoveListener, false);
- this.mouseDownListener = this.onMouseDown.bind(this);
- this.renderer.domElement.addEventListener('pointerdown', this.mouseDownListener, false);
- this.mouseUpListener = this.onMouseUp.bind(this);
- this.renderer.domElement.addEventListener('pointerup', this.mouseUpListener, false);
- this.keyDownListener = this.onKeyDown.bind(this);
- window.addEventListener('keydown', this.keyDownListener, false);
- }
- }
- removeEventHandlers() {
- if (this.useBuiltInControls) {
- this.renderer.domElement.removeEventListener('pointermove', this.mouseMoveListener);
- this.mouseMoveListener = null;
- this.renderer.domElement.removeEventListener('pointerdown', this.mouseDownListener);
- this.mouseDownListener = null;
- this.renderer.domElement.removeEventListener('pointerup', this.mouseUpListener);
- this.mouseUpListener = null;
- window.removeEventListener('keydown', this.keyDownListener);
- this.keyDownListener = null;
- }
- }
- setRenderMode(renderMode) {
- this.renderMode = renderMode;
- }
- onSplatMeshChanged(callback) {
- this.onSplatMeshChangedCallback = callback;
- }
- onKeyDown = function() {
- const forward = new THREE.Vector3();
- const tempMatrixLeft = new THREE.Matrix4();
- const tempMatrixRight = new THREE.Matrix4();
- return function(e) {
- forward.set(0, 0, -1);
- forward.transformDirection(this.camera.matrixWorld);
- tempMatrixLeft.makeRotationAxis(forward, Math.PI / 128);
- tempMatrixRight.makeRotationAxis(forward, -Math.PI / 128);
- switch (e.code) {
- case 'KeyG':
- this.focalAdjustment += 0.02;
- this.forceRenderNextFrame();
- break;
- case 'KeyF':
- this.focalAdjustment -= 0.02;
- this.forceRenderNextFrame();
- break;
- case 'ArrowLeft':
- this.camera.up.transformDirection(tempMatrixLeft);
- break;
- case 'ArrowRight':
- this.camera.up.transformDirection(tempMatrixRight);
- break;
- case 'KeyC':
- this.showMeshCursor = !this.showMeshCursor;
- break;
- case 'KeyU':
- this.showControlPlane = !this.showControlPlane;
- break;
- case 'KeyI':
- this.showInfo = !this.showInfo;
- if (this.showInfo) {
- this.infoPanel.show();
- } else {
- this.infoPanel.hide();
- }
- break;
- case 'KeyO':
- if (!this.usingExternalCamera) {
- this.setOrthographicMode(!this.camera.isOrthographicCamera);
- }
- break;
- case 'KeyP':
- if (!this.usingExternalCamera) {
- this.splatMesh.setPointCloudModeEnabled(!this.splatMesh.getPointCloudModeEnabled());
- }
- break;
- case 'Equal':
- if (!this.usingExternalCamera) {
- this.splatMesh.setSplatScale(this.splatMesh.getSplatScale() + 0.05);
- }
- break;
- case 'Minus':
- if (!this.usingExternalCamera) {
- this.splatMesh.setSplatScale(Math.max(this.splatMesh.getSplatScale() - 0.05, 0.0));
- }
- break;
- }
- };
- }();
- onMouseMove(mouse) {
- this.mousePosition.set(mouse.offsetX, mouse.offsetY);
- }
- onMouseDown() {
- this.mouseDownPosition.copy(this.mousePosition);
- this.mouseDownTime = getCurrentTime();
- }
- onMouseUp = function() {
- const clickOffset = new THREE.Vector2();
- return function(mouse) {
- clickOffset.copy(this.mousePosition).sub(this.mouseDownPosition);
- const mouseUpTime = getCurrentTime();
- const wasClick = mouseUpTime - this.mouseDownTime < 0.5 && clickOffset.length() < 2;
- if (wasClick) {
- this.onMouseClick(mouse);
- }
- };
- }();
- onMouseClick(mouse) {
- this.mousePosition.set(mouse.offsetX, mouse.offsetY);
- this.checkForFocalPointChange();
- }
- checkForFocalPointChange = function() {
- const renderDimensions = new THREE.Vector2();
- const toNewFocalPoint = new THREE.Vector3();
- const outHits = [];
- return function() {
- if (!this.transitioningCameraTarget) {
- this.getRenderDimensions(renderDimensions);
- outHits.length = 0;
- this.raycaster.setFromCameraAndScreenPosition(this.camera, this.mousePosition, renderDimensions);
- this.raycaster.intersectSplatMesh(this.splatMesh, outHits);
- if (outHits.length > 0) {
- const hit = outHits[0];
- const intersectionPoint = hit.origin;
- toNewFocalPoint.copy(intersectionPoint).sub(this.camera.position);
- if (toNewFocalPoint.length() > MINIMUM_DISTANCE_TO_NEW_FOCAL_POINT) {
- this.previousCameraTarget.copy(this.controls.target);
- this.nextCameraTarget.copy(intersectionPoint);
- this.transitioningCameraTarget = true;
- this.transitioningCameraTargetStartTime = getCurrentTime();
- }
- }
- }
- };
- }();
- getRenderDimensions(outDimensions) {
- if (this.rootElement) {
- outDimensions.x = this.rootElement.offsetWidth;
- outDimensions.y = this.rootElement.offsetHeight;
- } else {
- this.renderer.getSize(outDimensions);
- }
- }
- setOrthographicMode(orthographicMode) {
- if (orthographicMode === this.camera.isOrthographicCamera) return;
- const fromCamera = this.camera;
- const toCamera = orthographicMode ? this.orthographicCamera : this.perspectiveCamera;
- toCamera.position.copy(fromCamera.position);
- toCamera.up.copy(fromCamera.up);
- toCamera.rotation.copy(fromCamera.rotation);
- toCamera.quaternion.copy(fromCamera.quaternion);
- toCamera.matrix.copy(fromCamera.matrix);
- this.camera = toCamera;
- if (this.controls) {
- const resetControls = (controls) => {
- controls.saveState();
- controls.reset();
- };
- const fromControls = this.controls;
- const toControls = orthographicMode ? this.orthographicControls : this.perspectiveControls;
- resetControls(toControls);
- resetControls(fromControls);
- toControls.target.copy(fromControls.target);
- if (orthographicMode) {
- Viewer.setCameraZoomFromPosition(toCamera, fromCamera, fromControls);
- } else {
- Viewer.setCameraPositionFromZoom(toCamera, fromCamera, toControls);
- }
- this.controls = toControls;
- this.camera.lookAt(this.controls.target);
- }
- }
- static setCameraPositionFromZoom = function() {
- const tempVector = new THREE.Vector3();
- return function(positionCamera, zoomedCamera, controls) {
- const toLookAtDistance = 1 / (zoomedCamera.zoom * 0.001);
- tempVector.copy(controls.target).sub(positionCamera.position).normalize().multiplyScalar(toLookAtDistance).negate();
- positionCamera.position.copy(controls.target).add(tempVector);
- };
- }();
- static setCameraZoomFromPosition = function() {
- const tempVector = new THREE.Vector3();
- return function(zoomCamera, positionZamera, controls) {
- const toLookAtDistance = tempVector.copy(controls.target).sub(positionZamera.position).length();
- zoomCamera.zoom = 1 / (toLookAtDistance * .001);
- };
- }();
- updateSplatMesh = function() {
- const renderDimensions = new THREE.Vector2();
- return function() {
- if (!this.splatMesh) return;
- const splatCount = this.splatMesh.getSplatCount();
- if (splatCount > 0) {
- this.splatMesh.updateVisibleRegionFadeDistance(this.sceneRevealMode);
- this.splatMesh.updateTransforms();
- this.getRenderDimensions(renderDimensions);
- const focalLengthX = this.camera.projectionMatrix.elements[0] * 0.5 *
- this.devicePixelRatio * renderDimensions.x;
- const focalLengthY = this.camera.projectionMatrix.elements[5] * 0.5 *
- this.devicePixelRatio * renderDimensions.y;
- const focalMultiplier = this.camera.isOrthographicCamera ? (1.0 / this.devicePixelRatio) : 1.0;
- const focalAdjustment = this.focalAdjustment * focalMultiplier;
- const inverseFocalAdjustment = 1.0 / focalAdjustment;
- this.adjustForWebXRStereo(renderDimensions);
- this.splatMesh.updateUniforms(renderDimensions, focalLengthX * focalAdjustment, focalLengthY * focalAdjustment,
- this.camera.isOrthographicCamera, this.camera.zoom || 1.0, inverseFocalAdjustment);
- }
- };
- }();
- adjustForWebXRStereo(renderDimensions) {
- // TODO: Figure out a less hacky way to determine if stereo rendering is active
- if (this.camera && this.webXRActive) {
- const xrCamera = this.renderer.xr.getCamera();
- const xrCameraProj00 = xrCamera.projectionMatrix.elements[0];
- const cameraProj00 = this.camera.projectionMatrix.elements[0];
- renderDimensions.x *= (cameraProj00 / xrCameraProj00);
- }
- }
- isLoadingOrUnloading() {
- return Object.keys(this.splatSceneDownloadPromises).length > 0 || this.splatSceneDownloadAndBuildPromise !== null ||
- this.splatSceneRemovalPromise !== null;
- }
- isDisposingOrDisposed() {
- return this.disposing || this.disposed;
- }
- addSplatSceneDownloadPromise(promise) {
- this.splatSceneDownloadPromises[promise.id] = promise;
- }
- removeSplatSceneDownloadPromise(promise) {
- delete this.splatSceneDownloadPromises[promise.id];
- }
- setSplatSceneDownloadAndBuildPromise(promise) {
- this.splatSceneDownloadAndBuildPromise = promise;
- }
- clearSplatSceneDownloadAndBuildPromise() {
- this.splatSceneDownloadAndBuildPromise = null;
- }
- /**
- * Add a splat scene to the viewer and display any loading UI if appropriate.
- * @param {string} path Path to splat scene to be loaded
- * @param {object} options {
- *
- * splatAlphaRemovalThreshold: Ignore any splats with an alpha less than the specified
- * value (valid range: 0 - 255), defaults to 1
- *
- * showLoadingUI: Display a loading spinner while the scene is loading, defaults to true
- *
- * position (Array<number>): Position of the scene, acts as an offset from its default position, defaults to [0, 0, 0]
- *
- * rotation (Array<number>): Rotation of the scene represented as a quaternion, defaults to [0, 0, 0, 1]
- *
- * scale (Array<number>): Scene's scale, defaults to [1, 1, 1]
- *
- * onProgress: Function to be called as file data are received, or other processing occurs
- *
- * }
- * @return {AbortablePromise}
- */
- addSplatScene(path, options = {}) {
- if (this.isLoadingOrUnloading()) {
- throw new Error('Cannot add splat scene while another load or unload is already in progress.');
- }
- if (this.isDisposingOrDisposed()) {
- throw new Error('Cannot add splat scene after dispose() is called.');
- }
- if (options.progressiveLoad && this.splatMesh.scenes && this.splatMesh.scenes.length > 0) {
- console.log('addSplatScene(): "progressiveLoad" option ignore because there are multiple splat scenes');
- options.progressiveLoad = false;
- }
- const format = (options.format !== undefined && options.format !== null) ? options.format : sceneFormatFromPath(path);
- const progressiveLoad = Viewer.isProgressivelyLoadable(format) && options.progressiveLoad;
- const showLoadingUI = (options.showLoadingUI !== undefined && options.showLoadingUI !== null) ? options.showLoadingUI : true;
- let loadingUITaskId = null;
- if (showLoadingUI) {
- this.loadingSpinner.removeAllTasks();
- loadingUITaskId = this.loadingSpinner.addTask('Downloading...');
- }
- const hideLoadingUI = () => {
- this.loadingProgressBar.hide();
- this.loadingSpinner.removeAllTasks();
- };
- const onProgressUIUpdate = (percentComplete, percentCompleteLabel, loaderStatus) => {
- if (showLoadingUI) {
- if (loaderStatus === LoaderStatus.Downloading) {
- if (percentComplete == 100) {
- this.loadingSpinner.setMessageForTask(loadingUITaskId, 'Download complete!');
- } else {
- if (progressiveLoad) {
- this.loadingSpinner.setMessageForTask(loadingUITaskId, 'Downloading splats...');
- } else {
- const suffix = percentCompleteLabel ? `: ${percentCompleteLabel}` : `...`;
- this.loadingSpinner.setMessageForTask(loadingUITaskId, `Downloading${suffix}`);
- }
- }
- } else if (loaderStatus === LoaderStatus.Processing) {
- this.loadingSpinner.setMessageForTask(loadingUITaskId, 'Processing splats...');
- }
- }
- };
- let downloadDone = false;
- let downloadedPercentage = 0;
- const splatBuffersAddedUIUpdate = (firstBuild, finalBuild) => {
- if (showLoadingUI) {
- if (firstBuild && progressiveLoad || finalBuild && !progressiveLoad) {
- this.loadingSpinner.removeTask(loadingUITaskId);
- if (!finalBuild && !downloadDone) this.loadingProgressBar.show();
- }
- if (progressiveLoad) {
- if (finalBuild) {
- downloadDone = true;
- this.loadingProgressBar.hide();
- } else {
- this.loadingProgressBar.setProgress(downloadedPercentage);
- }
- }
- }
- };
- const onProgress = (percentComplete, percentCompleteLabel, loaderStatus) => {
- downloadedPercentage = percentComplete;
- onProgressUIUpdate(percentComplete, percentCompleteLabel, loaderStatus);
- if (options.onProgress) options.onProgress(percentComplete, percentCompleteLabel, loaderStatus);
- };
- const buildSection = (splatBuffer, firstBuild, finalBuild) => {
- if (!progressiveLoad && options.onProgress) options.onProgress(0, '0%', LoaderStatus.Processing);
- const addSplatBufferOptions = {
- 'rotation': options.rotation || options.orientation,
- 'position': options.position,
- 'scale': options.scale,
- 'splatAlphaRemovalThreshold': options.splatAlphaRemovalThreshold,
- };
- return this.addSplatBuffers([splatBuffer], [addSplatBufferOptions],
- finalBuild, firstBuild && showLoadingUI, showLoadingUI,
- progressiveLoad, progressiveLoad).then(() => {
- if (!progressiveLoad && options.onProgress) options.onProgress(100, '100%', LoaderStatus.Processing);
- splatBuffersAddedUIUpdate(firstBuild, finalBuild);
- });
- };
- const loadFunc = progressiveLoad ? this.downloadAndBuildSingleSplatSceneProgressiveLoad.bind(this) :
- this.downloadAndBuildSingleSplatSceneStandardLoad.bind(this);
- return loadFunc(path, format, options.splatAlphaRemovalThreshold, buildSection.bind(this), onProgress, hideLoadingUI.bind(this));
- }
- /**
- * Download a single splat scene, convert to splat buffer and then rebuild the viewer's splat mesh
- * by calling 'buildFunc' -- all before displaying the scene. Also sets/clears relevant instance synchronization objects,
- * and calls appropriate functions on success or failure.
- * @param {string} path Path to splat scene to be loaded
- * @param {SceneFormat} format Format of the splat scene file
- * @param {number} splatAlphaRemovalThreshold Ignore any splats with an alpha less than the specified value (valid range: 0 - 255)
- * @param {function} buildFunc Function to build the viewer's splat mesh with the downloaded splat buffer
- * @param {function} onProgress Function to be called as file data are received, or other processing occurs
- * @param {function} onException Function to be called when exception occurs
- * @return {AbortablePromise}
- */
- downloadAndBuildSingleSplatSceneStandardLoad(path, format, splatAlphaRemovalThreshold, buildFunc, onProgress, onException) {
- const downloadPromise = this.downloadSplatSceneToSplatBuffer(path, splatAlphaRemovalThreshold,
- onProgress, false, undefined, format);
- const downloadAndBuildPromise = abortablePromiseWithExtractedComponents(downloadPromise.abortHandler);
- downloadPromise.then((splatBuffer) => {
- this.removeSplatSceneDownloadPromise(downloadPromise);
- return buildFunc(splatBuffer, true, true).then(() => {
- downloadAndBuildPromise.resolve();
- this.clearSplatSceneDownloadAndBuildPromise();
- });
- })
- .catch((e) => {
- if (onException) onException();
- this.clearSplatSceneDownloadAndBuildPromise();
- this.removeSplatSceneDownloadPromise(downloadPromise);
- const error = (e instanceof AbortedPromiseError) ? e : new Error(`Viewer::addSplatScene -> Could not load file ${path}`);
- downloadAndBuildPromise.reject(error);
- });
- this.addSplatSceneDownloadPromise(downloadPromise);
- this.setSplatSceneDownloadAndBuildPromise(downloadAndBuildPromise.promise);
- return downloadAndBuildPromise.promise;
- }
- /**
- * Download a single splat scene and convert to splat buffer in a progressive manner, allowing rendering as the file downloads.
- * As each section is downloaded, the viewer's splat mesh is rebuilt by calling 'buildFunc'
- * Also sets/clears relevant instance synchronization objects, and calls appropriate functions on success or failure.
- * @param {string} path Path to splat scene to be loaded
- * @param {SceneFormat} format Format of the splat scene file
- * @param {number} splatAlphaRemovalThreshold Ignore any splats with an alpha less than the specified value (valid range: 0 - 255)
- * @param {function} buildFunc Function to rebuild the viewer's splat mesh after a new splat buffer section is downloaded
- * @param {function} onDownloadProgress Function to be called as file data are received
- * @param {function} onDownloadException Function to be called when exception occurs at any point during the full download
- * @return {AbortablePromise}
- */
- downloadAndBuildSingleSplatSceneProgressiveLoad(path, format, splatAlphaRemovalThreshold, buildFunc,
- onDownloadProgress, onDownloadException) {
- let progressiveLoadedSectionBuildCount = 0;
- let progressiveLoadedSectionBuilding = false;
- const queuedProgressiveLoadSectionBuilds = [];
- const checkAndBuildProgressiveLoadSections = () => {
- if (queuedProgressiveLoadSectionBuilds.length > 0 &&
- !progressiveLoadedSectionBuilding &&
- !this.isDisposingOrDisposed()) {
- progressiveLoadedSectionBuilding = true;
- const queuedBuild = queuedProgressiveLoadSectionBuilds.shift();
- buildFunc(queuedBuild.splatBuffer, queuedBuild.firstBuild, queuedBuild.finalBuild)
- .then(() => {
- progressiveLoadedSectionBuilding = false;
- if (queuedBuild.firstBuild) {
- progressiveLoadFirstSectionBuildPromise.reject = null;
- progressiveLoadFirstSectionBuildPromise.resolve();
- } else if (queuedBuild.finalBuild) {
- splatSceneDownloadAndBuildPromise.resolve();
- this.clearSplatSceneDownloadAndBuildPromise();
- }
- if (queuedProgressiveLoadSectionBuilds.length > 0) {
- delayedExecute(() => checkAndBuildProgressiveLoadSections());
- }
- });
- }
- };
- const onProgressiveLoadSectionProgress = (splatBuffer, finalBuild) => {
- if (!this.isDisposingOrDisposed()) {
- if (finalBuild || queuedProgressiveLoadSectionBuilds.length === 0 ||
- splatBuffer.getSplatCount() > queuedProgressiveLoadSectionBuilds[0].splatBuffer.getSplatCount()) {
- queuedProgressiveLoadSectionBuilds.push({
- splatBuffer,
- firstBuild: progressiveLoadedSectionBuildCount === 0,
- finalBuild
- });
- progressiveLoadedSectionBuildCount++;
- checkAndBuildProgressiveLoadSections();
- }
- }
- };
- let splatSceneDownloadPromise = this.downloadSplatSceneToSplatBuffer(path, splatAlphaRemovalThreshold, onDownloadProgress, true,
- onProgressiveLoadSectionProgress, format);
- const progressiveLoadFirstSectionBuildPromise = abortablePromiseWithExtractedComponents(splatSceneDownloadPromise.abortHandler);
- const splatSceneDownloadAndBuildPromise = abortablePromiseWithExtractedComponents();
- this.addSplatSceneDownloadPromise(splatSceneDownloadPromise);
- this.setSplatSceneDownloadAndBuildPromise(splatSceneDownloadAndBuildPromise.promise);
- splatSceneDownloadPromise.then(() => {
- this.removeSplatSceneDownloadPromise(splatSceneDownloadPromise);
- })
- .catch((e) => {
- this.clearSplatSceneDownloadAndBuildPromise();
- this.removeSplatSceneDownloadPromise(splatSceneDownloadPromise);
- const error = (e instanceof AbortedPromiseError) ? e : new Error(`Viewer::addSplatScene -> Could not load one or more scenes`);
- progressiveLoadFirstSectionBuildPromise.reject(error);
- if (onDownloadException) onDownloadException(error);
- });
- return progressiveLoadFirstSectionBuildPromise.promise;
- }
- /**
- * Add multiple splat scenes to the viewer and display any loading UI if appropriate.
- * @param {Array<object>} sceneOptions Array of per-scene options: {
- *
- * path: Path to splat scene to be loaded
- *
- * splatAlphaRemovalThreshold: Ignore any splats with an alpha less than the specified
- * value (valid range: 0 - 255), defaults to 1
- *
- * position (Array<number>): Position of the scene, acts as an offset from its default position, defaults to [0, 0, 0]
- *
- * rotation (Array<number>): Rotation of the scene represented as a quaternion, defaults to [0, 0, 0, 1]
- *
- * scale (Array<number>): Scene's scale, defaults to [1, 1, 1]
- * }
- * @param {boolean} showLoadingUI Display a loading spinner while the scene is loading, defaults to true
- * @param {function} onProgress Function to be called as file data are received
- * @return {AbortablePromise}
- */
- addSplatScenes(sceneOptions, showLoadingUI = true, onProgress = undefined) {
- if (this.isLoadingOrUnloading()) {
- throw new Error('Cannot add splat scene while another load or unload is already in progress.');
- }
- if (this.isDisposingOrDisposed()) {
- throw new Error('Cannot add splat scene after dispose() is called.');
- }
- const fileCount = sceneOptions.length;
- const percentComplete = [];
- let loadingUITaskId;
- if (showLoadingUI) {
- this.loadingSpinner.removeAllTasks();
- loadingUITaskId = this.loadingSpinner.addTask('Downloading...');
- }
- const onLoadProgress = (fileIndex, percent, percentLabel, loaderStatus) => {
- percentComplete[fileIndex] = percent;
- let totalPercent = 0;
- for (let i = 0; i < fileCount; i++) totalPercent += percentComplete[i] || 0;
- totalPercent = totalPercent / fileCount;
- percentLabel = `${totalPercent.toFixed(2)}%`;
- if (showLoadingUI) {
- if (loaderStatus === LoaderStatus.Downloading) {
- this.loadingSpinner.setMessageForTask(loadingUITaskId, totalPercent == 100 ?
- `Download complete!` : `Downloading: ${percentLabel}`);
- }
- }
- if (onProgress) onProgress(totalPercent, percentLabel, loaderStatus);
- };
- const baseDownloadPromises = [];
- const nativeDownloadPromises = [];
- for (let i = 0; i < sceneOptions.length; i++) {
- const options = sceneOptions[i];
- const format = (options.format !== undefined && options.format !== null) ? options.format : sceneFormatFromPath(options.path);
- const baseDownloadPromise = this.downloadSplatSceneToSplatBuffer(options.path, options.splatAlphaRemovalThreshold,
- onLoadProgress.bind(this, i), false, undefined, format);
- baseDownloadPromises.push(baseDownloadPromise);
- nativeDownloadPromises.push(baseDownloadPromise.promise);
- }
- const downloadAndBuildPromise = new AbortablePromise((resolve, reject) => {
- Promise.all(nativeDownloadPromises)
- .then((splatBuffers) => {
- if (showLoadingUI) this.loadingSpinner.removeTask(loadingUITaskId);
- if (onProgress) onProgress(0, '0%', LoaderStatus.Processing);
- this.addSplatBuffers(splatBuffers, sceneOptions, true, showLoadingUI, showLoadingUI, false, false).then(() => {
- if (onProgress) onProgress(100, '100%', LoaderStatus.Processing);
- this.clearSplatSceneDownloadAndBuildPromise();
- resolve();
- });
- })
- .catch((e) => {
- if (showLoadingUI) this.loadingSpinner.removeTask(loadingUITaskId);
- this.clearSplatSceneDownloadAndBuildPromise();
- const error = (e instanceof AbortedPromiseError) ? e :
- new Error(`Viewer::addSplatScenes -> Could not load one or more splat scenes.`);
- reject(error);
- })
- .finally(() => {
- this.removeSplatSceneDownloadPromise(downloadAndBuildPromise);
- });
- }, (reason) => {
- for (let baseDownloadPromise of baseDownloadPromises) {
- baseDownloadPromise.abort(reason);
- }
- });
- this.addSplatSceneDownloadPromise(downloadAndBuildPromise);
- this.setSplatSceneDownloadAndBuildPromise(downloadAndBuildPromise);
- return downloadAndBuildPromise;
- }
- /**
- * Download a splat scene and convert to SplatBuffer instance.
- * @param {string} path Path to splat scene to be loaded
- * @param {number} splatAlphaRemovalThreshold Ignore any splats with an alpha less than the specified
- * value (valid range: 0 - 255), defaults to 1
- *
- * @param {function} onProgress Function to be called as file data are received
- * @param {boolean} progressiveBuild Construct file sections into splat buffers as they are downloaded
- * @param {function} onSectionBuilt Function to be called when new section is added to the file
- * @param {string} format File format of the scene
- * @return {AbortablePromise}
- */
- downloadSplatSceneToSplatBuffer(path, splatAlphaRemovalThreshold = 1, onProgress = undefined,
- progressiveBuild = false, onSectionBuilt = undefined, format) {
- if (format === SceneFormat.Splat) {
- return SplatLoader.loadFromURL(path, onProgress, progressiveBuild,
- onSectionBuilt, splatAlphaRemovalThreshold, 0, false);
- } else if (format === SceneFormat.KSplat) {
- return KSplatLoader.loadFromURL(path, onProgress, progressiveBuild, onSectionBuilt);
- } else if (format === SceneFormat.Ply) {
- return PlyLoader.loadFromURL(path, onProgress, progressiveBuild, onSectionBuilt,
- splatAlphaRemovalThreshold, this.plyInMemoryCompressionLevel, this.sphericalHarmonicsDegree);
- }
- throw new Error(`Viewer::downloadSplatSceneToSplatBuffer -> File format not supported: ${path}`);
- }
- static isProgressivelyLoadable(format) {
- return format === SceneFormat.Splat || format === SceneFormat.KSplat || format === SceneFormat.Ply;
- }
- /**
- * Add one or more instances of SplatBuffer to the SplatMesh instance managed by the viewer and set up the sorting web worker.
- * This function will terminate the existing sort worker (if there is one).
- */
- addSplatBuffers = function() {
- return function(splatBuffers, splatBufferOptions = [], finalBuild = true, showLoadingUI = true,
- showLoadingUIForSplatTreeBuild = true, replaceExisting = false,
- enableRenderBeforeFirstSort = false, preserveVisibleRegion = true) {
- if (this.isDisposingOrDisposed()) return Promise.resolve();
- this.splatRenderReady = false;
- let splatProcessingTaskId = null;
- const removeSplatProcessingTask = () => {
- if (splatProcessingTaskId !== null) {
- this.loadingSpinner.removeTask(splatProcessingTaskId);
- splatProcessingTaskId = null;
- }
- };
- const finish = (buildResults, resolver) => {
- if (this.isDisposingOrDisposed()) return;
- // If we aren't calculating the splat distances from the center on the GPU, the sorting worker needs splat centers and
- // transform indexes so that it can calculate those distance values.
- if (!this.gpuAcceleratedSort && this.sortWorker) {
- this.sortWorker.postMessage({
- 'centers': buildResults.centers.buffer,
- 'sceneIndexes': buildResults.sceneIndexes.buffer,
- 'range': {
- 'from': buildResults.from,
- 'to': buildResults.to,
- 'count': buildResults.count
- }
- });
- }
- this.runSplatSort(true).then((sortRunning) => {
- if (!this.sortWorker || !sortRunning) {
- this.splatRenderReady = true;
- removeSplatProcessingTask();
- resolver();
- } else {
- if (enableRenderBeforeFirstSort) {
- this.splatRenderReady = true;
- } else {
- this.runAfterNextSort.push(() => {
- this.splatRenderReady = true;
- });
- }
- this.runAfterNextSort.push(() => {
- removeSplatProcessingTask();
- resolver();
- });
- }
- });
- };
- return new Promise((resolve) => {
- if (showLoadingUI) {
- splatProcessingTaskId = this.loadingSpinner.addTask('Processing splats...');
- }
- delayedExecute(() => {
- if (this.isDisposingOrDisposed()) {
- resolve();
- } else {
- const buildResults = this.addSplatBuffersToMesh(splatBuffers, splatBufferOptions, finalBuild,
- showLoadingUIForSplatTreeBuild, replaceExisting,
- preserveVisibleRegion);
- const maxSplatCount = this.splatMesh.getMaxSplatCount();
- if (this.sortWorker && this.sortWorker.maxSplatCount !== maxSplatCount) this.disposeSortWorker();
- const sortWorkerSetupPromise = (!this.sortWorker && maxSplatCount > 0) ?
- this.setupSortWorker(this.splatMesh) : Promise.resolve();
- sortWorkerSetupPromise.then(() => {
- finish(buildResults, resolve);
- });
- }
- }, true);
- });
- };
- }();
- /**
- * Add one or more instances of SplatBuffer to the SplatMesh instance managed by the viewer. By default, this function is additive;
- * all splat buffers contained by the viewer's splat mesh before calling this function will be preserved. This behavior can be
- * changed by passing 'true' for 'replaceExisting'.
- * @param {Array<SplatBuffer>} splatBuffers SplatBuffer instances
- * @param {Array<object>} splatBufferOptions Array of options objects: {
- *
- * splatAlphaRemovalThreshold: Ignore any splats with an alpha less than the specified
- * value (valid range: 0 - 255), defaults to 1
- *
- * position (Array<number>): Position of the scene, acts as an offset from its default position, defaults to [0, 0, 0]
- *
- * rotation (Array<number>): Rotation of the scene represented as a quaternion, defaults to [0, 0, 0, 1]
- *
- * scale (Array<number>): Scene's scale, defaults to [1, 1, 1]
- * }
- * @param {boolean} finalBuild Will the splat mesh be in its final state after this build?
- * @param {boolean} showLoadingUIForSplatTreeBuild Whether or not to show the loading spinner during construction of the splat tree.
- * @return {object} Object containing info about the splats that are updated
- */
- addSplatBuffersToMesh = function() {
- let splatOptimizingTaskId;
- return function(splatBuffers, splatBufferOptions, finalBuild = true, showLoadingUIForSplatTreeBuild = false,
- replaceExisting = false, preserveVisibleRegion = true) {
- if (this.isDisposingOrDisposed()) return;
- let allSplatBuffers = [];
- let allSplatBufferOptions = [];
- if (!replaceExisting) {
- allSplatBuffers = this.splatMesh.scenes.map((scene) => scene.splatBuffer) || [];
- allSplatBufferOptions = this.splatMesh.sceneOptions ? this.splatMesh.sceneOptions.map((sceneOptions) => sceneOptions) : [];
- }
- allSplatBuffers.push(...splatBuffers);
- allSplatBufferOptions.push(...splatBufferOptions);
- if (this.renderer) this.splatMesh.setRenderer(this.renderer);
- const onSplatTreeIndexesUpload = (finished) => {
- if (this.isDisposingOrDisposed()) return;
- const splatCount = this.splatMesh.getSplatCount();
- if (showLoadingUIForSplatTreeBuild && splatCount >= MIN_SPLAT_COUNT_TO_SHOW_SPLAT_TREE_LOADING_SPINNER) {
- if (!finished && !splatOptimizingTaskId) {
- this.loadingSpinner.setMinimized(true, true);
- splatOptimizingTaskId = this.loadingSpinner.addTask('Optimizing splats...');
- }
- }
- };
- const onSplatTreeReady = (finished) => {
- if (this.isDisposingOrDisposed()) return;
- if (finished && splatOptimizingTaskId) {
- this.loadingSpinner.removeTask(splatOptimizingTaskId);
- splatOptimizingTaskId = null;
- }
- };
- const buildResults = this.splatMesh.build(allSplatBuffers, allSplatBufferOptions, true, finalBuild, onSplatTreeIndexesUpload,
- onSplatTreeReady, preserveVisibleRegion);
- if (finalBuild && this.freeIntermediateSplatData) this.splatMesh.freeIntermediateSplatData();
- return buildResults;
- };
- }();
- /**
- * Set up the splat sorting web worker.
- * @param {SplatMesh} splatMesh SplatMesh instance that contains the splats to be sorted
- * @return {Promise}
- */
- setupSortWorker(splatMesh) {
- if (this.isDisposingOrDisposed()) return;
- return new Promise((resolve) => {
- const DistancesArrayType = this.integerBasedSort ? Int32Array : Float32Array;
- const splatCount = splatMesh.getSplatCount();
- const maxSplatCount = splatMesh.getMaxSplatCount();
- this.sortWorker = createSortWorker(maxSplatCount, this.sharedMemoryForWorkers, this.enableSIMDInSort,
- this.integerBasedSort, this.splatMesh.dynamicMode);
- this.sortWorker.onmessage = (e) => {
- if (e.data.sortDone) {
- this.sortRunning = false;
- if (this.sharedMemoryForWorkers) {
- this.splatMesh.updateRenderIndexes(this.sortWorkerSortedIndexes, e.data.splatRenderCount);
- } else {
- const sortedIndexes = new Uint32Array(e.data.sortedIndexes.buffer, 0, e.data.splatRenderCount);
- this.splatMesh.updateRenderIndexes(sortedIndexes, e.data.splatRenderCount);
- }
- this.lastSortTime = e.data.sortTime;
- this.sortPromiseResolver();
- this.sortPromiseResolver = null;
- this.forceRenderNextFrame();
- if (this.runAfterNextSort.length > 0) {
- this.runAfterNextSort.forEach((func) => {
- func();
- });
- this.runAfterNextSort.length = 0;
- }
- } else if (e.data.sortCanceled) {
- this.sortRunning = false;
- } else if (e.data.sortSetupPhase1Complete) {
- if (this.logLevel >= LogLevel.Info) console.log('Sorting web worker WASM setup complete.');
- if (this.sharedMemoryForWorkers) {
- this.sortWorkerSortedIndexes = new Uint32Array(e.data.sortedIndexesBuffer,
- e.data.sortedIndexesOffset, maxSplatCount);
- this.sortWorkerIndexesToSort = new Uint32Array(e.data.indexesToSortBuffer,
- e.data.indexesToSortOffset, maxSplatCount);
- this.sortWorkerPrecomputedDistances = new DistancesArrayType(e.data.precomputedDistancesBuffer,
- e.data.precomputedDistancesOffset,
- maxSplatCount);
- this.sortWorkerTransforms = new Float32Array(e.data.transformsBuffer,
- e.data.transformsOffset, Constants.MaxScenes * 16);
- } else {
- this.sortWorkerIndexesToSort = new Uint32Array(maxSplatCount);
- this.sortWorkerPrecomputedDistances = new DistancesArrayType(maxSplatCount);
- this.sortWorkerTransforms = new Float32Array(Constants.MaxScenes * 16);
- }
- for (let i = 0; i < splatCount; i++) this.sortWorkerIndexesToSort[i] = i;
- this.sortWorker.maxSplatCount = maxSplatCount;
- if (this.logLevel >= LogLevel.Info) {
- console.log('Sorting web worker ready.');
- const splatDataTextures = this.splatMesh.getSplatDataTextures();
- const covariancesTextureSize = splatDataTextures.covariances.size;
- const centersColorsTextureSize = splatDataTextures.centerColors.size;
- console.log('Covariances texture size: ' + covariancesTextureSize.x + ' x ' + covariancesTextureSize.y);
- console.log('Centers/colors texture size: ' + centersColorsTextureSize.x + ' x ' + centersColorsTextureSize.y);
- }
- resolve();
- }
- };
- });
- }
- disposeSortWorker() {
- if (this.sortWorker) this.sortWorker.terminate();
- this.sortWorker = null;
- this.sortPromise = null;
- if (this.sortPromiseResolver) {
- this.sortPromiseResolver();
- this.sortPromiseResolver = null;
- }
- this.sortRunning = false;
- }
- removeSplatScene(indexToRemove, showLoadingUI = true) {
- return this.removeSplatScenes([indexToRemove], showLoadingUI);
- }
- removeSplatScenes(indexesToRemove, showLoadingUI = true) {
- if (this.isLoadingOrUnloading()) {
- throw new Error('Cannot remove splat scene while another load or unload is already in progress.');
- }
- if (this.isDisposingOrDisposed()) {
- throw new Error('Cannot remove splat scene after dispose() is called.');
- }
- let sortPromise;
- this.splatSceneRemovalPromise = new Promise((resolve, reject) => {
- let revmovalTaskId;
- if (showLoadingUI) {
- this.loadingSpinner.removeAllTasks();
- this.loadingSpinner.show();
- revmovalTaskId = this.loadingSpinner.addTask('Removing splat scene...');
- }
- const checkAndHideLoadingUI = () => {
- if (showLoadingUI) {
- this.loadingSpinner.hide();
- this.loadingSpinner.removeTask(revmovalTaskId);
- }
- };
- const onDone = (error) => {
- checkAndHideLoadingUI();
- this.splatSceneRemovalPromise = null;
- if (!error) resolve();
- else reject(error);
- };
- const checkForEarlyExit = () => {
- if (this.isDisposingOrDisposed()) {
- onDone();
- return true;
- }
- return false;
- };
- sortPromise = this.sortPromise || Promise.resolve();
- sortPromise.then(() => {
- if (checkForEarlyExit()) return;
- const savedSplatBuffers = [];
- const savedSceneOptions = [];
- const savedSceneTransformComponents = [];
- for (let i = 0; i < this.splatMesh.scenes.length; i++) {
- let shouldRemove = false;
- for (let indexToRemove of indexesToRemove) {
- if (indexToRemove === i) {
- shouldRemove = true;
- break;
- }
- }
- if (!shouldRemove) {
- const scene = this.splatMesh.scenes[i];
- savedSplatBuffers.push(scene.splatBuffer);
- savedSceneOptions.push(this.splatMesh.sceneOptions[i]);
- savedSceneTransformComponents.push({
- 'position': scene.position.clone(),
- 'quaternion': scene.quaternion.clone(),
- 'scale': scene.scale.clone()
- });
- }
- }
- this.disposeSortWorker();
- this.splatMesh.dispose();
- this.sceneRevealMode = SceneRevealMode.Instant;
- this.createSplatMesh();
- this.addSplatBuffers(savedSplatBuffers, savedSceneOptions, true, false, true)
- .then(() => {
- if (checkForEarlyExit()) return;
- checkAndHideLoadingUI();
- this.splatMesh.scenes.forEach((scene, index) => {
- scene.position.copy(savedSceneTransformComponents[index].position);
- scene.quaternion.copy(savedSceneTransformComponents[index].quaternion);
- scene.scale.copy(savedSceneTransformComponents[index].scale);
- });
- this.splatMesh.updateTransforms();
- this.splatRenderReady = false;
- this.runSplatSort(true)
- .then(() => {
- if (checkForEarlyExit()) {
- this.splatRenderReady = true;
- return;
- }
- sortPromise = this.sortPromise || Promise.resolve();
- sortPromise.then(() => {
- this.splatRenderReady = true;
- onDone();
- });
- });
- })
- .catch((e) => {
- onDone(e);
- });
- });
- });
- return this.splatSceneRemovalPromise;
- }
- /**
- * Start self-driven mode
- */
- start() {
- if (this.selfDrivenMode) {
- if (this.webXRMode) {
- this.renderer.setAnimationLoop(this.selfDrivenUpdateFunc);
- } else {
- this.requestFrameId = requestAnimationFrame(this.selfDrivenUpdateFunc);
- }
- this.selfDrivenModeRunning = true;
- } else {
- throw new Error('Cannot start viewer unless it is in self driven mode.');
- }
- }
- /**
- * Stop self-driven mode
- */
- stop() {
- if (this.selfDrivenMode && this.selfDrivenModeRunning) {
- if (!this.webXRMode) {
- cancelAnimationFrame(this.requestFrameId);
- }
- this.selfDrivenModeRunning = false;
- }
- }
- /**
- * Dispose of all resources held directly and indirectly by this viewer.
- */
- async dispose() {
- if (this.isDisposingOrDisposed()) return this.disposePromise;
- let waitPromises = [];
- let promisesToAbort = [];
- for (let promiseKey in this.splatSceneDownloadPromises) {
- if (this.splatSceneDownloadPromises.hasOwnProperty(promiseKey)) {
- const downloadPromiseToAbort = this.splatSceneDownloadPromises[promiseKey];
- promisesToAbort.push(downloadPromiseToAbort);
- waitPromises.push(downloadPromiseToAbort.promise);
- }
- }
- if (this.sortPromise) {
- waitPromises.push(this.sortPromise);
- }
- this.disposing = true;
- this.disposePromise = Promise.all(waitPromises).finally(() => {
- this.stop();
- if (this.controls) {
- this.controls.dispose();
- this.controls = null;
- }
- if (this.splatMesh) {
- this.splatMesh.dispose();
- this.splatMesh = null;
- }
- if (this.sceneHelper) {
- this.sceneHelper.dispose();
- this.sceneHelper = null;
- }
- if (this.resizeObserver) {
- this.resizeObserver.unobserve(this.rootElement);
- this.resizeObserver = null;
- }
- this.disposeSortWorker();
- this.removeEventHandlers();
- this.loadingSpinner.removeAllTasks();
- this.loadingSpinner.setContainer(null);
- this.loadingProgressBar.hide();
- this.loadingProgressBar.setContainer(null);
- this.infoPanel.setContainer(null);
- this.camera = null;
- this.threeScene = null;
- this.splatRenderReady = false;
- this.initialized = false;
- if (this.renderer) {
- if (!this.usingExternalRenderer) {
- this.rootElement.removeChild(this.renderer.domElement);
- this.renderer.dispose();
- }
- this.renderer = null;
- }
- if (!this.usingExternalRenderer) {
- document.body.removeChild(this.rootElement);
- }
- this.sortWorkerSortedIndexes = null;
- this.sortWorkerIndexesToSort = null;
- this.sortWorkerPrecomputedDistances = null;
- this.sortWorkerTransforms = null;
- this.disposed = true;
- this.disposing = false;
- this.disposePromise = null;
- });
- promisesToAbort.forEach((toAbort) => {
- toAbort.abort('Scene disposed');
- });
- return this.disposePromise;
- }
- selfDrivenUpdate() {
- /* if (this.selfDrivenMode && !this.webXRMode) { //xzw delete 改用自己的
- this.requestFrameId = requestAnimationFrame(this.selfDrivenUpdateFunc);
- } */
- this.update();
- if (this.shouldRender()) {
- this.render();
- this.consecutiveRenderFrames++;
- } else {
- this.consecutiveRenderFrames = 0;
- }
- this.renderNextFrame = false;
- }
- forceRenderNextFrame() {
- this.renderNextFrame = true;
- }
- shouldRender = function() {
- let renderCount = 0;
- const lastCameraPosition = new THREE.Vector3();
- const lastCameraOrientation = new THREE.Quaternion();
- const changeEpsilon = 0.0001;
- return function() {
- let shouldRender = false;
- let cameraChanged = false;
- if (this.camera) {
- const cp = this.camera.position;
- const co = this.camera.quaternion;
- cameraChanged = Math.abs(cp.x - lastCameraPosition.x) > changeEpsilon ||
- Math.abs(cp.y - lastCameraPosition.y) > changeEpsilon ||
- Math.abs(cp.z - lastCameraPosition.z) > changeEpsilon ||
- Math.abs(co.x - lastCameraOrientation.x) > changeEpsilon ||
- Math.abs(co.y - lastCameraOrientation.y) > changeEpsilon ||
- Math.abs(co.z - lastCameraOrientation.z) > changeEpsilon ||
- Math.abs(co.w - lastCameraOrientation.w) > changeEpsilon;
- }
- shouldRender = this.renderMode !== RenderMode.Never && (renderCount === 0 || this.splatMesh.visibleRegionChanging ||
- cameraChanged || this.renderMode === RenderMode.Always || this.dynamicMode === true || this.renderNextFrame);
- if (this.camera) {
- lastCameraPosition.copy(this.camera.position);
- lastCameraOrientation.copy(this.camera.quaternion);
- }
- renderCount++;
- return shouldRender;
- };
- }();
- render = function() {
- return function() {
- if (!this.initialized || !this.splatRenderReady) return;
- const hasRenderables = (threeScene) => {
- for (let child of threeScene.children) {
- if (child.visible) return true;
- }
- return false;
- };
- let oldLayer = this.camera.layers.mask
- this.camera.layers.set(0)//xzw add
- const savedAuoClear = this.renderer.autoClear;
- if (hasRenderables(this.threeScene)) {
- this.renderer.render(this.threeScene, this.camera);
- this.renderer.autoClear = false;
- }
- this.renderer.render(this.splatMesh, this.camera);
- this.renderer.autoClear = false;
- if (this.sceneHelper.getFocusMarkerOpacity() > 0.0) this.renderer.render(this.sceneHelper.focusMarker, this.camera);
- if (this.showControlPlane) this.renderer.render(this.sceneHelper.controlPlane, this.camera);
- this.renderer.autoClear = savedAuoClear;
- };
- this.camera.layers.mask = oldLayer
- }();
- update(renderer, camera) {
- if (this.dropInMode) this.updateForDropInMode(renderer, camera);
- if (!this.initialized || !this.splatRenderReady) return;
- if (this.controls) {
- this.controls.update();
- if (this.camera.isOrthographicCamera && !this.usingExternalCamera) {
- Viewer.setCameraPositionFromZoom(this.camera, this.camera, this.controls);
- }
- }
- this.runSplatSort();
- this.updateForRendererSizeChanges();
- this.updateSplatMesh();
- this.updateMeshCursor();
- this.updateFPS();
- this.timingSensitiveUpdates();
- this.updateInfoPanel();
- this.updateControlPlane();
-
- }
- updateForDropInMode(renderer, camera) {
- this.renderer = renderer;
- if (this.splatMesh) this.splatMesh.setRenderer(this.renderer);
- this.camera = camera;
- if (this.controls) this.controls.object = camera;
- this.init();
- }
- updateFPS = function() {
- let lastCalcTime = getCurrentTime();
- let frameCount = 0;
- return function() {
- if (this.consecutiveRenderFrames > CONSECUTIVE_RENDERED_FRAMES_FOR_FPS_CALCULATION) {
- const currentTime = getCurrentTime();
- const calcDelta = currentTime - lastCalcTime;
- if (calcDelta >= 1.0) {
- this.currentFPS = frameCount;
- frameCount = 0;
- lastCalcTime = currentTime;
- } else {
- frameCount++;
- }
- } else {
- this.currentFPS = null;
- }
- };
- }();
- updateForRendererSizeChanges = function() {
- const lastRendererSize = new THREE.Vector2();
- const currentRendererSize = new THREE.Vector2();
- let lastCameraOrthographic;
- return function() {
- if (!this.usingExternalCamera) {
- this.renderer.getSize(currentRendererSize);
- if (lastCameraOrthographic === undefined || lastCameraOrthographic !== this.camera.isOrthographicCamera ||
- currentRendererSize.x !== lastRendererSize.x || currentRendererSize.y !== lastRendererSize.y) {
- if (this.camera.isOrthographicCamera) {
- this.camera.left = -currentRendererSize.x / 2.0;
- this.camera.right = currentRendererSize.x / 2.0;
- this.camera.top = currentRendererSize.y / 2.0;
- this.camera.bottom = -currentRendererSize.y / 2.0;
- } else {
- this.camera.aspect = currentRendererSize.x / currentRendererSize.y;
- }
- this.camera.updateProjectionMatrix();
- lastRendererSize.copy(currentRendererSize);
- lastCameraOrthographic = this.camera.isOrthographicCamera;
- }
- }
- };
- }();
- timingSensitiveUpdates = function() {
- let lastUpdateTime;
- return function() {
- const currentTime = getCurrentTime();
- if (!lastUpdateTime) lastUpdateTime = currentTime;
- const timeDelta = currentTime - lastUpdateTime;
- this.updateCameraTransition(currentTime);
- this.updateFocusMarker(timeDelta);
- lastUpdateTime = currentTime;
- };
- }();
- updateCameraTransition = function() {
- let tempCameraTarget = new THREE.Vector3();
- let toPreviousTarget = new THREE.Vector3();
- let toNextTarget = new THREE.Vector3();
- return function(currentTime) {
- if (this.transitioningCameraTarget) {
- toPreviousTarget.copy(this.previousCameraTarget).sub(this.camera.position).normalize();
- toNextTarget.copy(this.nextCameraTarget).sub(this.camera.position).normalize();
- const rotationAngle = Math.acos(toPreviousTarget.dot(toNextTarget));
- const rotationSpeed = rotationAngle / (Math.PI / 3) * .65 + .3;
- const t = (rotationSpeed / rotationAngle * (currentTime - this.transitioningCameraTargetStartTime));
- tempCameraTarget.copy(this.previousCameraTarget).lerp(this.nextCameraTarget, t);
- this.camera.lookAt(tempCameraTarget);
- this.controls.target.copy(tempCameraTarget);
- if (t >= 1.0) {
- this.transitioningCameraTarget = false;
- }
- }
- };
- }();
- updateFocusMarker = function() {
- const renderDimensions = new THREE.Vector2();
- let wasTransitioning = false;
- return function(timeDelta) {
- this.getRenderDimensions(renderDimensions);
- if (this.transitioningCameraTarget) {
- this.sceneHelper.setFocusMarkerVisibility(true);
- const currentFocusMarkerOpacity = Math.max(this.sceneHelper.getFocusMarkerOpacity(), 0.0);
- let newFocusMarkerOpacity = Math.min(currentFocusMarkerOpacity + FOCUS_MARKER_FADE_IN_SPEED * timeDelta, 1.0);
- this.sceneHelper.setFocusMarkerOpacity(newFocusMarkerOpacity);
- this.sceneHelper.updateFocusMarker(this.nextCameraTarget, this.camera, renderDimensions);
- wasTransitioning = true;
- this.forceRenderNextFrame();
- } else {
- let currentFocusMarkerOpacity;
- if (wasTransitioning) currentFocusMarkerOpacity = 1.0;
- else currentFocusMarkerOpacity = Math.min(this.sceneHelper.getFocusMarkerOpacity(), 1.0);
- if (currentFocusMarkerOpacity > 0) {
- this.sceneHelper.updateFocusMarker(this.nextCameraTarget, this.camera, renderDimensions);
- let newFocusMarkerOpacity = Math.max(currentFocusMarkerOpacity - FOCUS_MARKER_FADE_OUT_SPEED * timeDelta, 0.0);
- this.sceneHelper.setFocusMarkerOpacity(newFocusMarkerOpacity);
- if (newFocusMarkerOpacity === 0.0) this.sceneHelper.setFocusMarkerVisibility(false);
- }
- if (currentFocusMarkerOpacity > 0.0) this.forceRenderNextFrame();
- wasTransitioning = false;
- }
- };
- }();
- updateMeshCursor = function() {
- const outHits = [];
- const renderDimensions = new THREE.Vector2();
- return function() {
- if (this.showMeshCursor) {
- this.forceRenderNextFrame();
- this.getRenderDimensions(renderDimensions);
- outHits.length = 0;
- this.raycaster.setFromCameraAndScreenPosition(this.camera, this.mousePosition, renderDimensions);
- this.raycaster.intersectSplatMesh(this.splatMesh, outHits);
- if (outHits.length > 0) {
- this.sceneHelper.setMeshCursorVisibility(true);
- this.sceneHelper.positionAndOrientMeshCursor(outHits[0].origin, this.camera);
- } else {
- this.sceneHelper.setMeshCursorVisibility(false);
- }
- } else {
- if (this.sceneHelper.getMeschCursorVisibility()) this.forceRenderNextFrame();
- this.sceneHelper.setMeshCursorVisibility(false);
- }
- };
- }();
- updateInfoPanel = function() {
- const renderDimensions = new THREE.Vector2();
- return function() {
- if (!this.showInfo) return;
- const splatCount = this.splatMesh.getSplatCount();
- this.getRenderDimensions(renderDimensions);
- const cameraLookAtPosition = this.controls ? this.controls.target : null;
- const meshCursorPosition = this.showMeshCursor ? this.sceneHelper.meshCursor.position : null;
- const splatRenderCountPct = splatCount > 0 ? this.splatRenderCount / splatCount * 100 : 0;
- this.infoPanel.update(renderDimensions, this.camera.position, cameraLookAtPosition,
- this.camera.up, this.camera.isOrthographicCamera, meshCursorPosition,
- this.currentFPS || 'N/A', splatCount, this.splatRenderCount, splatRenderCountPct,
- this.lastSortTime, this.focalAdjustment, this.splatMesh.getSplatScale(),
- this.splatMesh.getPointCloudModeEnabled());
- };
- }();
- updateControlPlane() {
- if (this.showControlPlane) {
- this.sceneHelper.setControlPlaneVisibility(true);
- this.sceneHelper.positionAndOrientControlPlane(this.controls.target, this.camera.up);
- } else {
- this.sceneHelper.setControlPlaneVisibility(false);
- }
- }
- runSplatSort = function() {
- const mvpMatrix = new THREE.Matrix4();
- const cameraPositionArray = [];
- const lastSortViewDir = new THREE.Vector3(0, 0, -1);
- const sortViewDir = new THREE.Vector3(0, 0, -1);
- const lastSortViewPos = new THREE.Vector3();
- const sortViewOffset = new THREE.Vector3();
- const queuedSorts = [];
- const partialSorts = [
- {
- 'angleThreshold': 0.55,
- 'sortFractions': [0.125, 0.33333, 0.75]
- },
- {
- 'angleThreshold': 0.65,
- 'sortFractions': [0.33333, 0.66667]
- },
- {
- 'angleThreshold': 0.8,
- 'sortFractions': [0.5]
- }
- ];
- return function(force = false) {
- if (!this.initialized) return Promise.resolve(false);
- if (this.sortRunning) return Promise.resolve(true);
- if (this.splatMesh.getSplatCount() <= 0) {
- this.splatRenderCount = 0;
- return false;
- }
- let angleDiff = 0;
- let positionDiff = 0;
- let needsRefreshForRotation = false;
- let needsRefreshForPosition = false;
- sortViewDir.set(0, 0, -1).applyQuaternion(this.camera.quaternion);
- angleDiff = sortViewDir.dot(lastSortViewDir);
- positionDiff = sortViewOffset.copy(this.camera.position).sub(lastSortViewPos).length();
- if (!force) {
- if (!this.splatMesh.dynamicMode && queuedSorts.length === 0) {
- if (angleDiff <= 0.99) needsRefreshForRotation = true;
- if (positionDiff >= 1.0) needsRefreshForPosition = true;
- if (!needsRefreshForRotation && !needsRefreshForPosition) return Promise.resolve(false);
- }
- }
- this.sortRunning = true;
- const { splatRenderCount, shouldSortAll } = this.gatherSceneNodesForSort();
- this.splatRenderCount = splatRenderCount;
- mvpMatrix.copy(this.camera.matrixWorld).invert();
- const mvpCamera = this.perspectiveCamera || this.camera;
- mvpMatrix.premultiply(mvpCamera.projectionMatrix);
- mvpMatrix.multiply(this.splatMesh.matrixWorld);
- let gpuAcceleratedSortPromise = Promise.resolve();
- if (this.gpuAcceleratedSort && (queuedSorts.length <= 1 || queuedSorts.length % 2 === 0)) {
- gpuAcceleratedSortPromise = this.splatMesh.computeDistancesOnGPU(mvpMatrix, this.sortWorkerPrecomputedDistances);
- }
- gpuAcceleratedSortPromise.then(() => {
- if (this.splatMesh.dynamicMode || shouldSortAll) {
- queuedSorts.push(this.splatRenderCount);
- } else {
- if (queuedSorts.length === 0) {
- for (let partialSort of partialSorts) {
- if (angleDiff < partialSort.angleThreshold) {
- for (let sortFraction of partialSort.sortFractions) {
- queuedSorts.push(Math.floor(this.splatRenderCount * sortFraction));
- }
- break;
- }
- }
- queuedSorts.push(this.splatRenderCount);
- }
- }
- let sortCount = Math.min(queuedSorts.shift(), this.splatRenderCount);
- cameraPositionArray[0] = this.camera.position.x;
- cameraPositionArray[1] = this.camera.position.y;
- cameraPositionArray[2] = this.camera.position.z;
- const sortMessage = {
- 'modelViewProj': mvpMatrix.elements,
- 'cameraPosition': cameraPositionArray,
- 'splatRenderCount': this.splatRenderCount,
- 'splatSortCount': sortCount,
- 'usePrecomputedDistances': this.gpuAcceleratedSort
- };
- if (this.splatMesh.dynamicMode) {
- this.splatMesh.fillTransformsArray(this.sortWorkerTransforms);
- }
- if (!this.sharedMemoryForWorkers) {
- sortMessage.indexesToSort = this.sortWorkerIndexesToSort;
- sortMessage.transforms = this.sortWorkerTransforms;
- if (this.gpuAcceleratedSort) {
- sortMessage.precomputedDistances = this.sortWorkerPrecomputedDistances;
- }
- }
- this.sortPromise = new Promise((resolve) => {
- this.sortPromiseResolver = resolve;
- });
- this.sortWorker.postMessage({
- 'sort': sortMessage
- });
- if (queuedSorts.length === 0) {
- lastSortViewPos.copy(this.camera.position);
- lastSortViewDir.copy(sortViewDir);
- }
- });
- return gpuAcceleratedSortPromise;
- };
- }();
- /**
- * Determine which splats to render by checking which are inside or close to the view frustum
- */
- gatherSceneNodesForSort = function() {
- const nodeRenderList = [];
- let allSplatsSortBuffer = null;
- const tempVectorYZ = new THREE.Vector3();
- const tempVectorXZ = new THREE.Vector3();
- const tempVector = new THREE.Vector3();
- const modelView = new THREE.Matrix4();
- const baseModelView = new THREE.Matrix4();
- const sceneTransform = new THREE.Matrix4();
- const renderDimensions = new THREE.Vector3();
- const forward = new THREE.Vector3(0, 0, -1);
- const tempMax = new THREE.Vector3();
- const nodeSize = (node) => {
- return tempMax.copy(node.max).sub(node.min).length();
- };
- return function(gatherAllNodes = false) {
- this.getRenderDimensions(renderDimensions);
- const cameraFocalLength = (renderDimensions.y / 2.0) / Math.tan(this.camera.fov / 2.0 * THREE.MathUtils.DEG2RAD);
- const fovXOver2 = Math.atan(renderDimensions.x / 2.0 / cameraFocalLength);
- const fovYOver2 = Math.atan(renderDimensions.y / 2.0 / cameraFocalLength);
- const cosFovXOver2 = Math.cos(fovXOver2);
- const cosFovYOver2 = Math.cos(fovYOver2);
- const splatTree = this.splatMesh.getSplatTree();
- if (splatTree) {
- baseModelView.copy(this.camera.matrixWorld).invert();
- baseModelView.multiply(this.splatMesh.matrixWorld);
- let nodeRenderCount = 0;
- let splatRenderCount = 0;
- for (let s = 0; s < splatTree.subTrees.length; s++) {
- const subTree = splatTree.subTrees[s];
- modelView.copy(baseModelView);
- if (this.splatMesh.dynamicMode) {
- this.splatMesh.getSceneTransform(s, sceneTransform);
- modelView.multiply(sceneTransform);
- }
- const nodeCount = subTree.nodesWithIndexes.length;
- for (let i = 0; i < nodeCount; i++) {
- const node = subTree.nodesWithIndexes[i];
- if (!node.data || !node.data.indexes || node.data.indexes.length === 0) continue;
- tempVector.copy(node.center).applyMatrix4(modelView);
- const distanceToNode = tempVector.length();
- tempVector.normalize();
- tempVectorYZ.copy(tempVector).setX(0).normalize();
- tempVectorXZ.copy(tempVector).setY(0).normalize();
- const cameraAngleXZDot = forward.dot(tempVectorXZ);
- const cameraAngleYZDot = forward.dot(tempVectorYZ);
- const ns = nodeSize(node);
- const outOfFovY = cameraAngleYZDot < (cosFovYOver2 - .6);
- const outOfFovX = cameraAngleXZDot < (cosFovXOver2 - .6);
- if (!gatherAllNodes && ((outOfFovX || outOfFovY) && distanceToNode > ns)) {
- continue;
- }
- splatRenderCount += node.data.indexes.length;
- nodeRenderList[nodeRenderCount] = node;
- node.data.distanceToNode = distanceToNode;
- nodeRenderCount++;
- }
- }
- nodeRenderList.length = nodeRenderCount;
- nodeRenderList.sort((a, b) => {
- if (a.data.distanceToNode < b.data.distanceToNode) return -1;
- else return 1;
- });
- let currentByteOffset = splatRenderCount * Constants.BytesPerInt;
- for (let i = 0; i < nodeRenderCount; i++) {
- const node = nodeRenderList[i];
- const windowSizeInts = node.data.indexes.length;
- const windowSizeBytes = windowSizeInts * Constants.BytesPerInt;
- let destView = new Uint32Array(this.sortWorkerIndexesToSort.buffer,
- currentByteOffset - windowSizeBytes, windowSizeInts);
- destView.set(node.data.indexes);
- currentByteOffset -= windowSizeBytes;
- }
- return {
- 'splatRenderCount': splatRenderCount,
- 'shouldSortAll': false
- };
- } else {
- const totalSplatCount = this.splatMesh.getSplatCount();
- if (!allSplatsSortBuffer || allSplatsSortBuffer.length !== totalSplatCount) {
- allSplatsSortBuffer = new Uint32Array(totalSplatCount);
- for (let i = 0; i < totalSplatCount; i++) {
- allSplatsSortBuffer[i] = i;
- }
- }
- this.sortWorkerIndexesToSort.set(allSplatsSortBuffer);
- return {
- 'splatRenderCount': totalSplatCount,
- 'shouldSortAll': true
- };
- }
- };
- }();
- getSplatMesh() {
- return this.splatMesh;
- }
- /**
- * Get a reference to a splat scene.
- * @param {number} sceneIndex The index of the scene to which the reference will be returned
- * @return {SplatScene}
- */
- getSplatScene(sceneIndex) {
- return this.splatMesh.getScene(sceneIndex);
- }
- getSceneCount() {
- return this.splatMesh.getSceneCount();
- }
- isMobile() {
- return navigator.userAgent.includes('Mobi');
- }
- }
- /**
- * DropInViewer: Wrapper for a Viewer instance that enables it to be added to a Three.js scene like
- * any other Three.js scene object (Mesh, Object3D, etc.)
- */
- class DropInViewer extends THREE.Group {
- constructor(options = {}) {
- super();
- options.selfDrivenMode = false;
- options.useBuiltInControls = false;
- options.rootElement = null;
- options.ignoreDevicePixelRatio = false;
- options.dropInMode = true;
- options.camera = undefined;
- options.renderer = undefined;
- this.viewer = new Viewer(options);
- this.splatMesh = null;
- this.updateSplatMesh();
- this.callbackMesh = DropInViewer.createCallbackMesh();
- this.add(this.callbackMesh);
- this.callbackMesh.onBeforeRender = DropInViewer.onBeforeRender.bind(this, this.viewer);
- this.viewer.onSplatMeshChanged(() => {
- this.updateSplatMesh();
- });
- }
- updateSplatMesh() {
- if (this.splatMesh !== this.viewer.splatMesh) {
- if (this.splatMesh) {
- this.remove(this.splatMesh);
- }
- this.splatMesh = this.viewer.splatMesh;
- this.add(this.viewer.splatMesh);
- }
- }
- /**
- * Add a single splat scene to the viewer.
- * @param {string} path Path to splat scene to be loaded
- * @param {object} options {
- *
- * splatAlphaRemovalThreshold: Ignore any splats with an alpha less than the specified
- * value (valid range: 0 - 255), defaults to 1
- *
- * showLoadingUI: Display a loading spinner while the scene is loading, defaults to true
- *
- * position (Array<number>): Position of the scene, acts as an offset from its default position, defaults to [0, 0, 0]
- *
- * rotation (Array<number>): Rotation of the scene represented as a quaternion, defaults to [0, 0, 0, 1]
- *
- * scale (Array<number>): Scene's scale, defaults to [1, 1, 1]
- *
- * onProgress: Function to be called as file data are received
- *
- * }
- * @return {AbortablePromise}
- */
- addSplatScene(path, options = {}) {
- if (options.showLoadingUI !== false) options.showLoadingUI = true;
- return this.viewer.addSplatScene(path, options);
- }
- /**
- * Add multiple splat scenes to the viewer.
- * @param {Array<object>} sceneOptions Array of per-scene options: {
- *
- * path: Path to splat scene to be loaded
- *
- * splatAlphaRemovalThreshold: Ignore any splats with an alpha less than the specified
- * value (valid range: 0 - 255), defaults to 1
- *
- * position (Array<number>): Position of the scene, acts as an offset from its default position, defaults to [0, 0, 0]
- *
- * rotation (Array<number>): Rotation of the scene represented as a quaternion, defaults to [0, 0, 0, 1]
- *
- * scale (Array<number>): Scene's scale, defaults to [1, 1, 1]
- * }
- * @param {boolean} showLoadingUI Display a loading spinner while the scene is loading, defaults to true
- * @return {AbortablePromise}
- */
- addSplatScenes(sceneOptions, showLoadingUI) {
- if (showLoadingUI !== false) showLoadingUI = true;
- return this.viewer.addSplatScenes(sceneOptions, showLoadingUI);
- }
- /**
- * Get a reference to a splat scene.
- * @param {number} sceneIndex The index of the scene to which the reference will be returned
- * @return {SplatScene}
- */
- getSplatScene(sceneIndex) {
- return this.viewer.getSplatScene(sceneIndex);
- }
- removeSplatScene(index, showLoadingUI = true) {
- return this.viewer.removeSplatScene(index, showLoadingUI);
- }
- removeSplatScenes(indexes, showLoadingUI = true) {
- return this.viewer.removeSplatScenes(indexes, showLoadingUI);
- }
- getSceneCount() {
- return this.viewer.getSceneCount();
- }
- dispose() {
- return this.viewer.dispose();
- }
- static onBeforeRender(viewer, renderer, threeScene, camera) {
- viewer.update(renderer, camera);
- }
- static createCallbackMesh() {
- const geometry = new THREE.SphereGeometry(1, 8, 8);
- const material = new THREE.MeshBasicMaterial();
- material.colorWrite = false;
- material.depthWrite = false;
- const mesh = new THREE.Mesh(geometry, material);
- mesh.frustumCulled = false;
- return mesh;
- }
- }
- export { AbortablePromise, DropInViewer, KSplatLoader, Utils as LoaderUtils, LogLevel, OrbitControls, PlayCanvasCompressedPlyParser, PlyLoader, PlyParser, RenderMode, SceneFormat, SceneRevealMode, SplatBuffer, SplatBufferGenerator, SplatLoader, SplatParser, SplatPartitioner, SplatRenderMode, Viewer, WebXRMode
- ,Raycaster //xzw add
- }
- //# sourceMappingURL=gaussian-splats-3d.module.js.map
-
-
-
- /* 2024.9.18 修改如下(目的:修复移动模型后sh的光影错误的bug)
- 如果dynamic为false,直接改变splatScene的position且updateMatrixWorld不会有变化。 也就是它的matrix transform没作用
- viewer.splatMesh.children 变为 [], 没有变化。 所以猜测splatMesh.children无用,只不过是用到它的transform
- 那么初始的位置是直接作用到splatCenter? 改变初始位置后splatTree里的bound变了! dynamic的话就不变
- 如果dynamic为true , 移动scene 光影正确, 但 为什么所有0929的文件dynamic时都pick不到点?!!!! bound因最外层的marixW不对所以不对
- 最后决定:
- 改回用最外层,dynamic,在shader里弃用transform,改为直接用最外层的modelViewMatrix:mat4 transformModelViewMatrix = modelViewMatrix; 这样我就不需要改potree代码了。
- vec3 worldViewDir = normalize(splatCenter - vec3(inverse(modelMatrix) * vec4(cameraPosition, 1.0)));
- splatScene.updateTransform 将transfrom改为matrix, 或者直接单位矩阵。 for pick 和 node visible
- 但0829还是pick不了,发现是castRayAtSplatTreeNode函数中tempScale.x为0,原因未知,只好把这段判断去除。
- */
-
|