2
0

escher.js 161 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644464546464647464846494650465146524653465446554656465746584659466046614662466346644665466646674668466946704671467246734674467546764677467846794680468146824683468446854686468746884689469046914692469346944695469646974698469947004701470247034704470547064707470847094710471147124713471447154716471747184719472047214722472347244725472647274728472947304731473247334734473547364737473847394740474147424743474447454746474747484749475047514752475347544755475647574758475947604761476247634764476547664767476847694770477147724773477447754776477747784779478047814782478347844785478647874788478947904791479247934794479547964797479847994800480148024803480448054806480748084809481048114812481348144815481648174818481948204821482248234824482548264827482848294830483148324833483448354836483748384839484048414842484348444845484648474848484948504851485248534854485548564857485848594860486148624863486448654866486748684869487048714872487348744875487648774878487948804881488248834884488548864887488848894890489148924893489448954896489748984899490049014902490349044905490649074908490949104911491249134914491549164917491849194920492149224923492449254926492749284929493049314932493349344935493649374938493949404941494249434944494549464947494849494950495149524953495449554956495749584959496049614962496349644965496649674968496949704971497249734974497549764977497849794980498149824983498449854986498749884989499049914992499349944995499649974998499950005001500250035004500550065007500850095010501150125013501450155016501750185019502050215022502350245025502650275028502950305031503250335034503550365037503850395040504150425043504450455046504750485049505050515052505350545055505650575058505950605061506250635064506550665067506850695070507150725073507450755076507750785079508050815082508350845085508650875088508950905091509250935094509550965097509850995100510151025103510451055106510751085109511051115112511351145115511651175118511951205121512251235124512551265127512851295130513151325133513451355136513751385139514051415142514351445145514651475148514951505151515251535154515551565157515851595160516151625163516451655166516751685169517051715172517351745175517651775178517951805181518251835184518551865187518851895190519151925193519451955196519751985199520052015202520352045205520652075208520952105211521252135214521552165217521852195220522152225223522452255226522752285229523052315232523352345235523652375238523952405241524252435244524552465247524852495250525152525253525452555256525752585259526052615262526352645265526652675268526952705271527252735274527552765277527852795280528152825283528452855286528752885289529052915292529352945295529652975298529953005301530253035304530553065307530853095310531153125313531453155316531753185319532053215322532353245325532653275328532953305331533253335334533553365337533853395340534153425343534453455346534753485349535053515352535353545355535653575358535953605361536253635364536553665367536853695370537153725373537453755376537753785379538053815382538353845385538653875388538953905391539253935394539553965397539853995400540154025403540454055406540754085409541054115412541354145415541654175418541954205421542254235424542554265427542854295430543154325433543454355436543754385439544054415442544354445445544654475448544954505451545254535454545554565457545854595460546154625463546454655466546754685469547054715472547354745475547654775478547954805481548254835484548554865487548854895490549154925493549454955496549754985499550055015502550355045505550655075508550955105511551255135514551555165517551855195520552155225523552455255526552755285529553055315532553355345535553655375538553955405541554255435544554555465547554855495550555155525553555455555556555755585559556055615562556355645565556655675568556955705571557255735574557555765577557855795580558155825583558455855586558755885589559055915592559355945595559655975598559956005601560256035604560556065607560856095610561156125613561456155616561756185619562056215622562356245625562656275628562956305631563256335634563556365637563856395640564156425643564456455646564756485649565056515652565356545655565656575658565956605661566256635664566556665667566856695670567156725673567456755676567756785679568056815682568356845685568656875688568956905691569256935694569556965697569856995700570157025703570457055706570757085709571057115712571357145715571657175718571957205721572257235724572557265727572857295730573157325733573457355736573757385739574057415742574357445745574657475748574957505751575257535754575557565757575857595760576157625763576457655766576757685769577057715772577357745775577657775778577957805781578257835784578557865787578857895790579157925793579457955796579757985799580058015802580358045805580658075808580958105811581258135814581558165817581858195820582158225823582458255826582758285829583058315832583358345835583658375838583958405841584258435844584558465847584858495850585158525853585458555856585758585859586058615862586358645865586658675868586958705871587258735874587558765877587858795880588158825883588458855886588758885889589058915892589358945895589658975898589959005901590259035904590559065907590859095910591159125913591459155916591759185919592059215922592359245925592659275928592959305931593259335934593559365937593859395940594159425943594459455946594759485949595059515952595359545955595659575958595959605961596259635964596559665967596859695970597159725973597459755976597759785979598059815982598359845985598659875988598959905991599259935994599559965997599859996000600160026003600460056006600760086009601060116012601360146015601660176018601960206021602260236024602560266027602860296030603160326033603460356036603760386039604060416042604360446045604660476048604960506051605260536054605560566057605860596060606160626063606460656066606760686069607060716072607360746075607660776078607960806081608260836084608560866087608860896090609160926093609460956096609760986099610061016102610361046105610661076108610961106111611261136114611561166117611861196120612161226123612461256126612761286129613061316132613361346135613661376138613961406141614261436144614561466147614861496150615161526153615461556156615761586159616061616162616361646165616661676168616961706171617261736174617561766177617861796180618161826183618461856186618761886189619061916192619361946195619661976198619962006201620262036204620562066207620862096210621162126213621462156216621762186219622062216222622362246225622662276228622962306231623262336234623562366237623862396240624162426243624462456246624762486249625062516252625362546255625662576258625962606261626262636264626562666267626862696270627162726273627462756276627762786279628062816282628362846285628662876288628962906291629262936294629562966297629862996300630163026303630463056306630763086309631063116312631363146315631663176318631963206321632263236324632563266327632863296330633163326333633463356336633763386339634063416342634363446345634663476348634963506351635263536354635563566357635863596360636163626363636463656366636763686369637063716372637363746375637663776378637963806381638263836384638563866387638863896390639163926393639463956396639763986399640064016402640364046405640664076408640964106411641264136414641564166417641864196420642164226423642464256426642764286429643064316432643364346435643664376438643964406441644264436444644564466447644864496450645164526453645464556456645764586459646064616462646364646465646664676468646964706471647264736474647564766477647864796480648164826483648464856486648764886489649064916492649364946495649664976498649965006501650265036504650565066507650865096510651165126513651465156516651765186519652065216522652365246525652665276528652965306531653265336534653565366537653865396540654165426543654465456546654765486549655065516552655365546555655665576558655965606561656265636564656565666567656865696570657165726573657465756576657765786579658065816582658365846585658665876588658965906591659265936594659565966597659865996600660166026603660466056606660766086609661066116612661366146615661666176618661966206621662266236624662566266627662866296630663166326633
  1. (function (global, factory) {
  2. typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
  3. typeof define === 'function' && define.amd ? define(['exports'], factory) :
  4. (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.Escher = {}));
  5. })(this, (function (exports) { 'use strict';
  6. /**
  7. * EventManager is used to manager DOM events creating and destruction in a single function call.
  8. *
  9. * It is used by objects to make it easier to add and remove events from global DOM objects.
  10. *
  11. * @class
  12. */
  13. function EventManager()
  14. {
  15. /**
  16. * Stores all events in the manager, their target and callback.
  17. *
  18. * Format [target, event, callback, active]
  19. *
  20. * @type {Array}
  21. */
  22. this.events = [];
  23. }
  24. /**
  25. * Add new event to the manager.
  26. *
  27. * @param {Element} target Event target element.
  28. * @param {String} event Event name.
  29. * @param {Function} callback Callback function.
  30. */
  31. EventManager.prototype.add = function(target, event, callback)
  32. {
  33. this.events.push([target, event, callback, false]);
  34. };
  35. /**
  36. * Destroys this manager and remove all events.
  37. */
  38. EventManager.prototype.clear = function()
  39. {
  40. this.destroy();
  41. this.events = [];
  42. };
  43. /**
  44. * Creates all events in this manager.
  45. */
  46. EventManager.prototype.create = function()
  47. {
  48. for(var i = 0; i < this.events.length; i++)
  49. {
  50. var event = this.events[i];
  51. event[0].addEventListener(event[1], event[2]);
  52. event[3] = true;
  53. }
  54. };
  55. /**
  56. * Removes all events in this manager.
  57. */
  58. EventManager.prototype.destroy = function()
  59. {
  60. for(var i = 0; i < this.events.length; i++)
  61. {
  62. var event = this.events[i];
  63. event[0].removeEventListener(event[1], event[2]);
  64. event[3] = false;
  65. }
  66. };
  67. /**
  68. * Class representing a 2D vector. A 2D vector is an ordered pair of numbers (labeled x and y), which can be used to represent points in space, directions, etc.
  69. *
  70. * @class
  71. * @param {number} x X value.
  72. * @param {number} y Y value.
  73. */
  74. function Vector2(x, y)
  75. {
  76. this.x = x || 0;
  77. this.y = y || 0;
  78. }
  79. /**
  80. * Set vector x and y values.
  81. *
  82. * @param {number} x X value.
  83. * @param {number} y Y value.
  84. */
  85. Vector2.prototype.set = function(x, y)
  86. {
  87. this.x = x;
  88. this.y = y;
  89. };
  90. /**
  91. * Set a scalar value into the x and y values.
  92. *
  93. * @param {number} scalar Scalar value.
  94. */
  95. Vector2.prototype.setScalar = function(scalar)
  96. {
  97. this.x = scalar;
  98. this.y = scalar;
  99. };
  100. /**
  101. * Create a clone of this vector object.
  102. *
  103. * @return {Vector2} A new vector with the same values as this one.
  104. */
  105. Vector2.prototype.clone = function()
  106. {
  107. return new Vector2(this.x, this.y);
  108. };
  109. /**
  110. * Copy the content of another vector into this one.
  111. *
  112. * @param {Vector2} v
  113. */
  114. Vector2.prototype.copy = function(v)
  115. {
  116. this.x = v.x;
  117. this.y = v.y;
  118. };
  119. /**
  120. * Add the content of another vector to this one.
  121. *
  122. * @param {Vector2} v The other vector.
  123. */
  124. Vector2.prototype.add = function(v)
  125. {
  126. this.x += v.x;
  127. this.y += v.y;
  128. };
  129. /**
  130. * Add a scalar value to booth vector components.
  131. *
  132. * @param {number} s Scalar value.
  133. */
  134. Vector2.prototype.addScalar = function(s)
  135. {
  136. this.x += s;
  137. this.y += s;
  138. };
  139. /**
  140. * Add two vectors and store the result in this vector.
  141. *
  142. * @param {Vector2} a The first vector.
  143. * @param {Vector2} b The second vector.
  144. */
  145. Vector2.prototype.addVectors = function(a, b)
  146. {
  147. this.x = a.x + b.x;
  148. this.y = a.y + b.y;
  149. };
  150. /**
  151. * Scale a vector components and add the result to this vector.
  152. *
  153. * @param {Vector2} v The other vector.
  154. * @param {number} s Scalar value.
  155. */
  156. Vector2.prototype.addScaledVector = function(v, s)
  157. {
  158. this.x += v.x * s;
  159. this.y += v.y * s;
  160. };
  161. /**
  162. * Subtract the content of another vector to this one.
  163. *
  164. * @param {Vector2} v The other vector.
  165. */
  166. Vector2.prototype.sub = function(v)
  167. {
  168. this.x -= v.x;
  169. this.y -= v.y;
  170. };
  171. /**
  172. * Subtract a scalar value to booth vector components.
  173. *
  174. * @param {number} s Scalar value.
  175. */
  176. Vector2.prototype.subScalar = function(s)
  177. {
  178. this.x -= s;
  179. this.y -= s;
  180. };
  181. /**
  182. * Subtract two vectors and store the result in this vector.
  183. *
  184. * @param {Vector2} a The first vector.
  185. * @param {Vector2} b The second vector.
  186. */
  187. Vector2.prototype.subVectors = function(a, b)
  188. {
  189. this.x = a.x - b.x;
  190. this.y = a.y - b.y;
  191. };
  192. /**
  193. * Multiply the content of another vector to this one.
  194. *
  195. * @param {Vector2} v The other vector.
  196. */
  197. Vector2.prototype.multiply = function(v)
  198. {
  199. this.x *= v.x;
  200. this.y *= v.y;
  201. };
  202. /**
  203. * Multiply a scalar value by booth vector components.
  204. *
  205. * @param {number} scalar Scalar value.
  206. */
  207. Vector2.prototype.multiplyScalar = function(scalar)
  208. {
  209. this.x *= scalar;
  210. this.y *= scalar;
  211. };
  212. /**
  213. * Divide the content of another vector from this one.
  214. *
  215. * @param {Vector2} v
  216. */
  217. Vector2.prototype.divide = function(v)
  218. {
  219. this.x /= v.x;
  220. this.y /= v.y;
  221. };
  222. /**
  223. * Divide a scalar value by booth vector components.
  224. *
  225. * @param {number} s
  226. */
  227. Vector2.prototype.divideScalar = function(scalar)
  228. {
  229. return this.multiplyScalar(1 / scalar);
  230. };
  231. /**
  232. * Set the minimum of x and y coordinates between two vectors.
  233. *
  234. * X is set as the min between this vector and the other vector.
  235. *
  236. * @param {Vector2} v
  237. */
  238. Vector2.prototype.min = function(v)
  239. {
  240. this.x = Math.min(this.x, v.x);
  241. this.y = Math.min(this.y, v.y);
  242. };
  243. /**
  244. * Set the maximum of x and y coordinates between two vectors.
  245. *
  246. * X is set as the max between this vector and the other vector.
  247. *
  248. * @param {Vector2} v
  249. */
  250. Vector2.prototype.max = function(v)
  251. {
  252. this.x = Math.max(this.x, v.x);
  253. this.y = Math.max(this.y, v.y);
  254. };
  255. /**
  256. * Clamp the vector coordinates to the range defined by two vectors.
  257. *
  258. * Applied to x and y independently.
  259. *
  260. * @param {Vector2} min Minimum value.
  261. * @param {Vector2} max Maximum value.
  262. */
  263. Vector2.prototype.clamp = function(min, max)
  264. {
  265. // assumes min < max, componentwise
  266. this.x = Math.max(min.x, Math.min(max.x, this.x));
  267. this.y = Math.max(min.y, Math.min(max.y, this.y));
  268. };
  269. /**
  270. * Clamp the vector coordinates to the range defined by two scalars.
  271. *
  272. * @param {number} minVal Minimum value.
  273. * @param {number} maxVal Maximum value.
  274. */
  275. Vector2.prototype.clampScalar = function(minVal, maxVal)
  276. {
  277. this.x = Math.max(minVal, Math.min(maxVal, this.x));
  278. this.y = Math.max(minVal, Math.min(maxVal, this.y));
  279. };
  280. Vector2.prototype.clampLength = function(min, max)
  281. {
  282. var length = this.length();
  283. return this.divideScalar(length || 1).multiplyScalar(Math.max(min, Math.min(max, length)));
  284. };
  285. /**
  286. * Round the vector coordinates to integer by flooring to the smaller integer.
  287. */
  288. Vector2.prototype.floor = function()
  289. {
  290. this.x = Math.floor(this.x);
  291. this.y = Math.floor(this.y);
  292. };
  293. /**
  294. * Round the vector coordinates to integer by ceiling to the bigger integer.
  295. */
  296. Vector2.prototype.ceil = function()
  297. {
  298. this.x = Math.ceil(this.x);
  299. this.y = Math.ceil(this.y);
  300. };
  301. /**
  302. * Round the vector coordinates to their closest integer.
  303. */
  304. Vector2.prototype.round = function()
  305. {
  306. this.x = Math.round(this.x);
  307. this.y = Math.round(this.y);
  308. };
  309. /**
  310. * Negate the coordinates of this vector.
  311. */
  312. Vector2.prototype.negate = function()
  313. {
  314. this.x = -this.x;
  315. this.y = -this.y;
  316. return this;
  317. };
  318. /**
  319. * Dot multiplication between this vector and another vector.
  320. *
  321. * @param {Vector2} vector
  322. * @return {number} Result of the dot multiplication.
  323. */
  324. Vector2.prototype.dot = function(v)
  325. {
  326. return this.x * v.x + this.y * v.y;
  327. };
  328. /**
  329. * Cross multiplication between this vector and another vector.
  330. *
  331. * @param {Vector2} vector
  332. * @return {number} Result of the cross multiplication.
  333. */
  334. Vector2.prototype.cross = function(v)
  335. {
  336. return this.x * v.y - this.y * v.x;
  337. };
  338. /**
  339. * Squared length of the vector.
  340. *
  341. * Faster for comparions.
  342. *
  343. * @return {number} Squared length of the vector.
  344. */
  345. Vector2.prototype.lengthSq = function()
  346. {
  347. return this.x * this.x + this.y * this.y;
  348. };
  349. /**
  350. * Length of the vector.
  351. *
  352. * @return {number} Length of the vector.
  353. */
  354. Vector2.prototype.length = function()
  355. {
  356. return Math.sqrt(this.x * this.x + this.y * this.y);
  357. };
  358. /**
  359. * Manhattan length of the vector.
  360. *
  361. * @return {number} Manhattan length of the vector.
  362. */
  363. Vector2.prototype.manhattanLength = function()
  364. {
  365. return Math.abs(this.x) + Math.abs(this.y);
  366. };
  367. /**
  368. * Normalize the vector (make it length one).
  369. *
  370. * @return {Vector2} This vector.
  371. */
  372. Vector2.prototype.normalize = function()
  373. {
  374. return this.divideScalar(this.length() || 1);
  375. };
  376. /**
  377. * Computes the angle in radians with respect to the positive x-axis.
  378. *
  379. * @param {boolean} forcePositive If true, the angle will be forced to be positive.
  380. * @return {number} Angle in radians.
  381. */
  382. Vector2.prototype.angle = function(forcePositive)
  383. {
  384. var angle = Math.atan2(this.y, this.x);
  385. if(forcePositive && angle < 0)
  386. {
  387. angle += 2 * Math.PI;
  388. }
  389. return angle;
  390. };
  391. /**
  392. * Distance between two vector positions.
  393. *
  394. * @param {Vector2} v Vector to compute the distance to.
  395. * @return {number} Distance between the two vectors.
  396. */
  397. Vector2.prototype.distanceTo = function(v)
  398. {
  399. return Math.sqrt(this.distanceToSquared(v));
  400. };
  401. /**
  402. * Distance between two vector positions squared.
  403. *
  404. * Faster for comparisons.
  405. *
  406. * @param {Vector2} v Vector to compute the distance to.
  407. * @return {number} Distance between the two vectors squared.
  408. */
  409. Vector2.prototype.distanceToSquared = function(v)
  410. {
  411. var dx = this.x - v.x;
  412. var dy = this.y - v.y;
  413. return dx * dx + dy * dy;
  414. };
  415. /**
  416. * Manhattan distance between two vector positions.
  417. *
  418. * @param {Vector2} v Vector to compute the distance to.
  419. * @return {number} Manhattan distance between the two vectors.
  420. */
  421. Vector2.prototype.manhattanDistanceTo = function(v)
  422. {
  423. return Math.abs(this.x - v.x) + Math.abs(this.y - v.y);
  424. };
  425. /**
  426. * Scale the vector to have a defined length value.
  427. *
  428. * @param {number} length Length to scale the vector to.
  429. * @return {Vector2} This vector.
  430. */
  431. Vector2.prototype.setLength = function(length)
  432. {
  433. return this.normalize().multiplyScalar(length);
  434. };
  435. /**
  436. * Lerp this vector to another vector.
  437. *
  438. * @param {Vector2} v Vector to lerp to.
  439. * @param {number} alpha Lerp factor.
  440. */
  441. Vector2.prototype.lerp = function(v, alpha)
  442. {
  443. this.x += (v.x - this.x) * alpha;
  444. this.y += (v.y - this.y) * alpha;
  445. };
  446. /**
  447. * Lerp between this vector and another vector.
  448. *
  449. * @param {Vector2} v1 Vector to lerp from.
  450. * @param {Vector2} v2 Vector to lerp to.
  451. * @param {number} alpha Lerp factor.
  452. * @return {Vector2} This vector.
  453. *
  454. Vector2.prototype.lerpVectors = function(v1, v2, alpha)
  455. {
  456. return this.subVectors(v2, v1).multiplyScalar(alpha).add(v1);
  457. };
  458. /**
  459. * Check if two vectors are equal.
  460. *
  461. * @param {Vector2} v Vector to compare with.
  462. */
  463. Vector2.prototype.equals = function(v)
  464. {
  465. return ((v.x === this.x) && (v.y === this.y));
  466. };
  467. /**
  468. * Set vector value from array [x, y].
  469. *
  470. * The vector can be converted to array using the toArray() method.
  471. *
  472. * @param {number[]} array Array to set the vector value from.
  473. */
  474. Vector2.prototype.fromArray = function(array)
  475. {
  476. this.set(array[0], array[1]);
  477. };
  478. /**
  479. * Convert this vector to an array. Useful for serialization and storage.
  480. *
  481. * Values stored as [x, y].
  482. *
  483. * @return {number[]} Array containing the values of the vector.
  484. */
  485. Vector2.prototype.toArray = function()
  486. {
  487. return [this.x, this.y];
  488. };
  489. /**
  490. * Rotate the vector around a central point.
  491. *
  492. * @param {Vector2} center Point to rotate around.
  493. * @param {number} angle Angle in radians.
  494. */
  495. Vector2.prototype.rotateAround = function(center, angle)
  496. {
  497. var c = Math.cos(angle);
  498. var s = Math.sin(angle);
  499. var x = this.x - center.x;
  500. var y = this.y - center.y;
  501. this.x = x * c - y * s + center.x;
  502. this.y = x * s + y * c + center.y;
  503. };
  504. /**
  505. * 2D 3x2 transformation matrix, used to represent linear geometric transformations over objects.
  506. *
  507. * The values of the matrix are stored as numeric array. The matrix can be applied to the canvas or DOM elements using CSS transforms.
  508. *
  509. * @class
  510. * @param {number[]} values Array of matrix values by row, needs to have exactly 6 values. Default is the identity matrix.
  511. */
  512. function Matrix(values)
  513. {
  514. if(values !== undefined)
  515. {
  516. /**
  517. * Array that contains the matrix data by row. This matrix should have 6 values.
  518. *
  519. * Matrix can be directly edited by accessing this attribute.
  520. *
  521. * @type {number[]}
  522. */
  523. this.m = values;
  524. }
  525. else
  526. {
  527. this.identity();
  528. }
  529. }
  530. /**
  531. * Copy the content of another matrix and store in this one.
  532. *
  533. * @param {Matrix} mat Matrix to copy values from.
  534. */
  535. Matrix.prototype.copy = function(mat)
  536. {
  537. this.m = mat.m.slice(0);
  538. };
  539. /**
  540. * Create a new matrix object with a copy of the content of this one.
  541. *
  542. * @return {Matrix} Copy of this matrix.
  543. */
  544. Matrix.prototype.clone = function()
  545. {
  546. return new Matrix(this.m.slice(0))
  547. };
  548. /**
  549. * Reset this matrix to identity.
  550. */
  551. Matrix.prototype.identity = function()
  552. {
  553. this.m = [1, 0, 0, 1, 0, 0];
  554. };
  555. /**
  556. * Multiply another matrix by this one and store the result.
  557. *
  558. * @param {Matrix} mat Matrix to multiply by.
  559. */
  560. Matrix.prototype.multiply = function(mat)
  561. {
  562. var m0 = this.m[0] * mat.m[0] + this.m[2] * mat.m[1];
  563. var m1 = this.m[1] * mat.m[0] + this.m[3] * mat.m[1];
  564. var m2 = this.m[0] * mat.m[2] + this.m[2] * mat.m[3];
  565. var m3 = this.m[1] * mat.m[2] + this.m[3] * mat.m[3];
  566. var m4 = this.m[0] * mat.m[4] + this.m[2] * mat.m[5] + this.m[4];
  567. var m5 = this.m[1] * mat.m[4] + this.m[3] * mat.m[5] + this.m[5];
  568. this.m = [m0, m1, m2, m3, m4, m5];
  569. };
  570. /**
  571. * Premultiply another matrix by this one and store the result.
  572. *
  573. * @param {Matrix} mat Matrix to premultiply by.
  574. */
  575. Matrix.prototype.premultiply = function(mat)
  576. {
  577. var m0 = mat.m[0] * this.m[0] + mat.m[2] * this.m[1];
  578. var m1 = mat.m[1] * this.m[0] + mat.m[3] * this.m[1];
  579. var m2 = mat.m[0] * this.m[2] + mat.m[2] * this.m[3];
  580. var m3 = mat.m[1] * this.m[2] + mat.m[3] * this.m[3];
  581. var m4 = mat.m[0] * this.m[4] + mat.m[2] * this.m[5] + mat.m[4];
  582. var m5 = mat.m[1] * this.m[4] + mat.m[3] * this.m[5] + mat.m[5];
  583. this.m = [m0, m1, m2, m3, m4, m5];
  584. };
  585. /**
  586. * Compose this transformation matrix with position scale and rotation and origin point.
  587. *
  588. * @param {number} px Position X
  589. * @param {number} py Position Y
  590. * @param {number} sx Scale X
  591. * @param {number} sy Scale Y
  592. * @param {number} ox Origin X (applied before scale and rotation)
  593. * @param {number} oy Origin Y (applied before scale and rotation)
  594. * @param {number} rot Rotation angle (radians).
  595. */
  596. Matrix.prototype.compose = function(px, py, sx, sy, ox, oy, rot)
  597. {
  598. // Position
  599. this.m = [1, 0, 0, 1, px, py];
  600. // Rotation
  601. if(rot !== 0)
  602. {
  603. var c = Math.cos(rot);
  604. var s = Math.sin(rot);
  605. this.multiply(new Matrix([c, s, -s, c, 0, 0]));
  606. }
  607. // Scale
  608. if(sx !== 1 || sy !== 1)
  609. {
  610. this.scale(sx, sy);
  611. }
  612. // Origin
  613. if(ox !== 0 || oy !== 0)
  614. {
  615. this.multiply(new Matrix([1, 0, 0, 1, -ox, -oy]));
  616. }
  617. };
  618. /**
  619. * Apply translation to this matrix.
  620. *
  621. * Adds position over the transformation already stored in the matrix.
  622. *
  623. * @param {number} x
  624. * @param {number} y
  625. */
  626. Matrix.prototype.translate = function(x, y)
  627. {
  628. this.m[4] += this.m[0] * x + this.m[2] * y;
  629. this.m[5] += this.m[1] * x + this.m[3] * y;
  630. };
  631. /**
  632. * Apply rotation to this matrix.
  633. *
  634. * @param {number} rad Angle to rotate the matrix in radians.
  635. */
  636. Matrix.prototype.rotate = function(rad)
  637. {
  638. var c = Math.cos(rad);
  639. var s = Math.sin(rad);
  640. var m11 = this.m[0] * c + this.m[2] * s;
  641. var m12 = this.m[1] * c + this.m[3] * s;
  642. var m21 = this.m[0] * -s + this.m[2] * c;
  643. var m22 = this.m[1] * -s + this.m[3] * c;
  644. this.m[0] = m11;
  645. this.m[1] = m12;
  646. this.m[2] = m21;
  647. this.m[3] = m22;
  648. };
  649. /**
  650. * Apply scale to this matrix.
  651. *
  652. * @param {number} sx
  653. * @param {number} sy
  654. */
  655. Matrix.prototype.scale = function(sx, sy)
  656. {
  657. this.m[0] *= sx;
  658. this.m[1] *= sx;
  659. this.m[2] *= sy;
  660. this.m[3] *= sy;
  661. };
  662. /**
  663. * Set the position of the transformation matrix.
  664. *
  665. * @param {number} x
  666. * @param {number} y
  667. */
  668. Matrix.prototype.setPosition = function(x, y)
  669. {
  670. this.m[4] = x;
  671. this.m[5] = y;
  672. };
  673. /**
  674. * Extract the scale from the transformation matrix.
  675. *
  676. * @return {Vector2} Scale of the matrix transformation.
  677. */
  678. Matrix.prototype.getScale = function()
  679. {
  680. return new Vector2(this.m[0], this.m[3]);
  681. };
  682. /**
  683. * Extract the position from the transformation matrix.
  684. *
  685. * @return {Vector2} Position of the matrix transformation.
  686. */
  687. Matrix.prototype.getPosition = function()
  688. {
  689. return new Vector2(this.m[4], this.m[5]);
  690. };
  691. /**
  692. * Apply skew to this matrix.
  693. *
  694. * @param {number} radianX
  695. * @param {number} radianY
  696. */
  697. Matrix.prototype.skew = function(radianX, radianY)
  698. {
  699. this.multiply(new Matrix([1, Math.tan(radianY), Math.tan(radianX), 1, 0, 0]));
  700. };
  701. /**
  702. * Get the matrix determinant.
  703. *
  704. * @return {number} Determinant of this matrix.
  705. */
  706. Matrix.prototype.determinant = function()
  707. {
  708. return 1 / (this.m[0] * this.m[3] - this.m[1] * this.m[2]);
  709. };
  710. /**
  711. * Get the inverse matrix.
  712. *
  713. * @return {Matrix} New matrix instance containing the inverse matrix.
  714. */
  715. Matrix.prototype.getInverse = function()
  716. {
  717. var d = this.determinant();
  718. return new Matrix([this.m[3] * d, -this.m[1] * d, -this.m[2] * d, this.m[0] * d, d * (this.m[2] * this.m[5] - this.m[3] * this.m[4]), d * (this.m[1] * this.m[4] - this.m[0] * this.m[5])]);
  719. };
  720. /**
  721. * Transform a point using this matrix.
  722. *
  723. * @param {Vector2} p Point to be transformed.
  724. * @return {Vector2} Transformed point.
  725. */
  726. Matrix.prototype.transformPoint = function(p)
  727. {
  728. var px = p.x * this.m[0] + p.y * this.m[2] + this.m[4];
  729. var py = p.x * this.m[1] + p.y * this.m[3] + this.m[5];
  730. return new Vector2(px, py);
  731. };
  732. /**
  733. * Set a canvas context to use this transformation.
  734. *
  735. * @param {CanvasRenderingContext2D} context Canvas context to apply this matrix transform.
  736. */
  737. Matrix.prototype.setContextTransform = function(context)
  738. {
  739. context.setTransform(this.m[0], this.m[1], this.m[2], this.m[3], this.m[4], this.m[5]);
  740. };
  741. /**
  742. * Transform on top of the current context transformation.
  743. *
  744. * @param {CanvasRenderingContext2D} context Canvas context to apply this matrix transform.
  745. */
  746. Matrix.prototype.tranformContext = function(context)
  747. {
  748. context.transform(this.m[0], this.m[1], this.m[2], this.m[3], this.m[4], this.m[5]);
  749. };
  750. /**
  751. * Generate a CSS transform string that can be applied to the transform style of any DOM element.
  752. *
  753. * @returns {string} CSS transform matrix.
  754. */
  755. Matrix.prototype.cssTransform = function()
  756. {
  757. return "matrix(" + this.m[0] + "," + this.m[1] + "," + this.m[2] + "," + this.m[3] + "," + this.m[4] + "," + this.m[5] + ")";
  758. };
  759. /**
  760. * Class to implement UUID generation methods.
  761. *
  762. * @class
  763. */
  764. function UUID(){}
  765. /**
  766. * Generates a new random UUID v4 as string.
  767. *
  768. * @static
  769. * @return {string} UUID generated as string.
  770. */
  771. UUID.generate = (function ()
  772. {
  773. var lut = [];
  774. for(var i = 0; i < 256; i++)
  775. {
  776. lut[i] = (i < 16 ? "0" : "") + (i).toString(16);
  777. }
  778. return function generateUUID()
  779. {
  780. var d0 = Math.random() * 0XFFFFFFFF | 0;
  781. var d1 = Math.random() * 0XFFFFFFFF | 0;
  782. var d2 = Math.random() * 0XFFFFFFFF | 0;
  783. var d3 = Math.random() * 0XFFFFFFFF | 0;
  784. var uuid = lut[d0 & 0xff] + lut[d0 >> 8 & 0xff] + lut[d0 >> 16 & 0xff] + lut[d0 >> 24 & 0xff] + "-" +
  785. lut[d1 & 0xff] + lut[d1 >> 8 & 0xff] + "-" + lut[d1 >> 16 & 0x0f | 0x40] + lut[d1 >> 24 & 0xff] + "-" +
  786. lut[d2 & 0x3f | 0x80] + lut[d2 >> 8 & 0xff] + "-" + lut[d2 >> 16 & 0xff] + lut[d2 >> 24 & 0xff] +
  787. lut[d3 & 0xff] + lut[d3 >> 8 & 0xff] + lut[d3 >> 16 & 0xff] + lut[d3 >> 24 & 0xff];
  788. return uuid.toUpperCase();
  789. };
  790. })();
  791. /**
  792. * Base object class, implements all the object positioning and scaling features.
  793. *
  794. * Stores all the base properties shared between all objects as the position, transformation properties, children etc.
  795. *
  796. * Object2D object can be used as a group to the other objects drawn.
  797. *
  798. * @class
  799. */
  800. function Object2D()
  801. {
  802. /**
  803. * UUID of the object.
  804. *
  805. * @type {string}
  806. */
  807. this.uuid = UUID.generate();
  808. /**
  809. * List of children objects attached to the object.
  810. *
  811. * @type {Object2D[]}
  812. */
  813. this.children = [];
  814. /**
  815. * Parent object, the object position is affected by its parent position.
  816. *
  817. * @type {Object2D}
  818. */
  819. this.parent = null;
  820. /**
  821. * Depth level in the object tree, objects with higher depth are drawn on top.
  822. *
  823. * The layer value is considered first.
  824. *
  825. * @type {number}
  826. */
  827. this.level = 0;
  828. /**
  829. * Position of the object.
  830. *
  831. * The world position of the object is affected by its parent transform.
  832. *
  833. * @type {Vector2}
  834. */
  835. this.position = new Vector2(0, 0);
  836. /**
  837. * Origin of the object used as point of rotation.
  838. *
  839. * @type {Vector2}
  840. */
  841. this.origin = new Vector2(0, 0);
  842. /**
  843. * Scale of the object.
  844. *
  845. * The world scale of the object is affected by the parent transform.
  846. *
  847. * @type {Vector2}
  848. */
  849. this.scale = new Vector2(1, 1);
  850. /**
  851. * Rotation of the object relative to its center.
  852. *
  853. * The world rotation of the object is affected by the parent transform.
  854. *
  855. * @type {number}
  856. */
  857. this.rotation = 0.0;
  858. /**
  859. * Indicates if the object is visible.
  860. *
  861. * @type {boolean}
  862. */
  863. this.visible = true;
  864. /**
  865. * Layer of this object, objects are sorted by layer value.
  866. *
  867. * Lower layer value is draw first, higher layer value is drawn on top.
  868. *
  869. * @type {number}
  870. */
  871. this.layer = 0;
  872. /**
  873. * Local transformation matrix applied to the object.
  874. *
  875. * @type {Matrix}
  876. */
  877. this.matrix = new Matrix();
  878. /**
  879. * Global transformation matrix multiplied by the parent matrix.
  880. *
  881. * Used to transform the object before projecting into screen coordinates.
  882. *
  883. * @type {Matrix}
  884. */
  885. this.globalMatrix = new Matrix();
  886. /**
  887. * Inverse of the global (world) transform matrix.
  888. *
  889. * Used to convert pointer input points (viewport space) into object coordinates.
  890. *
  891. * @type {Matrix}
  892. */
  893. this.inverseGlobalMatrix = new Matrix();
  894. /**
  895. * Mask objects being applied to this object. Used to mask/subtract portions of this object when rendering.
  896. *
  897. * Multiple masks can be used simultaneously. Same mask might be reused for multiple objects.
  898. *
  899. * @type {Mask[]}
  900. */
  901. this.masks = [];
  902. /**
  903. * Indicates if the transform matrix should be automatically updated every frame.
  904. *
  905. * Set this false for better performance. But if you do so dont forget to set matrixNeedsUpdate every time that a transform attribute is changed.
  906. *
  907. * @type {boolean}
  908. */
  909. this.matrixAutoUpdate = true;
  910. /**
  911. * Indicates if the matrix needs to be updated, should be set true after changes to the object position, scale or rotation.
  912. *
  913. * The matrix is updated before rendering the object, after the matrix is updated this attribute is automatically reset to false.
  914. *
  915. * @type {boolean}
  916. */
  917. this.matrixNeedsUpdate = true;
  918. /**
  919. * Draggable controls if its possible to drag the object around. Set this true to enable dragging events on this object.
  920. *
  921. * The onPointerDrag callback is used to update the state of the object while being dragged, by default it just updates the object position.
  922. *
  923. * @type {boolean}
  924. */
  925. this.draggable = false;
  926. /**
  927. * Indicates if this object uses pointer events.
  928. *
  929. * Can be set false to skip the pointer interaction events, better performance if pointer events are not required.
  930. *
  931. * @type {boolean}
  932. */
  933. this.pointerEvents = true;
  934. /**
  935. * Flag to indicate whether this object ignores the viewport transformation.
  936. *
  937. * @type {boolean}
  938. */
  939. this.ignoreViewport = false;
  940. /**
  941. * Flag to indicate if the context of canvas should be saved before render.
  942. *
  943. * @type {boolean}
  944. */
  945. this.saveContextState = true;
  946. /**
  947. * Flag to indicate if the context of canvas should be restored after render.
  948. *
  949. * @type {boolean}
  950. */
  951. this.restoreContextState = true;
  952. /**
  953. * Flag indicating if the pointer is inside of the element.
  954. *
  955. * Used to control object event.
  956. *
  957. * @type {boolean}
  958. */
  959. this.pointerInside = false;
  960. /**
  961. * Flag to indicate if the object is currently being dragged.
  962. *
  963. * @type {boolean}
  964. */
  965. this.beingDragged = false;
  966. /**
  967. * Indicates if the object should be serialized or not as a child of another object.
  968. *
  969. * Used to prevent duplicate serialization data on custom objects. Should be set false for objects added on constructor.
  970. *
  971. * @type {boolean}
  972. */
  973. this.serializable = true;
  974. }
  975. Object2D.prototype.constructor = Object2D;
  976. /**
  977. * Type of the object, used for data serialization and/or checking the object type.
  978. *
  979. * The name used should match the object constructor name. But it is not required.
  980. *
  981. * If this type is from an external library you can add the library name to the object type name to prevent collisions.
  982. *
  983. * @type {string}
  984. */
  985. Object2D.prototype.type = "Object2D";
  986. /**
  987. * List of available object types known by the application. Stores the object constructor by object type.
  988. *
  989. * Newly created types should be introduced in this map for data serialization support.
  990. *
  991. * New object types should be added using the Object2D.register() method.
  992. *
  993. * @static
  994. * @type {Map<string, Function>}
  995. */
  996. Object2D.types = new Map([[Object2D.prototype.type, Object2D]]);
  997. /**
  998. * Register a object type into the application. Associates the type string to the object constructor.
  999. *
  1000. * Should be called for every new object class implemented if you want to be able to serialize and parse data.
  1001. *
  1002. * @static
  1003. * @param {Function} constructor Object constructor.
  1004. * @param {string} type Object type name.
  1005. */
  1006. Object2D.register = function(constructor, type)
  1007. {
  1008. Object2D.types.set(type, constructor);
  1009. };
  1010. /**
  1011. * Check if a point in world coordinates intersects this object or its children and get a list of the objects intersected.
  1012. *
  1013. * @param {Vector2} point Point in world coordinates.
  1014. * @param {Object2D[]} list List of objects intersected passed to children objects recursively.
  1015. * @return {Object2D[]} List of object intersected by this point.
  1016. */
  1017. Object2D.prototype.getWorldPointIntersections = function(point, list)
  1018. {
  1019. if(list === undefined)
  1020. {
  1021. list = [];
  1022. }
  1023. // Calculate the pointer position in the object coordinates
  1024. var localPoint = this.inverseGlobalMatrix.transformPoint(point);
  1025. if(this.isInside(localPoint))
  1026. {
  1027. list.push(this);
  1028. }
  1029. // Iterate trough the children
  1030. for(var i = 0; i < this.children.length; i++)
  1031. {
  1032. this.children[i].getWorldPointIntersections(point, list);
  1033. }
  1034. return list;
  1035. };
  1036. /**
  1037. * Check if a point in world coordinates intersects this object or some of its children.
  1038. *
  1039. * @param {Vector2} point Point in world coordinates.
  1040. * @param {boolean} recursive If set to true it will also check intersections with the object children.
  1041. * @return {boolean} Returns true if the point in inside of the object.
  1042. */
  1043. Object2D.prototype.isWorldPointInside = function(point, recursive)
  1044. {
  1045. // Calculate the pointer position in the object coordinates
  1046. var localPoint = this.inverseGlobalMatrix.transformPoint(point);
  1047. if(this.isInside(localPoint))
  1048. {
  1049. return true;
  1050. }
  1051. // Iterate trough the children
  1052. if(recursive)
  1053. {
  1054. for(var i = 0; i < this.children.length; i++)
  1055. {
  1056. if(this.children[i].isWorldPointInside(point, true))
  1057. {
  1058. return true;
  1059. }
  1060. }
  1061. }
  1062. return false;
  1063. };
  1064. /**
  1065. * Destroy the object, removes it from the parent object.
  1066. */
  1067. Object2D.prototype.destroy = function()
  1068. {
  1069. if(this.parent !== null)
  1070. {
  1071. this.parent.remove(this);
  1072. }
  1073. };
  1074. /**
  1075. * Traverse the object tree and run a function for all objects.
  1076. *
  1077. * @param {Function} callback Callback function that receives the object as parameter.
  1078. */
  1079. Object2D.prototype.traverse = function(callback)
  1080. {
  1081. callback(this);
  1082. for(var i = 0; i < this.children.length; i++)
  1083. {
  1084. this.children[i].traverse(callback);
  1085. }
  1086. };
  1087. /**
  1088. * Get a object from its children list by its UUID.
  1089. *
  1090. * @param {string} uuid UUID of the object to get.
  1091. * @return {Object2D} The object that has the UUID specified, null if the object was not found.
  1092. */
  1093. Object2D.prototype.getChildByUUID = function(uuid)
  1094. {
  1095. var object = null;
  1096. this.traverse(function(child)
  1097. {
  1098. if(child.uuid === uuid)
  1099. {
  1100. object = child;
  1101. }
  1102. });
  1103. return object;
  1104. };
  1105. /**
  1106. * Attach a children to this object.
  1107. *
  1108. * The object is set as children of this object and the transformations applied to this object are traversed to its children.
  1109. *
  1110. * @param {Object2D} object Object to attach to this object.
  1111. */
  1112. Object2D.prototype.add = function(object)
  1113. {
  1114. object.parent = this;
  1115. object.level = this.level + 1;
  1116. object.traverse(function(child)
  1117. {
  1118. if(child.onAdd !== null)
  1119. {
  1120. child.onAdd(this);
  1121. }
  1122. });
  1123. this.children.push(object);
  1124. };
  1125. /**
  1126. * Remove object from the children list.
  1127. *
  1128. * Resets the parent of the object to null and resets its level.
  1129. *
  1130. * @param {Object2D} children Object to be removed.
  1131. */
  1132. Object2D.prototype.remove = function(children)
  1133. {
  1134. var index = this.children.indexOf(children);
  1135. if(index !== -1)
  1136. {
  1137. var object = this.children[index];
  1138. object.parent = null;
  1139. object.level = 0;
  1140. object.traverse(function(child)
  1141. {
  1142. if(child.onRemove !== null)
  1143. {
  1144. child.onRemove(this);
  1145. }
  1146. });
  1147. this.children.splice(index, 1);
  1148. }
  1149. };
  1150. /**
  1151. * Check if a point is inside of the object. Used by the renderer check for pointer collision (required for the object to properly process pointer events).
  1152. *
  1153. * Point should be in local object coordinates.
  1154. *
  1155. * To check if a point in world coordinates intersects the object the inverseGlobalMatrix should be applied to that point before calling this method.
  1156. *
  1157. * @param {Vector2} point Point in local object coordinates.
  1158. * @return {boolean} True if the point is inside of the object.
  1159. */
  1160. Object2D.prototype.isInside = function(point)
  1161. {
  1162. return false;
  1163. };
  1164. /**
  1165. * Update the transformation matrix of the object.
  1166. *
  1167. * @param {CanvasRenderingContext2D} context Canvas 2d drawing context.
  1168. */
  1169. Object2D.prototype.updateMatrix = function(context)
  1170. {
  1171. if(this.matrixAutoUpdate || this.matrixNeedsUpdate)
  1172. {
  1173. this.matrix.compose(this.position.x, this.position.y, this.scale.x, this.scale.y, this.origin.x, this.origin.y, this.rotation);
  1174. this.globalMatrix.copy(this.matrix);
  1175. if(this.parent !== null)
  1176. {
  1177. this.globalMatrix.premultiply(this.parent.globalMatrix);
  1178. }
  1179. this.inverseGlobalMatrix = this.globalMatrix.getInverse();
  1180. this.matrixNeedsUpdate = false;
  1181. }
  1182. };
  1183. /**
  1184. * Apply the transform to the rendering context, it is assumed that the viewport transform is pre-applied to the context.
  1185. *
  1186. * This is called before style() and draw(). It can also be used for some pre-rendering logic.
  1187. *
  1188. * @param {CanvasRenderingContext2D} context Canvas 2d drawing context.
  1189. * @param {Viewport} viewport Viewport applied to the canvas.
  1190. * @param {Element} canvas DOM canvas element where the content is being drawn.
  1191. * @param {Renderer} renderer Renderer object being used to draw the object into the canvas.
  1192. */
  1193. Object2D.prototype.transform = function(context, viewport, canvas, renderer)
  1194. {
  1195. this.globalMatrix.tranformContext(context);
  1196. };
  1197. /**
  1198. * Style is called right before draw() it should not draw any content into the canvas, all context styling should be applied here (colors, fonts, etc).
  1199. *
  1200. * The draw() and style() methods can be useful for objects that share the same styling attributes but are drawing differently.
  1201. *
  1202. * Should be implemented by underlying classes.
  1203. *
  1204. * @param {CanvasRenderingContext2D} context Canvas 2d drawing context.
  1205. * @param {Viewport} viewport Viewport used to view the canvas content.
  1206. * @param {Element} canvas DOM canvas element where the content is being drawn.
  1207. */
  1208. Object2D.prototype.style = null; // function(context, viewport, canvas){};
  1209. /**
  1210. * Draw the object into the canvas, this is called transform() and style(), should be where the content is actually drawn into the canvas.
  1211. *
  1212. * Should be implemented by underlying classes.
  1213. *
  1214. * @param {CanvasRenderingContext2D} context Canvas 2d drawing context.
  1215. * @param {Viewport} viewport Viewport used to view the canvas content.
  1216. * @param {Element} canvas DOM canvas element where the content is being drawn.
  1217. */
  1218. Object2D.prototype.draw = null; // function(context, viewport, canvas){};
  1219. /**
  1220. * Callback method while the object is being dragged across the screen.
  1221. *
  1222. * By default is adds the delta value to the object position (making it follow the mouse movement).
  1223. *
  1224. * Delta is the movement of the pointer already translated into local object coordinates.
  1225. *
  1226. * To detect when the object drag stops the onPointerDragEnd() method can be used.
  1227. *
  1228. * @param {Pointer} pointer Pointer object that receives the user input.
  1229. * @param {Viewport} viewport Viewport where the object is drawn.
  1230. * @param {Vector2} delta Pointer movement diff in world space since the last frame.
  1231. * @param {Vector2} positionWorld Position of the dragging pointer in world coordinates.
  1232. */
  1233. Object2D.prototype.onPointerDrag = function(pointer, viewport, delta, positionWorld)
  1234. {
  1235. this.position.add(delta);
  1236. };
  1237. /**
  1238. * Callback method called when the pointer drag start after the button was pressed
  1239. *
  1240. * @param {Pointer} pointer Pointer object that receives the user input.
  1241. * @param {Viewport} viewport Viewport where the object is drawn.
  1242. */
  1243. Object2D.prototype.onPointerDragStart = null;
  1244. /**
  1245. * Callback method called when the pointer drag ends after the button has been released.
  1246. *
  1247. * @param {Pointer} pointer Pointer object that receives the user input.
  1248. * @param {Viewport} viewport Viewport where the object is drawn.
  1249. */
  1250. Object2D.prototype.onPointerDragEnd = null;
  1251. /**
  1252. * Method called when the object its added to a parent.
  1253. *
  1254. * @param {Object2D} parent Parent object were it was added.
  1255. */
  1256. Object2D.prototype.onAdd = null;
  1257. /**
  1258. * Method called when the object gets removed from its parent
  1259. *
  1260. * @param {Object2D} parent Parent object from were the object is being removed.
  1261. */
  1262. Object2D.prototype.onRemove = null;
  1263. /**
  1264. * Callback method called every time before the object is draw into the canvas.
  1265. *
  1266. * Should be used to run object logic, any preparation code, move the object, etc.
  1267. *
  1268. * This method is called for every object before rendering.
  1269. */
  1270. Object2D.prototype.onUpdate = null;
  1271. /**
  1272. * Callback method called when the pointer enters the object.
  1273. *
  1274. * It is not called while the pointer is inside of the object, just on the first time that the pointer enters the object for that use onPointerOver()
  1275. *
  1276. * @param {Pointer} pointer Pointer object that receives the user input.
  1277. * @param {Viewport} viewport Viewport where the object is drawn.
  1278. */
  1279. Object2D.prototype.onPointerEnter = null;
  1280. /**
  1281. * Method called when the was inside of the object and leaves the object.
  1282. *
  1283. * @param {Pointer} pointer Pointer object that receives the user input.
  1284. * @param {Viewport} viewport Viewport where the object is drawn.
  1285. */
  1286. Object2D.prototype.onPointerLeave = null;
  1287. /**
  1288. * Method while the pointer is over (inside) of the object.
  1289. *
  1290. * @param {Pointer} pointer Pointer object that receives the user input.
  1291. * @param {Viewport} viewport Viewport where the object is drawn.
  1292. */
  1293. Object2D.prototype.onPointerOver = null;
  1294. /**
  1295. * Method called while the pointer button is pressed.
  1296. *
  1297. * @param {Pointer} pointer Pointer object that receives the user input.
  1298. * @param {Viewport} viewport Viewport where the object is drawn.
  1299. */
  1300. Object2D.prototype.onButtonPressed = null;
  1301. /**
  1302. * Method called while the pointer button is double clicked.
  1303. *
  1304. * @param {Pointer} pointer Pointer object that receives the user input.
  1305. * @param {Viewport} viewport Viewport where the object is drawn.
  1306. */
  1307. Object2D.prototype.onDoubleClick = null;
  1308. /**
  1309. * Callback method called when the pointer button is pressed down (single time).
  1310. *
  1311. * @param {Pointer} pointer Pointer object that receives the user input.
  1312. * @param {Viewport} viewport Viewport where the object is drawn.
  1313. */
  1314. Object2D.prototype.onButtonDown = null;
  1315. /**
  1316. * Method called when the pointer button is released (single time).
  1317. *
  1318. * @param {Pointer} pointer Pointer object that receives the user input.
  1319. * @param {Viewport} viewport Viewport where the object is drawn.
  1320. */
  1321. Object2D.prototype.onButtonUp = null;
  1322. /**
  1323. * Serialize the object data into a JSON object. That can be written into a file, sent using HTTP request etc.
  1324. *
  1325. * All required attributes to recreate the object in its current state should be stored. Relations between children should be stored by their UUID only.
  1326. *
  1327. * Data has to be parsed back into a usable object.
  1328. *
  1329. * @param {boolean} recursive If set false the children list is not serialized, otherwise all children are serialized.
  1330. * @return {Object} Serialized object data.
  1331. */
  1332. Object2D.prototype.serialize = function(recursive)
  1333. {
  1334. var data = {
  1335. uuid: this.uuid,
  1336. type: this.type,
  1337. position: this.position.toArray(),
  1338. origin: this.origin.toArray(),
  1339. scale: this.scale.toArray(),
  1340. rotation: this.rotation,
  1341. visible: this.visible,
  1342. layer: this.layer,
  1343. matrix: this.matrix.m,
  1344. globalMatrix: this.globalMatrix.m,
  1345. inverseGlobalMatrix: this.inverseGlobalMatrix.m,
  1346. matrixAutoUpdate: this.matrixAutoUpdate,
  1347. draggable: this.draggable,
  1348. pointerEvents: this.pointerEvents,
  1349. ignoreViewport: this.ignoreViewport,
  1350. saveContextState: this.saveContextState,
  1351. restoreContextState: this.restoreContextState,
  1352. children: [],
  1353. masks: []
  1354. };
  1355. if(recursive !== false)
  1356. {
  1357. for(var i = 0; i < this.children.length; i++)
  1358. {
  1359. if(this.children[i].serializable)
  1360. {
  1361. data.children.push(this.children[i].serialize());
  1362. }
  1363. }
  1364. }
  1365. for(var i = 0; i < this.masks.length; i++)
  1366. {
  1367. data.masks.push(this.masks[i].uuid);
  1368. }
  1369. return data;
  1370. };
  1371. /**
  1372. * Parse serialized object data and fill the object attributes.
  1373. *
  1374. * Implementations of this method should only load the attributes added to the structure, the based method already loads common attributes.
  1375. *
  1376. * Dont forget to register object types using the Object2D.register() method.
  1377. *
  1378. * @param {Object} data Object data loaded from JSON.
  1379. * @param {Object2D} root Root object being loaded can be used to get references to other objects.
  1380. */
  1381. Object2D.prototype.parse = function(data, root)
  1382. {
  1383. this.uuid = data.uuid;
  1384. this.position.fromArray(data.position);
  1385. this.origin.fromArray(data.origin);
  1386. this.scale.fromArray(data.scale);
  1387. this.rotation = data.rotation;
  1388. this.visible = data.visible;
  1389. this.layer = data.layer;
  1390. this.matrix = new Matrix(data.matrix);
  1391. this.globalMatrix = new Matrix(data.globalMatrix);
  1392. this.inverseGlobalMatrix = new Matrix(data.inverseGlobalMatrix);
  1393. this.matrixAutoUpdate = data.matrixAutoUpdate;
  1394. this.draggable = data.draggable;
  1395. this.pointerEvents = data.pointerEvents;
  1396. this.ignoreViewport = data.ignoreViewport;
  1397. this.saveContextState = data.saveContextState;
  1398. this.restoreContextState = data.restoreContextState;
  1399. for(var i = 0; i < this.masks.length; i++)
  1400. {
  1401. data.masks.push(root.getChildByUUID(data.masks[i]));
  1402. }
  1403. };
  1404. /**
  1405. * Create objects from serialized object data into the proper data structures.
  1406. *
  1407. * All objects should implement serialization methods to serialize and load data properly.
  1408. *
  1409. * First all objects instances are created to ensure that object trying to get references to other object can have the data accessible, only then the parse method is called.
  1410. *
  1411. * @static
  1412. * @param {Object} data Object data loaded from JSON.
  1413. * @return {Object2D} Parsed object data.
  1414. */
  1415. Object2D.parse = function(data)
  1416. {
  1417. // List of objects created stored as pairs of object, data to be later parsed.
  1418. var objects = [];
  1419. // Parse all objects from the data object recursively and create the correct instances.
  1420. function createObjectInstances(data)
  1421. {
  1422. if(!Object2D.types.has(data.type))
  1423. {
  1424. throw new Error("Object type " + data.type + " unknown. Cannot parse data.");
  1425. }
  1426. var Constructor = Object2D.types.get(data.type);
  1427. var object = new Constructor();
  1428. object.uuid = data.uuid;
  1429. objects.push({object: object, data: data});
  1430. for(var i = 0; i < data.children.length; i++)
  1431. {
  1432. object.add(createObjectInstances(data.children[i]));
  1433. }
  1434. return object;
  1435. }
  1436. var root = createObjectInstances(data);
  1437. // Parse objects data
  1438. for(var i = 0; i < objects.length; i++)
  1439. {
  1440. objects[i].object.parse(objects[i].data, root);
  1441. }
  1442. return root;
  1443. };
  1444. /**
  1445. * Key is used by Keyboard, Pointer, etc, to represent a key state.
  1446. *
  1447. * @class
  1448. */
  1449. function Key()
  1450. {
  1451. /**
  1452. * Indicates if this key is currently pressed.
  1453. *
  1454. * @type {boolean}
  1455. */
  1456. this.pressed = false;
  1457. /**
  1458. * Indicates if this key was just pressed.
  1459. *
  1460. * @type {boolean}
  1461. */
  1462. this.justPressed = false;
  1463. /**
  1464. * Indicates if this key was just released.
  1465. *
  1466. * @type {boolean}
  1467. */
  1468. this.justReleased = false;
  1469. }
  1470. /**
  1471. * Key down event.
  1472. *
  1473. * @type {number}
  1474. */
  1475. Key.DOWN = -1;
  1476. /**
  1477. * Key up event.
  1478. *
  1479. * @type {number}
  1480. */
  1481. Key.UP = 1;
  1482. /**
  1483. * Key reset event.
  1484. *
  1485. * @type {number}
  1486. */
  1487. Key.RESET = 0;
  1488. Key.prototype.constructor = Key;
  1489. /**
  1490. * Update Key status based on new key state.
  1491. *
  1492. * @param {number} action Key action that was performed.
  1493. */
  1494. Key.prototype.update = function(action)
  1495. {
  1496. this.justPressed = false;
  1497. this.justReleased = false;
  1498. if(action === Key.DOWN)
  1499. {
  1500. if(this.pressed === false)
  1501. {
  1502. this.justPressed = true;
  1503. }
  1504. this.pressed = true;
  1505. }
  1506. else if(action === Key.UP)
  1507. {
  1508. if(this.pressed)
  1509. {
  1510. this.justReleased = true;
  1511. }
  1512. this.pressed = false;
  1513. }
  1514. else if(action === Key.RESET)
  1515. {
  1516. this.justReleased = false;
  1517. this.justPressed = false;
  1518. }
  1519. };
  1520. /**
  1521. * Set this key attributes manually.
  1522. *
  1523. * @param {boolean} justPressed Indicates if the button was just pressed.
  1524. * @param {boolean} pressed Indicates if the button is currently being pressed.
  1525. * @param {boolean} justReleased Indicates if the button was just released.
  1526. */
  1527. Key.prototype.set = function(justPressed, pressed, justReleased)
  1528. {
  1529. this.justPressed = justPressed;
  1530. this.pressed = pressed;
  1531. this.justReleased = justReleased;
  1532. };
  1533. /**
  1534. * Reset key to default values.
  1535. */
  1536. Key.prototype.reset = function()
  1537. {
  1538. this.justPressed = false;
  1539. this.pressed = false;
  1540. this.justReleased = false;
  1541. };
  1542. /**
  1543. * Pointer object is used to called input from the user, works for booth mouse or touch screens.
  1544. *
  1545. * It is responsible for synchronizing user input with the render of the graphics.
  1546. *
  1547. * @class
  1548. * @param {Element} domElement DOM element to create the pointer events.
  1549. * @param {Element} canvas Canvas DOM element where the content is being drawn.
  1550. */
  1551. function Pointer(domElement, canvas)
  1552. {
  1553. //Raw data
  1554. this._keys = new Array(5);
  1555. this._position = new Vector2(0, 0);
  1556. this._positionUpdated = false;
  1557. this._delta = new Vector2(0, 0);
  1558. this._wheel = 0;
  1559. this._wheelUpdated = false;
  1560. this._doubleClicked = new Array(5);
  1561. /**
  1562. * Array with pointer buttons status.
  1563. *
  1564. * @type {number[]}
  1565. */
  1566. this.keys = new Array(5);
  1567. /**
  1568. * Pointer position inside of the window (coordinates in window space).
  1569. *
  1570. * This value is accumulated from multiple mouse triggered events between updated.
  1571. *
  1572. * @type {Vector2}
  1573. */
  1574. this.position = new Vector2(0, 0);
  1575. /**
  1576. * Pointer movement (coordinates in window space). Since the last update.
  1577. *
  1578. * This value is accumulated from multiple mouse triggered events between updated.
  1579. *
  1580. * @type {Vector2}
  1581. */
  1582. this.delta = new Vector2(0, 0);
  1583. /**
  1584. * Pointer scroll wheel movement, since the last update.
  1585. *
  1586. * @type {number}
  1587. */
  1588. this.wheel = 0;
  1589. /**
  1590. * Indicates a button of the pointer was double clicked.
  1591. *
  1592. * @type {boolean}
  1593. */
  1594. this.doubleClicked = new Array(5);
  1595. /**
  1596. * DOM element where to attach the pointer events.
  1597. *
  1598. * @type {Element}
  1599. */
  1600. this.domElement = (domElement !== undefined) ? domElement : window;
  1601. /**
  1602. * Canvas attached to this pointer instance used to calculate position and delta in element space coordinates.
  1603. *
  1604. * @type {Element}
  1605. */
  1606. this.canvas = null;
  1607. if(canvas !== undefined)
  1608. {
  1609. this.setCanvas(canvas);
  1610. }
  1611. /**
  1612. * Event manager responsible for updating the raw data variables.
  1613. *
  1614. * Different events are used depending on the host platform.
  1615. *
  1616. * When the update method is called the raw data is reset.
  1617. *
  1618. * @type {EventManager}
  1619. */
  1620. this.events = new EventManager();
  1621. //Initialize key instances
  1622. for(var i = 0; i < 5; i++)
  1623. {
  1624. this._doubleClicked[i] = false;
  1625. this.doubleClicked[i] = false;
  1626. this._keys[i] = new Key();
  1627. this.keys[i] = new Key();
  1628. }
  1629. //Self pointer
  1630. var self = this;
  1631. //Scroll wheel
  1632. if(window.onmousewheel !== undefined)
  1633. {
  1634. //Chrome, edge
  1635. this.events.add(this.domElement, "mousewheel", function(event)
  1636. {
  1637. self._wheel = event.deltaY;
  1638. self._wheelUpdated = true;
  1639. });
  1640. }
  1641. else if(window.addEventListener !== undefined)
  1642. {
  1643. //Firefox
  1644. this.events.add(this.domElement, "DOMMouseScroll", function(event)
  1645. {
  1646. self._wheel = event.detail * 30;
  1647. self._wheelUpdated = true;
  1648. });
  1649. }
  1650. else
  1651. {
  1652. this.events.add(this.domElement, "wheel", function(event)
  1653. {
  1654. self._wheel = event.deltaY;
  1655. self._wheelUpdated = true;
  1656. });
  1657. }
  1658. //Touchscreen input events
  1659. if(window.ontouchstart !== undefined || navigator.msMaxTouchPoints > 0)
  1660. {
  1661. //Auxiliar variables to calculate touch delta
  1662. var lastTouch = new Vector2(0, 0);
  1663. //Touch start event
  1664. this.events.add(this.domElement, "touchstart", function(event)
  1665. {
  1666. var touch = event.touches[0];
  1667. self.updatePosition(touch.clientX, touch.clientY, 0, 0);
  1668. self.updateKey(Pointer.LEFT, Key.DOWN);
  1669. lastTouch.set(touch.clientX, touch.clientY);
  1670. });
  1671. //Touch end event
  1672. this.events.add(this.domElement, "touchend", function(event)
  1673. {
  1674. self.updateKey(Pointer.LEFT, Key.UP);
  1675. });
  1676. //Touch cancel event
  1677. this.events.add(this.domElement, "touchcancel", function(event)
  1678. {
  1679. self.updateKey(Pointer.LEFT, Key.UP);
  1680. });
  1681. //Touch move event
  1682. this.events.add(document.body, "touchmove", function(event)
  1683. {
  1684. var touch = event.touches[0];
  1685. self.updatePosition(touch.clientX, touch.clientY, touch.clientX - lastTouch.x, touch.clientY - lastTouch.y);
  1686. lastTouch.set(touch.clientX, touch.clientY);
  1687. });
  1688. }
  1689. //Move
  1690. this.events.add(this.domElement, "mousemove", function(event)
  1691. {
  1692. self.updatePosition(event.clientX, event.clientY, event.movementX, event.movementY);
  1693. });
  1694. //Button pressed
  1695. this.events.add(this.domElement, "mousedown", function(event)
  1696. {
  1697. self.updateKey(event.which - 1, Key.DOWN);
  1698. });
  1699. //Button released
  1700. this.events.add(this.domElement, "mouseup", function(event)
  1701. {
  1702. self.updateKey(event.which - 1, Key.UP);
  1703. });
  1704. //Drag start
  1705. this.events.add(this.domElement, "dragstart", function(event)
  1706. {
  1707. self.updateKey(event.which - 1, Key.UP);
  1708. });
  1709. //Pointer double click
  1710. this.events.add(this.domElement, "dblclick", function(event)
  1711. {
  1712. self._doubleClicked[event.which - 1] = true;
  1713. });
  1714. this.create();
  1715. }
  1716. Pointer.prototype = Pointer;
  1717. Pointer.prototype.constructor = Pointer;
  1718. /**
  1719. * Left pointer button.
  1720. *
  1721. * @static
  1722. * @type {number}
  1723. */
  1724. Pointer.LEFT = 0;
  1725. /**
  1726. * Middle pointer button.
  1727. *
  1728. * @static
  1729. * @type {number}
  1730. */
  1731. Pointer.MIDDLE = 1;
  1732. /**
  1733. * Right pointer button.
  1734. *
  1735. * @static
  1736. * @type {number}
  1737. */
  1738. Pointer.RIGHT = 2;
  1739. /**
  1740. * Back pointer navigation button.
  1741. *
  1742. * @static
  1743. * @type {number}
  1744. */
  1745. Pointer.BACK = 3;
  1746. /**
  1747. * Forward pointer navigation button.
  1748. *
  1749. * @static
  1750. * @type {number}
  1751. */
  1752. Pointer.FORWARD = 4;
  1753. /**
  1754. * Element to be used for coordinates calculation relative to that canvas.
  1755. *
  1756. * @param {DOM} element Canvas to be attached to the Pointer instance
  1757. */
  1758. Pointer.setCanvas = function(element)
  1759. {
  1760. this.canvas = element;
  1761. element.pointerInside = false;
  1762. element.addEventListener("mouseenter", function()
  1763. {
  1764. this.pointerInside = true;
  1765. });
  1766. element.addEventListener("mouseleave", function()
  1767. {
  1768. this.pointerInside = false;
  1769. });
  1770. };
  1771. /**
  1772. * Check if pointer is inside attached canvas (updated async).
  1773. *
  1774. * @return {boolean} True if pointer is currently inside the canvas
  1775. */
  1776. Pointer.insideCanvas = function()
  1777. {
  1778. return this.canvas !== null && this.canvas.pointerInside;
  1779. };
  1780. /**
  1781. * Check if pointer button is currently pressed.
  1782. *
  1783. * @param {Number} button Button to check status of
  1784. * @return {boolean} True if button is currently pressed
  1785. */
  1786. Pointer.buttonPressed = function(button)
  1787. {
  1788. return this.keys[button].pressed;
  1789. };
  1790. /**
  1791. * Check if pointer button was double clicked.
  1792. *
  1793. * @param {Number} button Button to check status of
  1794. * @return {boolean} True if some pointer button was just double clicked
  1795. */
  1796. Pointer.buttonDoubleClicked = function(button)
  1797. {
  1798. return this.doubleClicked[button];
  1799. };
  1800. /**
  1801. * Check if a pointer button was just pressed.
  1802. *
  1803. * @param {Number} button Button to check status of
  1804. * @return {boolean} True if button was just pressed
  1805. */
  1806. Pointer.buttonJustPressed = function(button)
  1807. {
  1808. return this.keys[button].justPressed;
  1809. };
  1810. /**
  1811. * Check if a pointer button was just released.
  1812. *
  1813. * @param {Number} button Button to check status of
  1814. * @return {boolean} True if button was just released
  1815. */
  1816. Pointer.buttonJustReleased = function(button)
  1817. {
  1818. return this.keys[button].justReleased;
  1819. };
  1820. /**
  1821. * Update pointer position.
  1822. *
  1823. * @param {Number} x
  1824. * @param {Number} y
  1825. * @param {Number} xDiff
  1826. * @param {Number} yDiff
  1827. */
  1828. Pointer.updatePosition = function(x, y, xDiff, yDiff)
  1829. {
  1830. if(this.canvas !== null)
  1831. {
  1832. var rect = this.canvas.getBoundingClientRect();
  1833. x -= rect.left;
  1834. y -= rect.top;
  1835. }
  1836. this._position.set(x, y);
  1837. this._delta.x += xDiff;
  1838. this._delta.y += yDiff;
  1839. this._positionUpdated = true;
  1840. };
  1841. /**
  1842. * Update a pointer button.
  1843. *
  1844. * @param {Number} button
  1845. * @param {Number} action
  1846. */
  1847. Pointer.updateKey = function(button, action)
  1848. {
  1849. if(button > -1)
  1850. {
  1851. this._keys[button].update(action);
  1852. }
  1853. };
  1854. /**
  1855. * Update pointer buttons state, position, wheel and delta synchronously.
  1856. *
  1857. * Should be called every frame on the update loop before reading any values from the pointer.
  1858. */
  1859. Pointer.update = function()
  1860. {
  1861. //Update pointer keys state
  1862. for(var i = 0; i < 5; i++)
  1863. {
  1864. if(this._keys[i].justPressed && this.keys[i].justPressed)
  1865. {
  1866. this._keys[i].justPressed = false;
  1867. }
  1868. if(this._keys[i].justReleased && this.keys[i].justReleased)
  1869. {
  1870. this._keys[i].justReleased = false;
  1871. }
  1872. this.keys[i].set(this._keys[i].justPressed, this._keys[i].pressed, this._keys[i].justReleased);
  1873. //Update pointer double click
  1874. if(this._doubleClicked[i] === true)
  1875. {
  1876. this.doubleClicked[i] = true;
  1877. this._doubleClicked[i] = false;
  1878. }
  1879. else
  1880. {
  1881. this.doubleClicked[i] = false;
  1882. }
  1883. }
  1884. //Update pointer wheel
  1885. if(this._wheelUpdated)
  1886. {
  1887. this.wheel = this._wheel;
  1888. this._wheelUpdated = false;
  1889. }
  1890. else
  1891. {
  1892. this.wheel = 0;
  1893. }
  1894. //Update pointer Position if needed
  1895. if(this._positionUpdated)
  1896. {
  1897. this.delta.copy(this._delta);
  1898. this.position.copy(this._position);
  1899. this._delta.set(0,0);
  1900. this._positionUpdated = false;
  1901. }
  1902. else
  1903. {
  1904. this.delta.x = 0;
  1905. this.delta.y = 0;
  1906. }
  1907. };
  1908. /**
  1909. * Create pointer events to collect input data.
  1910. *
  1911. * Should be called before using the pointer object.
  1912. */
  1913. Pointer.create = function()
  1914. {
  1915. this.events.create();
  1916. };
  1917. /**
  1918. * Dispose pointer events, should be called after the objects is no longer required.
  1919. *
  1920. * If not called leaves the window events created leaving a memory/code leak.
  1921. */
  1922. Pointer.dispose = function()
  1923. {
  1924. this.events.destroy();
  1925. };
  1926. /**
  1927. * Viewport defines the user view into the content being rendered, similar to a camera it defines the size of the content, rotation and position of the content.
  1928. *
  1929. * The viewport can be moved, rotated and scaled to navigate the virtual canvas.
  1930. *
  1931. * @class
  1932. * @param {Element} canvas Canvas DOM element where the viewport is being rendered.
  1933. */
  1934. function Viewport(canvas)
  1935. {
  1936. /**
  1937. * UUID of the object.
  1938. *
  1939. * @type {string}
  1940. */
  1941. this.uuid = UUID.generate();
  1942. /**
  1943. * Canvas DOM element where the viewport is being rendered.
  1944. *
  1945. * @type {Element}
  1946. */
  1947. this.canvas = canvas;
  1948. /**
  1949. * Position of the viewport.
  1950. *
  1951. * @type {Vector2}
  1952. */
  1953. this.position = new Vector2(0, 0);
  1954. /**
  1955. * Center point of the viewport. Relative to the size of the canvas.
  1956. *
  1957. * Rotation and zoom is applied relative to this point.
  1958. *
  1959. * @type {Vector2}
  1960. */
  1961. this.center = new Vector2(0, 0);
  1962. /**
  1963. * Scale of the object.
  1964. *
  1965. * @type {number}
  1966. */
  1967. this.scale = 1.0;
  1968. /**
  1969. * Rotation of the object relative to its center.
  1970. *
  1971. * @type {number}
  1972. */
  1973. this.rotation = 0.0;
  1974. /**
  1975. * Local transformation matrix applied to the object.
  1976. *
  1977. * @type {Matrix}
  1978. */
  1979. this.matrix = new Matrix();
  1980. /**
  1981. * Inverse of the local transformation matrix.
  1982. *
  1983. * Used to transform points from local to global coordinates.
  1984. *
  1985. * @type {Matrix}
  1986. */
  1987. this.inverseMatrix = new Matrix();
  1988. /**
  1989. * If true the matrix is updated before rendering the object.
  1990. *
  1991. * Disable this if you want to update the matrix manually.
  1992. *
  1993. * @type {boolean}
  1994. */
  1995. this.matrixNeedsUpdate = true;
  1996. /**
  1997. * Flag to indicate if the viewport should move when scaling.
  1998. *
  1999. * For some application its easier to focus the target if the viewport moves to the pointer location while scaling.
  2000. */
  2001. this.centerOnPointer = false;
  2002. /**
  2003. * Value of the initial point of rotation if the viewport is being rotated.
  2004. *
  2005. * Is set to null when the viewport is not being rotated.
  2006. */
  2007. this.rotationPoint = null;
  2008. }
  2009. /**
  2010. * Calculate and update the viewport transformation matrix.
  2011. *
  2012. * Also updates the inverse matrix of the viewport.
  2013. */
  2014. Viewport.prototype.updateMatrix = function()
  2015. {
  2016. if(this.matrixNeedsUpdate)
  2017. {
  2018. this.matrix.m = [1, 0, 0, 1, this.position.x , this.position.y];
  2019. if(this.center.x !== 0.0 || this.center.y !== 0.0) {
  2020. this.matrix.multiply(new Matrix([1, 0, 0, 1, this.center.x, this.center.y]));
  2021. }
  2022. if(this.rotation !== 0.0)
  2023. {
  2024. var c = Math.cos(this.rotation);
  2025. var s = Math.sin(this.rotation);
  2026. this.matrix.multiply(new Matrix([c, s, -s, c, 0, 0]));
  2027. }
  2028. if(this.scale !== 1.0)
  2029. {
  2030. this.matrix.multiply(new Matrix([this.scale, 0, 0, this.scale, 0, 0]));
  2031. }
  2032. if(this.center.x !== 0.0 || this.center.y !== 0.0) {
  2033. this.matrix.multiply(new Matrix([1, 0, 0, 1, -this.center.x, -this.center.y]));
  2034. }
  2035. this.inverseMatrix = this.matrix.getInverse();
  2036. this.matrixNeedsUpdate = false;
  2037. }
  2038. };
  2039. /**
  2040. * Center the viewport relative to a object.
  2041. *
  2042. * The position of the object is used a central point, this method does not consider "box" attributes or other strucures in the object.
  2043. *
  2044. * Uses the object's local transformation matrix and the canvas size to calculate the new position of the viewport.
  2045. *
  2046. * @param {Object2D} object Object to be centered on the viewport.
  2047. * @param {Element} canvas Canvas element where the image is drawn.
  2048. */
  2049. Viewport.prototype.centerObject = function(object, canvas)
  2050. {
  2051. var position = object.globalMatrix.transformPoint(new Vector2());
  2052. position.multiplyScalar(-this.scale);
  2053. position.x += canvas.width / 2;
  2054. position.y += canvas.height / 2;
  2055. this.position.copy(position);
  2056. this.matrixNeedsUpdate = true;
  2057. };
  2058. /**
  2059. * Viewport controls are used to allow the user to control the viewport.
  2060. *
  2061. * The user controls the viewport using pointer input (e.g. mouse, touchscreen)
  2062. *
  2063. * @class
  2064. * @param {Viewport} viewport
  2065. */
  2066. function ViewportControls(viewport)
  2067. {
  2068. /**
  2069. * Viewport being controlled by this object.
  2070. *
  2071. * @type {Viewport}
  2072. */
  2073. this.viewport = viewport;
  2074. /**
  2075. * Button used to drag and viewport around.
  2076. *
  2077. * On touch enabled devices the touch event is represented as a LEFT button.
  2078. *
  2079. * @type {number}
  2080. */
  2081. this.dragButton = Pointer.RIGHT;
  2082. /**
  2083. * Button used to rotate the viewport.
  2084. *
  2085. * @type {number}
  2086. */
  2087. this.rotateButton = Pointer.MIDDLE;
  2088. /**
  2089. * Is set to true allow the viewport to be scalled.
  2090. *
  2091. * Scaling is performed using the pointer scroll.
  2092. *
  2093. * @type {boolean}
  2094. */
  2095. this.allowScale = true;
  2096. /**
  2097. * Flag to indicate if the viewport should automatically be recentered.
  2098. *
  2099. * This will cause the viewport center property to be automatically set based on an heuristic defined by the user.
  2100. *
  2101. * @type {number}
  2102. */
  2103. this.recenterViewport = ViewportControls.RECENTER_NONE;
  2104. /**
  2105. * If true allows the viewport to be rotated.
  2106. *
  2107. * Rotation is performed by holding the RIGHT and LEFT pointer buttons and rotating around the initial point.
  2108. *
  2109. * @type {boolean}
  2110. */
  2111. this.allowRotation = true;
  2112. /**
  2113. * Value of the initial point of rotation if the viewport is being rotated.
  2114. *
  2115. * Is the value of the pointer position when the rotation starts.
  2116. *
  2117. * Is set to null when the viewport is not being rotated.
  2118. *
  2119. * @type {Vector2 | null}
  2120. */
  2121. this.rotationPoint = null;
  2122. /**
  2123. * Initial rotation of the viewport.
  2124. *
  2125. * Is set to the current rotation of the viewport when the rotation starts.
  2126. *
  2127. * @type {number}
  2128. */
  2129. this.rotationInitial = 0;
  2130. }
  2131. /**
  2132. * Viewport is not automatically recentered.
  2133. *
  2134. * The center point can be set manually by the developer.
  2135. *
  2136. * @type {number}
  2137. */
  2138. ViewportControls.RECENTER_NONE = 0;
  2139. /**
  2140. * Recenter the viewport automatically to the canvas.
  2141. *
  2142. * This will ensure that rotation and scaling will not cause the viewport to move around.
  2143. *
  2144. * @type {number}
  2145. */
  2146. ViewportControls.RECENTER_CANVAS = 1;
  2147. /**
  2148. * Viewport should automatically center on the pointer position.
  2149. *
  2150. * The viewport will simulataniously move to the pointer position while scalling.
  2151. *
  2152. * For some application its easier to focus the target if the viewport moves to the pointer location while scalling.
  2153. *
  2154. * @type {number}
  2155. */
  2156. ViewportControls.RECENTER_POINTER = 2;
  2157. /**
  2158. * Update the viewport controls using the pointer object.
  2159. *
  2160. * Should be called every frame before rendering.
  2161. *
  2162. * @param {Pointer} pointer Pointer used to control the viewport.
  2163. */
  2164. ViewportControls.prototype.update = function(pointer)
  2165. {
  2166. // Scale
  2167. if(this.allowScale && pointer.wheel !== 0)
  2168. {
  2169. var scale = pointer.wheel * 1e-3 * this.viewport.scale;
  2170. this.viewport.scale -= scale;
  2171. this.viewport.matrixNeedsUpdate = true;
  2172. }
  2173. // Rotation
  2174. if(this.allowRotation && pointer.buttonPressed(this.rotateButton))
  2175. {
  2176. // Rotation pivot
  2177. if(this.rotationPoint === null)
  2178. {
  2179. this.rotationPoint = pointer.position.clone();
  2180. this.rotationInitial = this.viewport.rotation;
  2181. }
  2182. else
  2183. {
  2184. var point = pointer.position.clone();
  2185. point.sub(this.rotationPoint);
  2186. this.viewport.rotation = this.rotationInitial + point.angle();
  2187. this.viewport.matrixNeedsUpdate = true;
  2188. }
  2189. return;
  2190. } else {
  2191. this.rotationPoint = null;
  2192. }
  2193. // Drag
  2194. if(pointer.buttonPressed(this.dragButton))
  2195. {
  2196. this.viewport.position.add(pointer.delta);
  2197. this.viewport.matrixNeedsUpdate = true;
  2198. }
  2199. if (pointer.canvas === null) {
  2200. return;
  2201. }
  2202. // Center viewport on canvas
  2203. if (this.recenterViewport === ViewportControls.RECENTER_CANVAS) {
  2204. var centerWorld = new Vector2(pointer.canvas.width / 2.0, pointer.canvas.height / 2.0);
  2205. centerWorld = this.viewport.inverseMatrix.transformPoint(centerWorld);
  2206. this.viewport.center.copy(centerWorld);
  2207. this.viewport.matrixNeedsUpdate = true;
  2208. }
  2209. // Center viewport on pointer
  2210. else if(this.recenterViewport === ViewportControls.RECENTER_POINTER)
  2211. {
  2212. var pointerWorld = this.viewport.inverseMatrix.transformPoint(pointer.position);
  2213. this.viewport.center.copy(pointerWorld);
  2214. this.viewport.matrixNeedsUpdate = true;
  2215. }
  2216. };
  2217. /**
  2218. * Animation timer should be used to run the update and render loops of the application.
  2219. *
  2220. * Underneat it uses the requestAnimationFrame() method that calls the function with the same rate as the screen refresh rate.
  2221. *
  2222. * @class
  2223. * @param {Function} callback Timer callback function.
  2224. */
  2225. function AnimationTimer(callback)
  2226. {
  2227. /**
  2228. * Task of the timer, executed at the timer defined rate.
  2229. *
  2230. * @type {Function}
  2231. */
  2232. this.callback = callback;
  2233. /**
  2234. * Indicates if the timer is currently running, it is set to true on start and reset to false on stop.
  2235. *
  2236. * @type {boolean}
  2237. */
  2238. this.running = false;
  2239. /**
  2240. * ID of the currently waiting timeout clock. Used to cancel the already request execution of the next clock tick.
  2241. *
  2242. * @type {number}
  2243. */
  2244. this.id = -1;
  2245. }
  2246. /**
  2247. * Start timer, is the timer is already running does not do anything.
  2248. */
  2249. AnimationTimer.prototype.start = function()
  2250. {
  2251. if(this.running)
  2252. {
  2253. return;
  2254. }
  2255. this.running = true;
  2256. var self = this;
  2257. function loop()
  2258. {
  2259. self.callback();
  2260. if(self.running)
  2261. {
  2262. self.id = requestAnimationFrame(loop);
  2263. }
  2264. }
  2265. loop();
  2266. };
  2267. /**
  2268. * Stop animation timer, should be called when the render loop is no longer in use to prevent code/memory leaks.
  2269. *
  2270. * If the timer is not stopped the loop will keep running using processing power and consuming memory.
  2271. */
  2272. AnimationTimer.prototype.stop = function()
  2273. {
  2274. this.running = false;
  2275. cancelAnimationFrame(this.id);
  2276. };
  2277. /**
  2278. * The renderer is responsible for drawing the objects structure into the canvas element and manage its rendering state.
  2279. *
  2280. * Object are updated by the renderer before drawing, the renderer sorts the objects by layer, checks for pointer events and draw the objects into the screen.
  2281. *
  2282. * Input handling is also performed by the renderer (it is also used for the event handling).
  2283. *
  2284. * @class
  2285. * @param {Element} canvas Canvas to render the content to.
  2286. * @param {Object} options Renderer canvas options.
  2287. */
  2288. function Renderer(canvas, options)
  2289. {
  2290. // Default options
  2291. var defaultOptions =
  2292. {
  2293. alpha: true,
  2294. disableContextMenu: true,
  2295. imageSmoothingEnabled: true,
  2296. imageSmoothingQuality: "low",
  2297. globalAlpha: 1.0,
  2298. // "source-over", "source-in", "source-out", "source-atop", "destination-over", "destination-in", "destination-out", "destination-atop", "lighter", "copy", "xor"
  2299. globalCompositeOperation: "source-over",
  2300. // "auto", "optimizeSpeed", "optimizeLegibility", "geometricPrecision"
  2301. textRendering: "auto",
  2302. filter: null
  2303. };
  2304. options = options ? Object.assign(defaultOptions, options) : defaultOptions;
  2305. /**
  2306. * Event manager for DOM events created by the renderer.
  2307. *
  2308. * Created automatically when the renderer is created. Disposed automatically when the renderer is destroyed.
  2309. *
  2310. * @type {EventManager}
  2311. */
  2312. this.manager = new EventManager();
  2313. if(options.disableContextMenu) {
  2314. this.manager.add(canvas, "contextmenu", function(e) {
  2315. e.preventDefault();
  2316. e.stopPropagation();
  2317. });
  2318. }
  2319. this.manager.create();
  2320. /**
  2321. * Canvas DOM element, the user needs to manage the canvas state.
  2322. *
  2323. * The canvas size (width and height) should always match its actual display size (adjusted for the device pixel ratio).
  2324. *
  2325. * @type {Element}
  2326. */
  2327. this.canvas = canvas;
  2328. /**
  2329. * Division where DOM and SVG objects should be placed at. This division should be perfectly aligned whit the canvas element.
  2330. *
  2331. * If no division is defined the canvas parent element is used by default to place these objects.
  2332. *
  2333. * The DOM container to be used can be obtained using the getDomContainer() method.
  2334. *
  2335. * @type {Element}
  2336. */
  2337. this.container = null;
  2338. /**
  2339. * Canvas 2D rendering context used to draw content.
  2340. *
  2341. * The options passed thought the constructor are applied to the context created.
  2342. *
  2343. * @type {CanvasRenderingContext2D}
  2344. */
  2345. this.context = this.canvas.getContext("2d", {alpha: options.alpha});
  2346. this.context.imageSmoothingEnabled = options.imageSmoothingEnabled;
  2347. this.context.imageSmoothingQuality = options.imageSmoothingQuality;
  2348. this.context.globalCompositeOperation = options.globalCompositeOperation;
  2349. this.context.globalAlpha = options.globalAlpha;
  2350. this.context.textRendering = options.textRendering;
  2351. this.context.filter = options.filter;
  2352. /**
  2353. * Pointer input handler object, automatically updated by the renderer.
  2354. *
  2355. * The pointer is attached to the DOM window and to the canvas provided by the user.
  2356. *
  2357. * @type {Pointer}
  2358. */
  2359. this.pointer = new Pointer(window, this.canvas);
  2360. /**
  2361. * Indicates if the canvas should be automatically cleared before new frame is drawn.
  2362. *
  2363. * If set to false the user should clear the frame before drawing.
  2364. *
  2365. * @type {boolean}
  2366. */
  2367. this.autoClear = true;
  2368. }
  2369. /**
  2370. * Get the DOM container to be used to store DOM and SVG objects.
  2371. *
  2372. * Can be set using the container attribute, by default the canvas parent element is used.
  2373. *
  2374. * @returns {Element} DOM element selected for objects.
  2375. */
  2376. Renderer.prototype.getDomContainer = function()
  2377. {
  2378. return this.container !== null ? this.container : this.canvas.parentElement;
  2379. };
  2380. /**
  2381. * Creates a infinite render loop to render the group into a viewport each frame.
  2382. *
  2383. * Automatically creates a viewport controls object, used for the user to control the viewport.
  2384. *
  2385. * The render loop can be accessed trough the animation timer returned. Should be stopped when no longer necessary to prevent memory/code leaks.
  2386. *
  2387. * @param {Object2D} group Object to be rendered, alongside with all its children. Object2D can be used as a container to group objects.
  2388. * @param {Viewport} viewport Viewport into the scene.
  2389. * @param {Function} onUpdate Function called before rendering the frame, can be used for additional logic code. Object logic should be directly written in the update method of objects.
  2390. * @return {AnimationTimer} Animation timer created for this render loop. Should be stopped when no longer necessary.
  2391. */
  2392. Renderer.prototype.createRenderLoop = function(group, viewport, onUpdate)
  2393. {
  2394. var self = this;
  2395. var controls = new ViewportControls(viewport);
  2396. var timer = new AnimationTimer(function()
  2397. {
  2398. if(onUpdate !== undefined)
  2399. {
  2400. onUpdate();
  2401. }
  2402. controls.update(self.pointer);
  2403. self.update(group, viewport);
  2404. });
  2405. timer.start();
  2406. return {timer: timer, controls: controls};
  2407. };
  2408. /**
  2409. * Dispose the renderer object, clears the pointer events attached to the window/canvas.
  2410. *
  2411. * Should be called if the renderer is no longer in use to prevent code/memory leaks.
  2412. */
  2413. Renderer.prototype.dispose = function(group, viewport, onUpdate)
  2414. {
  2415. this.manager.destroy();
  2416. this.pointer.dispose();
  2417. };
  2418. /**
  2419. * Renders a object using a user defined viewport into a canvas element.
  2420. *
  2421. * Before rendering automatically updates the input handlers and calculates the objects/viewport transformation matrices.
  2422. *
  2423. * The canvas state is saved and restored for each individual object, ensuring that the code of one object does not affect another one.
  2424. *
  2425. * Should be called at a fixed rate preferably using the requestAnimationFrame() method, its also possible to use the createRenderLoop() method, that automatically creates a infinite render loop.
  2426. *
  2427. * @param object {Object2D} Object to be updated and drawn into the canvas, the Object2D should be used as a group to store all the other objects to be updated and drawn.
  2428. * @param viewport {Viewport} Viewport to be updated (should be the one where the objects will be rendered after).
  2429. */
  2430. Renderer.prototype.update = function(object, viewport)
  2431. {
  2432. // Get objects to be rendered
  2433. var objects = [];
  2434. // Traverse object and get all objects into a list.
  2435. object.traverse(function(child)
  2436. {
  2437. if(child.visible)
  2438. {
  2439. objects.push(child);
  2440. }
  2441. });
  2442. // Sort objects by layer
  2443. objects.sort(function(a, b)
  2444. {
  2445. if(b.layer === a.layer)
  2446. {
  2447. return b.level - a.level;
  2448. }
  2449. return b.layer - a.layer;
  2450. });
  2451. // Pointer object update
  2452. var pointer = this.pointer;
  2453. pointer.update();
  2454. // Viewport transform matrix
  2455. viewport.updateMatrix();
  2456. // Project pointer coordinates
  2457. var point = pointer.position.clone();
  2458. var viewportPoint = viewport.inverseMatrix.transformPoint(point);
  2459. // Object pointer events
  2460. for(var i = 0; i < objects.length; i++)
  2461. {
  2462. var child = objects[i];
  2463. //Process the object pointer events
  2464. if(child.pointerEvents)
  2465. {
  2466. // Calculate the pointer position in the object coordinates
  2467. var localPoint = child.inverseGlobalMatrix.transformPoint(child.ignoreViewport ? point : viewportPoint);
  2468. // Check if the pointer pointer is inside
  2469. if(child.isInside(localPoint))
  2470. {
  2471. // Pointer enter
  2472. if(!child.pointerInside && child.onPointerEnter !== null)
  2473. {
  2474. child.onPointerEnter(pointer, viewport);
  2475. }
  2476. // Pointer over
  2477. if(child.onPointerOver !== null)
  2478. {
  2479. child.onPointerOver(pointer, viewport);
  2480. }
  2481. // Double click
  2482. if(pointer.buttonDoubleClicked(Pointer.LEFT) && child.onDoubleClick !== null)
  2483. {
  2484. child.onDoubleClick(pointer, viewport);
  2485. }
  2486. // Pointer pressed
  2487. if(pointer.buttonPressed(Pointer.LEFT) && child.onButtonPressed !== null)
  2488. {
  2489. child.onButtonPressed(pointer, viewport);
  2490. }
  2491. // Just released
  2492. if(pointer.buttonJustReleased(Pointer.LEFT) && child.onButtonUp !== null)
  2493. {
  2494. child.onButtonUp(pointer, viewport);
  2495. }
  2496. // Pointer just pressed
  2497. if(pointer.buttonJustPressed(Pointer.LEFT))
  2498. {
  2499. if(child.onButtonDown !== null)
  2500. {
  2501. child.onButtonDown(pointer, viewport);
  2502. }
  2503. // Drag object and break to only start a drag operation on the top element.
  2504. if(child.draggable)
  2505. {
  2506. child.beingDragged = true;
  2507. if(child.onPointerDragStart !== null)
  2508. {
  2509. child.onPointerDragStart(pointer, viewport);
  2510. }
  2511. break;
  2512. }
  2513. }
  2514. child.pointerInside = true;
  2515. }
  2516. else if(child.pointerInside)
  2517. {
  2518. // Pointer leave
  2519. if(child.onPointerLeave !== null)
  2520. {
  2521. child.onPointerLeave(pointer, viewport);
  2522. }
  2523. child.pointerInside = false;
  2524. }
  2525. // Stop object drag
  2526. if(pointer.buttonJustReleased(Pointer.LEFT))
  2527. {
  2528. if(child.draggable)
  2529. {
  2530. // On drag end callback
  2531. if(child.beingDragged === true && child.onPointerDragEnd !== null)
  2532. {
  2533. child.onPointerDragEnd(pointer, viewport);
  2534. }
  2535. child.beingDragged = false;
  2536. }
  2537. }
  2538. }
  2539. }
  2540. // Object drag events and update logic
  2541. for(var i = 0; i < objects.length; i++)
  2542. {
  2543. var child = objects[i];
  2544. // Pointer drag event
  2545. if(child.beingDragged)
  2546. {
  2547. if(child.onPointerDrag !== null)
  2548. {
  2549. var lastPosition = pointer.position.clone();
  2550. lastPosition.sub(pointer.delta);
  2551. // Get position and last position in world space to calculate world pointer movement
  2552. var positionWorld = viewport.inverseMatrix.transformPoint(pointer.position);
  2553. var lastWorld = viewport.inverseMatrix.transformPoint(lastPosition);
  2554. // Pointer movement delta in world coordinates
  2555. var delta = positionWorld.clone();
  2556. delta.sub(lastWorld);
  2557. child.onPointerDrag(pointer, viewport, delta, positionWorld);
  2558. }
  2559. }
  2560. // On update
  2561. if(child.onUpdate !== null)
  2562. {
  2563. child.onUpdate();
  2564. }
  2565. }
  2566. // Update transformation matrices
  2567. object.traverse(function(child)
  2568. {
  2569. child.updateMatrix();
  2570. });
  2571. this.context.setTransform(1, 0, 0, 1, 0, 0);
  2572. // Clear canvas content
  2573. if(this.autoClear)
  2574. {
  2575. this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
  2576. }
  2577. // Render into the canvas
  2578. for(var i = objects.length - 1; i >= 0; i--)
  2579. {
  2580. if(objects[i].isMask)
  2581. {
  2582. continue;
  2583. }
  2584. if(objects[i].saveContextState)
  2585. {
  2586. this.context.save();
  2587. }
  2588. // Apply all masks
  2589. var masks = objects[i].masks;
  2590. for(var j = 0; j < masks.length; j++)
  2591. {
  2592. if(!masks[j].ignoreViewport)
  2593. {
  2594. viewport.matrix.setContextTransform(this.context);
  2595. }
  2596. masks[j].transform(this.context, viewport, this.canvas, this);
  2597. masks[j].clip(this.context, viewport, this.canvas);
  2598. }
  2599. // Set the viewport transform
  2600. if(!objects[i].ignoreViewport)
  2601. {
  2602. viewport.matrix.setContextTransform(this.context);
  2603. }
  2604. else if(masks.length > 0)
  2605. {
  2606. this.context.setTransform(1, 0, 0, 1, 0, 0);
  2607. }
  2608. // Apply the object transform to the canvas context
  2609. objects[i].transform(this.context, viewport, this.canvas, this);
  2610. // Style the canvas context
  2611. if(objects[i].style !== null)
  2612. {
  2613. objects[i].style(this.context, viewport, this.canvas);
  2614. }
  2615. // Draw content into the canvas.
  2616. if(objects[i].draw !== null)
  2617. {
  2618. objects[i].draw(this.context, viewport, this.canvas);
  2619. }
  2620. if(objects[i].restoreContextState)
  2621. {
  2622. this.context.restore();
  2623. }
  2624. }
  2625. };
  2626. /**
  2627. * Box is described by a minimum and maximum points.
  2628. *
  2629. * Can be used for collision detection with points and other boxes.
  2630. *
  2631. * @class
  2632. * @param {Vector2} min Minimum point of the box.
  2633. * @param {Vector2} max Maximum point of the box.
  2634. */
  2635. function Box2(min, max)
  2636. {
  2637. /**
  2638. * Minimum point of the box.
  2639. *
  2640. * @type {Vector2}
  2641. */
  2642. this.min = (min !== undefined) ? min : new Vector2();
  2643. /**
  2644. * Maximum point of the box.
  2645. *
  2646. * @type {Vector2}
  2647. */
  2648. this.max = (max !== undefined) ? max : new Vector2();
  2649. }
  2650. /**
  2651. * Set the box values.
  2652. *
  2653. * @param {Vector2} min Minimum point of the box.
  2654. * @param {Vector2} max Maximum point of the box.
  2655. */
  2656. Box2.prototype.set = function(min, max)
  2657. {
  2658. this.min.copy(min);
  2659. this.max.copy(max);
  2660. return this;
  2661. };
  2662. /**
  2663. * Set the box from a list of Vector2 points.
  2664. *
  2665. * @param {Array} points
  2666. */
  2667. Box2.prototype.setFromPoints = function(points)
  2668. {
  2669. this.min = new Vector2(+Infinity, +Infinity);
  2670. this.max = new Vector2(-Infinity, -Infinity);
  2671. for(var i = 0, il = points.length; i < il; i++)
  2672. {
  2673. this.expandByPoint(points[i]);
  2674. }
  2675. return this;
  2676. };
  2677. /**
  2678. * Set the box minimum and maximum from center point and size.
  2679. *
  2680. * @param {Vector2} center
  2681. * @param {Vector2} size
  2682. */
  2683. Box2.prototype.setFromCenterAndSize = function(center, size)
  2684. {
  2685. var v1 = new Vector2();
  2686. var halfSize = v1.copy(size).multiplyScalar(0.5);
  2687. this.min.copy(center).sub(halfSize);
  2688. this.max.copy(center).add(halfSize);
  2689. return this;
  2690. };
  2691. /**
  2692. * Clone the box into a new object.
  2693. *
  2694. * Should be used when it it necessary to make operations to this box.
  2695. *
  2696. * @return {Box2} New box object with the copy of this object.
  2697. */
  2698. Box2.prototype.clone = function()
  2699. {
  2700. var box = new Box2();
  2701. box.copy(this);
  2702. return box;
  2703. };
  2704. /**
  2705. * Copy the box value from another box.
  2706. *
  2707. * @param {Box2} point
  2708. */
  2709. Box2.prototype.copy = function(box)
  2710. {
  2711. this.min.copy(box.min);
  2712. this.max.copy(box.max);
  2713. };
  2714. /**
  2715. * Check if the box is empty (size equals zero or is negative).
  2716. *
  2717. * The box size is condireded valid on two negative axis.
  2718. *
  2719. * @return {boolean} True if the box is empty.
  2720. */
  2721. Box2.prototype.isEmpty = function()
  2722. {
  2723. return (this.max.x < this.min.x) || (this.max.y < this.min.y);
  2724. };
  2725. /**
  2726. * Calculate the center point of the box.
  2727. *
  2728. * @param {Vector2} [target] Vector to store the result.
  2729. * @return {Vector2} Central point of the box.
  2730. */
  2731. Box2.prototype.getCenter = function(target)
  2732. {
  2733. if(target === undefined)
  2734. {
  2735. target = new Vector2();
  2736. }
  2737. this.isEmpty() ? target.set(0, 0) : target.addVectors(this.min, this.max).multiplyScalar(0.5);
  2738. return target;
  2739. };
  2740. /**
  2741. * Get the size of the box from its min and max points.
  2742. *
  2743. * @param {Vector2} [target] Vector to store the result.
  2744. * @return {Vector2} Vector with the calculated size.
  2745. */
  2746. Box2.prototype.getSize = function(target)
  2747. {
  2748. if(target === undefined)
  2749. {
  2750. target = new Vector2();
  2751. }
  2752. this.isEmpty() ? target.set(0, 0) : target.subVectors(this.max, this.min);
  2753. return target;
  2754. };
  2755. /**
  2756. * Expand the box to contain a new point.
  2757. *
  2758. * @param {Vector2} point
  2759. */
  2760. Box2.prototype.expandByPoint = function(point)
  2761. {
  2762. this.min.min(point);
  2763. this.max.max(point);
  2764. return this;
  2765. };
  2766. /**
  2767. * Expand the box by adding a border with the vector size.
  2768. *
  2769. * Vector is subtracted from min and added to the max points.
  2770. *
  2771. * @param {Vector2} vector
  2772. */
  2773. Box2.prototype.expandByVector = function(vector)
  2774. {
  2775. this.min.sub(vector);
  2776. this.max.add(vector);
  2777. };
  2778. /**
  2779. * Expand the box by adding a border with the scalar value.
  2780. *
  2781. * @param {number} scalar
  2782. */
  2783. Box2.prototype.expandByScalar = function(scalar)
  2784. {
  2785. this.min.addScalar(-scalar);
  2786. this.max.addScalar(scalar);
  2787. };
  2788. /**
  2789. * Check if the box contains a point inside.
  2790. *
  2791. * @param {Vector2} point
  2792. * @return {boolean} True if the box contains point.
  2793. */
  2794. Box2.prototype.containsPoint = function(point)
  2795. {
  2796. return !(point.x < this.min.x || point.x > this.max.x || point.y < this.min.y || point.y > this.max.y);
  2797. };
  2798. /**
  2799. * Check if the box fully contains another box inside (different from intersects box).
  2800. *
  2801. * Only returns true if the box is fully contained.
  2802. *
  2803. * @param {Box2} box
  2804. * @return {boolean} True if the box contains box.
  2805. */
  2806. Box2.prototype.containsBox = function(box)
  2807. {
  2808. return this.min.x <= box.min.x && box.max.x <= this.max.x && this.min.y <= box.min.y && box.max.y <= this.max.y;
  2809. };
  2810. /**
  2811. * Check if two boxes intersect each other, using 4 splitting planes to rule out intersections.
  2812. *
  2813. * @param {Box2} box
  2814. * @return {boolean} True if the boxes intersect each other.
  2815. */
  2816. Box2.prototype.intersectsBox = function(box)
  2817. {
  2818. return !(box.max.x < this.min.x || box.min.x > this.max.x || box.max.y < this.min.y || box.min.y > this.max.y);
  2819. };
  2820. /**
  2821. * Calculate the distance to a point.
  2822. *
  2823. * @param {Vector2} point
  2824. * @return {number} Distance to point calculated.
  2825. */
  2826. Box2.prototype.distanceToPoint = function(point)
  2827. {
  2828. var v = new Vector2();
  2829. var clampedPoint = v.copy(point).clamp(this.min, this.max);
  2830. return clampedPoint.sub(point).length();
  2831. };
  2832. /**
  2833. * Make a intersection between this box and another box.
  2834. *
  2835. * Store the result in this object.
  2836. *
  2837. * @param {Box2} box
  2838. */
  2839. Box2.prototype.intersect = function(box)
  2840. {
  2841. this.min.max(box.min);
  2842. this.max.min(box.max);
  2843. };
  2844. /**
  2845. * Make a union between this box and another box.
  2846. *
  2847. * Store the result in this object.
  2848. *
  2849. * @param {Box2} box
  2850. */
  2851. Box2.prototype.union = function(box)
  2852. {
  2853. this.min.min(box.min);
  2854. this.max.max(box.max);
  2855. };
  2856. /**
  2857. * Translate the box by a offset value, adds the offset to booth min and max.
  2858. *
  2859. * @param {Vector2} offset
  2860. */
  2861. Box2.prototype.translate = function(offset)
  2862. {
  2863. this.min.add(offset);
  2864. this.max.add(offset);
  2865. };
  2866. /**
  2867. * Checks if two boxes are equal.
  2868. *
  2869. * @param {Box2} box
  2870. * @return {boolean} True if the two boxes are equal.
  2871. */
  2872. Box2.prototype.equals = function(box)
  2873. {
  2874. return box.min.equals(this.min) && box.max.equals(this.max);
  2875. };
  2876. /**
  2877. * Store the box data into a numeric array.
  2878. *
  2879. * @return {number[]} Numeric array with box data min and max.
  2880. */
  2881. Box2.prototype.toArray = function()
  2882. {
  2883. return [this.min.x, this.min.y, this.max.x, this.max.y];
  2884. };
  2885. /**
  2886. * Set box data min and max from numeric array.
  2887. *
  2888. * @param {number[]} array Numeric array with box data min and max.
  2889. */
  2890. Box2.prototype.fromArray = function(array)
  2891. {
  2892. this.min.set(array[0], array[1]);
  2893. this.max.set(array[2], array[3]);
  2894. };
  2895. /**
  2896. * A mask can be used to set the drawing region.
  2897. *
  2898. * Masks are treated as objects their shape is used to filter other objects shape.
  2899. *
  2900. * Multiple mask objects can be active simultaneously, they have to be attached to the object mask list to filter the render region.
  2901. *
  2902. * A mask objects is draw using the context.clip() method.
  2903. *
  2904. * @class
  2905. * @extends {Object2D}
  2906. */
  2907. function Mask()
  2908. {
  2909. Object2D.call(this);
  2910. }
  2911. Mask.prototype = Object.create(Object2D.prototype);
  2912. Mask.prototype.constructor = Mask;
  2913. Mask.prototype.type = "Mask";
  2914. Object2D.register(Mask, "Mask");
  2915. Mask.prototype.isMask = true;
  2916. /**
  2917. * Clip the canvas context. Define a clipping path and set the clip using the context.clip() method.
  2918. *
  2919. * Ensures that next objects being drawn are clipped to the path stored here.
  2920. *
  2921. * More information about canvas clipping https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/clip.
  2922. *
  2923. * @param {CanvasRenderingContext2D} context Canvas 2d drawing context.
  2924. * @param {Viewport} viewport Viewport applied to the canvas.
  2925. * @param {DOM} canvas DOM canvas element where the content is being drawn.
  2926. */
  2927. Mask.prototype.clip = function(context, viewport, canvas){};
  2928. /**
  2929. * Box mask can be used to clear a box mask region.
  2930. *
  2931. * It will limit the drawing region to this box.
  2932. *
  2933. * @class
  2934. * @extends {Mask}
  2935. */
  2936. function BoxMask()
  2937. {
  2938. Mask.call(this);
  2939. /**
  2940. * Box object containing the size of the object.
  2941. *
  2942. * @type {Box2}
  2943. */
  2944. this.box = new Box2(new Vector2(-50, -35), new Vector2(50, 35));
  2945. /**
  2946. * If inverted the mask considers the outside of the box instead of the inside.
  2947. *
  2948. * @type {boolean}
  2949. */
  2950. this.invert = false;
  2951. }
  2952. BoxMask.prototype = Object.create(Mask.prototype);
  2953. BoxMask.prototype.constructor = BoxMask;
  2954. BoxMask.prototype.type = "BoxMask";
  2955. Object2D.register(BoxMask, "BoxMask");
  2956. BoxMask.prototype.isInside = function(point)
  2957. {
  2958. return this.box.containsPoint(point);
  2959. };
  2960. BoxMask.prototype.clip = function(context, viewport, canvas)
  2961. {
  2962. context.beginPath();
  2963. var width = this.box.max.x - this.box.min.x;
  2964. if(this.invert)
  2965. {
  2966. context.rect(this.box.min.x - 1e4, -5e3, 1e4, 1e4);
  2967. context.rect(this.box.max.x, -5e3, 1e4, 1e4);
  2968. context.rect(this.box.min.x, this.box.min.y - 1e4, width, 1e4);
  2969. context.rect(this.box.min.x, this.box.max.y, width, 1e4);
  2970. }
  2971. else
  2972. {
  2973. var height = this.box.max.y - this.box.min.y;
  2974. context.fillRect(this.box.min.x, this.box.min.y, width, height);
  2975. }
  2976. context.clip();
  2977. };
  2978. /**
  2979. * Style represents in a generic way a style applied to canvas drawing.
  2980. *
  2981. * Some styles (e.g. gradients, patterns) required a context to be generated this provides a generic way to share styles between objects.
  2982. *
  2983. * @class
  2984. */
  2985. function Style$1()
  2986. {
  2987. /**
  2988. * Cached style object pre-generated from previous calls. To avoid regenerating the same style object every cycle.
  2989. *
  2990. * Inherited classes should write their own get method that returns the style object and stores it in this property.
  2991. *
  2992. * @type {string | CanvasGradient | CanvasPattern}
  2993. */
  2994. this.cache = null;
  2995. /**
  2996. * Indicates if the style object needs to be updated, should be used after applying changed to the style in order to generate a new object.
  2997. *
  2998. * Inherited classes should implement this functionality.
  2999. *
  3000. * @type {boolean}
  3001. */
  3002. this.needsUpdate = true;
  3003. }
  3004. /**
  3005. * Get generated style object from style data and the drawing context.
  3006. *
  3007. * @param {CanvasRenderingContext2D} context Context being used to draw the object.
  3008. * @return {string | CanvasGradient | CanvasPattern} Return the canvas style object generated.
  3009. */
  3010. Style$1.prototype.get = function(context) {};
  3011. /**
  3012. * Serialize the style to JSON object, called by the objects using these styles.
  3013. *
  3014. * @return {Object} Serialized style data.
  3015. */
  3016. Style$1.prototype.serialize = function() {};
  3017. /**
  3018. * Parse the style attributes from JSON object data created with the serialize() method.
  3019. *
  3020. * @param {Object} data Serialized style data.
  3021. */
  3022. Style$1.prototype.parse = function(data) {};
  3023. /**
  3024. * List of available style types known by the application. Stores the object constructor by object type.
  3025. *
  3026. * @static
  3027. * @type {Map<string, Function>}
  3028. */
  3029. Style$1.types = new Map([]);
  3030. /**
  3031. * Register a style type to be serializable. Associates the type string to the object constructor.
  3032. *
  3033. * @param {Function} constructor Style constructor.
  3034. * @param {string} type Style type name.
  3035. */
  3036. Style$1.register = function(constructor, type)
  3037. {
  3038. Style$1.types.set(type, constructor);
  3039. };
  3040. /**
  3041. * Parse style from JSON serialized data, created a style of the correct data type automatically and parses its data.
  3042. *
  3043. * @param data JSON serialized data.
  3044. * @returns {Style} Parsed style from the provided data.
  3045. */
  3046. Style$1.parse = function (data)
  3047. {
  3048. var style = new (Style$1.types.get(data.type))();
  3049. style.parse(data);
  3050. return style;
  3051. };
  3052. /**
  3053. * Simple solid color style represented and stored as a CSS color.
  3054. *
  3055. * Example value formats supported "rgb(0, 153, 255)" or "rgba(0, 153, 255, 0.3)" or "#0099ff" or "#0099ffaa" or "red".
  3056. *
  3057. * @class
  3058. * @extends {Style}
  3059. * @param {string} color Color of the style, if undefined it is set to black.
  3060. */
  3061. function ColorStyle(color)
  3062. {
  3063. Style$1.call(this);
  3064. /**
  3065. * Color of this style object.
  3066. *
  3067. * @type {string}
  3068. */
  3069. this.color = color || "#000000";
  3070. }
  3071. ColorStyle.prototype = Object.create(Style$1.prototype);
  3072. Style$1.register(ColorStyle, "Color");
  3073. ColorStyle.prototype.get = function(context)
  3074. {
  3075. return this.color;
  3076. };
  3077. ColorStyle.prototype.serialize = function()
  3078. {
  3079. return {
  3080. type: "Color",
  3081. color: this.color
  3082. };
  3083. };
  3084. ColorStyle.prototype.parse = function(data)
  3085. {
  3086. this.color = data.color;
  3087. };
  3088. /**
  3089. * Box object draw a rectangular object.
  3090. *
  3091. * Can be used as a base to implement other box objects, already implements collision for pointer events.
  3092. *
  3093. * @class
  3094. * @extends {Object2D}
  3095. */
  3096. function Box()
  3097. {
  3098. Object2D.call(this);
  3099. /**
  3100. * Box object containing the size of the object.
  3101. *
  3102. * @type {Box2}
  3103. */
  3104. this.box = new Box2(new Vector2(-50, -50), new Vector2(50, 50));
  3105. /**
  3106. * Style of the object border line.
  3107. *
  3108. * If set null it is ignored.
  3109. *
  3110. * @type {Style}
  3111. */
  3112. this.strokeStyle = new ColorStyle("#000000");
  3113. /**
  3114. * Line width, only used if a valid strokeStyle is defined.
  3115. *
  3116. * @type {number}
  3117. */
  3118. this.lineWidth = 1;
  3119. /**
  3120. * Background color of the box.
  3121. *
  3122. * If set null it is ignored.
  3123. *
  3124. * @type {Style}
  3125. */
  3126. this.fillStyle = new ColorStyle("#FFFFFF");
  3127. }
  3128. Box.prototype = Object.create(Object2D.prototype);
  3129. Box.prototype.constructor = Box;
  3130. Box.prototype.type = "Box";
  3131. Object2D.register(Box, "Box");
  3132. Box.prototype.isInside = function(point)
  3133. {
  3134. return this.box.containsPoint(point);
  3135. };
  3136. Box.prototype.draw = function(context, viewport, canvas)
  3137. {
  3138. var width = this.box.max.x - this.box.min.x;
  3139. var height = this.box.max.y - this.box.min.y;
  3140. if(this.fillStyle !== null)
  3141. {
  3142. context.fillStyle = this.fillStyle.get(context);
  3143. context.fillRect(this.box.min.x, this.box.min.y, width, height);
  3144. }
  3145. if(this.strokeStyle !== null)
  3146. {
  3147. context.lineWidth = this.lineWidth;
  3148. context.strokeStyle = this.strokeStyle.get(context);
  3149. context.strokeRect(this.box.min.x, this.box.min.y, width, height);
  3150. }
  3151. };
  3152. Box.prototype.serialize = function(recursive)
  3153. {
  3154. var data = Object2D.prototype.serialize.call(this, recursive);
  3155. data.box = this.box.toArray();
  3156. data.strokeStyle = this.strokeStyle !== null ? this.strokeStyle.serialize() : null;
  3157. data.lineWidth = this.lineWidth;
  3158. data.fillStyle = this.fillStyle !== null ? this.fillStyle.serialize() : null;
  3159. return data;
  3160. };
  3161. Box.prototype.parse = function(data, root)
  3162. {
  3163. Object2D.prototype.parse.call(this, data, root);
  3164. this.box.fromArray(data.box);
  3165. this.strokeStyle = data.strokeStyle !== null ? Style$1.parse(data.strokeStyle) : null;
  3166. this.lineWidth = data.lineWidth;
  3167. this.fillStyle = data.fillStyle !== null ? Style$1.parse(data.fillStyle) : null;
  3168. };
  3169. /**
  3170. * Circle object draw a circular object, into the canvas.
  3171. *
  3172. * Can be used as a base to implement other circular objects, already implements the circle collision for pointer events.
  3173. *
  3174. * @class
  3175. * @extends {Object2D}
  3176. */
  3177. function Circle()
  3178. {
  3179. Object2D.call(this);
  3180. /**
  3181. * Radius of the circle.
  3182. *
  3183. * @type {number}
  3184. */
  3185. this.radius = 10.0;
  3186. /**
  3187. * Style of the object border line.
  3188. *
  3189. * If set null it is ignored.
  3190. *
  3191. * @type {Style}
  3192. */
  3193. this.strokeStyle = new ColorStyle("#000000");
  3194. /**
  3195. * Line width, only used if a valid strokeStyle is defined.
  3196. *
  3197. * @type {number}
  3198. */
  3199. this.lineWidth = 1;
  3200. /**
  3201. * Background color of the circle.
  3202. *
  3203. * If set null it is ignored.
  3204. *
  3205. * @type {Style}
  3206. */
  3207. this.fillStyle = new ColorStyle("#FFFFFF");
  3208. }
  3209. Circle.prototype = Object.create(Object2D.prototype);
  3210. Circle.prototype.constructor = Circle;
  3211. Circle.prototype.type = "Circle";
  3212. Object2D.register(Circle, "Circle");
  3213. Circle.prototype.isInside = function(point)
  3214. {
  3215. return point.length() <= this.radius;
  3216. };
  3217. Circle.prototype.draw = function(context, viewport, canvas)
  3218. {
  3219. context.beginPath();
  3220. context.arc(0, 0, this.radius, 0, 2 * Math.PI);
  3221. if(this.fillStyle !== null)
  3222. {
  3223. context.fillStyle = this.fillStyle.get(context);
  3224. context.fill();
  3225. }
  3226. if(this.strokeStyle !== null)
  3227. {
  3228. context.lineWidth = this.lineWidth;
  3229. context.strokeStyle = this.strokeStyle.get(context);
  3230. context.stroke();
  3231. }
  3232. };
  3233. Circle.prototype.serialize = function(recursive)
  3234. {
  3235. var data = Object2D.prototype.serialize.call(this, recursive);
  3236. data.radius = this.radius;
  3237. data.strokeStyle = this.strokeStyle !== null ? this.strokeStyle.serialize() : null;
  3238. data.lineWidth = this.lineWidth;
  3239. data.fillStyle = this.fillStyle !== null ? this.fillStyle.serialize() : null;
  3240. return data;
  3241. };
  3242. Circle.prototype.parse = function(data, root)
  3243. {
  3244. Object2D.prototype.parse.call(this, data, root);
  3245. this.radius = data.radius;
  3246. this.strokeStyle = data.strokeStyle !== null ? Style$1.parse(data.strokeStyle) : null;
  3247. this.lineWidth = data.lineWidth;
  3248. this.fillStyle = data.fillStyle !== null ? Style$1.parse(data.fillStyle) : null;
  3249. };
  3250. /**
  3251. * Line object draw a line from one point to another without any kind of interpolation.
  3252. *
  3253. * For drawing lines with interpolation check {BezierCurve}
  3254. *
  3255. * @class
  3256. * @extends {Object2D}
  3257. */
  3258. function Line()
  3259. {
  3260. Object2D.call(this);
  3261. /**
  3262. * Initial point of the line.
  3263. *
  3264. * Can be equal to the position object of another object. Making it automatically follow that object.
  3265. *
  3266. * @type {Vector2}
  3267. */
  3268. this.from = new Vector2();
  3269. /**
  3270. * Final point of the line.
  3271. *
  3272. * Can be equal to the position object of another object. Making it automatically follow that object.
  3273. *
  3274. * @type {Vector2}
  3275. */
  3276. this.to = new Vector2();
  3277. /**
  3278. * Dash line pattern to be used, if empty draws a solid line.
  3279. *
  3280. * Dash pattern is defined as the size of dashes as pairs of space with no line and with line.
  3281. *
  3282. * E.g if the dash pattern is [1, 2] we get 1 point with line, 2 without line repeat infinitelly.
  3283. *
  3284. * @type {number[]}
  3285. */
  3286. this.dashPattern = [5, 5];
  3287. /**
  3288. * Style of the object line.
  3289. *
  3290. * @type {Style}
  3291. */
  3292. this.strokeStyle = new ColorStyle("#000000");
  3293. /**
  3294. * Line width of the line.
  3295. *
  3296. * @type {number}
  3297. */
  3298. this.lineWidth = 1;
  3299. }
  3300. Line.prototype = Object.create(Object2D.prototype);
  3301. Line.prototype.constructor = Line;
  3302. Line.prototype.type = "Line";
  3303. Object2D.register(Line, "Line");
  3304. Line.prototype.style = function(context, viewport, canvas)
  3305. {
  3306. context.lineWidth = this.lineWidth;
  3307. context.strokeStyle = this.strokeStyle.get(context);
  3308. context.setLineDash(this.dashPattern);
  3309. };
  3310. Line.prototype.draw = function(context, viewport, canvas)
  3311. {
  3312. context.beginPath();
  3313. context.moveTo(this.from.x, this.from.y);
  3314. context.lineTo(this.to.x, this.to.y);
  3315. context.stroke();
  3316. };
  3317. Line.prototype.serialize = function(recursive)
  3318. {
  3319. var data = Object2D.prototype.serialize.call(this, recursive);
  3320. data.from = this.from.toArray();
  3321. data.to = this.to.toArray();
  3322. data.dashPattern = this.dashPattern;
  3323. data.strokeStyle = this.strokeStyle !== null ? this.strokeStyle.serialize() : null;
  3324. data.lineWidth = this.lineWidth;
  3325. return data;
  3326. };
  3327. Line.prototype.parse = function(data, root)
  3328. {
  3329. Object2D.prototype.parse.call(this, data, root);
  3330. this.to.fromArray(data.to);
  3331. this.from.fromArray(data.from);
  3332. this.dashPattern = data.dashPattern;
  3333. this.strokeStyle = data.strokeStyle !== null ? Style$1.parse(data.strokeStyle) : null;
  3334. this.lineWidth = data.lineWidth;
  3335. };
  3336. /**
  3337. * Text element, used to draw single line text into the canvas.
  3338. *
  3339. * For multi line text with support for line break check {MultiLineText} object.
  3340. *
  3341. * @class
  3342. * @extends {Object2D}
  3343. */
  3344. function Text()
  3345. {
  3346. Object2D.call(this);
  3347. /**
  3348. * Text value displayed by this element.
  3349. *
  3350. * @type {string}
  3351. */
  3352. this.text = "";
  3353. /**
  3354. * Font of the text.
  3355. *
  3356. * @type {string}
  3357. */
  3358. this.font = "16px Arial";
  3359. /**
  3360. * Style of the object border line. If set null it is ignored.
  3361. *
  3362. * @type {Style}
  3363. */
  3364. this.strokeStyle = null;
  3365. /**
  3366. * Line width, only used if a valid strokeStyle is defined.
  3367. *
  3368. * @type {number}
  3369. */
  3370. this.lineWidth = 1;
  3371. /**
  3372. * CSS background color of the box. If set null it is ignored.
  3373. *
  3374. * @type {Style}
  3375. */
  3376. this.fillStyle = new ColorStyle("#000000");
  3377. /**
  3378. * Text align property. Same values as used for canvas text applies
  3379. *
  3380. * Check documentation at https://developer.mozilla.org/en-US/docs/Web/CSS/text-align for mode details about this property.
  3381. *
  3382. * @type {string}
  3383. */
  3384. this.textAlign = "center";
  3385. /**
  3386. * Text baseline defines the vertical position of the text relative to the imaginary line Y position. Same values as used for canvas text applies
  3387. *
  3388. * Check documentation at https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/textBaseline for mode details about this property.
  3389. *
  3390. * @type {string}
  3391. */
  3392. this.textBaseline = "middle";
  3393. }
  3394. Text.prototype = Object.create(Object2D.prototype);
  3395. Text.prototype.constructor = Text;
  3396. Text.prototype.type = "Text";
  3397. Object2D.register(Text, "Text");
  3398. Text.prototype.draw = function(context, viewport, canvas)
  3399. {
  3400. context.font = this.font;
  3401. context.textAlign = this.textAlign;
  3402. context.textBaseline = this.textBaseline;
  3403. if(this.fillStyle !== null)
  3404. {
  3405. context.fillStyle = this.fillStyle.get(context);
  3406. context.fillText(this.text, 0, 0);
  3407. }
  3408. if(this.strokeStyle !== null)
  3409. {
  3410. context.strokeStyle = this.strokeStyle.get(context);
  3411. context.strokeText(this.text, 0, 0);
  3412. }
  3413. };
  3414. Text.prototype.serialize = function(recursive)
  3415. {
  3416. var data = Object2D.prototype.serialize.call(this, recursive);
  3417. data.text = this.text;
  3418. data.font = this.font;
  3419. data.strokeStyle = this.strokeStyle !== null ? this.strokeStyle.serialize() : null;
  3420. data.lineWidth = this.lineWidth;
  3421. data.fillStyle = this.fillStyle !== null ? this.fillStyle.serialize() : null;
  3422. data.textAlign = this.textAlign;
  3423. data.textBaseline = this.textBaseline;
  3424. return data;
  3425. };
  3426. Text.prototype.parse = function(data, root)
  3427. {
  3428. Object2D.prototype.parse.call(this, data, root);
  3429. this.text = data.text;
  3430. this.font = data.font;
  3431. this.strokeStyle = data.strokeStyle !== null ? Style.parse(data.strokeStyle) : null;
  3432. this.lineWidth = data.lineWidth;
  3433. this.fillStyle = data.fillStyle !== null ? Style.parse(data.fillStyle) : null;
  3434. this.textAlign = data.textAlign;
  3435. this.textBaseline = data.textBaseline;
  3436. };
  3437. /**
  3438. * Image object is used to draw an image from URL.
  3439. *
  3440. * @class
  3441. * @param {string} src Source URL of the image.
  3442. * @extends {Object2D}
  3443. */
  3444. function Image(src)
  3445. {
  3446. Object2D.call(this);
  3447. /**
  3448. * Box object containing the size of the object.
  3449. *
  3450. * @type {Box2}
  3451. */
  3452. this.box = new Box2();
  3453. /**
  3454. * Image source DOM element.
  3455. *
  3456. * @type {HTMLImageElement}
  3457. */
  3458. this.image = document.createElement("img");
  3459. if(src !== undefined)
  3460. {
  3461. this.setImage(src);
  3462. }
  3463. }
  3464. Image.prototype = Object.create(Object2D.prototype);
  3465. Image.prototype.constructor = Image;
  3466. Image.prototype.type = "Image";
  3467. Object2D.register(Image, "Image");
  3468. /**
  3469. * Set the image of the object.
  3470. *
  3471. * Automatically sets the box size to match the image.
  3472. *
  3473. * @param {string} src Source URL of the image.
  3474. */
  3475. Image.prototype.setImage = function(src)
  3476. {
  3477. var self = this;
  3478. this.image.onload = function()
  3479. {
  3480. self.box.min.set(0, 0);
  3481. self.box.max.set(this.naturalWidth, this.naturalHeight);
  3482. };
  3483. this.image.src = src;
  3484. };
  3485. Image.prototype.isInside = function(point)
  3486. {
  3487. return this.box.containsPoint(point);
  3488. };
  3489. Image.prototype.draw = function(context, viewport, canvas)
  3490. {
  3491. if(this.image.src.length > 0)
  3492. {
  3493. context.drawImage(this.image, 0, 0, this.image.naturalWidth, this.image.naturalHeight, this.box.min.x, this.box.min.y, this.box.max.x - this.box.min.x, this.box.max.y - this.box.min.y);
  3494. }
  3495. };
  3496. Image.prototype.serialize = function(recursive)
  3497. {
  3498. var data = Object2D.prototype.serialize.call(this, recursive);
  3499. data.box = this.box.toArray();
  3500. data.image = this.image.src;
  3501. return data;
  3502. };
  3503. Image.prototype.parse = function(data, root)
  3504. {
  3505. Object2D.prototype.parse.call(this, data, root);
  3506. this.box.fromArray(data.box);
  3507. this.image.src = data.image;
  3508. };
  3509. /**
  3510. * A DOM object transformed using CSS3D to be included in the scene.
  3511. *
  3512. * DOM objects always stay on top or bellow (depending on the DOM parent placement) of everything else. It is not possible to layer these object with regular canvas objects.
  3513. *
  3514. * By default mouse events are not supported for these objects (it does not implement pointer collision checking). Use the DOM events for interaction with these types of objects.
  3515. *
  3516. * @class
  3517. * @param {string} type Type of the DOM element (e.g. "div", "p", ...)
  3518. * @extends {Object2D}
  3519. */
  3520. function DOM(type)
  3521. {
  3522. Object2D.call(this);
  3523. /**
  3524. * Parent element that contains this DOM object.
  3525. *
  3526. * The DOM parent element if not set manually is automatically set to the parent of the drawing canvas.
  3527. *
  3528. * @type {Element}
  3529. */
  3530. this.parentElement = null;
  3531. /**
  3532. * DOM element contained by this object.
  3533. *
  3534. * By default it has the pointerEvents style set to none. In order to use any DOM event with this object first you have to set the element.style.pointerEvents to "auto".
  3535. *
  3536. * @type {Element}
  3537. */
  3538. this.element = document.createElement(type || "div");
  3539. this.element.style.transformStyle = "preserve-3d";
  3540. this.element.style.position = "absolute";
  3541. this.element.style.top = "0px";
  3542. this.element.style.bottom = "0px";
  3543. this.element.style.transformOrigin = "0px 0px";
  3544. this.element.style.overflow = "auto";
  3545. this.element.style.pointerEvents = "none";
  3546. /**
  3547. * Size of the DOM element, in world coordinates.
  3548. *
  3549. * Size is used to set the width and height of the DOM element.
  3550. *
  3551. * @type {Vector2}
  3552. */
  3553. this.size = new Vector2(100, 100);
  3554. }
  3555. DOM.prototype = Object.create(Object2D.prototype);
  3556. DOM.prototype.constructor = DOM;
  3557. DOM.prototype.type = "DOM";
  3558. Object2D.register(DOM, "DOM");
  3559. /**
  3560. * DOM object implements onAdd() method to automatically attach the DOM object to the DOM tree.
  3561. */
  3562. DOM.prototype.onAdd = function()
  3563. {
  3564. if(this.parentElement !== null)
  3565. {
  3566. this.parentElement.appendChild(this.element);
  3567. }
  3568. };
  3569. /**
  3570. * DOM object implements onRemove() method to automatically remove the DOM object to the DOM tree.
  3571. */
  3572. DOM.prototype.onRemove = function()
  3573. {
  3574. if(this.parentElement !== null)
  3575. {
  3576. this.parentElement.removeChild(this.element);
  3577. }
  3578. };
  3579. DOM.prototype.transform = function(context, viewport, canvas, renderer)
  3580. {
  3581. // Check if the DOM element parent is null
  3582. if(this.parentElement === null)
  3583. {
  3584. this.parentElement = renderer.getDomContainer();
  3585. this.parentElement.appendChild(this.element);
  3586. }
  3587. // CSS transformation matrix
  3588. if(this.ignoreViewport)
  3589. {
  3590. this.element.style.transform = this.globalMatrix.cssTransform();
  3591. }
  3592. else
  3593. {
  3594. var projection = viewport.matrix.clone();
  3595. projection.multiply(this.globalMatrix);
  3596. this.element.style.transform = projection.cssTransform();
  3597. }
  3598. // Size of the element
  3599. this.element.style.width = this.size.x + "px";
  3600. this.element.style.height = this.size.y + "px";
  3601. // Visibility
  3602. this.element.style.display = this.visible ? "block" : "none";
  3603. };
  3604. DOM.prototype.serialize = function(recursive)
  3605. {
  3606. var data = Object2D.prototype.serialize.call(this, recursive);
  3607. data.size = this.size.toArray();
  3608. data.element = this.element.outerHTML;
  3609. return data;
  3610. };
  3611. DOM.prototype.parse = function(data, root)
  3612. {
  3613. Object2D.prototype.parse.call(this, data, root);
  3614. this.size.fromArray(data.size);
  3615. var parser = new DOMParser();
  3616. var doc = parser.parseFromString(this.element.outerHTML, 'text/html');
  3617. this.element = doc.body.children[0];
  3618. };
  3619. /**
  3620. * Pattern object draw a image repeated as a pattern.
  3621. *
  3622. * Its similar to the Image class but the image can be repeat infinitely.
  3623. *
  3624. * @class
  3625. * @extends {Object2D}
  3626. * @param {string} src Source image URL.
  3627. */
  3628. function Pattern(src)
  3629. {
  3630. Object2D.call(this);
  3631. /**
  3632. * Box object containing the size of the object.
  3633. *
  3634. * @type {Box2}
  3635. */
  3636. this.box = new Box2();
  3637. /**
  3638. * Image source DOM element. Used as a source for the pattern image.
  3639. *
  3640. * This element can be replaced by one of other type (e.g canvas, video).
  3641. *
  3642. * @type {Element}
  3643. */
  3644. this.image = document.createElement("img");
  3645. /**
  3646. * Repetition indicates how the pattern image should be repeated.
  3647. *
  3648. * Possible values are "repeat", "repeat-x", "repeat-y" or "no-repeat".
  3649. *
  3650. * More information about this attribute here https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/createPattern.
  3651. *
  3652. * @type {string}
  3653. */
  3654. this.repetition = "repeat";
  3655. if(src !== undefined)
  3656. {
  3657. this.setImage(src);
  3658. }
  3659. }
  3660. Pattern.prototype = Object.create(Object2D.prototype);
  3661. Pattern.prototype.constructor = Pattern;
  3662. Pattern.prototype.type = "Pattern";
  3663. Object2D.register(Pattern, "Pattern");
  3664. /**
  3665. * Set the image source of the object. Can be anything accepted by the src field of an img element.
  3666. *
  3667. * Automatically sets the box size to match the image.
  3668. *
  3669. * @param {string} src Image source string.
  3670. */
  3671. Pattern.prototype.setImage = function(src)
  3672. {
  3673. var self = this;
  3674. this.image.onload = function()
  3675. {
  3676. self.box.min.set(0, 0);
  3677. self.box.max.set(this.naturalWidth, this.naturalHeight);
  3678. };
  3679. this.image.src = src;
  3680. };
  3681. Pattern.prototype.isInside = function(point)
  3682. {
  3683. return this.box.containsPoint(point);
  3684. };
  3685. Pattern.prototype.draw = function(context, viewport, canvas)
  3686. {
  3687. var width = this.box.max.x - this.box.min.x;
  3688. var height = this.box.max.y - this.box.min.y;
  3689. if(this.image.src.length > 0)
  3690. {
  3691. var pattern = context.createPattern(this.image, this.repetition);
  3692. context.fillStyle = pattern;
  3693. context.fillRect(this.box.min.x, this.box.min.y, width, height);
  3694. }
  3695. };
  3696. Pattern.prototype.serialize = function(recursive)
  3697. {
  3698. var data = Object2D.prototype.serialize.call(this, recursive);
  3699. data.box = this.box.toArray();
  3700. data.image = this.image.src;
  3701. data.repetition = this.repetition;
  3702. return data;
  3703. };
  3704. Pattern.prototype.parse = function(data, root)
  3705. {
  3706. Object2D.prototype.parse.call(this, data, root);
  3707. this.box.fromArray(data.box);
  3708. this.image.src = data.image;
  3709. this.repetition = data.repetition;
  3710. };
  3711. /**
  3712. * Multiple line text drawing directly into the canvas.
  3713. *
  3714. * Has support for basic text indent and alignment.
  3715. *
  3716. * @class
  3717. * @extends {Text}
  3718. */
  3719. function MultiLineText()
  3720. {
  3721. Text.call(this);
  3722. /**
  3723. * Maximum width of the text content. After text reaches the max width a line break is placed.
  3724. *
  3725. * Can be set to null to be ignored.
  3726. *
  3727. * @type {number}
  3728. */
  3729. this.maxWidth = null;
  3730. /**
  3731. * Height of each line of text, can be smaller or larger than the actual font size.
  3732. *
  3733. * Can be set to null to be ignored.
  3734. *
  3735. * @type {number}
  3736. */
  3737. this.lineHeight = null;
  3738. }
  3739. MultiLineText.prototype = Object.create(Text.prototype);
  3740. MultiLineText.prototype.constructor = MultiLineText;
  3741. MultiLineText.prototype.type = "MultiLineText";
  3742. Object2D.register(MultiLineText, "MultiLineText");
  3743. MultiLineText.prototype.draw = function(context, viewport, canvas)
  3744. {
  3745. context.font = this.font;
  3746. context.textAlign = this.textAlign;
  3747. context.textBaseline = this.textBaseline;
  3748. var lineHeight = this.lineHeight || Number.parseFloat(this.font);
  3749. var lines = this.text.split("\n");
  3750. var offsetY = 0;
  3751. // Iterate trough all lines (breakpoints)
  3752. for(var i = 0; i < lines.length; i++)
  3753. {
  3754. var line = lines[i];
  3755. var size = context.measureText(line);
  3756. var sublines = [];
  3757. // Split into multiple sub-lines
  3758. if(this.maxWidth !== null && size.width > this.maxWidth)
  3759. {
  3760. while(line.length > 0)
  3761. {
  3762. var subline = "";
  3763. var subsize = context.measureText(subline + line[0]);
  3764. while(subsize.width < this.maxWidth && line.length > 0)
  3765. {
  3766. subline += line[0];
  3767. line = line.substr(1);
  3768. subsize = context.measureText(subline + line[0]);
  3769. }
  3770. sublines.push(subline);
  3771. }
  3772. }
  3773. // Fits into a single line
  3774. else
  3775. {
  3776. sublines = [line];
  3777. }
  3778. for(var j = 0; j < sublines.length; j++)
  3779. {
  3780. if(this.fillStyle !== null)
  3781. {
  3782. context.fillStyle = this.fillStyle.get(context);
  3783. context.fillText(sublines[j], this.position.x, this.position.y + offsetY);
  3784. }
  3785. if(this.strokeStyle !== null)
  3786. {
  3787. context.lineWidth = this.lineWidth;
  3788. context.strokeStyle = this.strokeStyle.get(context);
  3789. context.strokeText(sublines[j], this.position.x, this.position.y + offsetY);
  3790. }
  3791. offsetY += lineHeight;
  3792. }
  3793. }
  3794. };
  3795. MultiLineText.prototype.serialize = function(recursive)
  3796. {
  3797. var data = Text.prototype.serialize.call(this, recursive);
  3798. data.maxWidth = this.maxWidth;
  3799. data.lineHeight = this.lineHeight;
  3800. return data;
  3801. };
  3802. MultiLineText.prototype.parse = function(data, root)
  3803. {
  3804. Text.prototype.parse.call(this, data, root);
  3805. this.maxWidth = data.maxWidth;
  3806. this.lineHeight = data.lineHeight;
  3807. };
  3808. /**
  3809. * Bezier curve object draw as bezier curve between two points.
  3810. *
  3811. * Bezier curve data is composed of two anchor points, one for the start of the curve and one for the end of the curve.
  3812. *
  3813. * @class
  3814. * @extends {Line}
  3815. */
  3816. function BezierCurve()
  3817. {
  3818. Line.call(this);
  3819. /**
  3820. * Initial position control point, indicates the tangent of the bezier curve on the first point.
  3821. *
  3822. * @type {Vector2}
  3823. */
  3824. this.fromCp = new Vector2();
  3825. /**
  3826. * Final position control point, indicates the tangent of the bezier curve on the last point.
  3827. *
  3828. * @type {Vector2}
  3829. */
  3830. this.toCp = new Vector2();
  3831. }
  3832. BezierCurve.prototype = Object.create(Line.prototype);
  3833. BezierCurve.prototype.constructor = BezierCurve;
  3834. BezierCurve.prototype.type = "BezierCurve";
  3835. Object2D.register(BezierCurve, "BezierCurve");
  3836. /**
  3837. * Create a bezier curve helper, to edit the bezier curve anchor points.
  3838. *
  3839. * Helper objects are added to the parent of the curve object.
  3840. *
  3841. * @static
  3842. * @param {BezierCurve} object Object to create the helper for.
  3843. */
  3844. BezierCurve.curveHelper = function(object)
  3845. {
  3846. var fromCp = new Circle();
  3847. fromCp.radius = 3;
  3848. fromCp.layer = object.layer + 1;
  3849. fromCp.draggable = true;
  3850. fromCp.onPointerDrag = function(pointer, viewport, delta)
  3851. {
  3852. Object2D.prototype.onPointerDrag.call(this, pointer, viewport, delta);
  3853. object.fromCp.copy(fromCp.position);
  3854. };
  3855. object.parent.add(fromCp);
  3856. var fromLine = new Line();
  3857. fromLine.from = object.from;
  3858. fromLine.to = object.fromCp;
  3859. object.parent.add(fromLine);
  3860. var toCp = new Circle();
  3861. toCp.radius = 3;
  3862. toCp.layer = object.layer + 1;
  3863. toCp.draggable = true;
  3864. toCp.onPointerDrag = function(pointer, viewport, delta)
  3865. {
  3866. Object2D.prototype.onPointerDrag.call(this, pointer, viewport, delta);
  3867. object.toCp.copy(toCp.position);
  3868. };
  3869. object.parent.add(toCp);
  3870. var toLine = new Line();
  3871. toLine.from = object.to;
  3872. toLine.to = object.toCp;
  3873. object.parent.add(toLine);
  3874. };
  3875. BezierCurve.prototype.draw = function(context, viewport, canvas)
  3876. {
  3877. context.beginPath();
  3878. context.moveTo(this.from.x, this.from.y);
  3879. context.bezierCurveTo(this.fromCp.x, this.fromCp.y, this.toCp.x, this.toCp.y, this.to.x, this.to.y);
  3880. context.stroke();
  3881. };
  3882. BezierCurve.prototype.serialize = function(recursive)
  3883. {
  3884. var data = Line.prototype.serialize.call(this, recursive);
  3885. data.fromCp = this.fromCp.toArray();
  3886. data.toCp = this.toCp.toArray();
  3887. return data;
  3888. };
  3889. BezierCurve.prototype.parse = function(data, root)
  3890. {
  3891. Line.prototype.parse.call(this, data, root);
  3892. this.fromCp.fromArray(data.fromCp);
  3893. this.toCp.fromArray(data.toCp);
  3894. };
  3895. /**
  3896. * Quadratic curve object draw as quadratic curve between two points.
  3897. *
  3898. * Quadratic curve data is composed of two anchor points, one for the start of the curve and one for the end of the curve.
  3899. *
  3900. * @class
  3901. * @extends {Object2D}
  3902. */
  3903. function QuadraticCurve()
  3904. {
  3905. Line.call(this);
  3906. /**
  3907. * Control point of the quadratic curve used to control the curvature of the line between the from and to point.
  3908. *
  3909. * The curve is interpolated in the direction of the control point it defined the path of the curve.
  3910. *
  3911. * @type {Vector2}
  3912. */
  3913. this.controlPoint = new Vector2();
  3914. }
  3915. QuadraticCurve.prototype = Object.create(Line.prototype);
  3916. QuadraticCurve.prototype.constructor = QuadraticCurve;
  3917. QuadraticCurve.prototype.type = "QuadraticCurve";
  3918. Object2D.register(QuadraticCurve, "QuadraticCurve");
  3919. /**
  3920. * Create a quadratic curve helper, to edit the curve control point.
  3921. *
  3922. * Helper objects are added to the parent of the curve object.
  3923. *
  3924. * @static
  3925. * @param {QuadraticCurve} object Object to create the helper for.
  3926. */
  3927. QuadraticCurve.curveHelper = function(object)
  3928. {
  3929. var fromLine = new Line();
  3930. fromLine.from = object.from;
  3931. fromLine.to = object.controlPoint;
  3932. object.parent.add(fromLine);
  3933. var controlPoint = new Circle();
  3934. controlPoint.radius = 3;
  3935. controlPoint.layer = object.layer + 1;
  3936. controlPoint.draggable = true;
  3937. controlPoint.position = object.controlPoint;
  3938. controlPoint.onPointerDrag = function(pointer, viewport, delta)
  3939. {
  3940. Object2D.prototype.onPointerDrag.call(this, pointer, viewport, delta);
  3941. object.controlPoint.copy(controlPoint.position);
  3942. };
  3943. object.parent.add(controlPoint);
  3944. var toLine = new Line();
  3945. toLine.from = object.to;
  3946. toLine.to = object.controlPoint;
  3947. object.parent.add(toLine);
  3948. };
  3949. QuadraticCurve.prototype.draw = function(context, viewport, canvas)
  3950. {
  3951. context.beginPath();
  3952. context.moveTo(this.from.x, this.from.y);
  3953. context.quadraticCurveTo(this.controlPoint.x, this.controlPoint.y, this.to.x, this.to.y);
  3954. context.stroke();
  3955. };
  3956. QuadraticCurve.prototype.serialize = function(recursive)
  3957. {
  3958. var data = Line.prototype.serialize.call(this, recursive);
  3959. data.controlPoint = this.controlPoint.toArray();
  3960. return data;
  3961. };
  3962. QuadraticCurve.prototype.parse = function(data, root)
  3963. {
  3964. Line.prototype.parse.call(this, data, root);
  3965. this.controlPoint.fromArray(data.controlPoint);
  3966. };
  3967. /**
  3968. * Rounded box object draw a rectangular object with rounded corners.
  3969. *
  3970. * @class
  3971. * @extends {Box}
  3972. */
  3973. function RoundedBox()
  3974. {
  3975. Box.call(this);
  3976. /**
  3977. * Radius of the circular section that makes up the box corners.
  3978. *
  3979. * @type {number}
  3980. */
  3981. this.radius = 5;
  3982. }
  3983. RoundedBox.prototype = Object.create(Box.prototype);
  3984. RoundedBox.prototype.constructor = RoundedBox;
  3985. RoundedBox.prototype.type = "RoundedBox";
  3986. Object2D.register(RoundedBox, "RoundedBox");
  3987. /**
  3988. * Draw a rounded rectangle into the canvas context using path to draw the rounded rectangle.
  3989. *
  3990. * @param {CanvasRenderingContext2D} context
  3991. * @param {number} x The top left x coordinate
  3992. * @param {number} y The top left y coordinate
  3993. * @param {number} width The width of the rectangle
  3994. * @param {number} height The height of the rectangle
  3995. * @param {number} radius Radius of the rectangle corners.
  3996. */
  3997. RoundedBox.roundRect = function(context, x, y, width, height, radius)
  3998. {
  3999. context.beginPath();
  4000. context.moveTo(x + radius, y);
  4001. context.lineTo(x + width - radius, y);
  4002. context.quadraticCurveTo(x + width, y, x + width, y + radius);
  4003. context.lineTo(x + width, y + height - radius);
  4004. context.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
  4005. context.lineTo(x + radius, y + height);
  4006. context.quadraticCurveTo(x, y + height, x, y + height - radius);
  4007. context.lineTo(x, y + radius);
  4008. context.quadraticCurveTo(x, y, x + radius, y);
  4009. context.closePath();
  4010. };
  4011. RoundedBox.prototype.draw = function(context, viewport, canvas)
  4012. {
  4013. var width = this.box.max.x - this.box.min.x;
  4014. var height = this.box.max.y - this.box.min.y;
  4015. if(this.fillStyle !== null)
  4016. {
  4017. context.fillStyle = this.fillStyle.get(context);
  4018. RoundedBox.roundRect(context, this.box.min.x, this.box.min.y, width, height, this.radius);
  4019. context.fill();
  4020. }
  4021. if(this.strokeStyle !== null)
  4022. {
  4023. context.lineWidth = this.lineWidth;
  4024. context.strokeStyle = this.strokeStyle.get(context);
  4025. RoundedBox.roundRect(context, this.box.min.x, this.box.min.y, width, height, this.radius);
  4026. context.stroke();
  4027. }
  4028. };
  4029. RoundedBox.prototype.serialize = function(recursive)
  4030. {
  4031. var data = Box.prototype.serialize.call(this, recursive);
  4032. data.radius = this.radius;
  4033. return data;
  4034. };
  4035. RoundedBox.prototype.parse = function(data, root)
  4036. {
  4037. Box.prototype.parse.call(this, data, root);
  4038. this.radius = data.radius;
  4039. };
  4040. /**
  4041. * Graph object is used to plot numeric graph data into the canvas.
  4042. *
  4043. * Graph data is composed of Y values that are interpolated across the X axis.
  4044. *
  4045. * @class
  4046. * @extends {Object2D}
  4047. */
  4048. function Graph()
  4049. {
  4050. Object2D.call(this);
  4051. /**
  4052. * Graph object containing the size of the object.
  4053. *
  4054. * @type {Box2}
  4055. */
  4056. this.box = new Box2(new Vector2(-50, -35), new Vector2(50, 35));
  4057. /**
  4058. * Color of the box border line.
  4059. *
  4060. * @type {ColorStyle}
  4061. */
  4062. this.strokeStyle = new ColorStyle("rgb(0, 153, 255)");
  4063. /**
  4064. * Line width used to stroke the graph data.
  4065. *
  4066. * @type {number}
  4067. */
  4068. this.lineWidth = 1.0;
  4069. /**
  4070. * Background color of the box.
  4071. *
  4072. * @type {ColorStyle}
  4073. */
  4074. this.fillStyle = new ColorStyle("rgba(0, 153, 255, 0.3)");
  4075. /**
  4076. * Minimum value of the graph.
  4077. *
  4078. * @type {number}
  4079. */
  4080. this.min = 0;
  4081. /**
  4082. * Maximum value of the graph.
  4083. *
  4084. * @type {number}
  4085. */
  4086. this.max = 10;
  4087. /**
  4088. * Data to be presented in the graph.
  4089. *
  4090. * The array should store numeric values.
  4091. *
  4092. * @type {Array<number>}
  4093. */
  4094. this.data = [];
  4095. }
  4096. Graph.prototype = Object.create(Object2D.prototype);
  4097. Graph.prototype.constructor = Graph;
  4098. Graph.prototype.type = "Graph";
  4099. Object2D.register(Graph, "Graph");
  4100. Graph.prototype.isInside = function(point)
  4101. {
  4102. return this.box.containsPoint(point);
  4103. };
  4104. Graph.prototype.draw = function(context, viewport, canvas)
  4105. {
  4106. if(this.data.length === 0)
  4107. {
  4108. return;
  4109. }
  4110. var width = this.box.max.x - this.box.min.x;
  4111. var height = this.box.max.y - this.box.min.y;
  4112. context.lineWidth = this.lineWidth;
  4113. context.beginPath();
  4114. var step = width / (this.data.length - 1);
  4115. var gamma = this.max - this.min;
  4116. context.moveTo(this.box.min.x, this.box.max.y - ((this.data[0] - this.min) / gamma) * height);
  4117. for(var i = 1, s = step; i < this.data.length; s += step, i++)
  4118. {
  4119. context.lineTo(this.box.min.x + s, this.box.max.y - ((this.data[i] - this.min) / gamma) * height);
  4120. }
  4121. if(this.strokeStyle !== null)
  4122. {
  4123. context.strokeStyle = this.strokeStyle.get(context);
  4124. context.stroke();
  4125. }
  4126. if(this.fillStyle !== null)
  4127. {
  4128. context.fillStyle = this.fillStyle.get(context);
  4129. context.lineTo(this.box.max.x, this.box.max.y);
  4130. context.lineTo(this.box.min.x, this.box.max.y);
  4131. context.fill();
  4132. }
  4133. };
  4134. Graph.prototype.serialize = function(recursive)
  4135. {
  4136. var data = Object2D.prototype.serialize.call(this, recursive);
  4137. data.box = this.box.toArray();
  4138. data.strokeStyle = this.strokeStyle !== null ? this.strokeStyle.serialize() : null;
  4139. data.lineWidth = this.lineWidth;
  4140. data.fillStyle = this.fillStyle !== null ? this.fillStyle.serialize() : null;
  4141. data.min = this.min;
  4142. data.max = this.max;
  4143. data.data = this.data;
  4144. return data;
  4145. };
  4146. Graph.prototype.parse = function(data, root)
  4147. {
  4148. Object2D.prototype.parse.call(this, data, root);
  4149. this.box.fromArray(data.box);
  4150. this.strokeStyle = data.strokeStyle !== null ? Style.parse(data.strokeStyle) : null;
  4151. this.lineWidth = data.lineWidth;
  4152. this.fillStyle = data.fillStyle !== null ? Style.parse(data.fillStyle) : null;
  4153. this.min = data.min;
  4154. this.max = data.max;
  4155. this.data = data.data;
  4156. };
  4157. /**
  4158. * Scatter graph can be used to draw numeric data as points.
  4159. *
  4160. * @class
  4161. * @extends {Object2D}
  4162. */
  4163. function ScatterGraph()
  4164. {
  4165. Graph.call(this);
  4166. /**
  4167. * Radius of each point represented in the scatter plot.
  4168. *
  4169. * @type {number}
  4170. */
  4171. this.radius = 5.0;
  4172. /**
  4173. * Draw lines betwen the points of the scatter graph.
  4174. *
  4175. * @type {boolean}
  4176. */
  4177. this.drawLine = false;
  4178. }
  4179. ScatterGraph.prototype = Object.create(Graph.prototype);
  4180. ScatterGraph.prototype.constructor = ScatterGraph;
  4181. ScatterGraph.prototype.type = "BarGraph";
  4182. Object2D.register(ScatterGraph, "BarGraph");
  4183. ScatterGraph.prototype.draw = function(context, viewport, canvas)
  4184. {
  4185. if(this.data.length === 0)
  4186. {
  4187. return;
  4188. }
  4189. var width = this.box.max.x - this.box.min.x;
  4190. var height = this.box.max.y - this.box.min.y;
  4191. var step = width / (this.data.length - 1);
  4192. var gamma = this.max - this.min;
  4193. context.lineWidth = this.lineWidth;
  4194. // Draw line
  4195. if(this.drawLine)
  4196. {
  4197. context.beginPath();
  4198. context.moveTo(this.box.min.x, this.box.max.y - ((this.data[0] - this.min) / gamma) * height);
  4199. for(var i = 1, s = step; i < this.data.length; s += step, i++)
  4200. {
  4201. context.lineTo(this.box.min.x + s, this.box.max.y - ((this.data[i] - this.min) / gamma) * height);
  4202. }
  4203. if(this.strokeStyle !== null)
  4204. {
  4205. context.strokeStyle = this.strokeStyle.get(context);
  4206. context.stroke();
  4207. }
  4208. }
  4209. // Draw circles
  4210. context.beginPath();
  4211. for(var i = 0, s = 0; i < this.data.length; s += step, i++)
  4212. {
  4213. var y = this.box.max.y - ((this.data[i] - this.min) / gamma) * height;
  4214. context.moveTo(this.box.min.x + s + this.radius, y);
  4215. context.arc(this.box.min.x + s, y, this.radius, 0, Math.PI * 2, true);
  4216. }
  4217. if(this.strokeStyle !== null)
  4218. {
  4219. context.strokeStyle = this.strokeStyle.get(context);
  4220. context.stroke();
  4221. }
  4222. if(this.fillStyle !== null)
  4223. {
  4224. context.fillStyle = this.fillStyle.get(context);
  4225. context.fill();
  4226. }
  4227. };
  4228. ScatterGraph.prototype.serialize = function(recursive)
  4229. {
  4230. var data = Graph.prototype.serialize.call(this, recursive);
  4231. data.radius = this.radius;
  4232. return data;
  4233. };
  4234. ScatterGraph.prototype.parse = function(data, root)
  4235. {
  4236. Graph.prototype.parse.call(this, data, root);
  4237. this.radius = data.radius;
  4238. };
  4239. /**
  4240. * Bar graph can be used to plot bar data into the canvas.
  4241. *
  4242. * @class
  4243. * @extends {Object2D}
  4244. */
  4245. function BarGraph()
  4246. {
  4247. Graph.call(this);
  4248. /**
  4249. * Width of each bar in the graph.
  4250. *
  4251. * If set null is automatically calculated from the graph size and number of points.
  4252. *
  4253. * @type {number}
  4254. */
  4255. this.barWidth = null;
  4256. }
  4257. BarGraph.prototype = Object.create(Graph.prototype);
  4258. BarGraph.prototype.constructor = BarGraph;
  4259. BarGraph.prototype.type = "BarGraph";
  4260. Object2D.register(BarGraph, "BarGraph");
  4261. BarGraph.prototype.draw = function(context, viewport, canvas)
  4262. {
  4263. if(this.data.length === 0)
  4264. {
  4265. return;
  4266. }
  4267. var width = this.box.max.x - this.box.min.x;
  4268. var height = this.box.max.y - this.box.min.y;
  4269. var step = width / (this.data.length - 1);
  4270. var gamma = this.max - this.min;
  4271. context.lineWidth = this.lineWidth;
  4272. context.beginPath();
  4273. var barWidth = this.barWidth !== null ? this.barWidth : width / this.data.length;
  4274. var barHalfWidth = barWidth / 2.0;
  4275. for(var i = 0, s = 0; i < this.data.length; s += step, i++)
  4276. {
  4277. var y = this.box.max.y - ((this.data[i] - this.min) / gamma) * height;
  4278. context.moveTo(this.box.min.x + s - barHalfWidth, y);
  4279. context.rect(this.box.min.x + s - barHalfWidth, y, barWidth, this.box.max.y - y);
  4280. }
  4281. if(this.strokeStyle !== null)
  4282. {
  4283. context.strokeStyle = this.strokeStyle.get(context);
  4284. context.stroke();
  4285. }
  4286. if(this.fillStyle !== null)
  4287. {
  4288. context.fillStyle = this.fillStyle.get(context);
  4289. context.fill();
  4290. }
  4291. };
  4292. BarGraph.prototype.serialize = function(recursive)
  4293. {
  4294. var data = Graph.prototype.serialize.call(this, recursive);
  4295. return data;
  4296. };
  4297. BarGraph.prototype.parse = function(data, root)
  4298. {
  4299. Graph.prototype.parse.call(this, data, root);
  4300. };
  4301. /**
  4302. * Gradient color stop is used to create the gradients by their color sections.
  4303. *
  4304. * The gradients are ordered, each stop has a target color that becomes solid on its offset value triggering the next color stop if there is one.
  4305. *
  4306. * @class
  4307. * @param offset Offset of the color stop between 0 and 1 inclusive.
  4308. * @param color CSS color value.
  4309. * @constructor
  4310. */
  4311. function GradientColorStop(offset, color)
  4312. {
  4313. /**
  4314. * Offset of the color stop between 0 and 1 inclusive.
  4315. *
  4316. * @type {number}
  4317. */
  4318. this.offset = offset;
  4319. /**
  4320. * CSS color value.
  4321. *
  4322. * @type {string}
  4323. */
  4324. this.color = color;
  4325. }
  4326. /**
  4327. * Gradient style is used to represent any type of gradient based style.
  4328. *
  4329. * It handles any gradient based operations and should be used as base for other gradient styles.
  4330. *
  4331. * @class
  4332. * @extends {Style}
  4333. */
  4334. function GradientStyle()
  4335. {
  4336. Style$1.call(this);
  4337. /**
  4338. * List of colors that compose this gradient ordered.
  4339. *
  4340. * You need to add at least one color stop to have a visible gradient.
  4341. *
  4342. * @type {GradientColorStop[]}
  4343. */
  4344. this.colors = [];
  4345. }
  4346. GradientStyle.prototype = Object.create(Style$1.prototype);
  4347. /**
  4348. * Add a new color stop defined by an offset and a color to the gradient.
  4349. *
  4350. * If the offset is not between 0 and 1 inclusive, or if color can't be parsed as a CSS color, an error is raised.
  4351. *
  4352. * @param {number} offset Offset of the color stop between 0 and 1 inclusive.
  4353. * @param {string} color CSS color value.
  4354. */
  4355. GradientStyle.prototype.addColorStop = function(offset, color)
  4356. {
  4357. this.colors.push(new GradientColorStop(offset, color));
  4358. };
  4359. GradientStyle.prototype.serialize = function()
  4360. {
  4361. return {
  4362. colors: this.colors
  4363. };
  4364. };
  4365. GradientStyle.prototype.parse = function(data)
  4366. {
  4367. var colors = [];
  4368. for(var i = 0; i < data.colors.length; i++)
  4369. {
  4370. colors.push(new GradientColorStop(data.colors[i].offset, data.colors[i].color));
  4371. }
  4372. this.colors = colors;
  4373. };
  4374. /**
  4375. * Linear gradient style, represents a gradient of colors from a point to another interpolating in between.
  4376. *
  4377. * Behind the of the two points used the color is solid.
  4378. *
  4379. * The get method returns a CanvasGradient https://developer.mozilla.org/en-US/docs/Web/API/CanvasGradient when generated.
  4380. *
  4381. * @class
  4382. * @extends {GradientStyle}
  4383. */
  4384. function LinearGradientStyle()
  4385. {
  4386. GradientStyle.call(this);
  4387. /**
  4388. * The coordinates of the starting point of the gradient.
  4389. *
  4390. * @type {Vector2}
  4391. */
  4392. this.start = new Vector2(-100, 0);
  4393. /**
  4394. * The coordinates of the ending point of the gradient.
  4395. *
  4396. * @type {Vector2}
  4397. */
  4398. this.end = new Vector2(100, 0);
  4399. }
  4400. LinearGradientStyle.prototype = Object.create(GradientStyle.prototype);
  4401. Style$1.register(LinearGradientStyle, "LinearGradient");
  4402. LinearGradientStyle.prototype.get = function(context)
  4403. {
  4404. var style = context.createLinearGradient(this.start.x, this.start.y, this.end.x, this.end.y);
  4405. for(var i = 0; i < this.colors.length; i++)
  4406. {
  4407. style.addColorStop(this.colors[i].offset, this.colors[i].color);
  4408. }
  4409. return style;
  4410. };
  4411. LinearGradientStyle.prototype.serialize = function ()
  4412. {
  4413. var data = GradientStyle.prototype.serialize.call(this);
  4414. Object.assign(data, {
  4415. type: "LinearGradient",
  4416. start: this.start.toArray(),
  4417. end: this.end.toArray()
  4418. });
  4419. return data;
  4420. };
  4421. LinearGradientStyle.prototype.parse = function (data)
  4422. {
  4423. GradientStyle.prototype.parse.call(this, data);
  4424. this.start.fromArray(data.start);
  4425. this.end.fromArray(data.end);
  4426. };
  4427. /**
  4428. * Gauge object is used to draw gauge like graphic.
  4429. *
  4430. * It has a defined range, start angle, end angle and style controls.
  4431. *
  4432. * @class
  4433. * @extends {Object2D}
  4434. */
  4435. function Gauge()
  4436. {
  4437. Object2D.call(this);
  4438. /**
  4439. * Value displayed by this gauge. It is displayed based on min and max values.
  4440. *
  4441. * @type {number}
  4442. */
  4443. this.value = 50;
  4444. /**
  4445. * Minimum value of the gauge. Necessary to display the value correctly to scale.
  4446. *
  4447. * @type {number}
  4448. */
  4449. this.min = 0;
  4450. /**
  4451. * Maximum value of the gauge. Necessary to display the value correctly to scale.
  4452. *
  4453. * @type {number}
  4454. */
  4455. this.max = 100;
  4456. /**
  4457. * Radius of the gauge object.
  4458. *
  4459. * @type {number}
  4460. */
  4461. this.radius = 80;
  4462. /**
  4463. * The line width of the gauge semi-circle.
  4464. *
  4465. * @type {number}
  4466. */
  4467. this.lineWidth = 10;
  4468. /**
  4469. * Start angle of the gauge.
  4470. *
  4471. * @type {number}
  4472. */
  4473. this.startAngle = Math.PI;
  4474. /**
  4475. * End angle of the gauge.
  4476. *
  4477. * @type {number}
  4478. */
  4479. this.endAngle = 2 * Math.PI;
  4480. /**
  4481. * If true draw a circular dial at the end of the gauge bar.
  4482. *
  4483. * @type {boolean}
  4484. */
  4485. this.dial = false;
  4486. /**
  4487. * Style of the base of the gauge object, (the background of the gauge bar).
  4488. *
  4489. * @type {Style}
  4490. */
  4491. this.baseStyle = new ColorStyle("#e9ecf1");
  4492. /**
  4493. * Style of the gauge bar.
  4494. *
  4495. * @type {Style}
  4496. */
  4497. this.barStyle = new LinearGradientStyle();
  4498. this.barStyle.start.set(-100, 0);
  4499. this.barStyle.end.set(100, 0);
  4500. this.barStyle.addColorStop(0, "#e5ff50");
  4501. this.barStyle.addColorStop(0.5, "#50ff67");
  4502. this.barStyle.addColorStop(1, "#32adff");
  4503. }
  4504. Gauge.prototype = Object.create(Object2D.prototype);
  4505. Gauge.prototype.constructor = Gauge;
  4506. Gauge.prototype.type = "Gauge";
  4507. Object2D.register(Gauge, "Gauge");
  4508. Gauge.prototype.isInside = function(point)
  4509. {
  4510. return point.length() <= this.radius;
  4511. };
  4512. Gauge.prototype.draw = function(context, viewport, canvas)
  4513. {
  4514. var percentage = this.value / (this.max - this.min);
  4515. var range = [this.startAngle, this.endAngle];
  4516. var diff = range[1] - range[0];
  4517. var angle = range[0] + diff * percentage;
  4518. var center = [0, 0];
  4519. //Back
  4520. context.lineWidth = this.lineWidth;
  4521. context.lineCap = "round";
  4522. context.strokeStyle = this.baseStyle.get(context);
  4523. context.beginPath();
  4524. context.arc(center[0], center[1], this.radius, range[0], range[1]);
  4525. context.stroke();
  4526. // Fill gradient
  4527. var gradient = context.createLinearGradient(-this.radius, 0, this.radius, 0);
  4528. context.strokeStyle = this.barStyle.get(context);
  4529. context.lineWidth = this.lineWidth;
  4530. context.beginPath();
  4531. context.arc(center[0], center[1], this.radius, range[0], angle);
  4532. context.stroke();
  4533. if(this.dial)
  4534. {
  4535. var dialAngle = (this.startAngle - this.endAngle) * percentage;
  4536. var dialCenter = [Math.cos(dialAngle) * this.radius, Math.sin(dialAngle) * this.radius];
  4537. dialCenter[0] = dialCenter[0] - center[0];
  4538. dialCenter[1] = dialCenter[1] - center[1];
  4539. context.fillStyle = "#FFFFFF";
  4540. context.beginPath();
  4541. context.arc(dialCenter[0], dialCenter[1], this.lineWidth / 2, 0, 2 * Math.PI);
  4542. context.fill();
  4543. context.fillStyle = gradient;
  4544. context.beginPath();
  4545. context.arc(dialCenter[0], dialCenter[1], this.lineWidth / 3, 0, 2 * Math.PI);
  4546. context.fill();
  4547. }
  4548. };
  4549. Gauge.prototype.serialize = function(recursive)
  4550. {
  4551. var data = Object2D.prototype.serialize.call(this, recursive);
  4552. data.value = this.value;
  4553. data.min = this.min;
  4554. data.max = this.max;
  4555. data.radius = this.radius;
  4556. data.lineWidth = this.lineWidth;
  4557. data.startAngle = this.startAngle;
  4558. data.endAngle = this.endAngle;
  4559. data.dial = this.dial;
  4560. data.baseStyle = this.baseStyle !== null ? this.baseStyle.serialize() : null;
  4561. data.barStyle = this.barStyle !== null ? this.barStyle.serialize() : null;
  4562. return data;
  4563. };
  4564. Gauge.prototype.parse = function(data, root)
  4565. {
  4566. Object2D.prototype.parse.call(this, data, root);
  4567. this.value = data.value;
  4568. this.min = data.min;
  4569. this.max = data.max;
  4570. this.radius = data.radius;
  4571. this.lineWidth = data.lineWidth;
  4572. this.startAngle = data.startAngle;
  4573. this.endAngle = data.endAngle;
  4574. this.dial = data.dial;
  4575. this.baseStyle = data.baseStyle !== null ? Style.parse(data.baseStyle) : null;
  4576. this.barStyle = data.barStyle !== null ? Style.parse(data.barStyle) : null;
  4577. };
  4578. /**
  4579. * Pie chart represents a set of data in a pie like chart graph.
  4580. *
  4581. * The values are drawn in porportion relative to their sum.
  4582. *
  4583. * @class
  4584. * @extends {Object2D}
  4585. */
  4586. function PieChart(data)
  4587. {
  4588. Object2D.call(this);
  4589. /**
  4590. * Data to be displayed on the pie chart. Each element should store a value and a stroke/fill styles.
  4591. *
  4592. * Each element should use the following structure {value: 0.0, fillStyle: ..., strokestyle: ...}.
  4593. *
  4594. * @type {Array<{value: number, fillStyle: ColorStyle, strokeStyle: ColorStyle}>}
  4595. */
  4596. this.data = data !== undefined ? data : [];
  4597. /**
  4598. * Variable pie slice size based on their value compared to the biggest value.
  4599. *
  4600. * @type {boolean}
  4601. */
  4602. this.sliceSize = false;
  4603. /**
  4604. * Radius of the pie chart object.
  4605. *
  4606. * @type {number}
  4607. */
  4608. this.radius = 50;
  4609. /**
  4610. * The line width of each pie chart section.
  4611. *
  4612. * @type {number}
  4613. */
  4614. this.lineWidth = 1.0;
  4615. /**
  4616. * Start angle of the pie chart.
  4617. *
  4618. * @type {number}
  4619. */
  4620. this.startAngle = 0;
  4621. /**
  4622. * End angle of the pie chart.
  4623. *
  4624. * @type {number}
  4625. */
  4626. this.endAngle = 2 * Math.PI;
  4627. }
  4628. PieChart.prototype = Object.create(Object2D.prototype);
  4629. PieChart.prototype.constructor = PieChart;
  4630. PieChart.prototype.type = "PieChart";
  4631. Object2D.register(PieChart, "PieChart");
  4632. PieChart.prototype.isInside = function(point)
  4633. {
  4634. return point.length() <= this.radius;
  4635. };
  4636. PieChart.prototype.draw = function(context)
  4637. {
  4638. if(this.data.length === 0)
  4639. {
  4640. return;
  4641. }
  4642. var sum = 0;
  4643. var max = this.data[0].value;
  4644. for(var i = 0; i < this.data.length; i++)
  4645. {
  4646. sum += this.data[i].value;
  4647. if(this.data[i].value > max)
  4648. {
  4649. max = this.data[i].value;
  4650. }
  4651. }
  4652. context.lineWidth = this.lineWidth;
  4653. var angleRange = this.endAngle - this.startAngle;
  4654. var angle = this.startAngle;
  4655. // Fill
  4656. for(var i = 0; i < this.data.length; i++)
  4657. {
  4658. var section = angleRange * (this.data[i].value / sum);
  4659. if(this.data[i].fillStyle)
  4660. {
  4661. context.beginPath();
  4662. context.moveTo(0, 0);
  4663. var radius = this.sliceSize ? ((this.data[i].value / max) * this.radius) : this.radius;
  4664. context.arc(0, 0, radius, angle, angle + section);
  4665. context.moveTo(0, 0);
  4666. context.fillStyle = this.data[i].fillStyle.get(context);
  4667. context.fill();
  4668. }
  4669. angle += section;
  4670. }
  4671. // Stroke
  4672. for(var i = 0; i < this.data.length; i++)
  4673. {
  4674. var section = angleRange * (this.data[i].value / sum);
  4675. if(this.data[i].strokeStyle)
  4676. {
  4677. context.beginPath();
  4678. context.moveTo(0, 0);
  4679. var radius = this.sliceSize ? ((this.data[i].value / max) * this.radius) : this.radius;
  4680. context.arc(0, 0, radius, angle, angle + section);
  4681. context.moveTo(0, 0);
  4682. context.strokeStyle = this.data[i].strokeStyle.get(context);
  4683. context.stroke();
  4684. }
  4685. angle += section;
  4686. }
  4687. };
  4688. PieChart.prototype.serialize = function(recursive)
  4689. {
  4690. var data = Object2D.prototype.serialize.call(this, recursive);
  4691. data.radius = this.radius;
  4692. data.lineWidth = this.lineWidth;
  4693. data.startAngle = this.startAngle;
  4694. data.endAngle = this.endAngle;
  4695. data.sliceSize = this.sliceSize;
  4696. return data;
  4697. };
  4698. PieChart.prototype.parse = function(data, root)
  4699. {
  4700. Object2D.prototype.parse.call(this, data, root);
  4701. this.radius = data.radius;
  4702. this.lineWidth = data.lineWidth;
  4703. this.startAngle = data.startAngle;
  4704. this.endAngle = data.endAngle;
  4705. this.sliceSize = data.sliceSize;
  4706. };
  4707. /**
  4708. * Path object can be used to draw paths build from commands into the canvas.
  4709. *
  4710. * These paths can be also obtained from SVG files as a SVG command list.
  4711. *
  4712. * @class
  4713. * @extends {Object2D}
  4714. */
  4715. function Path(path)
  4716. {
  4717. Object2D.call(this);
  4718. /**
  4719. * Path2D object containing the commands to draw the shape into the canvas.
  4720. *
  4721. * Check https://developer.mozilla.org/en-US/docs/Web/API/Path2D/Path2D for more details.
  4722. *
  4723. * @type {Path2D}
  4724. */
  4725. this.path = path !== undefined ? path : new Path2D("M10 10 h 80 v 80 h -80 Z");
  4726. /**
  4727. * Style of the object border line.
  4728. *
  4729. * If set null it is ignored.
  4730. *
  4731. * @type {Style}
  4732. */
  4733. this.strokeStyle = new ColorStyle("#000000");
  4734. /**
  4735. * Line width, only used if a valid strokeStyle is defined.
  4736. *
  4737. * @type {number}
  4738. */
  4739. this.lineWidth = 1;
  4740. /**
  4741. * Background color of the path.
  4742. *
  4743. * If set null it is ignored.
  4744. *
  4745. * @param {Style}
  4746. */
  4747. this.fillStyle = new ColorStyle("#FFFFFF");
  4748. }
  4749. Path.prototype = Object.create(Object2D.prototype);
  4750. Path.prototype.constructor = Path;
  4751. Path.prototype.type = "Path";
  4752. Object2D.register(Path, "Path");
  4753. Path.prototype.draw = function(context)
  4754. {
  4755. if(this.fillStyle !== null)
  4756. {
  4757. context.fillStyle = this.fillStyle.get(context);
  4758. context.fill(this.path);
  4759. }
  4760. if(this.strokeStyle !== null)
  4761. {
  4762. context.lineWidth = this.lineWidth;
  4763. context.strokeStyle = this.strokeStyle.get(context);
  4764. context.stroke(this.path);
  4765. }
  4766. };
  4767. Path.prototype.serialize = function(recursive)
  4768. {
  4769. var data = Object2D.prototype.serialize.call(this, recursive);
  4770. data.strokeStyle = this.strokeStyle !== null ? this.strokeStyle.serialize() : null;
  4771. data.lineWidth = this.lineWidth;
  4772. data.fillStyle = this.fillStyle !== null ? this.fillStyle.serialize() : null;
  4773. return data;
  4774. };
  4775. Path.prototype.parse = function(data, root)
  4776. {
  4777. Object2D.prototype.parse.call(this, data, root);
  4778. this.strokeStyle = data.strokeStyle !== null ? Style$1.parse(data.strokeStyle) : null;
  4779. this.lineWidth = data.lineWidth;
  4780. this.fillStyle = data.fillStyle !== null ? Style$1.parse(data.fillStyle) : null;
  4781. };
  4782. /**
  4783. * Pattern style represents an opaque object describing a pattern, based on an image, a canvas, or a video.
  4784. *
  4785. * The get method returns a CanvasPattern object https://developer.mozilla.org/en-US/docs/Web/API/CanvasPattern created by the context.createPattern() method.
  4786. *
  4787. * @class
  4788. * @extends {Style}
  4789. * @param {CanvasImageSource} source Source element of the pattern.
  4790. */
  4791. function PatternStyle(source)
  4792. {
  4793. Style$1.call(this);
  4794. /**
  4795. * Source of the pattern style. Can be a image, video or another canvas element
  4796. *
  4797. * By default a empty image element is created.
  4798. *
  4799. * @type {CanvasImageSource}
  4800. */
  4801. this.source = source || document.createElement("img");
  4802. /**
  4803. * Repetition indicates how the pattern image should be repeated.
  4804. *
  4805. * Possible values are "repeat", "repeat-x", "repeat-y" or "no-repeat".
  4806. *
  4807. * More information about this attribute here https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/createPattern.
  4808. *
  4809. * @type {string}
  4810. */
  4811. this.repetition = "repeat";
  4812. /**
  4813. * Transformation matrix applied to the pattern.
  4814. *
  4815. * The transformation allows to move, rotate and scale the pattern freely
  4816. *
  4817. * @type {Matrix}
  4818. */
  4819. this.matrix = new Matrix();
  4820. }
  4821. PatternStyle.prototype = Object.create(Style$1.prototype);
  4822. Style$1.register(PatternStyle, "Pattern");
  4823. /**
  4824. * Applies an 2x3 transformation matrix representing a linear transform to the pattern.
  4825. *
  4826. * @param {number[]} transform 2x3 Transformation matrix.
  4827. */
  4828. PatternStyle.prototype.setTransform = function(transform)
  4829. {
  4830. this.matrix.m = transform;
  4831. this.needsUpdate = true;
  4832. };
  4833. PatternStyle.prototype.get = function(context)
  4834. {
  4835. if(this.needsUpdate || this.cache === null)
  4836. {
  4837. this.cache = context.createPattern(this.source, this.repetition);
  4838. this.cache.setTransform(this.matrix.cssTransform());
  4839. this.needsUpdate = false;
  4840. }
  4841. return this.cache;
  4842. };
  4843. PatternStyle.prototype.serialize = function ()
  4844. {
  4845. var data = GradientStyle.prototype.serialize.call(this);
  4846. Object.assign(data, {
  4847. type: "Pattern",
  4848. matrix: this.matrix.m,
  4849. repetition: this.repetition,
  4850. source: this.source
  4851. });
  4852. return data;
  4853. };
  4854. PatternStyle.prototype.parse = function (data)
  4855. {
  4856. GradientStyle.prototype.parse.call(this, data);
  4857. this.matrix = new Matrix(data.matrix);
  4858. this.repetition = data.repetition;
  4859. this.source = data.source;
  4860. };
  4861. /**
  4862. * Radial gradient interpolates colors from a point to another point around up to a starting and finishing radius value.
  4863. *
  4864. * If the start and end point are the same it interpolates around the starting and ending radius forming a circle. Outside of the radius the color is solid.
  4865. *
  4866. * The get method returns a CanvasGradient https://developer.mozilla.org/en-US/docs/Web/API/CanvasGradient when generated.
  4867. *
  4868. * @class
  4869. * @extends {GradientStyle}
  4870. */
  4871. function RadialGradientStyle()
  4872. {
  4873. GradientStyle.call(this);
  4874. /**
  4875. * The coordinates of the starting circle of the gradient.
  4876. *
  4877. * @type {Vector2}
  4878. */
  4879. this.start = new Vector2(0, 0);
  4880. /**
  4881. * The radius of the starting circle.
  4882. *
  4883. * @type {number}
  4884. */
  4885. this.startRadius = 10;
  4886. /**
  4887. * The coordinates of the ending circle of the gradient.
  4888. *
  4889. * @type {Vector2}
  4890. */
  4891. this.end = new Vector2(0, 0);
  4892. /**
  4893. * The radius of the ending circle.
  4894. *
  4895. * @type {number}
  4896. */
  4897. this.endRadius = 50;
  4898. }
  4899. RadialGradientStyle.prototype = Object.create(GradientStyle.prototype);
  4900. Style$1.register(RadialGradientStyle, "RadialGradient");
  4901. RadialGradientStyle.prototype.get = function(context)
  4902. {
  4903. var style = context.createRadialGradient(this.start.x, this.start.y, this.startRadius, this.end.x, this.end.y, this.endRadius);
  4904. for(var i = 0; i < this.colors.length; i++)
  4905. {
  4906. style.addColorStop(this.colors[i].offset, this.colors[i].color);
  4907. }
  4908. return style;
  4909. };
  4910. RadialGradientStyle.prototype.serialize = function ()
  4911. {
  4912. var data = GradientStyle.prototype.serialize.call(this);
  4913. Object.assign(data, {
  4914. type: "RadialGradient",
  4915. start: this.start.toArray(),
  4916. end: this.end.toArray(),
  4917. startRadius: this.startRadius,
  4918. endRadius: this.endRadius
  4919. });
  4920. return data;
  4921. };
  4922. RadialGradientStyle.prototype.parse = function (data)
  4923. {
  4924. GradientStyle.prototype.parse.call(this, data);
  4925. this.start.fromArray(data.start);
  4926. this.end.fromArray(data.end);
  4927. this.startRadius = data.startRadius;
  4928. this.endRadius = data.endRadius;
  4929. };
  4930. /**
  4931. * Node graph object should be used as a container for node elements.
  4932. *
  4933. * The node graph object specifies how the nodes are processed, each individual node can store and process data, the node graph specified how this information is processed.
  4934. *
  4935. * All node elements are stored as children of the node graph.
  4936. *
  4937. * @class
  4938. * @extends {Object2D}
  4939. */
  4940. function NodeGraph()
  4941. {
  4942. Object2D.call(this);
  4943. }
  4944. NodeGraph.prototype = Object.create(Object2D.prototype);
  4945. NodeGraph.prototype.constructor = NodeGraph;
  4946. NodeGraph.prototype.type = "NodeGraph";
  4947. Object2D.register(NodeGraph, "NodeGraph");
  4948. /**
  4949. * Create and add a new node of specific node type to the graph.
  4950. *
  4951. * Automatically finds an empty space as close as possible to other nodes to add this new node.
  4952. *
  4953. * @param {Node} node Node object to be added.
  4954. * @return {Node} Node created (already added to the graph).
  4955. */
  4956. NodeGraph.prototype.addNode = function(node)
  4957. {
  4958. // Check available position on screen.
  4959. var x = 0, y = 0;
  4960. for(var i = 0; i < this.children.length; i++)
  4961. {
  4962. if(this.children[i].position.x > x)
  4963. {
  4964. x = this.children[i].position.x;
  4965. }
  4966. if(this.children[i].position.y > y)
  4967. {
  4968. y = this.children[i].position.y;
  4969. }
  4970. }
  4971. // Create and add new node
  4972. node.position.set(x + 200, y / 2.0);
  4973. this.add(node);
  4974. if(node.registerSockets !== null)
  4975. {
  4976. node.registerSockets();
  4977. }
  4978. return node;
  4979. };
  4980. /**
  4981. * Node connector is used to connect a output of a node to a input of another node.
  4982. *
  4983. * Some nodes outputs might support multiple connections having an output connected to multiple inputs.
  4984. *
  4985. * Data always goes from the output node to a input node.
  4986. *
  4987. * @class
  4988. * @extends {BezierCurve}
  4989. */
  4990. function NodeConnector()
  4991. {
  4992. BezierCurve.call(this);
  4993. /**
  4994. * Width of the connector line.
  4995. *
  4996. * @type {number}
  4997. */
  4998. this.lineWidth = 2;
  4999. /**
  5000. * Origin output socket that is attached to a node.
  5001. *
  5002. * @type {NodeSocket}
  5003. */
  5004. this.outputSocket = null;
  5005. /**
  5006. * Destination input socket that is attached to a node.
  5007. *
  5008. * @type {NodeSocket}
  5009. */
  5010. this.inputSocket = null;
  5011. }
  5012. NodeConnector.prototype = Object.create(BezierCurve.prototype);
  5013. NodeConnector.prototype.constructor = NodeConnector;
  5014. NodeConnector.prototype.type = "NodeConnector";
  5015. Object2D.register(NodeConnector, "NodeConnector");
  5016. NodeConnector.prototype.destroy = function()
  5017. {
  5018. BezierCurve.prototype.destroy.call(this);
  5019. if(this.outputSocket !== null)
  5020. {
  5021. this.outputSocket.removeConnector(this);
  5022. this.outputSocket = null;
  5023. }
  5024. if(this.inputSocket !== null)
  5025. {
  5026. this.inputSocket.removeConnector(this);
  5027. this.inputSocket = null;
  5028. }
  5029. };
  5030. NodeConnector.prototype.onUpdate = function()
  5031. {
  5032. if(this.outputSocket !== null)
  5033. {
  5034. this.from.copy(this.outputSocket.position);
  5035. }
  5036. if(this.inputSocket !== null)
  5037. {
  5038. this.to.copy(this.inputSocket.position);
  5039. }
  5040. // Center control points
  5041. this.fromCp.copy(this.from);
  5042. this.fromCp.add(this.to);
  5043. this.fromCp.multiplyScalar(0.5);
  5044. this.toCp.copy(this.fromCp);
  5045. var curvature = 0.5;
  5046. // Check vertical/horizontal distances
  5047. var yDistance = this.to.y - this.from.y;
  5048. var xDistance = this.to.x - this.from.x;
  5049. // Apply a offset to the control points
  5050. if(Math.abs(xDistance) > Math.abs(yDistance))
  5051. {
  5052. this.toCp.x += xDistance * curvature;
  5053. this.fromCp.x -= xDistance * curvature;
  5054. }
  5055. else
  5056. {
  5057. this.toCp.y += yDistance * curvature;
  5058. this.fromCp.y -= yDistance * curvature;
  5059. }
  5060. };
  5061. NodeConnector.prototype.serialize = function(recursive)
  5062. {
  5063. var data = BezierCurve.prototype.serialize.call(this, recursive);
  5064. data.outputSocket = this.outputSocket !== null ? this.outputSocket.uuid : null;
  5065. data.inputSocket = this.inputSocket !== null ? this.inputSocket.uuid : null;
  5066. return data;
  5067. };
  5068. NodeConnector.prototype.parse = function(data, root)
  5069. {
  5070. BezierCurve.prototype.parse.call(this, data, root);
  5071. if(data.outputSocket !== null)
  5072. {
  5073. this.outputSocket = root.getChildByUUID(data.outputSocket);
  5074. }
  5075. if(data.inputSocket !== null)
  5076. {
  5077. this.inputSocket = root.getChildByUUID(data.inputSocket);
  5078. }
  5079. };
  5080. /**
  5081. * Represents a node hook point. Is attached to the node element and represented visually.
  5082. *
  5083. * Can be used as a node input, output or as a bidirectional connection.
  5084. *
  5085. * @class
  5086. * @extends {Circle}
  5087. * @param {Node} node Node of this hook.
  5088. * @param {number} direction Direction of the hook.
  5089. * @param {string} category Data category of the node socket.
  5090. * @param {string} name Name of the node socket.
  5091. */
  5092. function NodeSocket(node, direction, category, name)
  5093. {
  5094. Circle.call(this);
  5095. this.draggable = true;
  5096. this.radius = 6;
  5097. this.layer = 1;
  5098. /**
  5099. * Name of the socket presented to the user.
  5100. *
  5101. * @type {string}
  5102. */
  5103. this.name = name !== undefined ? name : "";
  5104. /**
  5105. * Category of data available from this socket. Only sockets of the same category can be connected.
  5106. *
  5107. * Should directly store the data type name (e.g. "string", "number", "Object", etc).
  5108. *
  5109. * @type {string}
  5110. */
  5111. this.category = category !== undefined ? category : "";
  5112. /**
  5113. * Allow to connect a OUTPUT node to multiple INPUT sockets.
  5114. *
  5115. * A INPUT socket can only take one connection, this value is ignored for INPUT sockets.
  5116. *
  5117. * @type {boolean}
  5118. */
  5119. this.multiple = true;
  5120. /**
  5121. * Direction of the node hook, indicates the data flow of the socket.
  5122. *
  5123. * Can be INPUT or OUTPUT.
  5124. *
  5125. * @type {number}
  5126. */
  5127. this.direction = direction;
  5128. /**
  5129. * Node where this socket is attached to.
  5130. *
  5131. * Should be used to get data from node GUI and from other sockets.
  5132. *
  5133. * @type {Node}
  5134. */
  5135. this.node = node;
  5136. /**
  5137. * Node connector used to connect this socket to another node socket.
  5138. *
  5139. * Can be used to access the adjacent node. If the socket allows for multiple connections this array can have multiple elements.
  5140. *
  5141. * @type {NodeConnector[]}
  5142. */
  5143. this.connectors = [];
  5144. /**
  5145. * Indicates if the user is currently creating a new connection from this node socket.
  5146. *
  5147. * @type {boolean}
  5148. */
  5149. this.creatingConnection = false;
  5150. /**
  5151. * Text object used to present the name of the socket.
  5152. *
  5153. * Depending on the socket direction the text is aligned to the left or to the right.
  5154. *
  5155. * @type {Text}
  5156. */
  5157. this.text = new Text();
  5158. this.text.text = this.name;
  5159. if(this.direction === NodeSocket.INPUT)
  5160. {
  5161. this.text.position.x -= 10;
  5162. this.text.textAlign = "right";
  5163. }
  5164. else if(this.direction === NodeSocket.OUTPUT)
  5165. {
  5166. this.text.position.x += 10;
  5167. this.text.textAlign = "left";
  5168. }
  5169. this.add(this.text);
  5170. }
  5171. NodeSocket.prototype = Object.create(Circle.prototype);
  5172. NodeSocket.prototype.constructor = NodeSocket;
  5173. NodeSocket.prototype.type = "NodeSocket";
  5174. Object2D.register(NodeSocket, "NodeSocket");
  5175. /**
  5176. * Input hook can only be connected to an output.
  5177. *
  5178. * Is used to read data from the output.
  5179. *
  5180. * @type {number}
  5181. * @constant
  5182. */
  5183. NodeSocket.INPUT = 1;
  5184. /**
  5185. * Output hook can only be connected to an input.
  5186. *
  5187. * Writes data to the output.
  5188. *
  5189. * @type {number}
  5190. * @constant
  5191. */
  5192. NodeSocket.OUTPUT = 2;
  5193. /**
  5194. * Get value stored or calculated in node socket, it should be the calculated from node logic, node inputs, user input, etc.
  5195. *
  5196. * For input nodes the value should be fetched trough the connector object that is connected to an output node elsewhere.
  5197. *
  5198. * By default it the socket is an INPUT it gets the value trough the connector if available. Inputs will recursively propagate the method trough the graph to get their value.
  5199. *
  5200. * If the socket is an OUTPUT or there is no connection the method returns null by default, in this case the method should be extended by implementations of this class to process data.
  5201. *
  5202. * @return {Object} Return data calculated from the node.
  5203. */
  5204. NodeSocket.prototype.getValue = function()
  5205. {
  5206. // If the node is an input get its value from the output socket of the connection.
  5207. if(this.direction === NodeSocket.INPUT && this.connectors.length > 0 && this.connectors[0].outputSocket !== null)
  5208. {
  5209. return this.connectors[0].outputSocket.getValue();
  5210. }
  5211. return null;
  5212. };
  5213. /**
  5214. * Connect this node socket to another socket.
  5215. *
  5216. * Sockets have to be compatible otherwise the connection cannot be made and an error will be thrown.
  5217. *
  5218. * @param {NodeSocket} socket Socket to be connected with this
  5219. * @return {NodeConnector} Node connector created.
  5220. */
  5221. NodeSocket.prototype.connectTo = function(socket)
  5222. {
  5223. if(!this.isCompatible(socket))
  5224. {
  5225. throw new Error("Sockets are not compatible they cannot be connected.");
  5226. }
  5227. var connector = new NodeConnector();
  5228. this.attachConnector(connector);
  5229. socket.attachConnector(connector);
  5230. return connector;
  5231. };
  5232. /**
  5233. * Attach a node connector to this socket. Sets the correct input/output attributes on the socket and the connector.
  5234. *
  5235. * Automatically adds the connector to the same parent and the node socket if no parent defined for the connector.
  5236. *
  5237. * @param {NodeConnector} connector Connector to be attached to this socket.
  5238. */
  5239. NodeSocket.prototype.attachConnector = function(connector)
  5240. {
  5241. // If there is no space for a new connector delete the already existing connectors.
  5242. if(!this.canAddConnector())
  5243. {
  5244. this.destroyConnectors();
  5245. }
  5246. // Attach the socket to the correct direction of the connector
  5247. if(this.direction === NodeSocket.INPUT)
  5248. {
  5249. connector.inputSocket = this;
  5250. }
  5251. else if(this.direction === NodeSocket.OUTPUT)
  5252. {
  5253. connector.outputSocket = this;
  5254. }
  5255. // Add to the list connectors
  5256. this.connectors.push(connector);
  5257. if(connector.parent === null)
  5258. {
  5259. this.parent.add(connector);
  5260. }
  5261. };
  5262. /**
  5263. * Check if this socket is compatible (type and direction) with another socket.
  5264. *
  5265. * For two sockets to be compatible the data flow should be correct (one input and a output) and they should carry the same data type.
  5266. *
  5267. * @param {NodeSocket} socket Socket to verify compatibility with.
  5268. * @return {boolean} Returns true if the two sockets are compatible.
  5269. */
  5270. NodeSocket.prototype.isCompatible = function(socket)
  5271. {
  5272. return this.direction !== socket.direction && this.category === socket.category;
  5273. };
  5274. /**
  5275. * Check if this node socket can have a new connector attached to it.
  5276. *
  5277. * Otherwise it might be necessary to destroy old connectors before adding a new connector.
  5278. *
  5279. * @return {boolean} True if its possible to add a new connector to the socket, false otherwise.
  5280. */
  5281. NodeSocket.prototype.canAddConnector = function()
  5282. {
  5283. return !(this.connectors.length > 0 && ((this.direction === NodeSocket.INPUT) || (this.direction === NodeSocket.OUTPUT && !this.multiple)));
  5284. };
  5285. /**
  5286. * Check if this socket can be connected with another socket, they have to be compatible and have space for a new connector.
  5287. *
  5288. * @param {NodeSocket} socket Socket to verify connectivity with.
  5289. * @return {boolean} Returns true if the two sockets can be connected.
  5290. */
  5291. NodeSocket.prototype.canConnect = function(socket)
  5292. {
  5293. return this.isCompatible(socket) && this.canAddConnector();
  5294. };
  5295. /**
  5296. * Destroy a connector attached to this socket, calls the destroy() method of the connection.
  5297. */
  5298. NodeSocket.prototype.removeConnector = function(connector)
  5299. {
  5300. var index = this.connectors.indexOf(connector);
  5301. if(index !== -1)
  5302. {
  5303. this.connectors.splice(index, 1);
  5304. connector.destroy();
  5305. }
  5306. };
  5307. /**
  5308. * Destroy all connectors attached to this socket.
  5309. *
  5310. * Should be called when destroying the object or to clean up the object.
  5311. */
  5312. NodeSocket.prototype.destroyConnectors = function()
  5313. {
  5314. for(var i = 0; i < this.connectors.length; i++)
  5315. {
  5316. this.connectors[i].destroy();
  5317. }
  5318. };
  5319. NodeSocket.prototype.destroy = function()
  5320. {
  5321. Circle.prototype.destroy.call(this);
  5322. this.destroyConnectors();
  5323. };
  5324. NodeSocket.prototype.onPointerDragStart = function(pointer, viewport)
  5325. {
  5326. this.creatingConnection = true;
  5327. this.attachConnector(new NodeConnector());
  5328. };
  5329. NodeSocket.prototype.onPointerDrag = function(pointer, viewport, delta, position)
  5330. {
  5331. if(this.creatingConnection)
  5332. {
  5333. if(this.direction === NodeSocket.INPUT)
  5334. {
  5335. this.connectors[this.connectors.length - 1].from.copy(position);
  5336. }
  5337. else if(this.direction === NodeSocket.OUTPUT)
  5338. {
  5339. this.connectors[this.connectors.length - 1].to.copy(position);
  5340. }
  5341. }
  5342. };
  5343. NodeSocket.prototype.onPointerDragEnd = function(pointer, viewport)
  5344. {
  5345. if(this.creatingConnection)
  5346. {
  5347. var position = viewport.inverseMatrix.transformPoint(pointer.position);
  5348. var objects = this.parent.getWorldPointIntersections(position);
  5349. var found = false;
  5350. for(var i = 0; i < objects.length; i++)
  5351. {
  5352. if(objects[i] instanceof NodeSocket)
  5353. {
  5354. if(this.isCompatible(objects[i]))
  5355. {
  5356. objects[i].attachConnector(this.connectors[this.connectors.length - 1]);
  5357. found = true;
  5358. break;
  5359. }
  5360. }
  5361. }
  5362. if(!found)
  5363. {
  5364. this.connectors[this.connectors.length - 1].destroy();
  5365. }
  5366. }
  5367. this.creatingConnection = false;
  5368. };
  5369. NodeSocket.prototype.serialize = function(recursive)
  5370. {
  5371. var data = Circle.prototype.serialize.call(this, recursive);
  5372. data.name = this.name;
  5373. data.category = this.category;
  5374. data.multiple = this.multiple;
  5375. data.direction = this.direction;
  5376. data.node = this.node.uuid;
  5377. data.connectors = [];
  5378. for(var i = 0; i < this.connectors.length; i++)
  5379. {
  5380. data.connectors.push(this.connectors[i].uuid);
  5381. }
  5382. return data;
  5383. };
  5384. NodeSocket.prototype.parse = function(data, root)
  5385. {
  5386. Circle.prototype.parse.call(this, data, root);
  5387. this.name = data.name;
  5388. this.category = data.category;
  5389. this.multiple = data.multiple;
  5390. this.direction = data.direction;
  5391. this.node = root.getChildByUUID(data.node);
  5392. for(var i = 0; i < data.connectors.length; i++)
  5393. {
  5394. this.connectors.push(root.getChildByUUID(data.connectors[i]));
  5395. }
  5396. };
  5397. /**
  5398. * Node objects can be connected between them to create graphs.
  5399. *
  5400. * Each node contains inputs, outputs and a set of attributes containing their state. Inputs can be connected to outputs of other nodes, and vice-versa.
  5401. *
  5402. * This class implements node basic functionality, the logic to connect node and define inputs/outputs of the nodes.
  5403. *
  5404. * @class
  5405. * @extends {RoundedBox}
  5406. */
  5407. function Node()
  5408. {
  5409. RoundedBox.call(this);
  5410. this.draggable = true;
  5411. /**
  5412. * List of inputs of the node.
  5413. *
  5414. * @type {NodeSocket[]}
  5415. */
  5416. this.inputs = [];
  5417. /**
  5418. * List of outputs of the node.
  5419. *
  5420. * @type {NodeSocket[]}
  5421. */
  5422. this.outputs = [];
  5423. }
  5424. Node.prototype = Object.create(RoundedBox.prototype);
  5425. Node.prototype.constructor = Node;
  5426. Node.prototype.type = "Node";
  5427. Object2D.register(Node, "Node");
  5428. /**
  5429. * This method should be used for the node to register their socket inputs/outputs.
  5430. *
  5431. * It is called automatically after the node is added to the node graph to create sockets.
  5432. */
  5433. Node.prototype.registerSockets = null;
  5434. /**
  5435. * Add input to this node, can be connected to other nodes to receive data.
  5436. *
  5437. * @param {string} type Data type of the node socket.
  5438. * @param {string} name Name of the node socket.
  5439. * @return {NodeSocket} Node socket created for this node.
  5440. */
  5441. Node.prototype.addInput = function(type, name)
  5442. {
  5443. var socket = new NodeSocket(this, NodeSocket.INPUT, type, name);
  5444. this.inputs.push(socket);
  5445. this.parent.add(socket);
  5446. return socket;
  5447. };
  5448. /**
  5449. * Add output socket to this node, can be connected to other nodes to send data.
  5450. *
  5451. * @param {string} type Data type of the node socket.
  5452. * @param {string} name Name of the node socket.
  5453. * @return {NodeSocket} Node socket created for this node.
  5454. */
  5455. Node.prototype.addOutput = function(type, name)
  5456. {
  5457. var socket = new NodeSocket(this, NodeSocket.OUTPUT, type, name);
  5458. this.outputs.push(socket);
  5459. this.parent.add(socket);
  5460. return socket;
  5461. };
  5462. /**
  5463. * Get a output socket by its name. If there are multiple sockets with the same name only the first one found is returned.
  5464. *
  5465. * @param {string} name Name of the node socket to get.
  5466. * @return {NodeSocket} Node socket if it was found, null otherwise.
  5467. */
  5468. Node.prototype.getOutput = function(name)
  5469. {
  5470. for(var i = 0; i < this.outputs.length; i++)
  5471. {
  5472. if(this.outputs[i].name === name)
  5473. {
  5474. return this.outputs[i];
  5475. }
  5476. }
  5477. return null;
  5478. };
  5479. /**
  5480. * Get a input socket by its name. If there are multiple sockets with the same name only the first one found is returned.
  5481. *
  5482. * @param {string} name Name of the node socket to get.
  5483. * @return {NodeSocket} Node socket if it was found, null otherwise.
  5484. */
  5485. Node.prototype.getInput = function(name)
  5486. {
  5487. for(var i = 0; i < this.inputs.length; i++)
  5488. {
  5489. if(this.inputs[i].name === name)
  5490. {
  5491. return this.inputs[i];
  5492. }
  5493. }
  5494. return null;
  5495. };
  5496. Node.prototype.destroy = function()
  5497. {
  5498. RoundedBox.prototype.destroy.call(this);
  5499. for(var i = 0; i < this.inputs.length; i++)
  5500. {
  5501. this.inputs[i].destroy();
  5502. }
  5503. for(var i = 0; i < this.outputs.length; i++)
  5504. {
  5505. this.outputs[i].destroy();
  5506. }
  5507. };
  5508. Node.prototype.onUpdate = function()
  5509. {
  5510. var height = this.box.max.y - this.box.min.y;
  5511. // Input hooks position
  5512. var step = height / (this.inputs.length + 1);
  5513. var start = this.box.min.y + step;
  5514. for(var i = 0; i < this.inputs.length; i++)
  5515. {
  5516. this.inputs[i].position.set(this.position.x + this.box.min.x, this.position.y + (start + step * i));
  5517. }
  5518. // Output hooks position
  5519. step = height / (this.outputs.length + 1);
  5520. start = this.box.min.y + step;
  5521. for(var i = 0; i < this.outputs.length; i++)
  5522. {
  5523. this.outputs[i].position.set(this.position.x + this.box.max.x, this.position.y + (start + step * i));
  5524. }
  5525. };
  5526. Node.prototype.serialize = function(recursive)
  5527. {
  5528. var data = RoundedBox.prototype.serialize.call(this, recursive);
  5529. data.inputs = [];
  5530. for(var i = 0; i < this.inputs.length; i++)
  5531. {
  5532. data.inputs.push(this.inputs[i].uuid);
  5533. }
  5534. data.outputs = [];
  5535. for(var i = 0; i < this.outputs.length; i++)
  5536. {
  5537. data.outputs.push(this.outputs[i].uuid);
  5538. }
  5539. return data;
  5540. };
  5541. Node.prototype.parse = function(data, root)
  5542. {
  5543. RoundedBox.prototype.parse.call(this, data, root);
  5544. for(var i = 0; i < data.inputs.length; i++)
  5545. {
  5546. this.inputs.push(root.getChildByUUID(data.inputs[i]));
  5547. }
  5548. for(var i = 0; i < data.outputs.length; i++)
  5549. {
  5550. this.outputs.push(root.getChildByUUID(data.outputs[i]));
  5551. }
  5552. };
  5553. /**
  5554. * Class contains helper functions to create editing object tools.
  5555. *
  5556. * @class
  5557. */
  5558. function Helpers(){}
  5559. /**
  5560. * Create a rotation tool helper.
  5561. *
  5562. * When the object is dragged is changes the parent object rotation.
  5563. *
  5564. * @static
  5565. */
  5566. Helpers.rotateTool = function(object)
  5567. {
  5568. var tool = new Circle();
  5569. tool.radius = 4;
  5570. tool.layer = object.layer + 1;
  5571. tool.onPointerDrag = function(pointer, viewport, delta)
  5572. {
  5573. object.rotation += delta.x * 1e-3;
  5574. };
  5575. object.add(tool);
  5576. };
  5577. /**
  5578. * Create a box resize helper and attach it to an object to change the size of the object box.
  5579. *
  5580. * Each helper is positioned on one corner of the box, and the value of the corner is copied to the boxes as they are dragged.
  5581. *
  5582. * This method required to object to have a box property.
  5583. *
  5584. * @static
  5585. */
  5586. Helpers.boxResizeTool = function(object)
  5587. {
  5588. if(object.box === undefined)
  5589. {
  5590. console.warn("escher.js: Helpers.boxResizeTool(), object box property missing.");
  5591. return;
  5592. }
  5593. function updateHelpers()
  5594. {
  5595. topRight.position.copy(object.box.min);
  5596. bottomLeft.position.copy(object.box.max);
  5597. topLeft.position.set(object.box.max.x, object.box.min.y);
  5598. bottomRight.position.set(object.box.min.x, object.box.max.y);
  5599. }
  5600. var topRight = new Circle();
  5601. topRight.radius = 4;
  5602. topRight.layer = object.layer + 1;
  5603. topRight.draggable = true;
  5604. topRight.onPointerDrag = function(pointer, viewport, delta)
  5605. {
  5606. Object2D.prototype.onPointerDrag.call(this, pointer, viewport, delta);
  5607. object.box.min.copy(topRight.position);
  5608. updateHelpers();
  5609. };
  5610. object.add(topRight);
  5611. var topLeft = new Circle();
  5612. topLeft.radius = 4;
  5613. topLeft.layer = object.layer + 1;
  5614. topLeft.draggable = true;
  5615. topLeft.onPointerDrag = function(pointer, viewport, delta)
  5616. {
  5617. Object2D.prototype.onPointerDrag.call(this, pointer, viewport, delta);
  5618. object.box.max.x = topLeft.position.x;
  5619. object.box.min.y = topLeft.position.y;
  5620. updateHelpers();
  5621. };
  5622. object.add(topLeft);
  5623. var bottomLeft = new Circle();
  5624. bottomLeft.radius = 4;
  5625. bottomLeft.layer = object.layer + 1;
  5626. bottomLeft.draggable = true;
  5627. bottomLeft.onPointerDrag = function(pointer, viewport, delta)
  5628. {
  5629. Object2D.prototype.onPointerDrag.call(this, pointer, viewport, delta);
  5630. object.box.max.copy(bottomLeft.position);
  5631. updateHelpers();
  5632. };
  5633. object.add(bottomLeft);
  5634. var bottomRight = new Circle();
  5635. bottomRight.radius = 4;
  5636. bottomRight.layer = object.layer + 1;
  5637. bottomRight.draggable = true;
  5638. bottomRight.onPointerDrag = function(pointer, viewport, delta)
  5639. {
  5640. Object2D.prototype.onPointerDrag.call(this, pointer, viewport, delta);
  5641. object.box.min.x = bottomRight.position.x;
  5642. object.box.max.y = bottomRight.position.y;
  5643. updateHelpers();
  5644. };
  5645. object.add(bottomRight);
  5646. updateHelpers();
  5647. };
  5648. /**
  5649. * File utils is used to read and write files.
  5650. *
  5651. * Can be used alongside with object serialization to store and load objects from file.
  5652. *
  5653. * @class
  5654. * @static
  5655. */
  5656. function FileUtils(){}
  5657. /**
  5658. * Read a local or remote file as text data.
  5659. *
  5660. * @param {string} fname Path or URL of the file being read.
  5661. * @param {Function} onLoad onLoad callback receives the read data as parameter.
  5662. * @param {Function} onError onError call is called when a error occurs while reading the file.
  5663. */
  5664. FileUtils.read = function(fname, onLoad, onError)
  5665. {
  5666. var file = new XMLHttpRequest();
  5667. file.overrideMimeType("text/plain");
  5668. file.open("GET", fname, true);
  5669. if(onLoad !== undefined)
  5670. {
  5671. file.onload = function()
  5672. {
  5673. onLoad(file.response);
  5674. };
  5675. }
  5676. if(onError !== undefined)
  5677. {
  5678. file.onerror = onError;
  5679. }
  5680. file.send(null);
  5681. };
  5682. /**
  5683. * Write text to a file and automatically download it from blob storage.
  5684. *
  5685. * @method writeFile
  5686. * @param {string} fname Path of the file to write.
  5687. * @param {string} data Text data to be written to the file.
  5688. */
  5689. FileUtils.write = function(fname, data)
  5690. {
  5691. var blob = new Blob([data], {type:"octet/stream"});
  5692. var download = document.createElement("a");
  5693. download.download = fname;
  5694. download.href = window.URL.createObjectURL(blob);
  5695. download.style.display = "none";
  5696. download.onclick = function()
  5697. {
  5698. document.body.removeChild(this);
  5699. };
  5700. document.body.appendChild(download);
  5701. download.click();
  5702. };
  5703. /**
  5704. * Open file chooser dialog window for the user to select files stored in the system.
  5705. *
  5706. * The files selected are retrieved using the onLoad callback that receives a array of File objects.
  5707. *
  5708. * @param {Function} onLoad onLoad callback that receives array of files as parameter.
  5709. * @param {string} filter File type filter (e.g. ".zip,.rar, etc)
  5710. */
  5711. FileUtils.select = function(onLoad, filter)
  5712. {
  5713. var chooser = document.createElement("input");
  5714. chooser.type = "file";
  5715. chooser.style.display = "none";
  5716. document.body.appendChild(chooser);
  5717. if(filter !== undefined)
  5718. {
  5719. chooser.accept = filter;
  5720. }
  5721. chooser.onchange = function(event)
  5722. {
  5723. if(onLoad !== undefined)
  5724. {
  5725. onLoad(chooser.files);
  5726. }
  5727. document.body.removeChild(chooser);
  5728. };
  5729. chooser.click();
  5730. };
  5731. exports.AnimationTimer = AnimationTimer;
  5732. exports.BarGraph = BarGraph;
  5733. exports.BezierCurve = BezierCurve;
  5734. exports.Box = Box;
  5735. exports.Box2 = Box2;
  5736. exports.BoxMask = BoxMask;
  5737. exports.Circle = Circle;
  5738. exports.ColorStyle = ColorStyle;
  5739. exports.DOM = DOM;
  5740. exports.EventManager = EventManager;
  5741. exports.FileUtils = FileUtils;
  5742. exports.Gauge = Gauge;
  5743. exports.GradientColorStop = GradientColorStop;
  5744. exports.GradientStyle = GradientStyle;
  5745. exports.Graph = Graph;
  5746. exports.Helpers = Helpers;
  5747. exports.Image = Image;
  5748. exports.Key = Key;
  5749. exports.Line = Line;
  5750. exports.LinearGradientStyle = LinearGradientStyle;
  5751. exports.Mask = Mask;
  5752. exports.Matrix = Matrix;
  5753. exports.MultiLineText = MultiLineText;
  5754. exports.Node = Node;
  5755. exports.NodeConnector = NodeConnector;
  5756. exports.NodeGraph = NodeGraph;
  5757. exports.NodeSocket = NodeSocket;
  5758. exports.Object2D = Object2D;
  5759. exports.Path = Path;
  5760. exports.Pattern = Pattern;
  5761. exports.PatternStyle = PatternStyle;
  5762. exports.PieChart = PieChart;
  5763. exports.Pointer = Pointer;
  5764. exports.QuadraticCurve = QuadraticCurve;
  5765. exports.RadialGradientStyle = RadialGradientStyle;
  5766. exports.Renderer = Renderer;
  5767. exports.RoundedBox = RoundedBox;
  5768. exports.ScatterGraph = ScatterGraph;
  5769. exports.Style = Style$1;
  5770. exports.Text = Text;
  5771. exports.UUID = UUID;
  5772. exports.Vector2 = Vector2;
  5773. exports.Viewport = Viewport;
  5774. exports.ViewportControls = ViewportControls;
  5775. Object.defineProperty(exports, '__esModule', { value: true });
  5776. }));