123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644464546464647464846494650465146524653465446554656465746584659466046614662466346644665466646674668466946704671467246734674467546764677467846794680468146824683468446854686468746884689469046914692469346944695469646974698469947004701470247034704470547064707470847094710471147124713471447154716471747184719472047214722472347244725472647274728472947304731473247334734473547364737473847394740474147424743474447454746474747484749475047514752475347544755475647574758475947604761476247634764476547664767476847694770477147724773477447754776477747784779478047814782478347844785478647874788478947904791479247934794479547964797479847994800480148024803480448054806480748084809481048114812481348144815481648174818481948204821482248234824482548264827482848294830483148324833483448354836483748384839484048414842484348444845484648474848484948504851485248534854485548564857485848594860486148624863486448654866486748684869487048714872487348744875487648774878487948804881488248834884488548864887488848894890489148924893489448954896489748984899490049014902490349044905490649074908490949104911491249134914491549164917491849194920492149224923492449254926492749284929493049314932493349344935493649374938493949404941494249434944494549464947494849494950495149524953495449554956495749584959496049614962496349644965496649674968496949704971497249734974497549764977497849794980498149824983498449854986498749884989499049914992499349944995499649974998499950005001500250035004500550065007500850095010501150125013501450155016501750185019502050215022502350245025502650275028502950305031503250335034503550365037503850395040504150425043504450455046504750485049505050515052505350545055505650575058505950605061506250635064506550665067506850695070507150725073507450755076507750785079508050815082508350845085508650875088508950905091509250935094509550965097509850995100510151025103510451055106510751085109511051115112511351145115511651175118511951205121512251235124512551265127512851295130513151325133513451355136513751385139514051415142514351445145514651475148514951505151515251535154515551565157515851595160516151625163516451655166516751685169517051715172517351745175517651775178517951805181518251835184518551865187518851895190519151925193519451955196519751985199520052015202520352045205520652075208520952105211521252135214521552165217521852195220522152225223522452255226522752285229523052315232523352345235523652375238523952405241524252435244524552465247524852495250525152525253525452555256525752585259526052615262526352645265526652675268526952705271527252735274527552765277527852795280528152825283528452855286528752885289529052915292529352945295529652975298529953005301530253035304530553065307530853095310531153125313531453155316531753185319532053215322532353245325532653275328532953305331533253335334533553365337533853395340534153425343534453455346534753485349535053515352535353545355535653575358535953605361536253635364536553665367536853695370537153725373537453755376537753785379538053815382538353845385538653875388538953905391539253935394539553965397539853995400540154025403540454055406540754085409541054115412541354145415541654175418541954205421542254235424542554265427542854295430543154325433543454355436543754385439544054415442544354445445544654475448544954505451545254535454545554565457545854595460546154625463546454655466546754685469547054715472547354745475547654775478547954805481548254835484548554865487548854895490549154925493549454955496549754985499550055015502550355045505550655075508550955105511551255135514551555165517551855195520552155225523552455255526552755285529553055315532553355345535553655375538553955405541554255435544554555465547554855495550555155525553555455555556555755585559556055615562556355645565556655675568556955705571557255735574557555765577557855795580558155825583558455855586558755885589559055915592559355945595559655975598559956005601560256035604560556065607560856095610561156125613561456155616561756185619562056215622562356245625562656275628562956305631563256335634563556365637563856395640564156425643564456455646564756485649565056515652565356545655565656575658565956605661566256635664566556665667566856695670567156725673567456755676567756785679568056815682568356845685568656875688568956905691569256935694569556965697569856995700570157025703570457055706570757085709571057115712571357145715571657175718571957205721572257235724572557265727572857295730573157325733573457355736573757385739574057415742574357445745574657475748574957505751575257535754575557565757575857595760576157625763576457655766576757685769577057715772577357745775577657775778577957805781578257835784578557865787578857895790579157925793579457955796579757985799580058015802580358045805580658075808580958105811581258135814581558165817581858195820582158225823582458255826582758285829583058315832583358345835583658375838583958405841584258435844584558465847584858495850585158525853585458555856585758585859586058615862586358645865586658675868586958705871587258735874587558765877587858795880588158825883588458855886588758885889589058915892589358945895589658975898589959005901590259035904590559065907590859095910591159125913591459155916591759185919592059215922592359245925592659275928592959305931593259335934593559365937593859395940594159425943594459455946594759485949595059515952595359545955595659575958595959605961596259635964596559665967596859695970597159725973597459755976597759785979598059815982598359845985598659875988598959905991599259935994599559965997599859996000600160026003600460056006600760086009601060116012601360146015601660176018601960206021602260236024602560266027602860296030603160326033603460356036603760386039604060416042604360446045604660476048604960506051605260536054605560566057605860596060606160626063606460656066606760686069607060716072607360746075607660776078607960806081608260836084608560866087608860896090609160926093609460956096609760986099610061016102610361046105610661076108610961106111611261136114611561166117611861196120612161226123612461256126612761286129613061316132613361346135613661376138613961406141614261436144614561466147614861496150615161526153615461556156615761586159616061616162616361646165616661676168616961706171617261736174617561766177617861796180618161826183618461856186618761886189619061916192619361946195619661976198619962006201620262036204620562066207620862096210621162126213621462156216621762186219622062216222622362246225622662276228622962306231623262336234623562366237623862396240624162426243624462456246624762486249625062516252625362546255625662576258625962606261626262636264626562666267626862696270627162726273627462756276627762786279628062816282628362846285628662876288628962906291629262936294629562966297629862996300630163026303630463056306630763086309631063116312631363146315631663176318631963206321632263236324632563266327632863296330633163326333633463356336633763386339634063416342634363446345634663476348634963506351635263536354635563566357635863596360636163626363636463656366636763686369637063716372637363746375637663776378637963806381638263836384638563866387638863896390639163926393639463956396639763986399640064016402640364046405640664076408640964106411641264136414641564166417641864196420642164226423642464256426642764286429643064316432643364346435643664376438643964406441644264436444644564466447644864496450645164526453645464556456645764586459646064616462646364646465646664676468646964706471647264736474647564766477647864796480648164826483648464856486648764886489649064916492649364946495649664976498649965006501650265036504650565066507650865096510651165126513651465156516651765186519652065216522652365246525652665276528652965306531653265336534653565366537653865396540654165426543654465456546654765486549655065516552655365546555655665576558655965606561656265636564656565666567656865696570657165726573657465756576657765786579658065816582658365846585658665876588658965906591659265936594659565966597659865996600660166026603660466056606660766086609661066116612661366146615661666176618661966206621662266236624662566266627662866296630663166326633663466356636663766386639664066416642664366446645664666476648664966506651665266536654665566566657665866596660666166626663666466656666666766686669667066716672667366746675667666776678667966806681668266836684668566866687668866896690669166926693669466956696669766986699670067016702670367046705670667076708670967106711671267136714671567166717671867196720672167226723672467256726672767286729673067316732673367346735673667376738673967406741674267436744674567466747674867496750675167526753675467556756675767586759676067616762676367646765676667676768676967706771677267736774677567766777677867796780678167826783678467856786678767886789679067916792679367946795679667976798679968006801680268036804680568066807680868096810681168126813681468156816681768186819682068216822682368246825682668276828682968306831683268336834683568366837683868396840684168426843684468456846684768486849685068516852685368546855685668576858685968606861686268636864686568666867686868696870687168726873687468756876687768786879688068816882688368846885688668876888688968906891689268936894689568966897689868996900690169026903690469056906690769086909691069116912691369146915691669176918691969206921692269236924692569266927692869296930693169326933693469356936693769386939694069416942694369446945694669476948694969506951695269536954695569566957695869596960696169626963696469656966696769686969697069716972697369746975697669776978697969806981698269836984698569866987698869896990699169926993699469956996699769986999700070017002700370047005700670077008700970107011701270137014701570167017701870197020702170227023702470257026702770287029703070317032703370347035703670377038703970407041704270437044704570467047704870497050705170527053705470557056705770587059706070617062706370647065706670677068706970707071707270737074707570767077707870797080708170827083708470857086708770887089709070917092709370947095709670977098709971007101710271037104710571067107710871097110711171127113711471157116711771187119712071217122712371247125712671277128712971307131713271337134713571367137713871397140714171427143714471457146714771487149715071517152715371547155715671577158715971607161716271637164716571667167716871697170717171727173717471757176717771787179718071817182718371847185718671877188718971907191719271937194719571967197719871997200720172027203720472057206720772087209721072117212721372147215721672177218721972207221722272237224722572267227722872297230723172327233723472357236723772387239724072417242724372447245724672477248724972507251725272537254725572567257725872597260726172627263726472657266726772687269727072717272727372747275727672777278727972807281728272837284728572867287728872897290729172927293729472957296729772987299730073017302730373047305730673077308730973107311731273137314731573167317731873197320732173227323732473257326732773287329733073317332733373347335733673377338733973407341734273437344734573467347734873497350735173527353735473557356735773587359736073617362736373647365736673677368736973707371737273737374737573767377737873797380738173827383738473857386738773887389739073917392739373947395739673977398739974007401740274037404740574067407740874097410741174127413741474157416741774187419742074217422742374247425742674277428742974307431743274337434743574367437743874397440744174427443744474457446744774487449745074517452745374547455745674577458745974607461746274637464746574667467746874697470747174727473747474757476747774787479748074817482748374847485748674877488748974907491749274937494749574967497749874997500750175027503750475057506750775087509751075117512751375147515751675177518751975207521752275237524752575267527752875297530753175327533753475357536753775387539754075417542754375447545754675477548754975507551755275537554755575567557755875597560756175627563756475657566756775687569757075717572757375747575757675777578757975807581758275837584758575867587758875897590759175927593759475957596759775987599760076017602760376047605760676077608760976107611761276137614761576167617761876197620762176227623762476257626762776287629763076317632763376347635763676377638763976407641764276437644764576467647764876497650765176527653765476557656765776587659766076617662766376647665766676677668766976707671767276737674767576767677767876797680768176827683768476857686768776887689769076917692769376947695769676977698769977007701770277037704770577067707770877097710771177127713771477157716771777187719772077217722772377247725772677277728772977307731773277337734773577367737773877397740774177427743774477457746774777487749775077517752775377547755775677577758775977607761776277637764776577667767776877697770777177727773777477757776777777787779778077817782778377847785778677877788778977907791779277937794779577967797779877997800780178027803780478057806780778087809781078117812781378147815781678177818781978207821782278237824782578267827782878297830783178327833783478357836783778387839784078417842784378447845784678477848784978507851785278537854785578567857785878597860786178627863786478657866786778687869787078717872787378747875787678777878787978807881788278837884788578867887788878897890789178927893789478957896789778987899790079017902790379047905790679077908790979107911791279137914791579167917791879197920792179227923792479257926792779287929793079317932793379347935793679377938793979407941794279437944794579467947794879497950795179527953795479557956795779587959796079617962796379647965796679677968796979707971797279737974797579767977797879797980798179827983798479857986798779887989799079917992799379947995799679977998799980008001800280038004800580068007800880098010801180128013801480158016801780188019802080218022802380248025802680278028802980308031803280338034803580368037803880398040804180428043804480458046804780488049805080518052805380548055805680578058805980608061806280638064806580668067806880698070807180728073807480758076807780788079808080818082808380848085808680878088808980908091809280938094809580968097809880998100810181028103810481058106810781088109811081118112811381148115811681178118811981208121812281238124812581268127812881298130813181328133813481358136813781388139814081418142814381448145814681478148814981508151815281538154815581568157815881598160816181628163816481658166816781688169817081718172817381748175817681778178817981808181818281838184818581868187818881898190819181928193819481958196819781988199820082018202820382048205820682078208820982108211821282138214821582168217821882198220822182228223822482258226822782288229823082318232823382348235823682378238823982408241824282438244824582468247824882498250825182528253825482558256825782588259826082618262826382648265826682678268826982708271827282738274827582768277827882798280828182828283828482858286828782888289829082918292829382948295829682978298829983008301830283038304830583068307830883098310831183128313831483158316831783188319832083218322832383248325832683278328832983308331833283338334833583368337833883398340834183428343834483458346834783488349835083518352835383548355835683578358835983608361836283638364836583668367836883698370837183728373837483758376837783788379838083818382838383848385838683878388838983908391839283938394839583968397839883998400840184028403840484058406840784088409841084118412841384148415841684178418841984208421842284238424842584268427842884298430843184328433843484358436843784388439844084418442844384448445844684478448844984508451845284538454845584568457845884598460846184628463846484658466846784688469847084718472847384748475847684778478847984808481848284838484848584868487848884898490849184928493849484958496849784988499850085018502850385048505850685078508850985108511851285138514851585168517851885198520852185228523852485258526852785288529853085318532853385348535853685378538853985408541854285438544854585468547854885498550855185528553855485558556855785588559856085618562856385648565856685678568856985708571857285738574857585768577857885798580858185828583858485858586858785888589859085918592859385948595859685978598859986008601860286038604860586068607860886098610861186128613861486158616861786188619862086218622862386248625862686278628862986308631863286338634863586368637863886398640864186428643864486458646864786488649865086518652865386548655865686578658865986608661866286638664866586668667866886698670867186728673867486758676867786788679868086818682868386848685868686878688868986908691869286938694869586968697869886998700870187028703870487058706870787088709871087118712871387148715871687178718871987208721872287238724872587268727872887298730873187328733873487358736873787388739874087418742874387448745874687478748874987508751875287538754875587568757875887598760876187628763876487658766876787688769877087718772877387748775877687778778877987808781878287838784878587868787878887898790879187928793879487958796879787988799880088018802880388048805880688078808880988108811881288138814881588168817881888198820882188228823882488258826882788288829883088318832883388348835883688378838883988408841884288438844884588468847884888498850885188528853885488558856885788588859886088618862886388648865886688678868886988708871887288738874887588768877887888798880888188828883888488858886888788888889889088918892889388948895889688978898889989008901890289038904890589068907890889098910891189128913891489158916891789188919892089218922892389248925892689278928892989308931893289338934893589368937893889398940894189428943894489458946894789488949895089518952895389548955895689578958895989608961896289638964896589668967896889698970897189728973897489758976897789788979898089818982898389848985898689878988898989908991899289938994899589968997899889999000900190029003900490059006900790089009901090119012901390149015901690179018901990209021902290239024902590269027902890299030903190329033903490359036903790389039904090419042904390449045904690479048904990509051905290539054905590569057905890599060906190629063906490659066906790689069907090719072907390749075907690779078907990809081908290839084908590869087908890899090909190929093909490959096909790989099910091019102910391049105910691079108910991109111911291139114911591169117911891199120912191229123912491259126912791289129913091319132913391349135913691379138913991409141914291439144914591469147914891499150915191529153915491559156915791589159916091619162916391649165916691679168916991709171917291739174917591769177917891799180918191829183918491859186918791889189919091919192919391949195919691979198919992009201920292039204920592069207920892099210921192129213921492159216921792189219922092219222922392249225922692279228922992309231923292339234923592369237923892399240924192429243924492459246924792489249925092519252925392549255925692579258925992609261926292639264926592669267926892699270927192729273927492759276927792789279928092819282928392849285928692879288928992909291929292939294929592969297929892999300930193029303930493059306930793089309931093119312931393149315931693179318931993209321932293239324932593269327932893299330933193329333933493359336933793389339934093419342934393449345934693479348934993509351935293539354935593569357935893599360936193629363936493659366936793689369937093719372937393749375937693779378937993809381938293839384938593869387938893899390939193929393939493959396939793989399940094019402940394049405940694079408940994109411941294139414941594169417941894199420942194229423942494259426942794289429943094319432943394349435943694379438943994409441944294439444944594469447944894499450945194529453945494559456945794589459946094619462946394649465946694679468946994709471947294739474947594769477947894799480948194829483948494859486948794889489949094919492949394949495949694979498949995009501950295039504950595069507950895099510951195129513951495159516951795189519952095219522952395249525952695279528952995309531953295339534953595369537953895399540954195429543954495459546954795489549955095519552955395549555955695579558955995609561956295639564956595669567956895699570957195729573957495759576957795789579958095819582958395849585958695879588958995909591959295939594959595969597959895999600960196029603960496059606960796089609961096119612961396149615961696179618961996209621962296239624962596269627962896299630963196329633963496359636963796389639964096419642964396449645964696479648964996509651965296539654965596569657965896599660966196629663966496659666966796689669967096719672967396749675967696779678967996809681968296839684968596869687968896899690969196929693969496959696969796989699970097019702970397049705970697079708970997109711971297139714971597169717971897199720972197229723972497259726972797289729973097319732973397349735973697379738973997409741974297439744974597469747974897499750975197529753975497559756975797589759976097619762976397649765976697679768976997709771977297739774977597769777977897799780978197829783978497859786978797889789979097919792979397949795979697979798979998009801980298039804980598069807980898099810981198129813981498159816981798189819982098219822982398249825982698279828982998309831983298339834983598369837983898399840984198429843984498459846984798489849985098519852985398549855985698579858985998609861986298639864986598669867986898699870987198729873987498759876987798789879988098819882988398849885988698879888988998909891989298939894989598969897989898999900990199029903990499059906990799089909991099119912991399149915991699179918991999209921992299239924992599269927992899299930993199329933993499359936993799389939994099419942994399449945994699479948994999509951995299539954995599569957995899599960996199629963996499659966996799689969997099719972997399749975997699779978997999809981998299839984998599869987998899899990999199929993999499959996999799989999100001000110002100031000410005100061000710008100091001010011100121001310014100151001610017100181001910020100211002210023100241002510026100271002810029100301003110032100331003410035100361003710038100391004010041100421004310044100451004610047100481004910050100511005210053100541005510056100571005810059100601006110062100631006410065100661006710068100691007010071100721007310074100751007610077100781007910080100811008210083100841008510086100871008810089100901009110092100931009410095100961009710098100991010010101101021010310104101051010610107101081010910110101111011210113101141011510116101171011810119101201012110122101231012410125101261012710128101291013010131101321013310134101351013610137101381013910140101411014210143101441014510146101471014810149101501015110152101531015410155101561015710158101591016010161101621016310164101651016610167101681016910170101711017210173101741017510176101771017810179101801018110182101831018410185101861018710188101891019010191101921019310194101951019610197101981019910200102011020210203102041020510206102071020810209102101021110212102131021410215102161021710218102191022010221102221022310224102251022610227102281022910230102311023210233102341023510236102371023810239102401024110242102431024410245102461024710248102491025010251102521025310254102551025610257102581025910260102611026210263102641026510266102671026810269102701027110272102731027410275102761027710278102791028010281102821028310284102851028610287102881028910290102911029210293102941029510296102971029810299103001030110302103031030410305103061030710308103091031010311103121031310314103151031610317103181031910320103211032210323103241032510326103271032810329103301033110332103331033410335103361033710338103391034010341103421034310344103451034610347103481034910350103511035210353103541035510356103571035810359103601036110362103631036410365103661036710368103691037010371103721037310374103751037610377103781037910380103811038210383103841038510386103871038810389103901039110392103931039410395103961039710398103991040010401104021040310404104051040610407104081040910410104111041210413104141041510416104171041810419104201042110422104231042410425104261042710428104291043010431104321043310434104351043610437104381043910440104411044210443104441044510446104471044810449104501045110452104531045410455104561045710458104591046010461104621046310464104651046610467104681046910470104711047210473104741047510476104771047810479104801048110482104831048410485104861048710488104891049010491104921049310494104951049610497104981049910500105011050210503105041050510506105071050810509105101051110512105131051410515105161051710518105191052010521105221052310524105251052610527105281052910530105311053210533105341053510536105371053810539105401054110542105431054410545105461054710548105491055010551105521055310554105551055610557105581055910560105611056210563105641056510566105671056810569105701057110572105731057410575105761057710578105791058010581105821058310584105851058610587105881058910590105911059210593105941059510596105971059810599106001060110602106031060410605106061060710608106091061010611106121061310614106151061610617106181061910620106211062210623106241062510626106271062810629106301063110632106331063410635106361063710638106391064010641106421064310644106451064610647106481064910650106511065210653106541065510656106571065810659106601066110662106631066410665106661066710668106691067010671106721067310674106751067610677106781067910680106811068210683106841068510686106871068810689106901069110692106931069410695106961069710698106991070010701107021070310704107051070610707107081070910710107111071210713107141071510716107171071810719107201072110722107231072410725107261072710728107291073010731107321073310734107351073610737107381073910740107411074210743107441074510746107471074810749107501075110752107531075410755107561075710758107591076010761107621076310764107651076610767107681076910770107711077210773107741077510776107771077810779107801078110782107831078410785107861078710788107891079010791107921079310794107951079610797107981079910800108011080210803108041080510806108071080810809108101081110812108131081410815108161081710818108191082010821108221082310824108251082610827108281082910830108311083210833108341083510836108371083810839108401084110842108431084410845108461084710848108491085010851108521085310854108551085610857108581085910860108611086210863108641086510866108671086810869108701087110872108731087410875108761087710878108791088010881108821088310884108851088610887108881088910890108911089210893108941089510896108971089810899109001090110902109031090410905109061090710908109091091010911109121091310914109151091610917109181091910920109211092210923109241092510926109271092810929109301093110932109331093410935109361093710938109391094010941109421094310944109451094610947109481094910950109511095210953109541095510956109571095810959109601096110962109631096410965109661096710968109691097010971109721097310974109751097610977109781097910980109811098210983109841098510986109871098810989109901099110992109931099410995109961099710998109991100011001110021100311004110051100611007110081100911010110111101211013110141101511016110171101811019110201102111022110231102411025110261102711028110291103011031110321103311034110351103611037110381103911040110411104211043110441104511046110471104811049110501105111052110531105411055110561105711058110591106011061110621106311064110651106611067110681106911070110711107211073110741107511076110771107811079110801108111082110831108411085110861108711088110891109011091110921109311094110951109611097110981109911100111011110211103111041110511106111071110811109111101111111112111131111411115111161111711118111191112011121111221112311124111251112611127111281112911130111311113211133111341113511136111371113811139111401114111142111431114411145111461114711148111491115011151111521115311154111551115611157111581115911160111611116211163111641116511166111671116811169111701117111172111731117411175111761117711178111791118011181111821118311184111851118611187111881118911190111911119211193111941119511196111971119811199112001120111202112031120411205112061120711208112091121011211112121121311214112151121611217112181121911220112211122211223112241122511226112271122811229112301123111232112331123411235112361123711238112391124011241112421124311244112451124611247112481124911250112511125211253112541125511256112571125811259112601126111262112631126411265112661126711268112691127011271112721127311274112751127611277112781127911280112811128211283112841128511286112871128811289112901129111292112931129411295112961129711298112991130011301113021130311304113051130611307113081130911310113111131211313113141131511316113171131811319113201132111322113231132411325113261132711328113291133011331113321133311334113351133611337113381133911340113411134211343113441134511346113471134811349113501135111352113531135411355113561135711358113591136011361113621136311364113651136611367113681136911370113711137211373113741137511376113771137811379113801138111382113831138411385113861138711388113891139011391113921139311394113951139611397113981139911400114011140211403114041140511406114071140811409114101141111412114131141411415114161141711418114191142011421114221142311424114251142611427114281142911430114311143211433114341143511436114371143811439114401144111442114431144411445114461144711448114491145011451114521145311454114551145611457114581145911460114611146211463114641146511466114671146811469114701147111472114731147411475114761147711478114791148011481114821148311484114851148611487114881148911490114911149211493114941149511496114971149811499115001150111502115031150411505115061150711508115091151011511115121151311514115151151611517115181151911520115211152211523115241152511526115271152811529115301153111532115331153411535115361153711538115391154011541115421154311544115451154611547115481154911550115511155211553115541155511556115571155811559115601156111562115631156411565115661156711568115691157011571115721157311574115751157611577115781157911580115811158211583115841158511586115871158811589115901159111592115931159411595115961159711598115991160011601116021160311604116051160611607116081160911610116111161211613116141161511616116171161811619116201162111622116231162411625116261162711628116291163011631116321163311634116351163611637116381163911640116411164211643116441164511646116471164811649116501165111652116531165411655116561165711658116591166011661116621166311664116651166611667116681166911670116711167211673116741167511676116771167811679116801168111682116831168411685116861168711688116891169011691116921169311694116951169611697116981169911700117011170211703117041170511706117071170811709117101171111712117131171411715117161171711718117191172011721117221172311724117251172611727117281172911730117311173211733117341173511736117371173811739117401174111742117431174411745117461174711748117491175011751117521175311754117551175611757117581175911760117611176211763117641176511766117671176811769117701177111772117731177411775117761177711778117791178011781117821178311784117851178611787117881178911790117911179211793 |
- 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';
-
-
- /*
- let Ray$1 = THREE.Ray, Plane = THREE.Plane, MathUtils = THREE.MathUtils, EventDispatcher = THREE.EventDispatcher,
- Vector3 = THREE.Vector3, MOUSE = THREE.MOUSE, TOUCH = THREE.TOUCH, Quaternion = THREE.Quaternion,
- Spherical = THREE.Spherical, Vector2 = THREE.Vector2
- */
-
- //加
- {
-
-
- 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
-
- }
- //---------------------------------------------------
-
-
-
-
-
-
-
-
-
- /**
- * AbortablePromise: A quick & dirty wrapper for JavaScript's Promise class that allows the underlying
- * asynchronous operation to be cancelled. It is only meant for simple situations where no complex promise
- * chaining or merging occurs. It needs a significant amount of work to truly replicate the full
- * functionality of JavaScript's Promise class. Look at Util.fetchWithProgress() for example usage.
- *
- * This class was primarily added to allow splat scene downloads to be cancelled. It has not been tested
- * very thoroughly and the implementation is kinda janky. If you can at all help it, please avoid using it :)
- */
- class AbortablePromise {
- static idGen = 0;
- constructor(promiseFunc, abortHandler) {
- let promiseResolve;
- let promiseReject;
- this.promise = new Promise((resolve, reject) => {
- promiseResolve = resolve.bind(this);
- promiseReject = reject.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() {
- if (this.abortHandler) this.abortHandler();
- }
- }
- 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;
- let rejectFunc = null;
- const abortHandler = () => {
- abortController.abort();
- rejectFunc(new AbortedPromiseError('Fetch aborted.'));
- aborted = true;
- };
- return new AbortablePromise((resolve, reject) => {
- rejectFunc = 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;
- }
- }
- });
- }, 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 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);
- }
- }
- const toHalfFloat = THREE.DataUtils.toHalfFloat.bind(THREE.DataUtils);
- const toUint8 = (v) => {
- return Math.floor(v * 128) + 128;
- };
- const fromUint8 = (v) => {
- return (v / 255) * 2.0 - 1.0;
- };
- //xzw 改 暂时没用到 需要更新版本
- const fromHalfFloat = THREE.DataUtils.fromHalfFloat.bind(THREE.DataUtils);
- const fromHalfFloatToUint8 = (v) => {
- return Math.floor(fromHalfFloat(v) * 128) + 128;
- };
- const toUncompressedFloat = (f, compressionLevel, isSH = false) => {
- if (compressionLevel === 0) {
- return f;
- } else if (compressionLevel === 1 || compressionLevel === 2 && !isSH) {
- return THREE.DataUtils.fromHalfFloat(f);
- } else if (compressionLevel === 2) {
- return fromUint8(f);
- }
- };
- 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);
- }
- };
- /**
- * 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) {
- 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));
- 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;
- }
- }
- static computeCovariance = function() {//通过旋转和缩放计算协方差(决定姿态)得到6个数
- 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) {//场景的整体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 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) => {
- dest[0] = toUncompressedFloat(src[0], compressionLevel, true);
- dest[1] = toUncompressedFloat(src[1], compressionLevel, true);
- dest[2] = toUncompressedFloat(src[2], compressionLevel, true);
- 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) {
- tempMatrix3.setFromMatrix4(transform);
- 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]);
- }
- 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;
- else if (desiredOutputCompressionLevel == 2) outputConversionFunc = fromHalfFloatToUint8;
- } else if (compressionLevelForOutputConversion === 0) {
- if (desiredOutputCompressionLevel === 1) outputConversionFunc = toHalfFloat;
- else if (desiredOutputCompressionLevel == 2) outputConversionFunc = toUint8;
- }
- }
- 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);
- toUncompressedFloatArray3(shIn2, shIn2, this.compressionLevel);
- toUncompressedFloatArray3(shIn3, shIn3, this.compressionLevel);
- 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);
- toUncompressedFloatArray3(shIn2, shIn2, this.compressionLevel);
- toUncompressedFloatArray3(shIn3, shIn3, this.compressionLevel);
- toUncompressedFloatArray3(shIn4, shIn4, this.compressionLevel);
- toUncompressedFloatArray3(shIn5, shIn5, this.compressionLevel);
- 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]);
- return {
- versionMajor,
- versionMinor,
- maxSectionCount,
- sectionCount,
- maxSplatCount,
- splatCount,
- compressionLevel,
- sceneCenter
- };
- }
- 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;
- }
- 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.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 generateFromUncompressedSplatArrays(splatArrays, minimumAlpha, compressionLevel,
- sceneCenter, blockSize, bucketSize, options = []) {
- 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];
- }
- };
- let sphericalHarmonicsDegree = 0;
- for (let sa = 0; sa < splatArrays.length; sa ++) {
- const splatArray = splatArrays[sa];
- if (sa === 0 || splatArray.sphericalHarmonicsDegree < sphericalHarmonicsDegree) {
- if (sa > 0 && splatArray.sphericalHarmonicsDegree !== sphericalHarmonicsDegree) {
- const msg = 'SplatBuffer::generateFromUncompressedSplatArrays() -> ' +
- 'all splat arrays must have the same spherical harmonics degree.';
- throw new Error(msg);
- }
- sphericalHarmonicsDegree = splatArray.sphericalHarmonicsDegree;
- }
- }
- const {bytesPerCenter, bytesPerScale, bytesPerRotation, bytesPerColor, sphericalHarmonicsComponentsPerSplat,
- sphericalHarmonicsBytesPerSplat, bytesPerSplat} =
- SplatBuffer.calculateComponentStorage(compressionLevel, sphericalHarmonicsDegree);
- const compressionScaleRange = SplatBuffer.CompressionLevels[compressionLevel].ScaleRange;
- const sectionBuffers = [];
- const sectionHeaderBuffers = [];
- let totalSplatCount = 0;
- const tempRotation = new THREE.Quaternion();
- for (let sa = 0; sa < splatArrays.length; sa ++) {
- const splatArray = splatArrays[sa];
- const sectionOptions = options[sa] || {};
- const sectionBlockSize = (sectionOptions.blockSizeFactor || 1) * (blockSize || SplatBuffer.BucketBlockSize);
- const sectionBucketSize = Math.ceil((sectionOptions.bucketSizeFactor || 1) * (bucketSize || SplatBuffer.BucketSize));
- const validSplats = new UncompressedSplatArray(sphericalHarmonicsDegree);
- for (let i = 0; i < splatArray.splatCount; i++) {
- const targetSplat = splatArray.splats[i];
- let alpha;
- if (targetSplat[UncompressedSplatArray.OFFSET.OPACITY]) {
- alpha = targetSplat[UncompressedSplatArray.OFFSET.OPACITY];
- } else {
- alpha = 255;
- }
- if (alpha >= minimumAlpha) {
- validSplats.addSplat(targetSplat);
- }
- }
- 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 blockHalfSize = sectionBlockSize / 2.0;
- const compressionScaleFactor = compressionScaleRange / blockHalfSize;
- const doubleCompressionScaleRange = compressionScaleRange * 2 + 1;
- const tempCenterBuffer = new ArrayBuffer(bytesPerCenter);
- const tempScaleBuffer = new ArrayBuffer(bytesPerScale);
- const tempRotationBuffer = new ArrayBuffer(bytesPerRotation);
- const tempColorBuffer = new ArrayBuffer(bytesPerColor);
- const tempSHBuffer = new ArrayBuffer(sphericalHarmonicsBytesPerSplat);
- const bucketCenter = new THREE.Vector3();
- const bucketCenterDelta = 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 centerBase = bucketDataBytes + outSplatCount * bytesPerSplat;
- const scaleBase = centerBase + bytesPerCenter;
- const rotationBase = scaleBase + bytesPerScale;
- const colorBase = rotationBase + bytesPerRotation;
- const sphericalHarmonicsBase = colorBase + bytesPerColor;
- 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);
- if (targetSplat[UncompressedSplatArray.OFFSET.SCALE0] !== undefined) {
- tempRotation.set(targetSplat[UncompressedSplatArray.OFFSET.ROTATION0],
- targetSplat[UncompressedSplatArray.OFFSET.ROTATION1],
- targetSplat[UncompressedSplatArray.OFFSET.ROTATION2],
- targetSplat[UncompressedSplatArray.OFFSET.ROTATION3]);
- tempRotation.normalize();
- rot.set([tempRotation.x, tempRotation.y, tempRotation.z, tempRotation.w]);
- scale.set([targetSplat[UncompressedSplatArray.OFFSET.SCALE0],
- targetSplat[UncompressedSplatArray.OFFSET.SCALE1],
- targetSplat[UncompressedSplatArray.OFFSET.SCALE2]]);
- } else {
- rot.set([1.0, 0.0, 0.0, 0.0]);
- scale.set([0.01, 0.01, 0.01]);
- }
- center.set([targetSplat[UncompressedSplatArray.OFFSET.X],
- targetSplat[UncompressedSplatArray.OFFSET.Y],
- targetSplat[UncompressedSplatArray.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[UncompressedSplatArray.OFFSET.FRC0 + s];
- if (sphericalHarmonicsDegree >= 2) {
- for (let s = 0; s < 15; s++) shOut[s + 9] = targetSplat[UncompressedSplatArray.OFFSET.FRC9 + s];
- }
- }
- }
- } 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);
- if (targetSplat[UncompressedSplatArray.OFFSET.SCALE0] !== undefined) {
- tempRotation.set(targetSplat[UncompressedSplatArray.OFFSET.ROTATION0],
- targetSplat[UncompressedSplatArray.OFFSET.ROTATION1],
- targetSplat[UncompressedSplatArray.OFFSET.ROTATION2],
- targetSplat[UncompressedSplatArray.OFFSET.ROTATION3]);
- tempRotation.normalize();
- rot.set([toHalfFloat(tempRotation.x), toHalfFloat(tempRotation.y),
- toHalfFloat(tempRotation.z), toHalfFloat(tempRotation.w)]);
- scale.set([toHalfFloat(targetSplat[UncompressedSplatArray.OFFSET.SCALE0]),
- toHalfFloat(targetSplat[UncompressedSplatArray.OFFSET.SCALE1]),
- toHalfFloat(targetSplat[UncompressedSplatArray.OFFSET.SCALE2])]);
- } else {
- rot.set([toHalfFloat(1.), 0, 0, 0]);
- scale.set([toHalfFloat(0.01), toHalfFloat(0.01), toHalfFloat(0.01)]);
- }
- bucketCenterDelta.set(targetSplat[UncompressedSplatArray.OFFSET.X],
- targetSplat[UncompressedSplatArray.OFFSET.Y],
- targetSplat[UncompressedSplatArray.OFFSET.Z]).sub(bucketCenter);
- bucketCenterDelta.x = Math.round(bucketCenterDelta.x * compressionScaleFactor) + compressionScaleRange;
- bucketCenterDelta.x = clamp(bucketCenterDelta.x, 0, doubleCompressionScaleRange);
- bucketCenterDelta.y = Math.round(bucketCenterDelta.y * compressionScaleFactor) + compressionScaleRange;
- bucketCenterDelta.y = clamp(bucketCenterDelta.y, 0, doubleCompressionScaleRange);
- bucketCenterDelta.z = Math.round(bucketCenterDelta.z * compressionScaleFactor) + compressionScaleRange;
- bucketCenterDelta.z = clamp(bucketCenterDelta.z, 0, doubleCompressionScaleRange);
- 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[UncompressedSplatArray.OFFSET.FRC0 + s];
- shOut[s] = compressionLevel === 1 ? toHalfFloat(srcVal) : toUint8(srcVal);
- }
- const degree1ByteCount = 9 * bytesPerSHComponent;
- copyBetweenBuffers(shOut.buffer, 0, sectionBuffer, sphericalHarmonicsBase, degree1ByteCount);
- if (sphericalHarmonicsDegree >= 2) {
- for (let s = 0; s < 15; s++) {
- const srcVal = targetSplat[UncompressedSplatArray.OFFSET.FRC9 + s];
- shOut[s + 9] = compressionLevel === 1 ? toHalfFloat(srcVal) : toUint8(srcVal);
- }
- const degree2ByteCount = 15 * bytesPerSHComponent;
- copyBetweenBuffers(shOut.buffer, degree1ByteCount, sectionBuffer,
- sphericalHarmonicsBase + degree1ByteCount, degree2ByteCount);
- }
- }
- }
- 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);
- if (targetSplat[UncompressedSplatArray.OFFSET.FDC0] !== undefined) {
- rgba.set([targetSplat[UncompressedSplatArray.OFFSET.FDC0],
- targetSplat[UncompressedSplatArray.OFFSET.FDC1],
- targetSplat[UncompressedSplatArray.OFFSET.FDC2]]);
- } else {
- rgba.set([255, 0, 0]);
- }
- if (targetSplat[UncompressedSplatArray.OFFSET.OPACITY] !== undefined) {
- rgba[3] = targetSplat[UncompressedSplatArray.OFFSET.OPACITY];
- } else {
- rgba[3] = 255;
- }
- copyBetweenBuffers(rgba.buffer, 0, sectionBuffer, colorBase, 4);
- 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: sphericalHarmonicsDegree
- }, 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
- }, 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 CompressedPlyParser {
- 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,
- };
- }
- 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} = CompressedPlyParser.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 = CompressedPlyParser.decodeHeader(plyBuffer);
- let readIndex = CompressedPlyParser.readElementData(header.chunkElement, plyBuffer, header.headerSizeBytes, null, null, propertyFilter);
- CompressedPlyParser.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) {
- CompressedPlyParser.readElementData(vertexElement, vertexDataBuffer, veretxReadOffset, fromIndex, toIndex, propertyFilter);
- 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;
- const { positionExtremes, scaleExtremes, position, rotation, scale, color } =
- CompressedPlyParser.getElementStorageArrays(chunkElement, vertexElement);
- const OFFSET = UncompressedSplatArray.OFFSET;
- const tempSplat = UncompressedSplatArray.createSplat();
- for (let i = fromIndex; i <= toIndex; ++i) {
- CompressedPlyParser.decompressSplat(i, chunkSplatIndexOffset, position, positionExtremes,
- scale, scaleExtremes, rotation, color, tempSplat);
- const outBase = i * outBytesPerSplat + outOffset;
- const outCenter = new Float32Array(outBuffer, outBase, 3);
- const outScale = new Float32Array(outBuffer, outBase + outBytesPerCenter, 3);
- const outRotation = new Float32Array(outBuffer, outBase + outBytesPerCenter + outBytesPerScale, 4);
- const outColor = new Uint8Array(outBuffer, outBase + outBytesPerCenter + outBytesPerScale + outBytesPerRotation, 4);
- outCenter[0] = tempSplat[OFFSET.X];
- outCenter[1] = tempSplat[OFFSET.Y];
- outCenter[2] = tempSplat[OFFSET.Z];
- outScale[0] = tempSplat[OFFSET.SCALE0];
- outScale[1] = tempSplat[OFFSET.SCALE1];
- outScale[2] = tempSplat[OFFSET.SCALE2];
- outRotation[0] = tempSplat[OFFSET.ROTATION0];
- outRotation[1] = tempSplat[OFFSET.ROTATION1];
- outRotation[2] = tempSplat[OFFSET.ROTATION2];
- outRotation[3] = tempSplat[OFFSET.ROTATION3];
- outColor[0] = tempSplat[OFFSET.FDC0];
- outColor[1] = tempSplat[OFFSET.FDC1];
- outColor[2] = tempSplat[OFFSET.FDC2];
- outColor[3] = tempSplat[OFFSET.OPACITY];
- }
- }
- static parseToUncompressedSplatArray(plyBuffer) {
- const { chunkElement, vertexElement } = CompressedPlyParser.readPly(plyBuffer);
- const splatArray = new UncompressedSplatArray();
- const { positionExtremes, scaleExtremes, position, rotation, scale, color } =
- CompressedPlyParser.getElementStorageArrays(chunkElement, vertexElement);
- for (let i = 0; i < vertexElement.count; ++i) {
- splatArray.addDefaultSplat();
- const newSplat = splatArray.getSplat(splatArray.splatCount - 1);
- CompressedPlyParser.decompressSplat(i, 0, position, positionExtremes, scale, scaleExtremes, rotation, color, newSplat);
- }
- const mat = new THREE.Matrix4();
- mat.identity();
- return splatArray;
- }
- }
- class PlyParser {
- static HeaderEndToken = 'end_header';
- static BaseFields = ['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', 'red', 'green', 'blue', 'opacity'];
- static SphericalHarmonicsFields = Array.from(Array(45)).map((e, i) => (`f_rest_${i}`));
- static Fields = [[...PlyParser.BaseFields], [...PlyParser.BaseFields, ...PlyParser.SphericalHarmonicsFields]];
- static checkTextForEndHeader(endHeaderTestText) {
- if (endHeaderTestText.includes(PlyParser.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 PlyParser.checkTextForEndHeader(endHeaderTestText);
- }
- static decodeHeaderText(headerText) {
- const headerLines = headerText.split('\n');
- const prunedLines = [];
- let splatCount = 0;
- let propertyTypes = {};
- let compressed = false;
- for (let i = 0; i < headerLines.length; i++) {
- const line = headerLines[i].trim();
- prunedLines.push(line);
- if (line.startsWith('element chunk') || line.match(/[A-Za-z]*packed_[A-Za-z]*/)) {
- compressed = true;
- } else if (line.startsWith('element vertex')) {
- const splatCountMatch = line.match(/\d+/);
- if (splatCountMatch) {
- splatCount = parseInt(splatCountMatch[0]);
- }
- } else if (line.startsWith('property')) {
- const propertyMatch = line.match(/(\w+)\s+(\w+)\s+(\w+)/);
- if (propertyMatch) {
- const propertyType = propertyMatch[2];
- const propertyName = propertyMatch[3];
- propertyTypes[propertyName] = propertyType;
- }
- } else if (line === PlyParser.HeaderEndToken) {
- break;
- }
- }
- let bytesPerSplat = 0;
- let fieldOffsets = {};
- const fieldSize = {
- 'double': 8,
- 'int': 4,
- 'uint': 4,
- 'float': 4,
- 'short': 2,
- 'ushort': 2,
- 'uchar': 1,
- };
- const fieldNames = [];
- for (let fieldName in propertyTypes) {
- if (propertyTypes.hasOwnProperty(fieldName)) {
- fieldNames.push(fieldName);
- const type = propertyTypes[fieldName];
- fieldOffsets[fieldName] = bytesPerSplat;
- bytesPerSplat += fieldSize[type];
- }
- }
- let sphericalHarmonicsFieldCount = 0;
- let sphericalHarmonicsCoefficientsPerChannel = 0;
- for (let fieldName of fieldNames) {
- if (fieldName.startsWith('f_rest')) sphericalHarmonicsFieldCount++;
- }
- sphericalHarmonicsCoefficientsPerChannel = sphericalHarmonicsFieldCount / 3;
- let sphericalHarmonicsDegree = 0;
- if (sphericalHarmonicsCoefficientsPerChannel >= 3) sphericalHarmonicsDegree = 1;
- if (sphericalHarmonicsCoefficientsPerChannel >= 8) sphericalHarmonicsDegree = 2;
- let sphericalHarmonicsDegree1Fields = [];
- if (sphericalHarmonicsDegree >= 1) {
- for (let rgb = 0; rgb < 3; rgb++) {
- for (let i = 0; i < 3; i++) {
- sphericalHarmonicsDegree1Fields.push('f_rest_' + (i + sphericalHarmonicsCoefficientsPerChannel * rgb));
- }
- }
- }
- let sphericalHarmonicsDegree2Fields = [];
- if (sphericalHarmonicsDegree >= 2) {
- for (let rgb = 0; rgb < 3; rgb++) {
- for (let i = 0; i < 5; i++) {
- sphericalHarmonicsDegree2Fields.push('f_rest_' + (i + sphericalHarmonicsCoefficientsPerChannel * rgb + 3));
- }
- }
- }
- return {
- 'splatCount': splatCount,
- 'propertyTypes': propertyTypes,
- 'compressed': compressed,
- 'headerText': headerText,
- 'headerLines': prunedLines,
- 'headerSizeBytes': headerText.indexOf(PlyParser.HeaderEndToken) + PlyParser.HeaderEndToken.length + 1,
- 'bytesPerSplat': bytesPerSplat,
- 'fieldOffsets': fieldOffsets,
- 'sphericalHarmonicsDegree': sphericalHarmonicsDegree,
- 'sphericalHarmonicsCoefficientsPerChannel': sphericalHarmonicsCoefficientsPerChannel,
- 'sphericalHarmonicsDegree1Fields': sphericalHarmonicsDegree1Fields,
- 'sphericalHarmonicsDegree2Fields': sphericalHarmonicsDegree2Fields
- };
- }
- static decodeHeadeFromBuffer(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 (PlyParser.checkBufferForEndHeader(plyBuffer, headerOffset, readChunkSize * 2, decoder)) {
- break;
- }
- }
- return PlyParser.decodeHeaderText(headerText);
- }
- static findVertexData(plyBuffer, header) {
- return new DataView(plyBuffer, header.headerSizeBytes);
- }
- static readRawVertexFast(vertexData, offset, fieldOffsets, propertiesToRead, propertyTypes, outVertex) {
- let rawVertex = outVertex || {};
- for (let property of propertiesToRead) {
- const propertyType = propertyTypes[property];
- if (propertyType === 'float') {
- rawVertex[property] = vertexData.getFloat32(offset + fieldOffsets[property], true);
- }else if(propertyType === 'double'){//xzw add! 因为我试过一个ply的坐标是double
- rawVertex[property] = vertexData.getFloat64(offset + fieldOffsets[property], true);
- } else if (propertyType === 'uchar') {
- rawVertex[property] = vertexData.getUint8(offset + fieldOffsets[property]) / 255.0;
- }
- }
- }
- static parseToUncompressedSplatBufferSection(header, fromSplat, toSplat, vertexData, vertexDataOffset,
- toBuffer, toOffset, outSphericalHarmonicsDegree = 0) {
- outSphericalHarmonicsDegree = Math.min(outSphericalHarmonicsDegree, header.sphericalHarmonicsDegree);
- const outBytesPerCenter = SplatBuffer.CompressionLevels[0].BytesPerCenter;
- const outBytesPerScale = SplatBuffer.CompressionLevels[0].BytesPerScale;
- const outBytesPerRotation = SplatBuffer.CompressionLevels[0].BytesPerRotation;
- const outBytesPerColor = SplatBuffer.CompressionLevels[0].BytesPerColor;
- const outBytesPerSplat = SplatBuffer.CompressionLevels[0].SphericalHarmonicsDegrees[outSphericalHarmonicsDegree].BytesPerSplat;
- for (let i = fromSplat; i <= toSplat; i++) {
- const parsedSplat = PlyParser.parseToUncompressedSplat(vertexData, i, header, vertexDataOffset, outSphericalHarmonicsDegree);
- 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] = parsedSplat[UncompressedSplatArray.OFFSET.X];
- outCenter[1] = parsedSplat[UncompressedSplatArray.OFFSET.Y];
- outCenter[2] = parsedSplat[UncompressedSplatArray.OFFSET.Z];
- outScale[0] = parsedSplat[UncompressedSplatArray.OFFSET.SCALE0];
- outScale[1] = parsedSplat[UncompressedSplatArray.OFFSET.SCALE1];
- outScale[2] = parsedSplat[UncompressedSplatArray.OFFSET.SCALE2];
- outRotation[0] = parsedSplat[UncompressedSplatArray.OFFSET.ROTATION0];
- outRotation[1] = parsedSplat[UncompressedSplatArray.OFFSET.ROTATION1];
- outRotation[2] = parsedSplat[UncompressedSplatArray.OFFSET.ROTATION2];
- outRotation[3] = parsedSplat[UncompressedSplatArray.OFFSET.ROTATION3];
- outColor[0] = parsedSplat[UncompressedSplatArray.OFFSET.FDC0];
- outColor[1] = parsedSplat[UncompressedSplatArray.OFFSET.FDC1];
- outColor[2] = parsedSplat[UncompressedSplatArray.OFFSET.FDC2];
- outColor[3] = parsedSplat[UncompressedSplatArray.OFFSET.OPACITY];
- if (outSphericalHarmonicsDegree >= 1) {
- const outSphericalHarmonics = new Float32Array(toBuffer, outBase + outBytesPerCenter + outBytesPerScale +
- outBytesPerRotation + outBytesPerColor,
- parsedSplat.sphericalHarmonicsCount);
- for (let i = 0; i <= 8; i++) {
- outSphericalHarmonics[i] = parsedSplat[UncompressedSplatArray.OFFSET.FRC0 + i];
- }
- if (outSphericalHarmonicsDegree >= 2) {
- for (let i = 9; i <= 23; i++) {
- outSphericalHarmonics[i] = parsedSplat[UncompressedSplatArray.OFFSET.FRC0 + i];
- }
- }
- }
- }
- }
- static parseToUncompressedSplat = function() {
- let rawVertex = {};
- const tempRotation = new THREE.Quaternion();
- return function(vertexData, row, header, vertexDataOffset = 0, outSphericalHarmonicsDegree = 0) {
- outSphericalHarmonicsDegree = Math.min(outSphericalHarmonicsDegree, header.sphericalHarmonicsDegree);
- PlyParser.readRawVertexFast(vertexData, row * header.bytesPerSplat + vertexDataOffset, header.fieldOffsets,
- PlyParser.Fields[outSphericalHarmonicsDegree > 0 ? 1 : 0], header.propertyTypes, rawVertex);
- const newSplat = UncompressedSplatArray.createSplat(outSphericalHarmonicsDegree);
- if (rawVertex['scale_0'] !== undefined) {
- newSplat[UncompressedSplatArray.OFFSET.SCALE0] = Math.exp(rawVertex['scale_0']);
- newSplat[UncompressedSplatArray.OFFSET.SCALE1] = Math.exp(rawVertex['scale_1']);
- newSplat[UncompressedSplatArray.OFFSET.SCALE2] = Math.exp(rawVertex['scale_2']);
- } else {
- newSplat[UncompressedSplatArray.OFFSET.SCALE0] = 0.01;
- newSplat[UncompressedSplatArray.OFFSET.SCALE1] = 0.01;
- newSplat[UncompressedSplatArray.OFFSET.SCALE2] = 0.01;
- }
- if (rawVertex['f_dc_0'] !== undefined) {
- const SH_C0 = 0.28209479177387814;
- newSplat[UncompressedSplatArray.OFFSET.FDC0] = (0.5 + SH_C0 * rawVertex['f_dc_0']) * 255;
- newSplat[UncompressedSplatArray.OFFSET.FDC1] = (0.5 + SH_C0 * rawVertex['f_dc_1']) * 255;
- newSplat[UncompressedSplatArray.OFFSET.FDC2] = (0.5 + SH_C0 * rawVertex['f_dc_2']) * 255;
- } else if (rawVertex['red'] !== undefined) {
- newSplat[UncompressedSplatArray.OFFSET.FDC0] = rawVertex['red'] * 255;
- newSplat[UncompressedSplatArray.OFFSET.FDC1] = rawVertex['green'] * 255;
- newSplat[UncompressedSplatArray.OFFSET.FDC2] = rawVertex['blue'] * 255;
- } else {
- newSplat[UncompressedSplatArray.OFFSET.FDC0] = 0;
- newSplat[UncompressedSplatArray.OFFSET.FDC1] = 0;
- newSplat[UncompressedSplatArray.OFFSET.FDC2] = 0;
- }
- if (rawVertex['opacity'] !== undefined) {
- newSplat[UncompressedSplatArray.OFFSET.OPACITY] = (1 / (1 + Math.exp(-rawVertex['opacity']))) * 255;
- }else {//xzw add
- newSplat[UncompressedSplatArray.OFFSET.OPACITY] = 1;
- }
- newSplat[UncompressedSplatArray.OFFSET.FDC0] = clamp(Math.floor(newSplat[UncompressedSplatArray.OFFSET.FDC0]), 0, 255);
- newSplat[UncompressedSplatArray.OFFSET.FDC1] = clamp(Math.floor(newSplat[UncompressedSplatArray.OFFSET.FDC1]), 0, 255);
- newSplat[UncompressedSplatArray.OFFSET.FDC2] = clamp(Math.floor(newSplat[UncompressedSplatArray.OFFSET.FDC2]), 0, 255);
- newSplat[UncompressedSplatArray.OFFSET.OPACITY] = clamp(Math.floor(newSplat[UncompressedSplatArray.OFFSET.OPACITY]), 0, 255);
-
- if (outSphericalHarmonicsDegree >= 1) {
- if (rawVertex['f_rest_0'] !== undefined) {
- for (let i = 0; i < 9; i++) {
- newSplat[UncompressedSplatArray.OFFSET.FRC0 + i] = rawVertex[header.sphericalHarmonicsDegree1Fields[i]];
- }
- if (outSphericalHarmonicsDegree >= 2) {
- for (let i = 0; i < 15; i++) {
- newSplat[UncompressedSplatArray.OFFSET.FRC9 + i] = rawVertex[header.sphericalHarmonicsDegree2Fields[i]];
- }
- }
- } else {
- newSplat[UncompressedSplatArray.OFFSET.FRC0] = 0;
- newSplat[UncompressedSplatArray.OFFSET.FRC1] = 0;
- newSplat[UncompressedSplatArray.OFFSET.FRC2] = 0;
- }
- }
-
-
- if(rawVertex['rot_0'] == void 0){//xzw add 表现为任何方向都是圆形 sprite
- tempRotation.set(0,0,0,1);
- }else {
- tempRotation.set(rawVertex['rot_0'], rawVertex['rot_1'], rawVertex['rot_2'], rawVertex['rot_3']);
- tempRotation.normalize();
- }
-
- newSplat[UncompressedSplatArray.OFFSET.ROTATION0] = tempRotation.x;
- newSplat[UncompressedSplatArray.OFFSET.ROTATION1] = tempRotation.y;
- newSplat[UncompressedSplatArray.OFFSET.ROTATION2] = tempRotation.z;
- newSplat[UncompressedSplatArray.OFFSET.ROTATION3] = tempRotation.w;
- newSplat[UncompressedSplatArray.OFFSET.X] = rawVertex['x'];
- newSplat[UncompressedSplatArray.OFFSET.Y] = rawVertex['y'];
- newSplat[UncompressedSplatArray.OFFSET.Z] = rawVertex['z'];
- return newSplat;
- };
- }();
- static parseToUncompressedSplatArray(plyBuffer, outSphericalHarmonicsDegree = 0) {
- const header = PlyParser.decodeHeadeFromBuffer(plyBuffer);
- if (header.compressed) {
- return CompressedPlyParser.parseToUncompressedSplatArray(plyBuffer);
- } else {
- const splatCount = header.splatCount;
- const vertexData = PlyParser.findVertexData(plyBuffer, header);
- // TODO: Eventually properly support multiple degree spherical harmonics
- // figure out the SH degree from the number of coefficients
- /* let nRestCoeffs = 0;
- for (const propertyName in header.propertyTypes) {
- if (propertyName.startsWith('f_rest_')) {
- nRestCoeffs += 1;
- }
- }
- const nCoeffsPerColor = nRestCoeffs / 3;*/
- // const sphericalHarmonicsDegree = Math.sqrt(nCoeffsPerColor + 1) - 1;
- // const sphericalHarmonicsDegree = 0;
- // console.log('Detected degree', sphericalHarmonicsDegree, 'with ', nCoeffsPerColor, 'coefficients per color');
- // figure out the order in which spherical harmonics should be read
- /* const shFeatureOrder = [];
- for (let rgb = 0; rgb < 3; ++rgb) {
- shFeatureOrder.push(`f_dc_${rgb}`);
- }
- for (let i = 0; i < nCoeffsPerColor; ++i) {
- for (let rgb = 0; rgb < 3; ++rgb) {
- shFeatureOrder.push(`f_rest_${rgb * nCoeffsPerColor + i}`);
- }
- }*/
- const splatArray = new UncompressedSplatArray(outSphericalHarmonicsDegree);
- for (let row = 0; row < splatCount; row++) {
- const newSplat = PlyParser.parseToUncompressedSplat(vertexData, row, header, 0, outSphericalHarmonicsDegree);
- splatArray.addSplat(newSplat);
- }
- return splatArray;
- }
- }
- }
- 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) => {
- if (partitionSize <= 0) partitionSize = splatArray.splatCount;
- const centerA = new THREE.Vector3();
- const centerB = 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.sort((a, b) => {
- centerA.set(a[UncompressedSplatArray.OFFSET.X],
- a[UncompressedSplatArray.OFFSET.Y],
- a[UncompressedSplatArray.OFFSET.Z]).sub(sceneCenter);
- clampPoint(centerA);
- const centerADist = centerA.lengthSq();
- centerB.set(b[UncompressedSplatArray.OFFSET.X],
- b[UncompressedSplatArray.OFFSET.Y],
- b[UncompressedSplatArray.OFFSET.Z]).sub(sceneCenter);
- clampPoint(centerB);
- const centerBDist = centerB.lengthSq();
- 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
- };
- class Constants {
- static DepthMapRange = 1 << 16;
- static MemoryPageSize = 65536;
- static BytesPerFloat = 4;
- static BytesPerInt = 4;
- static MaxScenes = 32;
- static StreamingSectionSize = 524288;
- }
- 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, streamLoadData, onStreamedSectionProgress, minimumAlpha, compressionLevel,
- outSphericalHarmonicsDegree = 0, sectionSize, sceneCenter, blockSize, bucketSize) {
- const streamedSectionSizeBytes = Constants.StreamingSectionSize;
- const splatDataOffsetBytes = SplatBuffer.HeaderSizeBytes + SplatBuffer.SectionHeaderSizeBytes;
- const sectionCount = 1;
- let streamBufferIn;
- let streamBufferOut;
- let streamedSplatBuffer;
- let compressedPlyHeaderChunksBuffer;
- let maxSplatCount = 0;
- let splatCount = 0;
- let headerLoaded = false;
- let readyToLoadSplatData = false;
- let compressed = false;
- let streamLoadCompleteResolver;
- let streamLoadPromise = new Promise((resolve) => {
- streamLoadCompleteResolver = resolve;
- });
- let numBytesStreamed = 0;
- let numBytesParsed = 0;
- let numBytesDownloaded = 0;
- let headerText = '';
- let header = null;
- let chunks = [];
- const textDecoder = new TextDecoder();
- const localOnProgress = (percent, percentLabel, chunkData) => {
- const loadComplete = percent >= 100;
- if (streamLoadData) {
- 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 (PlyParser.checkTextForEndHeader(headerText)) {
- header = PlyParser.decodeHeaderText(headerText);
- outSphericalHarmonicsDegree = Math.min(outSphericalHarmonicsDegree, header.sphericalHarmonicsDegree);
- compressed = header.compressed;
- if (compressed) {
- header = CompressedPlyParser.decodeHeaderText(headerText);
- maxSplatCount = header.vertexElement.count;
- } else {
- maxSplatCount = header.splatCount;
- readyToLoadSplatData = true;
- }
- const shDescriptor = SplatBuffer.CompressionLevels[0].SphericalHarmonicsDegrees[outSphericalHarmonicsDegree];
- const splatBufferSizeBytes = splatDataOffsetBytes + shDescriptor.BytesPerSplat * maxSplatCount;
- streamBufferOut = 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()
- }, streamBufferOut);
- 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) {
- CompressedPlyParser.readElementData(header.chunkElement, compressedPlyHeaderChunksBuffer, header.headerSizeBytes);
- numBytesStreamed = sizeRequiredForHeaderAndChunks;
- numBytesParsed = sizeRequiredForHeaderAndChunks;
- readyToLoadSplatData = true;
- }
- }
- if (headerLoaded && readyToLoadSplatData) {
- if (chunks.length > 0) {
- streamBufferIn = storeChunksInBuffer(chunks, streamBufferIn);
- const bytesLoadedSinceLastStreamedSection = numBytesDownloaded - numBytesStreamed;
- if (bytesLoadedSinceLastStreamedSection > streamedSectionSizeBytes || 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(streamBufferIn, parsedDataViewOffset, numBytesToParse);
- const shDescriptor = SplatBuffer.CompressionLevels[0].SphericalHarmonicsDegrees[outSphericalHarmonicsDegree];
- const outOffset = splatCount * shDescriptor.BytesPerSplat + splatDataOffsetBytes;
- if (compressed) {
- CompressedPlyParser.parseToUncompressedSplatBufferSection(header.chunkElement, header.vertexElement, 0,
- addedSplatCount - 1, splatCount,
- dataToParse, 0, streamBufferOut, outOffset);
- } else {
- PlyParser.parseToUncompressedSplatBufferSection(header, 0, addedSplatCount - 1, dataToParse, 0,
- streamBufferOut, outOffset, outSphericalHarmonicsDegree);
- }
- splatCount = newSplatCount;
- if (!streamedSplatBuffer) {
- SplatBuffer.writeSectionHeaderToBuffer({
- maxSplatCount: maxSplatCount,
- splatCount: splatCount,
- bucketSize: 0,
- bucketCount: 0,
- bucketBlockSize: 0,
- compressionScaleRange: 0,
- storageSizeBytes: 0,
- fullBucketCount: 0,
- partiallyFilledBucketCount: 0,
- sphericalHarmonicsDegree: outSphericalHarmonicsDegree
- }, 0, streamBufferOut, SplatBuffer.HeaderSizeBytes);
- streamedSplatBuffer = new SplatBuffer(streamBufferOut, false);
- }
- streamedSplatBuffer.updateLoadedCounts(1, splatCount);
- onStreamedSectionProgress(streamedSplatBuffer, loadComplete);
- numBytesStreamed += streamedSectionSizeBytes;
- 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) {
- streamLoadCompleteResolver(streamedSplatBuffer);
- }
- }
- }
- if (onProgress) onProgress(percent, percentLabel, LoaderStatus.Downloading);
- };
- return fetchWithProgress(fileName, localOnProgress, !streamLoadData).then((plyFileData) => {
- if (onProgress) onProgress(0, '0%', LoaderStatus.Processing);
- const loadPromise = streamLoadData ? streamLoadPromise :
- 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, streamLoadData, onStreamedSectionProgress, minimumAlpha, compressionLevel,
- optimizeSplatData, sectionSize, sceneCenter, blockSize, bucketSize) {
- const splatDataOffsetBytes = SplatBuffer.HeaderSizeBytes + SplatBuffer.SectionHeaderSizeBytes;
- const streamSectionSizeBytes = Constants.StreamingSectionSize;
- const sectionCount = 1;
- let streamBufferIn;
- let streamBufferOut;
- let streamSplatBuffer;
- let maxSplatCount = 0;
- let splatCount = 0;
- let streamLoadCompleteResolver;
- let streamLoadPromise = new Promise((resolve) => {
- streamLoadCompleteResolver = resolve;
- });
- let numBytesStreamed = 0;
- let numBytesLoaded = 0;
- let chunks = [];
- const localOnProgress = (percent, percentStr, chunk, fileSize) => {
- const loadComplete = percent >= 100;
- if (!fileSize) streamLoadData = false;
- if (streamLoadData) {
- if (!streamBufferIn) {
- maxSplatCount = fileSize / SplatParser.RowSizeBytes;
- streamBufferIn = new ArrayBuffer(fileSize);
- const bytesPerSplat = SplatBuffer.CompressionLevels[0].SphericalHarmonicsDegrees[0].BytesPerSplat;
- const splatBufferSizeBytes = splatDataOffsetBytes + bytesPerSplat * maxSplatCount;
- streamBufferOut = 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()
- }, streamBufferOut);
- }
- if (chunk) {
- chunks.push(chunk);
- new Uint8Array(streamBufferIn, numBytesLoaded, chunk.byteLength).set(new Uint8Array(chunk));
- numBytesLoaded += chunk.byteLength;
- const bytesLoadedSinceLastSection = numBytesLoaded - numBytesStreamed;
- if (bytesLoadedSinceLastSection > streamSectionSizeBytes || loadComplete) {
- const bytesToUpdate = loadComplete ? bytesLoadedSinceLastSection : streamSectionSizeBytes;
- const addedSplatCount = bytesToUpdate / SplatParser.RowSizeBytes;
- const newSplatCount = splatCount + addedSplatCount;
- SplatParser.parseToUncompressedSplatBufferSection(splatCount, newSplatCount - 1, streamBufferIn, 0,
- streamBufferOut, splatDataOffsetBytes);
- splatCount = newSplatCount;
- if (!streamSplatBuffer) {
- SplatBuffer.writeSectionHeaderToBuffer({
- maxSplatCount: maxSplatCount,
- splatCount: splatCount,
- bucketSize: 0,
- bucketCount: 0,
- bucketBlockSize: 0,
- compressionScaleRange: 0,
- storageSizeBytes: 0,
- fullBucketCount: 0,
- partiallyFilledBucketCount: 0
- }, 0, streamBufferOut, SplatBuffer.HeaderSizeBytes);
- streamSplatBuffer = new SplatBuffer(streamBufferOut, false);
- }
- streamSplatBuffer.updateLoadedCounts(1, splatCount);
- onStreamedSectionProgress(streamSplatBuffer, loadComplete);
- numBytesStreamed += streamSectionSizeBytes;
- }
- }
- if (loadComplete) {
- streamLoadCompleteResolver(streamSplatBuffer);
- }
- }
- if (onProgress) onProgress(percent, percentStr, LoaderStatus.Downloading);
- return streamLoadData;
- };
- return fetchWithProgress(fileName, localOnProgress, true).then((fullBuffer) => {
- if (onProgress) onProgress(0, '0%', LoaderStatus.Processing);
- const loadPromise = streamLoadData ? streamLoadPromise :
- 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, onProgress, streamLoadData, onSectionBuilt) {
- let streamBuffer;
- let streamSplatBuffer;
- let headerBuffer;
- let header;
- let headerLoaded = false;
- let headerLoading = false;
- let sectionHeadersBuffer;
- let sectionHeaders = [];
- let sectionHeadersLoaded = false;
- let sectionHeadersLoading = false;
- let numBytesLoaded = 0;
- let numBytesStreamed = 0;
- let streamSectionSizeBytes = Constants.StreamingSectionSize;
- let totalBytesToDownload = 0;
- let loadComplete = false;
- let chunks = [];
- let streamLoadCompleteResolver;
- let streamLoadPromise = new Promise((resolve) => {
- streamLoadCompleteResolver = resolve;
- });
- 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(true);
- }, 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 (!streamBuffer) {
- streamBuffer = new ArrayBuffer(totalStorageSizeBytes);
- let offset = 0;
- for (let i = 0; i < chunks.length; i++) {
- const chunk = chunks[i];
- new Uint8Array(streamBuffer, 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 (sectionHeadersLoaded) {
- if (loadComplete) return;
- loadComplete = numBytesLoaded >= totalBytesToDownload;
- const bytesLoadedSinceLastSection = numBytesLoaded - numBytesStreamed;
- if (bytesLoadedSinceLastSection > streamSectionSizeBytes || loadComplete) {
- numBytesStreamed = numBytesLoaded;
- if (!streamSplatBuffer) streamSplatBuffer = new SplatBuffer(streamBuffer, 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 (numBytesLoaded >= bytesRequiredToReachSectionSplatData) {
- reachedSections++;
- const bytesPastSSectionSplatDataStart = numBytesLoaded - 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;
- streamSplatBuffer.updateLoadedCounts(reachedSections, loadedSplatCount);
- streamSplatBuffer.updateSectionLoadedCounts(i, loadedSplatsForSection);
- } else {
- break;
- }
- sectionBase += sectionHeader.storageSizeBytes;
- }
- onSectionBuilt(streamSplatBuffer, loadComplete);
- if (loadComplete) {
- streamLoadCompleteResolver(streamSplatBuffer);
- }
- }
- }
- };
- const localOnProgress = (percent, percentStr, chunk) => {
- if (chunk) {
- chunks.push(chunk);
- if (streamBuffer) {
- new Uint8Array(streamBuffer, numBytesLoaded, chunk.byteLength).set(new Uint8Array(chunk));
- }
- numBytesLoaded += chunk.byteLength;
- }
- if (streamLoadData) {
- checkAndLoadHeader();
- checkAndLoadSectionHeaders();
- checkAndLoadSections();
- }
- if (onProgress) onProgress(percent, percentStr, LoaderStatus.Downloading);
- };
- return fetchWithProgress(fileName, localOnProgress, !streamLoadData).then((fullBuffer) => {
- if (onProgress) onProgress(0, '0%', LoaderStatus.Processing);
- const loadPromise = streamLoadData ? streamLoadPromise : KSplatLoader.loadFromFileData(fullBuffer);
- return loadPromise.then((splatBuffer) => {
- if (onProgress) onProgress(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' };
- // 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 );
- 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;
- 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.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.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.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;
- }
- }
- 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();
- //直接在ray上乘上模型变换
- 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];
- splatTree.splatMesh.getSplatColor(splatGlobalIndex, tempColor);
- splatTree.splatMesh.getSplatCenter(splatGlobalIndex, tempCenter);
- splatTree.splatMesh.getSplatScaleAndRotation(splatGlobalIndex, tempScale, tempRotation);
- if (tempScale.x <= scaleEpsilon || tempScale.y <= scaleEpsilon || tempScale.z <= scaleEpsilon) {
- continue;
- }
- if (!this.raycastAgainstTrueSplatEllipsoid) {//是只需得到近似值的意思吗
- const radius = (tempScale.x + tempScale.y + tempScale.z) / 3; //椭球的平均半径
- if (ray.intersectSphere(tempCenter, radius, tempHit)) { //没细看,是pick球上哪个点?
- 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;
- };
- }();
- }
- /**
- * SplatScene: Descriptor for a single splat scene managed by an instance of SplatMesh.
- */
- class SplatScene {
- constructor(splatBuffer, position = new THREE.Vector3(), quaternion = new THREE.Quaternion(),
- scale = new THREE.Vector3(1, 1, 1), minimumAlpha = 1) {
- this.splatBuffer = splatBuffer;
- this.position = position.clone();
- this.quaternion = quaternion.clone();
- this.scale = scale.clone();
- this.transform = new THREE.Matrix4();
- this.minimumAlpha = minimumAlpha;
- this.updateTransform();
- }
- copyTransformData(otherScene) {
- this.position.copy(otherScene.position);
- this.quaternion.copy(otherScene.quaternion);
- this.scale.copy(otherScene.scale);
- this.transform.copy(otherScene.transform);
- }
- updateTransform() {
- this.transform.compose(this.position, this.quaternion, this.scale);
- }
- }
- /*
- 将点云中的点按照八叉树的方式一层层等分存储。
- 每个子节点自动向下分割,直到层数到达最大,或者其点数量小于最大容纳点数为止。
- */
- 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++) {//查找每一块包含的index
- 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);
- break //xzw!!!! add 防止ply转的如果都是000点,就会每块都有然后不停分割下去
- }
- }
- }
- 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 = {};//清空自身index
- 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) {//traverse
- 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 = 4;
- const CENTER_COLORS_ELEMENTS_PER_TEXEL = 4;
- const TRANSFORM_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;
- /**
- * 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(dynamicMode = true, halfPrecisionCovariancesOnGPU = false, devicePixelRatio = 1,
- enableDistancesComputationOnGPU = true, integerBasedDistancesComputation = false,
- antialiased = false, maxScreenSpaceSplatSize = 2048, logLevel = LogLevel.None, sphericalHarmonicsDegree = 0) {
- super(dummyGeometry, dummyMaterial);
- // Reference to a Three.js renderer
- this.renderer = undefined;
- // Use 16-bit floating point values when storing splat covariance data in textures, instead of 32-bit
- this.halfPrecisionCovariancesOnGPU = halfPrecisionCovariancesOnGPU;
- // 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;
- // 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;
- // Textures in which splat data will be stored for rendering
- this.splatDataTextures = {};
- this.distancesTransformFeedback = {
- 'id': null,
- 'vertexShader': null,
- 'fragmentShader': null,
- 'program': null,
- 'centersBuffer': null,
- 'transformIndexesBuffer': null,
- 'outDistancesBuffer': null,
- 'centersLoc': -1,
- 'modelViewProjLoc': -1,
- 'transformIndexesLoc': -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 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} 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 buildMaterial(dynamicMode = false, antialiased = false, maxScreenSpaceSplatSize = 2048,
- splatScale = 1.0, pointCloudModeEnabled = false, maxSphericalHarmonicsDegree = 0) {
- // Contains the code to project 3D covariance to 2D and from there calculate the quad (using the eigen vectors of the
- // 2D covariance) that is ultimately rasterized
- let vertexShaderSource = `
- precision highp float;
- #include <common>
- attribute uint splatIndex;
- uniform highp sampler2D covariancesTexture;
- uniform highp usampler2D centersColorsTexture;
- uniform highp sampler2D sphericalHarmonicsTexture;`;
- if (dynamicMode) {
- vertexShaderSource += `
- uniform highp usampler2D transformIndexesTexture;
- uniform highp mat4 transforms[${Constants.MaxScenes}];
- uniform vec2 transformIndexesTextureSize;
- `;
- }
- vertexShaderSource += `
- uniform vec2 focal;
- uniform float orthoZoom;
- uniform int orthographicMode;
- uniform int pointCloudModeEnabled;
- uniform float inverseFocalAdjustment;
- uniform vec2 viewport;
- uniform vec2 basisViewport;
- uniform vec2 covariancesTextureSize;
- uniform vec2 centersColorsTextureSize;
- uniform int sphericalHarmonicsDegree;
- uniform vec2 sphericalHarmonicsTextureSize;
- uniform int sphericalHarmonics8BitMode;
- uniform float visibleRegionRadius;
- uniform float visibleRegionFadeStartRadius;
- uniform float firstRenderTime;
- uniform float currentTime;
- uniform int fadeInComplete;
- uniform vec3 sceneCenter;
- uniform float splatScale;
- varying vec4 vColor;
- varying vec2 vUv;
- varying vec2 vPosition;
- 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);
- const vec3 vecOnes3 = vec3(1.0, 1.0, 1.0);
- 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));`;
- if (dynamicMode) {
- vertexShaderSource += `
- uint transformIndex = texture(transformIndexesTexture, getDataUV(1, 0, transformIndexesTextureSize)).r;
- mat4 transform = transforms[transformIndex];
- mat4 transformModelViewMatrix = modelViewMatrix * transform;
- `;
- } else {
- vertexShaderSource += `mat4 transformModelViewMatrix = modelViewMatrix;`;
- }
- vertexShaderSource += `
- 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;
- }
- vPosition = position.xy;
- vColor = uintToRGBAVec(sampledCenterColor.r);
- `;
- if (maxSphericalHarmonicsDegree >= 1) {
- vertexShaderSource += `
- if (sphericalHarmonicsDegree >= 1) {
- `;
- if (dynamicMode) {
- vertexShaderSource += `
- mat4 mTransform = modelMatrix * transform;
- vec3 worldViewDir = normalize(splatCenter - vec3(inverse(mTransform) * vec4(cameraPosition, 1.0)));
- `;
- } else {
- vertexShaderSource += `
- vec3 worldViewDir = normalize(splatCenter - cameraPosition);
- `;
- }
- if (maxSphericalHarmonicsDegree >= 2) {
- vertexShaderSource += `
- vec4 sampledSH0123 = texture(sphericalHarmonicsTexture, getDataUV(6, 0, sphericalHarmonicsTextureSize));
- vec4 sampledSH4567 = texture(sphericalHarmonicsTexture, getDataUV(6, 1, sphericalHarmonicsTextureSize));
- vec4 sampledSH891011 = texture(sphericalHarmonicsTexture, getDataUV(6, 2, sphericalHarmonicsTextureSize));
- vec3 sh1 = sampledSH0123.rgb;
- vec3 sh2 = vec3(sampledSH0123.a, sampledSH4567.rg);
- vec3 sh3 = vec3(sampledSH4567.ba, sampledSH891011.r);
- `;
- } else {
- vertexShaderSource += `
- 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);
- vec3 sh1 = vec3(sampledSH0123.rgb) * (1.0 - fOddOffset) + vec3(sampledSH0123.ba, sampledSH4567.r) * fOddOffset;
- vec3 sh2 = vec3(sampledSH0123.a, sampledSH4567.rg) * (1.0 - fOddOffset) + vec3(sampledSH4567.gba) * fOddOffset;
- vec3 sh3 = vec3(sampledSH4567.ba, sampledSH891011.r) * (1.0 - fOddOffset) + vec3(sampledSH891011.rgb) * fOddOffset;
- `;
- }
- vertexShaderSource += `
- if (sphericalHarmonics8BitMode == 1) {
- sh1 = sh1 * 2.0 - vecOnes3;
- sh2 = sh2 * 2.0 - vecOnes3;
- sh3 = sh3 * 2.0 - vecOnes3;
- }
- float x = worldViewDir.x;
- float y = worldViewDir.y;
- float z = worldViewDir.z;
- vColor.rgb += SH_C1 * (-sh1 * y + sh2 * z - sh3 * x);
- `;
- 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;
- vec4 sampledSH12131415 = texture(sphericalHarmonicsTexture, getDataUV(6, 3, sphericalHarmonicsTextureSize));
- vec4 sampledSH16171819 = texture(sphericalHarmonicsTexture, getDataUV(6, 4, sphericalHarmonicsTextureSize));
- vec4 sampledSH20212223 = texture(sphericalHarmonicsTexture, getDataUV(6, 5, sphericalHarmonicsTextureSize));
- vec3 sh4 = sampledSH891011.gba;
- vec3 sh5 = sampledSH12131415.rgb;
- vec3 sh6 = vec3(sampledSH12131415.a, sampledSH16171819.rg);
- vec3 sh7 = vec3(sampledSH16171819.ba, sampledSH20212223.r);
- vec3 sh8 = sampledSH20212223.gba;
- if (sphericalHarmonics8BitMode == 1) {
- sh4 = sh4 * 2.0 - vecOnes3;
- sh5 = sh5 * 2.0 - vecOnes3;
- sh6 = sh6 * 2.0 - vecOnes3;
- sh7 = sh7 * 2.0 - vecOnes3;
- sh8 = sh8 * 2.0 - vecOnes3;
- }
- 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 += `
- }
- `;
- }
- vertexShaderSource += `
- vec4 sampledCovarianceA = texture(covariancesTexture,
- getDataUVF(nearestEvenIndex, 1.5, oddOffset, covariancesTextureSize));
- vec4 sampledCovarianceB = texture(covariancesTexture,
- getDataUVF(nearestEvenIndex, 1.5, oddOffset + uint(1), covariancesTextureSize));
- vec3 cov3D_M11_M12_M13 = vec3(sampledCovarianceA.rgb) * (1.0 - fOddOffset) +
- vec3(sampledCovarianceA.ba, sampledCovarianceB.r) * fOddOffset;
- vec3 cov3D_M22_M23_M33 = vec3(sampledCovarianceA.a, sampledCovarianceB.rg) * (1.0 - fOddOffset) +
- vec3(sampledCovarianceB.gba) * fOddOffset;
- // 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
- );
-
- // 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. (This assumes假设
- // the current projection is a perspective projection).
- //翻译结果:构建投影矩阵仿射近似的雅可比矩阵。它将被用来变换3D协方差矩阵,而不是直接使用实际的投影矩阵,因为使用实际投影矩阵进行变换需要包含非线性成分(透视除法),这会导致非高斯的结果。这里假设当前的投影为透视投影。
- //看不懂 https://zhuanlan.zhihu.com/p/661569671
-
- 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 += `
- 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];
- float compensation = sqrt(max(detOrig / detBlur, 0.0));
- `;
- } else {
- vertexShaderSource += `
- cov2Dm[0][0] += 0.3;
- cov2Dm[1][1] += 0.3;
- float compensation = 1.0;
- `;
- }
- vertexShaderSource += `
- vColor.a *= compensation;
- if (vColor.a < minAlpha) return;
- // 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]);
- vec3 ndcCenter = clipCenter.xyz / clipCenter.w;
- // 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) * 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 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; //特征值L1
- float eigenValue2 = traceOver2 - term2; //特征值L1
- if (pointCloudModeEnabled == 1) {// each splat is rendered as a filled circle
- 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 (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;
- }
- 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; //将整个正方形做一定的拉伸形变,之后在fs中因为只显示中间的圆,所以也就是显示拉伸后的圆。(vs只计算四个顶点的具体位置,其他位置是插值得到的)
- // Scale the position data we send to the fragment shader
- vPosition *= sqrt8;
- }`;
-
- //面片模拟椭球,总是有厚度
- const fragmentShaderSource = `
- precision highp float;
- #include <common>
-
- uniform vec3 debugColor;
- varying vec4 vColor;
- varying vec2 vUv;
- varying vec2 vPosition;
- 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; //position的范围半径为1。指一个rectangle面中的范围。椭圆外的完全透明
- 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);
- //不知道为何ply转过来的vColor.a是0,(但是在外面访问的opacity并不是0), 导致全是黑色
- }`;
- 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
- },
- 'covariancesTexture': {//协方差
- 'type': 't',
- 'value': null
- },
- 'centersColorsTexture': {
- 'type': 't',
- 'value': null
- },
- 'sphericalHarmonicsTexture': {
- 'type': 't',
- 'value': null
- },
- '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()
- },
- 'covariancesTextureSize': {
- 'type': 'v2',
- 'value': new THREE.Vector2(1024, 1024)
- },
- '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
- },
- 'splatScale': {
- 'type': 'f',
- 'value': splatScale
- },
- 'pointCloudModeEnabled': {
- 'type': 'i',
- 'value': pointCloudModeEnabled ? 1 : 0
- }
- };
- if (dynamicMode) {
- uniforms['transformIndexesTexture'] = {
- 'type': 't',
- 'value': null
- };
- const transformMatrices = [];
- for (let i = 0; i < Constants.MaxScenes; i++) {
- transformMatrices.push(new THREE.Matrix4());
- }
- uniforms['transforms'] = {
- 'type': 'mat4',
- 'value': transformMatrices
- };
- uniforms['transformIndexesTextureSize'] = {
- '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;
- }
- /**
- * 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 buildGeomtery(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;
- }
- /**
- * 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(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);
- scenes[i] = SplatMesh.createScene(splatBuffer, position, rotation, scale, options.splatAlphaRemovalThreshold || 1);
- }
- return scenes;
- }
- static createScene(splatBuffer, position, rotation, scale, minimumAlpha) {
- return new SplatScene(splatBuffer, position, rotation, scale, minimumAlpha);
- }
- /**
- * 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) {
- this.sceneOptions = sceneOptions;
- this.finalBuild = finalBuild;
- const maxSplatCount = SplatMesh.getTotalMaxSplatCountForSplatBuffers(splatBuffers);
- const newScenes = SplatMesh.buildScenes(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();
- 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 = SplatMesh.buildGeomtery(maxSplatCount);
- this.material = SplatMesh.buildMaterial(this.dynamicMode, this.antialiased, this.maxScreenSpaceSplatSize,
- 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.visible = (this.scenes.length > 0);
- return dataUpdateResults;
- }
- /**
- * 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,
- 'transformIndexesBuffer': null,
- 'outDistancesBuffer': null,
- 'centersLoc': -1,
- 'modelViewProjLoc': -1,
- 'transformIndexesLoc': -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;
- } else 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) {
- if (!sinceLastBuildOnly) {
- this.setupDataTextures();
- } else {
- this.updateDataTextures();
- }
- this.updateVisibleRegion(sinceLastBuildOnly);
- }
- setupDataTextures() {
- const maxSplatCount = this.getMaxSplatCount();
- const splatCount = this.getSplatCount();
- this.disposeTextures();
- const computeDataTextureSize = (elementsPerTexel, elementsPerSplatl) => {
- const texSize = new THREE.Vector2(4096, 1024);
- while (texSize.x * texSize.y * elementsPerTexel < maxSplatCount * elementsPerSplatl) texSize.y *= 2;
- return texSize;
- };
- const covarianceCompressionLevel = this.getTargetCovarianceCompressionLevel();
- const sphericalHarmonicsCompressionLevel = this.getTargetSphericalHarmonicsCompressionLevel();
- const covariances = new Float32Array(maxSplatCount * COVARIANCES_ELEMENTS_PER_SPLAT);
- const centers = new Float32Array(maxSplatCount * 3);
- const colors = new Uint8Array(maxSplatCount * 4);
- let SphericalHarmonicsArrayType = Float32Array;
- if (sphericalHarmonicsCompressionLevel === 1) SphericalHarmonicsArrayType = Uint16Array;
- else if (sphericalHarmonicsCompressionLevel === 2) SphericalHarmonicsArrayType = Uint8Array;
- const sphericalHarmonicsComponentCount = getSphericalHarmonicsComponentCountForDegree(this.minSphericalHarmonicsDegree);
- let paddedSphericalHarmonicsComponentCount = sphericalHarmonicsComponentCount;
- if (paddedSphericalHarmonicsComponentCount % 2 !== 0) paddedSphericalHarmonicsComponentCount++;
- const sphericalHarmonics = this.minSphericalHarmonicsDegree ?
- new SphericalHarmonicsArrayType(maxSplatCount * sphericalHarmonicsComponentCount) : undefined;
- this.fillSplatDataArrays(covariances, centers, colors, sphericalHarmonics, undefined,
- covarianceCompressionLevel, sphericalHarmonicsCompressionLevel);
- // set up covariances data texture
- const covTexSize = computeDataTextureSize(COVARIANCES_ELEMENTS_PER_TEXEL, 6);
- let CovariancesDataType = covarianceCompressionLevel >= 1 ? Uint16Array : Float32Array;
- let covariancesTextureType = covarianceCompressionLevel >= 1 ? THREE.HalfFloatType : THREE.FloatType;
- const paddedCovariances = new CovariancesDataType(covTexSize.x * covTexSize.y * COVARIANCES_ELEMENTS_PER_TEXEL);
- paddedCovariances.set(covariances);
- const covTex = new THREE.DataTexture(paddedCovariances, covTexSize.x, covTexSize.y, THREE.RGBAFormat, covariancesTextureType);
- covTex.needsUpdate = true;
- this.material.uniforms.covariancesTexture.value = covTex;
- this.material.uniforms.covariancesTextureSize.value.copy(covTexSize);
- // 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, 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,
- 'centers': centers,
- 'colors': colors,
- 'sphericalHarmonics': sphericalHarmonics
- },
- 'covariances': {
- 'data': paddedCovariances,
- 'texture': covTex,
- 'size': covTexSize,
- 'compressionLevel': covarianceCompressionLevel
- },
- 'centerColors': {
- 'data': paddedCentersCols,
- 'texture': centersColsTex,
- 'size': centersColsTexSize
- }
- };
- if (sphericalHarmonics) {
- const sphericalHarmonicsElementsPerTexel = 4;
- const sphericalHarmonicsTexSize = computeDataTextureSize(sphericalHarmonicsElementsPerTexel,
- paddedSphericalHarmonicsComponentCount);
- const paddedSHArraySize = sphericalHarmonicsTexSize.x * sphericalHarmonicsTexSize.y * sphericalHarmonicsElementsPerTexel;
- const paddedSHArray = new SphericalHarmonicsArrayType(paddedSHArraySize);
- for (let c = 0; c < splatCount; c++) {
- const srcBase = sphericalHarmonicsComponentCount * c;
- const destBase = paddedSphericalHarmonicsComponentCount * c;
- for (let i = 0; i < sphericalHarmonicsComponentCount; i++) {
- paddedSHArray[destBase + i] = sphericalHarmonics[srcBase + i];
- }
- }
- const textureType = sphericalHarmonicsCompressionLevel === 2 ? THREE.UnsignedByteType : THREE.HalfFloatType;
- const sphericalHarmonicsTex = new THREE.DataTexture(paddedSHArray, sphericalHarmonicsTexSize.x,
- sphericalHarmonicsTexSize.y, THREE.RGBAFormat, textureType);
- sphericalHarmonicsTex.needsUpdate = true;
- this.material.uniforms.sphericalHarmonicsTexture.value = sphericalHarmonicsTex;
- this.material.uniforms.sphericalHarmonicsTextureSize.value.copy(sphericalHarmonicsTexSize);
- if (sphericalHarmonicsCompressionLevel === 2) {
- this.material.uniforms.sphericalHarmonics8BitMode.value = 1;
- }
- this.material.uniformsNeedUpdate = true;
- this.splatDataTextures['sphericalHarmonics'] = {
- 'componentCount': sphericalHarmonicsComponentCount,
- 'paddedComponentCount': paddedSphericalHarmonicsComponentCount,
- 'data': paddedSHArray,
- 'texture': sphericalHarmonicsTex,
- 'size': sphericalHarmonicsTexSize,
- 'compressionLevel': sphericalHarmonicsCompressionLevel
- };
- }
- if (this.dynamicMode) {
- const transformIndexesTexSize = computeDataTextureSize(TRANSFORM_INDEXES_ELEMENTS_PER_TEXEL, 4);
- const paddedTransformIndexes = new Uint32Array(transformIndexesTexSize.x *
- transformIndexesTexSize.y * TRANSFORM_INDEXES_ELEMENTS_PER_TEXEL);
- for (let c = 0; c < splatCount; c++) paddedTransformIndexes[c] = this.globalSplatIndexToSceneIndexMap[c];
- const transformIndexesTexture = new THREE.DataTexture(paddedTransformIndexes, transformIndexesTexSize.x,
- transformIndexesTexSize.y, THREE.RedIntegerFormat,
- THREE.UnsignedIntType);
- transformIndexesTexture.internalFormat = 'R32UI';
- transformIndexesTexture.needsUpdate = true;
- this.material.uniforms.transformIndexesTexture.value = transformIndexesTexture;
- this.material.uniforms.transformIndexesTextureSize.value.copy(transformIndexesTexSize);
- this.material.uniformsNeedUpdate = true;
- this.splatDataTextures['tansformIndexes'] = {
- 'data': paddedTransformIndexes,
- 'texture': transformIndexesTexture,
- 'size': transformIndexesTexSize
- };
- }
- }
- updateDataTextures() {
- const splatCount = this.getSplatCount();
- const covarianceCompressionLevel = this.splatDataTextures['covariances'].compressionLevel;
- const sphericalHarmonicsTextureDesc = this.splatDataTextures['sphericalHarmonics'];
- const sphericalHarmonicsCompressionLevel = sphericalHarmonicsTextureDesc ? sphericalHarmonicsTextureDesc.compressionLevel : 0;
- this.fillSplatDataArrays(this.splatDataTextures.baseData.covariances,
- this.splatDataTextures.baseData.centers, this.splatDataTextures.baseData.colors,
- this.splatDataTextures.baseData.sphericalHarmonics, undefined, covarianceCompressionLevel,
- sphericalHarmonicsCompressionLevel, this.lastBuildSplatCount, splatCount - 1, this.lastBuildSplatCount);
- const covariancesTextureDescriptor = this.splatDataTextures['covariances'];
- const paddedCovariances = covariancesTextureDescriptor.data;
- const covariancesTexture = covariancesTextureDescriptor.texture;
- const covarancesStartSplat = this.lastBuildSplatCount * COVARIANCES_ELEMENTS_PER_SPLAT;
- const covariancesEndSplat = splatCount * COVARIANCES_ELEMENTS_PER_SPLAT;
- for (let i = covarancesStartSplat; i < covariancesEndSplat; i++) {
- const covariance = this.splatDataTextures.baseData.covariances[i];
- paddedCovariances[i] = covariance;
- }
- const covariancesTextureProps = this.renderer ? this.renderer.properties.get(covariancesTexture) : null;
- if (!covariancesTextureProps || !covariancesTextureProps.__webglTexture) {
- covariancesTexture.needsUpdate = true;
- } else {
- const covaranceBytesPerElement = covarianceCompressionLevel ? 2 : 4;
- this.updateDataTexture(paddedCovariances, covariancesTextureDescriptor, covariancesTextureProps,
- COVARIANCES_ELEMENTS_PER_TEXEL, COVARIANCES_ELEMENTS_PER_SPLAT, covaranceBytesPerElement,
- this.lastBuildSplatCount, splatCount - 1);
- }
- const centerColorsTextureDescriptor = this.splatDataTextures['centerColors'];
- const paddedCenterColors = centerColorsTextureDescriptor.data;
- const centerColorsTexture = centerColorsTextureDescriptor.texture;
- SplatMesh.updateCenterColorsPaddedData(this.lastBuildSplatCount, splatCount, 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, centerColorsTextureProps,
- CENTER_COLORS_ELEMENTS_PER_TEXEL, CENTER_COLORS_ELEMENTS_PER_SPLAT, 4,
- this.lastBuildSplatCount, splatCount - 1);
- }
- if (this.splatDataTextures.baseData.sphericalHarmonics) {
- const sphericalHarmonicsComponentCount = sphericalHarmonicsTextureDesc.componentCount;
- const paddedSphericalHarmonicsComponentCount = sphericalHarmonicsTextureDesc.paddedComponentCount;
- const paddedSHArray = sphericalHarmonicsTextureDesc.data;
- for (let c = this.lastBuildSplatCount; c < splatCount; c++) {
- const srcBase = sphericalHarmonicsComponentCount * c;
- const destBase = paddedSphericalHarmonicsComponentCount * c;
- for (let i = 0; i < sphericalHarmonicsComponentCount; i++) {
- paddedSHArray[destBase + i] = this.splatDataTextures.baseData.sphericalHarmonics[srcBase + i];
- }
- }
- const sphericalHarmonicsTex = sphericalHarmonicsTextureDesc.texture;
- const sphericalHarmonicsTextureProps = this.renderer ? this.renderer.properties.get(sphericalHarmonicsTex) : null;
- if (!sphericalHarmonicsTextureProps || !sphericalHarmonicsTextureProps.__webglTexture) {
- sphericalHarmonicsTex.needsUpdate = true;
- } else {
- const sphericalHarmonicsElementsPerTexel = 4;
- let sphericalHarmonicsBytesPerElement = 4;
- if (sphericalHarmonicsCompressionLevel === 1) sphericalHarmonicsBytesPerElement = 2;
- else if (sphericalHarmonicsCompressionLevel === 2) sphericalHarmonicsBytesPerElement = 1;
- this.updateDataTexture(paddedSHArray, sphericalHarmonicsTextureDesc, sphericalHarmonicsTextureProps,
- sphericalHarmonicsElementsPerTexel, paddedSphericalHarmonicsComponentCount,
- sphericalHarmonicsBytesPerElement, this.lastBuildSplatCount, splatCount - 1);
- }
- }
- if (this.dynamicMode) {
- const transformIndexesTexDesc = this.splatDataTextures['tansformIndexes'];
- const paddedTransformIndexes = transformIndexesTexDesc.data;
- for (let c = this.lastBuildSplatCount; c < splatCount; c++) {
- paddedTransformIndexes[c] = this.globalSplatIndexToSceneIndexMap[c];
- }
- const transformIndexesTexture = transformIndexesTexDesc.texture;
- const transformIndexesTextureProps = this.renderer ? this.renderer.properties.get(transformIndexesTexture) : null;
- if (!transformIndexesTextureProps || !transformIndexesTextureProps.__webglTexture) {
- transformIndexesTexture.needsUpdate = true;
- } else {
- this.updateDataTexture(paddedTransformIndexes, transformIndexesTexDesc, transformIndexesTextureProps, 1, 1, 1,
- this.lastBuildSplatCount, splatCount - 1);
- }
- }
- }
- 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, textureDesc, textureProps, elementsPerTexel, elementsPerSplat, bytesPerElement, from, to) {
- const gl = this.renderer.getContext();
- const updateRegion = SplatMesh.computeTextureUpdateRegion(from, to, textureDesc.size.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 dataTexture = textureDesc.texture;
- const glType = this.webGLUtils.convert(dataTexture.type);
- const glFormat = this.webGLUtils.convert(dataTexture.format, dataTexture.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, textureDesc.size.x, updateHeight, glFormat, glType, updateDataView);
- //(target, level, xoffset, yoffset, width, height, format, type, pixels);
- gl.bindTexture(gl.TEXTURE_2D, currentTexture);
- }
-
- static updateCenterColorsPaddedData(to, from, centers, colors, paddedCenterColors) {
- for (let c = to; c < from; 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]);
- }
-
- }
- 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, false);
- 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) {//仅渲染前renderSplatCount个点,在前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;
- }//注:假设全部点都渲染的话,是无所谓index的顺序的,可以直接第0个为0,第n个为n……
- /**
- * 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();
- }
- }
- 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);
- }
- }
- 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 transformIndex;
- uniform ivec4 transforms[${Constants.MaxScenes}];
- void main(void) {
- ivec4 transform = transforms[transformIndex];
- 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 transformIndex;
- uniform mat4 transforms[${Constants.MaxScenes}];
- void main(void) {
- vec4 transformedCenter = transforms[transformIndex] * 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.transformIndexesLoc =
- gl.getAttribLocation(this.distancesTransformFeedback.program, 'transformIndex');
- 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.transformIndexesBuffer = gl.createBuffer();
- gl.bindBuffer(gl.ARRAY_BUFFER, this.distancesTransformFeedback.transformIndexesBuffer);
- gl.enableVertexAttribArray(this.distancesTransformFeedback.transformIndexesLoc);
- gl.vertexAttribIPointer(this.distancesTransformFeedback.transformIndexesLoc, 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>} transformIndexes The splat transform indexes
- * @param {number} offsetSplats Offset in the GPU buffer at which to start updating data, specified in splats
- */
- updateGPUTransformIndexesBufferForDistancesComputation(isUpdate, transformIndexes, 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.transformIndexesBuffer);
- if (isUpdate) {
- gl.bufferSubData(gl.ARRAY_BUFFER, subBufferOffset, transformIndexes);
- } else {
- const maxArray = new Uint32Array(this.getMaxSplatCount() * 4);
- maxArray.set(transformIndexes);
- 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.transformIndexesBuffer);
- gl.enableVertexAttribArray(this.distancesTransformFeedback.transformIndexesLoc);
- gl.vertexAttribIPointer(this.distancesTransformFeedback.transformIndexesLoc, 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} 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, centers, colors, sphericalHarmonics, applySceneTransform,
- covarianceCompressionLevel = 0, sphericalHarmonicsCompressionLevel = 1, srcStart, srcEnd, destStart = 0) {
- 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;
- const sceneTransform = applySceneTransform ? scene.transform : null;
- if (covariances) {
- splatBuffer.fillSplatCovarianceArray(covariances, sceneTransform,
- srcStart, srcEnd, destStart, covarianceCompressionLevel);
- }
- 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, floatCenters, null, null, 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, floatCenters, null, null, 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 = {};
- return function(globalIndex, outScale, outRotation, applySceneTransform) {
- this.getLocalSplatParameters(globalIndex, paramsObj, applySceneTransform);
- paramsObj.splatBuffer.getSplatScaleAndRotation(paramsObj.localIndex, outScale, outRotation, paramsObj.sceneTransform);
- };
- }();
- /**
- * 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();
- 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];
- }
- 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 = "AGFzbQEAAAAADAZkeWxpbmsAAAAAAAEbA2AAAGAQf39/f39/f39/f39/f39/fwBgAAF/AhIBA2VudgZtZW1vcnkCAwCAgAQDBAMAAQIHOQMRX193YXNtX2NhbGxfY3RvcnMAAAtzb3J0SW5kZXhlcwABE2Vtc2NyaXB0ZW5fdGxzX2luaXQAAgrHEAMDAAELuxAFAXwDewJ/A30CfiALIAprIQwCQCAOBEAgDQRAQfj///8HIQ5BiICAgHghDSALIAxNDQIgDCEBA0AgAyABQQJ0IgVqIAIgACAFaigCAEECdGooAgAiBTYCACAFIA4gBSAOSBshDiAFIA0gBSANShshDSABQQFqIgEgC0cNAAsMAgsgDwRAQfj///8HIQ5BiICAgHghDSALIAxNDQJBfyEPIAwhAgNAIA8gByAAIAJBAnQiFGooAgAiFUECdGooAgAiCkcEQAJ+IAX9CQIIIAggCkEGdGoiD/0JAgAgDyoCEP0gASAPKgIg/SACIA8qAjD9IAP95gEgBf0JAhggD/0JAgQgDyoCFP0gASAPKgIk/SACIA8qAjT9IAP95gH95AEgBf0JAiggD/0JAgggDyoCGP0gASAPKgIo/SACIA8qAjj9IAP95gH95AEgBf0JAjggD/0JAgwgDyoCHP0gASAPKgIs/SACIA8qAjz9IAP95gH95AEiEf0fArv9FCAR/R8Du/0iAf0MAAAAAABAj0AAAAAAAECPQCIS/fIBIhP9IQEiEJlEAAAAAAAA4ENjBEAgELAMAQtCgICAgICAgICAfwshGQJ+IBP9IQAiEJlEAAAAAAAA4ENjBEAgELAMAQtCgICAgICAgICAfwv9EiETAn4gEf0fALv9FCAR/R8Bu/0iASAS/fIBIhH9IQEiEJlEAAAAAAAA4ENjBEAgELAMAQtCgICAgICAgICAfwshGiATIBn9HgEhEgJ+IBH9IQAiEJlEAAAAAAAA4ENjBEAgELAMAQtCgICAgICAgICAfwv9EiAa/R4BIBL9DQABAgMICQoLEBESExgZGhshEiAKIQ8LIAMgFGogASAVQQR0av0AAAAgEv21ASIR/RsAIBH9GwFqIBH9GwJqIBH9GwNqIgo2AgAgCiAOIAogDkgbIQ4gCiANIAogDUobIQ0gAkEBaiICIAtHDQALDAILAn8gBSoCGLtEAAAAAABAj0CiIhCZRAAAAAAAAOBBYwRAIBCqDAELQYCAgIB4CyEKAn8gBSoCCLtEAAAAAABAj0CiIhCZRAAAAAAAAOBBYwRAIBCqDAELQYCAgIB4CyECAn8gBSoCKLtEAAAAAABAj0CiIhCZRAAAAAAAAOBBYwRAIBCqDAELQYCAgIB4CyEFQfj///8HIQ5BiICAgHghDSALIAxNDQEgAv0RIAr9HAEgBf0cAiESIAwhBQNAIAMgBUECdCICaiABIAAgAmooAgBBBHRq/QAAACAS/bUBIhH9GwAgEf0bAWogEf0bAmoiAjYCACACIA4gAiAOSBshDiACIA0gAiANShshDSAFQQFqIgUgC0cNAAsMAQsgDQRAQfj///8HIQ5BiICAgHghDSALIAxNDQEgDCEBA0AgAyABQQJ0IgVqAn8gAiAAIAVqKAIAQQJ0aioCALtEAAAAAAAAsECiIhCZRAAAAAAAAOBBYwRAIBCqDAELQYCAgIB4CyIKNgIAIAogDiAKIA5IGyEOIAogDSAKIA1KGyENIAFBAWoiASALRw0ACwwBCwJAIA9FBEAgCyAMSw0BQYiAgIB4IQ1B+P///wchDgwCC0H4////ByEOQYiAgIB4IQ0gCyAMTQ0BQX8hDyAMIQIDQCAPIAcgACACQQJ0IhRqKAIAQQJ0IhVqKAIAIgpHBEAgBf0JAgggCCAKQQZ0aiIP/QkCACAPKgIQ/SABIA8qAiD9IAIgDyoCMP0gA/3mASAF/QkCGCAP/QkCBCAPKgIU/SABIA8qAiT9IAIgDyoCNP0gA/3mAf3kASAF/QkCKCAP/QkCCCAPKgIY/SABIA8qAij9IAIgDyoCOP0gA/3mAf3kASAF/QkCOCAP/QkCDCAPKgIc/SABIA8qAiz9IAIgDyoCPP0gA/3mAf3kASERIAohDwsgAyAUagJ/IBEgASAVQQJ0IgpqKQIA/RL95gEiEv0fACAS/R8BkiARIBH9DQgJCgsMDQ4PAAAAAAAAAAAgASAKQQhyaikCAP0S/eYBIhL9HwCSIBL9HwGSu0QAAAAAAACwQKIiEJlEAAAAAAAA4EFjBEAgEKoMAQtBgICAgHgLIgo2AgAgCiAOIAogDkgbIQ4gCiANIAogDUobIQ0gAkEBaiICIAtHDQALDAELIAUqAighFiAFKgIYIRcgBSoCCCEYQfj///8HIQ5BiICAgHghDSAMIQUDQAJ/IBggASAAIAVBAnQiB2ooAgBBBHRqIgIqAgCUIBcgAioCBJSSIBYgAioCCJSSu0QAAAAAAACwQKIiEJlEAAAAAAAA4EFjBEAgEKoMAQtBgICAgHgLIQogAyAHaiAKNgIAIAogDiAKIA5IGyEOIAogDSAKIA1KGyENIAVBAWoiBSALRw0ACwsgCyAMSwRAIAlBAWuzIA2yIA6yk5UhFiAMIQ0DQAJ/IBYgAyANQQJ0aiIBKAIAIA5rspQiF4tDAAAAT10EQCAXqAwBC0GAgICAeAshCiABIAo2AgAgBCAKQQJ0aiIBIAEoAgBBAWo2AgAgDUEBaiINIAtHDQALCyAJQQJPBEAgBCgCACENQQEhDgNAIAQgDkECdGoiASABKAIAIA1qIg02AgAgDkEBaiIOIAlHDQALCyAMQQBKBEAgDCEOA0AgBiAOQQFrIgFBAnQiAmogACACaigCADYCACAOQQFKIQIgASEOIAINAAsLIAsgDEoEQCALIQ4DQCAGIAsgBCADIA5BAWsiDkECdCIBaigCAEECdGoiAigCACIFa0ECdGogACABaigCADYCACACIAVBAWs2AgAgDCAOSA0ACwsLBABBAAs=";
- //sortIndexes 按照深度排序
- function sortWorker(self) {
- let wasmInstance;
- let wasmMemory;
- let useSharedMemory;
- let integerBasedSort;
- let dynamicMode;
- let splatCount;
- let indexesToSortOffset; //存储首地址
- let sortedIndexesOffset;
- let transformIndexesOffset;
- 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);//由于新数组已经与 WebAssembly 内存关联,实际上是将 modelViewProj 的数据复制到了 WebAssembly 内存的指定位置。
- new Uint32Array(wasmMemory, frequenciesOffset, Constants.DepthMapRange).set(countsZero);
-
- wasmInstance.exports.sortIndexes(indexesToSortOffset, centersOffset, precomputedDistancesOffset,
- mappedDistancesOffset, frequenciesOffset, modelViewProjOffset,
- sortedIndexesOffset, transformIndexesOffset, 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;
- transformIndexes = e.data.transformIndexes;
- 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, transformIndexesOffset + e.data.range.from * 4,
- e.data.range.count).set(new Uint32Array(transformIndexes));
- }
- self.postMessage({
- 'centerDataSet': true,
- });
- } else if (e.data.sort) {//请求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 * 2, //创建一个初始大小为 .. 页(每页64KiB)的内存实例
- maximum: totalPagesRequired * 4,
- 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;
- transformIndexesOffset = sortedIndexesOffset + memoryRequiredForSortedIndexes;
- transformsOffset = transformIndexesOffset + memoryRequiredforTransformIndexes;
- wasmMemory = sorterWasmImport.env.memory.buffer; //SharedArrayBuffer
- if (useSharedMemory) {
- self.postMessage({
- 'sortSetupPhase1Complete': true,
- 'indexesToSortBuffer': wasmMemory,
- 'indexesToSortOffset': indexesToSortOffset,
- 'sortedIndexesBuffer': wasmMemory, //整理好的indexes
- 'sortedIndexesOffset': sortedIndexesOffset,
- 'precomputedDistancesBuffer': wasmMemory,
- 'precomputedDistancesOffset': precomputedDistancesOffset,
- 'transformsBuffer': wasmMemory,
- 'transformsOffset': transformsOffset
- });
- } else {
- self.postMessage({
- 'sortSetupPhase1Complete': true
- });
- }
- });
- }
- };
- }
- function createSortWorker(splatCount, useSharedMemory, integerBasedSort, dynamicMode) {
- const worker = new Worker(
- URL.createObjectURL(
- new Blob(['(', sortWorker.toString(), ')(self)'], {
- type: 'application/javascript',
- }),
- ),
- );
- const sorterWasmBinaryString = atob(SorterWasm);//二进制代码
- 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;
- }
- /*
- 相关函数见 setupSortWorker updateSplatSort updateRenderIndexes addSplatBuffers
- 为什么要手动排序?shader无法计算准确的深度吗
- */
- 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 || 2048;
- // 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 - 3. Default value is 0.
- this.sphericalHarmonicsDegree = options.sphericalHarmonicsDegree || 0;
- 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.runAfterFirstSort = [];
- 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;
- if (!this.dropInMode) this.init();
-
-
-
-
-
-
- }
- createSplatMesh() {
- this.splatMesh = new SplatMesh(this.dynamicScene, this.halfPrecisionCovariancesOnGPU, this.devicePixelRatio,
- this.gpuAcceleratedSort, this.integerBasedSort, this.antialiased,
- this.maxScreenSpaceSplatSize, this.logLevel, this.sphericalHarmonicsDegree);
- this.splatMesh.frustumCulled = false;
- }
- 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.perspectiveControls, this.orthographicControls]) {
- 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);
- }
- }
- this.controls = this.camera.isOrthographicCamera ? this.orthographicControls : this.perspectiveControls;
- }
- }
- 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;
- }
- 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.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;
- //this.camera.projectionMatrix.elements[0] 等效于 (2 * n) / (r - l),其中n是焦距(到近裁剪面的距离)。当它乘以0.5,然后再乘以renderDimensions.x(即视口宽度)时,你得到的是 (n * 宽度) / (r - l)。由于r - l等于宽度,最终就只剩下了n,即焦距。对于cameraFocalLengthY的计算应该得出相同的结果,所以实际上我只需要向updateUniforms()函数传递一个单独的焦距值即可。(see:https://github.com/mkkellogg/GaussianSplats3D/issues/108)
- 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.');
- }
- const format = (options.format !== undefined && options.format !== null) ? options.format : sceneFormatFromPath(path);
- const streamBuildSections = Viewer.isStreamable(format) && options.streamView;
- 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 (streamBuildSections) {
- 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...');
- } else {
- this.loadingSpinner.setMessageForTask(loadingUITaskId, 'Ready!');
- }
- }
- };
- let downloadDone = false;
- let downloadedPercentage = 0;
- const splatBuffersAddedUIUpdate = (firstBuild, finalBuild) => {
- if (showLoadingUI) {
- if (firstBuild && streamBuildSections || finalBuild && !streamBuildSections) {
- this.runAfterFirstSort.push(() => {
- this.loadingSpinner.removeTask(loadingUITaskId);
- if (!finalBuild && !downloadDone) this.loadingProgressBar.show();
- });
- }
- if (streamBuildSections) {
- 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 (!streamBuildSections && 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).then(() => {
- if (!streamBuildSections && options.onProgress) options.onProgress(100, '100%', LoaderStatus.Processing);
- splatBuffersAddedUIUpdate(firstBuild, finalBuild);
- });
- };
- const loadFunc = streamBuildSections ? this.downloadAndBuildSingleSplatSceneStreaming.bind(this) :
- this.downloadAndBuildSingleSplatSceneNonStreaming.bind(this);
- return loadFunc(path, format, options.splatAlphaRemovalThreshold, buildSection.bind(this), onProgress, hideLoadingUI.bind(this));
- }
- /**
- * Download a single non-streamed splat scene, convert to splat buffer and then rebuild the viewer's splat mesh
- * 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 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}
- */
- downloadAndBuildSingleSplatSceneNonStreaming(path, format, splatAlphaRemovalThreshold, buildFunc, onProgress, onException) {
- const downloadPromise = this.downloadSplatSceneToSplatBuffer(path, splatAlphaRemovalThreshold, onProgress, false, undefined, format)
- .then((splatBuffer) => {
- this.removeSplatSceneDownloadPromise(downloadPromise);
- return buildFunc(splatBuffer, true, true).then(() => {
- this.clearSplatSceneDownloadAndBuildPromise();
- });
- })
- .catch((e) => {
- if (onException) onException();
- this.clearSplatSceneDownloadAndBuildPromise();
- this.removeSplatSceneDownloadPromise(downloadPromise);
- if (!(e instanceof AbortedPromiseError)) {
- throw (new Error(`Viewer::addSplatScene -> Could not load file ${path}`));
- }
- });
- this.addSplatSceneDownloadPromise(downloadPromise);
- this.setSplatSceneDownloadAndBuildPromise(downloadPromise);
- return downloadPromise;
- }
- /**
- * Download a single splat scene and convert to splat buffer in a streamed 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}
- */
- downloadAndBuildSingleSplatSceneStreaming(path, format, splatAlphaRemovalThreshold, buildFunc,
- onDownloadProgress, onDownloadException) {
- let firstStreamedSectionDownloadAndBuildResolver;
- let firstStreamedSectionDownloadAndBuildRejecter;
- let splatSceneDownloadAndBuildResolver;
- let splatSceneDownloadAndBuildRejecter;
- let steamedSectionBuildCount = 0;
- let streamedSectionBuilding = false;
- const queuedStreamedSectionBuilds = [];
- const checkAndBuildStreamedSections = () => {
- if (queuedStreamedSectionBuilds.length > 0 && !streamedSectionBuilding && !this.isDisposingOrDisposed()) {
- streamedSectionBuilding = true;
- const queuedBuild = queuedStreamedSectionBuilds.shift();
- buildFunc(queuedBuild.splatBuffer, queuedBuild.firstBuild, queuedBuild.finalBuild)
- .then(() => {
- streamedSectionBuilding = false;
- if (queuedBuild.firstBuild) {
- firstStreamedSectionDownloadAndBuildRejecter = null;
- firstStreamedSectionDownloadAndBuildResolver();
- } else if (queuedBuild.finalBuild) {
- splatSceneDownloadAndBuildResolver();
- this.clearSplatSceneDownloadAndBuildPromise();
- }
- if (queuedStreamedSectionBuilds.length > 0) delayedExecute(() => checkAndBuildStreamedSections());
- });
- }
- };
- const onStreamedSectionProgress = (splatBuffer, finalBuild) => {
- if (!this.isDisposingOrDisposed()) {
- if (finalBuild || queuedStreamedSectionBuilds.length === 0 ||
- splatBuffer.getSplatCount() > queuedStreamedSectionBuilds[0].splatBuffer.getSplatCount()) {
- queuedStreamedSectionBuilds.push({
- splatBuffer,
- firstBuild: steamedSectionBuildCount === 0,
- finalBuild
- });
- steamedSectionBuildCount++;
- checkAndBuildStreamedSections();
- }
- }
- };
- let splatSceneDownloadPromise = this.downloadSplatSceneToSplatBuffer(path, splatAlphaRemovalThreshold,
- onDownloadProgress, true, onStreamedSectionProgress, format);
- const firstStreamedSectionBuildPromise = new AbortablePromise((resolver, rejecter) => {
- firstStreamedSectionDownloadAndBuildResolver = resolver;
- firstStreamedSectionDownloadAndBuildRejecter = rejecter;
- }, splatSceneDownloadPromise.abortHandler);
- const splatSceneDownloadAndBuildPromise = new AbortablePromise((resolver, rejecter) => {
- splatSceneDownloadAndBuildResolver = resolver;
- splatSceneDownloadAndBuildRejecter = rejecter;
- });
- this.addSplatSceneDownloadPromise(splatSceneDownloadPromise);
- this.setSplatSceneDownloadAndBuildPromise(splatSceneDownloadAndBuildPromise);
- splatSceneDownloadPromise.then(() => {
- this.removeSplatSceneDownloadPromise(splatSceneDownloadPromise);
- })
- .catch((e) => {
- this.clearSplatSceneDownloadAndBuildPromise();
- this.removeSplatSceneDownloadPromise(splatSceneDownloadPromise);
- if (!(e instanceof AbortedPromiseError)) {
- splatSceneDownloadAndBuildRejecter(e);
- if (firstStreamedSectionDownloadAndBuildRejecter) firstStreamedSectionDownloadAndBuildRejecter(e);
- if (onDownloadException) onDownloadException(e);
- }
- });
- return firstStreamedSectionBuildPromise;
- }
- /**
- * 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 = [];
- if (showLoadingUI) {
- this.loadingSpinner.removeAllTasks();
- this.loadingSpinner.show();
- }
- const onLoadProgress = (fileIndex, percent, percentLabel) => {
- 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) {
- this.loadingSpinner.setMessage(totalPercent == 100 ? `Download complete!` : `Downloading: ${percentLabel}`);
- }
- if (onProgress) onProgress(totalPercent, percentLabel, LoaderStatus.Downloading);
- };
- const downloadPromises = [];
- const nativeLoadPromises = [];
- const abortHandlers = [];
- 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 downloadPromise = this.downloadSplatSceneToSplatBuffer(options.path, options.splatAlphaRemovalThreshold,
- onLoadProgress.bind(this, i), false, undefined, format);
- abortHandlers.push(downloadPromise.abortHandler);
- downloadPromises.push(downloadPromise);
- nativeLoadPromises.push(downloadPromise.promise);
- this.addSplatSceneDownloadPromise(downloadPromise);
- }
- const downloadPromise = new AbortablePromise((resolve, reject) => {
- Promise.all(nativeLoadPromises)
- .then((splatBuffers) => {
- if (showLoadingUI) this.loadingSpinner.hide();
- if (onProgress) options.onProgress(0, '0%', LoaderStatus.Processing);
- this.addSplatBuffers(splatBuffers, sceneOptions, true, showLoadingUI, showLoadingUI).then(() => {
- if (onProgress) onProgress(100, '100%', LoaderStatus.Processing);
- this.clearSplatSceneDownloadAndBuildPromise();
- resolve();
- });
- })
- .catch((e) => {
- if (showLoadingUI) this.loadingSpinner.hide();
- this.clearSplatSceneDownloadAndBuildPromise();
- if (!(e instanceof AbortedPromiseError)) {
- reject(new Error(`Viewer::addSplatScenes -> Could not load one or more splat scenes.`));
- } else {
- resolve();
- }
- })
- .finally(() => {
- for (let downloadPromise of downloadPromises) {
- this.removeSplatSceneDownloadPromise(downloadPromise);
- }
- });
- }, () => {
- for (let abortHandler of abortHandlers) abortHandler();
- });
- this.setSplatSceneDownloadAndBuildPromise(downloadPromise);
- return downloadPromise;
- }
- /**
- * 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} streamBuiltSections 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,
- streamBuiltSections = false, onSectionBuilt = undefined, format) {
- if (format === SceneFormat.Splat) {
- return SplatLoader.loadFromURL(path, onProgress, streamBuiltSections, onSectionBuilt, splatAlphaRemovalThreshold, 0, false);
- } else if (format === SceneFormat.KSplat) {
- return KSplatLoader.loadFromURL(path, onProgress, streamBuiltSections, onSectionBuilt);
- } else if (format === SceneFormat.Ply) {
- return PlyLoader.loadFromURL(path, onProgress, streamBuiltSections, onSectionBuilt,
- splatAlphaRemovalThreshold, 0, this.sphericalHarmonicsDegree);
- }
- return AbortablePromise.reject(new Error(`Viewer::downloadSplatSceneToSplatBuffer -> File format not supported: ${path}`));
- }
- static isStreamable(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) {
- if (this.isDisposingOrDisposed()) return Promise.resolve();
- this.splatRenderReady = false;
- let splatProcessingTaskId = null;
- const finish = (buildResults) => {
- if (this.isDisposingOrDisposed()) return;
- if (splatProcessingTaskId !== null) {
- this.loadingSpinner.removeTask(splatProcessingTaskId);
- splatProcessingTaskId = null;
- }
- // 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,
- 'transformIndexes': buildResults.sceneIndexes.buffer,
- 'range': {
- 'from': buildResults.from,
- 'to': buildResults.to,
- 'count': buildResults.count
- }
- });
- }
- this.splatRenderReady = true;
- this.sortNeededForSceneChange = true;
- };
- 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);
- 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. This function is additive; all splat
- * buffers contained by the viewer's splat mesh before calling this function will be preserved.
- * @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(splatBuffers, splatBufferOptions, finalBuild = true, showLoadingUIForSplatTreeBuild = false) {
- if (this.isDisposingOrDisposed()) return;
- const allSplatBuffers = this.splatMesh.splatBuffers || [];
- const allSplatBufferOptions = this.splatMesh.splatBufferOptions || [];
- allSplatBuffers.push(...splatBuffers);
- allSplatBufferOptions.push(...splatBufferOptions);
- if (this.renderer) this.splatMesh.setRenderer(this.renderer);
- let splatOptimizingTaskId;
- 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);
- }
- };
- return this.splatMesh.build(allSplatBuffers, allSplatBufferOptions, true, finalBuild, onSplatTreeIndexesUpload, onSplatTreeReady);
- }
- /**
- * 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.integerBasedSort, this.splatMesh.dynamicMode);
- let sortCount = 0;
- 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 (sortCount === 0) {
- this.runAfterFirstSort.forEach((func) => {
- func();
- });
- this.runAfterFirstSort.length = 0;
- }
- sortCount++;
- } 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, //这是以后就是每次要渲染的indexes
- e.data.sortedIndexesOffset, maxSplatCount);
- this.sortWorkerIndexesToSort = new Uint32Array(e.data.indexesToSortBuffer, //准备sort的点,每次镜头改变时先选出要渲染的点,再排序
- 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; //初始化待sort的所有index,按顺序赋值就行
- 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(index, 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 = [];
- const savedVisibleRegionFadeStartRadius = this.splatMesh.visibleRegionFadeStartRadius;
- for (let i = 0; i < this.splatMesh.scenes.length; i++) {
- if (i !== index) {
- 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.createSplatMesh();
- this.addSplatBuffers(savedSplatBuffers, savedSceneOptions, true, false, true)
- .then(() => {
- if (checkForEarlyExit()) return;
- checkAndHideLoadingUI();
- this.splatMesh.visibleRegionFadeStartRadius = savedVisibleRegionFadeStartRadius;
- 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.updateSplatSort(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() {
- this.disposing = true;
- 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);
- }
- const 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;
- });
- promisesToAbort.forEach((toAbort) => {
- toAbort.abort();
- });
- return 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)) {//xzw
- 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.splatMesh.updateVisibleRegionFadeDistance(this.sceneRevealMode);
- this.updateSplatSort();
- 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);
- }
- }
- updateSplatSort = 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 async function(force = false) {
- if (this.sortRunning) return;
- if (this.splatMesh.getSplatCount() <= 0) return;
- 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.sortNeededForSceneChange && !this.splatMesh.dynamicMode && queuedSorts.length === 0) {
- if (angleDiff <= 0.99) needsRefreshForRotation = true;
- if (positionDiff >= 1.0) needsRefreshForPosition = true;
- if (!needsRefreshForRotation && !needsRefreshForPosition) return;
- }
- }
- 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);
- if (this.gpuAcceleratedSort && (queuedSorts.length <= 1 || queuedSorts.length % 2 === 0)) {
- await this.splatMesh.computeDistancesOnGPU(mvpMatrix, this.sortWorkerPrecomputedDistances);
- }
- 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);
- }
- this.sortNeededForSceneChange = false;
- };
- }();
- /**
- * 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) => { //从近到远排序。去掉后也不影响,毕竟之后还要sort index
- if (a.data.distanceToNode < b.data.distanceToNode) return -1;
- else return 1;
- });
-
- //将要渲染的点的index写入sortWorkerIndexesToSort的buffer
- 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, //写入sortWorkerIndexesToSort
- 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);
- }
- 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.callbackMesh = DropInViewer.createCallbackMesh();
- this.add(this.callbackMesh);
- this.callbackMesh.onBeforeRender = DropInViewer.onBeforeRender.bind(this, this.viewer);
- }
- /**
- * 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) {
- return this.viewer.removeSplatScene(index);
- }
- dispose() {
- return this.viewer.dispose();
- }
- static onBeforeRender(viewer, renderer, threeScene, camera) {
- if (this.splatMesh !== this.viewer.splatMesh) {
- if (this.splatMesh) {
- this.remove(this.splatMesh);
- }
- this.splatMesh = this.viewer.splatMesh;
- this.add(this.viewer.splatMesh);
- }
- 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, CompressedPlyParser, DropInViewer, KSplatLoader, Utils as LoaderUtils, LogLevel, OrbitControls, PlyLoader, PlyParser, RenderMode, SceneFormat, SceneRevealMode, SplatBuffer, SplatBufferGenerator, SplatLoader, SplatParser, SplatPartitioner, Viewer, WebXRMode
- ,Raycaster //add
- };
- //# sourceMappingURL=gaussian-splats-3d.module.js.map
|