| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644464546464647464846494650465146524653465446554656465746584659466046614662466346644665466646674668466946704671467246734674467546764677467846794680468146824683468446854686468746884689469046914692469346944695469646974698469947004701470247034704470547064707470847094710471147124713471447154716471747184719472047214722472347244725472647274728472947304731473247334734473547364737473847394740474147424743474447454746474747484749475047514752475347544755475647574758475947604761476247634764476547664767476847694770477147724773477447754776477747784779478047814782478347844785478647874788478947904791479247934794479547964797479847994800480148024803480448054806480748084809481048114812481348144815481648174818481948204821482248234824482548264827482848294830483148324833483448354836483748384839484048414842484348444845484648474848484948504851485248534854485548564857485848594860486148624863486448654866486748684869487048714872487348744875487648774878487948804881488248834884488548864887488848894890489148924893489448954896489748984899490049014902490349044905490649074908490949104911491249134914491549164917491849194920492149224923492449254926492749284929493049314932493349344935493649374938493949404941494249434944494549464947494849494950495149524953495449554956495749584959496049614962496349644965496649674968496949704971497249734974497549764977497849794980498149824983498449854986498749884989499049914992499349944995499649974998499950005001500250035004500550065007500850095010501150125013501450155016501750185019502050215022502350245025502650275028502950305031503250335034503550365037503850395040504150425043504450455046504750485049505050515052505350545055505650575058505950605061506250635064506550665067506850695070507150725073507450755076507750785079508050815082508350845085508650875088508950905091509250935094509550965097509850995100510151025103510451055106510751085109511051115112511351145115511651175118511951205121512251235124512551265127512851295130513151325133513451355136513751385139514051415142514351445145514651475148514951505151515251535154515551565157515851595160516151625163516451655166516751685169517051715172517351745175517651775178517951805181518251835184518551865187518851895190519151925193519451955196519751985199520052015202520352045205520652075208520952105211521252135214521552165217521852195220522152225223522452255226522752285229523052315232523352345235523652375238523952405241524252435244524552465247524852495250525152525253525452555256525752585259526052615262526352645265526652675268526952705271527252735274527552765277527852795280528152825283528452855286528752885289529052915292529352945295529652975298529953005301530253035304530553065307530853095310531153125313531453155316531753185319532053215322532353245325532653275328532953305331533253335334533553365337533853395340534153425343534453455346534753485349535053515352535353545355535653575358535953605361536253635364536553665367536853695370537153725373537453755376537753785379538053815382538353845385538653875388538953905391539253935394539553965397539853995400540154025403540454055406540754085409541054115412541354145415541654175418541954205421542254235424542554265427542854295430543154325433543454355436543754385439544054415442544354445445544654475448544954505451545254535454545554565457545854595460546154625463546454655466546754685469547054715472547354745475547654775478547954805481548254835484548554865487548854895490549154925493549454955496549754985499550055015502550355045505550655075508550955105511551255135514551555165517551855195520552155225523552455255526552755285529553055315532553355345535553655375538553955405541554255435544554555465547554855495550555155525553555455555556555755585559556055615562556355645565556655675568556955705571557255735574557555765577557855795580558155825583558455855586558755885589559055915592559355945595559655975598559956005601560256035604560556065607560856095610561156125613561456155616561756185619562056215622562356245625562656275628562956305631563256335634563556365637563856395640564156425643564456455646564756485649565056515652565356545655565656575658565956605661566256635664566556665667566856695670567156725673567456755676567756785679568056815682568356845685568656875688568956905691569256935694569556965697569856995700570157025703570457055706570757085709571057115712571357145715571657175718571957205721572257235724572557265727572857295730573157325733573457355736573757385739574057415742574357445745574657475748574957505751575257535754575557565757575857595760576157625763576457655766576757685769577057715772577357745775577657775778577957805781578257835784578557865787578857895790579157925793579457955796579757985799580058015802580358045805580658075808580958105811581258135814581558165817581858195820582158225823582458255826582758285829583058315832583358345835583658375838583958405841584258435844584558465847584858495850585158525853585458555856585758585859586058615862586358645865586658675868586958705871587258735874587558765877587858795880588158825883588458855886588758885889589058915892589358945895589658975898589959005901590259035904590559065907590859095910591159125913591459155916591759185919592059215922592359245925592659275928592959305931593259335934593559365937593859395940594159425943594459455946594759485949595059515952595359545955595659575958595959605961596259635964596559665967596859695970597159725973597459755976597759785979598059815982598359845985598659875988598959905991599259935994599559965997599859996000600160026003600460056006600760086009601060116012601360146015601660176018601960206021602260236024602560266027602860296030603160326033603460356036603760386039604060416042604360446045604660476048604960506051605260536054605560566057605860596060606160626063606460656066606760686069607060716072607360746075607660776078607960806081608260836084608560866087608860896090609160926093609460956096609760986099610061016102610361046105610661076108610961106111611261136114611561166117611861196120612161226123612461256126612761286129613061316132613361346135613661376138613961406141614261436144614561466147614861496150615161526153615461556156615761586159616061616162616361646165616661676168616961706171617261736174617561766177617861796180618161826183618461856186618761886189619061916192619361946195619661976198619962006201620262036204620562066207620862096210621162126213621462156216621762186219622062216222622362246225622662276228622962306231623262336234623562366237623862396240624162426243624462456246624762486249625062516252625362546255625662576258625962606261626262636264626562666267626862696270627162726273627462756276627762786279628062816282628362846285628662876288628962906291629262936294629562966297629862996300630163026303630463056306630763086309631063116312631363146315631663176318631963206321632263236324632563266327632863296330633163326333633463356336633763386339634063416342634363446345634663476348634963506351635263536354635563566357635863596360636163626363636463656366636763686369637063716372637363746375637663776378637963806381638263836384638563866387638863896390639163926393639463956396639763986399640064016402640364046405640664076408640964106411641264136414641564166417641864196420642164226423642464256426642764286429643064316432643364346435643664376438643964406441644264436444644564466447644864496450645164526453645464556456645764586459646064616462646364646465646664676468646964706471647264736474647564766477647864796480648164826483648464856486648764886489649064916492649364946495649664976498649965006501650265036504650565066507650865096510651165126513651465156516651765186519652065216522652365246525652665276528652965306531653265336534653565366537653865396540654165426543654465456546654765486549655065516552655365546555655665576558655965606561656265636564656565666567656865696570657165726573657465756576657765786579658065816582658365846585658665876588658965906591659265936594659565966597659865996600660166026603660466056606660766086609661066116612661366146615661666176618661966206621662266236624662566266627662866296630663166326633663466356636663766386639664066416642664366446645664666476648664966506651665266536654665566566657665866596660666166626663666466656666666766686669667066716672667366746675667666776678667966806681668266836684668566866687668866896690669166926693669466956696669766986699670067016702670367046705670667076708670967106711671267136714671567166717671867196720672167226723672467256726672767286729673067316732673367346735673667376738673967406741674267436744674567466747674867496750675167526753675467556756675767586759676067616762676367646765676667676768676967706771677267736774677567766777677867796780678167826783678467856786678767886789679067916792679367946795679667976798679968006801680268036804680568066807680868096810681168126813681468156816681768186819682068216822682368246825682668276828682968306831683268336834683568366837683868396840684168426843684468456846684768486849685068516852685368546855685668576858685968606861686268636864686568666867686868696870687168726873687468756876687768786879688068816882688368846885688668876888688968906891689268936894689568966897689868996900690169026903690469056906690769086909691069116912691369146915691669176918691969206921692269236924692569266927692869296930693169326933693469356936693769386939694069416942694369446945694669476948694969506951695269536954695569566957695869596960696169626963696469656966696769686969697069716972697369746975697669776978697969806981698269836984698569866987698869896990699169926993699469956996699769986999700070017002700370047005700670077008700970107011701270137014701570167017701870197020702170227023702470257026702770287029703070317032703370347035703670377038703970407041704270437044704570467047704870497050705170527053705470557056705770587059706070617062706370647065706670677068706970707071707270737074707570767077707870797080708170827083708470857086708770887089709070917092709370947095709670977098709971007101710271037104710571067107710871097110711171127113711471157116711771187119712071217122712371247125712671277128712971307131713271337134713571367137713871397140714171427143714471457146714771487149715071517152715371547155715671577158715971607161716271637164716571667167716871697170717171727173717471757176717771787179718071817182718371847185718671877188718971907191719271937194719571967197719871997200720172027203720472057206720772087209721072117212721372147215721672177218721972207221722272237224722572267227722872297230723172327233723472357236723772387239724072417242724372447245724672477248724972507251725272537254725572567257725872597260726172627263726472657266726772687269727072717272727372747275727672777278727972807281728272837284728572867287728872897290729172927293729472957296729772987299730073017302730373047305730673077308730973107311731273137314731573167317731873197320732173227323732473257326732773287329733073317332733373347335733673377338733973407341734273437344734573467347734873497350735173527353735473557356735773587359736073617362736373647365736673677368736973707371737273737374737573767377737873797380738173827383738473857386738773887389739073917392739373947395739673977398739974007401740274037404740574067407740874097410741174127413741474157416741774187419742074217422742374247425742674277428742974307431743274337434743574367437743874397440744174427443744474457446744774487449745074517452745374547455745674577458745974607461746274637464746574667467746874697470747174727473747474757476747774787479748074817482748374847485748674877488748974907491749274937494749574967497749874997500750175027503750475057506750775087509751075117512751375147515751675177518751975207521752275237524752575267527752875297530753175327533753475357536753775387539754075417542754375447545754675477548754975507551755275537554755575567557755875597560756175627563756475657566756775687569757075717572757375747575757675777578757975807581758275837584758575867587758875897590759175927593759475957596759775987599760076017602760376047605760676077608760976107611761276137614761576167617761876197620762176227623762476257626762776287629763076317632763376347635763676377638763976407641764276437644764576467647764876497650765176527653765476557656765776587659766076617662766376647665766676677668766976707671767276737674767576767677767876797680768176827683768476857686768776887689769076917692769376947695769676977698769977007701770277037704770577067707770877097710771177127713771477157716771777187719772077217722772377247725772677277728772977307731773277337734773577367737773877397740774177427743774477457746774777487749775077517752775377547755775677577758775977607761776277637764776577667767776877697770777177727773777477757776777777787779778077817782778377847785778677877788778977907791779277937794779577967797779877997800780178027803780478057806780778087809781078117812781378147815781678177818781978207821782278237824782578267827782878297830783178327833783478357836783778387839784078417842784378447845784678477848784978507851785278537854785578567857785878597860786178627863786478657866786778687869787078717872787378747875787678777878787978807881788278837884788578867887788878897890789178927893789478957896789778987899790079017902790379047905790679077908790979107911791279137914791579167917791879197920792179227923792479257926792779287929793079317932793379347935793679377938793979407941794279437944794579467947794879497950795179527953795479557956795779587959796079617962796379647965796679677968796979707971797279737974797579767977797879797980798179827983798479857986798779887989799079917992799379947995799679977998799980008001800280038004800580068007800880098010801180128013801480158016801780188019802080218022802380248025802680278028802980308031803280338034803580368037803880398040804180428043804480458046804780488049805080518052805380548055805680578058805980608061806280638064806580668067806880698070807180728073807480758076807780788079808080818082808380848085808680878088808980908091809280938094809580968097809880998100810181028103810481058106810781088109811081118112811381148115811681178118811981208121812281238124812581268127812881298130813181328133813481358136813781388139814081418142814381448145814681478148814981508151815281538154815581568157815881598160816181628163816481658166816781688169817081718172817381748175817681778178817981808181818281838184818581868187818881898190819181928193819481958196819781988199820082018202820382048205820682078208820982108211821282138214821582168217821882198220822182228223822482258226822782288229823082318232823382348235823682378238823982408241824282438244824582468247824882498250825182528253825482558256825782588259826082618262826382648265826682678268826982708271827282738274827582768277827882798280828182828283828482858286828782888289829082918292829382948295829682978298829983008301830283038304830583068307830883098310831183128313831483158316831783188319832083218322832383248325832683278328832983308331833283338334833583368337833883398340834183428343834483458346834783488349835083518352835383548355835683578358835983608361836283638364836583668367836883698370837183728373837483758376837783788379838083818382838383848385838683878388838983908391839283938394839583968397839883998400840184028403840484058406840784088409841084118412841384148415841684178418841984208421842284238424842584268427842884298430843184328433843484358436843784388439844084418442844384448445844684478448844984508451845284538454845584568457845884598460846184628463846484658466846784688469847084718472847384748475847684778478847984808481848284838484848584868487848884898490849184928493849484958496849784988499850085018502850385048505850685078508850985108511851285138514851585168517851885198520852185228523852485258526852785288529853085318532853385348535853685378538853985408541854285438544854585468547854885498550855185528553855485558556855785588559856085618562856385648565856685678568856985708571857285738574857585768577857885798580858185828583858485858586858785888589859085918592859385948595859685978598859986008601860286038604 | class Icube {  constructor(param) {    this.name = param.name || "货架" + parseInt(icubes.length + 1);    this.id = param.uid || BABYLON.Tools.RandomId();    this.rackingHighLevel = param.rackingHighLevel || g_rackingHighLevel;    this.rackingOrientation = param.hasOwnProperty("rackingOrientation")      ? param.rackingOrientation      : g_rackingOrientation;    this.palletType = param.palletType || g_palletInfo.value;    this.palletHeight = param.palletHeight || g_palletHeight;    this.palletWeight = param.palletWeight || g_palletWeight;    this.palletOverhang = param.hasOwnProperty("palletOverhang")      ? param.palletOverhang      : g_palletOverhang;    this.loadPalletOverhang = param.hasOwnProperty("loadPalletOverhang")      ? param.loadPalletOverhang      : g_loadPalletOverhang;    this.upRightDistance = param.upRightDistance || g_distUpRight;    this.drawMode = param.drawMode || g_drawMode;    this.spacingBetweenRows = param.spacingBetweenRows || g_spacingBetweenRows;    this.palletAtLevel = param.palletAtLevel || g_palletAtLevel;    this.rowData = [];    this.origPoints = [];    this.baseLines = param.baseLines;    for (let i = 0; i < this.baseLines.length; i++) {      this.baseLines[i].icube = this;    }    this.stores = []; // all available stores    this.infos = { uprights: [], capacity: [], cols: [], dimensions: [] };    this.isHorizontal =      this.rackingOrientation === OrientationRacking.horizontal ? true : false;    this.area = {      minX: 0,      minZ: 0,      maxX: 0,      maxZ: 0,      width: 0,      length: 0,      dimensions: [],    };    this.maxCol = 0;    this.maxRow = 0;    this.areaPoints = [];    // extra lifts & carriers & xtrack    this.extra = {      lift: 0,      carrier: 0,      xtrack: 0,    };    this.activedIOPorts = param.activedIOPorts || []; // info of actived IO ports    this.ports = []; // 3d objects of actived IO ports    this.activedXtrackIds = param.activedXtrackIds || []; // info of actived xtracks    this.activedXtrackIds = this.activedXtrackIds.sort((a, b) => {      return this.isHorizontal ? a - b : b - a;    });    this.activedChainConveyor = param.activedChainConveyor || []; // info of actived chain conveyors    this.chainConveyors = []; // 3d objects of actived chain conveyors    this.activedLiftInfos = param.activedLiftInfos || []; // info of actived lifts    this.lifts = []; // 3d objects of actived lifts    this.activedConnections = param.activedConnections || []; // info of actived connections    this.connections = []; // 3d objects of actived connections    this.activedChargers = param.activedChargers || []; // info of actived carrier charger    this.chargers = []; // 3d objects of actived carrier charger    this.activedSafetyFences = param.activedSafetyFences || []; // info of actived safety fence    this.safetyFences = []; // 3d objects of actived safety fence    this.activedTransferCarts = param.activedTransferCarts || []; // info of actived transfer carts    this.transferCarts = []; // 3d objects of actived transfer carts    this.activedPassthrough = param.activedPassthrough || []; // info of actived passthrough    this.activedSpacing = param.activedSpacing || []; // info of actived spacing    this.activedPillers = param.activedPillers || []; // info of actived pillers    this.pillers = []; // 3d objects of actived pillers    this.activedCarrierInfos = param.activedCarrierInfos || []; // info of actived carriers    this.carriers = []; // all carriers from scene - 3d objects    this.sku = param.sku || g_SKU; //    this.throughput = param.throughput || g_movesPerHour; //    this.pallets = []; // all pallets from scene - 3d objects    this.isSelect = false; //    this.SPSPalletLabels = null; //    this.SPSRowLabels = null; //    this.estimatedPrice = 0; //    this.calculatedLiftsNo = 0; // no of lifts from xcel    this.calculatedXtracksNo = 0; // no of xtracks from xcel    this.calculatedCarriersNo = 0; // no of carriers from xcel    this.calcAutoPrice = true; // calculate the price on update by default    this.measures = []; // 3d objects used to show measurement    this.transform = [];    this.software = new Software(this); // create json for it    this.firstSelector = null; // on click multiple selectors to draw between them    this.palletPositions = 0; // total pallets    this.activedProperty = null; // property of selected object    this.property = {      port: {        text: "开始设置输入/输出行",        selectors: [],      },      xtrack: {        text: "编辑X轨道放置",        selectors: [],      },      lift: {        text: "选择电梯位置",        selectors: [],      },      connection: {        text: "开始设置连接",        selectors: [],      },      charger: {        text: "选择充电器位置",        selectors: [],      },      safetyFence: {        text: "选择安全围栏位置",        selectors: [],      },      transferCart: {        text: "选择转运车位置",        selectors: [],      },      passthrough: {        text: "选择直通位置",        selectors: [],      },      spacing: {        text: "选择间距位置",        selectors: [],      },      chainconveyor: {        text: "选择链条输送机位置",        selectors: [],      },      liftpreloading: {        text: "放置电梯预加载输送机",        selectors: [],      },      pillers: {        text: "选择桩位置",        selectors: [],      },    };    this.floor = new BABYLON.PolygonMeshBuilder(      "icubeFloor",      [BABYLON.Vector3.Zero()],      scene    ).build(true);    g_loadPalletOverhang = this.loadPalletOverhang;    g_palletInfo.type = this.palletType;    addLevelVisibility(this.rackingHighLevel);    this.getOriginPoints();    this.drawHTMLTab();    this.init();  }  drawHTMLTab() {    //Add icube tab item    this.dom_item = document.createElement("div");    this.dom_item.classList.add("tab-item", "context-menu-one");    $(this.dom_item).attr("uuid", this.id);    this.dom_item.addEventListener(      "click",      (ev) => {        selectIcubeWithId(this.id, ev);      },      true    );    const edit_name = document.createElement("span");    $(edit_name).attr("title", "Rename");    this.settingIcubeName(edit_name, "glyphicon-edit");    this.dom_item.appendChild(edit_name);    edit_name.addEventListener(      "click",      () => {        $(this.dom_item).find("input").prop("disabled", false);        $(this.dom_item).find("input").select();      },      false    );    const dom_name = document.createElement("input");    dom_name.classList.add("icube-name");    this.dom_item.appendChild(dom_name);    $(dom_name).val(this.name);    $(dom_name).prop("disabled", true);    dom_name.addEventListener(      "change",      (ev) => {        renameIcubeWithId(this.id, ev);      },      false    );    $(dom_name).focusout(function () {      //disable edit      $(this).prop("disabled", true);    });    if (this.drawMode === 0) {      const dom_multiply = document.createElement("span");      $(dom_multiply).attr("title", "Multiply");      this.settingIcubeName(dom_multiply, "glyphicon-duplicate");      this.dom_item.appendChild(dom_multiply);      dom_multiply.addEventListener(        "click",        () => {          multiplyIcubeWithId(this.id);        },        false      );    }    const dom_remove = document.createElement("span");    $(dom_remove).attr("title", "Delete");    this.settingIcubeName(dom_remove, "glyphicon-trash");    this.dom_item.appendChild(dom_remove);    dom_remove.addEventListener(      "click",      () => {        removeIcubeWithId(this.id);      },      false    );    $("#icube-tab").append(this.dom_item);  }  getOriginPoints() {    this.calcArea();    const minVal = this.isHorizontal ? this.area.minX : this.area.minZ;    let spacing = [...this.activedSpacing].map((e, i) =>      parseFloat(        (          minVal +          (e + 1) *            (2 * g_palletOverhang +              2 * g_loadPalletOverhang +              g_palletInfo.length) +          i * this.spacingBetweenRows        ).toFixed(2)      )    );    let points = [];    for (let i = 0; i < this.baseLines.length; i++) {      for (let j = 0; j < this.baseLines[i].points.length; j++) {        points.push([          this.baseLines[i].points[j].x,          this.baseLines[i].points[j].z,        ]);      }    }    points.forEach((arr) => {      this.origPoints.push(arr.map((x) => x));    });    this.origPoints.forEach((arr) => {      for (let j = spacing.length - 1; j >= 0; j--) {        if (arr[this.isHorizontal ? 0 : 1] > spacing[j]) {          arr[this.isHorizontal ? 0 : 1] -= this.spacingBetweenRows;          arr[this.isHorizontal ? 0 : 1] = parseFloat(            arr[this.isHorizontal ? 0 : 1].toFixed(2)          );        }      }    });  }  // Html buton properties  settingIcubeName(elem, cssclass) {    elem.style.padding = "6px 1px";    elem.style.cursor = "pointer";    elem.classList.add("glyphicon", cssclass);    $(elem).mouseenter(function () {      elem.style.color = "#adadad";    });    $(elem).mouseleave(function () {      elem.style.color = "#ffffff";    });  }  // select this Icube  selectIcube() {    this.isSelect = true;    selectedIcube = this;    createSimulationList(this.id);    $(this.dom_item).addClass("select");    if (this.floor) this.floor.material = matManager.matIcubeFloorSelect;    this.addRowLabels();    this.showMeasurement();    //Set Left setting UI from icube    initToolBarForICube(      this.rackingHighLevel,      this.rackingOrientation,      this.palletHeight,      this.palletWeight,      this.palletOverhang,      this.loadPalletOverhang,      this.sku,      this.throughput,      this.calculatedCarriersNo,      this.calculatedLiftsNo,      this.extra,      this.upRightDistance,      this.calculatedXtracksNo,      this.palletAtLevel,      this.spacingBetweenRows    );    if (icubes.length > 1) {      $(".xtrack_connect").show();    }    renderScene();  }  // unselect this Icube  unSelectIcube() {    htmlElemAttr.forEach((prop) => {      finishToSet(prop);    });    this.isSelect = false;    $(this.dom_item).removeClass("select");    if (this.floor) this.floor.material = matManager.matIcubeFloor;    this.removeRowLabels();    this.showMeasurement();  }  init() {    this.updateIcube(      this.rackingHighLevel,      this.rackingOrientation,      this.palletType,      this.palletHeight,      this.palletWeight,      this.palletOverhang,      this.loadPalletOverhang,      this.sku,      this.throughput,      this.upRightDistance,      this.palletAtLevel,      this.spacingBetweenRows    );  }  // update this Icube properties  updateIcube(    rackingHighLevel,    rackingOrientation,    palletType,    palletHeight,    palletWeight,    palletOverhang,    loadPalletOverhang,    sku,    throughput,    upRightDistance,    palletAtLevel,    spacingBetweenRows,    callback = null  ) {    showLoadingPopUp(async () => {      menuEnabled = false;      if (palletOverhang !== this.palletOverhang) this.activedConnections = [];      this.rackingHighLevel = rackingHighLevel;      this.rackingOrientation = rackingOrientation;      this.isHorizontal =        this.rackingOrientation === OrientationRacking.horizontal          ? true          : false;      this.palletType = palletType;      this.palletHeight = palletHeight;      this.palletWeight = palletWeight;      this.palletOverhang = palletOverhang;      this.loadPalletOverhang = loadPalletOverhang;      this.sku = sku;      this.throughput = throughput;      this.upRightDistance = upRightDistance;      this.palletAtLevel = palletAtLevel;      this.spacingBetweenRows = spacingBetweenRows;      g_RenderEvent = false;      this.clearStructure();      this.removeAllProps();      htmlElemAttr.forEach((prop) => {        finishToSet(prop);      });      this.calcArea();      if (this.activedXtrackIds.length === 0) {        this.activedXtrackIds = this.calcIdealPosForXtrack(          g_recomandedXtrackAmount || 1        );        this.activedXtrackIds = this.activedXtrackIds.sort((a, b) => {          return this.isHorizontal ? a - b : b - a;        });      }      this.updateInfos();      this.updateStructure();      this.updateFloor();      if (this.isSelect) {        this.addRowLabels();      }      for (let i = 0; i < this.transform.length; i++) {        await Utils.solvePromise(          Utils.createThinInstance(this.transform[i].mesh, this.transform[i]),          (this.area.cols * this.area.rows) / 75        );      }      this.generateStores();      this.updateXtrackPlacement();      this.updateLiftPlacement();      this.updatePortPlacement();      this.updatePillersPlacement();      this.updateStores();      this.updatePallet();      this.updateChargerPlacement();      this.updateSafetyFencePlacement();      this.updateChainConveyorPlacement();      this.updateTransferCartPlacement();      if (this.calcAutoPrice) {        this.getEstimationPrice();      }      if (callback) {        callback();      } else {        if (this.activedProperty) {          this.previewProperty(this.activedProperty, false);        }      }      if (currentView == ViewType.top) {        this.set2D();      } else if (currentView == ViewType.free) {        this.set3D();      }      renderScene();      hideLoadingPopUp();      setTimeout(() => {        menuEnabled = true;      }, 100);    });  }  resetIcubeData() {    this.activedXtrackIds = [];    this.activedLiftInfos = [];    this.activedIOPorts = [];    this.activedConnections = [];    this.activedChargers = [];    this.activedSafetyFences = [];    this.activedTransferCarts = [];    this.activedPassthrough = [];    this.activedChainConveyor = [];    this.activedPillers = [];  }  clearStructure() {    for (let i = 0; i < this.transform.length; i++) {      if (this.transform[i].mesh) {        this.transform[i].mesh.thinInstanceCount = 0;        this.transform[i].mesh.dispose();      }    }    this.transform = [];    this.rowData = [];  }  // completly remove this icube  removeIcube() {    endSimulation();    this.clearStructure();    this.removeAllProps();    htmlElemAttr.forEach((prop) => {      finishToSet(prop);    });    this.removeAllBaseLines();    this.removeFloor();    this.removeRowLabels();    this.hideMeasurement();    // remove top tab    $(this.dom_item).remove();    g_totalPrice -= this.estimatedPrice;    $("#totalPrice").text("€" + formatIntNumber(g_totalPrice));    renderScene(4000);    this.removeAllCarriers();    this.removeAllPallets();    this.updateConnectionPlacement();    this.software.remove();    updateConnectorsPrice();    palletsNoJS();  }  getData() {    const points = [];    const clonedP = JSON.parse(JSON.stringify(this.areaPoints));    for (let j = 0; j < clonedP.length; j++) {      points.push({        x: this.areaPoints[j].x,        y: this.areaPoints[j].y,      });    }    return {      activedXtrackIds: JSON.parse(JSON.stringify(this.activedXtrackIds)),      activedLiftInfos: JSON.parse(JSON.stringify(this.activedLiftInfos)),      activedIOPorts: JSON.parse(JSON.stringify(this.activedIOPorts)),      activedChargers: JSON.parse(JSON.stringify(this.activedChargers)),      activedSafetyFences: JSON.parse(JSON.stringify(this.activedSafetyFences)),      activedTransferCarts: JSON.parse(        JSON.stringify(this.activedTransferCarts)      ),      activedConnections: JSON.parse(JSON.stringify(this.activedConnections)),      activedPassthrough: JSON.parse(JSON.stringify(this.activedPassthrough)),      activedChainConveyor: JSON.parse(        JSON.stringify(this.activedChainConveyor)      ),      activedSpacing: JSON.parse(JSON.stringify(this.activedSpacing)),      activedPillers: JSON.parse(JSON.stringify(this.activedPillers)),      palletAtLevel: JSON.parse(JSON.stringify(this.palletAtLevel)),      palletType: JSON.parse(JSON.stringify(this.palletType)),      dimensions: JSON.parse(JSON.stringify(this.area.dimensions)),      rackingHighLevel: this.rackingHighLevel,      rackingOrientation: this.rackingOrientation,      palletHeight: this.palletHeight,      palletWeight: this.palletWeight,      palletOverhang: this.palletOverhang,      loadPalletOverhang: this.loadPalletOverhang,      activedCarrierInfos: this.activedCarrierInfos,      throughput: this.throughput,      sku: this.sku,      upRightDistance: this.upRightDistance,      spacingBetweenRows: this.spacingBetweenRows,      drawMode: this.drawMode,      points: points,    };  }  /**   *   * @param { PropertyKey } prop - Icube property   * @param { String } func - function to call | remove, delete, dispose | dispose by default   */  emptyProperty(prop, func = "dispose") {    if (this.hasOwnProperty(prop)) {      this[prop].forEach((item) => {        if (Array.isArray(item)) {          item.forEach((child) => {            if (child[func] && typeof child[func] == "function") child[func]();          });        } else {          if (item[func] && typeof item[func] == "function") item[func]();        }      });      this[prop] = [];    }  }  /**   *   *  @param { PropertyKey } prop - Icube property   *  @param { Boolean } isPreview - false by default   */  finishToSetProperty(prop, isPreview = false) {    this.activedProperty = isPreview ? prop : null;    if (isPreview) {      $("#set-icube-" + prop)        .addClass("active-icube-setting")        .text("确认放置");    } else {      $("#set-icube-" + prop)        .removeClass("active-icube-setting")        .text(this.property[prop].text);      if (this.calcAutoPrice) this.getEstimationPrice();      if (prop === "passthrough") {        for (let i = this.activedPassthrough.length - 1; i >= 0; i--) {          if (            this.activedPassthrough[i][0].length === 0 ||            this.activedPassthrough[i][1].length === 0 ||            this.activedPassthrough[i][2].length === 0          )            this.activedPassthrough.splice(i, 1);        }        createPassThList();      }      if (prop === "xtrack") {        this.updateLastAddedXtrack(true);        for (let i = this.activedPillers.length - 1; i >= 0; i--) {          if (this.pillers[i]) {            this.pillers[i].dispose();            this.pillers.splice(i, 1);          }          this.activedPillers.splice(i, 1);        }        this.activedPillers = [];        this.pillers = [];      }      if (        ["lift", "chainconveyor", "liftpreloading", "pillers"].includes(prop)      ) {        this.updateRacking();      }    }    this.property[prop].selectors.forEach((item) => {      item.dispose();    });    this.property[prop].selectors = [];  }  /**   *   *  @param { PropertyKey } prop - Icube property   */  previewProperty(prop, message = true) {    switch (prop) {      case "port":        this.previewPortSite(prop);        break;      case "xtrack":        this.previewXtrackSite(prop, message);        break;      case "lift":        this.previewLiftSite(prop);        break;      case "connection":        this.previewConnectionSite(prop);        break;      case "charger":        this.previewChargerSite(prop);        break;      case "safetyFence":        this.previewSafetyFenceSite(prop);        break;      case "transferCart":        this.previewTransferCartSite(prop);        break;      case "passthrough":        this.previewPassthroughSite(prop, message);        break;      case "spacing":        this.previewSpacingSite(prop);        break;      case "chainconveyor":        this.previewChainConveyorSite(prop);        break;      case "liftpreloading":        this.previewLiftPreloadingSite(prop);        break;      case "pillers":        this.previewPillersSite(prop);        break;      default:        break;    }  }  /**   * Remove all iCube properties   */  removeAllProps() {    this.emptyProperty("xtracks");    this.emptyProperty("lifts", "remove");    this.emptyProperty("ports");    this.emptyProperty("connections");    this.emptyProperty("chargers");    this.emptyProperty("safetyFences");    this.emptyProperty("transferCarts");    this.emptyProperty("passthrough");    this.emptyProperty("spacing");    this.emptyProperty("chainConveyors");    this.emptyProperty("liftpreloading");    this.emptyProperty("pillers");  }  /**   * Add a selector for this property   * @param { PropertyKey } prop - Icube property   * @param { Vector3 } scaling - Selector's scaling   * @param { Function } callback - OnClick function   * @return { Mesh }   */  addSelector(prop) {    const selector = meshSelector.clone(prop + "SelectorClone");    selector.rotation.y = this.isHorizontal ? 0 : Math.PI / 2;    selector.isPickable = true;    selector.setEnabled(true);    selector.actionManager = new BABYLON.ActionManager(scene);    selector.actionManager.hoverCursor = "pointer";    selector.actionManager.registerAction(      new BABYLON.ExecuteCodeAction(        BABYLON.ActionManager.OnPointerOverTrigger,        () => {}      )    );    selector.actionManager.registerAction(      new BABYLON.ExecuteCodeAction(        BABYLON.ActionManager.OnLeftPickTrigger,        (evt) => {          this.onClickSelector(prop, evt.meshUnderPointer);          const behaviourName =            "add" +            prop.substr(0, 1).toUpperCase() +            prop.substr(1).toLowerCase();          Behavior.add(Behavior.type[behaviourName]);        }      )    );    return selector;  }  /**   * On click a specific selector   * @param { PropertyKey } prop - Icube property   * @param { Mesh } selector - 3d object clicked   */  onClickSelector(prop, selector) {    switch (prop) {      case "port":        this.updatePortPlacementBySelector(selector);        break;      case "lift":        this.updateLiftPlacementBySelector(selector);        break;      case "connection":        this.updateConnectionPlacementBySelector(selector);        break;      case "charger":        this.updateChargerPlacementBySelector(selector);        break;      case "safetyFence":        this.updateSafetyFencePlacementBySelector(selector);        break;      case "transferCart":        this.updateTransferCartPlacementBySelector(selector);        break;      case "spacing":        this.updateSpacingPlacementBySelector(selector);        break;      case "chainconveyor":        this.updateChainConveyorPlacementBySelector(selector);        break;      case "liftpreloading":        this.updateLiftPreloadingPlacementBySelector(selector);        break;      case "pillers":        this.updatePillersPlacementBySelector(selector);        break;      default:        break;    }  }  calcArea() {    this.area = {      minX: 1000,      minZ: 1000,      maxX: -1000,      maxZ: -1000,      width: 0,      length: 0,    };    this.areaPoints = [];    this.floorPoints = [];    //Find minX, minZ of icube area    for (let i = 0; i < this.baseLines.length; i++) {      const baseline = this.baseLines[i];      const sPoint = new BABYLON.Vector2(baseline.sPoint.x, baseline.sPoint.z);      const ePoint = new BABYLON.Vector2(baseline.ePoint.x, baseline.ePoint.z);      this.areaPoints.push(sPoint);      this.areaPoints.push(ePoint);      this.floorPoints.push(sPoint);      for (let j = 0; j < baseline.points.length; j++) {        const point = baseline.points[j];        const z = point.z;        const x = point.x;        if (this.area.minZ > z)          this.area.minZ = parseFloat(_round(z, 2).toFixed(2));        if (this.area.minX > x)          this.area.minX = parseFloat(_round(x, 2).toFixed(2));        if (this.area.maxZ < z)          this.area.maxZ = parseFloat(_round(z, 2).toFixed(2));        if (this.area.maxX < x)          this.area.maxX = parseFloat(_round(x, 2).toFixed(2));      }    }    this.area.width = this.area.maxX - this.area.minX;    this.area.length = this.area.maxZ - this.area.minZ;    const sizex = this.area.width;    const sizez = this.area.length;    const sizey =      g_bottomLength +      this.getHeightAtLevel(this.rackingHighLevel) +      g_StoreTopGap * (this.rackingHighLevel - 1);    this.area.dimensions = [      parseFloat(sizex.toFixed(5)),      parseFloat(sizey.toFixed(5)),      parseFloat(sizez.toFixed(5)),    ];  }  updateRacking(callback) {    this.updateIcube(      this.rackingHighLevel,      this.rackingOrientation,      this.palletType,      this.palletHeight,      this.palletWeight,      this.palletOverhang,      this.loadPalletOverhang,      this.sku,      this.throughput,      this.upRightDistance,      this.palletAtLevel,      this.spacingBetweenRows,      callback    );  }  insidePointInPolygon(point, vs) {    const x = point.x,      y = point.y;    let inside = false;    for (let i = 0, j = vs.length - 1; i < vs.length; j = i++) {      const xi = vs[i].x,        yi = vs[i].y;      const xj = vs[j].x,        yj = vs[j].y;      const intersect =        yi > y != yj > y && x < ((xj - xi) * (y - yi)) / (yj - yi) + xi;      if (intersect) inside = !inside;    }    return inside;  }  // add row labels  addRowLabels() {    this.removeRowLabels();    let objectTransform = [];    for (      let i = 0;      i < (this.isHorizontal ? this.maxCol + 1 : this.maxRow + 1);      i++    ) {      if (this.transform[3]) {        for (let j = 0; j < this.transform[3].data.length; j++) {          if (            this.isHorizontal &&            this.transform[3].data[j][1] === i &&            this.transform[3].data[j][2] === 0          ) {            objectTransform.push([              this.transform[3].position[j][0],              0.01,              (WHDimensions[1] + 2) / 2,            ]);            break;          }          if (            !this.isHorizontal &&            this.transform[3].data[j][0] === i &&            this.transform[3].data[j][2] === 0          ) {            objectTransform.push([              -(WHDimensions[0] + 2) / 2,              0.01,              this.transform[3].position[j][2],            ]);            break;          }        }      }    }    if (objectTransform.length > 0)      this.SPSRowLabels = _generateLabels(objectTransform);  }  // remove row labels  removeRowLabels() {    if (this.SPSRowLabels) {      this.SPSRowLabels.mesh.dispose(true, true);      this.SPSRowLabels.dispose();      this.SPSRowLabels = null;    }  }  calcPosAndUprightForRow(row) {    if (this.rowData[row]) return this.rowData[row];    let idx = 0;    this.infos.cols.forEach((val, key) => {      if (val.includes(row)) idx = key;    });    let upright = this.infos.uprights[idx] ? this.infos.uprights[idx] : 0;    const itemLength = useP(useP(g_palletInfo.racking) + useP(upright), false);    let posz = useP(itemLength) / 2;    let halfRacking = 0;    if (upright < 0) {      const halfRack = useP(useP(g_palletInfo.racking) / 2, false);      halfRacking = halfRack;      upright += halfRack;    }    this.infos.cols.forEach((val, key) => {      if (key < idx) {        posz +=          (val.length - 1) *            (useP(g_palletInfo.racking) + useP(this.infos.uprights[key])) +          (useP(g_palletInfo.racking) +            useP(g_xtrackFixedDim) +            useP(g_rackingPole));      } else {        if (key === idx) {          posz +=            val.indexOf(row) * (useP(g_palletInfo.racking) + useP(upright));        }      }    });    let hasAtrack = false;    if (      this.infos.cols[idx][this.infos.cols[idx].length - 1] === row &&      row !== (this.isHorizontal ? this.maxRow : this.maxCol) - 1    ) {      hasAtrack = this.activedXtrackIds[this.activedXtrackIds.length - idx - 1];    }    posz = useP(posz, false);    this.rowData[row] = [posz, itemLength, upright, hasAtrack, halfRacking];    return this.rowData[row];  }  isInsideLift(pos, liftBBox) {    if (!liftBBox || liftBBox.length === 0) return false;    let isInside = false;    for (let i = 0; i < liftBBox.length; i++) {      if (liftBBox[i][0] <= pos && liftBBox[i][1] >= pos) {        isInside = true;        break;      }    }    return isInside;  }  checkLiftBooundaries(col) {    let bbox = [];    const lifts = this.activedLiftInfos.filter(      (e) => e.row === col && e.index === -1    );    for (let l = 0; l < lifts.length; l++) {      const pos =        useP(this.isHorizontal ? this.area.maxZ : this.area.minX) +        (this.isHorizontal ? -1 : 1) * useP(lifts[l].length) +        lifts[l].bottomOrTop * (useP(g_xtrackFixedDim) / 2);      const liftLength =        g_liftFixedDim + (lifts[l].preloading === true ? 1.25 : 0);      bbox.push([        Math.min(          useP(pos, false),          useP(pos + lifts[l].bottomOrTop * useP(liftLength), false)        ),        Math.max(          useP(pos, false),          useP(pos + lifts[l].bottomOrTop * useP(liftLength), false)        ),      ]);    }    return bbox;  }  checkpPassth(r, c, h) {    let nextpassthR = false;    let prevpassthR = false;    let nextpassthC = false;    let prevpassthC = false;    let nextpassthH = false;    let prevpassthH = false;    let passth = false;    for (let i = 0; i < this.activedPassthrough.length; i++) {      if (        this.activedPassthrough[i][0].includes(r) &&        this.activedPassthrough[i][1].includes(c) &&        this.activedPassthrough[i][2].includes(h)      ) {        passth = true;      }      if (        this.activedPassthrough[i][0].includes(r + 1) &&        this.activedPassthrough[i][1].includes(c) &&        this.activedPassthrough[i][2].includes(h)      ) {        nextpassthR = true;      }      if (        this.activedPassthrough[i][0].includes(r - 1) &&        this.activedPassthrough[i][1].includes(c) &&        this.activedPassthrough[i][2].includes(h)      ) {        prevpassthR = true;      }      if (        this.activedPassthrough[i][0].includes(r) &&        this.activedPassthrough[i][1].includes(c + 1) &&        this.activedPassthrough[i][2].includes(h)      ) {        nextpassthC = true;      }      if (        this.activedPassthrough[i][0].includes(r) &&        this.activedPassthrough[i][1].includes(c - 1) &&        this.activedPassthrough[i][2].includes(h)      ) {        prevpassthC = true;      }      if (        this.activedPassthrough[i][0].includes(r) &&        this.activedPassthrough[i][1].includes(c) &&        this.activedPassthrough[i][2].includes(h + 1)      ) {        nextpassthH = true;      }      if (        this.activedPassthrough[i][0].includes(r) &&        this.activedPassthrough[i][1].includes(c) &&        this.activedPassthrough[i][2].includes(h - 1)      ) {        prevpassthH = true;      }    }    if (passth && c === 0) prevpassthC = true;    return [      passth,      prevpassthR,      prevpassthC,      prevpassthH,      nextpassthR,      nextpassthC,      nextpassthH,    ];  }  checkIfneedPillars(row, height) {    let supportPillar = [],      prevPillar = [],      nextPillar = [];    for (let i = 0; i < this.activedPassthrough.length; i++) {      const maxH = Math.max(...this.activedPassthrough[i][2]);      if (        this.activedPassthrough[i][0].includes(row) &&        this.activedPassthrough[i][2].includes(height)      ) {        supportPillar.push(maxH < this.rackingHighLevel - 1 ? true : false);      }      if (        this.activedPassthrough[i][0].includes(row - 1) &&        this.activedPassthrough[i][2].includes(height)      ) {        prevPillar.push(maxH < this.rackingHighLevel - 1 ? true : false);      }      if (        this.activedPassthrough[i][0].includes(row + 1) &&        this.activedPassthrough[i][2].includes(height)      ) {        nextPillar.push(maxH < this.rackingHighLevel - 1 ? true : false);      }    }    const needPillar =      supportPillar.length > 0 &&      supportPillar.filter((e) => e === false).length === 0;    const needPPillar =      prevPillar.length === 0 ||      prevPillar.filter((e) => e === false).length > 0;    const needNPillar =      nextPillar.length === 0 ||      nextPillar.filter((e) => e === false).length > 0;    if (needPillar && (needPPillar || needNPillar)) {      return [true, needPPillar];    }    return [false, false];  }  // create the structure  updateStructure() {    const itemInfoD = {      width: useP(        useP(2 * this.palletOverhang) +          useP(2 * this.loadPalletOverhang) +          useP(g_palletInfo.length) +          useP(g_rackingPole),        false      ),      length: useP(        useP(this.upRightDistance) + useP(g_palletInfo.racking),        false      ),      height: useP(useP(g_railHeight) + useP(this.palletHeight), false),    };    let itemHeight = itemInfoD.height;    let itemWidth = this.isHorizontal ? itemInfoD.width : itemInfoD.length;    let itemLength = this.isHorizontal ? itemInfoD.length : itemInfoD.width;    if (this.isHorizontal) {      this.maxCol = parseInt(        _round(          (this.area.dimensions[0] -            this.activedSpacing.length * this.spacingBetweenRows) /            itemWidth,          4        ).toFixed()      );      this.maxRow =        this.infos.cols[this.infos.cols.length - 1][          this.infos.cols[this.infos.cols.length - 1].length - 1        ] + 1;    } else {      this.maxCol =        this.infos.cols[this.infos.cols.length - 1][          this.infos.cols[this.infos.cols.length - 1].length - 1        ] + 1;      this.maxRow = parseInt(        _round(          (this.area.dimensions[2] -            this.activedSpacing.length * this.spacingBetweenRows) /            itemLength,          4        ).toFixed()      );    }    this.updateAmounts();    this.transform.push({      /* 0 */ mesh: itemInfo[ITEMTYPE.Auto.Racking].originMesh.clone(),      data: [],      position: [],      rotation: [],      scaling: [],      material: matManager.matAlu_blue,      visibility: true,    });    this.transform.push({      /* 1 */ mesh: itemInfo[ITEMTYPE.Auto.RackingBare].originMesh.clone(),      data: [],      position: [],      rotation: [],      scaling: [],      material: matManager.matAlu_gray,      visibility: true,    });    this.transform.push({      /* 2 */ mesh: itemInfo[ITEMTYPE.Auto.RackingBeam].originMesh.clone(),      data: [],      position: [],      rotation: [],      scaling: [],      material: matManager.matAlu_blue,      visibility: true,    });    this.transform.push({      /* 3 */ mesh: itemInfo[ITEMTYPE.Auto.Rail].originMesh.clone(),      data: [],      position: [],      rotation: [],      scaling: [],      material: matManager.matAlu_rail,      visibility: true,    });    this.transform.push({      /* 4 */ mesh: itemInfo[ITEMTYPE.Auto.Rail].originMesh.clone(),      data: [],      position: [],      rotation: [],      scaling: [],      material: matManager.matAlu_rail,      visibility: true,    });    this.transform.push({      /* 5 */ mesh: itemInfo[ITEMTYPE.Auto.RailLimit].originMesh.clone(),      data: [],      position: [],      rotation: [],      scaling: [],      material: matManager.matAlu_blue,      visibility: true,    });    this.transform.push({      /* 6 */ mesh: itemInfo[ITEMTYPE.Auto.Xtrack].originMesh.clone(),      data: [],      position: [],      rotation: [],      scaling: [],      material: matManager.matAlu_rail,      visibility: true,    });    this.transform.push({      /* 7 */ mesh: itemInfo[ITEMTYPE.Auto.Xtrack2].originMesh.clone(),      data: [],      position: [],      rotation: [],      scaling: [],      material: matManager.matAlu_xtrack_mesh,      visibility: true,    });    this.transform.push({      /* 8 */ mesh: itemInfo[ITEMTYPE.Auto.XtrackInter].originMesh.clone(),      data: [],      position: [],      rotation: [],      scaling: [],      material: matManager.matAlu_rail,      visibility: true,    });    this.transform.push({      /* 9 */ mesh: itemInfo[ITEMTYPE.Auto.XtrackInter2].originMesh.clone(),      data: [],      position: [],      rotation: [],      scaling: [],      material: matManager.matAlu_xtrack_mesh,      visibility: true,    });    this.rowData = [];    for (let h = 0; h < this.rackingHighLevel; h++) {      const palletInfo = this.palletAtLevel.filter((e) => e.idx === h + 1);      if (palletInfo.length > 0) {        itemHeight = g_railHeight + parseFloat(palletInfo[0].height);      } else {        itemHeight = itemInfoD.height;      }      const nrOfBares = _round((0.5 + itemHeight) / 0.4);      if (this.isHorizontal) {        let liftBBox = [];        for (let c = 0; c < this.maxCol; c++) {          liftBBox.push(this.checkLiftBooundaries(c));        }        for (let r = 0; r < this.maxRow; r++) {          const rowData = this.calcPosAndUprightForRow(r);          const posz = rowData[0];          itemLength = rowData[1];          const uprightDist = rowData[2];          const hasAtrack = rowData[3];          const halfRacking = rowData[4];          const rackingDim =            rowData[4] !== 0              ? parseFloat((g_palletInfo.racking / 2).toFixed(3))              : g_palletInfo.racking;          let spacingOffset = 0;          let endPos = BABYLON.Vector3.Zero();          for (let c = 0; c < this.maxCol; c++) {            const spacingRow = this.activedSpacing.indexOf(c - 1);            if (spacingRow > -1)              spacingOffset = (spacingRow + 1) * this.spacingBetweenRows;            const passthData = this.checkpPassth(r, c, h);            const pos = new BABYLON.Vector3(              useP(                useP(this.area.minX) +                  c * useP(itemWidth) +                  useP(itemWidth) / 2 +                  useP(spacingOffset),                false              ),              this.getHeightAtLevel(h),              useP(                useP(this.area.minZ) +                  useP(posz) +                  useP(g_railOutside) +                  useP(g_rackingPole) / 2,                false              )            );            if (              this.insidePointInPolygon(                new BABYLON.Vector2(                  pos.x,                  useP(                    useP(pos.z) + useP(rackingDim) - useP(itemLength) / 2,                    false                  )                ),                this.areaPoints              ) &&              this.insidePointInPolygon(                new BABYLON.Vector2(                  pos.x,                  useP(useP(pos.z) - useP(itemLength) / 2, false)                ),                this.areaPoints              )            ) {              if (!passthData[0]) {                if (                  !levelVisibility[h] &&                  ((h !== 0 && !levelVisibility[h - 1]) ||                    [0].includes(h) ||                    (!passthData[0] && passthData[3]))                )                  continue;                // Add racking-beam                for (let j = 0; j < 2; j++) {                  if (                    this.isInsideLift(                      pos.z + (j === 0 ? 0 : rackingDim) - itemLength / 2,                      liftBBox[c]                    )                  )                    break;                  this.transform[2].position.push([                    pos.x,                    pos.y,                    pos.z + (j === 0 ? 0 : rackingDim) - itemLength / 2,                  ]);                  this.transform[2].rotation.push([                    0,                    j === 0 ? 0 : Math.PI,                    0,                  ]);                  this.transform[2].scaling.push([                    itemWidth - g_rackingPole,                    1,                    1,                  ]);                  this.transform[2].data.push([r, c, h]);                }              }              if (!levelVisibility[h]) continue;              endPos = pos;              if (                (!passthData[0] && !passthData[6]) ||                (passthData[0] && !passthData[2]) ||                (!passthData[0] && !passthData[2] && !passthData[6])              ) {                // Add racking-bare                if (h !== this.rackingHighLevel - 1) {                  if (                    !this.isInsideLift(pos.z - uprightDist / 2, liftBBox[c]) &&                    !this.isInsideLift(pos.z - uprightDist / 2, liftBBox[c - 1])                  ) {                    for (let j = 0; j < nrOfBares; j++) {                      this.transform[1].position.push([                        pos.x - itemWidth / 2,                        pos.y + (0.4 * j + 0.1),                        pos.z - uprightDist / 2,                      ]);                      this.transform[1].rotation.push([                        [0, nrOfBares - 1].includes(j)                          ? 0                          : j % 2 !== 0                          ? -Math.PI / 10                          : Math.PI / 10,                        0,                        0,                      ]);                      this.transform[1].scaling.push([1, 1, rackingDim]);                      this.transform[1].data.push([r, c, h]);                    }                    if (                      this.activedSpacing.includes(c) ||                      !this.insidePointInPolygon(                        new BABYLON.Vector2(                          useP(                            useP(pos.x) + useP(itemWidth) + useP(itemWidth) / 2,                            false                          ),                          useP(useP(pos.z) - useP(rackingDim), false)                        ),                        this.areaPoints                      ) ||                      !this.insidePointInPolygon(                        new BABYLON.Vector2(                          useP(                            useP(pos.x) + useP(itemWidth) + useP(itemWidth) / 2,                            false                          ),                          useP(useP(pos.z), false)                        ),                        this.areaPoints                      )                    ) {                      if (endPos.x === 0 && endPos.z === 0) continue;                      if (!passthData[0]) {                        for (let j = 0; j < nrOfBares; j++) {                          this.transform[1].position.push([                            endPos.x + itemWidth / 2,                            pos.y + (0.4 * j + 0.1),                            endPos.z - uprightDist / 2,                          ]);                          this.transform[1].rotation.push([                            [0, nrOfBares - 1].includes(j)                              ? 0                              : j % 2 !== 0                              ? Math.PI / 10                              : -Math.PI / 10,                            Math.PI,                            0,                          ]);                          this.transform[1].scaling.push([1, 1, rackingDim]);                          this.transform[1].data.push([r, c, h]);                        }                      }                    }                  }                }                // add racking                for (let j = 0; j < 2; j++) {                  this.transform[0].position.push([                    pos.x - itemWidth / 2,                    pos.y + (h !== 0 ? 0.12 : 0),                    pos.z + (j === 0 ? 0 : rackingDim) - itemLength / 2,                  ]);                  this.transform[0].rotation.push([                    0,                    j === 0 ? Math.PI : 0,                    0,                  ]);                  this.transform[0].scaling.push([                    1,                    this.rackingHighLevel === 1                      ? 0.5                      : itemHeight +                        (h === 0                          ? 0.12                          : h === this.rackingHighLevel - 1                          ? -itemHeight / 1.25                          : 0),                    1,                  ]);                  this.transform[0].data.push([r, c, h]);                }                if (                  this.activedSpacing.includes(c) ||                  !this.insidePointInPolygon(                    new BABYLON.Vector2(                      useP(                        useP(pos.x) + useP(itemWidth) + useP(itemWidth) / 2,                        false                      ),                      useP(useP(pos.z) - useP(rackingDim), false)                    ),                    this.areaPoints                  ) ||                  !this.insidePointInPolygon(                    new BABYLON.Vector2(                      useP(                        useP(pos.x) + useP(itemWidth) + useP(itemWidth) / 2,                        false                      ),                      useP(useP(pos.z), false)                    ),                    this.areaPoints                  )                ) {                  if (endPos.x === 0 && endPos.z === 0) continue;                  if (!passthData[0]) {                    for (let j = 0; j < 2; j++) {                      this.transform[0].position.push([                        pos.x + itemWidth / 2,                        pos.y + (h !== 0 ? 0.12 : 0),                        pos.z + (j === 0 ? 0 : rackingDim) - itemLength / 2,                      ]);                      this.transform[0].rotation.push([                        0,                        j === 0 ? Math.PI : 0,                        0,                      ]);                      this.transform[0].scaling.push([                        1,                        this.rackingHighLevel === 1                          ? 0.5                          : itemHeight +                            (h === 0                              ? 0.12                              : h === this.rackingHighLevel - 1                              ? -itemHeight / 1.25                              : 0),                        1,                      ]);                      this.transform[0].data.push([r, c, h]);                    }                  }                }              } else {                const [supportPillar, firstRow] = this.checkIfneedPillars(r, h);                if (supportPillar) {                  this.transform[0].position.push([                    pos.x - itemWidth / 2,                    pos.y + (h !== 0 ? 0.12 : 0),                    pos.z + (firstRow ? 0 : rackingDim) - itemLength / 2,                  ]);                  this.transform[0].rotation.push([                    0,                    firstRow ? Math.PI : 0,                    0,                  ]);                  this.transform[0].scaling.push([                    1,                    this.rackingHighLevel === 1                      ? 0.5                      : itemHeight +                        (h === 0                          ? 0.12                          : h === this.rackingHighLevel - 1                          ? -itemHeight / 1.25                          : 0),                    1,                  ]);                  this.transform[0].data.push([r, c, h]);                  if (                    this.activedSpacing.includes(c) ||                    !this.insidePointInPolygon(                      new BABYLON.Vector2(                        pos.x + itemWidth + itemWidth / 2,                        pos.z - rackingDim                      ).scale(0.99),                      this.areaPoints                    ) ||                    !this.insidePointInPolygon(                      new BABYLON.Vector2(                        pos.x + itemWidth + itemWidth / 2,                        pos.z                      ).scale(0.99),                      this.areaPoints                    )                  ) {                    if (endPos.x === 0 && endPos.z === 0) continue;                    this.transform[0].position.push([                      pos.x + itemWidth / 2,                      pos.y + (h !== 0 ? 0.12 : 0),                      pos.z + (firstRow ? 0 : rackingDim) - itemLength / 2,                    ]);                    this.transform[0].rotation.push([                      0,                      firstRow ? Math.PI : 0,                      0,                    ]);                    this.transform[0].scaling.push([                      1,                      this.rackingHighLevel === 1                        ? 0.5                        : itemHeight +                          (h === 0                            ? 0.12                            : h === this.rackingHighLevel - 1                            ? -itemHeight / 1.25                            : 0),                      1,                    ]);                    this.transform[0].data.push([r, c, h]);                  }                }              }            }            let isLast = false;            if (              this.insidePointInPolygon(                new BABYLON.Vector2(                  pos.x,                  useP(                    useP(pos.z) -                      (useP(uprightDist) / 2 + useP(rackingDim) / 2),                    false                  )                ),                this.areaPoints              ) &&              this.insidePointInPolygon(                new BABYLON.Vector2(                  pos.x,                  useP(                    useP(pos.z) -                      (useP(uprightDist) / 2 - useP(rackingDim) / 2),                    false                  )                ),                this.areaPoints              )            ) {              let limits = [];              let offset = 0;              const prev = this.transform[3].data.filter(                (e) => e[0] === r - 1 && e[1] === c && e[2] === h              );              const isFirst = r === 0 || prev.length === 0 || passthData[1];              isLast =                r === this.maxRow - 1 ||                !this.insidePointInPolygon(                  new BABYLON.Vector2(                    pos.x,                    useP(                      useP(pos.z) -                        useP(uprightDist) / 2 +                        useP(rackingDim) / 2 +                        useP(hasAtrack ? g_xtrackFixedDim : uprightDist) +                        useP(rackingDim),                      false                    )                  ),                  this.areaPoints                ) ||                passthData[4];              if (isFirst) {                limits.push(r);                offset = -g_railOutside;              }              if (isLast) {                limits.push(r);                offset = limits.length > 1 ? 0 : g_railOutside;              }              if (!passthData[0]) {                const currentPos = this.isInsideLift(                  pos.z - uprightDist / 2,                  liftBBox[c]                );                const prevPos = this.isInsideLift(                  pos.z - uprightDist / 2 - rackingDim / 2,                  liftBBox[c]                );                const nextPos = this.isInsideLift(                  pos.z - uprightDist / 2 + rackingDim / 2,                  liftBBox[c]                );                let notInLift = 0;                let scaling = !currentPos                  ? rackingDim +                    g_rackingPole +                    Math.abs(limits.length > 1 ? 2 * g_railOutside : offset)                  : 0;                if (scaling === 0) {                  if (!prevPos || !nextPos) {                    notInLift = !prevPos ? -1 : 1;                    scaling = rackingDim / 2 + offset;                  }                }                this.transform[3].position.push([                  pos.x,                  pos.y,                  pos.z -                    (uprightDist / 2 - offset / 2) +                    notInLift * (scaling / 2 + g_rackingPole / 2),                ]);                this.transform[3].rotation.push([0, 0, 0]);                this.transform[3].scaling.push(                  scaling === 0 ? [0, 0, 0] : [1, 1, scaling]                );                this.transform[3].data.push([r, c, h]);                for (let i = 0; i < limits.length; i++) {                  const idx =                    offset === 0 ? (i === 0 ? -1 : 1) * g_railOutside : offset;                  this.transform[5].position.push([                    pos.x,                    pos.y,                    pos.z + (idx < 0 ? 0 : rackingDim) - itemLength / 2 + idx,                  ]);                  this.transform[5].rotation.push([                    0,                    idx > 0 ? Math.PI : 0,                    0,                  ]);                  this.transform[5].scaling.push(                    scaling === 0 ? [0, 0, 0] : [1, 1, 1]                  );                  this.transform[5].data.push([r, c, h]);                }              }            }            //Rail for xtrack            if (!isLast) {              // last row doesn't need rails or xTracks after it              if (!passthData[0] && !passthData[4]) {                if (!hasAtrack) {                  if (                    !this.insidePointInPolygon(                      new BABYLON.Vector2(                        pos.x,                        useP(                          useP(pos.z) +                            useP(itemLength) / 2 +                            useP(g_palletInfo.racking),                          false                        )                      ),                      this.areaPoints                    ) ||                    !this.insidePointInPolygon(                      new BABYLON.Vector2(                        pos.x,                        useP(useP(pos.z) - useP(itemLength) / 2, false)                      ),                      this.areaPoints                    )                  )                    continue;                  const currentPos = this.isInsideLift(                    pos.z + halfRacking / 2 + rackingDim / 2,                    liftBBox[c]                  );                  const prevPos = this.isInsideLift(                    pos.z +                      halfRacking / 2 +                      rackingDim / 2 -                      (uprightDist + halfRacking) / 2,                    liftBBox[c]                  );                  const nextPos = this.isInsideLift(                    pos.z +                      halfRacking / 2 +                      rackingDim / 2 +                      (uprightDist + halfRacking) / 2,                    liftBBox[c]                  );                  if (                    (currentPos && !nextPos) ||                    (!currentPos && !nextPos && prevPos)                  ) {                    const rLength =                      !currentPos && !nextPos && prevPos                        ? (uprightDist + halfRacking) / 1.5                        : (uprightDist + halfRacking) / 3;                    this.transform[4].position.push([                      pos.x,                      pos.y,                      pos.z +                        halfRacking / 2 +                        rackingDim / 2 +                        (uprightDist + halfRacking) / 2 -                        rLength / 2,                    ]);                    this.transform[4].rotation.push([0, 0, 0]);                    this.transform[4].scaling.push([1, 1, rLength]);                    this.transform[4].data.push([r, c, h]);                  } else {                    if (                      (currentPos && !prevPos) ||                      (!currentPos && !prevPos && nextPos)                    ) {                      const rLength =                        !currentPos && !prevPos && nextPos                          ? (uprightDist + halfRacking) / 1.5                          : (uprightDist + halfRacking) / 3;                      this.transform[4].position.push([                        pos.x,                        pos.y,                        pos.z +                          halfRacking / 2 +                          rackingDim / 2 -                          (uprightDist + halfRacking) / 2 +                          rLength / 2,                      ]);                      this.transform[4].rotation.push([0, 0, 0]);                      this.transform[4].scaling.push([1, 1, rLength]);                      this.transform[4].data.push([r, c, h]);                    } else {                      if (!currentPos) {                        this.transform[4].position.push([                          pos.x,                          pos.y,                          pos.z + halfRacking / 2 + rackingDim / 2,                        ]);                        this.transform[4].rotation.push([0, 0, 0]);                        this.transform[4].scaling.push([                          1,                          1,                          uprightDist + halfRacking,                        ]);                        this.transform[4].data.push([r, c, h]);                      }                    }                  }                } else {                  if (                    !this.insidePointInPolygon(                      new BABYLON.Vector2(                        pos.x,                        useP(                          useP(pos.z) +                            useP(rackingDim) / 2 -                            useP(uprightDist) / 2 +                            useP(g_xtrackFixedDim) +                            useP(g_palletInfo.racking),                          false                        )                      ),                      this.areaPoints                    ) ||                    !this.insidePointInPolygon(                      new BABYLON.Vector2(                        pos.x,                        useP(                          useP(pos.z) +                            useP(rackingDim) / 2 -                            useP(uprightDist) / 2 -                            useP(g_palletInfo.racking),                          false                        )                      ),                      this.areaPoints                    )                  )                    continue;                  const passthDataNext = this.checkpPassth(r + 1, c + 1, h);                  for (let i = 6; i < 10; i++) {                    if (i > 7) {                      if (c === this.maxCol - 1) continue;                      if (passthData[5]) continue;                      if (passthDataNext[0]) continue;                      if (                        !this.insidePointInPolygon(                          new BABYLON.Vector2(                            pos.x + itemWidth,                            pos.z - uprightDist / 2                          ),                          this.areaPoints                        ) ||                        !this.insidePointInPolygon(                          new BABYLON.Vector2(                            pos.x + itemWidth,                            pos.z + uprightDist / 2 + g_xtrackFixedDim                          ),                          this.areaPoints                        )                      )                        continue;                    }                    let scaling =                      i > 7 && this.palletOverhang !== 0.05                        ? 1 + this.loadPalletOverhang + this.palletOverhang                        : 1 + this.loadPalletOverhang;                    let offset =                      i > 7                        ? g_rackingPole / 2 +                          (1.2 +                            this.palletOverhang +                            this.loadPalletOverhang) /                            2 +                          (this.palletOverhang !== 0.05                            ? (this.palletOverhang + this.loadPalletOverhang) /                              2                            : this.loadPalletOverhang)                        : 0;                    if (i > 7 && this.activedSpacing.includes(c)) {                      offset += this.spacingBetweenRows / 2;                      scaling += 2 * this.spacingBetweenRows;                    }                    this.transform[i].position.push([                      pos.x + offset,                      pos.y,                      pos.z +                        rackingDim / 2 -                        uprightDist / 2 +                        g_xtrackFixedDim / 2 +                        g_rackingPole / 2,                    ]);                    this.transform[i].rotation.push([0, 0, 0]);                    this.transform[i].scaling.push([                      scaling,                      1,                      g_xtrackFixedDim === 1.35 ? 1 : 1.15,                    ]);                    this.transform[i].data.push([r, c, h, hasAtrack]);                  }                }              }            }          }        }      } else {        let liftBBox = [];        for (let r = 0; r < this.maxRow; r++) {          liftBBox.push(this.checkLiftBooundaries(r));        }        for (let c = 0; c < this.maxCol; c++) {          const rowData = this.calcPosAndUprightForRow(c);          const posx = rowData[0];          itemWidth = rowData[1];          const uprightDist = rowData[2];          const hasAtrack = rowData[3];          const halfRacking = rowData[4];          const rackingDim =            rowData[4] !== 0              ? parseFloat((g_palletInfo.racking / 2).toFixed(3))              : g_palletInfo.racking;          let spacingOffset = 0;          let endPos = BABYLON.Vector3.Zero();          for (let r = 0; r < this.maxRow; r++) {            const spacingRow = this.activedSpacing.indexOf(r - 1);            if (spacingRow > -1)              spacingOffset = (spacingRow + 1) * this.spacingBetweenRows;            const passthData = this.checkpPassth(c, r, h);            const pos = new BABYLON.Vector3(              useP(                useP(this.area.minX) +                  useP(posx) +                  useP(g_railOutside) +                  useP(g_rackingPole) / 2,                false              ),              this.getHeightAtLevel(h),              useP(                useP(this.area.minZ) +                  r * useP(itemLength) +                  useP(itemLength) / 2 +                  useP(spacingOffset),                false              )            );            if (              this.insidePointInPolygon(                new BABYLON.Vector2(                  useP(                    useP(pos.x) + useP(rackingDim) - useP(itemWidth) / 2,                    false                  ),                  pos.z                ),                this.areaPoints              ) &&              this.insidePointInPolygon(                new BABYLON.Vector2(                  useP(useP(pos.x) - useP(itemWidth) / 2, false),                  pos.z                ),                this.areaPoints              )            ) {              if (!passthData[0]) {                if (                  !levelVisibility[h] &&                  ((h !== 0 && !levelVisibility[h - 1]) ||                    [0].includes(h) ||                    (!passthData[0] && passthData[3]))                )                  continue;                // Add racking-beam                for (let j = 0; j < 2; j++) {                  if (                    this.isInsideLift(                      pos.x + (j === 0 ? 0 : rackingDim) - itemWidth / 2,                      liftBBox[r]                    )                  )                    break;                  this.transform[2].position.push([                    pos.x + (j === 0 ? 0 : rackingDim) - itemWidth / 2,                    pos.y,                    pos.z,                  ]);                  this.transform[2].rotation.push([                    0,                    j === 0 ? Math.PI / 2 : (3 * Math.PI) / 2,                    0,                  ]);                  this.transform[2].scaling.push([                    itemLength - g_rackingPole,                    1,                    1,                  ]);                  this.transform[2].data.push([r, c, h]);                }              }              if (!levelVisibility[h]) continue;              endPos = pos;              if (                (!passthData[0] && !passthData[6]) ||                (passthData[0] && !passthData[2]) ||                (!passthData[0] && !passthData[2] && !passthData[6])              ) {                // Add racking-bare                if (h !== this.rackingHighLevel - 1) {                  if (                    !this.isInsideLift(pos.x - uprightDist / 2, liftBBox[r]) &&                    !this.isInsideLift(pos.x - uprightDist / 2, liftBBox[r - 1])                  ) {                    for (let j = 0; j < nrOfBares; j++) {                      this.transform[1].position.push([                        pos.x - uprightDist / 2,                        pos.y + (0.4 * j + 0.1),                        pos.z - itemLength / 2,                      ]);                      this.transform[1].rotation.push([                        [0, nrOfBares - 1].includes(j)                          ? 0                          : j % 2 !== 0                          ? -Math.PI / 10                          : Math.PI / 10,                        Math.PI / 2,                        0,                      ]);                      this.transform[1].scaling.push([1, 1, rackingDim]);                      this.transform[1].data.push([r, c, h]);                    }                    if (                      this.activedSpacing.includes(r) ||                      !this.insidePointInPolygon(                        new BABYLON.Vector2(                          useP(useP(pos.x) - useP(rackingDim), false),                          useP(                            useP(pos.z) +                              useP(itemLength) +                              useP(itemLength) / 2,                            false                          )                        ),                        this.areaPoints                      ) ||                      !this.insidePointInPolygon(                        new BABYLON.Vector2(                          pos.x,                          useP(                            useP(pos.z) +                              useP(itemLength) +                              useP(itemLength) / 2,                            false                          )                        ),                        this.areaPoints                      )                    ) {                      if (endPos.x === 0 && endPos.z === 0) continue;                      if (!passthData[0]) {                        for (let j = 0; j < nrOfBares; j++) {                          this.transform[1].position.push([                            endPos.x - uprightDist / 2,                            pos.y + (0.4 * j + 0.1),                            endPos.z + itemLength / 2,                          ]);                          this.transform[1].rotation.push([                            [0, nrOfBares - 1].includes(j)                              ? 0                              : j % 2 !== 0                              ? Math.PI / 10                              : -Math.PI / 10,                            (3 * Math.PI) / 2,                            0,                          ]);                          this.transform[1].scaling.push([1, 1, rackingDim]);                          this.transform[1].data.push([r, c, h]);                        }                      }                    }                  }                }                // add racking                for (let j = 0; j < 2; j++) {                  this.transform[0].position.push([                    pos.x + (j === 0 ? 0 : rackingDim) - itemWidth / 2,                    pos.y + (h !== 0 ? 0.12 : 0),                    pos.z - itemLength / 2,                  ]);                  this.transform[0].rotation.push([                    0,                    j === 0 ? -Math.PI / 2 : Math.PI / 2,                    0,                  ]);                  this.transform[0].scaling.push([                    1,                    this.rackingHighLevel === 1                      ? 0.5                      : itemHeight +                        (h === 0                          ? 0.12                          : h === this.rackingHighLevel - 1                          ? -itemHeight / 1.25                          : 0),                    1,                  ]);                  this.transform[0].data.push([r, c, h]);                }                if (                  this.activedSpacing.includes(r) ||                  !this.insidePointInPolygon(                    new BABYLON.Vector2(                      useP(useP(pos.x) - useP(rackingDim), false),                      useP(                        useP(pos.z) + useP(itemLength) + useP(itemLength) / 2,                        false                      )                    ),                    this.areaPoints                  ) ||                  !this.insidePointInPolygon(                    new BABYLON.Vector2(                      pos.x,                      useP(                        useP(pos.z) + useP(itemLength) + useP(itemLength) / 2,                        false                      )                    ),                    this.areaPoints                  )                ) {                  if (endPos.x === 0 && endPos.z === 0) continue;                  if (!passthData[0]) {                    for (let j = 0; j < 2; j++) {                      this.transform[0].position.push([                        pos.x + (j === 0 ? 0 : rackingDim) - itemWidth / 2,                        pos.y + (h !== 0 ? 0.12 : 0),                        pos.z + itemLength / 2,                      ]);                      this.transform[0].rotation.push([                        0,                        j === 0 ? -Math.PI / 2 : Math.PI / 2,                        0,                      ]);                      this.transform[0].scaling.push([                        1,                        this.rackingHighLevel === 1                          ? 0.5                          : itemHeight +                            (h === 0                              ? 0.12                              : h === this.rackingHighLevel - 1                              ? -itemHeight / 1.25                              : 0),                        1,                      ]);                      this.transform[0].data.push([r, c, h]);                    }                  }                }              } else {                const [supportPillar, firstRow] = this.checkIfneedPillars(c, h);                if (supportPillar) {                  const j = c === 0 ? 0 : 1;                  this.transform[0].position.push([                    pos.x + (firstRow ? 0 : rackingDim) - itemWidth / 2,                    pos.y + (h !== 0 ? 0.12 : 0),                    pos.z - itemLength / 2,                  ]);                  this.transform[0].rotation.push([                    0,                    firstRow ? -Math.PI / 2 : Math.PI / 2,                    0,                  ]);                  this.transform[0].scaling.push([                    1,                    this.rackingHighLevel === 1                      ? 0.5                      : itemHeight +                        (h === 0                          ? 0.12                          : h === this.rackingHighLevel - 1                          ? -itemHeight / 1.25                          : 0),                    1,                  ]);                  this.transform[0].data.push([r, c, h]);                  if (                    this.activedSpacing.includes(r) ||                    !this.insidePointInPolygon(                      new BABYLON.Vector2(                        useP(useP(pos.x) - useP(rackingDim), false),                        useP(                          useP(pos.z) + useP(itemLength) + useP(itemLength) / 2,                          false                        )                      ),                      this.areaPoints                    ) ||                    !this.insidePointInPolygon(                      new BABYLON.Vector2(                        pos.x,                        useP(                          useP(pos.z) + useP(itemLength) + useP(itemLength) / 2,                          false                        )                      ),                      this.areaPoints                    )                  ) {                    if (endPos.x === 0 && endPos.z === 0) continue;                    this.transform[0].position.push([                      pos.x + (firstRow ? 0 : rackingDim) - itemWidth / 2,                      pos.y + (h !== 0 ? 0.12 : 0),                      pos.z + itemLength / 2,                    ]);                    this.transform[0].rotation.push([                      0,                      firstRow ? -Math.PI / 2 : Math.PI / 2,                      0,                    ]);                    this.transform[0].scaling.push([                      1,                      this.rackingHighLevel === 1                        ? 0.5                        : itemHeight +                          (h === 0                            ? 0.12                            : h === this.rackingHighLevel - 1                            ? -itemHeight / 1.25                            : 0),                      1,                    ]);                    this.transform[0].data.push([r, c, h]);                  }                }              }            }            let isLast = false;            if (              this.insidePointInPolygon(                new BABYLON.Vector2(                  useP(                    useP(pos.x) -                      (useP(uprightDist) / 2 + useP(rackingDim) / 2),                    false                  ),                  pos.z                ),                this.areaPoints              ) &&              this.insidePointInPolygon(                new BABYLON.Vector2(                  useP(                    useP(pos.x) -                      (useP(uprightDist) / 2 - useP(rackingDim) / 2),                    false                  ),                  pos.z                ),                this.areaPoints              )            ) {              let limits = [];              let offset = 0;              const prev = this.transform[3].data.filter(                (e) => e[0] === r && e[1] === c - 1 && e[2] === h              );              const isFirst = c === 0 || prev.length === 0 || passthData[1];              isLast =                c === this.maxCol - 1 ||                !this.insidePointInPolygon(                  new BABYLON.Vector2(                    useP(                      useP(pos.x) -                        useP(uprightDist) / 2 +                        useP(rackingDim) / 2 +                        useP(hasAtrack ? g_xtrackFixedDim : uprightDist) +                        useP(rackingDim),                      false                    ),                    pos.z                  ),                  this.areaPoints                ) ||                passthData[4];              if (isFirst) {                limits.push(r);                offset = -g_railOutside;              }              if (isLast) {                limits.push(r);                offset = limits.length > 1 ? 0 : g_railOutside;              }              if (!passthData[0]) {                const currentPos = this.isInsideLift(                  pos.x - uprightDist / 2,                  liftBBox[r]                );                const prevPos = this.isInsideLift(                  pos.x - uprightDist / 2 - rackingDim / 2,                  liftBBox[r]                );                const nextPos = this.isInsideLift(                  pos.x - uprightDist / 2 + rackingDim / 2,                  liftBBox[r]                );                let notInLift = 0;                let scaling = !currentPos                  ? rackingDim +                    g_rackingPole +                    Math.abs(limits.length > 1 ? 2 * g_railOutside : offset)                  : 0;                if (scaling === 0) {                  if (!prevPos || !nextPos) {                    notInLift = !prevPos ? -1 : 1;                    scaling = rackingDim / 2 + offset;                  }                }                this.transform[3].position.push([                  pos.x -                    (uprightDist / 2 - offset / 2) +                    notInLift * (scaling / 2 + g_rackingPole / 2),                  pos.y,                  pos.z,                ]);                this.transform[3].rotation.push([0, Math.PI / 2, 0]);                this.transform[3].scaling.push(                  scaling === 0 ? [0, 0, 0] : [1, 1, scaling]                );                this.transform[3].data.push([r, c, h]);                for (let i = 0; i < limits.length; i++) {                  const idx =                    offset === 0 ? (i === 0 ? -1 : 1) * g_railOutside : offset;                  this.transform[5].position.push([                    pos.x + (idx < 0 ? 0 : rackingDim) - itemWidth / 2 + idx,                    pos.y,                    pos.z,                  ]);                  this.transform[5].rotation.push([                    0,                    idx > 0 ? (3 * Math.PI) / 2 : Math.PI / 2,                    0,                  ]);                  this.transform[5].scaling.push(                    scaling === 0 ? [0, 0, 0] : [1, 1, 1]                  );                  this.transform[5].data.push([r, c, h]);                }              }            }            //Rail for xtrack            if (!isLast) {              // last row doesn't need rails or xTracks after it              if (!passthData[0] && !passthData[4]) {                if (!hasAtrack) {                  if (                    !this.insidePointInPolygon(                      new BABYLON.Vector2(                        useP(                          useP(pos.x) +                            useP(itemLength) / 2 +                            useP(g_palletInfo.racking),                          false                        ),                        pos.z                      ),                      this.areaPoints                    ) ||                    !this.insidePointInPolygon(                      new BABYLON.Vector2(                        useP(useP(pos.x) - useP(itemLength) / 2, false),                        pos.z                      ),                      this.areaPoints                    )                  )                    continue;                  const currentPos = this.isInsideLift(                    pos.x + halfRacking / 2 + rackingDim / 2,                    liftBBox[r]                  );                  const prevPos = this.isInsideLift(                    pos.x +                      halfRacking / 2 +                      rackingDim / 2 -                      (uprightDist + halfRacking) / 2,                    liftBBox[r]                  );                  const nextPos = this.isInsideLift(                    pos.x +                      halfRacking / 2 +                      rackingDim / 2 +                      (uprightDist + halfRacking) / 2,                    liftBBox[r]                  );                  if (                    (currentPos && !nextPos) ||                    (!currentPos && !nextPos && prevPos)                  ) {                    const rLength =                      !currentPos && !nextPos && prevPos                        ? (uprightDist + halfRacking) / 1.5                        : (uprightDist + halfRacking) / 3;                    this.transform[4].position.push([                      pos.x +                        halfRacking / 2 +                        rackingDim / 2 +                        (uprightDist + halfRacking) / 2 -                        rLength / 2,                      pos.y,                      pos.z,                    ]);                    this.transform[4].rotation.push([0, Math.PI / 2, 0]);                    this.transform[4].scaling.push([1, 1, rLength]);                    this.transform[4].data.push([r, c, h]);                  } else {                    if (                      (currentPos && !prevPos) ||                      (!currentPos && !prevPos && nextPos)                    ) {                      const rLength =                        !currentPos && !prevPos && nextPos                          ? (uprightDist + halfRacking) / 1.5                          : (uprightDist + halfRacking) / 3;                      this.transform[4].position.push([                        pos.x +                          halfRacking / 2 +                          rackingDim / 2 -                          (uprightDist + halfRacking) / 2 +                          rLength / 2,                        pos.y,                        pos.z,                      ]);                      this.transform[4].rotation.push([0, Math.PI / 2, 0]);                      this.transform[4].scaling.push([1, 1, rLength]);                      this.transform[4].data.push([r, c, h]);                    } else {                      if (!currentPos) {                        this.transform[4].position.push([                          pos.x + halfRacking / 2 + rackingDim / 2,                          pos.y,                          pos.z,                        ]);                        this.transform[4].rotation.push([0, Math.PI / 2, 0]);                        this.transform[4].scaling.push([                          1,                          1,                          uprightDist + halfRacking,                        ]);                        this.transform[4].data.push([r, c, h]);                      }                    }                  }                } else {                  if (                    !this.insidePointInPolygon(                      new BABYLON.Vector2(                        useP(                          useP(pos.x) +                            useP(rackingDim) / 2 -                            useP(uprightDist) / 2 +                            useP(g_xtrackFixedDim) +                            useP(g_palletInfo.racking),                          false                        ),                        pos.z                      ),                      this.areaPoints                    ) ||                    !this.insidePointInPolygon(                      new BABYLON.Vector2(                        useP(                          useP(pos.x) +                            useP(rackingDim) / 2 -                            useP(uprightDist) / 2 -                            useP(g_palletInfo.racking),                          false                        ),                        pos.z                      ),                      this.areaPoints                    )                  )                    continue;                  const passthDataNext = this.checkpPassth(c + 1, r + 1, h);                  for (let i = 6; i < 10; i++) {                    if (i > 7) {                      if (r === this.maxRow - 1) continue;                      if (passthData[5]) continue;                      if (passthDataNext[0]) continue;                      if (                        !this.insidePointInPolygon(                          new BABYLON.Vector2(                            pos.x - uprightDist / 2,                            pos.z + itemLength                          ),                          this.areaPoints                        ) ||                        !this.insidePointInPolygon(                          new BABYLON.Vector2(                            pos.x + uprightDist / 2 + g_xtrackFixedDim,                            pos.z + itemLength                          ),                          this.areaPoints                        )                      )                        continue;                    }                    let scaling =                      i > 7 && this.palletOverhang !== 0.05                        ? 1 + this.loadPalletOverhang + this.palletOverhang                        : 1 + this.loadPalletOverhang;                    let offset =                      i > 7                        ? g_rackingPole / 2 +                          (1.2 +                            this.palletOverhang +                            this.loadPalletOverhang) /                            2 +                          (this.palletOverhang !== 0.05                            ? (this.palletOverhang + this.loadPalletOverhang) /                              2                            : this.loadPalletOverhang)                        : 0;                    if (i > 7 && this.activedSpacing.includes(r)) {                      offset += this.spacingBetweenRows / 2;                      scaling += 2 * this.spacingBetweenRows;                    }                    this.transform[i].position.push([                      pos.x +                        rackingDim / 2 -                        uprightDist / 2 +                        g_xtrackFixedDim / 2 +                        g_rackingPole / 2,                      pos.y,                      pos.z + offset,                    ]);                    this.transform[i].rotation.push([0, Math.PI / 2, 0]);                    this.transform[i].scaling.push([                      scaling,                      1,                      g_xtrackFixedDim === 1.35 ? 1 : 1.15,                    ]);                    this.transform[i].data.push([r, c, h, hasAtrack]);                  }                }              }            }          }        }      }    }  }  getHeightAtLevel(level, customHeight = 0) {    let height = 0;    for (let i = 0; i < level; i++) {      if (customHeight !== 0) {        height += customHeight;      } else {        const palletInfo = this.palletAtLevel.filter((e) => e.idx === i + 1);        if (palletInfo.length > 0) {          height += useP(palletInfo[0].height) + useP(g_railHeight);        } else {          height += useP(this.palletHeight) + useP(g_railHeight);        }      }    }    return customHeight !== 0 ? height : useP(height, false);  }  // check for ideal xtrack position based on pallet distribution  calcIdealPosForXtrack(calculatedXtracks) {    const max = [      this.isHorizontal ? this.area.minZ : this.area.minX,      this.isHorizontal ? this.area.maxZ : this.area.maxX,    ];    const dist = parseFloat(      (max[1] - max[0] - 2 * g_diffToEnd[g_palletInfo.max]).toFixed(3)    );    const width = _round(      g_PalletW[g_palletInfo.max] +        g_spacingBPallets[g_palletInfo.max] +        2 * g_loadPalletOverhang,      2    );    const capacity = _round(      (dist + g_spacingBPallets[g_palletInfo.max]) / width    );    let optimPos = [];    if (calculatedXtracks > 1 || this.drawMode === sceneMode.normal) {      let step = Math.floor(        (capacity - calculatedXtracks) / (calculatedXtracks + 1)      );      step = step === 0 ? 1 : step;      const palletDim =        g_diffToEnd[g_palletInfo.max] +        g_difftoXtrack[g_palletInfo.max] +        step * (g_palletInfo.width + 2 * g_loadPalletOverhang) +        (step - 1) * g_spacingBPallets[g_palletInfo.max] +        g_xtrackFixedDim / 2;      const palletDim1 =        2 * g_difftoXtrack[g_palletInfo.max] +        step * (g_palletInfo.width + 2 * g_loadPalletOverhang) +        (step - 1) * g_spacingBPallets[g_palletInfo.max] +        g_xtrackFixedDim / 2;      for (let i = 0; i < calculatedXtracks; i++) {        const xtrackPos =          max[1] -          max[0] -          (i * g_xtrackFixedDim) / 2 -          i * palletDim1 -          palletDim;        optimPos.push(parseFloat(xtrackPos.toFixed(3)));      }      let allDims = [parseFloat((max[1] - max[0]).toFixed(3))]        .concat(optimPos)        .concat([0]);      let diffi = parseFloat(        (allDims[0] - allDims[1] - g_xtrackFixedDim / 2).toFixed(3)      );      let diffl = parseFloat(        (          allDims[allDims.length - 2] -          allDims[allDims.length - 1] -          g_xtrackFixedDim / 2        ).toFixed(3)      );      if (        step > 1 &&        diffl < diffi &&        (diffi - diffl > width || diffl < width)      ) {        let idx = 0;        while (diffl < diffi && (diffi - diffl > width || diffl < width)) {          for (let i = idx; i < optimPos.length; i++) {            optimPos[i] += width;          }          idx += 1;          allDims = [parseFloat((max[1] - max[0]).toFixed(3))]            .concat(optimPos)            .concat([0]);          diffi = parseFloat(            (allDims[0] - allDims[1] - g_xtrackFixedDim / 2).toFixed(3)          );          diffl = parseFloat(            (              allDims[allDims.length - 2] -              allDims[allDims.length - 1] -              g_xtrackFixedDim / 2            ).toFixed(3)          );        }      }      if (        step === 1 &&        diffi < diffl &&        (diffl - diffi > width || diffi < width)      ) {        let idx = 1;        while (diffi < diffl && (diffl - diffi > width || diffi < width)) {          for (let i = idx; i < optimPos.length; i++) {            optimPos[i] -= width;          }          idx += 1;          allDims = [parseFloat((max[1] - max[0]).toFixed(3))]            .concat(optimPos)            .concat([0]);          diffi = parseFloat(            (allDims[0] - allDims[1] - g_xtrackFixedDim / 2).toFixed(3)          );          diffl = parseFloat(            (              allDims[allDims.length - 2] -              allDims[allDims.length - 1] -              g_xtrackFixedDim / 2            ).toFixed(3)          );        }      }      for (let i = 0; i < optimPos.length; i++) {        optimPos[i] = parseFloat(optimPos[i].toFixed(3));      }    } else {      this.updateInfos();      const itemLength =        g_PalletW[g_palletInfo.max] +        this.infos.uprights[0] +        2 * g_loadPalletOverhang;      let lefts = [];      let rights = [];      const maxCol =        this.infos.cols[this.infos.cols.length - 1][          this.infos.cols[this.infos.cols.length - 1].length - 1        ] + 1;      for (let i = 0; i < maxCol; i++) {        if (this.isHorizontal) {          const left = this.area.minX + g_palletInfo.length;          const right = this.area.maxX - g_palletInfo.length;          if (            this.insidePointInPolygon(              new BABYLON.Vector2(                left,                this.area.minZ +                  i * itemLength +                  g_railOutside +                  g_rackingPole / 2              ).scale(0.99),              this.areaPoints            ) &&            this.insidePointInPolygon(              new BABYLON.Vector2(                left,                this.area.minZ +                  i * itemLength +                  itemLength / 2 +                  g_railOutside +                  g_rackingPole / 2 -                  (this.infos.uprights[0] / 2 - g_palletInfo.racking / 2)              ).scale(0.99),              this.areaPoints            )          ) {            lefts.push(i);          }          if (            this.insidePointInPolygon(              new BABYLON.Vector2(                right,                this.area.minZ +                  i * itemLength +                  g_railOutside +                  g_rackingPole / 2              ).scale(0.99),              this.areaPoints            ) &&            this.insidePointInPolygon(              new BABYLON.Vector2(                right,                this.area.minZ +                  i * itemLength +                  itemLength / 2 +                  g_railOutside +                  g_rackingPole / 2 -                  (this.infos.uprights[0] / 2 - g_palletInfo.racking / 2)              ).scale(0.99),              this.areaPoints            )          ) {            rights.push(i);          }        } else {          const left = this.area.minZ + g_palletInfo.length;          const right = this.area.maxZ - g_palletInfo.length;          if (            this.insidePointInPolygon(              new BABYLON.Vector2(                this.area.minX +                  i * itemLength +                  g_railOutside +                  g_rackingPole / 2,                left              ).scale(0.99),              this.areaPoints            ) &&            this.insidePointInPolygon(              new BABYLON.Vector2(                this.area.minX +                  i * itemLength +                  itemLength / 2 +                  g_railOutside +                  g_rackingPole / 2 -                  (this.infos.uprights[0] / 2 - g_palletInfo.racking / 2),                left              ).scale(0.99),              this.areaPoints            )          ) {            lefts.push(i);          }          if (            this.insidePointInPolygon(              new BABYLON.Vector2(                this.area.minX +                  i * itemLength +                  g_railOutside +                  g_rackingPole / 2,                right              ).scale(0.99),              this.areaPoints            ) &&            this.insidePointInPolygon(              new BABYLON.Vector2(                this.area.minX +                  i * itemLength +                  itemLength / 2 +                  g_railOutside +                  g_rackingPole / 2 -                  (this.infos.uprights[0] / 2 - g_palletInfo.racking / 2),                right              ).scale(0.99),              this.areaPoints            )          ) {            rights.push(i);          }        }      }      let completedRows = [];      if (rights.length > lefts.right) {        for (let i = 0; i < rights.length; i++) {          if (lefts.includes(rights[i])) completedRows.push(rights[i]);        }      } else {        for (let i = 0; i < lefts.length; i++) {          if (rights.includes(lefts[i])) completedRows.push(lefts[i]);        }      }      let posX;      const row = completedRows[parseInt(completedRows.length / 2)];      const data = this.calcPosAndUprightForRow(row);      if (this.isHorizontal) {        posX = parseFloat((this.area.minZ + data[0] - data[2] / 2).toFixed(3));      } else {        posX = parseFloat((this.area.minX + data[0] - data[2] / 2).toFixed(3));      }      const dist = parseFloat(        (          Math.abs(max[0] - posX) -          g_diffToEnd[g_palletInfo.max] -          g_difftoXtrack[g_palletInfo.max]        ).toFixed(3)      );      const width = _round(        g_PalletW[g_palletInfo.max] +          g_spacingBPallets[g_palletInfo.max] +          2 * g_loadPalletOverhang,        2      );      const cap = _round((dist + g_spacingBPallets[g_palletInfo.max]) / width);      const length = useP(        useP(max[0]) +          useP(g_diffToEnd[g_palletInfo.max]) +          useP(g_difftoXtrack[g_palletInfo.max]) +          cap * useP(width) -          useP(g_spacingBPallets[g_palletInfo.max]),        false      );      const xtrackPos = this.isHorizontal ? max[1] - length : length - max[0];      optimPos.push(parseFloat(xtrackPos.toFixed(3)));    }    return optimPos;  }  //--------------------------------------------------------------------------------------------------------------------  //---------Start IOPort---------//  //--------------------------------------------------------------------------------------------------------------------  // show possible position for input/output selectors  previewPortSite(prop) {    this.finishToSetProperty(prop, true);    for (let i = 0; i < this.transform[5].data.length; i++) {      if (this.transform[5].data[i][2] !== 0) continue;      let portPosition;      if (this.isHorizontal)        portPosition =          this.transform[5].rotation[i][1] !== 0 ? "top" : "bottom";      else        portPosition =          this.transform[5].rotation[i][1] !== Math.PI / 2 ? "right" : "left";      const initPosition = new BABYLON.Vector3(        this.transform[5].position[i][0],        this.transform[5].position[i][1],        this.transform[5].position[i][2]      );      const [position] = this.getInputPosition(initPosition, portPosition);      const selector = this.addSelector(prop);      selector.scaling = new BABYLON.Vector3(1.3, 0.2, 2);      selector.position = position;      selector.portType = 0;      selector.portPosition = portPosition;      selector.row = this.transform[5].data[i][0];      selector.col = this.transform[5].data[i][1];      this.property["port"].selectors.push(selector);    }    Utils.logg(      "单击一次可设置输入,单击两次可设置输出,单击三次可删除端口",      "提示"    );  }  // on click selector on scene - enable/disable xtracks  updatePortPlacementBySelector(selector) {    if (this.property["port"].selectors.includes(selector)) {      let portInfoIndex = -1;      for (let i = 0; i < this.activedIOPorts.length; i++) {        if (          selector.col === this.activedIOPorts[i].col &&          selector.row === this.activedIOPorts[i].row &&          selector.portPosition === this.activedIOPorts[i].portPosition        ) {          selector.portType = this.activedIOPorts[i].portType;          portInfoIndex = i;          break;        }      }      selector.portType += 1;      selector.portType = selector.portType % 3;      const portInfo = {        portType: selector.portType,        portPosition: selector.portPosition,        col: selector.col,        row: selector.row,      };      if (portInfoIndex !== -1) {        if (selector.portType === 0)          this.activedIOPorts.splice(portInfoIndex, 1);        else this.activedIOPorts[portInfoIndex] = portInfo;      } else {        this.activedIOPorts.push(portInfo);      }      this.emptyProperty("ports");      this.updatePortPlacement();      // update safety fences      this.updateSafetyFenceOnIOPorts();    }  }  // on update icube, if there are lifts, show them  updatePortPlacement() {    for (let i = this.activedIOPorts.length - 1; i >= 0; i--) {      if (!this._addPort(this.activedIOPorts[i]))        this.activedIOPorts.splice(i, 1);    }  }  // add IO port onclick or one by one on update/load  _addPort(infoPort) {    const infoData = this.transform[5].data.filter(      (e) => e[0] === infoPort.row && e[2] === 0 && e[1] === infoPort.col    );    if (infoData.length === 0) {      const options = this.transform[5].data.filter(        (e) =>          e[2] === 0 &&          e[this.isHorizontal ? 1 : 0] ===            (this.isHorizontal ? infoPort.col : infoPort.row)      );      if (options.length === 0) return false;      if (this.isHorizontal) {        if (infoPort.row > options[options.length - 1][0]) {          infoPort.row = options[options.length - 1][0];        } else {          if (infoPort.row < options[0][0]) {            infoPort.row = options[0][0];          }        }      } else {        if (infoPort.col > options[options.length - 1][1]) {          infoPort.col = options[options.length - 1][1];        } else {          if (infoPort.col < options[0][1]) {            infoPort.col = options[0][1];          }        }      }    }    let initPosition = BABYLON.Vector3.Zero();    this.transform[5].data.forEach((elem, index) => {      if (        elem[2] === 0 &&        elem[1] === infoPort.col &&        elem[0] === infoPort.row      ) {        initPosition = new BABYLON.Vector3(          this.transform[5].position[index][0],          this.transform[5].position[index][1],          this.transform[5].position[index][2]        );      }    });    const [position, rotation] = this.getInputPosition(      initPosition,      infoPort.portPosition    );    otherItemInfo[ITEMTYPE.Other.PortArrow].originMesh.renderingGroupId = 1;    const inputPort = otherItemInfo[      ITEMTYPE.Other.PortArrow    ].originMesh.createInstance("icubePort" + "Instance");    inputPort.origin = otherItemInfo[ITEMTYPE.Other.PortArrow].originMesh;    inputPort.isPickable = false;    inputPort.setEnabled(true);    inputPort.scaling.scaleInPlace(0.6);    inputPort.position = position;    inputPort.rotation = rotation;    if (infoPort.portType === 2) {      inputPort.rotation.y += Math.PI;    }    this.ports.push(inputPort);    return true;  }  getInputPosition(initPosition, portPosition) {    let initRotation = BABYLON.Vector3.Zero();    switch (portPosition) {      case "bottom":        while (          this.insidePointInPolygon(            new BABYLON.Vector2(initPosition.x, initPosition.z),            this.areaPoints          )        ) {          initPosition.z -= 0.1;        }        initPosition.z -= 2.5;        initRotation.y = 0;        break;      case "top":        while (          this.insidePointInPolygon(            new BABYLON.Vector2(initPosition.x, initPosition.z),            this.areaPoints          )        ) {          initPosition.z += 0.1;        }        initPosition.z += 2.5;        initRotation.y = Math.PI;        break;      case "left":        while (          this.insidePointInPolygon(            new BABYLON.Vector2(initPosition.x, initPosition.z),            this.areaPoints          )        ) {          initPosition.x -= 0.1;        }        initPosition.x -= 2.5;        initRotation.y = Math.PI / 2;        break;      case "right":        while (          this.insidePointInPolygon(            new BABYLON.Vector2(initPosition.x, initPosition.z),            this.areaPoints          )        ) {          initPosition.x += 0.1;        }        initPosition.x += 2.5;        initRotation.y = -Math.PI / 2;        break;      default:        break;    }    return [initPosition, initRotation];  }  //--------------------------------------------------------------------------------------------------------------------  //---------End IOPort---------//  //--------------------------------------------------------------------------------------------------------------------  //--------------------------------------------------------------------------------------------------------------------  //---------Start Xtrack---------//  //--------------------------------------------------------------------------------------------------------------------  // show possible position for xtracks selectors  previewXtrackSite(prop, message) {    this.finishToSetProperty(prop, true);    this.hideMeasurement();    const selector = new XtrackSelector(this, scene);    this.property["xtrack"].selectors.push(selector);    // show existed xtracks    for (let i = 0; i < this.activedXtrackIds.length; i++) {      selector.addXtrack(this.activedXtrackIds[i], false);    }    if (message) Utils.logg("单击加号按钮添加更多x轨迹。拖动选择器以定位它");  }  // place xtrack auto on click plus, or enter or confirm  updateLastAddedXtrack(removeSelector) {    if (this.property["xtrack"].selectors.length > 0) {      const selector = this.property["xtrack"].selectors[0];      if (selector.currentXtrack && selector.currentXtrack.xtrack) {        const xtrack = selector.currentXtrack.xtrack;        selector.removeCurrentXtrack();        if (this.activedXtrackIds.indexOf(xtrack) < 0) {          selector.addXtrack(xtrack, false);          this.updateXtrackPlacementBySelector(xtrack);          selector.updatePalletsNo();          Behavior.add(Behavior.type.addXtrack);          this.updateRacking(() => {            this.previewProperty("xtrack", false);          });        }        renderScene();      }    }    if (removeSelector) {      this.showMeasurement();    }  }  // on click selector on scene - enable/disable xtracks  updateXtrackPlacementBySelector(selector) {    showLoadingPopUp(() => {      if (isNaN(selector)) return;      const idx = this.activedXtrackIds.indexOf(selector);      if (idx !== -1) {        this.activedXtrackIds.splice(idx, 1);      } else {        this.activedXtrackIds.push(selector);        this.activedXtrackIds = this.activedXtrackIds.sort((a, b) => {          return this.isHorizontal ? a - b : b - a;        });      }      if (this.calculatedXtracksNo <= this.activedXtrackIds.length) {        const diff = this.activedXtrackIds.length - this.calculatedXtracksNo;        if (this.extra.xtrack === 1 && diff === 0) {          Utils.logg("删除了额外的X轨道", "提示");        }        if (this.extra.xtrack === 0 && diff === 1) {          Utils.logg("添加了额外的X轨道", "提示");        }        this.extra.xtrack = diff;        updateXtrackAmount(this.calculatedXtracksNo, this.extra.xtrack);      }    });    hideLoadingPopUp();  }  // on update icube, if there are activeXtracks, show them  updateXtrackPlacement() {    if (this.calculatedXtracksNo < this.activedXtrackIds.length) {      const diff = this.activedXtrackIds.length - this.calculatedXtracksNo;      this.extra.xtrack = diff;      updateXtrackAmount(this.calculatedXtracksNo, this.extra.xtrack);    }    if (this.calculatedXtracksNo > this.activedXtrackIds.length) {      this.calculatedXtracksNo = this.activedXtrackIds.length;      this.extra.xtrack = 0;      updateXtrackAmount(this.calculatedXtracksNo, this.extra.xtrack);    }  }  //--------------------------------------------------------------------------------------------------------------------  //---------End Xtrack---------//  //--------------------------------------------------------------------------------------------------------------------  //--------------------------------------------------------------------------------------------------------------------  //---------Start Lift---------//  //--------------------------------------------------------------------------------------------------------------------  // show possible position for lift selectors  previewLiftSite(prop) {    this.finishToSetProperty(prop, true);    if (this.activedXtrackIds.length === 0) {      Utils.logg("放置升降机前,请放置一个或多个x轨道", "提示");      return;    }    const itemLength =      2 * this.palletOverhang +      2 * this.loadPalletOverhang +      g_palletInfo.length +      g_rackingPole;    const max = [      this.isHorizontal ? this.area.minZ : this.area.minX,      this.isHorizontal ? this.area.maxZ : this.area.maxX,    ];    if (this.drawMode === 0) {      if (this.transform[5]) {        for (let i = 0; i < this.transform[5].position.length; i++) {          if (this.transform[5].position[i][1] !== 0) continue;          let pos = BABYLON.Vector3.Zero();          if (this.isHorizontal) {            if (this.transform[5].rotation[i][1] !== 0) {              if (                this.transform[5].position[i][2] +                  (g_liftFixedDim - g_railOutside) >                WHDimensions[1] / 2              )                continue;              pos = new BABYLON.Vector3(                this.transform[5].position[i][0],                this.transform[5].position[i][1],                this.transform[5].position[i][2] +                  g_liftFixedDim / 2 -                  g_railOutside              );              const length =                max[1] - (pos.z - g_liftFixedDim / 2 - 2 * g_railOutside);              this._showLiftSelectors(                pos,                _round(length, 3),                1,                this.transform[5].data[i][1],                this.transform[5].data[i][0]              );            } else {              if (                this.transform[5].position[i][2] -                  (g_liftFixedDim + g_railOutside) <                -WHDimensions[1] / 2              )                continue;              pos = new BABYLON.Vector3(                this.transform[5].position[i][0],                this.transform[5].position[i][1],                this.transform[5].position[i][2] -                  g_liftFixedDim / 2 +                  g_railOutside              );              const length =                max[1] - (pos.z + g_liftFixedDim / 2 + 2 * g_railOutside);              this._showLiftSelectors(                pos,                _round(length, 3),                -1,                this.transform[5].data[i][1],                this.transform[5].data[i][0]              );            }          } else {            if (this.transform[5].rotation[i][1] !== Math.PI / 2) {              if (                this.transform[5].position[i][0] +                  (g_liftFixedDim - g_railOutside) >                WHDimensions[0] / 2              )                continue;              pos = new BABYLON.Vector3(                this.transform[5].position[i][0] +                  g_liftFixedDim / 2 -                  g_railOutside,                this.transform[5].position[i][1],                this.transform[5].position[i][2]              );              const length =                Math.abs(max[1] - max[0]) -                (max[1] - pos.x + g_liftFixedDim - 2 * g_railOutside);              this._showLiftSelectors(                pos,                _round(length, 3),                1,                this.transform[5].data[i][0],                this.transform[5].data[i][1]              );            } else {              if (                this.transform[5].position[i][0] -                  (g_liftFixedDim + g_railOutside) <                -WHDimensions[0] / 2              )                continue;              pos = new BABYLON.Vector3(                this.transform[5].position[i][0] -                  g_liftFixedDim / 2 +                  g_railOutside,                this.transform[5].position[i][1],                this.transform[5].position[i][2]              );              const length =                Math.abs(max[1] - max[0]) -                (max[1] - pos.x - g_liftFixedDim + 2 * g_railOutside);              this._showLiftSelectors(                pos,                _round(length, 3),                -1,                this.transform[5].data[i][0],                this.transform[5].data[i][1]              );            }          }        }      }    }    for (let i = 0; i < this.activedXtrackIds.length; i++) {      const position = _round(        max[this.isHorizontal ? 1 : 0] +          (this.isHorizontal ? -1 : 1) * this.activedXtrackIds[i],        3      );      const parts = this.transform[6].data.filter(        (e) => e[3] === this.activedXtrackIds[i]      );      if (parts.length === 0) continue;      const railProp = parts[0][this.isHorizontal ? 0 : 1];      let spacingOffset = 0;      for (        let j = 0;        j < (this.isHorizontal ? this.maxCol : this.maxRow) + 1;        j++      ) {        let exist = false;        for (let k = 0; k < this.rackingHighLevel; k++) {          const particles = this.transform[3].data.filter(            (e) =>              [railProp, railProp + 1].includes(e[this.isHorizontal ? 0 : 1]) &&              e[this.isHorizontal ? 1 : 0] === j &&              e[2] === k          );          if (particles.length > 1) {            exist = true;            break;          }        }        if (!exist) continue;        if (this.isHorizontal) {          const spacingRow = this.activedSpacing.indexOf(j - 1);          if (spacingRow > -1)            spacingOffset = (spacingRow + 1) * this.spacingBetweenRows;          if (            Math.abs(max[0] - position) >            2 *              (g_railOutside +                g_spacingBPallets[g_palletInfo.max] +                g_loadPalletOverhang +                g_PalletW[g_palletInfo.max])          ) {            const pos1 = new BABYLON.Vector3(              this.area.minX + j * itemLength + itemLength / 2 + spacingOffset,              0,              position - g_xtrackFixedDim / 2 - g_liftFixedDim / 2            );            this._showLiftSelectors(pos1, this.activedXtrackIds[i], -1, j);          }          if (            Math.abs(max[1] - position) >            2 *              (g_railOutside +                g_spacingBPallets[g_palletInfo.max] +                g_loadPalletOverhang +                g_PalletW[g_palletInfo.max])          ) {            const pos2 = new BABYLON.Vector3(              this.area.minX + j * itemLength + itemLength / 2 + spacingOffset,              0,              position + g_xtrackFixedDim / 2 + g_liftFixedDim / 2            );            this._showLiftSelectors(pos2, this.activedXtrackIds[i], 1, j);          }        } else {          const spacingRow = this.activedSpacing.indexOf(j - 1);          if (spacingRow > -1)            spacingOffset = (spacingRow + 1) * this.spacingBetweenRows;          if (            Math.abs(max[0] - position) >            2 *              (g_railOutside +                g_spacingBPallets[g_palletInfo.max] +                g_loadPalletOverhang +                g_PalletW[g_palletInfo.max])          ) {            const pos1 = new BABYLON.Vector3(              position - g_xtrackFixedDim / 2 - g_liftFixedDim / 2,              0,              this.area.minZ + j * itemLength + itemLength / 2 + spacingOffset            );            this._showLiftSelectors(pos1, this.activedXtrackIds[i], -1, j);          }          if (            Math.abs(max[1] - position) >            2 *              (g_railOutside +                g_spacingBPallets[g_palletInfo.max] +                g_loadPalletOverhang +                g_PalletW[g_palletInfo.max])          ) {            const pos2 = new BABYLON.Vector3(              position + g_xtrackFixedDim / 2 + g_liftFixedDim / 2,              0,              this.area.minZ + j * itemLength + itemLength / 2 + spacingOffset            );            this._showLiftSelectors(pos2, this.activedXtrackIds[i], 1, j);          }        }      }    }  }  // on click selector on scene - enable/disable lift  updateLiftPlacementBySelector(selector) {    if (this.property["lift"].selectors.includes(selector)) {      let liftInfoIndex = -1;      for (let i = 0; i < this.activedLiftInfos.length; i++) {        if (          selector.length === this.activedLiftInfos[i].length &&          selector.bottomOrTop === this.activedLiftInfos[i].bottomOrTop &&          selector.row === this.activedLiftInfos[i].row &&          selector.index === this.activedLiftInfos[i].index        ) {          selector.selected = true;          liftInfoIndex = i;          break;        }      }      selector.selected = !selector.selected;      if (selector.selected) {        selector.material = matManager.matActiveSelector;        //Store lift info        const liftInfo = {          length: selector.length,          bottomOrTop: selector.bottomOrTop,          index: selector.index,          row: selector.row,          preloading: false,        };        this.activedLiftInfos.push(liftInfo);        this._addLift(liftInfo);      } else {        selector.material = matManager.matSelector;        // remove connected chain conveyor        const conveyors = this.activedChainConveyor.filter(          (e) =>            e.length === this.activedLiftInfos[liftInfoIndex].length &&            e.bottomOrTop === this.activedLiftInfos[liftInfoIndex].bottomOrTop        );        if (conveyors.length > 0) {          const conveyorIndex = this.activedChainConveyor.indexOf(conveyors[0]);          this.chainConveyors[conveyorIndex].dispose();          this.chainConveyors.splice(conveyorIndex, 1);          this.activedChainConveyor.splice(conveyorIndex, 1);        }        this._removeLift(this.activedLiftInfos[liftInfoIndex]);        this.activedLiftInfos.splice(liftInfoIndex, 1);      }      if (this.calculatedLiftsNo <= this.activedLiftInfos.length) {        const diff = this.activedLiftInfos.length - this.calculatedLiftsNo;        if (this.extra.lift === 1 && diff === 0) {          Utils.logg("额外垂直运输工具已移除", "提示");        }        if (this.extra.lift === 0 && diff === 1) {          Utils.logg("添加了额外的垂直运输工具", "提示");        }        this.extra.lift = diff;        updateLiftAmount(this.calculatedLiftsNo, this.extra.lift);      }      this.previewProperty("lift");    }  }  // on update icube, if there are lifts, show them  updateLiftPlacement() {    for (let i = this.activedLiftInfos.length - 1; i >= 0; i--) {      if (!this._addLift(this.activedLiftInfos[i]))        this.activedLiftInfos.splice(i, 1);    }    if (this.calculatedLiftsNo <= this.activedLiftInfos.length) {      const diff = this.activedLiftInfos.length - this.calculatedLiftsNo;      this.extra.lift = diff;      updateLiftAmount(this.calculatedLiftsNo, this.extra.lift);    }  }  // create the selector for each lift  _showLiftSelectors(position, length, bottomOrTop, row, index = -1) {    const selector = this.addSelector("lift");    selector.scaling = new BABYLON.Vector3(1.2, 0.2, 1.6);    selector.selected =      this.activedLiftInfos.filter(        (e) =>          e.length === length &&          e.bottomOrTop === bottomOrTop &&          e.row === row &&          e.index === index      ).length > 0        ? true        : false;    selector.material = selector.selected      ? matManager.matActiveSelector      : matManager.matSelector;    selector.position = position;    selector.index = index;    selector.length = length;    selector.bottomOrTop = bottomOrTop;    selector.row = row;    // if selectors overlap each other    let intersect = false;    for (let i = 0; i < this.property["lift"].selectors.length; i++) {      if (this.isHorizontal) {        if (          this.property["lift"].selectors[i].material ===            matManager.matActiveSelector &&          this.property["lift"].selectors[i].position.x === selector.position.x        ) {          const min = Math.min(            this.property["lift"].selectors[i].position.z,            selector.position.z          );          const max = Math.max(            this.property["lift"].selectors[i].position.z,            selector.position.z          );          if (max - min < g_liftFixedDim) {            intersect = true;            break;          }        }      } else {        if (          this.property["lift"].selectors[i].material ===            matManager.matActiveSelector &&          this.property["lift"].selectors[i].position.z === selector.position.z        ) {          const min = Math.min(            this.property["lift"].selectors[i].position.x,            selector.position.x          );          const max = Math.max(            this.property["lift"].selectors[i].position.x,            selector.position.x          );          if (max - min < g_liftFixedDim) {            intersect = true;            break;          }        }      }    }    if (intersect) {      selector.dispose();      return;    }    for (let i = this.property["lift"].selectors.length - 1; i >= 0; i--) {      if (this.isHorizontal) {        if (          selector.material === matManager.matActiveSelector &&          this.property["lift"].selectors[i].position.x === selector.position.x        ) {          const min = Math.min(            this.property["lift"].selectors[i].position.z,            selector.position.z          );          const max = Math.max(            this.property["lift"].selectors[i].position.z,            selector.position.z          );          if (max - min < g_liftFixedDim) {            this.property["lift"].selectors[i].dispose();            this.property["lift"].selectors.splice(i, 1);            break;          }        }      } else {        if (          selector.material === matManager.matActiveSelector &&          this.property["lift"].selectors[i].position.z === selector.position.z        ) {          const min = Math.min(            this.property["lift"].selectors[i].position.x,            selector.position.x          );          const max = Math.max(            this.property["lift"].selectors[i].position.x,            selector.position.x          );          if (max - min < g_liftFixedDim) {            this.property["lift"].selectors[i].dispose();            this.property["lift"].selectors.splice(i, 1);            break;          }        }      }    }    this.property["lift"].selectors.push(selector);  }  // add lift onclick or one by one on update/load  _addLift(liftInfo) {    if (liftInfo.row > (this.isHorizontal ? this.maxCol : this.maxRow) - 1)      return false;    const itemLength =      2 * this.palletOverhang +      2 * this.loadPalletOverhang +      g_palletInfo.length +      g_rackingPole;    let posx, posz;    const max = [      this.isHorizontal ? this.area.minZ : this.area.minX,      this.isHorizontal ? this.area.maxZ : this.area.maxX,    ];    const position =      max[this.isHorizontal ? 1 : 0] +      (this.isHorizontal ? -1 : 1) * liftInfo.length;    let part = [];    this.transform[3].data.forEach((elem, index) => {      if (elem[this.isHorizontal ? 1 : 0] === liftInfo.row) {        part.push(this.transform[3].position[index]);      }    });    if (this.isHorizontal) {      posx =        part.length > 0          ? part[0][0]          : this.area.minX + liftInfo.row * itemLength + itemLength / 2;      posz =        position +        liftInfo.bottomOrTop *          ((liftInfo.index === -1            ? g_xtrackFixedDim / 2            : g_palletInfo.racking / 2) +            g_liftFixedDim / 2);    } else {      posx =        position +        liftInfo.bottomOrTop *          ((liftInfo.index === -1            ? g_xtrackFixedDim / 2            : g_palletInfo.racking / 2) +            g_liftFixedDim / 2);      posz =        part.length > 0          ? part[0][2]          : this.area.minZ + liftInfo.row * itemLength + itemLength / 2;    }    if (!posx || !posz) return false;    const lift = new Lift(this, liftInfo, _round(posx, 3), _round(posz, 3));    this.lifts.push(lift);    return true;  }  // remove clicked lift, by row and col  _removeLift(liftInfo) {    let idx = -1;    for (let i = 0; i < this.lifts.length; i++) {      if (        this.lifts[i].length === liftInfo.length &&        this.lifts[i].length === liftInfo.length &&        this.lifts[i].row === liftInfo.row &&        this.lifts[i].index === liftInfo.index      ) {        this.lifts[i].remove();        idx = i;        break;      }    }    if (idx >= 0) this.lifts.splice(idx, 1);  }  //--------------------------------------------------------------------------------------------------------------------  //---------End Lift---------//  //--------------------------------------------------------------------------------------------------------------------  //--------------------------------------------------------------------------------------------------------------------  //---------Start Pallet---------//  //--------------------------------------------------------------------------------------------------------------------  // on change pallet type or update icube or add/remove xtracks  updatePallet(palletType = null) {    if (palletType !== null) {      this.palletType = palletType;    }    this.removeAllPallets();    this.addPallets();    palletsNoJS();  }  // add all the pallets - on update pallet  addPallets() {    if (!this.transform[3]) return;    let row0 = 0;    let rowN = 0;    for (let i = 0; i < this.transform[3].data.length; i++) {      if (        this.transform[3].data[i][this.isHorizontal ? 1 : 0] === 0 &&        this.transform[3].data[i][2] === 0      )        row0++;      if (        this.transform[3].data[i][this.isHorizontal ? 1 : 0] ===          (this.isHorizontal ? this.maxCol : this.maxRow) - 1 &&        this.transform[3].data[i][2] === 0      )        rowN++;    }    let atHeight = -1;    for (let i = this.rackingHighLevel - 1; i >= 0; i--) {      for (let j = 0; j < this.activedPassthrough.length; j++) {        const col =          row0 >= rowN            ? 0            : (this.isHorizontal ? this.maxCol : this.maxRow) - 1;        if (          this.activedPassthrough[j][1].includes(col) &&          !this.activedPassthrough[j][2].includes(i)        ) {          atHeight = i;          break;        }      }      if (atHeight !== -1) break;    }    if (atHeight === -1) atHeight = this.rackingHighLevel - 1;    let startAt = 0;    let palletTransforms = [];    for (let j = 0; j < g_palletInfo.order.length; j++) {      let lifts = this.activedLiftInfos.filter((e) => e.row == startAt);      while (lifts.length != 0) {        startAt += 1;        lifts = this.activedLiftInfos.filter((e) => e.row == startAt);      }      const store = this.stores.filter(        (e) => e.height === atHeight && e.row === startAt      );      startAt += 1;      if (store.length === 0) break;      palletTransforms = palletTransforms.concat(        this.renderPallet(store[0], g_palletInfo.order[j], true)      );    }    startAt = (this.isHorizontal ? this.maxCol : this.maxRow) - 1;    if (row0 !== rowN && this.drawMode === sceneMode.draw) {      for (let j = 0; j < g_palletInfo.order.length; j++) {        let lifts = this.activedLiftInfos.filter((e) => e.row == startAt);        while (lifts.length != 0) {          startAt -= 1;          lifts = this.activedLiftInfos.filter((e) => e.row == startAt);        }        const store = this.stores.filter(          (e) => e.height === atHeight && e.row === startAt        );        startAt -= 1;        if (store.length === 0) break;        palletTransforms = palletTransforms.concat(          this.renderPallet(store[0], g_palletInfo.order[j], true)        );      }    }    this.SPSPalletLabels = _generateLabels(      palletTransforms,      "",      true,      Math.PI / 2,      this.isHorizontal ? 0 : Math.PI / 2    );  }  renderPallet(store, type, returnData = false) {    let data = [];    const palletInfo = this.palletAtLevel.filter(      (e) => e.idx === store.height + 1    );    for (let i = 0; i < store.positions.length; i++) {      const steps = store.positions[i][type];      for (let k = 0; k < steps.length; k++) {        const correctPos = new BABYLON.Vector3(          steps[k][0],          this.getHeightAtLevel(store.height),          steps[k][2]        );        let pallet = new Pallet(          type,          palletInfo.length > 0            ? parseFloat(palletInfo[0].height)            : this.palletHeight        );        pallet.props.push(store.row);        pallet.setPosition(correctPos);        pallet.setRotation(          new BABYLON.Vector3(0, this.isHorizontal ? 0 : -Math.PI / 2, 0)        );        this.pallets.push(pallet);        data.push([          correctPos.x,          correctPos.y + (pallet.baseHeight + pallet.height + 0.01),          correctPos.z,          parseInt(k + 1),        ]);      }    }    if (returnData) return data;  }  // remove all the pallets items - on update pallet or delete Icube  removeAllPallets() {    this.emptyProperty("pallets", "remove");    // remove the sps labels from scene    if (this.SPSPalletLabels) {      this.SPSPalletLabels.mesh.dispose(true, true);      this.SPSPalletLabels.dispose();      this.SPSPalletLabels = null;    }  }  //--------------------------------------------------------------------------------------------------------------------  //---------End Pallet---------//  //--------------------------------------------------------------------------------------------------------------------  //--------------------------------------------------------------------------------------------------------------------  //---------Start Carrier---------//  //--------------------------------------------------------------------------------------------------------------------  // on change number of carriers or update icube  updateCarrier(extra = -1) {    if (extra === -1) {      if (this.activedCarrierInfos.length > this.calculatedCarriersNo) {        this.extra.carrier =          this.activedCarrierInfos.length - this.calculatedCarriersNo;      }    } else {      this.extra.carrier = extra;    }    updateCarrierAmount(this.calculatedCarriersNo, this.extra.carrier);    const carriers = this.calculatedCarriersNo + this.extra.carrier;    this.removeAllCarriers();    this.add3DCarrier(carriers);    renderScene();  }  // add all the carriers - on update carrier  add3DCarrier(carriersLength) {    if (!this.transform[3]) return;    //Add 3D-Carrier    let rails = [];    for (      let c = (this.isHorizontal ? this.maxCol : this.maxRow) - 1;      c >= 0;      c--    ) {      for (let h = 0; h < this.rackingHighLevel; h++) {        const data = this.transform[3].data.filter(          (e) =>            e[this.isHorizontal ? 0 : 1] === 0 &&            e[this.isHorizontal ? 1 : 0] === c &&            e[2] === h        );        if (data.length > 0) {          const indexOf = this.transform[3].data.indexOf(data[0]);          if (            indexOf !== -1 &&            this.isInsideLift(              this.transform[3].position[indexOf][this.isHorizontal ? 2 : 0] +                g_liftFixedDim / 2,              this.checkLiftBooundaries(c)            )          )            continue;          if (rails.length < carriersLength) rails.push(data[0]);          else break;        }      }      if (rails.length === carriersLength) break;    }    for (let i = 0; i < rails.length; i++) {      const carrier = new Carrier(this, rails[i]);      this.activedCarrierInfos.push(        i < this.calculatedCarriersNo ? true : false      );      this.carriers.push(carrier);    }  }  // remove all the carriers items - on update carrier or delete Icube  removeAllCarriers() {    this.emptyProperty("carriers", "remove");    this.activedCarrierInfos = [];  }  //--------------------------------------------------------------------------------------------------------------------  //---------End Carrier---------//  //--------------------------------------------------------------------------------------------------------------------  //--------------------------------------------------------------------------------------------------------------------  //---------Start 2D/3D Stuff---------//  //--------------------------------------------------------------------------------------------------------------------  // remove icube lines - on remove Icube  removeAllBaseLines() {    this.baseLines.forEach(function (baseline) {      baseline.line.dispose();      baseline.dimension.dispose();    });  }  // show 2d lines - toggle 2d/3d view  set2D() {    this.baseLines.forEach(function (line) {      line.set2D();    });    this.floor.isVisible = true;  }  // hide 2d lines - toggle 2d/3d view  set3D() {    this.baseLines.forEach(function (line) {      line.set3D();    });    this.floor.isVisible = false;  }  // on update icube  updateFloor() {    this.removeFloor();    if (this.floorPoints.length !== 0) {      this.floor = new BABYLON.PolygonMeshBuilder(        "icubeFloor",        this.floorPoints,        scene      ).build(true);      this.floor.isPickable = false;      this.floor.position.y = 0.25;      this.floor.material = this.isSelect        ? matManager.matIcubeFloorSelect        : matManager.matIcubeFloor;    }  }  // on update icube floor or delete icube  removeFloor() {    if (this.floor) {      this.floor.dispose();      this.floor = null;    }  }  //--------------------------------------------------------------------------------------------------------------------  //---------End 2D/3D Stuff---------//  //--------------------------------------------------------------------------------------------------------------------  //--------------------------------------------------------------------------------------------------------------------  //---------Start Connections---------//  //--------------------------------------------------------------------------------------------------------------------  // show possible position for conection selectors  previewConnectionSite(prop) {    this.finishToSetProperty(prop, true);    const validIcube = getValidIcubeToConect();    for (let i = 0; i < validIcube.length; i++) {      let pos = 0;      let direction = 0;      if (this.isHorizontal) {        if (this.area.minX < validIcube[i].area.minX) {          pos = (validIcube[i].area.minX + this.area.maxX) / 2;          direction = 1;        } else {          pos = (this.area.minX + validIcube[i].area.maxX) / 2;          direction = -1;        }      } else {        if (this.area.minZ < validIcube[i].area.minZ) {          pos = (validIcube[i].area.minZ + this.area.maxZ) / 2;          direction = 1;        } else {          pos = (this.area.minZ + validIcube[i].area.maxZ) / 2;          direction = -1;        }      }      const icubeId = validIcube[i].id.split("-");      const max = [        this.isHorizontal ? this.area.minZ : this.area.minX,        this.isHorizontal ? this.area.maxZ : this.area.maxX,      ];      for (let h = 0; h <= this.rackingHighLevel; h++) {        for (let j = 0; j <= this.activedXtrackIds.length; j++) {          const selector = this.addSelector(prop);          selector.scaling = new BABYLON.Vector3(1, 0.2, 1);          selector.index = [this.activedXtrackIds[j], h, icubeId[0], direction];          selector.selected = this.activedConnections.some((ele) => {            return JSON.stringify(ele) === JSON.stringify(selector.index);          });          selector.material = selector.selected            ? matManager.matActiveSelector            : matManager.matSelector;          if (!this.isHorizontal) {            selector.position = new BABYLON.Vector3(              max[0] + this.activedXtrackIds[j],              this.getHeightAtLevel(h) + 0.012,              pos            );          } else {            selector.position = new BABYLON.Vector3(              pos,              this.getHeightAtLevel(h) + 0.012,              max[1] - this.activedXtrackIds[j]            );          }          if (h === this.rackingHighLevel) {            selector.spec = true;            selector.material = matManager.allRowsMat;          }          this.property["connection"].selectors.push(selector);        }      }    }  }  // on click selector on scene - enable/disable connection  updateConnectionPlacementBySelector(selector) {    if (this.property["connection"].selectors.includes(selector)) {      selector.selected = !selector.selected;      const index = selector.index;      if (selector.selected) {        if (selector.spec) {          const selectors = this.property["connection"].selectors.filter(            (e) =>              (e.index[0] === index[0]) & (e.index[2] === index[2]) & !e.spec          );          for (let i = 0; i < selectors.length; i++) {            selectors[i].material = matManager.matActiveSelector;            selectors[i].selected = true;            const idx = this.activedConnections.some((ele) => {              return JSON.stringify(ele) === JSON.stringify(selectors[i].index);            });            if (!idx) {              this.activedConnections.push(selectors[i].index);            }          }        } else {          const idx = this.activedConnections.some((ele) => {            return JSON.stringify(ele) === JSON.stringify(index);          });          if (!idx) {            this.activedConnections.push(index);          }        }        selector.material = matManager.matActiveSelector;      } else {        if (selector.spec) {          const selectors = this.property["connection"].selectors.filter(            (e) =>              (e.index[0] === index[0]) & (e.index[2] === index[2]) & !e.spec          );          for (let i = 0; i < selectors.length; i++) {            selectors[i].material = matManager.matSelector;            selectors[i].selected = false;            for (let j = 0; j < this.activedConnections.length; j++) {              if (                JSON.stringify(this.activedConnections[j]) ===                JSON.stringify(selectors[i].index)              ) {                this.activedConnections.splice(j, 1);                break;              }            }          }        } else {          for (let i = 0; i < this.activedConnections.length; i++) {            if (              JSON.stringify(this.activedConnections[i]) ===              JSON.stringify(index)            ) {              this.activedConnections.splice(i, 1);              break;            }          }        }        selector.material = selector.spec          ? matManager.allRowsMat          : matManager.matSelector;      }      this.emptyProperty("connections");      this.updateConnectionPlacement();    }  }  // on update icube, if there are connections, show them  updateConnectionPlacement() {    if (!this.transform[6]) return;    for (let i = this.activedConnections.length - 1; i >= 0; i--) {      const conn = this.activedConnections[i];      const validIcube = icubes.filter((e) => e.id.indexOf(conn[2]) !== -1);      if (validIcube.length === 0) {        this.activedConnections.splice(i, 1);        continue;      }      if (!validIcube[0].activedXtrackIds.includes(conn[0])) {        this.activedConnections.splice(i, 1);        continue;      }      let thisData = null;      let thatData = null;      const that = validIcube[0];      // this icube last row, valid icube first row      if (conn[3] === 1) {        const maxRow = this.transform[6].data.filter(          (e) => e[3] === conn[0] && e[2] === conn[1]        );        const minRow = that.transform[6].data.filter(          (e) => e[3] === conn[0] && e[2] === conn[1]        );        if (this.isHorizontal) {          for (let j = 0; j < this.transform[6].data.length; j++) {            if (              this.transform[6].data[j][3] === conn[0] &&              this.transform[6].data[j][2] === conn[1] &&              this.transform[6].data[j][1] === maxRow[maxRow.length - 1][1]            ) {              thisData = [...this.transform[6].position[j]];              break;            }          }          for (let j = 0; j < that.transform[6].data.length; j++) {            if (              that.transform[6].data[j][3] === conn[0] &&              that.transform[6].data[j][2] === conn[1] &&              that.transform[6].data[j][1] === minRow[0][1]            ) {              thatData = [...that.transform[6].position[j]];              break;            }          }        } else {          for (let j = 0; j < this.transform[6].data.length; j++) {            if (              this.transform[6].data[j][3] === conn[0] &&              this.transform[6].data[j][2] === conn[1] &&              this.transform[6].data[j][0] === maxRow[maxRow.length - 1][0]            ) {              thisData = [...this.transform[6].position[j]];              break;            }          }          for (let j = 0; j < that.transform[6].data.length; j++) {            if (              that.transform[6].data[j][3] === conn[0] &&              that.transform[6].data[j][2] === conn[1] &&              that.transform[6].data[j][0] === minRow[0][0]            ) {              thatData = [...that.transform[6].position[j]];              break;            }          }        }      } else {        const minRow = this.transform[6].data.filter(          (e) => e[3] === conn[0] && e[2] === conn[1]        );        const maxRow = that.transform[6].data.filter(          (e) => e[3] === conn[0] && e[2] === conn[1]        );        if (this.isHorizontal) {          for (let j = 0; j < this.transform[6].data.length; j++) {            if (              this.transform[6].data[j][3] === conn[0] &&              this.transform[6].data[j][2] === conn[1] &&              this.transform[6].data[j][1] === minRow[0][1]            ) {              thisData = [...this.transform[6].position[j]];              break;            }          }          for (let j = 0; j < that.transform[6].data.length; j++) {            if (              that.transform[6].data[j][3] === conn[0] &&              that.transform[6].data[j][2] === conn[1] &&              that.transform[6].data[j][1] === maxRow[maxRow.length - 1][1]            ) {              thatData = [...that.transform[6].position[j]];              break;            }          }        } else {          for (let j = 0; j < this.transform[6].data.length; j++) {            if (              this.transform[6].data[j][3] === conn[0] &&              this.transform[6].data[j][2] === conn[1] &&              this.transform[6].data[j][0] === minRow[0][0]            ) {              thisData = [...this.transform[6].position[j]];              break;            }          }          for (let j = 0; j < that.transform[6].data.length; j++) {            if (              that.transform[6].data[j][3] === conn[0] &&              that.transform[6].data[j][2] === conn[1] &&              that.transform[6].data[j][0] === maxRow[maxRow.length - 1][0]            ) {              thatData = [...that.transform[6].position[j]];              break;            }          }        }      }      //console.log(conn, thisData, thatData)      if (thisData && thatData) {        const itemLength = 0.53;        const scale = BABYLON.Vector3.Distance(          new BABYLON.Vector3(thisData[0], thisData[1], thisData[2]),          new BABYLON.Vector3(thatData[0], thatData[1], thatData[2])        );        let conectors = [];        for (let i = 0; i < parseInt(scale / itemLength) - 1; i++) {          const connector = itemInfo[            ITEMTYPE.Auto.XtrackExt          ].originMesh.createInstance("icubeConnector" + "Instance");          connector.origin = itemInfo[ITEMTYPE.Auto.XtrackExt].originMesh;          connector.name = itemInfo[ITEMTYPE.Auto.XtrackExt].name;          connector.type = itemInfo[ITEMTYPE.Auto.XtrackExt].type;          connector.direction = itemInfo[ITEMTYPE.Auto.XtrackExt].direction;          connector.scaling.z = g_xtrackFixedDim === 1.35 ? 1 : 1.15;          connector.isPickable = false;          connector.setEnabled(true);          if (!this.isHorizontal) {            connector.position = new BABYLON.Vector3(              thisData[0],              thisData[1],              Math.min(thisData[2], thatData[2]) + (i + 1) * itemLength            );            connector.rotation.y = Math.PI / 2;          } else {            connector.position = new BABYLON.Vector3(              Math.min(thisData[0], thatData[0]) + (i + 1) * itemLength,              thisData[1],              thisData[2]            );          }          conectors.push(connector);        }        this.connections.push(conectors);      }    }  }  //--------------------------------------------------------------------------------------------------------------------  //---------End Connections---------//  //--------------------------------------------------------------------------------------------------------------------  //--------------------------------------------------------------------------------------------------------------------  //---------Start ChargingStation---------//  //--------------------------------------------------------------------------------------------------------------------  // show possible position for charger selectors  previewChargerSite(prop) {    this.finishToSetProperty(prop, true);    for (let i = 0; i < this.transform[5].data.length; i++) {      let chargerPos;      if (this.isHorizontal)        chargerPos = this.transform[5].rotation[i][1] !== 0 ? "top" : "bottom";      else        chargerPos =          this.transform[5].rotation[i][1] !== Math.PI / 2 ? "right" : "left";      let pos = BABYLON.Vector3.Zero();      switch (chargerPos) {        case "bottom":          pos = new BABYLON.Vector3(            this.transform[5].position[i][0],            this.transform[5].position[i][1],            this.transform[5].position[i][2] - g_width / 2          );          break;        case "top":          pos = new BABYLON.Vector3(            this.transform[5].position[i][0],            this.transform[5].position[i][1],            this.transform[5].position[i][2] + g_width / 2          );          break;        case "left":          pos = new BABYLON.Vector3(            this.transform[5].position[i][0] - g_width / 2,            this.transform[5].position[i][1],            this.transform[5].position[i][2]          );          break;        case "right":          pos = new BABYLON.Vector3(            this.transform[5].position[i][0] + g_width / 2,            this.transform[5].position[i][1],            this.transform[5].position[i][2]          );          break;        default:          break;      }      const selector = this.addSelector(prop);      selector.scaling = new BABYLON.Vector3(0.9, 0.2, 0.5);      selector.selected =        this.activedChargers.filter(          (e) =>            e.col === this.transform[5].data[i][1] &&            e.row === this.transform[5].data[i][0] &&            e.height === this.transform[5].data[i][2] &&            e.chargerPos === chargerPos        ).length > 0          ? true          : false;      selector.material = selector.selected        ? matManager.matActiveSelector        : matManager.matSelector;      selector.position = pos;      selector.chargerPos = chargerPos;      selector.row = this.transform[5].data[i][0];      selector.col = this.transform[5].data[i][1];      selector.height = this.transform[5].data[i][2];      this.property["charger"].selectors.push(selector);    }  }  // on click selector on scene - enable/disable charger  updateChargerPlacementBySelector(selector) {    if (this.property["charger"].selectors.includes(selector)) {      selector.selected = !selector.selected;      if (selector.selected) {        const totalChargers = this.calculatedCarriersNo + this.extra.carrier;        if (totalChargers === this.chargers.length) {          selector.selected = false;          Utils.logg("所有所需充电器均已放置", "提示");          return;        }        selector.material = matManager.matActiveSelector;        //Store charger info        const chargerInfo = {          col: selector.col,          row: selector.row,          height: selector.height,          chargerPos: selector.chargerPos,        };        //Add charger        this._addCharger(chargerInfo);        this.activedChargers.push(chargerInfo);      } else {        selector.material = matManager.matSelector;        //Remove charger        for (let i = 0; i < this.chargers.length; i++) {          if (            this.chargers[i].metadata.col === selector.col &&            this.chargers[i].metadata.row === selector.row &&            this.chargers[i].metadata.height === selector.height &&            this.chargers[i].metadata.chargerPos === selector.chargerPos          ) {            this.chargers[i].dispose();            this.chargers.splice(i, 1);            break;          }        }        for (let i = 0; i < this.activedChargers.length; i++) {          if (            selector.col === this.activedChargers[i].col &&            selector.row === this.activedChargers[i].row &&            this.activedChargers[i].height === selector.height &&            this.activedChargers[i].chargerPos === selector.chargerPos          ) {            this.activedChargers.splice(i, 1);            break;          }        }      }    }  }  // on update icube, if there are charger, show them  updateChargerPlacement() {    for (let i = this.activedChargers.length - 1; i >= 0; i--) {      if (!this._addCharger(this.activedChargers[i]))        this.activedChargers.splice(i, 1);    }  }  // add charger onclick or one by one on update/load  _addCharger(infoCharger) {    let initPosition = null;    let initRotation = null;    let position = [];    this.transform[5].data.forEach((elem, index) => {      if (        elem[2] === infoCharger.height &&        elem[1] === infoCharger.col &&        elem[0] === infoCharger.row      ) {        position = this.transform[5].position[index];      }    });    if (position.length === 0) return false;    initPosition = new BABYLON.Vector3(position[0], position[1], position[2]);    switch (infoCharger.chargerPos) {      case "bottom":        initPosition = new BABYLON.Vector3(          initPosition.x,          this.getHeightAtLevel(infoCharger.height),          initPosition.z - 0.035        );        initRotation = BABYLON.Vector3.Zero();        break;      case "top":        initPosition = new BABYLON.Vector3(          initPosition.x,          this.getHeightAtLevel(infoCharger.height),          initPosition.z + 0.035        );        initRotation = new BABYLON.Vector3(0, Math.PI, 0);        break;      case "left":        initPosition = new BABYLON.Vector3(          initPosition.x - 0.035,          this.getHeightAtLevel(infoCharger.height),          initPosition.z        );        initRotation = new BABYLON.Vector3(0, Math.PI / 2, 0);        break;      case "right":        initPosition = new BABYLON.Vector3(          initPosition.x + 0.035,          this.getHeightAtLevel(infoCharger.height),          initPosition.z        );        initRotation = new BABYLON.Vector3(0, -Math.PI / 2, 0);        break;      default:        break;    }    const inputCharger = otherItemInfo[      ITEMTYPE.Other.CarrierCharger    ].originMesh.createInstance("icubeCharger" + "Instance");    inputCharger.origin =      otherItemInfo[ITEMTYPE.Other.CarrierCharger].originMesh;    inputCharger.metadata = infoCharger;    inputCharger.isPickable = false;    inputCharger.setEnabled(true);    inputCharger.position = initPosition;    inputCharger.rotation = initRotation;    this.chargers.push(inputCharger);    return true;  }  //--------------------------------------------------------------------------------------------------------------------  //---------End ChargingStation---------//  //--------------------------------------------------------------------------------------------------------------------  //--------------------------------------------------------------------------------------------------------------------  //---------Start ChainConveyor---------//  //--------------------------------------------------------------------------------------------------------------------  // show possible position for chain conveyor selectors  previewChainConveyorSite(prop) {    this.finishToSetProperty(prop, true);    const positions = this.getChainCPosition();    if (positions.length === 0) {      Utils.logg("没有可用位置", "提示");      return;    }    for (let i = 0; i < positions.length; i++) {      const [position, scale] = this.calculateChainLimits(positions[i]);      if (position && scale) {        const selector = this.addSelector(prop);        selector.selected =          this.activedChainConveyor.filter(            (e) =>              e.length === positions[i].length &&              e.row === positions[i].row &&              e.bottomOrTop === positions[i].bottomOrTop          ).length > 0            ? true            : false;        selector.material = selector.selected          ? matManager.matActiveSelector          : matManager.matSelector;        selector.position = position;        selector.scaling.z = scale;        selector.row = positions[i].row;        selector.length = positions[i].length;        selector.bottomOrTop = positions[i].bottomOrTop;        selector.preloading = positions[i].preloading;        this.property["chainconveyor"].selectors.push(selector);      }    }  }  // calculate chainConveyor position & scale  calculateChainLimits(infoChainC) {    const max = [      this.isHorizontal ? this.area.minZ : this.area.minX,      this.isHorizontal ? this.area.maxZ : this.area.maxX,    ];    let p1 =      max[this.isHorizontal ? 1 : 0] +      (this.isHorizontal ? -1 : 1) *        (infoChainC.length -          (infoChainC.preloading === true ? infoChainC.bottomOrTop * 1.25 : 0));    p1 += infoChainC.bottomOrTop * (g_liftFixedDim + g_xtrackFixedDim / 2);    let limits = [];    this.transform[5].data.forEach((elem, index) => {      if (elem[this.isHorizontal ? 1 : 0] === infoChainC.row) {        limits.push(this.transform[5].position[index]);      }    });    let p2 = null;    for (let j = 0; j < limits.length; j++) {      if (this.isHorizontal) {        if (infoChainC.bottomOrTop === 1) {          if (limits[j][2] > p1) {            p2 = limits[j][2];          }        } else {          if (limits[j][2] < p1) {            p2 = limits[j][2];          }        }      } else {        if (infoChainC.bottomOrTop === 1) {          if (limits[j][0] > p1) {            p2 = limits[j][0];          }        } else {          if (limits[j][0] < p1) {            p2 = limits[j][0];          }        }      }    }    let position, scale;    if (p1 && p2) {      scale = Math.abs(p2 - p1);      if (this.isHorizontal) {        position = BABYLON.Vector3.Center(          new BABYLON.Vector3(limits[0][0], 0, p1),          new BABYLON.Vector3(limits[0][0], 0, p2)        );      } else {        position = BABYLON.Vector3.Center(          new BABYLON.Vector3(p1, 0, limits[0][2]),          new BABYLON.Vector3(p2, 0, limits[0][2])        );      }    }    return [position, scale];  }  getChainCPosition() {    const avLifts = this.lifts.filter((e) => e.index === -1);    if (avLifts.length === 0) return [];    let avLifts2 = [];    const minXtrack = Math.min(...this.activedXtrackIds);    const maxXtrack = Math.max(...this.activedXtrackIds);    for (let i = 0; i < avLifts.length; i++) {      const conv = this.activedLiftInfos.filter(        (e) =>          e.row === avLifts[i].row &&          e.length === avLifts[i].length &&          e.bottomOrTop === avLifts[i].bottomOrTop &&          e.preloading === true      );      if (conv.length > 0) {        if (this.isHorizontal) {          if (            avLifts[i].length - 4 < 0 ||            avLifts[i].length + 4 > this.area.maxZ - this.area.minZ          )            continue;        } else {          if (            avLifts[i].length - 4 < 0 ||            avLifts[i].length + 4 > this.area.minX - this.area.maxX          )            continue;        }      }      const prop = avLifts[i].length;      const prop2 = avLifts[i].row;      if (        prop === minXtrack &&        avLifts[i].bottomOrTop === (this.isHorizontal ? 1 : -1)      ) {        avLifts2.push({          row: avLifts[i].row,          length: avLifts[i].length,          bottomOrTop: avLifts[i].bottomOrTop,          preloading: avLifts[i].preloading,        });      } else {        if (          prop === maxXtrack &&          avLifts[i].bottomOrTop === (this.isHorizontal ? -1 : 1)        ) {          avLifts2.push({            row: avLifts[i].row,            length: avLifts[i].length,            bottomOrTop: avLifts[i].bottomOrTop,            preloading: avLifts[i].preloading,          });        } else {          const xtracks = this.transform[6].data.filter(            (e) => e[this.isHorizontal ? 1 : 0] === prop2          );          if (xtracks.length > 0) {            for (let j = 0; j < xtracks.length; j++) {              if (avLifts[i].bottomOrTop === 1) {                const bigger = xtracks.filter((e) => e[3] < avLifts[i].length);                if (bigger.length > 0) continue;                avLifts2.push({                  row: avLifts[i].row,                  length: avLifts[i].length,                  bottomOrTop: avLifts[i].bottomOrTop,                  preloading: avLifts[i].preloading,                });                break;              } else {                const bigger = xtracks.filter((e) => e[3] > avLifts[i].length);                if (bigger.length > 0) continue;                avLifts2.push({                  row: avLifts[i].row,                  length: avLifts[i].length,                  bottomOrTop: avLifts[i].bottomOrTop,                  preloading: avLifts[i].preloading,                });                break;              }            }          } else {            avLifts2.push({              row: avLifts[i].row,              length: avLifts[i].length,              bottomOrTop: avLifts[i].bottomOrTop,              preloading: avLifts[i].preloading,            });          }        }      }    }    return avLifts2;  }  // on click selector on scene - enable/disable chain conveyor  updateChainConveyorPlacementBySelector(selector) {    if (this.property["chainconveyor"].selectors.includes(selector)) {      let chainCInfoIndex = -1;      for (let i = 0; i < this.activedChainConveyor.length; i++) {        if (          selector.bottomOrTop === this.activedChainConveyor[i].bottomOrTop &&          selector.row === this.activedChainConveyor[i].row &&          selector.length === this.activedChainConveyor[i].length        ) {          selector.selected = true;          chainCInfoIndex = i;          break;        }      }      selector.selected = !selector.selected;      if (selector.selected) {        selector.material = matManager.matActiveSelector;        //Store chain conveyor info        const chainCInfo = {          row: selector.row,          length: selector.length,          bottomOrTop: selector.bottomOrTop,          preloading: selector.preloading,        };        //Add chain conveyor        this._addChainConveyor(chainCInfo);        this.activedChainConveyor.push(chainCInfo);      } else {        selector.material = matManager.matSelector;        //Remove chain conveyor        if (this.chainConveyors[chainCInfoIndex]) {          this.chainConveyors[chainCInfoIndex].dispose();          this.chainConveyors.splice(chainCInfoIndex, 1);          this.activedChainConveyor.splice(chainCInfoIndex, 1);        }      }    }  }  // on update icube, if there are chain conveyor, show them  updateChainConveyorPlacement() {    for (let i = this.activedChainConveyor.length - 1; i >= 0; i--) {      if (!this._addChainConveyor(this.activedChainConveyor[i]))        this.activedChainConveyor.splice(i, 1);    }  }  // add chain conveyor onclick or one by one on update/load  _addChainConveyor(infoChainC) {    const [position, scale] = this.calculateChainLimits(infoChainC);    if (position && scale) {      const inputConveyor =        otherItemInfo[ITEMTYPE.Other.ChainConveyor].originMesh.clone(          "icubeChainConveyor"        );      inputConveyor.isPickable = false;      inputConveyor.setEnabled(true);      const kids = inputConveyor.getChildren();      for (let k = 0; k < kids.length; k++) {        kids[k].setEnabled(true);        if (k === 0) {          kids[k].scaling.z = scale * 0.9;        }      }      inputConveyor.position = position;      inputConveyor.rotation.y = this.isHorizontal ? 0 : Math.PI / 2;      this.chainConveyors.push(inputConveyor);      return true;    }    return false;  }  //--------------------------------------------------------------------------------------------------------------------  //---------End ChainConveyor---------//  //--------------------------------------------------------------------------------------------------------------------  //--------------------------------------------------------------------------------------------------------------------  //---------Start LiftPreloading---------//  //--------------------------------------------------------------------------------------------------------------------  // show possible position for lift preloading selectors  previewLiftPreloadingSite(prop) {    this.finishToSetProperty(prop, true);    const positions = this.getLiftPreloadingPosition();    if (positions.length === 0) {      if (this.activedLiftInfos.length === 0) {        Utils.logg("没有可用位置", "提示");      }      return;    }    for (let i = 0; i < positions.length; i++) {      const selector = this.addSelector(prop);      selector.scaling = new BABYLON.Vector3(0.9, 0.2, 0.5);      selector.selected =        this.activedLiftInfos.filter(          (e) =>            e.col === positions[i].col &&            e.row === positions[i].row &&            e.hasOwnProperty("preloading") &&            e.preloading === true        ).length > 0          ? true          : false;      selector.material = selector.selected        ? matManager.matActiveSelector        : matManager.matSelector;      selector.position = positions[i].node.position.clone();      if (this.isHorizontal)        selector.position.z -= (positions[i].bottomOrTop * g_width) / 2;      else selector.position.x -= (positions[i].bottomOrTop * g_width) / 2;      selector.row = positions[i].row;      selector.length = positions[i].length;      selector.bottomOrTop = positions[i].bottomOrTop;      this.property["liftpreloading"].selectors.push(selector);    }  }  getLiftPreloadingPosition() {    const positions = this.lifts.filter((e) => e.index === -1);    if (positions.length === 0) return [];    for (let i = positions.length - 1; i >= 0; i--) {      const prop = this.isHorizontal ? positions[i].row : positions[i].col;      // between xtracks      if (        this.activedXtrackIds.includes(prop) &&        this.activedXtrackIds.includes(prop - 1)      ) {        positions.splice(i, 1);        continue;      }      // racking limits      if (        [0, this.isHorizontal ? this.maxRow - 2 : this.maxCol - 2].includes(          prop        )      ) {        if (prop === 0) {          if (this.isHorizontal) {            if (positions[i].posz - 2.5 * 0.75 < warehouse.minZ) {              positions.splice(i, 1);            }          } else {            if (positions[i].posx - 2.5 * 0.75 < warehouse.minX) {              positions.splice(i, 1);            }          }        } else {          if (this.isHorizontal) {            if (positions[i].posz + 2.5 * 0.75 > warehouse.maxZ) {              positions.splice(i, 1);            }          } else {            if (positions[i].posx + 2.5 * 0.75 > warehouse.maxX) {              positions.splice(i, 1);            }          }        }      }    }    // lift overlay    for (      let i = 0;      i < (this.isHorizontal ? this.maxRow - 2 : this.maxCol - 2);      i++    ) {      const lifts = positions        .filter((e) => (this.isHorizontal ? e.col : e.row) === i)        .sort((a, b) => {          return this.isHorizontal ? a.row - b.row : a.col - b.col;        });      if (lifts.length > 1) {        let closeLift = [];        for (let j = 0; j < lifts.length; j++) {          if (lifts[j + 1]) {            if (this.isHorizontal) {              if (lifts[j + 1].posz - lifts[j].posz < 2 * g_width) {                closeLift = [lifts[j], lifts[j + 1]];                break;              }            } else {              if (lifts[j + 1].posx - lifts[j].posx < 2 * g_width) {                closeLift = [lifts[j], lifts[j + 1]];                break;              }            }          }        }        if (closeLift.length > 0) {          const indexof0 = positions.indexOf(closeLift[0]);          const indexof1 = positions.indexOf(closeLift[1]);          positions.splice(Math.max(indexof0, indexof1), 1);          positions.splice(Math.min(indexof0, indexof1), 1);        }      }    }    // conveyor overlay    for (let i = 0; i < positions.length; i++) {      const conv = this.activedChainConveyor.filter(        (e) => e.row === positions[i].row && e.col === positions[i].col      );      if (conv.length > 0) {        if (this.isHorizontal) {          if (            positions[i].posz - 4 < warehouse.minZ ||            positions[i].posz + 4 > warehouse.maxZ          ) {            positions.splice(i, 1);          }        } else {          if (            positions[i].posx - 4 < warehouse.minX ||            positions[i].posx + 4 > warehouse.maxX          ) {            positions.splice(i, 1);          }        }      }    }    return positions;  }  // on click selector on scene - enable/disable lift preloading  updateLiftPreloadingPlacementBySelector(selector) {    if (this.property["liftpreloading"].selectors.includes(selector)) {      for (let i = 0; i < this.activedLiftInfos.length; i++) {        if (          selector.length === this.activedLiftInfos[i].length &&          selector.bottomOrTop === this.activedLiftInfos[i].bottomOrTop &&          selector.row === this.activedLiftInfos[i].row &&          this.activedLiftInfos[i].hasOwnProperty("preloading") &&          this.activedLiftInfos[i].preloading === true        ) {          selector.selected = true;          break;        }      }      const liftInfo = this.activedLiftInfos.filter(        (e) =>          e.length === selector.length &&          e.bottomOrTop === selector.bottomOrTop &&          e.row === selector.row &&          e.index === -1      );      const indexOf = this.activedLiftInfos.indexOf(liftInfo[0]);      const liftInfoA = this.lifts.filter(        (e) =>          e.length === selector.length &&          e.bottomOrTop === selector.bottomOrTop &&          e.row === selector.row &&          e.index === -1      );      const indexOfA = this.lifts.indexOf(liftInfoA[0]);      selector.selected = !selector.selected;      if (selector.selected) {        selector.material = matManager.matActiveSelector;        this.lifts[indexOfA].preloading = true;        this.lifts[indexOfA].addPreloading();        this.activedLiftInfos[indexOf].preloading = true;      } else {        selector.material = matManager.matSelector;        this.lifts[indexOfA].preloading = false;        this.lifts[indexOfA].removePreloading();        this.activedLiftInfos[indexOf].preloading = false;      }    }  }  //--------------------------------------------------------------------------------------------------------------------  //---------End LiftPreloading---------//  //--------------------------------------------------------------------------------------------------------------------  //--------------------------------------------------------------------------------------------------------------------  //---------Start SafetyFence---------//  //--------------------------------------------------------------------------------------------------------------------  // show possible position for safety fence selectors  previewSafetyFenceSite(prop) {    this.finishToSetProperty(prop, true);    const safetyFence = ["bottom", "top"];    const safetyFenceV = ["left", "right"];    for (let i = 0; i < safetyFence.length; i++) {      const selector = this.addSelector(prop);      selector.safetyFPos = this.isHorizontal        ? safetyFence[i]        : safetyFenceV[i];      selector.position = this.isHorizontal        ? new BABYLON.Vector3(            (this.area.maxX + this.area.minX) / 2,            0,            i === 0 ? this.area.minZ - 0.4 : this.area.maxZ + 0.4          )        : new BABYLON.Vector3(            i === 0 ? this.area.minX - 0.4 : this.area.maxX + 0.4,            0,            (this.area.maxZ + this.area.minZ) / 2          );      selector.scaling = new BABYLON.Vector3(        this.isHorizontal          ? this.area.maxX - this.area.minX          : this.area.maxZ - this.area.minZ,        0.2,        0.6      );      selector.selected =        this.activedSafetyFences.filter(          (e) =>            e.safetyFPos ===            (this.isHorizontal ? safetyFence[i] : safetyFenceV[i])        ).length > 0          ? true          : false;      selector.material = selector.selected        ? matManager.matActiveSelector        : matManager.matSelector;      this.property["safetyFence"].selectors.push(selector);    }  }  // on click selector on scene - enable/disable safetyFence  updateSafetyFencePlacementBySelector(selector) {    if (this.property["safetyFence"].selectors.includes(selector)) {      let safetyFenceInfoIndex = -1;      for (let i = 0; i < this.activedSafetyFences.length; i++) {        if (selector.safetyFPos === this.activedSafetyFences[i].safetyFPos) {          selector.selected = true;          safetyFenceInfoIndex = i;          break;        }      }      selector.selected = !selector.selected;      if (selector.selected) {        selector.material = matManager.matActiveSelector;        const ioPorts = this.activedIOPorts.filter(          (e) => e.portPosition === selector.safetyFPos        );        let doorsInfo = [];        ioPorts.forEach((ioPort) => {          doorsInfo.push({            col: ioPort.col,            row: ioPort.row,          });        });        //Store safetyFence info        const safetyFenceInfo = {          safetyFDoors: doorsInfo,          safetyFPos: selector.safetyFPos,        };        //Add safetyFence        this._addSafetyFence(safetyFenceInfo);        this.activedSafetyFences.push(safetyFenceInfo);      } else {        selector.material = matManager.matSelector;        //Remove safetyFence        let indexes = [];        this.safetyFences.forEach((item, index) => {          if (item.safetyFPos === selector.safetyFPos) {            item.dispose();            indexes.push(index);          }        });        for (let i = this.safetyFences.length; i >= 0; i--) {          if (indexes.includes(i)) this.safetyFences.splice(i, 1);        }        this.activedSafetyFences.splice(safetyFenceInfoIndex, 1);      }      this.updateSafetyFenceForPassTh();    }  }  // on update icube, if there are safetyFence, show it  updateSafetyFencePlacement() {    for (let i = this.activedSafetyFences.length - 1; i >= 0; i--) {      this._addSafetyFence(this.activedSafetyFences[i]);    }    this.updateSafetyFenceForPassTh();  }  // add safetyFence onclick or one by one on update/load  _addSafetyFence(infoSafetyFence) {    let rightArray = [];    let rightArray2 = [];    for (let i = 0; i < this.rackingHighLevel; i++) {      for (let j = 0; j < this.transform[5].data.length; j++) {        if (["bottom", "left"].includes(infoSafetyFence.safetyFPos)) {          if (            this.transform[5].rotation[j][1] ===            (this.isHorizontal ? 0 : Math.PI / 2)          ) {            rightArray.push(this.transform[5].position[j]);            rightArray2.push(this.transform[5].data[j]);          }        } else {          if (            this.transform[5].rotation[j][1] !==            (this.isHorizontal ? 0 : Math.PI / 2)          ) {            rightArray.push(this.transform[5].position[j]);            rightArray2.push(this.transform[5].data[j]);          }        }      }    }    const itemLength =      2 * this.palletOverhang +      2 * this.loadPalletOverhang +      g_palletInfo.length +      g_rackingPole;    for (let i = infoSafetyFence.safetyFDoors.length - 1; i >= 0; i--) {      if (this.isHorizontal) {        if (infoSafetyFence.safetyFDoors[i].col >= this.maxCol) {          infoSafetyFence.safetyFDoors.splice(i, 1);        }      } else {        if (infoSafetyFence.safetyFDoors[i].row >= this.maxRow) {          infoSafetyFence.safetyFDoors.splice(i, 1);        }      }    }    rightArray.forEach((item, index) => {      let safetyFenceInfo;      if (        infoSafetyFence.safetyFDoors.length !== 0 &&        rightArray2[index][2] === 0 &&        infoSafetyFence.safetyFDoors.filter(          (e) =>            e.col === rightArray2[index][1] && e.row === rightArray2[index][0]        ).length !== 0      ) {        safetyFenceInfo = itemInfo[ITEMTYPE.Auto.SafetyFenceWithD];      } else {        if (rightArray2[index][2] === 0)          safetyFenceInfo = itemInfo[ITEMTYPE.Auto.SafetyFenceWithoutD];        else safetyFenceInfo = itemInfo[ITEMTYPE.Auto.SafetyFenceForPallet];      }      const safetyFence = safetyFenceInfo.originMesh.createInstance(        "safetyFence" + "Instance"      );      safetyFence.origin = safetyFenceInfo.originMesh;      safetyFence.safetyFPos = infoSafetyFence.safetyFPos;      safetyFence.isPickable = false;      safetyFence.data = rightArray2[index];      safetyFence.setEnabled(true);      safetyFence.position = new BABYLON.Vector3(item[0], item[1], item[2]);      if (this.isHorizontal) {        safetyFence.position.z += ["bottom", "left"].includes(          infoSafetyFence.safetyFPos        )          ? -g_railOutside          : g_railOutside;      } else {        safetyFence.position.x += ["bottom", "left"].includes(          infoSafetyFence.safetyFPos        )          ? -g_railOutside          : g_railOutside;        safetyFence.rotation.y = Math.PI / 2;      }      if (!["bottom", "left"].includes(infoSafetyFence.safetyFPos))        safetyFence.rotation.y += Math.PI;      safetyFence.scaling.x = itemLength * 0.68;      let heightOffset = this.palletHeight;      if (this.palletHeight >= 1)        heightOffset = this.palletHeight - (this.palletHeight - 1) * 0.26;      else heightOffset = this.palletHeight + (1 - this.palletHeight) * 0.26;      safetyFence.scaling.y = heightOffset;      this.safetyFences.push(safetyFence);    });  }  // on add/remove passthrough  updateSafetyFenceForPassTh() {    for (let i = this.safetyFences.length - 1; i >= 0; i--) {      const palletInfo = this.palletAtLevel.filter(        (e) => e.idx === this.safetyFences[i].data[2] + 1      );      if (palletInfo.length > 0) {        let heightOffset = parseFloat(palletInfo[0].height);        if (parseFloat(palletInfo[0].height) >= 1)          heightOffset -= (parseFloat(palletInfo[0].height) - 1) * 0.26;        else heightOffset += (1 - parseFloat(palletInfo[0].height)) * 0.26;        this.safetyFences[i].scaling.y = heightOffset;      }      for (let j = 0; j < this.activedPassthrough.length; j++) {        if (this.isHorizontal) {          const idx = this.safetyFences[i].safetyFPos === "bottom" ? -1 : 1;          if (            this.activedPassthrough[j][0].includes(              this.safetyFences[i].data[0] + idx            ) &&            this.activedPassthrough[j][1].includes(              this.safetyFences[i].data[1]            ) &&            this.activedPassthrough[j][2].includes(this.safetyFences[i].data[2])          ) {            this.safetyFences[i].dispose();            this.safetyFences.splice(i, 1);            break;          }        } else {          const idx = this.safetyFences[i].safetyFPos === "left" ? -1 : 1;          if (            this.activedPassthrough[j][0].includes(              this.safetyFences[i].data[1] + idx            ) &&            this.activedPassthrough[j][1].includes(              this.safetyFences[i].data[0]            ) &&            this.activedPassthrough[j][2].includes(this.safetyFences[i].data[2])          ) {            this.safetyFences[i].dispose();            this.safetyFences.splice(i, 1);            break;          }        }      }    }  }  // update safety fence based on io ports  updateSafetyFenceOnIOPorts() {    this.activedSafetyFences.forEach((item) => {      const ioPorts = this.activedIOPorts.filter(        (e) => e.portPosition === item.safetyFPos      );      let doorsInfo = [];      ioPorts.forEach((ioPort) => {        doorsInfo.push({          col: ioPort.col,          row: ioPort.row,        });      });      item.safetyFDoors = doorsInfo;    });    this.emptyProperty("safetyFences");    this.updateSafetyFencePlacement();  }  //--------------------------------------------------------------------------------------------------------------------  //---------End SafetyFence---------//  //--------------------------------------------------------------------------------------------------------------------  //--------------------------------------------------------------------------------------------------------------------  //---------Start TransferCart---------//  //--------------------------------------------------------------------------------------------------------------------  // show possible position for transfer cart selectors  previewTransferCartSite(prop) {    this.finishToSetProperty(prop, true);    this.firstSelector = null;    const transferCart = ["bottom", "top"];    const transferCartV = ["left", "right"];    let positions = [];    for (let i = 0; i < transferCart.length; i++) {      positions.push(this.getTransferCartPositions(transferCart[i]));    }    if (positions[0].length === 0 && positions[1].length === 0) {      Utils.logg("货架和墙壁之间没有足够的空间放置转运车", "提示");      return;    }    Utils.logg("选择转运车轨道的起点和终点", "提示");    for (let i = 0; i < positions.length; i++) {      for (let j = 0; j < positions[i].length; j++) {        const selector = this.addSelector(prop);        selector.scaling = new BABYLON.Vector3(1.2, 0.2, 1);        selector.transferCPos = this.isHorizontal          ? transferCart[i]          : transferCartV[i];        selector.transferCIndex = j;        selector.position = positions[i][j];        this.property["transferCart"].selectors.push(selector);      }    }  }  // get position and dimension of transfer cart  getTransferCartPositions(transferCartPos, transferCIndex = -1) {    let auxRackings = [];    let possArray = [];    let rottArray = [];    this.transform[5].data.forEach((elem, index) => {      if (elem[2] === 0) {        possArray.push(this.transform[5].position[index]);        rottArray.push(this.transform[5].rotation[index]);      }    });    for (let i = 0; i < possArray.length; i++) {      if (        ["bottom", "left"].includes(transferCartPos) &&        rottArray[i][1] === (this.isHorizontal ? 0 : Math.PI / 2)      )        auxRackings.push(          new BABYLON.Vector3(possArray[i][0], possArray[i][1], possArray[i][2])        );      if (        ["top", "right"].includes(transferCartPos) &&        rottArray[i][1] !== (this.isHorizontal ? 0 : Math.PI / 2)      )        auxRackings.push(          new BABYLON.Vector3(possArray[i][0], possArray[i][1], possArray[i][2])        );    }    const itemLength =      2 * this.palletOverhang +      2 * this.loadPalletOverhang +      g_palletInfo.length;    const all = auxRackings;    for (let i = all.length - 1; i >= 0; i--) {      if (this.isHorizontal) {        all[i].z += ["bottom", "left"].includes(transferCartPos)          ? -itemLength * 1.2          : itemLength * 1.2;        if (["bottom", "left"].includes(transferCartPos)) {          if (all[i].z < warehouse.minZ + itemLength / 2) all.splice(i, 1);        } else {          if (all[i].z > warehouse.maxZ - itemLength / 2) all.splice(i, 1);        }      } else {        all[i].x += ["bottom", "left"].includes(transferCartPos)          ? -itemLength * 1.2          : itemLength * 1.2;        if (["bottom", "left"].includes(transferCartPos)) {          if (all[i].x < warehouse.minX + itemLength / 2) all.splice(i, 1);        } else {          if (all[i].x > warehouse.maxX - itemLength / 2) all.splice(i, 1);        }      }    }    if (transferCIndex !== -1) return all[transferCIndex];    else return all;  }  // on click selector on scene - enable/disable transfer cart  updateTransferCartPlacementBySelector(selector) {    if (this.property["transferCart"].selectors.includes(selector)) {      for (let i = this.transferCarts.length - 1; i >= 0; i--) {        if (this.transferCarts[i].transferCPos === selector.transferCPos) {          this.transferCarts[i].dispose();          this.transferCarts.splice(i, 1);        }      }      for (let i = this.activedTransferCarts.length - 1; i >= 0; i--) {        if (this.activedTransferCarts[i].transferCPos === selector.transferCPos)          this.activedTransferCarts.splice(i, 1);      }      if (this.firstSelector === null) {        this.property["transferCart"].selectors.forEach((select) => {          if (select.transferCPos === selector.transferCPos)            select.material = matManager.matSelector;        });        selector.material = matManager.matActiveSelector;        this.firstSelector = selector;        return;      } else {        if (selector.transferCPos !== this.firstSelector.transferCPos) {          this.firstSelector.material = matManager.matSelector;          selector.material = matManager.matActiveSelector;          this.firstSelector = selector;          return;        } else {          if (this.firstSelector === selector) {            this.firstSelector.material = matManager.matSelector;            this.firstSelector = null;            return;          }        }      }      const s1 =        this.firstSelector.transferCIndex > selector.transferCIndex          ? selector          : this.firstSelector;      const s2 =        this.firstSelector.transferCIndex > selector.transferCIndex          ? this.firstSelector          : selector;      let autoTransC = 0;      this.property["transferCart"].selectors.forEach((select) => {        if (          select.transferCPos === s1.transferCPos &&          select.transferCIndex >= s1.transferCIndex &&          select.transferCIndex <= s2.transferCIndex        ) {          //Store transferCart info          const transferCartInfo = {            transferCIndex: select.transferCIndex,            transferCPos: select.transferCPos,            transferCAuto: autoTransC === 1 ? true : false,          };          //Add transferCart          this._addTransferCart(transferCartInfo);          this.activedTransferCarts.push(transferCartInfo);          autoTransC++;          select.material = matManager.matActiveSelector;        }      });      this.firstSelector = null;    }  }  // on update icube, if there are transfer cart, show it  updateTransferCartPlacement() {    for (let i = this.activedTransferCarts.length - 1; i >= 0; i--) {      if (!this._addTransferCart(this.activedTransferCarts[i]))        this.activedTransferCarts.splice(i, 1);    }  }  // add transfer cart onclick or one by one on update/load  _addTransferCart(infoTransferCart) {    const item = this.getTransferCartPositions(      infoTransferCart.transferCPos,      infoTransferCart.transferCIndex    );    if (!item) return false;    const tranfserCartInfo = itemInfo[ITEMTYPE.Auto.RailAutomatedTransCart];    const itemLength =      2 * this.palletOverhang +      2 * this.loadPalletOverhang +      g_palletInfo.length +      2 * g_rackingPole;    const tranfserCart = tranfserCartInfo.originMesh.createInstance(      "tranfserCart" + "Instance"    );    tranfserCart.origin = tranfserCartInfo.originMesh;    tranfserCart.type = ITEMTYPE.Auto.RailAutomatedTransCart;    if (infoTransferCart.transferCAuto) {      const tranfserCartInfoA = itemInfo[ITEMTYPE.Auto.AutomatedTransferCart];      const tranfserCartA = tranfserCartInfoA.originMesh.createInstance(        "tranfserCartA" + "Instance"      );      tranfserCartA.origin = tranfserCartInfoA.originMesh;      tranfserCartA.type = ITEMTYPE.Auto.AutomatedTransferCart;      tranfserCartA.setParent(tranfserCart);    }    tranfserCart.transferCPos = infoTransferCart.transferCPos;    tranfserCart.transferCIndex = infoTransferCart.transferCIndex;    tranfserCart.isPickable = false;    tranfserCart.setEnabled(true);    tranfserCart.position = item;    if (!this.isHorizontal) tranfserCart.rotation.y = Math.PI / 2;    if (!["bottom", "left"].includes(infoTransferCart.transferCPos))      tranfserCart.rotation.y += Math.PI;    tranfserCart.scaling.x = itemLength * 0.68;    this.transferCarts.push(tranfserCart);    return true;  }  //--------------------------------------------------------------------------------------------------------------------  //---------End TransferCart---------//  //--------------------------------------------------------------------------------------------------------------------  //--------------------------------------------------------------------------------------------------------------------  //---------Start Passthrough---------//  //--------------------------------------------------------------------------------------------------------------------  // show possible position for passthrough selectors  previewPassthroughSite(prop, id) {    this.finishToSetProperty(prop, true);    if (!isNaN(parseInt(id))) {      this.showSelectors(0, id);      this.showSelectors(1, id);      this.showSelectors(2, id);    } else {      const id = parseInt(Math.random() * 100);      this.activedPassthrough.push([[], [], [], id]);      this.showSelectors(0, this.activedPassthrough.length - 1);      this.showSelectors(1, this.activedPassthrough.length - 1);      this.showSelectors(2, this.activedPassthrough.length - 1);    }  }  // show seletors for setting width,depth or height  showSelectors(stage, activedPassId) {    switch (stage) {      case 0:        for (          let i = 0;          i < (this.isHorizontal ? this.maxRow : this.maxCol);          i++        ) {          const selector = meshSelector.clone("passthroughSelector" + "Clone");          selector.scaling = new BABYLON.Vector3(1, 0.2, 0.9 * g_width);          const rowData = this.calcPosAndUprightForRow(i);          const posz = rowData[0];          const uprightDist = rowData[2];          if (this.isHorizontal) {            selector.position = new BABYLON.Vector3(              this.area.maxX + 2,              0,              this.area.minZ + posz - uprightDist / 2            );          } else {            selector.position = new BABYLON.Vector3(              this.area.minX + posz - uprightDist / 2,              0,              this.area.maxZ + 2            );            selector.rotation.y = Math.PI / 2;          }          selector.stage = stage;          selector.passthroughId = i;          this.setSelector(selector, activedPassId);          this.property["passthrough"].selectors.push(selector);        }        break;      case 1:        let elemPos = 0;        let spacingOffset = 0;        const itemLength =          2 * this.palletOverhang +          2 * this.loadPalletOverhang +          g_palletInfo.length +          g_rackingPole;        for (          let i = 0;          i < (this.isHorizontal ? this.maxCol : this.maxRow);          i++        ) {          const spacingRow = this.activedSpacing.indexOf(i - 1);          if (spacingRow > -1)            spacingOffset = (spacingRow + 1) * this.spacingBetweenRows;          elemPos =            (this.isHorizontal ? this.area.minX : this.area.minZ) +            i * itemLength +            itemLength / 2 +            spacingOffset;          const selector = meshSelector.clone("passthroughSelector" + "Clone");          selector.scaling = new BABYLON.Vector3(1, 0.2, 0.9 * g_width);          if (this.isHorizontal) {            selector.position = new BABYLON.Vector3(              elemPos,              1 / 2.5,              this.area.maxZ + 1.5 * g_width            );          } else {            selector.position = new BABYLON.Vector3(              this.area.minX - 1.5 * g_width,              1 / 2.5,              elemPos            );            selector.rotation.y = Math.PI / 2;          }          selector.stage = stage;          selector.passthroughId = i;          this.setSelector(selector, activedPassId);          this.property["passthrough"].selectors.push(selector);        }        const specSelector = meshSelector.clone(          "passthroughSelector" + "Clone"        );        specSelector.scaling = new BABYLON.Vector3(1, 0.2, 0.9 * g_width);        if (this.isHorizontal) {          specSelector.position = new BABYLON.Vector3(            (this.isHorizontal ? this.area.minX : this.area.minZ) -              itemLength / 2,            1 / 2.5,            this.area.maxZ + 1.5 * g_width          );        } else {          specSelector.position = new BABYLON.Vector3(            this.area.minX - 1.5 * g_width,            1 / 2.5,            (this.isHorizontal ? this.area.minX : this.area.minZ) -              itemLength / 2          );          specSelector.rotation.y = Math.PI / 2;        }        specSelector.isSpec = true;        specSelector.stage = stage;        this.setSelector(specSelector, activedPassId);        this.property["passthrough"].selectors.push(specSelector);        break;      case 2:        for (let i = 0; i < this.rackingHighLevel; i++) {          const selector = meshSelector.clone("passthroughSelector" + "Clone");          selector.rotation = new BABYLON.Vector3(0, 0.8, Math.PI / 2);          selector.scaling = new BABYLON.Vector3(1, 0.2, g_width * 0.75);          if (this.isHorizontal) {            selector.position = new BABYLON.Vector3(              this.area.maxX + 1,              this.getHeightAtLevel(i) + 1,              this.area.maxZ + 1            );            selector.rotation.y += Math.PI / 2;          } else {            selector.position = new BABYLON.Vector3(              this.area.minX - 1,              this.getHeightAtLevel(i) + 1,              this.area.maxZ + 1            );          }          selector.stage = stage;          selector.passthroughId = i;          this.setSelector(selector, activedPassId);          this.property["passthrough"].selectors.push(selector);        }        break;      default:        break;    }    renderScene();  }  setSelector(selector, activedPassId) {    selector.isPickable = true;    selector.setEnabled(true);    selector.activedPassId = activedPassId;    selector.actionManager = new BABYLON.ActionManager(scene);    selector.actionManager.hoverCursor = "pointer";    selector.actionManager.registerAction(      new BABYLON.ExecuteCodeAction(        BABYLON.ActionManager.OnPointerOverTrigger,        () => {}      )    );    selector.actionManager.registerAction(      new BABYLON.ExecuteCodeAction(        BABYLON.ActionManager.OnLeftPickTrigger,        (evt) => {          selectedIcube.updatePassthroughPlacementBySelector(            evt.meshUnderPointer          );        }      )    );    if (selector.isSpec) {      selector.isPassthrough =        this.activedPassthrough[activedPassId][1].length ===        (this.isHorizontal ? this.maxRow : this.maxCol)          ? true          : false;      selector.material = matManager.allRowsMat;    } else {      selector.isPassthrough = this.activedPassthrough[activedPassId][        selector.stage      ].includes(selector.passthroughId)        ? true        : false;      selector.material =        selector.isPassthrough === true          ? matManager.matActiveSelector          : matManager.matSelector;    }  }  // on click selector on scene - enable/disable passthrough  updatePassthroughPlacementBySelector(selector) {    const stage = selector.stage;    if (this.property["passthrough"].selectors.includes(selector)) {      selector.isPassthrough = !selector.isPassthrough;      if (!selector.isSpec)        selector.material =          selector.isPassthrough === true            ? matManager.matActiveSelector            : matManager.matSelector;      if (selector.isSpec) {        this.property["passthrough"].selectors.forEach((select) => {          if (select.stage === 1 && !select.isSpec) {            select.isPassthrough = selector.isPassthrough;            select.material =              select.isPassthrough === true                ? matManager.matActiveSelector                : matManager.matSelector;          }        });      }    }    const passthroughInfo = this.activedPassthrough[selector.activedPassId];    if (!passthroughInfo) return;    const prevPass = [      passthroughInfo[0],      passthroughInfo[1],      passthroughInfo[2],      passthroughInfo[3],    ];    passthroughInfo[stage] = [];    this.property["passthrough"].selectors.forEach((selector) => {      if (        selector.stage === stage &&        selector.isPassthrough === true &&        !selector.isSpec      )        passthroughInfo[stage].push(selector.passthroughId);    });    //Add passthrough    if (      passthroughInfo[0].length !== 0 &&      passthroughInfo[1].length !== 0 &&      passthroughInfo[2].length !== 0    ) {      Behavior.add(Behavior.type.addPassthrough);      this.updateRacking(() => {        this.previewProperty("passthrough", selector.activedPassId);      });    } else {      if (        prevPass[0].length !== 0 &&        prevPass[1].length !== 0 &&        prevPass[2].length !== 0 &&        (passthroughInfo[0].length === 0 ||          passthroughInfo[1].length === 0 ||          passthroughInfo[2].length === 0)      ) {        Behavior.add(Behavior.type.addPassthrough);        this.updateRacking(() => {          this.previewProperty("passthrough", false);        });      }    }  }  //--------------------------------------------------------------------------------------------------------------------  //---------End Passthrough---------//  //--------------------------------------------------------------------------------------------------------------------  //--------------------------------------------------------------------------------------------------------------------  //---------Start Spacing---------//  //--------------------------------------------------------------------------------------------------------------------  // show possible position for spacing selectors  previewSpacingSite(prop) {    this.finishToSetProperty(prop, true);    let positions = [];    let spacingOffset = 0;    if (this.isHorizontal) {      for (let i = 0; i < this.maxCol; i++) {        const spacingRow = this.activedSpacing.indexOf(i - 1);        if (spacingRow > -1)          spacingOffset = (spacingRow + 1) * this.spacingBetweenRows;        positions.push(          new BABYLON.Vector3(            this.area.minX +              spacingOffset +              (i + 1) *                (2 * g_palletOverhang +                  2 * g_loadPalletOverhang +                  g_palletInfo.length +                  g_rackingPole),            0,            this.area.maxZ + g_width * 0.5          )        );      }    } else {      for (let i = 0; i < this.maxRow; i++) {        const spacingRow = this.activedSpacing.indexOf(i - 1);        if (spacingRow > -1)          spacingOffset = (spacingRow + 1) * this.spacingBetweenRows;        positions.push(          new BABYLON.Vector3(            this.area.minX - g_width * 0.5,            0,            this.area.minZ +              spacingOffset +              (i + 1) *                (2 * g_palletOverhang +                  2 * g_loadPalletOverhang +                  g_palletInfo.length +                  g_rackingPole)          )        );      }    }    for (let j = 0; j < positions.length; j++) {      const selector = this.addSelector(prop);      selector.scaling = new BABYLON.Vector3(0.5, 0.2, 1.2);      selector.position = positions[j];      selector.spacingId = j;      selector.selected = this.activedSpacing.includes(selector.spacingId)        ? true        : false;      selector.material = selector.selected        ? matManager.matActiveSelector        : matManager.matSelector;      if (        selector.spacingId ===          (this.isHorizontal ? this.maxCol - 1 : this.maxRow - 1) &&        !selector.selected      )        selector.isVisible = false;      this.property["spacing"].selectors.push(selector);    }  }  // on click selector on scene - enable/disable transfer cart  updateSpacingPlacementBySelector(selector) {    if (this.property["spacing"].selectors.includes(selector)) {      selector.selected = !selector.selected;      const spacingId = selector.spacingId;      const xtrackIdPos = this.activedSpacing.indexOf(spacingId);      if (selector.selected) {        if (xtrackIdPos === -1) {          this.activedSpacing.push(spacingId);          this.activedSpacing = this.activedSpacing.sort((a, b) => {            return a - b;          });        }      } else {        if (xtrackIdPos !== -1) this.activedSpacing.splice(xtrackIdPos, 1);      }      selector.material = selector.selected        ? matManager.matActiveSelector        : matManager.matSelector;      this.updateSpacingPlacement(true);    }  }  // on update spacing value  updateDistanceBetweenRows() {    this.spacingBetweenRows = g_spacingBetweenRows;    this.updateSpacingPlacement();  }  // on update spacing value  updateSpacingPlacement(redraw = false) {    const minVal = this.isHorizontal ? this.area.minX : this.area.minZ;    const maxVal = this.isHorizontal ? WHDimensions[0] : WHDimensions[1];    let spacing = [...this.activedSpacing].map((e, i) =>      parseFloat(        (          minVal +          (e + 1) *            (2 * g_palletOverhang +              2 * g_loadPalletOverhang +              g_palletInfo.length) +          i * this.spacingBetweenRows        ).toFixed(2)      )    );    const length = useP(      useP(2 * this.palletOverhang) +        useP(2 * this.loadPalletOverhang) +        useP(g_palletInfo.length) +        useP(g_rackingPole),      false    );    let oPoints = [];    this.origPoints.forEach((arr) => {      oPoints.push(arr.map((x) => x));    });    const idx = this.isHorizontal ? 0 : 1;    for (let i = 0; i < oPoints.length; i++) {      for (let j = spacing.length - 1; j >= 0; j--) {        if (oPoints[i][idx] > spacing[j]) {          oPoints[i][idx] += this.spacingBetweenRows;          if (oPoints[i][idx] > maxVal) oPoints[i][idx] -= g_rackingUpRightW;          oPoints[i][idx] = parseFloat(oPoints[i][idx].toFixed(2));        }      }    }    if (redraw) {      let points = [],        k = 0;      for (let i = 0; i < this.baseLines.length; i++) {        for (let j = 0; j < this.baseLines[i].points.length; j++) {          points.push([            this.baseLines[i].points[j].x,            this.baseLines[i].points[j].z,          ]);          if (            JSON.stringify(points[points.length - 1]) !==            JSON.stringify(oPoints[k])          ) {            if (oPoints[k][0] > warehouse.maxX) oPoints[k][0] -= length;            if (oPoints[k][0] < warehouse.minX) oPoints[k][0] += length;            if (oPoints[k][1] > warehouse.maxZ) oPoints[k][1] -= length;            if (oPoints[k][1] < warehouse.minZ) oPoints[k][1] += length;            oPoints[k] = [              parseFloat(oPoints[k][0].toFixed(2)),              parseFloat(oPoints[k][1].toFixed(2)),            ];            this.baseLines[i].points[j].x = oPoints[k][0];            this.baseLines[i].points[j].z = oPoints[k][1];            if (j === 0) {              this.baseLines[i].sPoint.x = oPoints[k][0];              this.baseLines[i].sPoint.z = oPoints[k][1];            } else {              this.baseLines[i].ePoint.x = oPoints[k][0];              this.baseLines[i].ePoint.z = oPoints[k][1];            }            this.baseLines[i].updateBaseline();          }          k++;        }      }      if (JSON.stringify(this.points) !== JSON.stringify(oPoints)) {        updateSelectedIcube(() => {          this.showMeasurement();          this.previewProperty("spacing");        });      }    }  }  //--------------------------------------------------------------------------------------------------------------------  //---------End Spacing---------//  //--------------------------------------------------------------------------------------------------------------------  //--------------------------------------------------------------------------------------------------------------------  //---------Start Pillers---------//  //--------------------------------------------------------------------------------------------------------------------  // show possible position for Pillers selectors  previewPillersSite(prop) {    this.finishToSetProperty(prop, true);    let stores = this.stores.filter((e) => e.height === 0);    for (let i = 0; i < stores.length; i++) {      const origLength = stores[i].original.length >= 2 ? 1 : 0;      for (let j = 0; j < stores[i].original[origLength].length; j++) {        const dimension = stores[i].original[origLength][j];        const dist = parseFloat(          (            dimension[1] -            dimension[0] -            (stores[i].ends.includes(dimension[1])              ? g_diffToEnd[g_palletInfo.max]              : g_difftoXtrack[g_palletInfo.max]) -            (stores[i].ends.includes(dimension[0])              ? g_diffToEnd[g_palletInfo.max]              : g_difftoXtrack[g_palletInfo.max])          ).toFixed(3)        );        const width = _round(          g_PalletW[g_palletInfo.max] +            g_spacingBPallets[g_palletInfo.max] +            2 * g_loadPalletOverhang,          2        );        const capacity = _round(          (dist + g_spacingBPallets[g_palletInfo.max]) / width        );        for (let k = 0; k < capacity; k++) {          const pos1 =            dimension[0] +            (stores[i].ends.includes(dimension[0])              ? g_diffToEnd[g_palletInfo.max]              : g_difftoXtrack[g_palletInfo.max]) +            k * g_spacingBPallets[g_palletInfo.max] +            (k + 1) * (g_PalletW[g_palletInfo.max] + 2 * g_loadPalletOverhang) -            g_PalletW[g_palletInfo.max] / 2;          const pos = new BABYLON.Vector3(            this.isHorizontal ? stores[i].rails[0][0][0] : pos1,            0.4,            this.isHorizontal ? pos1 : stores[i].rails[0][0][2]          );          const selector = this.addSelector(prop);          selector.scaling = new BABYLON.Vector3(0.6, 0.2, 0.6);          selector.selected =            this.activedPillers.filter(              (e) => e.row === stores[i].row && e.idx === k && e.slotId === j            ).length > 0              ? true              : false;          selector.material = selector.selected            ? matManager.matActiveSelector            : matManager.matSelector;          selector.position = pos;          selector.idx = k;          selector.row = stores[i].row;          selector.slotId = j;          this.property["pillers"].selectors.push(selector);        }      }    }  }  // on click selector on scene - enable/disable transfer cart  updatePillersPlacementBySelector(selector) {    if (this.property["pillers"].selectors.includes(selector)) {      selector.selected = !selector.selected;      if (selector.selected) {        this.activedPillers.push({          row: selector.row,          idx: selector.idx,          slotId: selector.slotId,          position: [selector.position.x, selector.position.z],        });      } else {        //Remove pillar        for (let i = 0; i < this.pillers.length; i++) {          if (            this.pillers[i].metadata.row === selector.row &&            this.pillers[i].metadata.idx === selector.idx &&            this.pillers[i].metadata.slotId === selector.slotId          ) {            this.pillers[i].dispose();            this.pillers.splice(i, 1);            break;          }        }        for (let i = 0; i < this.activedPillers.length; i++) {          if (            selector.row === this.activedPillers[i].row &&            selector.idx === this.activedPillers[i].idx &&            selector.slotId === this.activedPillers[i].slotId          ) {            this.activedPillers.splice(i, 1);            break;          }        }      }      selector.material = selector.selected        ? matManager.matActiveSelector        : matManager.matSelector;    }  }  // on update icube, if there are pillers, show it  updatePillersPlacement() {    for (let i = this.activedPillers.length - 1; i >= 0; i--) {      if (        this.activedPillers[i].row >=        (this.isHorizontal ? this.maxCol : this.maxRow)      ) {        this.activedPillers.splice(i, 1);      } else {        const stores = this.stores.filter(          (e) => e.row === this.activedPillers[i].row        );        let position = new BABYLON.Vector3(          this.activedPillers[i].position[0],          0.1,          this.activedPillers[i].position[1]        );        if (stores.length > 0 && stores[0].rails.length > 0) {          if (this.isHorizontal) {            position.x = stores[0].rails[0][0][0];          } else {            position.z = stores[0].rails[0][0][2];          }        }        const piller = pillerSign.createInstance("piller" + "Instance");        piller.origin = pillerSign;        piller.metadata = this.activedPillers[i];        piller.position = position;        piller.isPickable = false;        piller.setEnabled(true);        this.pillers.push(piller);      }    }  }  //--------------------------------------------------------------------------------------------------------------------  //---------End Pillers---------//  //--------------------------------------------------------------------------------------------------------------------  // add xtrack lines  addXtrackLines(offset) {    let pos = BABYLON.Vector3.Zero();    const range = [      this.isHorizontal ? this.area.minZ : this.area.minX,      this.isHorizontal ? this.area.maxZ : this.area.maxX,    ];    const center = (range[0] + range[1]) / 2;    if (this.isHorizontal)      pos = new BABYLON.Vector3(-(WHDimensions[0] / 2 + offset), 0, center);    else pos = new BABYLON.Vector3(center, 0, -(WHDimensions[1] / 2 + offset));    let positions = [];    const Xline = new BABYLON.TransformNode("abs", scene);    for (let i = 0; i < this.activedXtrackIds.length; i++) {      const xtrack = Utils.createLine({        labelScale: 1,        length: parseFloat(Number(g_xtrackFixedDim).toFixed(2)),        color: BABYLON.Color3.FromHexString("#0059a4"),      });      xtrack.position = pos.clone();      xtrack.rotation.y = this.isHorizontal ? Math.PI : Math.PI / 2;      if (this.isHorizontal) {        xtrack.position.z =          range[this.isHorizontal ? 1 : 0] +          (this.isHorizontal ? -1 : 1) * this.activedXtrackIds[i];        positions.push(xtrack.position.z);      } else {        xtrack.position.x =          range[this.isHorizontal ? 1 : 0] +          (this.isHorizontal ? -1 : 1) * this.activedXtrackIds[i];        positions.push(xtrack.position.x);      }      xtrack.setParent(Xline);    }    let intvals = [range[0]];    for (let i = 0; i < positions.length; i++) {      intvals.push(        _round(positions[i] - g_xtrackFixedDim / 2, 3),        _round(positions[i] + g_xtrackFixedDim / 2, 3)      );    }    intvals.push(range[1]);    intvals = intvals.sort((a, b) => {      return a - b;    });    for (let i = 0; i < intvals.length; i += 2) {      const val = _round(Math.abs(intvals[i + 1] - intvals[i]), 3);      const text = Utils.round5(val * rateUnit) + unitChar;      const mesh = new BABYLON.MeshBuilder.CreatePlane(        "TextPlane",        {          width: 3,          height: 1,          sideOrientation: 2,        },        scene      );      mesh.rotation = new BABYLON.Vector3(        -Math.PI / 2,        this.isHorizontal ? -Math.PI / 2 : 0,        0      );      mesh.scaling = new BABYLON.Vector3(0.75, 0.75, 0.75);      mesh.position = pos.clone();      mesh.visibility = 0.0001;      const input = new BABYLON.GUI.TextBlock("labelD");      input.width = "100px";      input.height = "80px";      input.color = "white";      input.fontSize = 18;      input.text = "";      input.rotation = this.isHorizontal ? -Math.PI / 2 : 0;      input.fontFamily = "FontAwesome";      input.isPointerBlocker = false;      ggui.addControl(input);      input.linkWithMesh(mesh);      mesh.label = input;      if (this.isHorizontal) {        input.linkOffsetX = 14;        mesh.position.z = (intvals[i + 1] + intvals[i]) / 2;      } else {        input.linkOffsetY = 14;        mesh.position.x = (intvals[i + 1] + intvals[i]) / 2;      }      input.text += text;      mesh.setParent(Xline);    }    Xline.setEnabled(false);    return Xline;  }  // create measurement  createMeasurement() {    const index = icubes.findIndex((icube) => icube === this);    const icubePos = BABYLON.Vector3.Center(      new BABYLON.Vector3(this.area.minX, 0, this.area.minZ),      new BABYLON.Vector3(this.area.maxX, 0, this.area.maxZ)    );    const maxDim = Math.max(      WHDimensions[0],      WHDimensions[1],      2 * WHDimensions[2]    );    const topScale = (maxDim / 10) * 6.5;    // top - view    let measureLinesTop = [];    for (let i = 0; i < this.baseLines.length; i++) {      const dist = BABYLON.Vector3.Distance(        this.baseLines[i].points[0],        this.baseLines[i].points[1]      );      const center = BABYLON.Vector3.Center(        this.baseLines[i].points[0],        this.baseLines[i].points[1]      );      const m0 = this.generateMeasure({        length: parseFloat(Number(dist).toFixed(2)),        text1: parseFloat(Number(dist * rateUnit).toFixed(2)) + unitChar,        text2: null,        labelScale: topScale,        textRot:          this.baseLines[i].points[0].z !== this.baseLines[i].points[1].z            ? this.baseLines[i].points[0].z < this.baseLines[i].points[1].z              ? Math.PI / 2              : -Math.PI / 2            : 0,        baseline: this.isSelect === true ? i : null,        fontSize: 18,        color: icubeColors[index],        view: 1,      });      let xDir =        this.baseLines[i].points[0].x < this.baseLines[i].points[1].x          ? true          : false;      let zDir =        this.baseLines[i].points[0].z < this.baseLines[i].points[1].z          ? true          : false;      m0.rotation.x = Math.PI;      m0.rotation.y =        this.baseLines[i].points[0].x === this.baseLines[i].points[1].x          ? zDir === true            ? Math.PI            : 0          : Math.PI / 2;      m0.position.x =        this.baseLines[i].points[0].x === this.baseLines[i].points[1].x          ? (zDir === true ? 1 : -1) *            (WHDimensions[0] / 2 + (index + 2) * (1 + 0.3))          : center.x;      m0.position.z =        this.baseLines[i].points[0].z === this.baseLines[i].points[1].z          ? (xDir === true ? -1 : 1) *            (WHDimensions[1] / 2 + (index + 2) * (1 + 0.3))          : center.z;      m0.setEnabled(false);      measureLinesTop.push(m0);    }    // add xtrack view on top    const m00 = this.addXtrackLines((index + 2) * 1.3);    measureLinesTop.push(m00);    this.measures.push(measureLinesTop);    // front - view    // length    const m1 = this.generateMeasure({      length: parseFloat(        Number(this.area.dimensions[this.isHorizontal ? 0 : 2]).toFixed(2)      ),      text1:        parseFloat(          Number(            this.area.dimensions[this.isHorizontal ? 0 : 2] * rateUnit          ).toFixed(2)        ) + unitChar,      text2: (this.isHorizontal ? this.maxCol : this.maxRow) + "rows",      labelScale: topScale,      textRot: 0,      fontSize: 18,      color: icubeColors[index],      view: 2,    });    m1.rotation.y = this.isHorizontal ? -Math.PI / 2 : Math.PI;    m1.rotation.z = -Math.PI / 2;    m1.position = this.isHorizontal      ? new BABYLON.Vector3(          icubePos.x,          (-(index + 1) * topScale) / 20,          -WHDimensions[1] / 2        )      : new BABYLON.Vector3(          -WHDimensions[0] / 2,          (-(index + 1) * topScale) / 20,          icubePos.z        );    m1.setEnabled(false);    // height    const m11 = this.generateMeasure({      length: parseFloat(Number(this.area.dimensions[1]).toFixed(2)),      text1:        parseFloat(Number(this.area.dimensions[1] * rateUnit).toFixed(2)) +        unitChar,      text2: null,      labelScale: topScale,      textRot: -Math.PI / 2,      fontSize: 18,      color: icubeColors[index],      view: 2,    });    m11.rotation.x = Math.PI / 2;    m11.rotation.y = this.isHorizontal ? -Math.PI / 2 : Math.PI;    m11.rotation.z = -Math.PI / 2;    m11.position = new BABYLON.Vector3(      -WHDimensions[0] / 2 - ((index + 1) * topScale) / 20,      this.area.dimensions[1] / 2,      -WHDimensions[1] / 2 - ((index + 1) * topScale) / 20    );    m11.setEnabled(false);    // one raw height    let rawh = [m1, m11];    for (let i = 0; i < this.rackingHighLevel; i++) {      const palletInfo = this.palletAtLevel.filter((e) => e.idx === i + 1);      const heightP =        palletInfo.length > 0          ? parseFloat(palletInfo[0].height)          : this.palletHeight;      const fullHeight =        heightP +        g_railHeight +        (i < this.rackingHighLevel - 1 ? g_StoreTopGap : 0);      const m12 = this.generateMeasure({        length: parseFloat(Number(heightP).toFixed(2)),        text1: null,        text2: parseFloat(Number(heightP * rateUnit).toFixed(2)), //+ unitChar,        labelScale: topScale,        textRot: -Math.PI / 2,        fontSize: 16,        color: icubeColors[index],        view: 2,      });      m12.rotation.x = Math.PI / 2;      m12.rotation.y = this.isHorizontal ? -Math.PI / 2 : Math.PI;      m12.rotation.z = -Math.PI / 2;      m12.position = new BABYLON.Vector3(        -WHDimensions[0] / 2 - ((index + 1) * topScale) / 40,        this.getHeightAtLevel(i) + heightP / 2 + g_bottomLength + g_railHeight,        -WHDimensions[1] / 2 - ((index + 1) * topScale) / 40      );      m12.setEnabled(false);      rawh.push(m12);      const m1112 = this.generateMeasure({        length: parseFloat(Number(fullHeight).toFixed(2)),        text1: parseFloat(Number(fullHeight * rateUnit).toFixed(2)), //+ unitChar,,        text2: null,        labelScale: topScale,        textRot: -Math.PI / 2,        fontSize: 16,        color: icubeColors[index],        view: 2,      });      m1112.rotation.x = Math.PI / 2;      m1112.rotation.y = this.isHorizontal ? -Math.PI / 2 : Math.PI;      m1112.rotation.z = -Math.PI / 2;      m1112.position = new BABYLON.Vector3(        -WHDimensions[0] / 2 - ((index + 1) * topScale) / 40,        this.getHeightAtLevel(i) + fullHeight / 2 + g_bottomLength,        -WHDimensions[1] / 2 - ((index + 1) * topScale) / 40      );      m1112.setEnabled(false);      rawh.push(m1112);    }    // store length L1    const width1 =      2 * this.palletOverhang +      2 * this.loadPalletOverhang +      g_palletInfo.length;    const width2 = width1 + g_rackingPole;    const m13 = this.generateMeasure({      length: parseFloat(Number(width1).toFixed(3)),      text1: parseFloat(width1).toFixed(3),      text2: null,      labelScale: topScale,      textRot: 0,      fontSize: 16,      color: icubeColors[index],      view: 2,    });    m13.rotation.y = this.isHorizontal ? -Math.PI / 2 : 0;    m13.rotation.z = -Math.PI / 2;    m13.position = this.isHorizontal      ? new BABYLON.Vector3(          this.area.minX + width2 / 2,          (-(index + 1) * topScale) / 50,          -WHDimensions[2] / 2        )      : new BABYLON.Vector3(          -WHDimensions[0] / 2,          (-(index + 1) * topScale) / 50,          this.area.minZ + width2 / 2        );    m13.setEnabled(false);    rawh.push(m13);    // store length L2    const m14 = this.generateMeasure({      length: parseFloat(Number(width2).toFixed(3)),      text1: null,      text2: parseFloat(width2).toFixed(3),      labelScale: topScale,      textRot: 0,      fontSize: 16,      color: icubeColors[index],      view: 2,    });    m14.rotation.y = this.isHorizontal ? -Math.PI / 2 : 0;    m14.rotation.z = -Math.PI / 2;    m14.position = this.isHorizontal      ? new BABYLON.Vector3(          this.area.minX + width2 / 2,          (-(index + 1) * topScale) / 50,          -WHDimensions[2] / 2        )      : new BABYLON.Vector3(          -WHDimensions[0] / 2,          (-(index + 1) * topScale) / 50,          this.area.minZ + width2 / 2        );    m14.setEnabled(false);    rawh.push(m14);    this.measures.push(rawh);    // side - view    // height    const m21 = this.generateMeasure({      length: parseFloat(Number(this.area.dimensions[1]).toFixed(2)),      text1:        parseFloat(Number(this.area.dimensions[1] * rateUnit).toFixed(2)) +        unitChar,      text2: null,      labelScale: topScale,      textRot: -Math.PI / 2,      fontSize: 16,      color: icubeColors[index],      view: 3,    });    m21.rotation.x = Math.PI / 2;    m21.rotation.y = this.isHorizontal ? -Math.PI / 2 : 0;    m21.rotation.z = 0;    m21.position = new BABYLON.Vector3(      -WHDimensions[0] / 2 - ((index + 1) * topScale) / 30,      this.area.dimensions[1] / 2,      -WHDimensions[1] / 2 - ((index + 1) * topScale) / 30    );    m21.setEnabled(false);    // dist between rackings    let rawu = [m21];    let prevUp = -1;    for (let r = 0; r < (this.isHorizontal ? this.maxRow : this.maxCol); r++) {      const rowData = this.calcPosAndUprightForRow(r);      const posz = rowData[0];      const uprightDist = rowData[2];      const halfRacking = rowData[4];      const rackingDim =        rowData[4] !== 0          ? parseFloat((g_palletInfo.racking / 2).toFixed(3))          : g_palletInfo.racking;      if (uprightDist !== prevUp) {        prevUp = uprightDist;        const m22 = this.generateMeasure({          length: parseFloat(Number(prevUp).toFixed(2)),          text1: null,          text2: parseFloat(Number(prevUp * rateUnit).toFixed(2)), //+ unitChar,          labelScale: topScale,          textRot: 0,          fontSize: 16,          color: icubeColors[index],          view: 3,        });        m22.rotation.y = this.isHorizontal ? Math.PI : -Math.PI / 2;        m22.rotation.z = -Math.PI / 2;        m22.position = this.isHorizontal          ? new BABYLON.Vector3(              -WHDimensions[0] / 2,              (-(index + 1) * topScale) / 50,              this.area.minZ +                posz +                g_railOutside +                g_rackingPole / 2 +                halfRacking / 2 +                rackingDim / 2            )          : new BABYLON.Vector3(              this.area.minX +                posz +                g_railOutside +                g_rackingPole / 2 +                halfRacking / 2 +                rackingDim / 2,              (-(index + 1) * topScale) / 50,              -WHDimensions[1] / 2            );        m22.setEnabled(false);        rawu.push(m22);      }    }    if (g_palletInfo.order.length > 1) {      const type = ["(800x1200)", "(1000x1200)", "(1200x1200)"];      for (let i = 0; i < g_palletInfo.order.length; i++) {        const palletNo = this.pallets.filter(          (e) => e.type === g_palletInfo.order[i]        ).length;        const m3 = this.generateMeasure({          length:            i === 1              ? parseFloat(                  Number(                    this.area.dimensions[this.isHorizontal ? 2 : 0]                  ).toFixed(2)                )              : 0,          text1:            i === 1              ? parseFloat(                  Number(                    this.area.dimensions[this.isHorizontal ? 2 : 0] * rateUnit                  ).toFixed(2)                ) + unitChar              : "",          text2: palletNo + type[g_palletInfo.order[i]],          labelScale: topScale,          textRot: 0,          fontSize: 15,          color: icubeColors[index],          view: 3,        });        m3.rotation.y = this.isHorizontal ? Math.PI : -Math.PI / 2;        m3.rotation.z = -Math.PI / 2;        m3.position = this.isHorizontal          ? new BABYLON.Vector3(              -WHDimensions[0] / 2,              (-(index + 1) * topScale) / 20,              icubePos.z + (i - 1) * 2            )          : new BABYLON.Vector3(              icubePos.x + (i - 1) * 2,              (-(index + 1) * topScale) / 20,              -WHDimensions[1] / 2            );        m3.setEnabled(false);        rawu.push(m3);      }    } else {      const m2 = this.generateMeasure({        length: parseFloat(          Number(this.area.dimensions[this.isHorizontal ? 2 : 0]).toFixed(2)        ),        text1:          parseFloat(            Number(              this.area.dimensions[this.isHorizontal ? 2 : 0] * rateUnit            ).toFixed(2)          ) + unitChar,        text2:          this.pallets.filter((e) => e.type === g_palletInfo.max).length +          "pallets",        labelScale: topScale,        textRot: 0,        fontSize: 18,        color: icubeColors[index],        view: 3,      });      m2.rotation.y = this.isHorizontal ? Math.PI : -Math.PI / 2;      m2.rotation.z = -Math.PI / 2;      m2.position = this.isHorizontal        ? new BABYLON.Vector3(            -WHDimensions[0] / 2,            (-(index + 1) * topScale) / 20,            icubePos.z          )        : new BABYLON.Vector3(            icubePos.x,            (-(index + 1) * topScale) / 20,            -WHDimensions[1] / 2          );      m2.setEnabled(false);      rawu.push(m2);    }    this.measures.push(rawu);  }  // generate measurement objects  generateMeasure(params) {    const limit = params.length === 0 ? 0 : 0.15;    const l1 = [      new BABYLON.Vector3(-limit, 0, params.length / 2),      new BABYLON.Vector3(limit, 0, params.length / 2),    ];    const l2 = [      new BABYLON.Vector3(-limit, 0, -params.length / 2),      new BABYLON.Vector3(limit, 0, -params.length / 2),    ];    const l3 = [      new BABYLON.Vector3(0, 0, params.length / 2),      new BABYLON.Vector3(0, 0, -params.length / 2),    ];    let lineColor = new BABYLON.Color4(0, 0, 0, 1);    if (params.color) {      lineColor.r = params.color.r;      lineColor.g = params.color.g;      lineColor.b = params.color.b;    }    this.dom_item.style.backgroundColor =      "rgba(" +      lineColor.r * 356 +      "," +      lineColor.g * 356 +      "," +      lineColor.b * 356 +      ",0.9)";    const line = new BABYLON.MeshBuilder.CreateLineSystem(      "lines",      { lines: [l1, l2, l3] },      scene    );    line.isPickable = false;    line.color = lineColor;    line.enableEdgesRendering();    line.edgesWidth = 5;    line.edgesColor = lineColor;    let mesh;    if (params.hasOwnProperty("baseline") && params.baseline !== null) {      mesh = new BABYLON.MeshBuilder.CreatePlane(        "TextPlane",        { width: 2, height: 1, sideOrientation: 2 },        scene      );      mesh.rotation = new BABYLON.Vector3(Math.PI / 2, Math.PI / 2, 0);      mesh.visibility = 0.0001;      mesh.position.y = -0.05;      mesh.position.x = -0.5;      mesh.scaling = new BABYLON.Vector3(        params.labelScale / 10,        params.labelScale / 20,        params.labelScale / 10      );    } else {      mesh = new BABYLON.TransformNode("TextPlane", scene);    }    mesh.setParent(line);    const input = new BABYLON.GUI.TextBlock("labelD");    input.width = "100px";    input.height = "80px";    input.color = params.view > 1 ? "#000000" : "#ffffff";    input.fontSize = params.fontSize;    input.text = "";    input.rotation = params.textRot;    input.fontWeight = "800";    input.fontFamily = "FontAwesome";    input.isPointerBlocker = false;    ggui.addControl(input);    input.linkWithMesh(mesh);    if (params.hasOwnProperty("baseline") && params.baseline !== null) {      if (params.textRot === 0) {        input.linkOffsetY = 10;      } else {        input.linkOffsetX = (params.textRot < 0 ? 1 : -1) * 10;      }    }    if (params.text1) {      if (currentView === ViewType.top && this.isSelect === true)        input.text += "\uf040 ";      input.text += params.text1.toString();    }    input.text += "\n";    if (params.text2) {      input.text += params.text2.toString();    }    mesh.label = input;    if (params.hasOwnProperty("baseline") && params.baseline !== null) {      mesh.actionManager = new BABYLON.ActionManager(scene);      mesh.actionManager.hoverCursor = "pointer";      mesh.actionManager.registerAction(        new BABYLON.ExecuteCodeAction(          BABYLON.ActionManager.OnPointerOverTrigger,          () => {}        )      );      mesh.actionManager.registerAction(        new BABYLON.ExecuteCodeAction(          BABYLON.ActionManager.OnLeftPickTrigger,          () => {            this.baseLines[params.baseline].addLabel(mesh);          }        )      );    }    return line;  }  // show measurement for specific view  showMeasurement() {    this.hideMeasurement();    this.createMeasurement();    const index = currentView - 1;    for (let i = 0; i < this.measures.length; i++) {      for (let j = this.measures[i].length - 1; j >= 0; j--) {        this.measures[i][j].setEnabled(i === index ? true : false);        const kids = this.measures[i][j].getChildren();        kids.forEach((kid) => {          if (kid.label) {            kid.label.isVisible = i === index ? true : false;          }          kid.isVisible = i === index ? true : false;        });      }    }  }  // hide measurement  hideMeasurement() {    for (let i = 0; i < this.measures.length; i++) {      for (let j = this.measures[i].length - 1; j >= 0; j--) {        const kids = this.measures[i][j].getChildren();        kids.forEach((kid) => {          if (kid.label) {            kid.label.dispose();          }          kid.dispose(false, true);        });        this.measures[i][j].dispose(true, true);        this.measures[i][j] = null;      }    }    this.measures = [];  }  // update SKU  updateSKU(sku = null) {    if (sku) {      this.sku = sku;      this.updateAmounts();    }  }  // update throughput  updateThroughput(throughput = null) {    if (throughput) {      this.throughput = throughput;      this.updateAmounts();    }  }  // generate store informations  generateStores() {    for (let i = this.stores.length - 1; i >= 0; i--) {      this.stores[i].dispose();      this.stores.splice(i, 1);    }    this.stores = [];    const max = [      this.isHorizontal ? this.area.minZ : this.area.minX,      this.isHorizontal ? this.area.maxZ : this.area.maxX,    ];    const min = max[this.isHorizontal ? 1 : 0];    for (let h = 0; h < this.rackingHighLevel; h++) {      const system = this.transform[5];      for (        let i = 0;        i < (this.isHorizontal ? this.maxCol : this.maxRow);        i++      ) {        let positions = [];        for (let j = 0; j < system.data.length; j++) {          if (            system.data[j][this.isHorizontal ? 1 : 0] === i &&            system.data[j][2] === h          ) {            positions.push(system.position[j]);          }        }        if (positions.length > 1) {          let full = true;          if (positions.length > 2) {            full = false;          }          if (this.isHorizontal) {            if (              positions[0][2] - this.area.minZ > 0.1 ||              this.area.maxZ - positions[1][2] > 0.1            )              full = false;          } else {            if (              positions[0][0] - this.area.minX > 0.1 ||              this.area.maxX - positions[1][0] > 0.1            )              full = false;          }          for (let j = 0; j < this.activedPassthrough.length; j++) {            if (              this.activedPassthrough[j][2].includes(h) &&              this.activedPassthrough[j][1].includes(i)            ) {              full = false;              break;            }          }          const store = new Store(positions, i, h, min, full, this);          this.stores.push(store);        }      }    }  }  // update infos  updateInfos() {    const max = [      this.isHorizontal ? this.area.minZ : this.area.minX,      this.isHorizontal ? this.area.maxZ : this.area.maxX,    ];    // if the icube almost start / end with a x-Track, then remove that x-Track    if (      Math.abs(        max[this.isHorizontal ? 1 : 0] +          (this.isHorizontal ? -1 : 1) *            this.activedXtrackIds[this.activedXtrackIds.length - 1] -          g_xtrackFixedDim / 2 -          max[0]      ) <      g_palletInfo.racking + g_difftoXtrack[g_palletInfo.max]    ) {      this.activedXtrackIds.splice(this.activedXtrackIds.length - 1, 1);    }    if (      Math.abs(        max[this.isHorizontal ? 1 : 0] +          (this.isHorizontal ? -1 : 1) * this.activedXtrackIds[0] +          g_xtrackFixedDim / 2 -          max[1]      ) <      g_palletInfo.racking + g_difftoXtrack[g_palletInfo.max]    ) {      this.activedXtrackIds.splice(0, 1);    }    let xtracks = [...this.activedXtrackIds];    if (xtracks.length > 0) {      let dimChunk = [max[0]];      xtracks = xtracks.sort((a, b) => {        return this.isHorizontal ? b - a : a - b;      });      for (let i = 0; i < xtracks.length; i++) {        const position =          useP(max[this.isHorizontal ? 1 : 0]) +          (this.isHorizontal ? -1 : 1) * useP(xtracks[i]);        dimChunk.push(useP(position - useP(g_xtrackFixedDim) / 2, false));        dimChunk.push(useP(position + useP(g_xtrackFixedDim) / 2, false));      }      dimChunk.push(max[1]);      let cols = [];      let capacity = [];      let uprights = [];      let dimensions = [];      for (let i = 0; i < dimChunk.length; i += 2) {        dimensions.push(dimChunk.slice(i, i + 2));        capacity.push([]);      }      for (let i = 0; i < dimensions.length; i++) {        for (let j = 0; j < g_PalletW.length; j++) {          const dist =            useP(dimensions[i][1]) -            useP(dimensions[i][0]) -            useP(              [0, dimensions.length - 1].includes(i)                ? g_diffToEnd[j]                : g_difftoXtrack[j]            ) -            useP(g_difftoXtrack[j]);          const width =            useP(g_PalletW[j]) +            useP(g_spacingBPallets[j]) +            2 * useP(g_loadPalletOverhang);          const step = _round((dist + useP(g_spacingBPallets[j])) / width);          capacity[i].push(step);        }      }      for (let i = 0; i < dimensions.length; i++) {        const diff =          (useP(dimensions[i][1]) -            useP(dimensions[i][0]) -            useP(g_rackingPole) -            useP(              [0, dimensions.length - 1].includes(i)                ? g_diffToEnd[g_palletInfo.max]                : g_difftoXtrack[g_palletInfo.max]            ) -            useP(g_difftoXtrack[g_palletInfo.max])) /          (useP(g_palletInfo.racking) + useP(g_MinDistUpRights));        let step = Math.floor(diff) + 2;        const localCap = capacity[i][g_palletInfo.max];        // 2 pallets need 2 standers (2 halfs)        if (localCap === 2) step = 3;        // 4 pallets need 3 standers (3 halfs)        if (localCap === 4) step = 4;        // 1 pallet but too much space need 2 standers (2 halfs)        if (          localCap === 1 &&          dimensions[i][1] - dimensions[i][0] >            g_palletInfo.racking +              ([0, dimensions.length - 1].includes(i)                ? g_diffToEnd[g_palletInfo.max]                : g_difftoXtrack[g_palletInfo.max]) +              g_difftoXtrack[g_palletInfo.max]        )          step = 3;        cols.push(step);        // Utils.boxes(new BABYLON.Vector3(this.area.minX, 0, dimensions[i][0]));        // Utils.boxes(new BABYLON.Vector3(this.area.minX, 0, dimensions[i][1]), '#0000ff');      }      for (let i = 0; i < dimensions.length; i++) {        let uprightDist = parseFloat(          (            (useP(dimensions[i][1]) -              useP(dimensions[i][0]) -              useP(g_rackingPole) -              useP([0, dimensions.length - 1].includes(i) ? g_railOutside : 0) -              (cols[i] - 1) * useP(g_palletInfo.racking)) /            useP(cols[i] - 2)          ).toFixed(2)        );        if (!isFinite(uprightDist)) uprightDist = 0;        uprights.push(uprightDist);      }      let k = 0;      const colsArray = [];      for (let i = 0; i < cols.length; i++) {        colsArray.push([]);        for (let j = 0; j < (cols[i] == 1 ? cols[i] : cols[i] - 1); j++) {          colsArray[colsArray.length - 1].push(k);          k++;        }      }      this.infos = {        uprights: uprights,        capacity: capacity,        cols: colsArray,        dimensions: dimensions,      };    } else {      let capacity = [];      for (let j = 0; j < g_PalletW.length; j++) {        const dist = useP(max[1]) - useP(max[0]) - 2 * useP(g_diffToEnd[j]);        const width =          useP(g_PalletW[j]) +          useP(g_spacingBPallets[j]) +          2 * useP(g_loadPalletOverhang);        const step = _round((dist + useP(g_spacingBPallets[j])) / width);        capacity.push(step);      }      const racking = g_palletInfo.racking;      const diff =        (useP(max[1]) -          useP(max[0]) -          2 * useP(racking) -          2 * useP(g_railOutside)) /        (useP(g_palletInfo.racking) + useP(g_MinDistUpRights));      const cols = Math.floor(diff) + 2;      const colsArray = Array.from(Array(cols).keys());      const uprightDist = parseFloat(        (          (useP(max[1]) -            useP(max[0]) -            useP(cols * racking) -            2 * useP(g_railOutside) -            useP(g_rackingPole)) /          useP(cols - 1)        ).toFixed(4)      );      this.infos = {        uprights: [uprightDist],        capacity: [capacity],        cols: [colsArray],        dimensions: [max],      };    }    // console.log(this.infos);  }  getStoreIndex(points) {    let idx = -1;    for (let i = 0; i < this.infos.dimensions.length; i++) {      if (        points[0] >= this.infos.dimensions[i][0] - g_xtrackFixedDim / 2 &&        points[1] <= this.infos.dimensions[i][1] + g_xtrackFixedDim / 2      ) {        idx = i;        break;      }    }    if (idx !== -1) return idx;    else return 0;  }  // update store informations  updateStores() {    this.updateInfos();    this.generateStores();    for (let i = 0; i < this.stores.length; i++) {      this.stores[i].update(        this.activedXtrackIds,        this.activedLiftInfos,        this.activedPillers      );    }  }  // calculate Icube dimensions  updateAmounts() {    // required no of lifts    const palletPerHour = parseInt(      3600 / (60 + (this.area.dimensions[1] * 1000) / 250)    );    this.calculatedLiftsNo = Math.ceil(this.throughput / palletPerHour);    updateLiftAmount(this.calculatedLiftsNo, this.extra.lift);    // required no of xtracks    const noOfRows = this.isHorizontal ? this.maxCol : this.maxRow;    const k2 = _round(      (_round(this.area.dimensions[this.isHorizontal ? 2 : 0], 2) - 1.55) /        (g_palletInfo.width + 0.05)    );    const m4 = noOfRows * this.rackingHighLevel * k2;    const k3 = m4 / this.sku;    const p5 = k2 / 2;    this.calculatedXtracksNo = Math.ceil(p5 / k3);    const dist = parseFloat(      (        _round(this.area.dimensions[this.isHorizontal ? 2 : 0], 2) -        2 * g_diffToEnd[g_palletInfo.max] -        g_PalletW[g_palletInfo.max] -        2 * g_loadPalletOverhang      ).toFixed(3)    );    const width = _round(      g_PalletW[g_palletInfo.max] +        2 * g_difftoXtrack[g_palletInfo.max] +        2 * g_loadPalletOverhang +        g_xtrackFixedDim,      2    );    this.calculatedXtracksNo = Math.min(      this.calculatedXtracksNo,      _round(dist / width)    );    updateXtrackAmount(this.calculatedXtracksNo, this.extra.xtrack);  }  getEstimationPrice() {    if (g_tutorialIsRunning) return;    g_priceChanged++;    // no of xtracks    const xtracks = this.transform[6] ? this.transform[6].position.length : 0;    // default data    let data = {      height_icube: Math.ceil(this.area.dimensions[1]),      sku: this.sku,      moves_per_hour: this.throughput,      overhang: this.palletOverhang * 1000,      xtrack: xtracks,      lifts: this.calculatedLiftsNo + this.extra.lift,    };    // pallet 1    const pallet1_idx = this.palletType.indexOf(Math.max(...this.palletType));    const pallet_1 = {      pallet1_distr: Math.max(...this.palletType) / 100,      pallet1_length:        (g_PalletW[pallet1_idx] + 2 * this.loadPalletOverhang) * 1000,      pallet1_width: g_PalletH[pallet1_idx] * 1000,      pallet1_height: this.palletHeight * 1000,      pallet1_weight: this.palletWeight,    };    data = Object.assign({}, data, pallet_1);    // pallet 2    for (let i = 0; i < this.palletType.length; i++) {      if (i !== pallet1_idx && this.palletType[i] !== 0) {        const pallet_2 = {          pallet2_distr: this.palletType[i] / 100,          pallet2_length: (g_PalletW[i] + 2 * this.loadPalletOverhang) * 1000,          pallet2_width: g_PalletH[i] * 1000,          pallet2_height: this.palletHeight * 1000,          pallet2_weight: this.palletWeight,        };        data = Object.assign({}, data, pallet_2);        break;      }    }    // rows/pallets/layers    const palletData = this.getPalletNoJS(pallet1_idx);    let pPerRow = [];    for (let i = 0; i < palletData.length; i++) {      const rows = palletData[i];      for (let j = 0; j < rows.length; j++) {        if (pPerRow.length === 0) {          pPerRow.push([rows[j], 1]);        } else {          const array = pPerRow.filter(            (e) => e[0][0] === rows[j][0] && e[0][1] === rows[j][1]          );          if (array.length > 0) {            array[0][1]++;          } else {            pPerRow.push([rows[j], 1]);          }        }      }    }    let rows = 0;    let maxPalletNo = 0;    const palletPerRow = {};    for (let i = 0; i < pPerRow.length; i++) {      palletPerRow["rows" + (i + 1)] = pPerRow[i][1];      palletPerRow["pallets" + (i + 1)] = pPerRow[i][0][0];      palletPerRow["layers" + (i + 1)] = pPerRow[i][0][1];      data = Object.assign({}, data, palletPerRow);      rows += pPerRow[i][1];      if (pPerRow[i][0][0] > maxPalletNo) maxPalletNo = pPerRow[i][0][0];    }    // inventory    g_inventory["g_xtrack"] = xtracks;    // required no of carriers    const F2 =      rows *        ((g_PalletH[pallet1_idx] * 1000 +          115 +          2 * this.palletOverhang * 1000) /          1000) +      1; /*width*/    const F3 =      maxPalletNo *      (((g_PalletW[pallet1_idx] + 2 * this.loadPalletOverhang) * 1000 + 20) /        1000); /*depth*/    const palletPerHourC = parseInt(3600 / (120 + (F2 + F3) / 0.96));    this.calculatedCarriersNo = Math.ceil(this.throughput / palletPerHourC);    this.updateCarrier();    updateCarrierAmount(this.calculatedCarriersNo, this.extra.carrier);    $.ajax({      type: "POST",      url: g_BasePath + "home/getPriceFromExcel",      dataType: "json",      data: data,      success: (data) => {        g_priceUpdated++;        if (g_priceChanged === g_priceUpdated) {          $("#waiting").hide();        }        const total = { ...data["total_excluding"] };        delete data["total_excluding"];        const pallets = this.getPalletNoJS();        this.palletPositions = pallets.reduce((a, b) => a + b, 0);        data["racking"]["qty"] = this.palletPositions;        data["extra_carrier"] = {          qty: this.extra.carrier,          val:            this.extra.carrier *            (data["carrier"]["val"] / data["carrier"]["qty"]),        };        total["val"] +=          /*data['extra_lift']['val']*/ +data["extra_carrier"]["val"];        data["total_excluding"] = total;        this.estimatedPrice = data["total_excluding"]["val"];        setPriceTable(data, this);        // inventory        updateInventory();      },      error: (err) => {        //console.log(err.responseText);      },    });  }  getPalletNoJS(palletTypeIdx = -1) {    let palletsNo = palletTypeIdx !== -1 ? [] : [0, 0, 0];    const row = this.isHorizontal ? this.maxCol : this.maxRow;    for (let j = 0; j < row; j++) {      if (palletTypeIdx !== -1) {        palletsNo[j] = [];      }      for (let h = 0; h < this.rackingHighLevel; h++) {        const stores = this.stores.filter((e) => e.row === j && e.height === h);        if (palletTypeIdx !== -1) {          // get number of pallets per row for a specific palletType          let pallNo = 0;          stores.forEach((store) => {            store.capacity.forEach((capacity) => {              pallNo += capacity[palletTypeIdx];            });          });          if (palletsNo[j].length === 0) {            palletsNo[j].push([pallNo, 1]);          } else {            const array = palletsNo[j].filter((e) => e[0] === pallNo);            if (array.length > 0) {              array[0][1]++;            } else {              palletsNo[j].push([pallNo, 1]);            }          }        } else {          stores.forEach((store) => {            store.capacity.forEach((capacity) => {              palletsNo[0] += capacity[0];              palletsNo[1] += capacity[1];              palletsNo[2] += capacity[2];            });          });        }      }    }    if (palletTypeIdx !== -1) return palletsNo;    let palletsNoDistr = [];    for (let i = 0; i < palletsNo.length; i++) {      if (!g_palletInfo.order.includes(i)) {        palletsNo[i] = 0;      }    }    let totalPalletsCount = palletsNo.reduce((a, b) => a + b, 0);    const totalPalletTypes = this.palletType.filter((e) => e !== 0).length;    const palletsCount = _round(totalPalletsCount / totalPalletTypes);    this.palletType.forEach((val, idx) => {      palletsNoDistr[idx] = _round((val * palletsCount) / 100);    });    return palletsNoDistr;  }  // optimize icube dimensions once the draw is done  optimizeRacking() {    //if (this.drawMode === 1 || (this.drawMode === 0 && this.baseLines.length === 4)) {    if (this.stores.length === 0) return;    let xtracks = [];    let min = this.infos.dimensions[0][0];    const prevXtracks = [...this.activedXtrackIds];    const max = this.infos.dimensions[this.infos.dimensions.length - 1][1];    const width =      useP(g_PalletW[g_palletInfo.max]) +      useP(g_spacingBPallets[g_palletInfo.max]) +      2 * useP(g_loadPalletOverhang);    for (let i = 0; i < this.infos.dimensions.length; i++) {      const cap = this.infos.capacity[i][g_palletInfo.max];      let offset = 0;      if ([0, this.infos.dimensions.length - 1].includes(i)) {        offset =          useP(g_diffToEnd[g_palletInfo.max]) +          useP(g_difftoXtrack[g_palletInfo.max]);      } else {        offset = 2 * useP(g_difftoXtrack[g_palletInfo.max]);      }      const length = useP(        useP(min) +          offset +          cap * width -          useP(g_spacingBPallets[g_palletInfo.max]),        false      );      if (i < this.infos.dimensions.length - 1) {        xtracks.push(useP(useP(length) + useP(g_xtrackFixedDim) / 2, false));        min = useP(useP(length) + useP(g_xtrackFixedDim), false);      } else {        min = length;      }    }    const range = [      this.isHorizontal ? this.area.minZ : this.area.minX,      this.isHorizontal ? this.area.maxZ : this.area.maxX,    ];    const toSubtract = useP(useP(max) - useP(min), false);    // console.log(toSubtract)    if (toSubtract <= 0.02) return;    this.activedXtrackIds = xtracks.map((e) =>      parseFloat(        (this.isHorizontal          ? range[1] - e - toSubtract + g_spacingBPallets[g_palletInfo.max] / 2          : e - range[0] + g_spacingBPallets[g_palletInfo.max] / 2        ).toFixed(3)      )    );    this.activedXtrackIds = this.activedXtrackIds.sort((a, b) => {      return this.isHorizontal ? a - b : b - a;    });    this.activedPillers = [];    for (let i = 0; i < this.activedLiftInfos.length; i++) {      for (let j = 0; j < prevXtracks.length; j++) {        if (this.activedLiftInfos[i].length == prevXtracks[j]) {          this.activedLiftInfos[i].length = this.activedXtrackIds[j];          break;        }      }    }    for (let j = 0; j < this.baseLines.length; j++) {      for (let i = 0; i < this.baseLines[j].points.length; i++) {        if (this.isHorizontal) {          if (this.baseLines[j].points[i].z === max) {            this.baseLines[j].points[i].z = parseFloat(              (                this.baseLines[j].points[i].z -                toSubtract +                g_spacingBPallets[g_palletInfo.max]              ).toFixed(3)            );          }        } else {          if (this.baseLines[j].points[i].x === max) {            this.baseLines[j].points[i].x = parseFloat(              (                this.baseLines[j].points[i].x -                toSubtract +                g_spacingBPallets[g_palletInfo.max]              ).toFixed(3)            );          }        }      }      this.baseLines[j].updateBaseline();    }    // optimize racking on the other side    if (!g_optimizeDirectTL) {      for (let j = 0; j < this.baseLines.length; j++) {        for (let i = 0; i < this.baseLines[j].points.length; i++) {          if (this.isHorizontal) {            this.baseLines[j].points[i].z = parseFloat(              (this.baseLines[j].points[i].z + toSubtract).toFixed(3)            );          } else {            this.baseLines[j].points[i].x = parseFloat(              (this.baseLines[j].points[i].x + toSubtract).toFixed(3)            );          }        }        this.baseLines[j].updateBaseline();      }    }    Behavior.add(Behavior.type.optimization);    this.updateRacking(() => {      this.showMeasurement();    });    //}  }}class Store {  constructor(rails, row, height, min, full, icube) {    this.row = row;    this.height = height;    this.min = min;    this.full = full;    this.rails = []; // racking limits    this.dimension = []; // store points => original[original.length - 1]    this.original = []; // original store points => [0] - simple, [1] - xtracks, [2] - lifts, [3] - passth, [4] - pillers    this.capacity = []; // store capacity    this.positions = []; // pallets position    this.ends = [];    this.icube = icube;    this.isHorizontal = icube.isHorizontal;    this.step = icube.isHorizontal ? icube.maxCol : icube.maxRow;    this.init(rails);  }  init(rails) {    this.original[0] = [];    this.rails.push([]);    for (let i = 0; i < rails.length; i++) {      if (i !== 0 && i % 2 === 0) {        this.rails.push([]);      }      this.rails[this.rails.length - 1].push(rails[i]);    }    for (let i = 0; i < this.rails.length; i++) {      let val1, val2;      if (this.isHorizontal) {        val1 = _round(this.rails[i][0][2], 2);        val2 = _round(this.rails[i][1][2], 2);        if (Math.abs(val1 - this.icube.area.minZ) < 1)          val1 = this.icube.area.minZ;        if (Math.abs(val2 - this.icube.area.maxZ) < 1)          val2 = this.icube.area.maxZ;      } else {        val1 = _round(this.rails[i][0][0], 2);        val2 = _round(this.rails[i][1][0], 2);        if (Math.abs(val1 - this.icube.area.minX) < 1)          val1 = this.icube.area.minX;        if (Math.abs(val2 - this.icube.area.maxX) < 1)          val2 = this.icube.area.maxX;      }      this.original[0].push([        parseFloat(val1.toFixed(2)),        parseFloat(val2.toFixed(2)),      ]);      this.dimension = [...this.original[0]];      this.ends.push(parseFloat(val1.toFixed(2)), parseFloat(val2.toFixed(2)));    }    // console.log(this.dimension)    this._updatePropsBasedOnDim();  }  _updatePropsBasedOnDim() {    this.capacity = [];    this.positions = [];    for (let i = 0; i < this.dimension.length; i++) {      this.capacity.push([]);      for (let j = 0; j < g_PalletW.length; j++) {        const dist =          useP(this.dimension[i][1]) -          useP(this.dimension[i][0]) -          useP(            this.ends.includes(this.dimension[i][1])              ? g_diffToEnd[j]              : g_difftoXtrack[j]          ) -          useP(            this.ends.includes(this.dimension[i][0])              ? g_diffToEnd[j]              : g_difftoXtrack[j]          );        const width =          useP(g_PalletW[j]) +          useP(g_spacingBPallets[j]) +          2 * useP(g_loadPalletOverhang);        const step = _round((dist + useP(g_spacingBPallets[j])) / width);        this.capacity[this.capacity.length - 1][j] = step;      }      this.positions.push([[], [], []]);      for (let j = 0; j < g_PalletW.length; j++) {        for (let k = 0; k < this.capacity[i][j]; k++) {          const pos1 =            this.dimension[i][0] +            (this.ends.includes(this.dimension[i][0])              ? g_diffToEnd[j]              : g_difftoXtrack[j]) +            k * g_spacingBPallets[j] +            (k + 1) * (g_PalletW[j] + 2 * g_loadPalletOverhang) -            g_PalletW[j] / 2 -            g_loadPalletOverhang;          this.positions[this.positions.length - 1][j].push([            _round(this.isHorizontal ? this.rails[0][0][0] : pos1, 3),            this.icube.getHeightAtLevel(this.height),            _round(this.isHorizontal ? pos1 : this.rails[0][0][2], 3),          ]);        }      }    }    // console.log(this.capacity)    // console.log(this.positions)    // console.log(this.dimension)  }  update(xtracks, lifts, pillers) {    this.dimension = [...this.original[0]];    if (xtracks.length !== 0) {      this.original[1] = [];      const xtrackScale = xtracks.map(        (e) => this.min + (this.isHorizontal ? -1 : +1) * e      );      for (let i = 0; i < this.dimension.length; i++) {        let points = [this.dimension[i][0], this.dimension[i][1]];        for (let j = 0; j < xtrackScale.length; j++) {          if (            this.dimension[i][0] < xtrackScale[j] &&            this.dimension[i][1] > xtrackScale[j]          ) {            points.push(              _round(xtrackScale[j] - g_xtrackFixedDim / 2, 3),              _round(xtrackScale[j] + g_xtrackFixedDim / 2, 3)            );          }        }        points = points.sort((a, b) => {          return a - b;        });        for (let j = 0; j < points.length; j += 2) {          this.original[1].push([points[j], points[j + 1]]);        }      }      if (this.original[1].length === 0) {        this.original[1] = [...this.original[0]];      }      this.dimension = [...this.original[1]];    } else {      for (let i = this.original.length - 1; i > 0; i--) {        this.original.splice(i, 1);      }    }    const localLifts = lifts.filter((e) => e.index === -1);    if (localLifts.length !== 0) {      this.original[2] = [];      let liftScale = [];      for (let i = 0; i < localLifts.length; i++) {        const lift = { ...localLifts[i] };        lift.scaled = this.min + (this.isHorizontal ? -1 : +1) * lift.length;        lift.scaled = _round(          lift.scaled + (lift.bottomOrTop * g_xtrackFixedDim) / 2,          3        );        liftScale.push(lift);      }      for (let i = 0; i < this.dimension.length; i++) {        let points = [this.dimension[i][0], this.dimension[i][1]];        for (let j = 0; j < liftScale.length; j++) {          if (liftScale[j].row === this.row) {            const liftLength =              g_liftFixedDim + (liftScale[j].preloading === true ? 1.25 : 0);            if (              liftScale[j].scaled >= this.dimension[i][0] &&              liftScale[j].scaled <= this.dimension[i][1]            ) {              if (liftScale[j].scaled === this.dimension[i][0]) {                const dist = parseFloat((points[1] - points[0]).toFixed(3));                if (dist < liftLength) {                  points = [];                } else {                  points[0] += liftLength;                }                points[0] = _round(points[0], 3);              } else {                const dist = parseFloat((points[1] - points[0]).toFixed(3));                if (dist < liftLength) {                  points = [];                } else {                  points[1] -= liftLength;                }                points[1] = _round(points[1], 3);              }              this.full = false;            }          }        }        for (let j = 0; j < points.length; j += 2) {          this.original[2].push([points[j], points[j + 1]]);        }      }      if (this.original[2].length === 0) {        this.original[2] = [...this.original[1]];      }      this.dimension = [...this.original[2]];    } else {      for (let i = this.original.length - 1; i > 1; i--) {        this.original.splice(i, 1);      }    }    if (pillers.length !== 0) {      this.original[3] = [];      let pillerScale = [];      for (let i = 0; i < pillers.length; i++) {        const piller = this.isHorizontal          ? _round(pillers[i].position[1], 3)          : _round(pillers[i].position[0], 3);        pillerScale.push({          scaled: piller,          row: pillers[i].row,          idx: pillers[i].idx,          slotId: pillers[i].slotId,        });      }      for (let i = 0; i < this.dimension.length; i++) {        let points = [this.dimension[i][0], this.dimension[i][1]];        let pilers = pillerScale.filter(          (e) => e.slotId === i && e.row === this.row        );        if (pilers.length > 0) {          pilers = pilers.sort((a, b) => {            return a.idx - b.idx;          });          for (let j = 0; j < pilers.length; j++) {            let minV = _round(              pilers[j].scaled - g_PalletW[g_palletInfo.max] / 3,              3            );            minV = minV < points[0] ? points[0] : minV;            let maxV = _round(              pilers[j].scaled + g_PalletW[g_palletInfo.max] / 3,              3            );            maxV = maxV > points[1] ? points[1] : maxV;            points.push(minV, maxV);          }          this.full = false;        }        points = points.sort((a, b) => {          return a - b;        });        points = points.reverse();        for (let j = points.length - 1; j >= 0; j -= 2) {          if (j > 0) {            if (              Math.abs(points[j] - points[j - 1]) < g_PalletW[g_palletInfo.max]            ) {              points.splice(j, 1);              points.splice(j - 1, 1);            }          }        }        points = points.reverse();        if (points.length > 0) {          for (let j = 0; j < points.length; j += 2) {            this.original[3].push([points[j], points[j + 1]]);          }        } else {          this.original[3].push([]);        }      }      if (this.original[3].length === 0) {        if (this.original[2] && this.original[2].length > 0) {          this.original[3] = [...this.original[2]];        } else {          this.original[3] = [...this.original[1]];        }      }      this.dimension = [...this.original[3]];    } else {      for (let i = this.original.length - 1; i > 2; i--) {        this.original.splice(i, 1);      }    }    this._updatePropsBasedOnDim();    /*for (let i = 0; i < this.dimension.length; i++) {            if (this.isHorizontal) {                Utils.boxes(new BABYLON.Vector3(this.rails[0][0][0], this.icube.getHeightAtLevel(this.height), this.dimension[i][0]), '#0000ff')                Utils.boxes(new BABYLON.Vector3(this.rails[0][0][0], this.icube.getHeightAtLevel(this.height), this.dimension[i][1]))            }            else {                Utils.boxes(new BABYLON.Vector3(this.dimension[i][0], this.icube.getHeightAtLevel(this.height), this.rails[0][0][2]), '#0000ff')                Utils.boxes(new BABYLON.Vector3(this.dimension[i][1], this.icube.getHeightAtLevel(this.height), this.rails[0][0][2]))            }        }*/  }  dispose() {    this.row = -1;    this.height = -1;    this.step = -1;    this.rails = [];    this.dimension = [];    this.capacity = [];    this.isHorizontal = false;    this.uprightDist = 0;  }}class XtrackSelector {  constructor(icube, scene) {    this.icube = icube;    this.scene = scene;    this.engine = scene.getEngine();    this.line = null;    this.buttons = [];    this.xtracks = [];    this.currentXtrack = null;    this.previewPallets = [];    this.labels = [];    this.tooltips = [];    this.offset = 2;    this.max = 0;    this.init();    return this;  }  init() {    const scale = WHDimensions[this.icube.isHorizontal ? 1 : 0] / 10;    let pos = BABYLON.Vector3.Zero();    const range = [      this.icube.isHorizontal ? this.icube.area.minZ : this.icube.area.minX,      this.icube.isHorizontal ? this.icube.area.maxZ : this.icube.area.maxX,    ];    this.max = range;    const dist = Math.abs(range[0] - range[1]);    const center = (range[0] + range[1]) / 2;    if (this.icube.isHorizontal)      pos = new BABYLON.Vector3(this.icube.area.minX - this.offset, 0, center);    else      pos = new BABYLON.Vector3(center, 0, this.icube.area.minZ - this.offset);    // line    this.line = Utils.createLine({      labelScale: 1,      length: parseFloat(Number(dist).toFixed(2)),      color: BABYLON.Color3.FromHexString("#0059a4"),    });    this.line.position = pos.clone();    this.line.rotation.y = this.icube.isHorizontal ? 0 : Math.PI / 2;    for (let i = 0; i < 2; i++) {      const m1 = new BABYLON.TransformNode("m1", this.scene);      if (this.icube.isHorizontal)        m1.position = new BABYLON.Vector3(          pos.x,          0.05,          this.max[i] + ((i == 0 ? -1 : 1) * scale) / 3        );      else        m1.position = new BABYLON.Vector3(          this.max[i] + ((i == 0 ? -1 : 1) * scale) / 3,          0.05,          pos.z        );      m1.setParent(this.line);      const labelPlus = Utils.createButonUI("\uf055");      ggui.addControl(labelPlus);      labelPlus.linkWithMesh(m1);      labelPlus.onPointerUpObservable.add(() => {        this.icube.updateLastAddedXtrack(false);        const pallet3n =          g_diffToEnd[g_palletInfo.max] +          g_difftoXtrack[g_palletInfo.max] +          3 * (g_palletInfo.width + 2 * g_loadPalletOverhang) +          2 * g_spacingBPallets[g_palletInfo.max] +          g_xtrackFixedDim / 2;        const xtrack1 =          (this.max[0] + pallet3n - this.max[this.icube.isHorizontal ? 1 : 0]) /          (this.icube.isHorizontal ? -1 : 1);        const xtrack2 =          (this.max[1] - pallet3n - this.max[this.icube.isHorizontal ? 1 : 0]) /          (this.icube.isHorizontal ? -1 : 1);        const xtrack =          i == 0            ? parseFloat(xtrack1.toFixed(3))            : parseFloat(xtrack2.toFixed(3));        this.currentXtrack = this.addXtrack(xtrack, true);        this.updatePalletsNo();        renderScene();      });      this.buttons.push(labelPlus);      const tooltip = Utils.createTooltipUI("添加新的X轨迹");      tooltip.linkOffsetY = 25;      tooltip.linkOffsetX = -5;      ggui.addControl(tooltip);      tooltip.linkWithMesh(m1);      this.tooltips.push(tooltip);      labelPlus.onPointerEnterObservable.add(() => {        this.tooltips[0].isVisible = true;      });      labelPlus.onPointerOutObservable.add(() => {        this.tooltips[0].isVisible = false;      });    }    for (let i = 0; i < 2; i++) {      const pallet = new BABYLON.Mesh.CreateBox("pallet", 1, this.scene);      pallet.material = matManager.matConveyor_belt;      pallet.setEnabled(false);      pallet.position = pos.clone();      pallet.rotation.y = this.icube.isHorizontal ? 0 : Math.PI / 2;      pallet.scaling = new BABYLON.Vector3(        0.2,        0.1,        g_PalletW[g_palletInfo.max]      );      this.previewPallets.push(pallet);    }  }  /**   * Add this xtrack, movable-true(just added, or edited)-else(otherwise)   * @param {*} xtrack   * @param {*} editable   */  addXtrack(xtrack, movable = false) {    const Xline = Utils.createLine({      labelScale: 1,      length: parseFloat(Number(g_xtrackFixedDim).toFixed(2)),      color: BABYLON.Color3.FromHexString("#0059a4"),    });    Xline.xtrack = xtrack;    Xline.rotation.y = this.icube.isHorizontal ? Math.PI : Math.PI / 2;    const m1 = new BABYLON.TransformNode("m1", scene);    m1.setParent(Xline);    const m2 = new BABYLON.TransformNode("m2", scene);    m2.setParent(Xline);    if (this.icube.isHorizontal) {      m1.position.z = g_xtrackFixedDim / 2;      m2.position.z = -g_xtrackFixedDim / 2;      Xline.position.x = this.line.position.x;      Xline.position.z =        Math.floor(          _round(            this.max[this.icube.isHorizontal ? 1 : 0] +              (this.icube.isHorizontal ? -1 : 1) * xtrack,            3          ) * 200        ) / 200;    } else {      m1.position.x = g_xtrackFixedDim / 2;      m2.position.x = -g_xtrackFixedDim / 2;      Xline.position.z = this.line.position.z;      Xline.position.x =        Math.floor(          _round(            this.max[this.icube.isHorizontal ? 1 : 0] +              (this.icube.isHorizontal ? -1 : 1) * xtrack,            3          ) * 200        ) / 200;    }    Xline.labels = [];    for (let i = 0; i < 4; i++) {      const labelText = Utils.createInputTextUI();      labelText.color = "#f0f0f0";      labelText.isVisible = true;      labelText.width = "45px";      labelText.fontWeight = "600";      labelText.rotation = this.icube.isHorizontal ? -Math.PI / 2 : 0;      this.labels.push(labelText);      ggui.addControl(labelText);      labelText.linkWithMesh(i % 2 === 0 ? m1 : m2);      if (this.icube.isHorizontal) {        labelText.linkOffsetY = (i % 2 === 0 ? 1 : -1) * 25;        labelText.linkOffsetX = (i < 2 ? -0.8 : 1.2) * 8;      } else {        labelText.linkOffsetX = (i % 2 === 0 ? -1 : 1) * 25;        labelText.linkOffsetY = (i < 2 ? -0.8 : 1.2) * 8;      }      Xline.labels.push(labelText);    }    if (movable) {      const labelMove = Utils.createButonUI("\uf0b2");      ggui.addControl(labelMove);      labelMove.linkWithMesh(Xline);      labelMove.linkOffsetY = this.icube.isHorizontal ? 0 : -10;      labelMove.linkOffsetX = this.icube.isHorizontal ? -10 : 0;      labelMove.scaleX = 0.8;      labelMove.scaleY = 0.8;      this.buttons.push(labelMove);      labelMove.isClicked = false;      labelMove.isPointerBlocker = true;      labelMove.onPointerDownObservable.add(() => {        this.scene.activeCamera.detachControl(g_canvas);        labelMove.isClicked = true;        for (let i = 0; i < this.buttons.length; i++) {          this.buttons[i].isPointerBlocker = false;        }      });      labelMove.onPointerUpObservable.add(() => {        this.scene.activeCamera.attachControl(g_canvas, true);        labelMove.isClicked = false;        for (let i = 0; i < this.buttons.length; i++) {          this.buttons[i].isPointerBlocker = true;        }      });      this.scene.onPointerMove = (e) => {        if (labelMove.isClicked) {          const pickinfo = this.scene.pick(            this.scene.pointerX,            this.scene.pointerY,            function (mesh) {              return mesh.id == "floor";            }          );          if (pickinfo.hit) {            let xtrack1;            const currentPos = pickinfo.pickedPoint.clone();            if (this.icube.isHorizontal) {              currentPos.z = this.snapTo(currentPos.z);              Xline.position.z = Utils.round5(_round(currentPos.z, 3));              xtrack1 = Utils.round5(                _round(                  (currentPos.z - this.max[this.icube.isHorizontal ? 1 : 0]) /                    (this.icube.isHorizontal ? -1 : 1),                  3                )              );            } else {              currentPos.x = this.snapTo(currentPos.x);              Xline.position.x = Utils.round5(_round(currentPos.x, 3));              xtrack1 = Utils.round5(                _round(                  (currentPos.x - this.max[this.icube.isHorizontal ? 1 : 0]) /                    (this.icube.isHorizontal ? -1 : 1),                  3                )              );            }            Xline.xtrack = parseFloat(xtrack1.toFixed(3));            this.updatePalletsNo();            renderScene(-1);          }        }      };      const labelConf = Utils.createButonUI("\uf00c");      ggui.addControl(labelConf);      labelConf.linkWithMesh(Xline);      labelConf.linkOffsetY = this.icube.isHorizontal ? 0 : 10;      labelConf.linkOffsetX = this.icube.isHorizontal ? 10 : 0;      labelConf.scaleX = 0.8;      labelConf.scaleY = 0.8;      this.buttons.push(labelConf);      labelConf.onPointerUpObservable.add(() => {        this.removeCurrentXtrack();        if (this.icube.activedXtrackIds.indexOf(Xline.xtrack) < 0) {          this.addXtrack(Xline.xtrack, false);          this.icube.updateXtrackPlacementBySelector(Xline.xtrack);          this.updatePalletsNo();          Behavior.add(Behavior.type.addXtrack);          this.icube.updateRacking(() => {            this.icube.previewProperty("xtrack", false);          });        }        renderScene();      });      Xline.buttons = [labelMove, labelConf];      return Xline;    } else {      const labelEdit = Utils.createButonUI("\uf040");      ggui.addControl(labelEdit);      labelEdit.linkWithMesh(Xline);      labelEdit.linkOffsetY = this.icube.isHorizontal ? 0 : -10;      labelEdit.linkOffsetX = this.icube.isHorizontal ? -10 : 0;      labelEdit.scaleX = 0.8;      labelEdit.scaleY = 0.8;      this.buttons.push(labelEdit);      labelEdit.onPointerUpObservable.add(() => {        for (let i = this.icube.activedLiftInfos.length - 1; i >= 0; i--) {          if (this.icube.activedLiftInfos[i].length === xtrack) {            this.icube.activedLiftInfos.splice(i, 1);          }        }        for (let i = this.icube.activedChainConveyor.length - 1; i >= 0; i--) {          if (this.icube.activedChainConveyor[i].length === xtrack) {            this.icube.activedChainConveyor.splice(i, 1);          }        }        this.icube.updateLastAddedXtrack(false);        this.icube.updateXtrackPlacementBySelector(xtrack);        this.removeXtrack(xtrack);        this.currentXtrack = this.addXtrack(xtrack, true);        this.updatePalletsNo();        renderScene();      });      const labelDelete = Utils.createButonUI("\uf1f8");      ggui.addControl(labelDelete);      labelDelete.linkWithMesh(Xline);      labelDelete.linkOffsetY = this.icube.isHorizontal ? 0 : 10;      labelDelete.linkOffsetX = this.icube.isHorizontal ? 10 : 0;      labelDelete.scaleX = 0.8;      labelDelete.scaleY = 0.8;      this.buttons.push(labelDelete);      labelDelete.onPointerUpObservable.add(() => {        if (this.icube.activedXtrackIds.length === 1) {          Utils.logg(            "Your racking needs at least one X-track element",            "custom"          );          return;        }        for (let i = this.icube.activedLiftInfos.length - 1; i >= 0; i--) {          if (this.icube.activedLiftInfos[i].length === xtrack) {            this.icube.activedLiftInfos.splice(i, 1);          }        }        for (let i = this.icube.activedChainConveyor.length - 1; i >= 0; i--) {          if (this.icube.activedChainConveyor[i].length === xtrack) {            this.icube.activedChainConveyor.splice(i, 1);          }        }        this.icube.updateLastAddedXtrack(false);        this.icube.updateXtrackPlacementBySelector(xtrack);        this.removeXtrack(xtrack);        Behavior.add(Behavior.type.addXtrack);        renderScene();        this.icube.updateRacking(() => {          this.icube.previewProperty("xtrack", false);        });      });      Xline.buttons = [labelEdit, labelDelete];      this.xtracks.push(Xline);      Xline.labels[0].isVisible = false;      Xline.labels[1].isVisible = false;      const xtrackScale = this.icube.isHorizontal        ? Xline.position.z        : Xline.position.x;      const p1 =        Math.floor(_round(xtrackScale - g_xtrackFixedDim / 2, 3) * 200) / 200;      const p2 =        Math.floor(_round(xtrackScale + g_xtrackFixedDim / 2, 3) * 200) / 200;      Xline.labels[2].isVisible = true;      Xline.labels[2].value = _round(Math.abs(p1 - this.max[0]), 3);      Xline.labels[2].text = Xline.labels[2].value + unitChar;      Xline.labels[3].isVisible = true;      Xline.labels[3].value = _round(Math.abs(this.max[1] - p2), 3);      Xline.labels[3].text = Xline.labels[3].value + unitChar;      if (        Math.abs(xtrackScale - this.max[0]) >        Math.abs(xtrackScale - this.max[1])      ) {        Xline.labels[2].isVisible = false;      } else {        Xline.labels[3].isVisible = false;      }    }  }  /**   * Remove this xtrack   * @param {*} xtrack   */  removeXtrack(xtrack) {    for (let i = 0; i < this.xtracks.length; i++) {      if (this.xtracks[i].xtrack === xtrack) {        this.xtracks[i].buttons.forEach((button) => {          button.dispose();        });        this.xtracks[i].labels.forEach((label) => {          label.dispose();        });        this.xtracks[i].dispose();        this.xtracks.splice(i, 1);        break;      }    }  }  /**   * Remove selected xtrack(just added, or edited)   */  removeCurrentXtrack() {    if (this.currentXtrack) {      this.currentXtrack.buttons.forEach((button) => {        button.dispose();      });      this.currentXtrack.labels.forEach((label) => {        label.dispose();      });      this.previewPallets.forEach((pallet) => {        pallet.setEnabled(false);      });      this.currentXtrack.dispose();      this.currentXtrack = null;    }  }  /**   * Position xtrack selector at 1,2,3 pallets from start   * @param {*} currentPos   */  snapTo(currentPos) {    const pallet1 =      g_diffToEnd[g_palletInfo.max] +      g_difftoXtrack[g_palletInfo.max] +      (g_palletInfo.width + 2 * g_loadPalletOverhang) +      g_xtrackFixedDim / 2;    const pallet2 =      pallet1 +      (g_palletInfo.width + 2 * g_loadPalletOverhang) +      g_spacingBPallets[g_palletInfo.max];    const pallet3 =      pallet2 +      (g_palletInfo.width + 2 * g_loadPalletOverhang) +      g_spacingBPallets[g_palletInfo.max];    if (currentPos < this.max[0] + pallet1) {      currentPos = this.max[0] + pallet1;    } else {      if (        currentPos >= this.max[0] + pallet1 &&        currentPos < this.max[0] + pallet2      ) {        currentPos = this.max[0] + pallet2;      } else {        if (          currentPos >= this.max[0] + pallet2 &&          currentPos < this.max[0] + pallet3        ) {          currentPos = this.max[0] + pallet3;        }      }    }    if (currentPos > this.max[1] - pallet1) {      currentPos = this.max[1] - pallet1;    } else {      if (        currentPos <= this.max[1] - pallet1 &&        currentPos > this.max[1] - pallet2      ) {        currentPos = this.max[1] - pallet2;      } else {        if (          currentPos <= this.max[1] - pallet2 &&          currentPos > this.max[1] - pallet3        ) {          currentPos = this.max[1] - pallet3;        }      }    }    return currentPos;  }  /**   * Show number of pallets, difference   */  updatePalletsNo() {    let xtrackScale = this.icube.activedXtrackIds.map((e) =>      _round(        this.max[this.icube.isHorizontal ? 1 : 0] +          (this.icube.isHorizontal ? -1 : +1) * e,        3      )    );    xtrackScale = this.icube.isHorizontal ? xtrackScale.reverse() : xtrackScale;    const xtrack = this.currentXtrack      ? this.currentXtrack      : this.xtracks[this.xtracks.length - 1];    let intvals = [this.max[0]];    for (let i = 0; i < xtrackScale.length; i++) {      intvals.push(        useP(useP(xtrackScale[i]) - useP(g_xtrackFixedDim) / 2, false),        useP(useP(xtrackScale[i]) + useP(g_xtrackFixedDim) / 2, false)      );    }    intvals.push(this.max[1]);    let dims = [];    for (let i = 0; i < intvals.length; i += 2) {      if (this.icube.isHorizontal) {        if (          xtrack.position.z >= intvals[i] &&          xtrack.position.z <= intvals[i + 1]        ) {          dims.push(intvals[i], intvals[i + 1]);          break;        }      } else {        if (          xtrack.position.x >= intvals[i] &&          xtrack.position.x <= intvals[i + 1]        ) {          dims.push(intvals[i], intvals[i + 1]);          break;        }      }    }    if (dims.length > 0) {      let p1, p2;      if (this.icube.isHorizontal) {        p1 = useP(useP(xtrack.position.z) - useP(g_xtrackFixedDim) / 2, false);        p2 = useP(useP(xtrack.position.z) + useP(g_xtrackFixedDim) / 2, false);      } else {        p1 = useP(useP(xtrack.position.x) - useP(g_xtrackFixedDim) / 2, false);        p2 = useP(useP(xtrack.position.x) + useP(g_xtrackFixedDim) / 2, false);      }      const dimension = [        [dims[0], p1],        [p2, dims[1]],      ];      for (let i = 0; i < dimension.length; i++) {        const positions = [];        const j = g_palletInfo.max;        const dist =          useP(dimension[i][1]) -          useP(dimension[i][0]) -          useP(            this.max.includes(dimension[i][1])              ? g_diffToEnd[j]              : g_difftoXtrack[j]          ) -          useP(            this.max.includes(dimension[i][0])              ? g_diffToEnd[j]              : g_difftoXtrack[j]          );        const width =          useP(g_PalletW[j]) +          useP(g_spacingBPallets[j]) +          2 * useP(g_loadPalletOverhang);        const capacity = _round((dist + useP(g_spacingBPallets[j])) / width);        for (let k = 0; k < capacity; k++) {          const pos1 =            dimension[i][0] +            (this.max.includes(dimension[i][0])              ? g_diffToEnd[j]              : g_difftoXtrack[j]) +            k * g_spacingBPallets[j] +            (k + 1) * (g_PalletW[j] + 2 * g_loadPalletOverhang) -            g_PalletW[j] / 2 -            g_loadPalletOverhang;          positions.push(_round(pos1, 3));        }        xtrack.labels[i].text = capacity + " pallets";        xtrack.labels[i + 2].value = _round(          dimension[i][1] - dimension[i][0],          3        );        xtrack.labels[i + 2].text = xtrack.labels[i + 2].value + unitChar;        if (positions.length > 0) {          const diff =            useP(dist, false) -            positions.length * (g_PalletW[j] + 2 * g_loadPalletOverhang) -            (positions.length - 1) * g_spacingBPallets[j];          if (diff > 0.01) {            this.previewPallets[i].scaling.z = _round(diff, 3);            this.previewPallets[i].setEnabled(true);            if (this.icube.isHorizontal) {              this.previewPallets[i].position.z = dimension[i][1] - diff / 2;            } else {              this.previewPallets[i].position.x = dimension[i][1] - diff / 2;            }          } else {            this.previewPallets[i].setEnabled(false);          }        } else {          this.previewPallets[i].setEnabled(false);        }      }    }  }  /**   * Remove selector with all it's xtracks   */  dispose() {    for (let i = this.buttons.length - 1; i >= 0; i--) {      this.buttons[i].dispose();      this.buttons.splice(i, 1);    }    if (this.line) this.line.dispose();    for (let i = this.xtracks.length - 1; i >= 0; i--) {      this.xtracks[i].dispose();      this.xtracks.splice(i, 1);    }    for (let i = this.previewPallets.length - 1; i >= 0; i--) {      this.previewPallets[i].dispose();      this.previewPallets.splice(i, 1);    }    for (let i = this.labels.length - 1; i >= 0; i--) {      this.labels[i].dispose();      this.labels.splice(i, 1);    }    for (let i = this.tooltips.length - 1; i >= 0; i--) {      this.tooltips[i].dispose();      this.tooltips.splice(i, 1);    }    this.scene = null;    this.engine = null;  }}
 |