From 7b01bde92fe093ffdcc72f6231ec88da169eb9df Mon Sep 17 00:00:00 2001 From: Tamrat Belayneh Date: Tue, 22 Jun 2021 23:21:36 -0700 Subject: [PATCH 01/57] Added I3S data source support in Cesium --- .../gallery/I3S 3D Object Layer.html | 77 + .../gallery/I3S 3D Object Layer.jpg | Bin 0 -> 18709 bytes .../gallery/I3S Feature Picking.html | 150 + .../gallery/I3S Feature Picking.jpg | Bin 0 -> 19393 bytes .../gallery/I3S IntegratedMesh Layer.html | 76 + .../gallery/I3S IntegratedMesh Layer.jpg | Bin 0 -> 15995 bytes .../ArcGISTiledElevationTerrainProvider.js | 25 +- Source/DataSources/I3SDataSource.js | 4381 +++++++++++++++++ 8 files changed, 4696 insertions(+), 13 deletions(-) create mode 100644 Apps/Sandcastle/gallery/I3S 3D Object Layer.html create mode 100644 Apps/Sandcastle/gallery/I3S 3D Object Layer.jpg create mode 100644 Apps/Sandcastle/gallery/I3S Feature Picking.html create mode 100644 Apps/Sandcastle/gallery/I3S Feature Picking.jpg create mode 100644 Apps/Sandcastle/gallery/I3S IntegratedMesh Layer.html create mode 100644 Apps/Sandcastle/gallery/I3S IntegratedMesh Layer.jpg create mode 100644 Source/DataSources/I3SDataSource.js diff --git a/Apps/Sandcastle/gallery/I3S 3D Object Layer.html b/Apps/Sandcastle/gallery/I3S 3D Object Layer.html new file mode 100644 index 000000000000..ba4f5b7f276d --- /dev/null +++ b/Apps/Sandcastle/gallery/I3S 3D Object Layer.html @@ -0,0 +1,77 @@ + + + + + + + + + I3S Consumption Cesium Demo + + + + + + + +
+
+

Loading...

+
+
+ + + diff --git a/Apps/Sandcastle/gallery/I3S 3D Object Layer.jpg b/Apps/Sandcastle/gallery/I3S 3D Object Layer.jpg new file mode 100644 index 0000000000000000000000000000000000000000..0f9ede8948f3870a563f33c2d25b17305028716e GIT binary patch literal 18709 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 literal 0 HcmV?d00001 diff --git a/Apps/Sandcastle/gallery/I3S Feature Picking.html b/Apps/Sandcastle/gallery/I3S Feature Picking.html new file mode 100644 index 000000000000..0ba67a81af49 --- /dev/null +++ b/Apps/Sandcastle/gallery/I3S Feature Picking.html @@ -0,0 +1,150 @@ + + + + + + + + + I3S Feature Picking Cesium Demo + + + + + + + +
+
+

Loading...

+
+
+ + + diff --git a/Apps/Sandcastle/gallery/I3S Feature Picking.jpg b/Apps/Sandcastle/gallery/I3S Feature Picking.jpg new file mode 100644 index 0000000000000000000000000000000000000000..30e841384a6f98f6ae991542cfd8ffd696284efb GIT binary patch literal 19393 zcmbTcbyQnV6fPQyOVQ$5q_hNgE0oeArMSDhy9L)G!HO1Zad(0{q^*xP_E(8D?k{*`Loi}~M%_6!{Z6YFV8c=%5PT8N*fkBnghfQf#AQEx zl#^Fb{G_R+t)r`_Z(wO?c?j`9}p246&(}%B`!7XYkEdzR(8&hqT-U$ zvhs?`#-`?$R!CcW$H3sw@Cfwh=-ABc-2B4gpCuT4b8CBNckl210rKqp-^Jw>>iXtC zTxd^A_B}+0zy6EIwHeZN!7J+%}|if&{T#`Dc0b? zJG7NvbqxR1=34Js#-osXU6VTLrX#^XMW^MsY|`XD0E2lqz11{O^6=w*$z4!63f-vr zVQt}fbB+QSB&74+2h)Q!k*)7fzftNqB!dbjTn-T#W_;o2*dHKOXTLSRe?gB%ClobO zmwFSbAB znKn&Z1(ID5G!}Mc8Diju%u|eejU4K?x%1iMq-;GiO@HS%0GH#7m^(QjgSX~(Z2P8h z9FVTbihq;HGM-<4-c=<3(mB-M++$1ynZB%Hv`=xEpX_0^8rL+=?F&%Cj;faqcp|1TKAcgV2qj*NZ=)j7NX$qB&m z7Cmt}HT=oNll{US(~N(Jmu-2+iBPQY?gP@|k9<%i>Trl8OH-uP)W!2mLED1kcR^Ma z-&XL86O7jwIZd)|IUG}|V z!FAc0y*crhjsvM~O{xu?JFYQN=6IZb;i=pCQuIY1O`fBFJ==1015S0OfL@9y8!DhP zU>nZsY2Vao&zsm?JJe{)_#ndp-AO)8EmnyEriG<5X-~&yu5ltt+bfj||K2cu5yY2p ze}1sP7Sw&*-E4i`){P)z(Hy znS}dA^8N0PqFTeyfFe5doLplFflt?d9fOY)*{b`}a+RZS=e+4S>-<7VsJpa=wIMf# z+vnv$p&UUddcmeB)tPkMbNEu1qJW>rxk1OwHOV8uLFR=o0KM;incO_V!_9BssK^A0 zhnRrAC34t)1n61g8utrbg=B5XXUNZ(P@%pf@y`1eAe1x<4fmN8dtfeKt^CDf-1|S= zTgl;F1lg==?P^lS^Xaa)Dn@pm_f^Wsy%ATuLmg`Ht67jP*F`f3e%DS=j$#o-i-Y*E z`Swxrt9atf)3P&URno4FqOyK^D8b zajjlA?wh>sG_;T4d9$+;9TX$V+Au|gL4yQV9hO0CAT9PeLaddqr&S2W^WAv){r?8& zRtEoMpq&Ld*N%EuaSa7zdzlep5fZ)Sn+))(v~*C{e$y`}GGF3- zCH!zp#zDGsS$oU?MQEHtX~M)?yEk3QKHBmUSzCE4>W34%S(%6XvDEL?@0(;l0&sm} zW$xEwy=%%!y{XS10pGP>&l441R>Bq<%;7b{hO6&yrd1mv7@UXa4l($)qiFMD)iam~ z@)gm$9hKXFEMS8sIaMlDj`X(&QZ7$$$TpkEBY<2lpcj0n?J0GNQfL_{ibB`Sc7FsU z>AXP=!}AQ)4Qby7DtBZUfB!74#*!*}(au$LHWW{~ zB2O?mYQ(8bOVO{PxPBP=Zqkd}yQBU-zB_wKd9#k>Zz^+LymwXLj!oju!=GC%zHQ7g ze-H6|CE{%`fJ5Ntc>M6%D1nfP2kltGpuzf$yi-OOeQb*T zrq9HI&I=bMoZs=WznB8dKm)vfH^?0Cy>OfE$d`*P<(FGT)M2LRh3sSF05>Ro_Sm&g9$f36DeZ;t@oWxDK&@U08xE zN%TI{Bs_Jwk6->WKDK32Lm00!yrgYPks2Tymb;NJ+BfB{|4A1#erCEYZQ~n)edp`X z6AO}Y-83aL!FBSwEO_78jsykMw=C9uKvWs3hJYGn>?oE_T(p-oCmg-L7=J#fDMCoA z%n40LuTyY=LXc!5JtU|+#LXi>X~xT#Rn>{B1%K4l<$G^yCkADjmGBgKs+GdeDI3lP zNT!h=p;GL;N_$Q1>>zfGZ|^ICgj;QQMCHO7(zdyGu3DcT5Vd6uwSM_K0_~6RyDHCk z-L zis{pfiBu@UiIFg|>s+B@tsZj(^mFkDIdZ*~t`{nus9bc1nY2mRQufyamE$Z$dsq_1 z*4x(7*wD=F-k}IeW{GuA4ORoN7eYBJAo|)GGYgOdDP2c_A))DK21%r=CU!TeUA~mO zt;xt0sFx)Z`pZ1fsT5NTo6^hm`i%{nZs|?$l~k1MU;ty>Md>_NMY*2IBxYVNNpD8# z=Rar6X?ySA6CX!p0HT(|b{_;#5oLb!HKp-`4D`QFeys2HWYfyhFZgWnWpHE-22I%d z7Lo^UsY0ho{+eQUiEWCW2wvA6gJftVo9u;WNJuOAD&gc`(FsB$t$*5JoF`i}%<<{P zjz~B*8s0!47Zo+!-jpjBu2gG+8gHy5YdS&sqi9ws@JzNIw|w&KbW?O<#1W3&fm&HJE9{h z1s(x*hO3_!Hu*1UHAG|CEw*1RqN6ie5x<+#BHt5Qr#5nc@H++8u?JH9@RuBkCHs~9 z!Zs}Rg)Keyr!3pAe`RLXyIU8_wywC|3iG9lh_-@FIT{A6rJ8ingZ(=d+ z^SS`}BOu^=I;4R%yQ*(^m$0k$pK+}#`B=4&!Cw4PNDEuWn9?pJ4V#h_D>;`Sb!|_S zE}0jXJ*{Hn^vjKQ6eAn=BAuxeTuRJB-e#yc$cP9(bvPW_kN#1wm zV!zKiA!T&q2E_9loh^_Q!N$0fICk0l)Y_WA3i|k}Isx=^xTULtOp2dIRl*#Dp-tzK zy~Z-V`TPL~qaRw4x34OWLH*WOR)ht5UtZSe`$^|cv_ZqBNC3tcQ zvDEq%2+%A;m&lK-fBRJ50T$~IN`m8OvPUp-cf7~kH0dqID3BtnGsmgh{iodY5f$g7- zQYCH@==#Wv5=vRSWHBM9u_b-1moSim2XdblcI;a^t}<8GcfK!2^^a9sWn@4OB$N?f zAK3iO8*`Q?v{@w+KAR=D`RpxJE}@Lg+s`guwOP{7$TBr7pA+!{0Y-FI4204g4I9+E zh^si}9LN1)u!V-!+17_Nrj6vPt9Z-Y2idqXctMhIz^jVN>Kov?^5uLwZkyg+nZAsB zXkD^TD50GAxk;i5ZHY=;B>CQCZ$=*XqNk#nHKlcxxH8Y6fz}i68R;#pt#X+$?T%PB zjUpY=Mn7+Pw@of-TUYz;NZNXTW_FW&yHWI`AueD3BcLlG#B`>u7YkK6JtU3AgW)ks z|1bDus(dSqTAK#;wwm?%D4U)$j1MBVM?Uv3b)_2+5`XH#MYAb@86lYZ;nVden=dk~ zUYOZ(K??N4f$D<;(O6y;PT%gP;!@Q6Re;tZ6DQv5V20N-7v1kgy;h$&I_ejLSCuC! zXPH1)J}{rW!ra$picqT2Rn%VKeG$S`3w$EFmGJ>`&g7a4^hc6*h2;_l66RGwyBv|3 zvJJ@S8b|*iN#8Htud|E8b*{%r6-8P1#HPij9D7kswJ=i<@cbp8pvVALt6yW~f!o%O z6CFAAhLwBHKq*Cn!!NH4i0-xC*_)ZT8l|+3-nArF#H%!0qNL_G!BEGZx1W|G{DEyG z9A**VAKiY&iq(FP028pY&dmalBGQmkGxjn@hYS2R=&fpvf6~CQ_?9s$u?VKOykczf zcH8j<@6a@B-B(y;q4gKrcYrA+%Y}|p5|p$~FDiQz-=EVedUw9?{70MVD}}39a@zI& z=x@a>7ZQ*>_Ow0{P5$f#!pApxVU1f4a$J^=04(%7;H-B@I0JSwB-PL~@k%!go9_`& znZeQ?%1Q}jGJ6q5%V+!u5LGK)3}qj5R+qmE{P+k6(Y$kQqjU-Bm4Lm3Jh}ZXTy4;!Bwm=n8Z5Cw-@jnG%k&{W^?_*+?Qp1w)k!WKc16j;2nI zVUijv^7ydW6;7*T8|f};GaM>__oU~uAyVnhq6KM$ne;$=;AyxN+pmAOZ|VeFqiKIe zCVx8mZbPfxNEndr<>bB0EF!1jkQCj~dWyPAdUz;9{SsLD=Nj3A+cQ@S??BC#!v-8juA**&m!&Ph@4!-DhdE1EON4v2@Xt@*NrSB->m_<3I@S1~*S zm`2cGAOgJRQ`l~U^2glIH*TZ1L${04r?&$$~hf9E4O5StqdDi{kcD|abIPhQkpg6-gI8mi+#u6i|i@8G$d~# z<)FrG?+%~gZ3pC?i?h3rcwX!F+!3I};ICP}sUa!vSbAPVx>_lPe=R%shD-Pj#H7kQ z+8Ij7j^J1TR;RV61K|?V*bc$3=U5TP1<_7D&@#5|^48>`jMkzleH(pm_UAlV)LP|G zk}X%(#Uwh`0$FlJp4jVxVdB;m^xXC-taj!ZyN(K9`pw%@jK9)tgi?WpZIc}CYoyrp z4y6cZovXVHDe7pbcQV!oXJE@+#6J?p1%39$0cl;^8A~@m7{nC2_d=hE-H9pCpO#@guv>p6gdS&#)&pzvzO^5k=FC z6l^x?xNZ%To6d@Qpxb{0xPaN(=E;p&^s-FHf+yQwSc{FwdEjC-)M0sLwL$TMb9L<< z+3#5K%ch-K7Y==?h$q?FuZ=Z>@p3(|2ywBg|9XO@HCr9&1xJRJWSc>qtrt|cLj@B# zHy}wZt*<&URhiB0sSh=e076@X&kgG**S_xwBHhoDizAW-CBgH%?-_;HUh+c);|c|M z4+3-kAcND2{7i=;ZY4C@n%XmgT-6wj5hE{h!z)+&r%eVLq0dUiSEULBS~F_aOYELt&;h!B%xfIi@Hk> zob-&9v9o@T>PTV|eEEF8)wt_DIC-&1zIe6^m-ES%v#@D~6#?;egLx=ZLU>T;xk*y( zeWor_I6r!o)~O!K>%{x@t}Gq;9X%HrF6uC#m98*V;5k)+$uS#o(7TZVGIzdj5N`K( z|K7YPV78n>;HwFm&<#iy2}K7I%gHT__)U9n@gzI~5Y2+71a`vWE+yYlAqq2`&O1#h zSBn1rt_1E%g8f>U38T9>zfSmS#dQ3M%~aifqZ(I1lMSP;Om7lSoxWq2`q6fRyHUf+ zvi)ifnGj|7w`cOrS(7b8$sH%Qw%F#h9SP5fW*1gpY?u6j?<^b^o?;x_boVCJc#Q`! z{N1k~Wn6M$k`-){9vh3TNuJE;dp;MDyW32;Db?+{Ice~eN~yK<%<*90a*fHKO{IKS zNRm7`8#o!|RPj(8tqlg=x<=0`+B?3gX6x!8{V;LXCU>^_g|r@uiTz%Wz8E>_)q{;p zuR08v->t0jb`SpjX`1eJU+tuj636Du#Oge^}7 zIy;gN?UPL*x&M&IVooVgsVQ!ULDd(!e;KW!s#Llf=B3{H1<-Tsd{6n_u4e<8W7RLV z@rhKu(#{YIOV?gm<+D=6IOvh;5&IRlca$J~yd*u+Sr^vx7E#@icbxkOAn=xs_0=7= zY-wqX{+LRkSDG>qtDW2Mk=s3-0{bmG37qaspoj0UBO@IyLAxZyWY55qFR|AMnW;HS zC41gjq1CBQOEu)wAQ!^Upu+}s~V?0^jVUm z6!(kPTW+&*B9|J{QrdFHmqz|wM-dckDHQ#ZWZ68NG%)lFfx8*}MeZ;7h({Gee3E%G z1(x{*`N-RC+AelmB|p;Vd~ug_iYuvC%NrxYmY$1X(AcLL$+ zCPkNn#7l1@Qqet0HTB~jG-M&NHMyc_|0XCho`%nAmZd4x#%YH{vclIt&{vesItA1> z(b-j`k!kPf#(-lP_RQ#IV6lzXZTBK5~zZp zT{oy6eftQwv`Ia>`j|P9x+1}Bp2ck`k5PzD6t1!W#YOslOg-N;fmH1KE(Fs%Itx3> zxO257D~x;k^*y5^d8wG9wH4M>h>=7ptl_}!E7DP`x@ig{8(1gM7c}PBQf`CH*npSd zK@!JJ>mL>xx-uK5W+w4JTloxxR@Yjs*8^(cAv7RGqWB(GQ-@@zH=XZ$p=UwD^pZ($ zFqm-6p9!H^qKD;1PpK&WX-9m9Dm)R?79s!Avuu6L-*FP9Fm2yTWN4X0 ziqTUDZEx#|8K0BVJHgc_&#HW0GMMU1ZtX3a@B~$?gZ-R zNB4-&@CZs1pgt{s{Ot{_a(*=fdvl>wAaTzcwE}0+0l8R&jxrFxQCn6J{MU{6_dqn) z?6)(cqx3DF>(iPui=2qSYrSHyLTujL8|-m!r*MoJH`#O(M;YV|$`;OEU|kAZH?}`! z%m4nO`f|YdDv`f{pP!al?4?8)Hp`q%hlDD*gWAfWhQl|hsiR3V7q;*kPZ3ct% zL9&;u=2^$v+MHZczVtef!}XKD{ugjIolhp#q9E?poA(pF%ae}ft}#9(&h+KiiW;Os zHp>aQx##_dIa^09_tM+X1V^w+aKc zU8F2uqhH)yo(`%AME$tzxXoUQX)G-Y3fWk3>*!c));sy8XlOY4!RazXFGVX9{#;BVdwsH)}h(YYQHs6a(p4U=f={d7Fwo6WoQHih<=u#05z6}E` zR5}P7b6t}ki&_0>ZCKN{Cy_1LKc8&1|I{*}qUG}CODZ?7_|diJBE5%yofMM!n9TM* z_aWUJBwK6Wi{~OK!x{hzh^j5*f{nq)jST;^(g z=+-fk6vOmXPJxy8aIsGImoD#mK)2Ee>9rJ9m+R%T2P>jO6Qd{UdGBbinIJ{BnkYI5 z7dMTBjF6ei;p1#^|2a25k|40k4}GRczUTl;2q-cfcWsG~?+gg}S7~P#_nOTpv3$4YdbK(f z%@O<;J;9R614wn}dUNDvc>hCn{4TK<&o8ls%h|lQTiULTYdb8;M?x7onXFznszw-V3%k>DD zan41LHfkf}Jt163toNHS1d84PRznHfA;LfdqzdMHc>h&Ll}J5pgv=**7$H zysR0mewj`a{_l>kb$U?!4~YYWQe&AFjr0wkwZgS579OLU=~vQKY_aV*Qw%AF(F#3N z!!=b_PXlsJ^{&PKj2rt7EbF5OU-7Y zvt+uQrZyp)5qfTMa68_@uZ+(z_R?*vj9ZkQ8XJ49D|b0_smfQB5AUsr1RRk;%6BniwkL)5zWnI7 zIC%QJB_@vfo8d1WZ)G9oZX}j zCcozJvuW@0wV>~|Ht}O^7HlM4Y#K(#V5Sih;YbV=`-@Jx%r9N^-dn0&a-v)w7_p<_C!)iSZ|U9k2x#pJ*_w+^vTwSNZ(h|s z=2t{g4(J@a(sxCX-mL6Rw8X(5{>8TqS=s%OqfbcgsBAL-94v63Aee-U($tvyj!Juf2%j! zM64_Cd?L3Ln6gZ@pk~7dzR||(JmUqRN10+dlA=RY+RVr%*!0Y}k2^d5t&RlPf}8O* ziyi?{@~)2nX52(0fx7y`dmA4C&T?ks%hd|eR2H8z z@Xw7E{-^;+8er(U>35z;w;51KPzlu2)8_Yz!BY0`XI+Lh&U$$XQXv1Dv()0WaYDz@ z5SvplYZ*Ks&13cTraevW7b|>R|dp=0A8xS3_#E%$4a{8M05K5t^ML0|5Ay*imI|4icq!wj&G557T#kRZ(ovs z0Jr_9x1g{U&5LWvLTc&O$1Pk>jgjl>N4xn#d{)0$x*NR77NchRT6h)`MeV-R@b#sR z4K=kGi_q=^O z`C8kf79P%r9|6zrBsTF)xw=#=_^3-Y1vRo*&Bj^yb6w&Ct(+}};GsV&6GdFSuR@Hc zMB#fla*c;2aXO4`y>ZAE{6rza*~@me*zz$Zi3n1nad1x&bGCFer&o~8t=4R5oKgre zCs}&bi;wN({ocUE;A3dJ1X#td1<`0O66bVup;&EzOqOgYWELZv&=!4uoD25DZm~q& zZqm7#!t|HByZbWeFB=u2Y5nPypgYCu{G+L(vv$VC423fZ8IXtGs+>1<_7(Bvjy}OG zi*;UhdtGO)%;1IHKtrGGmv2O}bX4Al;?-3!+yl#zQ|Uxe%@WGoic-^!IMr|wy~s|2 zV{D&iXMIn5#u0#2+p?xHso(l5V1@O0yxhPuK=X=An82O^xRO!FOPX>#f z#^l?8$w5`KLYFhgUNy=s_(_fIFs_-U@Q0)E_)`2=Vp`^Hu77byH)u9I)YDvV4*c2@ z-j^7U87Df@tLfX*XqhMvCAzzW3fiZ?Bk|C01RVBFsSu(KdrGm-)cgxv>X{}Td9^B# zn>r(AgPE1DmrJO~9tZnap#m~Syo)R(luR4Tg^wo@w zHuz)cskU>!UIr_ASiY(eQHisP(Hc_p&6~Mh6WbZa2A52Cx(Du#u4X-^7^N1H;z+?L zR(;xCbSB&Jjb|Rbfg65xQIe+K-w$BTzn}yK4&J`}&;IlWXR*vJc05eMgdPFm4=+F( zX&Q4h4<|EZ*f0tnE^X#!pTu|4OfOlDwm5M6Ic3-fq+YD=GuqD;&ui08oK2Y8MNF7a zWZ%ZgU-{E9@TwYIPk6N#z45Ob&F3te_P>H``>qoNzedG{P#8jspvBM%BYB}Nlc2h% zB4diUU?82Sjkk?9t>Vfcl_<+!S3-wgsxCISnWZ}olcACfg?(rssRESwM?hwMk{s%t(17%tz2 zJjK{M?SPmeRT2=Ia=e!vyjqx%wW*LKh^%aN zlM@}+LCtlsJOU)EgL^GP9+;c%49ihAruYZ=&h%xl#Ln1O7=OK!djupd72J8Ld0!xC zcrCLqui;T! zmR+W>P;3ia-$5cq(>rB^SF(wr+^jvs)@qYN4Vli^sdOm<7)mbiKwEV+>N=Q z*YaWy6%)E@Hx>s+itPketzOsk$a7YgSU2>c(mwb8@>Ukna;<*^SRO9EA6ib=FAnrK zop>LtQHiF&ueB#>i=7Dqc0gt?cH?yIm^xy{v|@P)c77#w6cdVAwPJ~Q;F8Zj{SqZ! z#nx5G2V%}D`adM$8#Y_Oct^C7f-gml+u-k{xdAY=vr%ukxskw0cKZED<1Eb!ZcbPlij~FCox4|&jo7&Qz54Lut z>1%R&d0Io?^0Mwyj%B8JyD+KLhfs{0&-bEYf3?_CH;=NVYPC^p0t0QV&lEUSw$8_i z#aOu!ZUQ+6T_1x=O|xhRZ(P1x7GL`uRY?XSX+I1R@fBO1DOe?jPAzQQ)lio|P%RHu zFQHOLHmO|5O#?V%>8!5flZk%mYFSRJaM?WqHn^Tw43A^e{@r{8e2^HpGH{Krap63^ zSU(zv>IyHK<2L1+at*1C3gcnevXTZ+eD)ZC2!-HHTbo&Zb4n8mXPZ&wYqxQW^{ql+ z9T&DOpA#3uzn+XEc5`6EDPrAyo~v4wfb%W0juIv zShtP=5?{ZFoih(=+k!QNEP9driJgL$Wpj(PNYzd6r0AT51=??(0nWrdMkF}Y*etb2 zgp})2^KJP&@xv*7%Z2s}Z-)kki&9kOHUIi=u_iDmJC+n@robwCbXlf@xkg)+^d14- ztcll_caC4Y$8(OQxW<}{4H|jP8b8O-XTA7Y%lB~at)y3)R8%@!Mv?dk2%EV!r4$V5 zKo=T#DorkSwcWd$tixB|)}HR(eymco^rVIjedU_~6OLL+=(#CwlZ*93WjA_po3DkE zz8Ji%_SO6|>Ma&)^d1YPRKyKW0Y9yc%y!^N3VgqZc3F{a{$l6%w{77y8$LUyiyH`_Wq=#jp5b0f$K%uU8BJXJF{UY;urJKNZHB&ucDD?H^G|~dtP^J6#- zoZKw3&*bqwk9CEAU$!@njAFw95GuiK!^##ZE-B_%&pC(8OLC-zN;s_!WK6O~+^1VC zmi=bckgh{P8i+&bWyfI2^j``>(m`)?OKjD9)KxuJSZ-~+FW5BA4Pnj}MK ztqKo30QZCDy^@vWUb0+BbOoX~P{>o5|stTUr zL-kT*ENW1}h8vS2uIdBu-&OmjO@zp)CFGsIp;$t7u!u zZ{UnB3{195+mlmqW@ShUYC$D~QU$ah-b*Ivl6(+}-Wa0_mCbM^S?Hl( z_lq~^nR6Ag+wGcjVeQC!fI>ixV!lq;0P8QJRL>f{GrKa?^#xmyMn1hZ5TeU8fziEd zlKUdech>|D_A&{PFhT{^S<=I)b>E@*laIrI^u!26+jiEV%F9zEYCo>Hd@x5mz?6{4D;Zo>QOlzQ@z0V)4tf5k|2ke(ibv+q`YQOTyjL-O;*yT|eHQhlFy^ zJ1pz#K)ut+oo2=X}2FeEuSF*@KdacYCYGUpuj;rY_-x>n;nP zq}G?7!aE^sbsusw->}KzUc;T^!eG>Rx^X=BcB@QGlzP$$MW=9s9ml0B z^|32O(x2|^MD_2F`XfmZ_Aip2m!drbjmiXRZ#r9UKRfOW%rLH=P_xRt+cs+r)H&bP za)GGcJpz8NfyRl{y_I_pPsE90OxS)u2^Z}Cwo>Lq4QCv!;deT!dpR#~O<8Gk#~u9; z@zxltwgfa{tq5L7Ga7?FB3!zITozhkUwh2(M95=8R#|P|;{FYY0B2RM@ijf|caMQ)8k{8BJ9a-*nZp&sROx+a3G>|B{wp`R8R* zZbCX09a*lfEyA}i^Os-7r1OUm*kaQ5K-9?4ibUE-2%oX(&~w5C{bO&eDCHHd?dt43 zdyFl_-no!S|4*qUr02jTyhPT!a@jCCFtTD~(+QY<1xWB$7r!^{zH9Lu&2iY-&o6k_Vt*#jg?$s!z<w@Bx8w=WukIikdW&_Y0Q&&x4a?T86$yjR)&!+*ycID~4$+wB(iuPE-`6Fwr)$b} z;A{MX{_4;4grP+_p{giHH4Y<*y(d>sl_SfuaXMr>dIeh8L;63)!6YyP3nJ;y-be7^ z#}>4ZCK%dII`*Cwo5H!OvYJ%7qIe zd{p%Gr|w@D&fIJGZ#^icYUqzbsn$+})8k#zKi8UHtDSfOPB7F_*N7P?Tu_7p=5-S^ z+#B1%c-$mhKs#*fHTzP^>Gg*Ahkor8(_L*QG81<1jyE+t)skt}<)qG5BO(Cut};T; zbk;WqxYQU3)iRJA^31PufQG=8Z5l?gwu0GycGVU~M#kS5^pBJ?4j**z0%&VzIUNyq z`TMJ&vF^pWqT#CPufd`EZE+7kCDZFts(C2Jk~fv1Ka+3-3d&VeQ~-IX4;g5&Yw zR+xM}<;swqHDVSc*nrrc;wqQ{Sz6+8l??rER~L{v+$j2?(sDovc$adLptKV)ksABM zsJRljrxXGsJfL14pWTWUcKwQAosZ*|NMN|ECu_80e^L zN)D2yh$-H$sZtI|F^FF-To^9czTdSJHR2m(Rz);@sYa|)3!IouN}2DTXpfByweK1>f8-1WwZ z?R-+w2~tNEY)ucH;(>V2B^AG;bm&F21nfU0OCnc*(Lmnmj4A<^ca^gZnG>v{shTW5 z*QJu;6Q8s{$p&fb_dHJrj`Ku#Ag*|IL|Lqmvg0OUOwsb+j7rXbgOX_u29x8Iacc_; z%YZQhv!7Gh3c8#}+2p%PzMD$GpBCSg<|8w)6q%#Ud@@JXR4xJZWfC4@BPZ<|Kq~mb zr+u!3h1gr5(Nk&{zvWZmT4e%y3!W?vr`4l6z`1Yn8r&2U&U0K7~s!0xAkZWx@WV-x@yZdKt4PIfaalqtrQapM|hr_u~2>m z`DIKVlgiq3ICTzhzIeUKUU{wO8Gd5T<78=KC$4a$-; zBg#=@{6RhaLOcxV@uaiMG6>nQeFQjGO1(zq-;_n4)_`*w;$s<}PLCZ{AuQA*{RLC) z*ALWBIpVC>c-81jFulo?#Ii@75E zvrNsGss>Pz5M!H20A|cWG_gyxh6r zIJHY~8)Qmteu`;Td%<9FXX=v-h+a}3u1Yu3;XIe~w%P-;3(^=mUQWyGE1?W|5=6sg zpHa#AbI^3NY?04*@8nCqq8*}Gkp7!FOYEHe)#x2-4r}su__ABAR_%Gp84dj6>jKCe zakH+2=t{JFevD|zHR^BmdQWRrx$$+Wg^KLztd~2`3B-$tTw{2Wuqp*!cBS6*yOJz@ z>bj-S+BzOoorK%(*9mFj56Cf zZL%#hGWri;*p3B>-pSOAuVwC&7Ep>71t!9r?zm>@g0weVS_)Ow3>1CQqT?0C#v81o zh1%4VTaa_R3ZZJixv$JGe5IefCjkqYXCLG9J5^urItRD?gC0gmy8AHrSn==sjCrfD@WJ-NIK4GVat(oIX$;Ti zH-mI1>!=tcaQK&Bz@g@E)-=W0F5%0j107c~76)S0H%skc5$NvwpMOhafyqmNt$f5q zbWw#v#u-S~6jjm{d*XcIlgMRbw~LpoK;WRC(x1fq4%9%CP_84-_LR+iGwiHm%COaN zD01Ao!(V?PMd8>(G4Jizt*Q(`e$5elbvCQn`9w;WY5|Eg(ZtmsB*=}hR-k3*WN^kL z!OkSY104vdP;0xFbz6=$DsAfaC1}0+V75uk%28d$rYFi2^{S1C=uQ-AJQ7cyU+(1P=fv)liV1=EeC3>c z{f^}wF=ygSY=3|#tP-2=b*(`8?U~r8;L;S53>Qogukx!TU8)9sDUw14~eHTqT!CmGvPe_&Nl+x+h&Q4I- zLo1DsqWL$SgixTA&(Pb|O;*hePTeR~5FHW7kGvQiaW}E`e5mM&PLQnosC2V3&7N4# zYtsAeW&P$QsU8zyFCU|Ce#@2Z(LN$`5Qx}yJcOp6iC*BubAm0yVRkyY#!x&QtE}YC z#$mY`LIH&THCgS7XThgimP^w)E|6)-;TmICsF7jy5AXIbzf26!x@AZ7EM0tDSjzkU z`XSE2))<>d{@2Bp$M)yI(;J$CX5kw5Eh3y1e=c+3lpiY}zDR0dLb4-MpS4a5-S=~x z%A}(EaLAl9Mm&k6T){oUeu0BM97yRq78C01e3-M%HkxbchTps@ptc}#jH?hPS97WebC(E|=q` zX6bOs%Lh8dLIqu1tB=daIo6-7gfaMS!svXWMu1)~9wgs%4M-F@mA28zCFz(+lfF>m zUP;>-Py6P`N(q#^G3Pj+TBDVc)28Adu6Q&=Rl5L{Gd`h9PTu zQFK4loLA zWmf9d0Zj<>7=Iw(3ieNt^NyRFQMN#Swh*$40pT72=2rQGsglK3Dc(n~L?sDS4VN|Z zPF8eR3%7c5LGwJ|ED&>OmOdB@ok8{UBKDJrl50AwWxt1JvTR@Y#;1_ElUa19v#qK7 zPciiQV1sw)P&r=Sm}I@K%N|!R((ru?QZ78l8j-4G_(f!tMod+}b#;_(gYS7iUHJh& zkrZJjrx?Xv9!|T`+)*$WN=X3~50`K>evflny_=;>#v1u%pq_wQm1Xw|``2O|Drrj@ zn&7oOiKWbZE7P7g56@BjkPmXqRO5Z|Y@#8diWiXIUY*rNQVSBQ*Mw6u+rQ*$3jWO* zR@7X(})9X(6wbTRdkb+4QOhr${rs4ymd#S6=Jo# zyD@r`Jy4$jqJ#X@xN*#cMHrSIy|Ln%zWyY#3VhDK`R>=npNs=WN;{}>OS{$COOZ{5 z?XY@WbpGWZ8w>X-1Lea_;INu_=IU&GobqYZ^^*@W=KGW~M9(W&8 z*Wib7Yx(rM2-&XXl0zy7eXPIhJj9SLPSj1mFeKxK)b;I4#yU=}w-PRgVP_J_b!h7C zHOg<^qDcr;QMw>@#$P03c*iF*O*-Vcn1*0E}b+4sbEi8VXdo;notEK5ULBP z`BjJqfH9FxDbP*y{=dl5F|xBUPlNve;VJOWiuX6)+uktve>@tdL<}!=D>RU}gQ?ui zVC)whMcQ}?uz)}ezm3Ok>{JjD2e+_&< z)wN$5=`z~bwbVLpp$?(_mtnahouZLjbDx-&H~`4|h~mDGk6Sv-O?@@1>8)_hGuz7| zJ3vV!g35ND=OU&xIo5?E$b4X?KMvff`@?9*zV)tGPSWo6d72vtV!FFjGDQQv&peKH z0(i;n4l~oeb{;<3`gQk|%OrkgRg{9PyT>5(#w$PJBo`N6BD#*s4<*)iryS?SwkD%*z~~Y=%cJ9iR|L6BCmAHjK@?C9(z_H~PHouN2m;meP5m zUBzOMpeLyS;Gs&5-*ng3AG247t^6zF%e$@9NquKy=PE?96C#_4 zQNpSNxa?>7TMPZ-pl%-|-Co0Jzr2xGjDg(#74(1XQLHb=&yN;*dRfA;HS|zL1;Ii} zK1Slq3M&jqas~!K=aPJU+uoEk@qW_W3swB&4FNnBGkAWLY>ck_q0Z2sk@-mak9v z*zC06wj{#9Tu3s6l6D+%l6b{&8jhFd>ag8?s_{f%sexp~GIhaa z$5FWV=L?>Ky|~hkv5zX~k7Lf`UNmc&%y0Mm9#2 z;-ecQ+JE4kUk$ImIDB2cwYAe^)N~&T1=Mw`gppM(*PvEn4&FkJgf`-&5-@iB#!GF_ z%Wd4;bv^6Hz7zPnSome6Yf<=i=IX_D03p<+hG#}Lq5Q`zRf8P2C#fWK;|JGcocNPU zcF~&KQJUdeJ7AL!E=dUaRIuTBz$EmqCY+nEQvEIe00;9vM;$cl$#y)yU9i0HM~L;0 z6WZCsb8&SnQNYr~F$`%6`NwcM`TVdjc90`tG7e6Lnd8*>hi12lZuY)O@?JJz!+60Z zPjP}e;MYOnZ`(WK2kg75wugP;+aaS#bk`Hp8clxt?D5Ct$#$8VbwcGr2HBO!$OW;J!tMvswrfwrUNOFt!oDKXl6Y;b z;k>oBc%x6=UkQM?%ahtRvRo02d88bMepLSeTAhdkA2AgZBKi-;zE{lfp5~{){W9BB_>164t}X56X#7ZR zRbDV-5$?`5V{az}oMey*Cb-|)H5cJ;jQmNX_oig9Vb6KUl2m<*-Bq_+r#xawV zjx&xZqPpmj2fjqb^uP2H$&UUKGKpgYH0hpiUUp?!szUt(j^0EH_$Ec#Sj`B0hcbB2V>NB;)?1kHQ z)3kdE`V0OE`Qm%;1M8k7*EFk!v(B>T zJ!I~9IN3f~KR?v!J}D;8iJl}}vtv}c8D=cU_s!IvPt?~jdH0j?qKf)Tf54BT{ST~< z@YkXkzv|@w0C3km;j5{9QSi znr-Bkt#Nw~4n1V3|Y5AU%jYSH@o)w5xqG_r_WrG2=||{;337bWBUNq;6f>HFnGSd(bdU?N%&Q z0_;geDw`f{JuV6`jRW3@N~@I6K~YF%H}}B*KWW+Aw?DGW?#TPY{TGQ YzoFaCK66kAl(FO1iYw+G$BKXd+35yJ$p8QV literal 0 HcmV?d00001 diff --git a/Apps/Sandcastle/gallery/I3S IntegratedMesh Layer.html b/Apps/Sandcastle/gallery/I3S IntegratedMesh Layer.html new file mode 100644 index 000000000000..a26ef5e6fd5c --- /dev/null +++ b/Apps/Sandcastle/gallery/I3S IntegratedMesh Layer.html @@ -0,0 +1,76 @@ + + + + + + + + + I3S Consumption Cesium Demo + + + + + + + +
+
+

Loading...

+
+
+ + + diff --git a/Apps/Sandcastle/gallery/I3S IntegratedMesh Layer.jpg b/Apps/Sandcastle/gallery/I3S IntegratedMesh Layer.jpg new file mode 100644 index 0000000000000000000000000000000000000000..5c73ec0467e6f00a14b31feb7513b38bb37392cb GIT binary patch literal 15995 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 j^qA1uL`+TV|N6iOLkB { + console.log(i3sNode); + let geometry = i3sNode.geometryData[0]; + console.log(geometry); + if (pickedPosition) { + let location = geometry.getClosestPointIndex( + pickedPosition.x, pickedPosition.y, pickedPosition.z); + console.log("Location", location); + if (location.index !== -1 && geometry.customAttributes["feature-index"]) { + let featureIndex = geometry.customAttributes["feature-index"][location.index]; + for (let fieldName in i3sNode.fields) { + let field = i3sNode.fields[fieldName]; + console.log(field.name + ": " + field.values[featureIndex]); + } + } + } + }); + } + + console.log(viewer.scene.camera); +}, + Cesium.ScreenSpaceEventType.LEFT_CLICK); + +*/ + +/* +.####.##.....##.########...#######..########..########..######. +..##..###...###.##.....##.##.....##.##.....##....##....##....## +..##..####.####.##.....##.##.....##.##.....##....##....##...... +..##..##.###.##.########..##.....##.########.....##.....######. +..##..##.....##.##........##.....##.##...##......##..........## +..##..##.....##.##........##.....##.##....##.....##....##....## +.####.##.....##.##.........#######..##.....##....##.....######. +*/ + +import ArcGISTiledElevationTerrainProvider from "../Core/ArcGISTiledElevationTerrainProvider.js"; +import Batched3DModel3DTileContent from "../Scene/Batched3DModel3DTileContent.js"; +import buildModuleUrl from "../Core/buildModuleUrl.js"; +import Cartesian2 from "../Core/Cartesian2.js"; +import Cartesian3 from "../Core/Cartesian3.js"; +import Cartesian4 from "../Core/Cartesian4.js"; +import Cartographic from "../Core/Cartographic.js"; +import Cesium3DTile from "../Scene/Cesium3DTile.js"; +import Cesium3DTileset from "../Scene/Cesium3DTileset.js"; +import CesiumMath from "../Core/Math.js"; +import Color from "../Core/Color.js"; +import defaultValue from "../Core/defaultValue.js"; +import defined from "../Core/defined.js"; +import DeveloperError from "../Core/DeveloperError.js"; +import Ellipsoid from "../Core/Ellipsoid.js"; +import EntityCluster from "./EntityCluster.js"; +import EntityCollection from "./EntityCollection.js"; +import Event from "../Core/Event.js"; +import FeatureDetection from "../Core/FeatureDetection.js"; +import HeadingPitchRoll from "../Core/HeadingPitchRoll.js"; +import HeightmapEncoding from "../Core/HeightmapEncoding.js"; +import Lerc from "../ThirdParty/LercDecode.js"; +import Matrix3 from "../Core/Matrix3.js"; +import Matrix4 from "../Core/Matrix4.js"; +import Plane from "../Core/Plane.js"; +import Quaternion from "../Core/Quaternion.js"; +import Resource from "../Core/Resource.js"; +import Transforms from "../Core/Transforms.js"; +import WebMercatorProjection from "../Core/WebMercatorProjection.js"; +import when from "../ThirdParty/when.js"; + +/* +..######...##........#######..########.....###....##........######. +.##....##..##.......##.....##.##.....##...##.##...##.......##....## +.##........##.......##.....##.##.....##..##...##..##.......##...... +.##...####.##.......##.....##.########..##.....##.##........######. +.##....##..##.......##.....##.##.....##.#########.##.............## +.##....##..##.......##.....##.##.....##.##.....##.##.......##....## +..######...########..#######..########..##.....##.########..######. +*/ + +// Maps i3Snode by URI +let _i3sContentCache = {}; + +// Prevent ESLint from issuing warnings about undefined Promise +// eslint-disable-next-line no-undef +let _Promise = Promise; + +// Code traces +// set to true to turn on code tracing for debugging purposes +let _tracecode = false; +let traceCode = function () {}; +if (_tracecode) { + traceCode = console.log; +} + +/* +.####..#######...######. +..##..##.....##.##....## +..##.........##.##...... +..##...#######...######. +..##.........##.......## +..##..##.....##.##....## +.####..#######...######. + +.########.....###....########....###... +.##.....##...##.##......##......##.##.. +.##.....##..##...##.....##.....##...##. +.##.....##.##.....##....##....##.....## +.##.....##.#########....##....######### +.##.....##.##.....##....##....##.....## +.########..##.....##....##....##.....## + +..######...#######..##.....##.########...######..######## +.##....##.##.....##.##.....##.##.....##.##....##.##...... +.##.......##.....##.##.....##.##.....##.##.......##...... +..######..##.....##.##.....##.########..##.......######.. +.......##.##.....##.##.....##.##...##...##.......##...... +.##....##.##.....##.##.....##.##....##..##....##.##...... +..######...#######...#######..##.....##..######..######## +*/ + +/** + * This class implements using an I3S scene server as a Cesium data source. The URL + * that is used for loadUrl should return a scene object. Currently supported I3S + * versions are 1.6 and 1.7. I3SDataSource is the main public class for I3S support. + * All other classes in this source file implement the Object Model for the I3S entities, + * which may at some point have more public interfaces if further introspection or + * customization need to be added. + * @alias I3SDataSource + * @constructor + * + * @param {String} [name] The name of this data source. If undefined, a name + * will be derived from the url. + * @param {Scene} [scene] The scene to populate with the tileset + * + * + * @example + * let dataSource = new I3SDataSource(); + * dataSource.loadUrl('https://tiles.arcgis.com/tiles/u0sSNqDXr7puKJrF/arcgis/rest/services/Frankfurt2017_v17/SceneServer'); + * dataSource.dataSources.add(dataSource); + * + * @example + * var geoidService = new Cesium.ArcGISTiledElevationTerrainProvider({ + * url : "https://tiles.arcgis.com/tiles/z2tnIkrLQ2BRzr6P/arcgis/rest/services/EGM2008/ImageServer", + * }); + * let dataSource = new I3SDataSource("", viewer.scene, { + * autoCenterCameraOnStart : true, // auto center to the location of the i3s + * geoidTiledTerrainProvider : geoidService, // pass the geoid service + * + */ + +function I3SDataSource(name, scene, options) { + //All public configuration is defined as ES5 properties + //These are just the "private" letiables and their defaults. + this._name = name; + this._changed = new Event(); + this._error = new Event(); + this._isLoading = false; + this._loading = new Event(); + this._entityCollection = new EntityCollection(); + this._entityCluster = new EntityCluster(); + this._scene = scene; + this._traceFetches = false; + this._traceVisuals = false; + this._autoCenterCameraOnStart = false; + this._GLTFProcessingQueue = new I3SGLTFProcessingQueue(); + + options = defaultValue(options, defaultValue.EMPTY_OBJECT); + + if (defined(options.traceVisuals)) { + this._traceVisuals = options.traceVisuals; + } + if (defined(options.traceFetches)) { + this._traceFetches = options.traceFetches; + } + + if (defined(options.autoCenterCameraOnStart)) { + this._autoCenterCameraOnStart = options.autoCenterCameraOnStart; + } + + if (defined(options.geoidTiledTerrainProvider)) { + this._geoidTiledTerrainProvider = options.geoidTiledTerrainProvider; + } +} + +/* +.########..########...#######..########..########.########..########.####.########..######. +.##.....##.##.....##.##.....##.##.....##.##.......##.....##....##.....##..##.......##....## +.##.....##.##.....##.##.....##.##.....##.##.......##.....##....##.....##..##.......##...... +.########..########..##.....##.########..######...########.....##.....##..######....######. +.##........##...##...##.....##.##........##.......##...##......##.....##..##.............## +.##........##....##..##.....##.##........##.......##....##.....##.....##..##.......##....## +.##........##.....##..#######..##........########.##.....##....##....####.########..######. +*/ + +Object.defineProperties(I3SDataSource.prototype, { + //The below properties must be implemented by all DataSource instances + + /** + * Gets a human-readable name for this instance. + * @memberof I3SDataSource.prototype + * @type {String} + */ + name: { + get: function () { + return this._name; + }, + }, + /** + * this property is always undefined. + * @memberof I3SDataSource.prototype + * @type {DataSourceClock} + */ + clock: { + value: undefined, + writable: false, + }, + /** + * Gets the collection of Entity instances. + * @memberof I3SDataSource.prototype + * @type {EntityCollection} + */ + entities: { + get: function () { + return this._entityCollection; + }, + }, + /** + * Gets a value indicating if the data source is currently loading data. + * @memberof I3SDataSource.prototype + * @type {Boolean} + */ + isLoading: { + get: function () { + return this._isLoading; + }, + }, + /** + * Gets an event that will be raised when the underlying data changes. + * @memberof I3SDataSource.prototype + * @type {Event} + */ + changedEvent: { + get: function () { + return this._changed; + }, + }, + /** + * Gets an event that will be raised if an error is encountered during + * processing. + * @memberof I3SDataSource.prototype + * @type {Event} + */ + errorEvent: { + get: function () { + return this._error; + }, + }, + /** + * Gets an event that will be raised when the data source either starts or + * stops loading. + * @memberof I3SDataSource.prototype + * @type {Event} + */ + loadingEvent: { + get: function () { + return this._loading; + }, + }, + + //These properties are specific to this DataSource. + /** + * Gets whether or not this data source should be displayed. + * @memberof I3SDataSource.prototype + * @type {Boolean} + */ + show: { + get: function () { + return this._entityCollection; + }, + set: function (value) { + this._entityCollection = value; + }, + }, + /** + * Gets or sets the clustering options for this data source. This object can be shared between multiple data sources. + * @memberof I3SDataSource.prototype + * @type {EntityCluster} + */ + clustering: { + get: function () { + return this._entityCluster; + }, + set: function (value) { + if (!defined(value)) { + throw new DeveloperError("value must be defined."); + } + this._entityCluster = value; + }, + }, + /** + * Gets or sets debugging and tracing of I3S fetches. + * @memberof I3SDataSource.prototype + * @type {bool} + */ + traceFetches: { + get: function () { + return this._traceFetches; + }, + set: function (value) { + if (!defined(value)) { + throw new DeveloperError("value must be defined."); + } + this._traceFetches = value; + }, + }, + /** + * Gets or sets debugging and tracing of I3S entities' bounding boxes. + * @memberof I3SDataSource.prototype + * @type {bool} + */ + traceVisuals: { + get: function () { + return this._traceVisuals; + }, + set: function (value) { + if (!defined(value)) { + throw new DeveloperError("value must be defined."); + } + this._traceVisuals = value; + }, + }, + /** + * Gets or sets auto centering of the camera on the data set. + * @memberof I3SDataSource.prototype + * @type {bool} + */ + autoCenterCameraOnStart: { + get: function () { + return this._autoCenterCameraOnStart; + }, + set: function (value) { + if (!defined(value)) { + throw new DeveloperError("value must be defined."); + } + this._autoCenterCameraOnStart = value; + }, + }, + + /** + * The terrain provider referencing the GEOID service to be used for orthometric to ellipsoidal conversion + * @memberof Viewer.prototype + * + * @type {TerrainProvider} + */ + geoidTiledServiceProvider: { + get: function () { + return this._geoidTiledServiceProvider; + }, + set: function (value) { + this.geoidTiledServiceProvider = value; + }, + }, +}); + +/* +.##........#######.....###....########..########.########...######. +.##.......##.....##...##.##...##.....##.##.......##.....##.##....## +.##.......##.....##..##...##..##.....##.##.......##.....##.##...... +.##.......##.....##.##.....##.##.....##.######...########...######. +.##.......##.....##.#########.##.....##.##.......##...##.........## +.##.......##.....##.##.....##.##.....##.##.......##....##..##....## +.########..#######..##.....##.########..########.##.....##..######. +*/ + +/** + * Asynchronously loads the I3S scene at the provided url, replacing any existing data. + * @param {Object} [url] The url to be processed. + * @returns {Promise} a promise that will resolve when the I3S scene is loaded. + */ +I3SDataSource.prototype.loadUrl = function (url) { + let parts = url.split("?"); + this._url = parts[0]; + this._query = parts[1]; + this._completeUrl = url; + + let deferredPromise = new when.defer(); + this._sceneServer = new I3SSceneServer(this); + this._sceneServer.load(this._completeUrl).then(() => { + deferredPromise.resolve(); + }); + + return deferredPromise; +}; + +/** + * Loads the provided data, replacing any existing data. + * @param {Array} [data] The object to be processed. + */ +I3SDataSource.prototype.load = function (data) { + //>>includeStart('debug', pragmas.debug); + if (!defined(data)) { + throw new DeveloperError("data is required."); + } + //>>includeEnd('debug'); + + //Clear out any data that might already exist. + this._setLoading(true); + let entities = this._entityCollection; + + //It's a good idea to suspend events when making changes to a + //large amount of entities. This will cause events to be batched up + //into the minimal amount of function calls and all take place at the + //end of processing (when resumeEvents is called). + entities.suspendEvents(); + entities.removeAll(); + + //Once all data is processed, call resumeEvents and raise the changed event. + entities.resumeEvents(); + this._changed.raiseEvent(this); + this._setLoading(false); +}; + +/** + * @private + */ +I3SDataSource.prototype._setLoading = function (isLoading) { + if (this._isLoading !== isLoading) { + this._isLoading = isLoading; + this._loading.raiseEvent(this, isLoading); + } +}; + +/** + * @private + */ +I3SDataSource.prototype._loadJson = function (uri, success, fail) { + return new _Promise((resolve, reject) => { + if (this._traceFetches) { + console.log("I3S FETCH:", uri); + } + let request = fetch(uri); + request.then((response) => { + response.json().then((data) => { + if (data.error) { + console.error(this._data.error.message); + fail(reject); + } else { + success(data, resolve); + } + }); + }); + }); +}; + +/** + * @private + */ +I3SDataSource.prototype._loadBinary = function (uri, success, fail) { + return new _Promise((resolve, reject) => { + if (this._traceFetches) { + traceCode("I3S FETCH:", uri); + } + let request = fetch(uri); + request.then((response) => { + response.arrayBuffer().then((data) => { + if (data.error) { + console.error(this._data.error.message); + fail(reject); + } else { + success(data, resolve); + } + }); + }); + }); +}; + +/** + * @private + */ +I3SDataSource.prototype._binarizeGLTF = function (rawGLTF) { + let encoder = new TextEncoder(); + let rawGLTFData = encoder.encode(JSON.stringify(rawGLTF)); + let binaryGLTFData = new Uint8Array(rawGLTFData.byteLength + 20); + let binaryGLTF = { + magic: new Uint8Array(binaryGLTFData.buffer, 0, 4), + version: new Uint32Array(binaryGLTFData.buffer, 4, 1), + length: new Uint32Array(binaryGLTFData.buffer, 8, 1), + chunkLength: new Uint32Array(binaryGLTFData.buffer, 12, 1), + chunkType: new Uint32Array(binaryGLTFData.buffer, 16, 1), + chunkData: new Uint8Array( + binaryGLTFData.buffer, + 20, + rawGLTFData.byteLength + ), + }; + + binaryGLTF.magic[0] = "g".charCodeAt(); + binaryGLTF.magic[1] = "l".charCodeAt(); + binaryGLTF.magic[2] = "T".charCodeAt(); + binaryGLTF.magic[3] = "F".charCodeAt(); + + binaryGLTF.version[0] = 2; + binaryGLTF.length[0] = binaryGLTFData.byteLength; + binaryGLTF.chunkLength[0] = rawGLTFData.byteLength; + binaryGLTF.chunkType[0] = 0x4e4f534a; // JSON + binaryGLTF.chunkData.set(rawGLTFData); + + return binaryGLTFData; +}; + +/** + * @private + */ +I3SDataSource.prototype._binarizeB3DM = function ( + featureTableJSON, + batchTabelJSON, + binaryGLTFData +) { + let encoder = new TextEncoder(); + + // Feature Table + let featureTableOffset = 28; + let featureTableJSONData = encoder.encode(featureTableJSON); + let featureTableLength = featureTableJSONData.byteLength; + + // Batch Table + let batchTableOffset = featureTableOffset + featureTableLength; + let batchTableJSONData = encoder.encode(batchTabelJSON); + + // Calculate alignment buffer by padding the remainder of the batch table + let paddingCount = (batchTableOffset + batchTableJSONData.byteLength) % 8; + let batchTableLength = batchTableJSONData.byteLength + paddingCount; + let paddingStart = batchTableJSONData.byteLength; + let paddingStop = batchTableLength; + + // Binary GLTF + let binaryGLTFOffset = batchTableOffset + batchTableLength; + let binaryGLTFLength = binaryGLTFData.byteLength; + + let dataSize = featureTableLength + batchTableLength + binaryGLTFLength; + let b3dmRawData = new Uint8Array(28 + dataSize); + + let b3dmData = { + magic: new Uint8Array(b3dmRawData.buffer, 0, 4), + version: new Uint32Array(b3dmRawData.buffer, 4, 1), + byteLength: new Uint32Array(b3dmRawData.buffer, 8, 1), + featureTableJSONByteLength: new Uint32Array(b3dmRawData.buffer, 12, 1), + featureTableBinaryByteLength: new Uint32Array(b3dmRawData.buffer, 16, 1), + batchTableJSONByteLength: new Uint32Array(b3dmRawData.buffer, 20, 1), + batchTableBinaryByteLength: new Uint32Array(b3dmRawData.buffer, 24, 1), + featureTable: new Uint8Array( + b3dmRawData.buffer, + featureTableOffset, + featureTableLength + ), + batchTable: new Uint8Array( + b3dmRawData.buffer, + batchTableOffset, + batchTableLength + ), + binaryGLTF: new Uint8Array( + b3dmRawData.buffer, + binaryGLTFOffset, + binaryGLTFLength + ), + }; + + b3dmData.magic[0] = "b".charCodeAt(); + b3dmData.magic[1] = "3".charCodeAt(); + b3dmData.magic[2] = "d".charCodeAt(); + b3dmData.magic[3] = "m".charCodeAt(); + + b3dmData.version[0] = 1; + b3dmData.byteLength[0] = b3dmRawData.byteLength; + + b3dmData.featureTable.set(featureTableJSONData); + b3dmData.featureTableJSONByteLength[0] = featureTableLength; + b3dmData.featureTableBinaryByteLength[0] = 0; + + b3dmData.batchTable.set(batchTableJSONData); + for (let index = paddingStart; index < paddingStop; ++index) { + b3dmData.batchTable[index] = 0x20; + } + b3dmData.batchTableJSONByteLength[0] = batchTableLength; + b3dmData.batchTableBinaryByteLength[0] = 0; + + b3dmData.binaryGLTF.set(binaryGLTFData); + + return b3dmRawData; +}; + +/* +.##.....##.########.####.##.......####.########.##....## +.##.....##....##.....##..##........##.....##.....##..##. +.##.....##....##.....##..##........##.....##......####.. +.##.....##....##.....##..##........##.....##.......##... +.##.....##....##.....##..##........##.....##.......##... +.##.....##....##.....##..##........##.....##.......##... +..#######.....##....####.########.####....##.......##... + +.##.....##.########.########.##.....##..#######..########...######. +.###...###.##..........##....##.....##.##.....##.##.....##.##....## +.####.####.##..........##....##.....##.##.....##.##.....##.##...... +.##.###.##.######......##....#########.##.....##.##.....##..######. +.##.....##.##..........##....##.....##.##.....##.##.....##.......## +.##.....##.##..........##....##.....##.##.....##.##.....##.##....## +.##.....##.########....##....##.....##..#######..########...######. +*/ + +/** + * @private + */ +function _WGS84ToCartesian(long, lat, height) { + return Ellipsoid.WGS84.cartographicToCartesian( + new Cartographic( + CesiumMath.toRadians(long), + CesiumMath.toRadians(lat), + height + ) + ); +} + +/** + * @private + */ +function _longLatsToMeter(longitude1, latitude1, longitude2, latitude2) { + let p1 = _WGS84ToCartesian(longitude1, latitude1, 0); + let p2 = _WGS84ToCartesian(longitude2, latitude2, 0); + + return Cartesian3.distance(p1, p2); +} + +/** + * @private + */ +function _computeExtent(minLongitude, minLatitude, maxLongitude, maxLatitude) { + let extent = { + minLongitude: minLongitude, + maxLongitude: maxLongitude, + minLatitude: minLatitude, + maxLatitude: maxLatitude, + }; + + // Compute the center + extent.centerLongitude = (extent.maxLongitude + extent.minLongitude) / 2; + extent.centerLatitude = (extent.maxLatitude + extent.minLatitude) / 2; + + // Compute the spans + extent.longitudeSpan = _longLatsToMeter( + extent.minLongitude, + extent.minLatitude, + extent.maxLongitude, + extent.minLatitude + ); + + extent.latitudeSpan = _longLatsToMeter( + extent.minLongitude, + extent.minLatitude, + extent.minLongitude, + extent.maxLatitude + ); + + return extent; +} + +/** + * @private + */ + +/* +..######...######..########.##....##.######## +.##....##.##....##.##.......###...##.##...... +.##.......##.......##.......####..##.##...... +..######..##.......######...##.##.##.######.. +.......##.##.......##.......##..####.##...... +.##....##.##....##.##.......##...###.##...... +..######...######..########.##....##.######## + +..######..########.########..##.....##.########.########. +.##....##.##.......##.....##.##.....##.##.......##.....## +.##.......##.......##.....##.##.....##.##.......##.....## +..######..######...########..##.....##.######...########. +.......##.##.......##...##....##...##..##.......##...##.. +.##....##.##.......##....##....##.##...##.......##....##. +..######..########.##.....##....###....########.##.....## +*/ + +/** + * This class implements an I3S scene server + * @alias I3SSceneServer + * @param {I3SDataSource} [dataSource] The data source that is the + * owner of this scene server + * @constructor + */ +function I3SSceneServer(dataSource) { + this._dataSource = dataSource; + this._entities = {}; + this._layerCollection = []; + this._uri = ""; +} + +Object.defineProperties(I3SSceneServer.prototype, { + /** + * Gets the collection of Layers. + * @memberof I3SSceneServer.prototype + * @type {LayerCollection} + */ + layers: { + get: function () { + return this._layerCollection; + }, + }, + /** + * Gets the URI of the scene server. + * @memberof I3SSceneServer.prototype + * @type {String} + */ + uri: { + get: function () { + return this._uri; + }, + }, + /** + * Gets the I3S data for this object. + * @memberof I3SSceneServer.prototype + * @type {Object} + */ + data: { + get: function () { + return this._data; + }, + }, +}); + +/** + * Loads the provided data on the server. + * @param {String} [uri] The uri where to fetch the data from. + */ +I3SSceneServer.prototype.load = function (uri) { + this._uri = uri; + + return this._dataSource._loadJson( + uri, + (data, resolve) => { + // Success + this._data = data; + let layerPromises = []; + for (let layer of this._data.layers) { + let newLayer = new I3SLayer( + this, + layer, + this._data.layers.indexOf(layer) + ); + this._layerCollection.push(newLayer); + layerPromises.push(newLayer.load()); + } + + _Promise.all(layerPromises).then(() => { + this._computeExtent(); + + if (this._dataSource._autoCenterCameraOnStart) { + this.centerCamera("topdown"); + } + + resolve(); + this._createVisualElements(); + }); + }, + (reject) => { + // Fail + reject(); + } + ); +}; + +/** + * Centers the camera at the center of the extent of the scene at an altitude of 10000m in topdown. + * Or 1000m when in oblique. + * @param {String} [mode] Use "topdown" to set the camera be top down, otherwise, the camera is set with a pitch 0f 0.2 radians. + */ +I3SSceneServer.prototype.centerCamera = function (mode) { + if (mode === "topdown") { + this._dataSource.camera.setView({ + destination: _WGS84ToCartesian( + this._extent.centerLongitude, + this._extent.centerLatitude, + 10000.0 + ), + }); + } else { + this._dataSource.camera.setView({ + destination: _WGS84ToCartesian( + this._extent.minLongitude, + this._extent.minLatitude, + 1000.0 + ), + orientation: { + heading: Math.PI / 4, + pitch: -0.2, + roll: 0, + }, + }); + } +}; + +/** + * @private + */ +I3SSceneServer.prototype._computeExtent = function () { + let minLongitude = Number.MAX_VALUE; + let maxLongitude = -Number.MAX_VALUE; + let minLatitude = Number.MAX_VALUE; + let maxLatitude = -Number.MAX_VALUE; + + // Compute the extent from all layers + for (let layer of this._layerCollection) { + if (layer._data.store && layer._data.store.extent) { + let layerExtent = layer._data.store.extent; + minLongitude = Math.min(minLongitude, layerExtent[0]); + minLatitude = Math.min(minLatitude, layerExtent[1]); + maxLongitude = Math.max(maxLongitude, layerExtent[2]); + maxLatitude = Math.max(maxLatitude, layerExtent[3]); + } + } + this._extent = _computeExtent( + minLongitude, + minLatitude, + maxLongitude, + maxLatitude + ); +}; + +/** + * @private + */ +I3SSceneServer.prototype._createVisualElements = function () { + if (!this._dataSource._traceVisuals) { + return; + } + // Add an entity for display + this._entities.extentOutline = this._dataSource.entities.add({ + name: "Extent", + position: _WGS84ToCartesian( + this._extent.centerLongitude, + this._extent.centerLatitude, + 0.0 + ), + plane: { + plane: new Plane(Cartesian3.UNIT_Z, 0.0), + dimensions: new Cartesian2( + this._extent.longitudeSpan, + this._extent.latitudeSpan + ), + fill: false, + outline: true, + outlineColor: Color.GREEN, + }, + }); +}; + +/* +.##..........###....##....##.########.########. +.##.........##.##....##..##..##.......##.....## +.##........##...##....####...##.......##.....## +.##.......##.....##....##....######...########. +.##.......#########....##....##.......##...##.. +.##.......##.....##....##....##.......##....##. +.########.##.....##....##....########.##.....## +*/ + +/** + * This class implements an I3S layer, in Cesium, each I3SLayer + * creates a Cesium3DTileset + * @alias I3SLayer + * @constructor + * @param {I3SSceneServer} [sceneServer] The scene server that is the + * container for this layer + * @param {object} [layerData] The layer data that is loaded from the scene + * server + * @param {integer} [index] The index of the layer to be reflected + */ +function I3SLayer(sceneServer, layerData, index) { + this._parent = sceneServer; + this._dataSource = sceneServer._dataSource; + + if (layerData.href === undefined) { + // assign a default layer + layerData.href = "./layers/" + index; + } + + this._uri = layerData.href; + let query = ""; + if (this._dataSource._query && this._dataSource._query !== "") { + query = "?" + this._dataSource._query; + } + this._completeUriWithoutQuery = + sceneServer._dataSource._url + "/" + this._uri; + this._completeUri = this._completeUriWithoutQuery + query; + + this._data = layerData; + this._entities = {}; + this._rootNode = null; + this._nodePages = {}; + this._nodePageFetches = {}; + + this._computeGeometryDefinitions(true); +} + +Object.defineProperties(I3SLayer.prototype, { + /** + * Gets the uri for the layer. + * @memberof I3SLayer.prototype + * @type {string} + */ + uri: { + get: function () { + return this._uri; + }, + }, + /** + * Gets the complete uri for the layer. + * @memberof I3SLayer.prototype + * @type {string} + */ + completeUri: { + get: function () { + return this._completeUri; + }, + }, + /** + * Gets the root node of this layer. + * @memberof I3SLayer.prototype + * @type {I3SNode} + */ + rootNode: { + get: function () { + return this._rootNode; + }, + }, + /** + * Gets the Cesium3DTileSet for this layer. + * @memberof I3SLayer.prototype + * @type {I3SNode} + */ + tileset: { + get: function () { + return this._tileset; + }, + }, + /** + * Gets the nodes for this layer. + * @memberof I3SLayer.prototype + * @type {I3SNode} + */ + nodes: { + get: function () { + return this._nodes; + }, + }, + /** + * Gets the I3S data for this object. + * @memberof I3SLayer.prototype + * @type {Object} + */ + data: { + get: function () { + return this._data; + }, + }, +}); + +/** + * Gets the list of egm files required for a given extent + * @returns The list of required urls to load + */ + +function GetCoveredTiles(terrainProvider, extents) { + return terrainProvider.readyPromise.then(function () { + return GetTiles(terrainProvider, extents); + }); +} + +function GetTiles(terrainProvider, extents) { + let tilingScheme = terrainProvider.tilingScheme; + + // Sort points into a set of tiles + let tileRequests = []; // Result will be an Array as it's easier to work with + let tileRequestSet = {}; // A unique set + + let maxLevel = terrainProvider._lodCount; + + let minCorner = Cartographic.fromDegrees( + extents.minLongitude, + extents.minLatitude + ); + let maxCorner = Cartographic.fromDegrees( + extents.maxLongitude, + extents.maxLatitude + ); + let minCornerXY = tilingScheme.positionToTileXY(minCorner, maxLevel); + let maxCornerXY = tilingScheme.positionToTileXY(maxCorner, maxLevel); + + //Get all the tiles in between + for (let x = minCornerXY.x; x <= maxCornerXY.x; x++) { + for (let y = minCornerXY.y; y <= maxCornerXY.y; y++) { + let xy = new Cartesian2(x, y); + let key = xy.toString(); + if (!tileRequestSet.hasOwnProperty(key)) { + // When tile is requested for the first time + let value = { + x: xy.x, + y: xy.y, + level: maxLevel, + tilingScheme: tilingScheme, + terrainProvider: terrainProvider, + positions: [], + }; + tileRequestSet[key] = value; + tileRequests.push(value); + } + } + } + + // Send request for each required tile + var tilePromises = []; + for (let i = 0; i < tileRequests.length; ++i) { + var tileRequest = tileRequests[i]; + var requestPromise = tileRequest.terrainProvider.requestTileGeometry( + tileRequest.x, + tileRequest.y, + tileRequest.level + ); + + tilePromises.push(requestPromise); + } + + return when.all(tilePromises).then((heightMapBuffers) => { + let heightMaps = []; + let tilesAreReady = new Array(); + for (let i in heightMapBuffers) { + let options = { + tilingScheme: tilingScheme, + x: tileRequests[i].x, + y: tileRequests[i].y, + level: tileRequests[i].level, + }; + let heightMap = heightMapBuffers[i]; + + let projectionType = "Geographic"; + if (tilingScheme._projection instanceof WebMercatorProjection) { + projectionType = "WebMercator"; + } + + let heightMapData = { + projectionType: projectionType, + projection: tilingScheme._projection, + nativeExtent: tilingScheme.tileXYToNativeRectangle( + options.x, + options.y, + options.level + ), + height: heightMap._height, + width: heightMap._width, + scale: heightMap._structure.heightScale, + offset: heightMap._structure.heightOffset, + }; + + if (heightMap._encoding == HeightmapEncoding.LERC) { + let result = Lerc.decode(heightMap._buffer); + heightMapData.buffer = result.pixels[0]; + } else { + heightMapData.buffer = heightMap._buffer; + } + + heightMaps.push(heightMapData); + } + + return heightMaps; + }); +} + +/** + * Loads the content, including the root node definition and its children + * @returns {Promise} a promise that is resolved when the layer data is loaded + */ +I3SLayer.prototype.load = function () { + return new _Promise((resolve, reject) => { + this._computeExtent(); + + //Load tiles from arcgis + + var geoidTerrainProvider = this._dataSource._geoidTiledTerrainProvider; + + let dataIsReady = new when.defer(); + let geoidDataList = []; + if (defined(geoidTerrainProvider)) { + if (geoidTerrainProvider.ready) { + let tilesReadyPromise = GetCoveredTiles( + geoidTerrainProvider, + this._extent + ); + when(tilesReadyPromise, function (heightMaps) { + geoidDataList = heightMaps; + dataIsReady.resolve(); + }); + } else { + console.log( + "Geoid Terrain service not available - no geoid conversion will be performed." + ); + dataIsReady.resolve(); + } + } else { + console.log( + "No Geoid Terrain service provided - no geoid conversion will be performed." + ); + dataIsReady.resolve(); + } + + dataIsReady.then(() => { + this._dataSource._geoidDataList = geoidDataList; + console.log("Starting to load visual elements"); + this._createVisualElements(); + if (this._data.spatialReference.wkid === 4326) { + this._loadNodePage(0).then(() => { + this._loadRootNode().then(() => { + this._create3DTileSet(); + if (this._data.store.version === "1.6") { + this._rootNode._loadChildren().then(() => { + resolve(); + }); + } else { + resolve(); + } + }); + }); + } else { + console.log( + "Unsupported spatial reference: " + this._data.spatialReference.wkid + ); + resolve(); + } + }); + }); +}; + +/** + * @private + */ +I3SLayer.prototype._computeGeometryDefinitions = function (useCompression) { + // create a table of all geometry buffers based on + // the number of attributes and whether they are + // compressed or not, sort them by priority + + this._geometryDefinitions = []; + + if (this._data.geometryDefinitions) { + for (let geometryDefinition of this._data.geometryDefinitions) { + let geometryBuffersInfo = []; + let geometryBuffers = geometryDefinition.geometryBuffers; + + for (let geometryBuffer of geometryBuffers) { + let collectedAttributes = []; + let compressed = false; + + if (geometryBuffer.compressedAttributes && useCompression) { + // check if compressed + compressed = true; + let attributes = geometryBuffer.compressedAttributes.attributes; + for (let attribute of attributes) { + collectedAttributes.push(attribute); + } + } else { + // uncompressed attributes + for (let attribute in geometryBuffer) { + if (attribute !== "offset") { + collectedAttributes.push(attribute); + } + } + } + + geometryBuffersInfo.push({ + compressed: compressed, + attributes: collectedAttributes, + index: geometryBuffers.indexOf(geometryBuffer), + }); + } + + // rank the buffer info + geometryBuffersInfo.sort((a, b) => { + if (a.compressed && !b.compressed) { + return -1; + } else if (!a.compressed && b.compressed) { + return 1; + } + return a.attributes.length - b.attributes.length; + }); + this._geometryDefinitions.push(geometryBuffersInfo); + } + } +}; + +/** + * @private + */ +I3SLayer.prototype._findBestGeometryBuffers = function ( + definition, + attributes +) { + // find the most appropriate geometry definition + // based on the required attributes, and by favouring + // compression to improve bandwidth requirements + + let geometryDefinition = this._geometryDefinitions[definition]; + + if (geometryDefinition) { + for (let index = 0; index < geometryDefinition.length; ++index) { + let geometryBufferInfo = geometryDefinition[index]; + let missed = false; + let geometryAttributes = geometryBufferInfo.attributes; + for (let attribute of attributes) { + if (!geometryAttributes.includes(attribute)) { + missed = true; + break; + } + } + if (!missed) { + return { + bufferIndex: geometryBufferInfo.index, + definition: geometryDefinition, + geometryBufferInfo: geometryBufferInfo, + }; + } + } + } + + return 0; +}; + +/** + * @private + */ +I3SLayer.prototype._loadRootNode = function () { + if (this._data.nodePages) { + let rootIndex = 0; + if (this._data.nodePages.rootIndex !== undefined) { + rootIndex = this._data.nodePages.rootIndex; + } + this._rootNode = new I3SNode(this, rootIndex); + } else { + this._rootNode = new I3SNode(this, this._data.store.rootNode); + } + + return this._rootNode.load(true); +}; + +/** + * @private + */ +I3SLayer.prototype._getNodeInNodePages = function (nodeIndex) { + let Index = Math.floor(nodeIndex / this._data.nodePages.nodesPerPage); + let offsetInPage = nodeIndex % this._data.nodePages.nodesPerPage; + let that = this; + return new _Promise((resolve, reject) => { + that._loadNodePage(Index).then(() => { + resolve(that._nodePages[Index][offsetInPage]); + }); + }); +}; + +/** + * @private + */ +I3SLayer.prototype._loadNodePage = function (page) { + let that = this; + return new _Promise((resolve, reject) => { + if (that._nodePages[page] !== undefined) { + resolve(); + } else if (that._nodePageFetches[page] !== undefined) { + that._nodePageFetches[page]._promise = that._nodePageFetches[ + page + ]._promise.then(() => { + resolve(); + }); + } else { + let query = ""; + if (this._dataSource._query && this._dataSource._query !== "") { + query = "?" + this._dataSource._query; + } + + let nodePageURI = this._completeUriWithoutQuery + "/nodepages/"; + nodePageURI += page + query; + + that._nodePageFetches[page] = {}; + that._nodePageFetches[page]._promise = new _Promise((resolve, reject) => { + that._nodePageFetches[page]._resolve = resolve; + }); + + let _resolve = function () { + // resolve the chain of promises + that._nodePageFetches[page]._resolve(); + delete that._nodePageFetches[page]; + resolve(); + }; + + fetch(nodePageURI) + .then((response) => { + response + .json() + .then((data) => { + if (data.error && data.error.code !== 200) { + _resolve(); + } else { + that._nodePages[page] = data.nodes; + _resolve(); + } + }) + .catch(() => { + _resolve(); + }); + }) + .catch(() => { + _resolve(); + }); + } + }); +}; + +/** + * @private + */ +I3SLayer.prototype._computeExtent = function () { + let layerExtent = this._data.store.extent; + this._extent = _computeExtent( + layerExtent[0], + layerExtent[1], + layerExtent[2], + layerExtent[3] + ); +}; + +/** + * @private + */ +I3SLayer.prototype._createVisualElements = function () { + if (!this._dataSource._traceVisuals) { + return; + } + // Add an entity for display + this._entities.extentOutline = this._dataSource.entities.add({ + name: "Extent", + position: _WGS84ToCartesian( + this._extent.centerLongitude, + this._extent.centerLatitude, + 0.0 + ), + plane: { + plane: new Plane(Cartesian3.UNIT_Z, 0.0), + dimensions: new Cartesian2( + this._extent.longitudeSpan, + this._extent.latitudeSpan + ), + fill: false, + outline: true, + outlineColor: Color.YELLOW, + }, + }); +}; + +/** + * @private + */ +I3SLayer.prototype._create3DTileSet = function () { + let inPlaceTileset = { + asset: { + version: "1.0", + }, + geometricError: Number.MAX_VALUE, + root: this._rootNode._create3DTileDefinition(), + }; + + let tilesetBlob = new Blob([JSON.stringify(inPlaceTileset)], { + type: "application/json", + }); + + let inPlaceTilesetURL = URL.createObjectURL(tilesetBlob); + + this._tileset = this._dataSource._scene.primitives.add( + new Cesium3DTileset({ + url: inPlaceTilesetURL, + debugShowBoundingVolume: this._dataSource._traceVisuals, + skipLevelOfDetail: true, + }) + ); + + this._tileset._isI3STileSet = true; + + this._tileset.readyPromise.then(() => { + this._tileset.tileLoad.addEventListener((tile) => {}); + + this._tileset.tileUnload.addEventListener((tile) => { + tile._i3sNode._clearGeometryData(); + tile._contentResource._url = tile._i3sNode._completeUriWithoutQuery; + }); + + this._tileset.tileVisible.addEventListener((tile) => { + if (tile._i3sNode) { + tile._i3sNode._loadChildren(); + } + }); + }); +}; + +/* +.##....##..#######..########..######## +.###...##.##.....##.##.....##.##...... +.####..##.##.....##.##.....##.##...... +.##.##.##.##.....##.##.....##.######.. +.##..####.##.....##.##.....##.##...... +.##...###.##.....##.##.....##.##...... +.##....##..#######..########..######## +*/ + +/** + * This class implements an I3S Node, in Cesium, each I3SNode + * creates a Cesium3DTile + * @alias I3SNode + * @constructor + * @param {I3SLayer|I3SNode} [parent] The parent of that node + * @param {String|Number} [ref] The uri or nodepage index to load the data from + */ +function I3SNode(parent, ref) { + this._parent = parent; + this._dataSource = parent._dataSource; + + if (this._parent instanceof I3SNode) { + this._level = this._parent._level + 1; + } else { + this._level = 0; + } + + // attach the current layer + if (this._parent instanceof I3SLayer) { + this._layer = this._parent; + } else { + this._layer = this._parent._layer; + } + + if (this._level === 0) { + if (typeof ref === "number") { + this._nodeIndex = ref; + this._uri = "./nodes/" + ref; + } else { + this._uri = ref; + } + } else if (typeof ref === "number") { + this._nodeIndex = ref; + this._uri = "../" + ref; + } else { + this._uri = ref; + } + + let query = ""; + if (this._dataSource._query && this._dataSource._query !== "") { + query = "?" + this._dataSource._query; + } + + this._completeUriWithoutQuery = + this._parent._completeUriWithoutQuery + "/" + this._uri; + this._completeUri = this._completeUriWithoutQuery + query; + + this._entities = {}; + this._tile = null; + this._geometryData = []; + this._featureData = []; + this._fields = {}; + this._children = []; +} + +Object.defineProperties(I3SNode.prototype, { + /** + * Gets the uri for the node. + * @memberof I3SNode.prototype + * @type {string} + */ + uri: { + get: function () { + return this._uri; + }, + }, + /** + * Gets the complete uri for the node. + * @memberof I3SNode.prototype + * @type {string} + */ + completeUri: { + get: function () { + return this._completeUri; + }, + }, + /** + * Gets the parent layer + * @memberof I3SNode.prototype + * @type {Object} + */ + layer: { + get: function () { + return this._layer; + }, + }, + /** + * Gets the parent node + * @memberof I3SNode.prototype + * @type {Object} + */ + parent: { + get: function () { + return this._parent; + }, + }, + /** + * Gets the children nodes + * @memberof I3SNode.prototype + * @type {Array} + */ + children: { + get: function () { + return this._children; + }, + }, + /** + * Gets the collection of geometries + * @memberof I3SNode.prototype + * @type {Array} + */ + geometryData: { + get: function () { + return this._geometryData; + }, + }, + /** + * Gets the collection of features + * @memberof I3SNode.prototype + * @type {Array} + */ + featureData: { + get: function () { + return this._featureData; + }, + }, + /** + * Gets the collection of fields + * @memberof I3SNode.prototype + * @type {Array} + */ + fields: { + get: function () { + return this._fields; + }, + }, + /** + * Gets the Cesium3DTileSet for this layer. + * @memberof I3SNode.prototype + * @type {I3SNode} + */ + tile: { + get: function () { + return this._tile; + }, + }, + /** + * Gets the I3S data for this object. + * @memberof I3SNode.prototype + * @type {Object} + */ + data: { + get: function () { + return this._data; + }, + }, +}); + +/** + * Loads the node definition. + * @returns {Promise} a promise that is resolved when the I3S Node data is loaded + */ +I3SNode.prototype.load = function (isRoot) { + let that = this; + + function processData() { + that._createVisualElements(); + if (!isRoot) { + // Create a new tile + let tileDefinition = that._create3DTileDefinition(); + + let tileBlob = new Blob([JSON.stringify(tileDefinition)], { + type: "application/json", + }); + + let inPlaceTileURL = URL.createObjectURL(tileBlob); + let resource = Resource.createIfNeeded(inPlaceTileURL); + + that._tile = new Cesium3DTile( + that._layer._tileset, + resource, + tileDefinition, + that._parent._tile + ); + } + } + + // if we don't have a nodepage index load from json + if (this._nodeIndex === undefined) { + return this._dataSource._loadJson( + this._completeUri, + (data, resolve) => { + // Success + that._data = data; + processData(); + resolve(); + }, + (reject) => { + // Fail + reject(); + } + ); + } + + return new _Promise((resolve, reject) => { + this._layer._getNodeInNodePages(this._nodeIndex).then((data) => { + that._data = data; + processData(); + resolve(); + }); + }); +}; + +/** + * Loads the node fields. + * @returns {Promise} a promise that is resolved when the I3S Node fields are loaded + */ +I3SNode.prototype.loadFields = function () { + // check if we must load fields + let fields = this._layer._data.attributeStorageInfo; + let fieldPromiseReady = when.defer(); + + let that = this; + function createAndLoadField(fields, index) { + if (!fields || index >= fields.length) { + return fieldPromiseReady.resolve(); + } + + let newField = new I3SField(that, fields[index]); + that._fields[newField._storageInfo.name] = newField; + newField.load().then(() => { + createAndLoadField(fields, index + 1); + }); + } + + createAndLoadField(fields, 0); + return fieldPromiseReady; +}; + +/** + * @private + */ +I3SNode.prototype._loadChildren = function (waitAllChildren) { + return new _Promise((resolve, reject) => { + if (!this._childrenAreLoaded) { + this._childrenAreLoaded = true; + let childPromises = []; + if (this._data.children) { + for (let child of this._data.children) { + let newChild = new I3SNode(this, child.href ? child.href : child); + this._children.push(newChild); + let childIsLoaded = newChild.load(); + if (waitAllChildren) { + childPromises.push(childIsLoaded); + } + childIsLoaded.then(() => { + this._tile.children.push(newChild._tile); + }); + } + if (waitAllChildren) { + _Promise.all(childPromises).then(() => { + resolve(); + }); + } else { + resolve(); + } + } else { + resolve(); + } + } else { + resolve(); + } + }); +}; + +/** + * @private + */ +I3SNode.prototype._loadGeometryData = function () { + let geometryPromises = []; + + // To debug decoding for a specific tile, add a condition + // that wraps this if/else to match the tile uri + if (this._data.geometryData) { + for (let geometry of this._data.geometryData) { + let newGeometryData = new I3SGeometry(this, geometry.href); + this._geometryData.push(newGeometryData); + geometryPromises.push(newGeometryData.load()); + } + } else if (this._data.mesh) { + let geometryDefinition = this._layer._findBestGeometryBuffers( + this._data.mesh.geometry.definition, + ["position", "uv0"] + ); + + let geometryURI = "./geometries/" + geometryDefinition.bufferIndex; + let newGeometryData = new I3SGeometry(this, geometryURI); + newGeometryData._geometryDefinitions = geometryDefinition.definition; + newGeometryData._geometryBufferInfo = geometryDefinition.geometryBufferInfo; + this._geometryData.push(newGeometryData); + geometryPromises.push(newGeometryData.load()); + } + + return _Promise.all(geometryPromises); +}; + +/** + * @private + */ +I3SNode.prototype._loadFeatureData = function () { + let featurePromises = []; + + // To debug decoding for a specific tile, add a condition + // that wraps this if/else to match the tile uri + if (this._data.featureData) { + for (let feature of this._data.featureData) { + let newfeatureData = new I3SFeature(this, feature.href); + this._featureData.push(newfeatureData); + featurePromises.push(newfeatureData.load()); + } + } + + return _Promise.all(featurePromises); +}; + +/** + * @private + */ +I3SNode.prototype._clearGeometryData = function () { + this._geometryData = []; +}; + +/** + * @private + */ +I3SNode.prototype._create3DTileDefinition = function () { + let obb = this._data.obb; + let mbs = this._data.mbs; + + let boundingVolume = {}; + let position; + + if (obb) { + boundingVolume = { + box: [ + 0, + 0, + 0, + obb.halfSize[0], + 0, + 0, + 0, + obb.halfSize[1], + 0, + 0, + 0, + obb.halfSize[2], + ], + }; + position = _WGS84ToCartesian(obb.center[0], obb.center[1], obb.center[2]); + } else if (mbs) { + boundingVolume = { + sphere: [0, 0, 0, mbs[3]], + }; + position = _WGS84ToCartesian(mbs[0], mbs[1], mbs[2]); + } else { + console.error(this); + } + + // compute the geometric error + let metersPerPixel = Infinity; + + let span = 0; + if (this._data.mbs) { + span = this._data.mbs[3]; + } else if (this._data.obb) { + span = Math.max( + Math.max(this._data.obb.halfSize[0], this._data.obb.halfSize[1]), + this._data.obb.halfSize[2] + ); + } + + // get the meters/pixel density required to pop the next LOD + if (this._data.lodThreshold !== undefined) { + if ( + this._layer._data.nodePages.lodSelectionMetricType === + "maxScreenThresholdSQ" + ) { + let maxScreenThreshold = + Math.sqrt(this._data.lodThreshold) / (Math.PI * 0.25); + metersPerPixel = span / maxScreenThreshold; + } else { + console.error("Unsupported lodSelectionMetricType in Layer"); + } + } else if (this._data.lodSelection !== undefined) { + for (let lodSelection of this._data.lodSelection) { + if (lodSelection.metricType === "maxScreenThreshold") { + metersPerPixel = span / lodSelection.maxError; + } + } + } + + if (metersPerPixel === Infinity) { + metersPerPixel = 100000; + } + + // calculate the length of 16 pixels in order to trigger the screen space error + let geometricError = metersPerPixel * 16; + + // transformations + let hpr = new HeadingPitchRoll(0, 0, 0); + let orientation = Transforms.headingPitchRollQuaternion(position, hpr); + + if (this._data.obb) { + orientation = new Quaternion( + this._data.obb.quaternion[0], + this._data.obb.quaternion[1], + this._data.obb.quaternion[2], + this._data.obb.quaternion[3] + ); + } + + this._rotationMatrix = Matrix3.fromQuaternion(orientation); + this._inverseRotationMatrix = new Matrix3(); + Matrix3.inverse(this._rotationMatrix, this._inverseRotationMatrix); + + this._globalTransforms = new Matrix4( + this._rotationMatrix[0], + this._rotationMatrix[1], + this._rotationMatrix[2], + 0, + this._rotationMatrix[3], + this._rotationMatrix[4], + this._rotationMatrix[5], + 0, + this._rotationMatrix[6], + this._rotationMatrix[7], + this._rotationMatrix[8], + 0, + position.x, + position.y, + position.z, + 1 + ); + + this.inverseGlobalTransform = new Matrix4(); + Matrix4.inverse(this._globalTransforms, this.inverseGlobalTransform); + + let localTransforms = this._globalTransforms.clone(); + + if (this._parent._globalTransforms) { + Matrix4.multiply( + this._globalTransforms, + this._parent.inverseGlobalTransform, + localTransforms + ); + } + + // get children definition + let childrenDefinition = []; + for (let child of this._children) { + childrenDefinition.push(child._create3DTileDefinition()); + } + + // Create a tile set + let inPlaceTileDefinition = { + children: childrenDefinition, + refine: "REPLACE", + boundingVolume: boundingVolume, + transform: [ + localTransforms[0], + localTransforms[4], + localTransforms[8], + localTransforms[12], + localTransforms[1], + localTransforms[5], + localTransforms[9], + localTransforms[13], + localTransforms[2], + localTransforms[6], + localTransforms[10], + localTransforms[14], + localTransforms[3], + localTransforms[7], + localTransforms[11], + localTransforms[15], + ], + content: { + uri: this._completeUriWithoutQuery, + }, + geometricError: geometricError, + }; + + // Add an entry in the data source content cache + _i3sContentCache[this._completeUriWithoutQuery] = { + i3sNode: this, + }; + + return inPlaceTileDefinition; +}; + +/** + * @private + */ +I3SNode.prototype._scheduleCreateContentURL = function () { + let that = this; + return new _Promise((resolve, reject) => { + that._createContentURL(resolve, that._tile); + }); +}; + +/** + * @private + */ +I3SNode.prototype._createContentURL = function (resolve, tile) { + let rawGLTF = { + scene: 0, + scenes: [ + { + nodes: [0], + }, + ], + nodes: [ + { + name: "singleNode", + }, + ], + meshes: [], + buffers: [], + bufferViews: [], + accessors: [], + materials: [], + textures: [], + images: [], + samplers: [], + asset: { + version: "2.0", + }, + }; + + // Feature Table + let featureTableJSON = JSON.stringify({ BATCH_LENGTH: 0 }); + + // Batch Table + let batchTableJSON = JSON.stringify({}); + + // Load the geometry data + let dataPromises = [this._loadFeatureData(), this._loadGeometryData()]; + + _Promise.all(dataPromises).then(() => { + // Binary GLTF + let generateGLTF = new _Promise((resolve, reject) => { + if (this._geometryData && this._geometryData.length > 0) { + let task = this._dataSource._GLTFProcessingQueue.addTask({ + geometryData: this._geometryData[0], + featureData: this._featureData, + defaultGeometrySchema: this._layer._data.store.defaultGeometrySchema, + url: this._geometryData[0]._completeUri, + tile: this._tile, + }); + task.then((data) => { + rawGLTF = data.gltfData; + this._geometryData[0].customAttributes = data.customAttributes; + resolve(); + }); + } else { + resolve(); + } + }); + + generateGLTF.then(() => { + let binaryGLTFData = this._dataSource._binarizeGLTF(rawGLTF); + let b3dmRawData = this._dataSource._binarizeB3DM( + featureTableJSON, + batchTableJSON, + binaryGLTFData + ); + let b3dmDataBlob = new Blob([b3dmRawData], { + type: "application/binary", + }); + this._b3dmURL = URL.createObjectURL(b3dmDataBlob); + resolve(); + }); + }); +}; + +/** + * @private + */ +I3SNode.prototype._createVisualElements = function () { + if (!this._dataSource._traceVisuals) { + return; + } + let obb = this._data.obb; + let mbs = this._data.mbs; + + if (obb) { + // Add an entity for display + let orientation = new Quaternion( + obb.quaternion[0], + obb.quaternion[1], + obb.quaternion[2], + obb.quaternion[3] + ); + + let position = _WGS84ToCartesian( + obb.center[0], + obb.center[1], + obb.center[2] + ); + + this._entities.locator = this._dataSource.entities.add({ + name: "Extent", + position: position, + orientation: orientation, + box: { + dimensions: new Cartesian3( + obb.halfSize[0] * 2, + obb.halfSize[1] * 2, + obb.halfSize[2] * 2 + ), + fill: false, + outline: true, + outlineColor: Color.ORANGE, + }, + }); + } else if (mbs) { + let position = _WGS84ToCartesian(mbs[0], mbs[1], mbs[2]); + + // Add an entity for display + this._entities.locator = this._dataSource.entities.add({ + name: "Extent", + position: position, + ellipse: { + semiMinorAxis: mbs[3], + semiMajorAxis: mbs[3], + fill: false, + outline: true, + outlineColor: Color.ORANGE, + }, + }); + } +}; + +/* +.########.########....###....########.##.....##.########..######## +.##.......##.........##.##......##....##.....##.##.....##.##...... +.##.......##........##...##.....##....##.....##.##.....##.##...... +.######...######...##.....##....##....##.....##.########..######.. +.##.......##.......#########....##....##.....##.##...##...##...... +.##.......##.......##.....##....##....##.....##.##....##..##...... +.##.......########.##.....##....##.....#######..##.....##.######## +*/ + +/** + * This class implements an I3S Feature + * @alias I3SFeature + * @constructor + * @param {I3SNode} [parent] The parent of that feature + * @param {String} [uri] The uri to load the data from + */ +function I3SFeature(parent, uri) { + this._parent = parent; + this._dataSource = parent._dataSource; + this._layer = parent._layer; + this._uri = uri; + let query = ""; + if (this._dataSource._query && this._dataSource._query !== "") { + query = "?" + this._dataSource._query; + } + + this._completeUriWithoutQuery = + this._parent._completeUriWithoutQuery + "/" + this._uri; + this._completeUri = this._completeUriWithoutQuery + query; +} + +Object.defineProperties(I3SFeature.prototype, { + /** + * Gets the uri for the feature. + * @memberof I3SFeature.prototype + * @type {string} + */ + uri: { + get: function () { + return this._uri; + }, + }, + /** + * Gets the complete uri for the feature. + * @memberof I3SFeature.prototype + * @type {string} + */ + completeUri: { + get: function () { + return this._completeUri; + }, + }, + /** + * Gets the I3S data for this object. + * @memberof I3SFeature.prototype + * @type {Object} + */ + data: { + get: function () { + return this._data; + }, + }, +}); + +/** + * Loads the content. + * @returns {Promise} a promise that is resolved when the data of the I3S feature is loaded + */ +I3SFeature.prototype.load = function () { + return this._dataSource._loadJson( + this._completeUri, + (data, resolve) => { + this._data = data; + resolve(); + }, + (reject) => { + reject(); + } + ); +}; + +/* +.########.####.########.##.......########. +.##........##..##.......##.......##.....## +.##........##..##.......##.......##.....## +.######....##..######...##.......##.....## +.##........##..##.......##.......##.....## +.##........##..##.......##.......##.....## +.##.......####.########.########.########. +*/ +/** + * This class implements an I3S Field which is custom data attachec + * to nodes + * @alias I3SField + * @constructor + * @param {I3SNode} [parent] The parent of that geometry + * @param {Object} [storageInfo] The structure containing the storage info of the field + */ +function I3SField(parent, storageInfo) { + this._storageInfo = storageInfo; + this._parent = parent; + this._dataSource = parent._dataSource; + this._uri = "/attributes/" + storageInfo.key + "/0"; + let query = ""; + if (this._dataSource._query && this._dataSource._query !== "") { + query = "?" + this._dataSource._query; + } + + this._completeUriWithoutQuery = + this._parent._completeUriWithoutQuery + this._uri; + this._completeUri = this._completeUriWithoutQuery + query; +} + +Object.defineProperties(I3SField.prototype, { + /** + * Gets the uri for the field. + * @memberof I3SField.prototype + * @type {string} + */ + uri: { + get: function () { + return this._uri; + }, + }, + /** + * Gets the complete uri for the field. + * @memberof I3SField.prototype + * @type {string} + */ + completeUri: { + get: function () { + return this._completeUri; + }, + }, + /** + * Gets the header for this field. + * @memberof I3SField.prototype + * @type {Object} + */ + header: { + get: function () { + return this._header; + }, + }, + /** + * Gets the values for this field. + * @memberof I3SField.prototype + * @type {Object} + */ + values: { + get: function () { + return this._values && this._values.attributeValues + ? this._values.attributeValues + : []; + }, + }, + /** + * Gets the name for the field. + * @memberof I3SField.prototype + * @type {string} + */ + name: { + get: function () { + return this._storageInfo.name; + }, + }, +}); + +/** + * Loads the content. + * @returns {Promise} a promise that is resolved when the geometry data is loaded + */ +I3SField.prototype.load = function () { + return this._dataSource._loadBinary( + this._completeUri, + (data, resolve) => { + // check if we have a 404 + let dataView = new DataView(data); + let success = true; + if (dataView.getUint8(0) === "{".charCodeAt(0)) { + let textContent = new TextDecoder(); + let str = textContent.decode(data); + if (str.includes("404")) { + success = false; + console.error("Failed to load:", this._completeUri); + } + } + + if (success) { + this._data = data; + let offset = this._parseHeader(dataView); + + // @TODO: find out why we must skip 4 bytes when the value type is float 64 + if ( + this._storageInfo && + this._storageInfo.attributeValues && + this._storageInfo.attributeValues.valueType === "Float64" + ) { + offset += 4; + } + + this._parseBody(dataView, offset); + } + + resolve(); + }, + (reject) => { + reject(); + } + ); +}; + +/** + * @private + */ +I3SField.prototype._parseValue = function (dataView, type, offset) { + let value = null; + if (type === "UInt8") { + value = dataView.getUint8(offset); + offset += 1; + } else if (type === "Int8") { + value = dataView.getInt8(offset); + offset += 1; + } else if (type === "UInt16") { + value = dataView.getUint16(offset, true); + offset += 2; + } else if (type === "Int16") { + value = dataView.getInt16(offset, true); + offset += 2; + } else if (type === "UInt32") { + value = dataView.getUint32(offset, true); + offset += 4; + } else if (type === "Oid32") { + value = dataView.getUint32(offset, true); + offset += 4; + } else if (type === "Int32") { + value = dataView.getInt32(offset, true); + offset += 4; + } else if (type === "Float32") { + value = dataView.getFloat32(offset, true); + offset += 4; + } else if (type === "UInt64") { + value = dataView.getUint64(offset, true); + offset += 8; + } else if (type === "Int64") { + value = dataView.getInt64(offset, true); + offset += 8; + } else if (type === "Float64") { + value = dataView.getFloat64(offset, true); + offset += 8; + } else if (type === "String") { + value = String.fromCharCode(dataView.getUint8(offset)); + offset += 1; + } + + return { + value: value, + offset: offset, + }; +}; + +/** + * @private + */ +I3SField.prototype._parseHeader = function (dataView) { + let offset = 0; + this._header = {}; + for (let item of this._storageInfo.header) { + let parsedValue = this._parseValue(dataView, item.valueType, offset); + this._header[item.property] = parsedValue.value; + offset = parsedValue.offset; + } + return offset; +}; + +/** + * @private + */ +I3SField.prototype._parseBody = function (dataView, offset) { + this._values = {}; + for (let item of this._storageInfo.ordering) { + let desc = this._storageInfo[item]; + if (desc) { + this._values[item] = []; + for (let index = 0; index < this._header.count; ++index) { + if (desc.valueType !== "String") { + let parsedValue = this._parseValue(dataView, desc.valueType, offset); + this._values[item].push(parsedValue.value); + offset = parsedValue.offset; + } else { + let stringLen = this._values.attributeByteCounts[index]; + let stringContent = ""; + for (let cIndex = 0; cIndex < stringLen; ++cIndex) { + let parsedValue = this._parseValue( + dataView, + desc.valueType, + offset + ); + stringContent += parsedValue.value; + offset = parsedValue.offset; + } + this._values[item].push(stringContent); + } + } + } + } +}; + +/* +..######...########..#######..##.....##.########.########.########..##....## +.##....##..##.......##.....##.###...###.##..........##....##.....##..##..##. +.##........##.......##.....##.####.####.##..........##....##.....##...####.. +.##...####.######...##.....##.##.###.##.######......##....########.....##... +.##....##..##.......##.....##.##.....##.##..........##....##...##......##... +.##....##..##.......##.....##.##.....##.##..........##....##....##.....##... +..######...########..#######..##.....##.########....##....##.....##....##... +*/ + +/** + * This class implements an I3S Geometry, in Cesium, each I3SGeometry + * generates an in memory b3dm so be used as content for a Cesium3DTile + * @alias I3SGeometry + * @constructor + * @param {I3SNode} [parent] The parent of that geometry + * @param {String} [uri] The uri to load the data from + */ +function I3SGeometry(parent, uri) { + this._parent = parent; + this._dataSource = parent._dataSource; + this._layer = parent._layer; + this._uri = uri; + let query = ""; + if (this._dataSource._query && this._dataSource._query !== "") { + query = "?" + this._dataSource._query; + } + + if (this._parent._nodeIndex) { + this._completeUriWithoutQuery = + this._layer._completeUriWithoutQuery + + "/nodes/" + + this._parent._data.mesh.geometry.resource + + "/" + + this._uri; + this._completeUri = this._completeUriWithoutQuery + query; + } else { + this._completeUriWithoutQuery = + this._parent._completeUriWithoutQuery + "/" + this._uri; + this._completeUri = this._completeUriWithoutQuery + query; + } +} + +Object.defineProperties(I3SGeometry.prototype, { + /** + * Gets the uri for the geometry. + * @memberof I3SGeometry.prototype + * @type {string} + */ + uri: { + get: function () { + return this._uri; + }, + }, + /** + * Gets the complete uri for the geometry. + * @memberof I3SGeometry.prototype + * @type {string} + */ + completeUri: { + get: function () { + return this._completeUri; + }, + }, + /** + * Gets the I3S data for this object. + * @memberof I3SGeometry.prototype + * @type {Object} + */ + data: { + get: function () { + return this._data; + }, + }, +}); + +/** + * Loads the content. + * @returns {Promise} a promise that is resolved when the geometry data is loaded + */ +I3SGeometry.prototype.load = function () { + return this._dataSource._loadBinary( + this._completeUri, + (data, resolve) => { + this._data = data; + resolve(); + }, + (reject) => { + reject(); + } + ); +}; + +/** + * Gets the closest point to px,py,pz in this geometry + * @param {float} [px] the x component of the point to query + * @param {float} [py] the y component of the point to query + * @param {float} [pz] the z component of the point to query + * @returns {object} a structure containing the index of the closest point, + * the squared distance from the queried point to the point that is found + * the distance from the queried point to the point that is found + * the queried position in local space + * the closest position in local space + */ +I3SGeometry.prototype.getClosestPointIndex = function (px, py, pz) { + if (this.customAttributes && this.customAttributes.positions) { + let transformation = new Matrix4(); + transformation = Matrix4.inverse( + this._parent._tile.computedTransform, + transformation + ); + + // convert queried position to local + let position = new Cartesian4(px, py, pz, 1); + position = Matrix4.multiplyByVector(transformation, position, position); + + // Brute force lookup, @TODO: this can be improved with a spatial partitioning search system + let count = this.customAttributes.positions.length; + let bestIndex = -1; + let bestDistanceSquared = Number.MAX_VALUE; + let bestX, bestY, bestZ; + let x, y, z, distanceSquared; + let positions = this.customAttributes.positions; + for (let loop = 0; loop < count; loop += 3) { + x = positions[loop] - position.x; + y = positions[loop + 1] - position.y; + z = positions[loop + 2] - position.z; + + distanceSquared = x * x + y * y + z * z; + if (bestDistanceSquared > distanceSquared) { + bestDistanceSquared = distanceSquared; + bestIndex = loop / 3; + bestX = positions[loop]; + bestY = positions[loop + 1]; + bestZ = positions[loop + 2]; + } + } + + return { + index: bestIndex, + distanceSquared: bestDistanceSquared, + distance: Math.sqrt(bestDistanceSquared), + queriedPosition: { + x: position.x, + y: position.y, + z: position.z, + }, + closestPosition: { + x: bestX, + y: bestY, + z: bestZ, + }, + }; + } + return { + index: -1, + distanceSquared: Number.Infinity, + distance: Number.Infinity, + }; +}; + +/** + * @private + */ +I3SGeometry.prototype._generateGLTF = function ( + nodesInScene, + nodes, + meshes, + buffers, + bufferViews, + accessors +) { + let query = ""; + if (this._dataSource._query && this._dataSource._query !== "") { + query = "?" + this._dataSource._query; + } + + // Get the material definition + let materialInfo = this._parent._data.mesh + ? this._parent._data.mesh.material + : null; + let materialIndex = 0; + let isTextured = false; + let gltfMaterial = { + pbrMetallicRoughness: { + metallicFactor: 0.0, + }, + doubleSided: true, + name: "Material", + }; + + if (materialInfo) { + materialIndex = materialInfo.definition; + } + + let materialDefinition; + if (this._layer._data.materialDefinitions) { + materialDefinition = this._layer._data.materialDefinitions[materialIndex]; + } + + if (materialDefinition) { + gltfMaterial = materialDefinition; + + // Textured. @TODO: extend to other textured cases + if ( + materialDefinition.pbrMetallicRoughness && + materialDefinition.pbrMetallicRoughness.baseColorTexture + ) { + isTextured = true; + } + } + + let texturePath; + + if (this._parent._data.textureData) { + texturePath = + this._parent._completeUriWithoutQuery + + "/" + + this._parent._data.textureData[0].href + + query; + } else { + // Choose the JPG for the texture + let textureName = "0"; + + if (this._layer._data.textureSetDefinitions) { + for (let textureSetDefinition of this._layer._data + .textureSetDefinitions) { + for (let textureFormat of textureSetDefinition.formats) { + if (textureFormat.format === "jpg") { + textureName = textureFormat.name; + break; + } + } + } + } + + if (this._parent._data.mesh) { + texturePath = + this._layer._completeUriWithoutQuery + + "/nodes/" + + this._parent._data.mesh.material.resource + + "/textures/" + + textureName + + query; + } + } + + let gltfTextures = []; + let gltfImages = []; + let gltfSamplers = []; + + if (isTextured) { + gltfTextures = [ + { + sampler: 0, + source: 0, + }, + ]; + + gltfImages = [ + { + uri: texturePath, + }, + ]; + + gltfSamplers = [ + { + magFilter: 9729, + minFilter: 9986, + wrapS: 10497, + wrapT: 10497, + }, + ]; + + gltfMaterial.pbrMetallicRoughness.baseColorTexture.index = 0; + } + + let gltfData = { + scene: 0, + scenes: [ + { + nodes: nodesInScene, + }, + ], + nodes: nodes, + meshes: meshes, + buffers: buffers, + bufferViews: bufferViews, + accessors: accessors, + materials: [gltfMaterial], + textures: gltfTextures, + images: gltfImages, + samplers: gltfSamplers, + asset: { + version: "2.0", + }, + }; + + return gltfData; +}; + +/* +..######..##.....##..######..########..#######..##.....## +.##....##.##.....##.##....##....##....##.....##.###...### +.##.......##.....##.##..........##....##.....##.####.#### +.##.......##.....##..######.....##....##.....##.##.###.## +.##.......##.....##.......##....##....##.....##.##.....## +.##....##.##.....##.##....##....##....##.....##.##.....## +..######...#######...######.....##.....#######..##.....## + +.##.....##..#######...#######..##....##..######. +.##.....##.##.....##.##.....##.##...##..##....## +.##.....##.##.....##.##.....##.##..##...##...... +.#########.##.....##.##.....##.#####.....######. +.##.....##.##.....##.##.....##.##..##.........## +.##.....##.##.....##.##.....##.##...##..##....## +.##.....##..#######...#######..##....##..######. +*/ + +// Reimplement Cesium3DTile.prototype.requestContent so that +// We get a chance to load our own b3dm from I3S data +Cesium3DTile.prototype._hookedRequestContent = + Cesium3DTile.prototype.requestContent; + +/** + * @private + */ +Cesium3DTile.prototype._resolveHookedObject = function () { + // Keep a handle on the early promises + let _contentReadyToProcessPromise = this._contentReadyToProcessPromise; + let _contentReadyPromise = this._contentReadyPromise; + + // Call the real requestContent function + this._hookedRequestContent(); + + // Fulfill the promises + if (_contentReadyToProcessPromise) { + this._contentReadyToProcessPromise.then(() => { + _contentReadyToProcessPromise.resolve(); + }); + } + + if (_contentReadyPromise) { + this._contentReadyPromise.then(() => { + this._isLoading = false; + this._content._contentReadyPromise.resolve(); + }); + } +}; + +Cesium3DTile.prototype.requestContent = function () { + if (!this.tileset._isI3STileSet) { + return this._hookedRequestContent(); + } + + if (!this._isLoading) { + this._isLoading = true; + + let key = this._contentResource._url; + if (this._contentResource._originalUrl) { + key = this._contentResource._originalUrl; + } + + if (key.startsWith("blob:")) { + key = key.slice(5); + } + + let content = _i3sContentCache[key]; + if (!content) { + console.error("invalid key", key, _i3sContentCache); + this._resolveHookedObject(); + } else { + // We load the I3S content + this._i3sNode = content.i3sNode; + this._i3sNode._tile = this; + + // Create early promises that will be fulfilled later + this._contentReadyToProcessPromise = when.defer(); + this._contentReadyPromise = when.defer(); + + this._i3sNode._scheduleCreateContentURL().then(() => { + if (!this._contentResource._originalUrl) { + this._contentResource._originalUrl = this._contentResource._url; + } + + this._contentResource._url = this._i3sNode._b3dmURL; + this._resolveHookedObject(); + }); + } + + // Returns the number of requests + return 0; + } + + return 1; +}; + +Object.defineProperties(Batched3DModel3DTileContent.prototype, { + /** + * Gets the I3S Node for the tile content. + * @memberof Batched3DModel3DTileContent.prototype + * @type {string} + */ + i3sNode: { + get: function () { + return this._tile._i3sNode; + }, + }, +}); + +/* +.##......##..#######..########..##....##.########.########. +.##..##..##.##.....##.##.....##.##...##..##.......##.....## +.##..##..##.##.....##.##.....##.##..##...##.......##.....## +.##..##..##.##.....##.########..#####....######...########. +.##..##..##.##.....##.##...##...##..##...##.......##...##.. +.##..##..##.##.....##.##....##..##...##..##.......##....##. +..###..###...#######..##.....##.##....##.########.##.....## + +..######...#######..########..######## +.##....##.##.....##.##.....##.##...... +.##.......##.....##.##.....##.##...... +.##.......##.....##.##.....##.######.. +.##.......##.....##.##.....##.##...... +.##....##.##.....##.##.....##.##...... +..######...#######..########..######## +*/ +function _workerCode() { + let _tracecode = false; + let traceCode = function () {}; + if (_tracecode) { + traceCode = console.log; + } + + // adapted from Ellipsoid.prototype.geodeticSurfaceNormalCartographic in Ellipsoid.js + function geodeticSurfaceNormalCartographic(cartographic, result) { + let longitude = cartographic.longitude; + let latitude = cartographic.latitude; + let cosLatitude = Math.cos(latitude); + + let x = cosLatitude * Math.cos(longitude); + let y = cosLatitude * Math.sin(longitude); + let z = Math.sin(latitude); + + // Normalize + let length = Math.sqrt(x * x + y * y + z * z); + result.x = x / length; + result.y = y / length; + result.z = z / length; + } + + // adapted from Ellipsoid.prototype.cartographicToCartesian in Ellipsoid.js + let n = { x: 0, y: 0, z: 0 }; + let k = { x: 0, y: 0, z: 0 }; + function cartographicToCartesian(cartographic, ellipsoidRadiiSquare, result) { + geodeticSurfaceNormalCartographic(cartographic, n); + k.x = ellipsoidRadiiSquare.x * n.x; + k.y = ellipsoidRadiiSquare.y * n.y; + k.z = ellipsoidRadiiSquare.z * n.z; + let gamma = Math.sqrt(n.x * k.x + n.y * k.y + n.z * k.z); + k.x /= gamma; + k.y /= gamma; + k.z /= gamma; + + n.x *= cartographic.height; + n.y *= cartographic.height; + n.z *= cartographic.height; + + result.x = k.x + n.x; + result.y = k.y + n.y; + result.z = k.z + n.z; + } + + // adapted from Matrix3.multiplyByVector in Matrix3.js + function multiplyByVector(matrix, cartesian, result) { + let vX = cartesian.x; + let vY = cartesian.y; + let vZ = cartesian.z; + + result.x = matrix[0] * vX + matrix[3] * vY + matrix[6] * vZ; + result.y = matrix[1] * vX + matrix[4] * vY + matrix[7] * vZ; + result.z = matrix[2] * vX + matrix[5] * vY + matrix[8] * vZ; + } + + const _degToRad = 0.017453292519943; + let cartographic = { + longitude: 0, + latitude: 0, + height: 0, + }; + + let position = { + x: 0, + y: 0, + z: 0, + }; + + let normal = { + x: 0, + y: 0, + z: 0, + }; + + let rotatedPosition = { + x: 0, + y: 0, + z: 0, + }; + + let rotatedNormal = { + x: 0, + y: 0, + z: 0, + }; + + /* + ..#######..########..########.##.....##..#######..##.....##.########.########.########..####..######. + .##.....##.##.....##....##....##.....##.##.....##.###...###.##..........##....##.....##..##..##....## + .##.....##.##.....##....##....##.....##.##.....##.####.####.##..........##....##.....##..##..##...... + .##.....##.########.....##....#########.##.....##.##.###.##.######......##....########...##..##...... + .##.....##.##...##......##....##.....##.##.....##.##.....##.##..........##....##...##....##..##...... + .##.....##.##....##.....##....##.....##.##.....##.##.....##.##..........##....##....##...##..##....## + ..#######..##.....##....##....##.....##..#######..##.....##.########....##....##.....##.####..######. + .########..#######. + ....##....##.....## + ....##....##.....## + ....##....##.....## + ....##....##.....## + ....##....##.....## + ....##.....#######. + .########.##.......##.......####.########...######...#######..####.########.....###....##...... + .##.......##.......##........##..##.....##.##....##.##.....##..##..##.....##...##.##...##...... + .##.......##.......##........##..##.....##.##.......##.....##..##..##.....##..##...##..##...... + .######...##.......##........##..########...######..##.....##..##..##.....##.##.....##.##...... + .##.......##.......##........##..##..............##.##.....##..##..##.....##.#########.##...... + .##.......##.......##........##..##........##....##.##.....##..##..##.....##.##.....##.##...... + .########.########.########.####.##.........######...#######..####.########..##.....##.######## + */ + + function mercatorAngleToGeodeticLatitude(mercatorAngle) { + return Math.PI / 2.0 - 2.0 * Math.atan(Math.exp(-mercatorAngle)); + } + + function geodeticLatitudeToMercatorAngle(latitude) { + let maximumLatitude = mercatorAngleToGeodeticLatitude(Math.PI); + + // Clamp the latitude coordinate to the valid Mercator bounds. + if (latitude > maximumLatitude) { + latitude = maximumLatitude; + } else if (latitude < -maximumLatitude) { + latitude = -maximumLatitude; + } + var sinLatitude = Math.sin(latitude); + return 0.5 * Math.log((1.0 + sinLatitude) / (1.0 - sinLatitude)); + } + + function geographicToWebMercator(lon, lat, ellipsoid) { + let semimajorAxis = ellipsoid._maximumRadius; + + var x = lon * semimajorAxis; + var y = geodeticLatitudeToMercatorAngle(lat) * semimajorAxis; + + let result = { x: x, y: y }; + return { x: x, y: y }; + } + + function bilinearInterpolate(tx, ty, h00, h10, h01, h11) { + let a = h00 * (1 - tx) + h10 * tx; + let b = h01 * (1 - tx) + h11 * tx; + return a * (1 - ty) + b * ty; + } + + function sampleMap(u, v, width, data) { + let address = u + v * width; + return data[address]; + } + + function sampleGeoid(sampleX, sampleY, geoidData) { + let extent = geoidData.nativeExtent; + let x = + ((sampleX - extent.west) / (extent.east - extent.west)) * + (geoidData.width - 1); + let y = + ((sampleY - extent.south) / (extent.north - extent.south)) * + (geoidData.height - 1); + let xi = Math.floor(x); + let yi = Math.floor(y); + + x -= xi; + y -= yi; + + xNext = xi < geoidData.width ? xi + 1 : xi; + yNext = yi < geoidData.height ? yi + 1 : yi; + + yi = geoidData.height - 1 - yi; + yNext = geoidData.height - 1 - yNext; + + let h00 = sampleMap(xi, yi, geoidData.width, geoidData.buffer); + let h10 = sampleMap(xNext, yi, geoidData.width, geoidData.buffer); + let h01 = sampleMap(xi, yNext, geoidData.width, geoidData.buffer); + let h11 = sampleMap(xNext, yNext, geoidData.width, geoidData.buffer); + + let finalHeight = bilinearInterpolate(x, y, h00, h10, h01, h11); + finalHeight = finalHeight * geoidData.scale + geoidData.offset; + return finalHeight; + } + + function sampleGeoidFromList(lon, lat, geoidDataList) { + for (let i in geoidDataList) { + let pt = { + longitude: lon, + latitude: lat, + height: 0, + }; + + let localExtent = geoidDataList[i].nativeExtent; + let lonRadian = (lon / 180) * Math.PI; + let latRadian = (lat / 180) * Math.PI; + + let localPt = {}; + if (geoidDataList[i].projectionType === "WebMercator") { + localPt = geographicToWebMercator( + lonRadian, + latRadian, + geoidDataList[i].projection._ellipsoid + ); + } else { + localPt.x = lonRadian; + localPt.y = latRadian; + } + + if ( + localPt.x > localExtent.west && + localPt.x < localExtent.east && + localPt.y > localExtent.south && + localPt.y < localExtent.north + ) { + return sampleGeoid(localPt.x, localPt.y, geoidDataList[i]); + } + } + + return 0; + } + + function orthometricToEllipsoidal( + vertexCount, + position, + scale_x, + scale_y, + center, + geoidDataList, + fast + ) { + // Fast conversion (using the center of the tile) + let height = sampleGeoidFromList(center.long, center.lat, geoidDataList); + + if (fast) { + for (let index = 0; index < vertexCount; ++index) { + position[index * 3 + 2] += height; + } + } else { + for (let index = 0; index < vertexCount; ++index) { + let height = sampleGeoidFromList( + center.long + scale_x * position[index * 3], + center.lat + scale_y * position[index * 3 + 1], + geoidDataList + ); + position[index * 3 + 2] += height; + } + } + } + + /* + .########.########.....###....##....##..######..########..#######..########..##.....## + ....##....##.....##...##.##...###...##.##....##.##.......##.....##.##.....##.###...### + ....##....##.....##..##...##..####..##.##.......##.......##.....##.##.....##.####.#### + ....##....########..##.....##.##.##.##..######..######...##.....##.########..##.###.## + ....##....##...##...#########.##..####.......##.##.......##.....##.##...##...##.....## + ....##....##....##..##.....##.##...###.##....##.##.......##.....##.##....##..##.....## + ....##....##.....##.##.....##.##....##..######..##........#######..##.....##.##.....## + + .##........#######...######.....###....##...... + .##.......##.....##.##....##...##.##...##...... + .##.......##.....##.##........##...##..##...... + .##.......##.....##.##.......##.....##.##...... + .##.......##.....##.##.......#########.##...... + .##.......##.....##.##....##.##.....##.##...... + .########..#######...######..##.....##.######## + */ + + function transformToLocal( + vertexCount, + positions, + normals, + cartographicCenter, + cartesianCenter, + parentRotation, + ellipsoidRadiiSquare, + scale_x, + scale_y + ) { + if ( + vertexCount === 0 || + positions === undefined || + positions.length === 0 + ) { + return; + } + + traceCode("converting " + vertexCount + " vertices "); + for (let i = 0; i < vertexCount; ++i) { + let indexOffset = i * 3; + let indexOffset1 = indexOffset + 1; + let indexOffset2 = indexOffset + 2; + + // Convert position from long, lat, height to Cartesian + cartographic.longitude = + _degToRad * + (cartographicCenter.long + scale_x * positions[indexOffset]); + cartographic.latitude = + _degToRad * + (cartographicCenter.lat + scale_y * positions[indexOffset1]); + cartographic.height = cartographicCenter.alt + positions[indexOffset2]; + + cartographicToCartesian(cartographic, ellipsoidRadiiSquare, position); + + position.x -= cartesianCenter.x; + position.y -= cartesianCenter.y; + position.z -= cartesianCenter.z; + + multiplyByVector(parentRotation, position, rotatedPosition); + + positions[indexOffset] = rotatedPosition.x; + positions[indexOffset1] = rotatedPosition.y; + positions[indexOffset2] = rotatedPosition.z; + + if (normals) { + normal.x = normals[indexOffset]; + normal.y = normals[indexOffset1]; + normal.z = normals[indexOffset2]; + + multiplyByVector(parentRotation, normal, rotatedNormal); + + // @TODO: check if normals are Z-UP or Y-UP and flip y and z + normals[indexOffset] = rotatedNormal.x; + normals[indexOffset1] = rotatedNormal.y; + normals[indexOffset2] = rotatedNormal.z; + } + } + } + + /* + ..######..########...#######..########.....##.....##.##.....## + .##....##.##.....##.##.....##.##.....##....##.....##.##.....## + .##.......##.....##.##.....##.##.....##....##.....##.##.....## + .##.......########..##.....##.########.....##.....##.##.....## + .##.......##...##...##.....##.##...........##.....##..##...##. + .##....##.##....##..##.....##.##...........##.....##...##.##.. + ..######..##.....##..#######..##............#######.....###... + */ + + function cropUVs(vertexCount, uv0s, uvRegions) { + for (let vertexIndex = 0; vertexIndex < vertexCount; ++vertexIndex) { + let minU = uvRegions[vertexIndex * 4] / 65535.0; + let minV = uvRegions[vertexIndex * 4 + 1] / 65535.0; + let scaleU = + (uvRegions[vertexIndex * 4 + 2] - uvRegions[vertexIndex * 4]) / 65535.0; + let scaleV = + (uvRegions[vertexIndex * 4 + 3] - uvRegions[vertexIndex * 4 + 1]) / + 65535.0; + + uv0s[vertexIndex * 2] *= scaleU; + uv0s[vertexIndex * 2] += minU; + + uv0s[vertexIndex * 2 + 1] *= scaleV; + uv0s[vertexIndex * 2 + 1] += minV; + } + } + + /* + ..######...########.##....##.########.########.....###....########.######## + .##....##..##.......###...##.##.......##.....##...##.##......##....##...... + .##........##.......####..##.##.......##.....##..##...##.....##....##...... + .##...####.######...##.##.##.######...########..##.....##....##....######.. + .##....##..##.......##..####.##.......##...##...#########....##....##...... + .##....##..##.......##...###.##.......##....##..##.....##....##....##...... + ..######...########.##....##.########.##.....##.##.....##....##....######## + + .####.##....##.########.########.########..##....##....###....##...... + ..##..###...##....##....##.......##.....##.###...##...##.##...##...... + ..##..####..##....##....##.......##.....##.####..##..##...##..##...... + ..##..##.##.##....##....######...########..##.##.##.##.....##.##...... + ..##..##..####....##....##.......##...##...##..####.#########.##...... + ..##..##...###....##....##.......##....##..##...###.##.....##.##...... + .####.##....##....##....########.##.....##.##....##.##.....##.######## + + .########..##.....##.########.########.########.########. + .##.....##.##.....##.##.......##.......##.......##.....## + .##.....##.##.....##.##.......##.......##.......##.....## + .########..##.....##.######...######...######...########. + .##.....##.##.....##.##.......##.......##.......##...##.. + .##.....##.##.....##.##.......##.......##.......##....##. + .########...#######..##.......##.......########.##.....## + */ + + function generateGLTFBuffer( + vertexCount, + indices, + positions, + normals, + uv0s, + colors + ) { + if ( + vertexCount === 0 || + positions === undefined || + positions.length === 0 + ) { + return { + buffers: [], + bufferViews: [], + accessors: [], + meshes: [], + nodes: [], + nodesInScene: [], + }; + } + + let buffers = []; + let bufferViews = []; + let accessors = []; + let meshes = []; + let nodes = []; + let nodesInScene = []; + + // if we provide indices, then the vertex count is the length + // of that array, otherwise we assume non-indexed triangle + if (indices) { + vertexCount = indices.length; + } + + // allocate array + let indexArray = new Uint32Array(vertexCount); + + if (indices) { + // set the indices + for (let vertexIndex = 0; vertexIndex < vertexCount; ++vertexIndex) { + indexArray[vertexIndex] = indices[vertexIndex]; + } + } else { + // generate indices + for (let vertexIndex = 0; vertexIndex < vertexCount; ++vertexIndex) { + indexArray[vertexIndex] = vertexIndex; + } + } + + // push to the buffers, bufferViews and accessors + let indicesBlob = new Blob([indexArray], { type: "application/binary" }); + let indicesURL = URL.createObjectURL(indicesBlob); + + let endIndex = vertexCount; + + // POSITIONS + let meshPositions = positions.subarray(0, endIndex * 3); + let positionsBlob = new Blob([meshPositions], { + type: "application/binary", + }); + let positionsURL = URL.createObjectURL(positionsBlob); + + // NORMALS + let meshNormals = normals ? normals.subarray(0, endIndex * 3) : null; + let normalsURL = null; + if (meshNormals) { + let normalsBlob = new Blob([meshNormals], { + type: "application/binary", + }); + normalsURL = URL.createObjectURL(normalsBlob); + } + + // UV0s + let meshUv0s = uv0s ? uv0s.subarray(0, endIndex * 2) : null; + let uv0URL = null; + if (meshUv0s) { + let uv0Blob = new Blob([meshUv0s], { type: "application/binary" }); + uv0URL = URL.createObjectURL(uv0Blob); + } + + // Colors + // @TODO: check we can directly import vertex colors as bytes instead + // of having to convert to float + let meshColorsInBytes = colors ? colors.subarray(0, endIndex * 4) : null; + let meshColors = null; + let colorsURL = null; + if (meshColorsInBytes) { + let colorCount = meshColorsInBytes.length; + meshColors = new Float32Array(colorCount); + for (let i = 0; i < colorCount; ++i) { + meshColors[i] = meshColorsInBytes[i] / 255.0; + } + + let colorsBlob = new Blob([meshColors], { type: "application/binary" }); + colorsURL = URL.createObjectURL(colorsBlob); + } + + let posIndex = 0; + let normalIndex = 0; + let uv0Index = 0; + let colorIndex = 0; + let indicesIndex = 0; + + let currentIndex = posIndex; + + let attributes = {}; + + // POSITIONS + attributes.POSITION = posIndex; + buffers.push({ + uri: positionsURL, + byteLength: meshPositions.byteLength, + }); + bufferViews.push({ + buffer: posIndex, + byteOffset: 0, + byteLength: meshPositions.byteLength, + target: 34962, + }); + accessors.push({ + bufferView: posIndex, + byteOffset: 0, + componentType: 5126, + count: vertexCount, + type: "VEC3", + max: [0, 0, 0], + min: [0, 0, 0], + }); + + // NORMALS + if (normalsURL) { + ++currentIndex; + normalIndex = currentIndex; + attributes.NORMAL = normalIndex; + buffers.push({ + uri: normalsURL, + byteLength: meshNormals.byteLength, + }); + bufferViews.push({ + buffer: normalIndex, + byteOffset: 0, + byteLength: meshNormals.byteLength, + target: 34962, + }); + accessors.push({ + bufferView: normalIndex, + byteOffset: 0, + componentType: 5126, + count: vertexCount, + type: "VEC3", + max: [0, 0, 0], + min: [0, 0, 0], + }); + } + + // UV0 + if (uv0URL) { + ++currentIndex; + uv0Index = currentIndex; + attributes.TEXCOORD_0 = uv0Index; + buffers.push({ + uri: uv0URL, + byteLength: meshUv0s.byteLength, + }); + bufferViews.push({ + buffer: uv0Index, + byteOffset: 0, + byteLength: meshUv0s.byteLength, + target: 34962, + }); + accessors.push({ + bufferView: uv0Index, + byteOffset: 0, + componentType: 5126, + count: vertexCount, + type: "VEC2", + max: [0, 0], + min: [0, 0], + }); + } + + // COLORS + if (colorsURL) { + ++currentIndex; + colorIndex = currentIndex; + attributes.COLOR_0 = colorIndex; + buffers.push({ + uri: colorsURL, + byteLength: meshColors.byteLength, + }); + bufferViews.push({ + buffer: colorIndex, + byteOffset: 0, + byteLength: meshColors.byteLength, + target: 34962, + }); + accessors.push({ + bufferView: colorIndex, + byteOffset: 0, + componentType: 5126, + count: vertexCount, + type: "VEC4", + max: [0, 0, 0, 0], + min: [0, 0, 0, 0], + }); + } + + // INDICES + ++currentIndex; + indicesIndex = currentIndex; + buffers.push({ + uri: indicesURL, + byteLength: indexArray.byteLength, + }); + bufferViews.push({ + buffer: indicesIndex, + byteOffset: 0, + byteLength: indexArray.byteLength, + target: 34963, + }); + accessors.push({ + bufferView: indicesIndex, + byteOffset: 0, + componentType: 5125, + count: vertexCount, + type: "SCALAR", + max: [0], + min: [0], + }); + + // create a new mesh for this page + meshes.push({ + primitives: [ + { + attributes: attributes, + indices: indicesIndex, + material: 0, + }, + ], + }); + nodesInScene.push(0); + nodes.push({ mesh: 0 }); + + return { + buffers: buffers, + bufferViews: bufferViews, + accessors: accessors, + meshes: meshes, + nodes: nodes, + nodesInScene: nodesInScene, + }; + } + + /* + ..######...########..#######..##.....##.########.########.########..##....## + .##....##..##.......##.....##.###...###.##..........##....##.....##..##..##. + .##........##.......##.....##.####.####.##..........##....##.....##...####.. + .##...####.######...##.....##.##.###.##.######......##....########.....##... + .##....##..##.......##.....##.##.....##.##..........##....##...##......##... + .##....##..##.......##.....##.##.....##.##..........##....##....##.....##... + ..######...########..#######..##.....##.########....##....##.....##....##... + + .########..########..######...#######..########..########.########. + .##.....##.##.......##....##.##.....##.##.....##.##.......##.....## + .##.....##.##.......##.......##.....##.##.....##.##.......##.....## + .##.....##.######...##.......##.....##.##.....##.######...########. + .##.....##.##.......##.......##.....##.##.....##.##.......##...##.. + .##.....##.##.......##....##.##.....##.##.....##.##.......##....##. + .########..########..######...#######..########..########.##.....## + */ + + function decode(data, schema, bufferInfo, featureData) { + let magicNumber = new Uint8Array(data, 0, 5); + if ( + magicNumber[0] === "D".charCodeAt() && + magicNumber[1] === "R".charCodeAt() && + magicNumber[2] === "A".charCodeAt() && + magicNumber[3] === "C".charCodeAt() && + magicNumber[4] === "O".charCodeAt() + ) { + return decodeDracoEncodedGeometry(data, bufferInfo); + } + return decodeBinaryGeometry(data, schema, bufferInfo, featureData); + } + + /* + .########..########.....###.....######...#######. + .##.....##.##.....##...##.##...##....##.##.....## + .##.....##.##.....##..##...##..##.......##.....## + .##.....##.########..##.....##.##.......##.....## + .##.....##.##...##...#########.##.......##.....## + .##.....##.##....##..##.....##.##....##.##.....## + .########..##.....##.##.....##..######...#######. + + .########..########..######...#######..########..########.########. + .##.....##.##.......##....##.##.....##.##.....##.##.......##.....## + .##.....##.##.......##.......##.....##.##.....##.##.......##.....## + .##.....##.######...##.......##.....##.##.....##.######...########. + .##.....##.##.......##.......##.....##.##.....##.##.......##...##.. + .##.....##.##.......##....##.##.....##.##.....##.##.......##....##. + .########..########..######...#######..########..########.##.....## + */ + + function decodeDracoEncodedGeometry(data, bufferInfo) { + // Create the Draco decoder. + // eslint-disable-next-line new-cap,no-undef + const dracoDecoderModule = DracoDecoderModule(); + const buffer = new dracoDecoderModule.DecoderBuffer(); + + let byteArray = new Uint8Array(data); + buffer.Init(byteArray, byteArray.length); + + // Create a buffer to hold the encoded data. + const dracoDecoder = new dracoDecoderModule.Decoder(); + const geometryType = dracoDecoder.GetEncodedGeometryType(buffer); + let metadataQuerier = new dracoDecoderModule.MetadataQuerier(); + + // Decode the encoded geometry. + // See: https://github.com/google/draco/blob/master/src/draco/javascript/emscripten/draco_web_decoder.idl + let dracoGeometry; + let status; + if (geometryType === dracoDecoderModule.TRIANGULAR_MESH) { + dracoGeometry = new dracoDecoderModule.Mesh(); + status = dracoDecoder.DecodeBufferToMesh(buffer, dracoGeometry); + } + + let decodedGeometry = { + vertexCount: [0], + featureCount: 0, + }; + + // if all is OK + if (status && status.ok() && dracoGeometry.ptr !== 0) { + let faceCount = dracoGeometry.num_faces(); + let attributesCount = dracoGeometry.num_attributes(); + let vertexCount = dracoGeometry.num_points(); + decodedGeometry.indices = new Uint32Array(faceCount * 3); + let faces = decodedGeometry.indices; + + decodedGeometry.vertexCount[0] = vertexCount; + decodedGeometry.scale_x = 1; + decodedGeometry.scale_y = 1; + + // Decode faces + // @TODO: Replace that code with GetTrianglesUInt32Array for better efficiency + let face = new dracoDecoderModule.DracoInt32Array(3); + for (let index = 0; index < faceCount; ++index) { + dracoDecoder.GetFaceFromMesh(dracoGeometry, index, face); + faces[index * 3] = face.GetValue(0); + faces[index * 3 + 1] = face.GetValue(1); + faces[index * 3 + 2] = face.GetValue(2); + } + + dracoDecoderModule.destroy(face); + + for (let index = 0; index < attributesCount; ++index) { + let dracoAttribute = dracoDecoder.GetAttribute(dracoGeometry, index); + + let attributeData = decodeDracoAttribute( + dracoDecoderModule, + dracoDecoder, + dracoGeometry, + dracoAttribute, + vertexCount + ); + + // initial mapping + let dracoAttributeType = dracoAttribute.attribute_type(); + let attributei3sName = "unknown"; + + if (dracoAttributeType === dracoDecoderModule.POSITION) { + attributei3sName = "positions"; + } else if (dracoAttributeType === dracoDecoderModule.NORMAL) { + attributei3sName = "normals"; + } else if (dracoAttributeType === dracoDecoderModule.COLOR) { + attributei3sName = "colors"; + } else if (dracoAttributeType === dracoDecoderModule.TEX_COORD) { + attributei3sName = "uv0s"; + } + + // get the metadata + let metadata = dracoDecoder.GetAttributeMetadata(dracoGeometry, index); + + if (metadata.ptr) { + let numEntries = metadataQuerier.NumEntries(metadata); + for (let entry = 0; entry < numEntries; ++entry) { + let entryName = metadataQuerier.GetEntryName(metadata, entry); + if (entryName === "i3s-scale_x") { + decodedGeometry.scale_x = metadataQuerier.GetDoubleEntry( + metadata, + "i3s-scale_x" + ); + } else if (entryName === "i3s-scale_y") { + decodedGeometry.scale_y = metadataQuerier.GetDoubleEntry( + metadata, + "i3s-scale_y" + ); + } else if (entryName === "i3s-attribute-type") { + attributei3sName = metadataQuerier.GetStringEntry( + metadata, + "i3s-attribute-type" + ); + } + } + } + + if (decodedGeometry[attributei3sName] !== undefined) { + console.log("Attribute already exists", attributei3sName); + } + + decodedGeometry[attributei3sName] = attributeData; + + if (attributei3sName === "feature-index") { + decodedGeometry.featureCount++; + } + } + + dracoDecoderModule.destroy(dracoGeometry); + } + + dracoDecoderModule.destroy(metadataQuerier); + dracoDecoderModule.destroy(dracoDecoder); + + return decodedGeometry; + } + + function decodeDracoAttribute( + dracoDecoderModule, + dracoDecoder, + dracoGeometry, + dracoAttribute, + vertexCount + ) { + let bufferSize = dracoAttribute.num_components() * vertexCount; + let dracoAttributeData = null; + + let handlers = [ + function () {}, // DT_INVALID - 0 + function () { + // DT_INT8 - 1 + dracoAttributeData = new dracoDecoderModule.DracoInt8Array(bufferSize); + let success = dracoDecoder.GetAttributeInt8ForAllPoints( + dracoGeometry, + dracoAttribute, + dracoAttributeData + ); + + if (!success) { + console.error("Bad stream"); + } + let attributeData = new Int8Array(bufferSize); + for (let i = 0; i < bufferSize; ++i) { + attributeData[i] = dracoAttributeData.GetValue(i); + } + return attributeData; + }, + function () { + // DT_UINT8 - 2 + dracoAttributeData = new dracoDecoderModule.DracoInt8Array(bufferSize); + let success = dracoDecoder.GetAttributeUInt8ForAllPoints( + dracoGeometry, + dracoAttribute, + dracoAttributeData + ); + + if (!success) { + console.error("Bad stream"); + } + let attributeData = new Uint8Array(bufferSize); + for (let i = 0; i < bufferSize; ++i) { + attributeData[i] = dracoAttributeData.GetValue(i); + } + return attributeData; + }, + function () { + // DT_INT16 - 3 + dracoAttributeData = new dracoDecoderModule.DracoInt16Array(bufferSize); + let success = dracoDecoder.GetAttributeInt16ForAllPoints( + dracoGeometry, + dracoAttribute, + dracoAttributeData + ); + + if (!success) { + console.error("Bad stream"); + } + let attributeData = new Int16Array(bufferSize); + for (let i = 0; i < bufferSize; ++i) { + attributeData[i] = dracoAttributeData.GetValue(i); + } + return attributeData; + }, + function () { + // DT_UINT16 - 4 + dracoAttributeData = new dracoDecoderModule.DracoInt16Array(bufferSize); + let success = dracoDecoder.GetAttributeUInt16ForAllPoints( + dracoGeometry, + dracoAttribute, + dracoAttributeData + ); + + if (!success) { + console.error("Bad stream"); + } + let attributeData = new Uint16Array(bufferSize); + for (let i = 0; i < bufferSize; ++i) { + attributeData[i] = dracoAttributeData.GetValue(i); + } + return attributeData; + }, + function () { + // DT_INT32 - 5 + dracoAttributeData = new dracoDecoderModule.DracoInt32Array(bufferSize); + let success = dracoDecoder.GetAttributeInt32ForAllPoints( + dracoGeometry, + dracoAttribute, + dracoAttributeData + ); + + if (!success) { + console.error("Bad stream"); + } + let attributeData = new Int32Array(bufferSize); + for (let i = 0; i < bufferSize; ++i) { + attributeData[i] = dracoAttributeData.GetValue(i); + } + return attributeData; + }, + function () { + // DT_UINT32 - 6 + dracoAttributeData = new dracoDecoderModule.DracoInt32Array(bufferSize); + let success = dracoDecoder.GetAttributeUInt32ForAllPoints( + dracoGeometry, + dracoAttribute, + dracoAttributeData + ); + + if (!success) { + console.error("Bad stream"); + } + let attributeData = new Uint32Array(bufferSize); + for (let i = 0; i < bufferSize; ++i) { + attributeData[i] = dracoAttributeData.GetValue(i); + } + return attributeData; + }, + function () { + // DT_INT64 - 7 + }, + function () { + // DT_UINT64 - 8 + }, + function () { + // DT_FLOAT32 - 9 + dracoAttributeData = new dracoDecoderModule.DracoFloat32Array( + bufferSize + ); + let success = dracoDecoder.GetAttributeFloatForAllPoints( + dracoGeometry, + dracoAttribute, + dracoAttributeData + ); + + if (!success) { + console.error("Bad stream"); + } + let attributeData = new Float32Array(bufferSize); + for (let i = 0; i < bufferSize; ++i) { + attributeData[i] = dracoAttributeData.GetValue(i); + } + return attributeData; + }, + function () { + // DT_FLOAT64 - 10 + }, + function () { + // DT_FLOAT32 - 11 + dracoAttributeData = new dracoDecoderModule.DracoUInt8Array(bufferSize); + let success = dracoDecoder.GetAttributeUInt8ForAllPoints( + dracoGeometry, + dracoAttribute, + dracoAttributeData + ); + + if (!success) { + console.error("Bad stream"); + } + let attributeData = new Uint8Array(bufferSize); + for (let i = 0; i < bufferSize; ++i) { + attributeData[i] = dracoAttributeData.GetValue(i); + } + return attributeData; + }, + ]; + + let attributeData = handlers[dracoAttribute.data_type()](); + + if (dracoAttributeData) { + dracoDecoderModule.destroy(dracoAttributeData); + } + + return attributeData; + } + + /* + .########..####.##....##....###....########..##....## + .##.....##..##..###...##...##.##...##.....##..##..##. + .##.....##..##..####..##..##...##..##.....##...####.. + .########...##..##.##.##.##.....##.########.....##... + .##.....##..##..##..####.#########.##...##......##... + .##.....##..##..##...###.##.....##.##....##.....##... + .########..####.##....##.##.....##.##.....##....##... + + .########..########..######...#######..########..########.########. + .##.....##.##.......##....##.##.....##.##.....##.##.......##.....## + .##.....##.##.......##.......##.....##.##.....##.##.......##.....## + .##.....##.######...##.......##.....##.##.....##.######...########. + .##.....##.##.......##.......##.....##.##.....##.##.......##...##.. + .##.....##.##.......##....##.##.....##.##.....##.##.......##....##. + .########..########..######...#######..########..########.##.....## + */ + + let binaryAttributeDecoders = { + position: function (decodedGeometry, data, offset) { + let count = decodedGeometry.vertexCount * 3; + decodedGeometry.positions = new Float32Array(data, offset, count); + offset += count * 4; + return offset; + }, + normal: function (decodedGeometry, data, offset) { + let count = decodedGeometry.vertexCount * 3; + decodedGeometry.normals = new Float32Array(data, offset, count); + offset += count * 4; + return offset; + }, + uv0: function (decodedGeometry, data, offset) { + let count = decodedGeometry.vertexCount * 2; + decodedGeometry.uv0s = new Float32Array(data, offset, count); + offset += count * 4; + return offset; + }, + color: function (decodedGeometry, data, offset) { + let count = decodedGeometry.vertexCount * 4; + decodedGeometry.colors = new Uint8Array(data, offset, count); + offset += count; + return offset; + }, + featureId: function (decodedGeometry, data, offset) { + let count = decodedGeometry.featureCount; + let decodedGeometryBytes = new Uint8Array(data, offset, 8); + let id = decodedGeometryBytes[7]; + id <<= 8; + id = decodedGeometryBytes[6]; + id <<= 8; + id = decodedGeometryBytes[5]; + id <<= 8; + id = decodedGeometryBytes[4]; + id <<= 8; + id = decodedGeometryBytes[3]; + id <<= 8; + id = decodedGeometryBytes[2]; + id <<= 8; + id = decodedGeometryBytes[1]; + id <<= 8; + id = decodedGeometryBytes[0]; + decodedGeometry.featureId = id; + offset += count * 8; + return offset; + }, + id: function (decodedGeometry, data, offset) { + let count = decodedGeometry.featureCount; + let decodedGeometryBytes = new Uint8Array(data, offset, 8); + let id = decodedGeometryBytes[7]; + id <<= 8; + id = decodedGeometryBytes[6]; + id <<= 8; + id = decodedGeometryBytes[5]; + id <<= 8; + id = decodedGeometryBytes[4]; + id <<= 8; + id = decodedGeometryBytes[3]; + id <<= 8; + id = decodedGeometryBytes[2]; + id <<= 8; + id = decodedGeometryBytes[1]; + id <<= 8; + id = decodedGeometryBytes[0]; + decodedGeometry.featureId = id; + offset += count * 8; + return offset; + }, + faceRange: function (decodedGeometry, data, offset) { + let count = decodedGeometry.featureCount * 2; + decodedGeometry.faceRange = new Uint32Array(data, offset, count); + offset += count * 4; + return offset; + }, + uvRegion: function (decodedGeometry, data, offset) { + let count = decodedGeometry.vertexCount * 4; + decodedGeometry["uv-region"] = new Uint16Array(data, offset, count); + offset += count * 2; + return offset; + }, + region: function (decodedGeometry, data, offset) { + let count = decodedGeometry.vertexCount * 4; + decodedGeometry["uv-region"] = new Uint16Array(data, offset, count); + offset += count * 2; + return offset; + }, + }; + + function decodeBinaryGeometry(data, schema, bufferInfo, featureData) { + // From this spec: + // https://github.com/Esri/i3s-spec/blob/master/docs/1.7/defaultGeometrySchema.cmn.md + let decodedGeometry = { + vertexCount: 0, + }; + + let dataView = new DataView(data); + + try { + let offset = 0; + decodedGeometry.vertexCount = dataView.getUint32(offset, 1); + offset += 4; + + decodedGeometry.featureCount = dataView.getUint32(offset, 1); + offset += 4; + + if (bufferInfo) { + for (let item of bufferInfo.attributes) { + if (binaryAttributeDecoders[item]) { + offset = binaryAttributeDecoders[item]( + decodedGeometry, + data, + offset + ); + } else { + console.error("Unknown decoder for", item); + } + } + } else { + let ordering = schema.ordering; + let featureAttributeOrder = schema.featureAttributeOrder; + + if ( + featureData && + featureData.geometryData && + featureData.geometryData[0] && + featureData.geometryData[0].params + ) { + ordering = Object.keys( + featureData.geometryData[0].params.vertexAttributes + ); + featureAttributeOrder = Object.keys( + featureData.geometryData[0].params.featureAttributes + ); + } + + // use default geometry schema + for (let item of ordering) { + let decoder = binaryAttributeDecoders[item]; + if (!decoder) { + console.log(item); + } + offset = decoder(decodedGeometry, data, offset); + } + + for (let item of featureAttributeOrder) { + let decoder = binaryAttributeDecoders[item]; + if (!decoder) { + console.log(item); + } + offset = decoder(decodedGeometry, data, offset); + } + } + } catch (e) { + console.error(e); + } + + decodedGeometry.scale_x = 1; + decodedGeometry.scale_y = 1; + + return decodedGeometry; + } + + /* + .##......##..#######..########..##....##.########.########......######..########.##.....##.########. + .##..##..##.##.....##.##.....##.##...##..##.......##.....##....##....##....##....##.....##.##.....## + .##..##..##.##.....##.##.....##.##..##...##.......##.....##....##..........##....##.....##.##.....## + .##..##..##.##.....##.########..#####....######...########......######.....##....##.....##.########. + .##..##..##.##.....##.##...##...##..##...##.......##...##............##....##....##.....##.##.....## + .##..##..##.##.....##.##....##..##...##..##.......##....##.....##....##....##....##.....##.##.....## + ..###..###...#######..##.....##.##....##.########.##.....##.....######.....##.....#######..########. + */ + onmessage = function (e) { + traceCode(e.data.url); + + // Decode the data into geometry + let geometryData = decode( + e.data.binaryData, + e.data.schema, + e.data.bufferInfo, + e.data.featureData + ); + + // Adjust height from orthometric to ellipsoidal + if (e.data.geoidDataList && e.data.geoidDataList.length > 0) { + orthometricToEllipsoidal( + geometryData.vertexCount, + geometryData.positions, + geometryData.scale_x, + geometryData.scale_y, + e.data.cartographicCenter, + e.data.geoidDataList, + false + ); + } + + // Transform vertices to local + transformToLocal( + geometryData.vertexCount, + geometryData.positions, + geometryData.normals, + e.data.cartographicCenter, + e.data.cartesianCenter, + e.data.parentRotation, + e.data.ellipsoidRadiiSquare, + geometryData.scale_x, + geometryData.scale_y + ); + + // Adjust UVs if there is a UV region + if (geometryData.uv0s && geometryData["uv-region"]) { + cropUVs( + geometryData.vertexCount, + geometryData.uv0s, + geometryData["uv-region"] + ); + } + + // Create the final buffer + let meshData = generateGLTFBuffer( + geometryData.vertexCount, + geometryData.indices, + geometryData.positions, + geometryData.normals, + geometryData.uv0s, + geometryData.colors + ); + + let customAttributes = {}; + if (geometryData["feature-index"]) { + customAttributes.positions = geometryData.positions; + customAttributes["feature-index"] = geometryData["feature-index"]; + } + + if (geometryData["featureId"]) { + customAttributes.positions = geometryData.positions; + customAttributes["featureId"] = geometryData["featureId"]; + } + + meshData.customAttributes = customAttributes; + postMessage(meshData); + }; +} + +/* +.########..########...#######...######..########..######...######..####.##....##..######.. +.##.....##.##.....##.##.....##.##....##.##.......##....##.##....##..##..###...##.##....##. +.##.....##.##.....##.##.....##.##.......##.......##.......##........##..####..##.##....... +.########..########..##.....##.##.......######....######...######...##..##.##.##.##...#### +.##........##...##...##.....##.##.......##.............##.......##..##..##..####.##....##. +.##........##....##..##.....##.##....##.##.......##....##.##....##..##..##...###.##....##. +.##........##.....##..#######...######..########..######...######..####.##....##..######.. + +..#######..##.....##.########.##.....##.######## +.##.....##.##.....##.##.......##.....##.##...... +.##.....##.##.....##.##.......##.....##.##...... +.##.....##.##.....##.######...##.....##.######.. +.##..##.##.##.....##.##.......##.....##.##...... +.##....##..##.....##.##.......##.....##.##...... +..#####.##..#######..########..#######..######## +*/ +function I3SGLTFProcessingQueue() { + this._queue = []; + this._processing = false; + this._createWorkers(() => { + this._process(); + }); +} + +I3SGLTFProcessingQueue.prototype._process = function () { + for (let worker of this._workers) { + if (worker.isReadyToWork) { + if (this._queue.length > 0) { + let task = this._queue.shift(); + task.execute(worker); + traceCode("Process Queue:" + this._queue.length); + } + } + } + setTimeout(() => { + this._process(); + }, 100); +}; + +I3SGLTFProcessingQueue.prototype._createWorkers = function (cb) { + let workerCode = String(_workerCode); + + let externalModules = ["/Source/ThirdParty/Workers/draco_decoder.js"]; + + let externalModuleCode = ""; + let fetchPromises = []; + + for (let index = 0; index < externalModules.length; ++index) { + let moduleLoadedResolve; + let moduleLoaded = new _Promise((resolve, reject) => { + moduleLoadedResolve = resolve; + }); + fetchPromises.push(moduleLoaded); + + // eslint-disable-next-line no-loop-func + fetch(externalModules[index]).then((response) => { + response.text().then((data) => { + externalModuleCode += data; + moduleLoadedResolve(); + }); + }); + } + + let externalModulesLoaded = _Promise.all(fetchPromises); + let that = this; + externalModulesLoaded.then(() => { + workerCode = workerCode.replace("function _workerCode() {", ""); + workerCode = workerCode.slice(0, -1); + let blob = new Blob([externalModuleCode + workerCode], { + type: "test/javascript", + }); + + // Create the workers + let workerCount = FeatureDetection.hardwareConcurrency - 1; + traceCode("Using " + workerCount + " workers"); + that._workers = []; + + for (let loop = 0; loop < workerCount; ++loop) { + let worker = new Worker(window.URL.createObjectURL(blob)); + + worker.setTask = function (task) { + worker._task = task; + worker.isReadyToWork = false; + worker.postMessage(task.payload); + }; + + worker.onmessage = function (e) { + let task = worker._task; + + let gltfData = task._geometry._generateGLTF( + e.data.nodesInScene, + e.data.nodes, + e.data.meshes, + e.data.buffers, + e.data.bufferViews, + e.data.accessors + ); + + worker._task = null; + worker.isReadyToWork = true; + task.resolve({ + gltfData: gltfData, + customAttributes: e.data.customAttributes, + }); + }; + + worker.isReadyToWork = true; + that._workers.push(worker); + } + + cb(); + }); +}; + +I3SGLTFProcessingQueue.prototype.addTask = function (data) { + let newTask = { + _geometry: data.geometryData, + _featureData: + data.featureData && data.featureData[0] ? data.featureData[0] : null, + _schema: data.defaultGeometrySchema, + _tile: data.tile, + _geoidDataList: data.geometryData._dataSource._geoidDataList, + execute: function (worker) { + // Prepare the data to send to the worker + //let geometryData = this._geometry._data; + let parentData = this._geometry._parent._data; + let parentRotationInverseMatrix = this._geometry._parent + ._inverseRotationMatrix; + + let payload = { + binaryData: this._geometry._data, + featureData: this._featureData ? this._featureData._data : null, + schema: this._schema, + bufferInfo: this._geometry._geometryBufferInfo, + ellipsoidRadiiSquare: Ellipsoid.WGS84._radiiSquared, + url: data.url, + geoidDataList: this._geoidDataList, + }; + + let center = { + long: 0, + lat: 0, + alt: 0, + }; + + if (parentData.obb) { + center.long = parentData.obb.center[0]; + center.lat = parentData.obb.center[1]; + center.alt = parentData.obb.center[2]; + } else if (parentData.mbs) { + center.long = parentData.mbs[0]; + center.lat = parentData.mbs[1]; + center.alt = parentData.mbs[2]; + } + + payload.cartographicCenter = center; + payload.cartesianCenter = _WGS84ToCartesian( + center.long, + center.lat, + center.alt + ); + + let axisFlipRotation = Matrix3.fromRotationX(-Math.PI / 2); + let parentRotation = new Matrix3(); + + Matrix3.multiply( + axisFlipRotation, + parentRotationInverseMatrix, + parentRotation + ); + + payload.parentRotation = [ + parentRotation[0], + parentRotation[1], + parentRotation[2], + parentRotation[3], + parentRotation[4], + parentRotation[5], + parentRotation[6], + parentRotation[7], + parentRotation[8], + ]; + + this.payload = payload; + + // launch the job on the web worker + worker.setTask(this); + }, + }; + + return new _Promise((resolve, reject) => { + newTask.resolve = resolve; + newTask.reject = reject; + this._queue.push(newTask); + }); +}; + +/* +.########.##.....##.########...#######..########..######## +.##........##...##..##.....##.##.....##.##.....##....##... +.##.........##.##...##.....##.##.....##.##.....##....##... +.######......###....########..##.....##.########.....##... +.##.........##.##...##........##.....##.##...##......##... +.##........##...##..##........##.....##.##....##.....##... +.########.##.....##.##.........#######..##.....##....##... +*/ +export default I3SDataSource; From 4b7c0817546ec6d49b5fa19e4a99bb8247a1549c Mon Sep 17 00:00:00 2001 From: Tamrat Belayneh Date: Wed, 23 Jun 2021 17:26:03 -0700 Subject: [PATCH 02/57] Fixes for travis-ci issues --- Apps/Sandcastle/gallery/I3S 3D Object Layer.html | 4 ++-- Apps/Sandcastle/gallery/I3S Feature Picking.html | 16 ++++++++-------- .../gallery/I3S IntegratedMesh Layer.html | 4 ++-- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Apps/Sandcastle/gallery/I3S 3D Object Layer.html b/Apps/Sandcastle/gallery/I3S 3D Object Layer.html index ba4f5b7f276d..434b74fef0cb 100644 --- a/Apps/Sandcastle/gallery/I3S 3D Object Layer.html +++ b/Apps/Sandcastle/gallery/I3S 3D Object Layer.html @@ -38,7 +38,7 @@

Loading...

function startup(Cesium) { "use strict"; //Sandcastle_Begin - let viewer = new Cesium.Viewer("cesiumContainer", { + var viewer = new Cesium.Viewer("cesiumContainer", { terrainProvider: new Cesium.createWorldTerrain({}), animation: false, timeline: false, @@ -51,7 +51,7 @@

Loading...

"https://tiles.arcgis.com/tiles/z2tnIkrLQ2BRzr6P/arcgis/rest/services/EGM2008/ImageServer", }); //Create I3S dataSource and autocenters camera to the I3S layers' extent - let dataSource = new Cesium.I3SDataSource( + var dataSource = new Cesium.I3SDataSource( "San Francisco", viewer.scene, { diff --git a/Apps/Sandcastle/gallery/I3S Feature Picking.html b/Apps/Sandcastle/gallery/I3S Feature Picking.html index 0ba67a81af49..587461b2e362 100644 --- a/Apps/Sandcastle/gallery/I3S Feature Picking.html +++ b/Apps/Sandcastle/gallery/I3S Feature Picking.html @@ -38,7 +38,7 @@

Loading...

function startup(Cesium) { "use strict"; //Sandcastle_Begin - let viewer = new Cesium.Viewer("cesiumContainer", { + var viewer = new Cesium.Viewer("cesiumContainer", { terrainProvider: new Cesium.createWorldTerrain({}), animation: false, timeline: false, @@ -51,7 +51,7 @@

Loading...

"https://tiles.arcgis.com/tiles/z2tnIkrLQ2BRzr6P/arcgis/rest/services/EGM2008/ImageServer", }); //Create I3S dataSource and autocenters camera to the I3S layers' extent - let dataSource = new Cesium.I3SDataSource("New York", viewer.scene, { + var dataSource = new Cesium.I3SDataSource("New York", viewer.scene, { autoCenterCameraOnStart: true, geoidTiledTerrainProvider: geoidService, }); @@ -82,11 +82,11 @@

Loading...

pickedFeature.content && pickedFeature.content.i3sNode ) { - let i3sNode = pickedFeature.content.i3sNode; + var i3sNode = pickedFeature.content.i3sNode; i3sNode.loadFields().then(() => { - let geometry = i3sNode.geometryData[0]; + var geometry = i3sNode.geometryData[0]; if (pickedPosition) { - let location = geometry.getClosestPointIndex( + var location = geometry.getClosestPointIndex( pickedPosition.x, pickedPosition.y, pickedPosition.z @@ -98,13 +98,13 @@

Loading...

location.index !== -1 && geometry.customAttributes["feature-index"] ) { - let featureIndex = + var featureIndex = geometry.customAttributes["feature-index"][location.index]; if (Object.keys(i3sNode.fields).length > 0) { description = ''; - for (let fieldName in i3sNode.fields) { - let field = i3sNode.fields[fieldName]; + for (var fieldName in i3sNode.fields) { + var field = i3sNode.fields[fieldName]; //console.log(field.name + ": " + field.values[featureIndex]); description += ""; diff --git a/Apps/Sandcastle/gallery/I3S IntegratedMesh Layer.html b/Apps/Sandcastle/gallery/I3S IntegratedMesh Layer.html index a26ef5e6fd5c..b07c417507ff 100644 --- a/Apps/Sandcastle/gallery/I3S IntegratedMesh Layer.html +++ b/Apps/Sandcastle/gallery/I3S IntegratedMesh Layer.html @@ -39,7 +39,7 @@

Loading...

"use strict"; //Sandcastle_Begin // Create a Viewer instances wiht ArcGIS Tiled Elevation Layer - let viewer = new Cesium.Viewer("cesiumContainer", { + var viewer = new Cesium.Viewer("cesiumContainer", { terrainProvider: new Cesium.ArcGISTiledElevationTerrainProvider({ url: "https://elevation3d.arcgis.com/arcgis/rest/services/WorldElevation3D/Terrain3D/ImageServer", @@ -50,7 +50,7 @@

Loading...

//Create an I3S DataSource with the camera autocentered to the I3S layers' extent //No need to do a geoid conversion as both the elevation proivder (ArcGISTiledElevationTerrainProvider) //and the I3S layer to be added (San Francisco) use orthometric height systems . - let dataSource = new Cesium.I3SDataSource( + var dataSource = new Cesium.I3SDataSource( "San Francisco", viewer.scene, { From 37bf80a9b1adbf39044d611137a438e63ac60c5e Mon Sep 17 00:00:00 2001 From: Tamrat Belayneh Date: Wed, 30 Jun 2021 10:24:02 -0700 Subject: [PATCH 03/57] Fixed lint issues --- .../gallery/I3S Feature Picking.html | 31 +- Source/DataSources/I3SDataSource.js | 1337 +++++++++-------- 2 files changed, 715 insertions(+), 653 deletions(-) diff --git a/Apps/Sandcastle/gallery/I3S Feature Picking.html b/Apps/Sandcastle/gallery/I3S Feature Picking.html index 587461b2e362..7a2f09a39e8a 100644 --- a/Apps/Sandcastle/gallery/I3S Feature Picking.html +++ b/Apps/Sandcastle/gallery/I3S Feature Picking.html @@ -83,15 +83,17 @@

Loading...

pickedFeature.content.i3sNode ) { var i3sNode = pickedFeature.content.i3sNode; - i3sNode.loadFields().then(() => { + i3sNode.loadFields().then(function () { + console.log(i3sNode); var geometry = i3sNode.geometryData[0]; + console.log(geometry); if (pickedPosition) { var location = geometry.getClosestPointIndex( pickedPosition.x, pickedPosition.y, pickedPosition.z ); - //console.log("Location", location); + console.log("Location", location); var description = "No attributes"; var name = ""; if ( @@ -104,15 +106,18 @@

Loading...

description = '
" + field.name + ""; description += field.values[featureIndex] + "
'; for (var fieldName in i3sNode.fields) { - var field = i3sNode.fields[fieldName]; - //console.log(field.name + ": " + field.values[featureIndex]); - description += ""; - if ( - (!name || 0 === name.length) && - isNameProperty(field.name) - ) { - name = field.values[featureIndex]; + if (fieldName.legnth > 0) { + var field = i3sNode.fields[fieldName]; + //console.log(field.name + ": " + field.values[featureIndex]); + description += ""; + if ( + (!name || 0 === name.length) && + isNameProperty(field.name) + ) { + name = field.values[featureIndex]; + } } } description += "
" + field.name + ""; - description += field.values[featureIndex] + "
" + field.name + ""; + description += + field.values[featureIndex] + "
"; @@ -127,8 +132,11 @@

Loading...

} }); } + + console.log(viewer.scene.camera); }, Cesium.ScreenSpaceEventType.LEFT_CLICK); + function isNameProperty(propertyName) { var name = propertyName.toLowerCase(); if ( @@ -139,6 +147,7 @@

Loading...

} return false; } + //Sandcastle_End if (typeof Cesium !== "undefined") { window.startupCalled = true; diff --git a/Source/DataSources/I3SDataSource.js b/Source/DataSources/I3SDataSource.js index b05d6638e772..3fd195dbed4f 100644 --- a/Source/DataSources/I3SDataSource.js +++ b/Source/DataSources/I3SDataSource.js @@ -75,7 +75,7 @@ /* // Create a Viewer instances and add the DataSource. -let viewer = new Cesium.Viewer("cesiumContainer", { +var viewer = new Cesium.Viewer("cesiumContainer", { animation: false, timeline: false, }); @@ -83,7 +83,7 @@ let viewer = new Cesium.Viewer("cesiumContainer", { viewer.clock.shouldAnimate = false; -let tours = { +var tours = { "Frankfurt": "https://tiles.arcgis.com/tiles/u0sSNqDXr7puKJrF/arcgis/rest/services/Frankfurt2017_v17/SceneServer" }; @@ -93,7 +93,7 @@ var geoidService = new Cesium.ArcGISTiledElevationTerrainProvider({ url : "https://tiles.arcgis.com/tiles/z2tnIkrLQ2BRzr6P/arcgis/rest/services/EGM2008/ImageServer", }); -let dataSource = new Cesium.I3SDataSource("", viewer.scene, { +var dataSource = new Cesium.I3SDataSource("", viewer.scene, { traceFetches : false, // for tracing I3S fetches traceVisuals : false, // for tracing visuals autoCenterCameraOnStart : true, // auto center to the location of the i3s @@ -124,19 +124,19 @@ viewer.screenSpaceEventHandler.setInputAction(function onLeftClick( if (pickedFeature && pickedFeature.content && pickedFeature.content.i3sNode) { - let i3sNode = pickedFeature.content.i3sNode; + var i3sNode = pickedFeature.content.i3sNode; i3sNode.loadFields().then(() => { console.log(i3sNode); - let geometry = i3sNode.geometryData[0]; + var geometry = i3sNode.geometryData[0]; console.log(geometry); if (pickedPosition) { - let location = geometry.getClosestPointIndex( + var location = geometry.getClosestPointIndex( pickedPosition.x, pickedPosition.y, pickedPosition.z); console.log("Location", location); if (location.index !== -1 && geometry.customAttributes["feature-index"]) { - let featureIndex = geometry.customAttributes["feature-index"][location.index]; - for (let fieldName in i3sNode.fields) { - let field = i3sNode.fields[fieldName]; + var featureIndex = geometry.customAttributes["feature-index"][location.index]; + for (var fieldName in i3sNode.fields) { + var field = i3sNode.fields[fieldName]; console.log(field.name + ": " + field.values[featureIndex]); } } @@ -159,10 +159,7 @@ viewer.screenSpaceEventHandler.setInputAction(function onLeftClick( ..##..##.....##.##........##.....##.##....##.....##....##....## .####.##.....##.##.........#######..##.....##....##.....######. */ - -import ArcGISTiledElevationTerrainProvider from "../Core/ArcGISTiledElevationTerrainProvider.js"; import Batched3DModel3DTileContent from "../Scene/Batched3DModel3DTileContent.js"; -import buildModuleUrl from "../Core/buildModuleUrl.js"; import Cartesian2 from "../Core/Cartesian2.js"; import Cartesian3 from "../Core/Cartesian3.js"; import Cartesian4 from "../Core/Cartesian4.js"; @@ -202,16 +199,16 @@ import when from "../ThirdParty/when.js"; */ // Maps i3Snode by URI -let _i3sContentCache = {}; +var _i3sContentCache = {}; // Prevent ESLint from issuing warnings about undefined Promise -// eslint-disable-next-line no-undef -let _Promise = Promise; +//eslint-disable-next-line no-undef +var _Promise = Promise; // Code traces // set to true to turn on code tracing for debugging purposes -let _tracecode = false; -let traceCode = function () {}; +var _tracecode = false; +var traceCode = function () {}; if (_tracecode) { traceCode = console.log; } @@ -258,7 +255,7 @@ if (_tracecode) { * * * @example - * let dataSource = new I3SDataSource(); + * var dataSource = new I3SDataSource(); * dataSource.loadUrl('https://tiles.arcgis.com/tiles/u0sSNqDXr7puKJrF/arcgis/rest/services/Frankfurt2017_v17/SceneServer'); * dataSource.dataSources.add(dataSource); * @@ -266,7 +263,7 @@ if (_tracecode) { * var geoidService = new Cesium.ArcGISTiledElevationTerrainProvider({ * url : "https://tiles.arcgis.com/tiles/z2tnIkrLQ2BRzr6P/arcgis/rest/services/EGM2008/ImageServer", * }); - * let dataSource = new I3SDataSource("", viewer.scene, { + * var dataSource = new I3SDataSource("", viewer.scene, { * autoCenterCameraOnStart : true, // auto center to the location of the i3s * geoidTiledTerrainProvider : geoidService, // pass the geoid service * @@ -502,14 +499,14 @@ Object.defineProperties(I3SDataSource.prototype, { * @returns {Promise} a promise that will resolve when the I3S scene is loaded. */ I3SDataSource.prototype.loadUrl = function (url) { - let parts = url.split("?"); + var parts = url.split("?"); this._url = parts[0]; this._query = parts[1]; this._completeUrl = url; - let deferredPromise = new when.defer(); + var deferredPromise = new when.defer(); this._sceneServer = new I3SSceneServer(this); - this._sceneServer.load(this._completeUrl).then(() => { + this._sceneServer.load(this._completeUrl).then(function () { deferredPromise.resolve(); }); @@ -529,7 +526,7 @@ I3SDataSource.prototype.load = function (data) { //Clear out any data that might already exist. this._setLoading(true); - let entities = this._entityCollection; + var entities = this._entityCollection; //It's a good idea to suspend events when making changes to a //large amount of entities. This will cause events to be batched up @@ -558,54 +555,66 @@ I3SDataSource.prototype._setLoading = function (isLoading) { * @private */ I3SDataSource.prototype._loadJson = function (uri, success, fail) { - return new _Promise((resolve, reject) => { - if (this._traceFetches) { - console.log("I3S FETCH:", uri); - } - let request = fetch(uri); - request.then((response) => { - response.json().then((data) => { - if (data.error) { - console.error(this._data.error.message); - fail(reject); - } else { - success(data, resolve); - } - }); - }); - }); + return new _Promise( + function (resolve, reject) { + if (this._traceFetches) { + console.log("I3S FETCH:", uri); + } + var request = fetch(uri); + request.then( + function (response) { + response.json().then( + function (data) { + if (data.error) { + console.error(this._data.error.message); + fail(reject); + } else { + success(data, resolve); + } + }.bind(this) + ); + }.bind(this) + ); + }.bind(this) + ); }; /** * @private */ I3SDataSource.prototype._loadBinary = function (uri, success, fail) { - return new _Promise((resolve, reject) => { - if (this._traceFetches) { - traceCode("I3S FETCH:", uri); - } - let request = fetch(uri); - request.then((response) => { - response.arrayBuffer().then((data) => { - if (data.error) { - console.error(this._data.error.message); - fail(reject); - } else { - success(data, resolve); - } - }); - }); - }); + return new _Promise( + function (resolve, reject) { + if (this._traceFetches) { + traceCode("I3S FETCH:", uri); + } + var request = fetch(uri); + request.then( + function (response) { + response.arrayBuffer().then( + function (data) { + if (data.error) { + console.error(this._data.error.message); + fail(reject); + } else { + success(data, resolve); + } + }.bind(this) + ); + }.bind(this) + ); + }.bind(this) + ); }; /** * @private */ I3SDataSource.prototype._binarizeGLTF = function (rawGLTF) { - let encoder = new TextEncoder(); - let rawGLTFData = encoder.encode(JSON.stringify(rawGLTF)); - let binaryGLTFData = new Uint8Array(rawGLTFData.byteLength + 20); - let binaryGLTF = { + var encoder = new TextEncoder(); + var rawGLTFData = encoder.encode(JSON.stringify(rawGLTF)); + var binaryGLTFData = new Uint8Array(rawGLTFData.byteLength + 20); + var binaryGLTF = { magic: new Uint8Array(binaryGLTFData.buffer, 0, 4), version: new Uint32Array(binaryGLTFData.buffer, 4, 1), length: new Uint32Array(binaryGLTFData.buffer, 8, 1), @@ -640,31 +649,31 @@ I3SDataSource.prototype._binarizeB3DM = function ( batchTabelJSON, binaryGLTFData ) { - let encoder = new TextEncoder(); + var encoder = new TextEncoder(); // Feature Table - let featureTableOffset = 28; - let featureTableJSONData = encoder.encode(featureTableJSON); - let featureTableLength = featureTableJSONData.byteLength; + var featureTableOffset = 28; + var featureTableJSONData = encoder.encode(featureTableJSON); + var featureTableLength = featureTableJSONData.byteLength; // Batch Table - let batchTableOffset = featureTableOffset + featureTableLength; - let batchTableJSONData = encoder.encode(batchTabelJSON); + var batchTableOffset = featureTableOffset + featureTableLength; + var batchTableJSONData = encoder.encode(batchTabelJSON); // Calculate alignment buffer by padding the remainder of the batch table - let paddingCount = (batchTableOffset + batchTableJSONData.byteLength) % 8; - let batchTableLength = batchTableJSONData.byteLength + paddingCount; - let paddingStart = batchTableJSONData.byteLength; - let paddingStop = batchTableLength; + var paddingCount = (batchTableOffset + batchTableJSONData.byteLength) % 8; + var batchTableLength = batchTableJSONData.byteLength + paddingCount; + var paddingStart = batchTableJSONData.byteLength; + var paddingStop = batchTableLength; // Binary GLTF - let binaryGLTFOffset = batchTableOffset + batchTableLength; - let binaryGLTFLength = binaryGLTFData.byteLength; + var binaryGLTFOffset = batchTableOffset + batchTableLength; + var binaryGLTFLength = binaryGLTFData.byteLength; - let dataSize = featureTableLength + batchTableLength + binaryGLTFLength; - let b3dmRawData = new Uint8Array(28 + dataSize); + var dataSize = featureTableLength + batchTableLength + binaryGLTFLength; + var b3dmRawData = new Uint8Array(28 + dataSize); - let b3dmData = { + var b3dmData = { magic: new Uint8Array(b3dmRawData.buffer, 0, 4), version: new Uint32Array(b3dmRawData.buffer, 4, 1), byteLength: new Uint32Array(b3dmRawData.buffer, 8, 1), @@ -702,7 +711,7 @@ I3SDataSource.prototype._binarizeB3DM = function ( b3dmData.featureTableBinaryByteLength[0] = 0; b3dmData.batchTable.set(batchTableJSONData); - for (let index = paddingStart; index < paddingStop; ++index) { + for (var index = paddingStart; index < paddingStop; ++index) { b3dmData.batchTable[index] = 0x20; } b3dmData.batchTableJSONByteLength[0] = batchTableLength; @@ -748,8 +757,8 @@ function _WGS84ToCartesian(long, lat, height) { * @private */ function _longLatsToMeter(longitude1, latitude1, longitude2, latitude2) { - let p1 = _WGS84ToCartesian(longitude1, latitude1, 0); - let p2 = _WGS84ToCartesian(longitude2, latitude2, 0); + var p1 = _WGS84ToCartesian(longitude1, latitude1, 0); + var p2 = _WGS84ToCartesian(longitude2, latitude2, 0); return Cartesian3.distance(p1, p2); } @@ -758,7 +767,7 @@ function _longLatsToMeter(longitude1, latitude1, longitude2, latitude2) { * @private */ function _computeExtent(minLongitude, minLatitude, maxLongitude, maxLatitude) { - let extent = { + var extent = { minLongitude: minLongitude, maxLongitude: maxLongitude, minLatitude: minLatitude, @@ -865,12 +874,12 @@ I3SSceneServer.prototype.load = function (uri) { return this._dataSource._loadJson( uri, - (data, resolve) => { + function (data, resolve) { // Success this._data = data; - let layerPromises = []; - for (let layer of this._data.layers) { - let newLayer = new I3SLayer( + var layerPromises = []; + for (var layer of this._data.layers) { + var newLayer = new I3SLayer( this, layer, this._data.layers.indexOf(layer) @@ -879,18 +888,20 @@ I3SSceneServer.prototype.load = function (uri) { layerPromises.push(newLayer.load()); } - _Promise.all(layerPromises).then(() => { - this._computeExtent(); + _Promise.all(layerPromises).then( + function () { + this._computeExtent(); - if (this._dataSource._autoCenterCameraOnStart) { - this.centerCamera("topdown"); - } + if (this._dataSource._autoCenterCameraOnStart) { + this.centerCamera("topdown"); + } - resolve(); - this._createVisualElements(); - }); - }, - (reject) => { + resolve(); + this._createVisualElements(); + }.bind(this) + ); + }.bind(this), + function (reject) { // Fail reject(); } @@ -931,15 +942,15 @@ I3SSceneServer.prototype.centerCamera = function (mode) { * @private */ I3SSceneServer.prototype._computeExtent = function () { - let minLongitude = Number.MAX_VALUE; - let maxLongitude = -Number.MAX_VALUE; - let minLatitude = Number.MAX_VALUE; - let maxLatitude = -Number.MAX_VALUE; + var minLongitude = Number.MAX_VALUE; + var maxLongitude = -Number.MAX_VALUE; + var minLatitude = Number.MAX_VALUE; + var maxLatitude = -Number.MAX_VALUE; // Compute the extent from all layers - for (let layer of this._layerCollection) { + for (var layer of this._layerCollection) { if (layer._data.store && layer._data.store.extent) { - let layerExtent = layer._data.store.extent; + var layerExtent = layer._data.store.extent; minLongitude = Math.min(minLongitude, layerExtent[0]); minLatitude = Math.min(minLatitude, layerExtent[1]); maxLongitude = Math.max(maxLongitude, layerExtent[2]); @@ -1013,7 +1024,7 @@ function I3SLayer(sceneServer, layerData, index) { } this._uri = layerData.href; - let query = ""; + var query = ""; if (this._dataSource._query && this._dataSource._query !== "") { query = "?" + this._dataSource._query; } @@ -1105,33 +1116,33 @@ function GetCoveredTiles(terrainProvider, extents) { } function GetTiles(terrainProvider, extents) { - let tilingScheme = terrainProvider.tilingScheme; + var tilingScheme = terrainProvider.tilingScheme; // Sort points into a set of tiles - let tileRequests = []; // Result will be an Array as it's easier to work with - let tileRequestSet = {}; // A unique set + var tileRequests = []; // Result will be an Array as it's easier to work with + var tileRequestSet = {}; // A unique set - let maxLevel = terrainProvider._lodCount; + var maxLevel = terrainProvider._lodCount; - let minCorner = Cartographic.fromDegrees( + var minCorner = Cartographic.fromDegrees( extents.minLongitude, extents.minLatitude ); - let maxCorner = Cartographic.fromDegrees( + var maxCorner = Cartographic.fromDegrees( extents.maxLongitude, extents.maxLatitude ); - let minCornerXY = tilingScheme.positionToTileXY(minCorner, maxLevel); - let maxCornerXY = tilingScheme.positionToTileXY(maxCorner, maxLevel); + var minCornerXY = tilingScheme.positionToTileXY(minCorner, maxLevel); + var maxCornerXY = tilingScheme.positionToTileXY(maxCorner, maxLevel); //Get all the tiles in between - for (let x = minCornerXY.x; x <= maxCornerXY.x; x++) { - for (let y = minCornerXY.y; y <= maxCornerXY.y; y++) { - let xy = new Cartesian2(x, y); - let key = xy.toString(); + for (var x = minCornerXY.x; x <= maxCornerXY.x; x++) { + for (var y = minCornerXY.y; y <= maxCornerXY.y; y++) { + var xy = new Cartesian2(x, y); + var key = xy.toString(); if (!tileRequestSet.hasOwnProperty(key)) { // When tile is requested for the first time - let value = { + var value = { x: xy.x, y: xy.y, level: maxLevel, @@ -1147,7 +1158,7 @@ function GetTiles(terrainProvider, extents) { // Send request for each required tile var tilePromises = []; - for (let i = 0; i < tileRequests.length; ++i) { + for (var i = 0; i < tileRequests.length; ++i) { var tileRequest = tileRequests[i]; var requestPromise = tileRequest.terrainProvider.requestTileGeometry( tileRequest.x, @@ -1158,24 +1169,24 @@ function GetTiles(terrainProvider, extents) { tilePromises.push(requestPromise); } - return when.all(tilePromises).then((heightMapBuffers) => { - let heightMaps = []; - let tilesAreReady = new Array(); - for (let i in heightMapBuffers) { - let options = { + return when.all(tilePromises).then(function (heightMapBuffers) { + var heightMaps = []; + var tilesAreReady = new Array(); + for (var i in heightMapBuffers) { + var options = { tilingScheme: tilingScheme, x: tileRequests[i].x, y: tileRequests[i].y, level: tileRequests[i].level, }; - let heightMap = heightMapBuffers[i]; + var heightMap = heightMapBuffers[i]; - let projectionType = "Geographic"; + var projectionType = "Geographic"; if (tilingScheme._projection instanceof WebMercatorProjection) { projectionType = "WebMercator"; } - let heightMapData = { + var heightMapData = { projectionType: projectionType, projection: tilingScheme._projection, nativeExtent: tilingScheme.tileXYToNativeRectangle( @@ -1190,7 +1201,7 @@ function GetTiles(terrainProvider, extents) { }; if (heightMap._encoding == HeightmapEncoding.LERC) { - let result = Lerc.decode(heightMap._buffer); + var result = Lerc.decode(heightMap._buffer); heightMapData.buffer = result.pixels[0]; } else { heightMapData.buffer = heightMap._buffer; @@ -1208,63 +1219,72 @@ function GetTiles(terrainProvider, extents) { * @returns {Promise} a promise that is resolved when the layer data is loaded */ I3SLayer.prototype.load = function () { - return new _Promise((resolve, reject) => { - this._computeExtent(); + return new _Promise( + function (resolve, reject) { + this._computeExtent(); - //Load tiles from arcgis + //Load tiles from arcgis - var geoidTerrainProvider = this._dataSource._geoidTiledTerrainProvider; + var geoidTerrainProvider = this._dataSource._geoidTiledTerrainProvider; - let dataIsReady = new when.defer(); - let geoidDataList = []; - if (defined(geoidTerrainProvider)) { - if (geoidTerrainProvider.ready) { - let tilesReadyPromise = GetCoveredTiles( - geoidTerrainProvider, - this._extent - ); - when(tilesReadyPromise, function (heightMaps) { - geoidDataList = heightMaps; + var dataIsReady = new when.defer(); + var geoidDataList = []; + if (defined(geoidTerrainProvider)) { + if (geoidTerrainProvider.ready) { + var tilesReadyPromise = GetCoveredTiles( + geoidTerrainProvider, + this._extent + ); + when(tilesReadyPromise, function (heightMaps) { + geoidDataList = heightMaps; + dataIsReady.resolve(); + }); + } else { + console.log( + "Geoid Terrain service not available - no geoid conversion will be performed." + ); dataIsReady.resolve(); - }); + } } else { console.log( - "Geoid Terrain service not available - no geoid conversion will be performed." + "No Geoid Terrain service provided - no geoid conversion will be performed." ); dataIsReady.resolve(); } - } else { - console.log( - "No Geoid Terrain service provided - no geoid conversion will be performed." - ); - dataIsReady.resolve(); - } - dataIsReady.then(() => { - this._dataSource._geoidDataList = geoidDataList; - console.log("Starting to load visual elements"); - this._createVisualElements(); - if (this._data.spatialReference.wkid === 4326) { - this._loadNodePage(0).then(() => { - this._loadRootNode().then(() => { - this._create3DTileSet(); - if (this._data.store.version === "1.6") { - this._rootNode._loadChildren().then(() => { - resolve(); - }); - } else { - resolve(); - } - }); - }); - } else { - console.log( - "Unsupported spatial reference: " + this._data.spatialReference.wkid - ); - resolve(); - } - }); - }); + dataIsReady.then( + function () { + this._dataSource._geoidDataList = geoidDataList; + console.log("Starting to load visual elements"); + this._createVisualElements(); + if (this._data.spatialReference.wkid === 4326) { + this._loadNodePage(0).then( + function () { + this._loadRootNode().then( + function () { + this._create3DTileSet(); + if (this._data.store.version === "1.6") { + this._rootNode._loadChildren().then(function () { + resolve(); + }); + } else { + resolve(); + } + }.bind(this) + ); + }.bind(this) + ); + } else { + console.log( + "Unsupported spatial reference: " + + this._data.spatialReference.wkid + ); + resolve(); + } + }.bind(this) + ); + }.bind(this) + ); }; /** @@ -1278,24 +1298,24 @@ I3SLayer.prototype._computeGeometryDefinitions = function (useCompression) { this._geometryDefinitions = []; if (this._data.geometryDefinitions) { - for (let geometryDefinition of this._data.geometryDefinitions) { - let geometryBuffersInfo = []; - let geometryBuffers = geometryDefinition.geometryBuffers; + for (var geometryDefinition of this._data.geometryDefinitions) { + var geometryBuffersInfo = []; + var geometryBuffers = geometryDefinition.geometryBuffers; - for (let geometryBuffer of geometryBuffers) { - let collectedAttributes = []; - let compressed = false; + for (var geometryBuffer of geometryBuffers) { + var collectedAttributes = []; + var compressed = false; if (geometryBuffer.compressedAttributes && useCompression) { // check if compressed compressed = true; - let attributes = geometryBuffer.compressedAttributes.attributes; - for (let attribute of attributes) { + var attributes = geometryBuffer.compressedAttributes.attributes; + for (var attribute of attributes) { collectedAttributes.push(attribute); } } else { // uncompressed attributes - for (let attribute in geometryBuffer) { + for (var attribute in geometryBuffer) { if (attribute !== "offset") { collectedAttributes.push(attribute); } @@ -1310,7 +1330,7 @@ I3SLayer.prototype._computeGeometryDefinitions = function (useCompression) { } // rank the buffer info - geometryBuffersInfo.sort((a, b) => { + geometryBuffersInfo.sort(function (a, b) { if (a.compressed && !b.compressed) { return -1; } else if (!a.compressed && b.compressed) { @@ -1334,14 +1354,14 @@ I3SLayer.prototype._findBestGeometryBuffers = function ( // based on the required attributes, and by favouring // compression to improve bandwidth requirements - let geometryDefinition = this._geometryDefinitions[definition]; + var geometryDefinition = this._geometryDefinitions[definition]; if (geometryDefinition) { - for (let index = 0; index < geometryDefinition.length; ++index) { - let geometryBufferInfo = geometryDefinition[index]; - let missed = false; - let geometryAttributes = geometryBufferInfo.attributes; - for (let attribute of attributes) { + for (var index = 0; index < geometryDefinition.length; ++index) { + var geometryBufferInfo = geometryDefinition[index]; + var missed = false; + var geometryAttributes = geometryBufferInfo.attributes; + for (var attribute of attributes) { if (!geometryAttributes.includes(attribute)) { missed = true; break; @@ -1365,7 +1385,7 @@ I3SLayer.prototype._findBestGeometryBuffers = function ( */ I3SLayer.prototype._loadRootNode = function () { if (this._data.nodePages) { - let rootIndex = 0; + var rootIndex = 0; if (this._data.nodePages.rootIndex !== undefined) { rootIndex = this._data.nodePages.rootIndex; } @@ -1381,11 +1401,11 @@ I3SLayer.prototype._loadRootNode = function () { * @private */ I3SLayer.prototype._getNodeInNodePages = function (nodeIndex) { - let Index = Math.floor(nodeIndex / this._data.nodePages.nodesPerPage); - let offsetInPage = nodeIndex % this._data.nodePages.nodesPerPage; - let that = this; - return new _Promise((resolve, reject) => { - that._loadNodePage(Index).then(() => { + var Index = Math.floor(nodeIndex / this._data.nodePages.nodesPerPage); + var offsetInPage = nodeIndex % this._data.nodePages.nodesPerPage; + var that = this; + return new _Promise(function (resolve, reject) { + that._loadNodePage(Index).then(function () { resolve(that._nodePages[Index][offsetInPage]); }); }); @@ -1395,65 +1415,70 @@ I3SLayer.prototype._getNodeInNodePages = function (nodeIndex) { * @private */ I3SLayer.prototype._loadNodePage = function (page) { - let that = this; - return new _Promise((resolve, reject) => { - if (that._nodePages[page] !== undefined) { - resolve(); - } else if (that._nodePageFetches[page] !== undefined) { - that._nodePageFetches[page]._promise = that._nodePageFetches[ - page - ]._promise.then(() => { + var that = this; + return new _Promise( + function (resolve, reject) { + if (that._nodePages[page] !== undefined) { resolve(); - }); - } else { - let query = ""; - if (this._dataSource._query && this._dataSource._query !== "") { - query = "?" + this._dataSource._query; - } + } else if (that._nodePageFetches[page] !== undefined) { + that._nodePageFetches[page]._promise = that._nodePageFetches[ + page + ]._promise.then(function () { + resolve(); + }); + } else { + var query = ""; + if (this._dataSource._query && this._dataSource._query !== "") { + query = "?" + this._dataSource._query; + } - let nodePageURI = this._completeUriWithoutQuery + "/nodepages/"; - nodePageURI += page + query; + var nodePageURI = this._completeUriWithoutQuery + "/nodepages/"; + nodePageURI += page + query; - that._nodePageFetches[page] = {}; - that._nodePageFetches[page]._promise = new _Promise((resolve, reject) => { - that._nodePageFetches[page]._resolve = resolve; - }); + that._nodePageFetches[page] = {}; + that._nodePageFetches[page]._promise = new _Promise(function ( + resolve, + reject + ) { + that._nodePageFetches[page]._resolve = resolve; + }); - let _resolve = function () { - // resolve the chain of promises - that._nodePageFetches[page]._resolve(); - delete that._nodePageFetches[page]; - resolve(); - }; + var _resolve = function () { + // resolve the chain of promises + that._nodePageFetches[page]._resolve(); + delete that._nodePageFetches[page]; + resolve(); + }; - fetch(nodePageURI) - .then((response) => { - response - .json() - .then((data) => { - if (data.error && data.error.code !== 200) { + fetch(nodePageURI) + .then(function (response) { + response + .json() + .then(function (data) { + if (data.error && data.error.code !== 200) { + _resolve(); + } else { + that._nodePages[page] = data.nodes; + _resolve(); + } + }) + .catch(function () { _resolve(); - } else { - that._nodePages[page] = data.nodes; - _resolve(); - } - }) - .catch(() => { - _resolve(); - }); - }) - .catch(() => { - _resolve(); - }); - } - }); + }); + }) + .catch(function () { + _resolve(); + }); + } + }.bind(this) + ); }; /** * @private */ I3SLayer.prototype._computeExtent = function () { - let layerExtent = this._data.store.extent; + var layerExtent = this._data.store.extent; this._extent = _computeExtent( layerExtent[0], layerExtent[1], @@ -1494,7 +1519,7 @@ I3SLayer.prototype._createVisualElements = function () { * @private */ I3SLayer.prototype._create3DTileSet = function () { - let inPlaceTileset = { + var inPlaceTileset = { asset: { version: "1.0", }, @@ -1502,11 +1527,11 @@ I3SLayer.prototype._create3DTileSet = function () { root: this._rootNode._create3DTileDefinition(), }; - let tilesetBlob = new Blob([JSON.stringify(inPlaceTileset)], { + var tilesetBlob = new Blob([JSON.stringify(inPlaceTileset)], { type: "application/json", }); - let inPlaceTilesetURL = URL.createObjectURL(tilesetBlob); + var inPlaceTilesetURL = URL.createObjectURL(tilesetBlob); this._tileset = this._dataSource._scene.primitives.add( new Cesium3DTileset({ @@ -1518,20 +1543,22 @@ I3SLayer.prototype._create3DTileSet = function () { this._tileset._isI3STileSet = true; - this._tileset.readyPromise.then(() => { - this._tileset.tileLoad.addEventListener((tile) => {}); + this._tileset.readyPromise.then( + function () { + this._tileset.tileLoad.addEventListener(function (tile) {}); - this._tileset.tileUnload.addEventListener((tile) => { - tile._i3sNode._clearGeometryData(); - tile._contentResource._url = tile._i3sNode._completeUriWithoutQuery; - }); + this._tileset.tileUnload.addEventListener(function (tile) { + tile._i3sNode._clearGeometryData(); + tile._contentResource._url = tile._i3sNode._completeUriWithoutQuery; + }); - this._tileset.tileVisible.addEventListener((tile) => { - if (tile._i3sNode) { - tile._i3sNode._loadChildren(); - } - }); - }); + this._tileset.tileVisible.addEventListener(function (tile) { + if (tile._i3sNode) { + tile._i3sNode._loadChildren(); + } + }); + }.bind(this) + ); }; /* @@ -1583,7 +1610,7 @@ function I3SNode(parent, ref) { this._uri = ref; } - let query = ""; + var query = ""; if (this._dataSource._query && this._dataSource._query !== "") { query = "?" + this._dataSource._query; } @@ -1708,20 +1735,20 @@ Object.defineProperties(I3SNode.prototype, { * @returns {Promise} a promise that is resolved when the I3S Node data is loaded */ I3SNode.prototype.load = function (isRoot) { - let that = this; + var that = this; function processData() { that._createVisualElements(); if (!isRoot) { // Create a new tile - let tileDefinition = that._create3DTileDefinition(); + var tileDefinition = that._create3DTileDefinition(); - let tileBlob = new Blob([JSON.stringify(tileDefinition)], { + var tileBlob = new Blob([JSON.stringify(tileDefinition)], { type: "application/json", }); - let inPlaceTileURL = URL.createObjectURL(tileBlob); - let resource = Resource.createIfNeeded(inPlaceTileURL); + var inPlaceTileURL = URL.createObjectURL(tileBlob); + var resource = Resource.createIfNeeded(inPlaceTileURL); that._tile = new Cesium3DTile( that._layer._tileset, @@ -1736,26 +1763,28 @@ I3SNode.prototype.load = function (isRoot) { if (this._nodeIndex === undefined) { return this._dataSource._loadJson( this._completeUri, - (data, resolve) => { + function (data, resolve) { // Success that._data = data; processData(); resolve(); }, - (reject) => { + function (reject) { // Fail reject(); } ); } - return new _Promise((resolve, reject) => { - this._layer._getNodeInNodePages(this._nodeIndex).then((data) => { - that._data = data; - processData(); - resolve(); - }); - }); + return new _Promise( + function (resolve, reject) { + this._layer._getNodeInNodePages(this._nodeIndex).then(function (data) { + that._data = data; + processData(); + resolve(); + }); + }.bind(this) + ); }; /** @@ -1764,18 +1793,18 @@ I3SNode.prototype.load = function (isRoot) { */ I3SNode.prototype.loadFields = function () { // check if we must load fields - let fields = this._layer._data.attributeStorageInfo; - let fieldPromiseReady = when.defer(); + var fields = this._layer._data.attributeStorageInfo; + var fieldPromiseReady = when.defer(); - let that = this; + var that = this; function createAndLoadField(fields, index) { if (!fields || index >= fields.length) { return fieldPromiseReady.resolve(); } - let newField = new I3SField(that, fields[index]); + var newField = new I3SField(that, fields[index]); that._fields[newField._storageInfo.name] = newField; - newField.load().then(() => { + newField.load().then(function () { createAndLoadField(fields, index + 1); }); } @@ -1788,60 +1817,64 @@ I3SNode.prototype.loadFields = function () { * @private */ I3SNode.prototype._loadChildren = function (waitAllChildren) { - return new _Promise((resolve, reject) => { - if (!this._childrenAreLoaded) { - this._childrenAreLoaded = true; - let childPromises = []; - if (this._data.children) { - for (let child of this._data.children) { - let newChild = new I3SNode(this, child.href ? child.href : child); - this._children.push(newChild); - let childIsLoaded = newChild.load(); - if (waitAllChildren) { - childPromises.push(childIsLoaded); + return new _Promise( + function (resolve, reject) { + if (!this._childrenAreLoaded) { + this._childrenAreLoaded = true; + var childPromises = []; + if (this._data.children) { + for (var child of this._data.children) { + var newChild = new I3SNode(this, child.href ? child.href : child); + this._children.push(newChild); + var childIsLoaded = newChild.load(); + if (waitAllChildren) { + childPromises.push(childIsLoaded); + } + childIsLoaded.then( + function () { + this._tile.children.push(newChild._tile); + }.bind(this) + ); } - childIsLoaded.then(() => { - this._tile.children.push(newChild._tile); - }); - } - if (waitAllChildren) { - _Promise.all(childPromises).then(() => { + if (waitAllChildren) { + _Promise.all(childPromises).then(function () { + resolve(); + }); + } else { resolve(); - }); + } } else { resolve(); } } else { resolve(); } - } else { - resolve(); - } - }); + }.bind(this) + ); }; /** * @private */ I3SNode.prototype._loadGeometryData = function () { - let geometryPromises = []; + var geometryPromises = []; // To debug decoding for a specific tile, add a condition // that wraps this if/else to match the tile uri if (this._data.geometryData) { - for (let geometry of this._data.geometryData) { - let newGeometryData = new I3SGeometry(this, geometry.href); + for (var geometry of this._data.geometryData) { + var newGeometryData = new I3SGeometry(this, geometry.href); this._geometryData.push(newGeometryData); geometryPromises.push(newGeometryData.load()); } } else if (this._data.mesh) { - let geometryDefinition = this._layer._findBestGeometryBuffers( + var geometryDefinition = this._layer._findBestGeometryBuffers( this._data.mesh.geometry.definition, ["position", "uv0"] ); - let geometryURI = "./geometries/" + geometryDefinition.bufferIndex; - let newGeometryData = new I3SGeometry(this, geometryURI); + var geometryURI = "./geometries/" + geometryDefinition.bufferIndex; + var newGeometryData = new I3SGeometry(this, geometryURI); newGeometryData._geometryDefinitions = geometryDefinition.definition; newGeometryData._geometryBufferInfo = geometryDefinition.geometryBufferInfo; this._geometryData.push(newGeometryData); @@ -1855,13 +1888,13 @@ I3SNode.prototype._loadGeometryData = function () { * @private */ I3SNode.prototype._loadFeatureData = function () { - let featurePromises = []; + var featurePromises = []; // To debug decoding for a specific tile, add a condition // that wraps this if/else to match the tile uri if (this._data.featureData) { - for (let feature of this._data.featureData) { - let newfeatureData = new I3SFeature(this, feature.href); + for (var feature of this._data.featureData) { + var newfeatureData = new I3SFeature(this, feature.href); this._featureData.push(newfeatureData); featurePromises.push(newfeatureData.load()); } @@ -1881,11 +1914,11 @@ I3SNode.prototype._clearGeometryData = function () { * @private */ I3SNode.prototype._create3DTileDefinition = function () { - let obb = this._data.obb; - let mbs = this._data.mbs; + var obb = this._data.obb; + var mbs = this._data.mbs; - let boundingVolume = {}; - let position; + var boundingVolume = {}; + var position; if (obb) { boundingVolume = { @@ -1915,9 +1948,9 @@ I3SNode.prototype._create3DTileDefinition = function () { } // compute the geometric error - let metersPerPixel = Infinity; + var metersPerPixel = Infinity; - let span = 0; + var span = 0; if (this._data.mbs) { span = this._data.mbs[3]; } else if (this._data.obb) { @@ -1933,14 +1966,14 @@ I3SNode.prototype._create3DTileDefinition = function () { this._layer._data.nodePages.lodSelectionMetricType === "maxScreenThresholdSQ" ) { - let maxScreenThreshold = + var maxScreenThreshold = Math.sqrt(this._data.lodThreshold) / (Math.PI * 0.25); metersPerPixel = span / maxScreenThreshold; } else { console.error("Unsupported lodSelectionMetricType in Layer"); } } else if (this._data.lodSelection !== undefined) { - for (let lodSelection of this._data.lodSelection) { + for (var lodSelection of this._data.lodSelection) { if (lodSelection.metricType === "maxScreenThreshold") { metersPerPixel = span / lodSelection.maxError; } @@ -1952,11 +1985,11 @@ I3SNode.prototype._create3DTileDefinition = function () { } // calculate the length of 16 pixels in order to trigger the screen space error - let geometricError = metersPerPixel * 16; + var geometricError = metersPerPixel * 16; // transformations - let hpr = new HeadingPitchRoll(0, 0, 0); - let orientation = Transforms.headingPitchRollQuaternion(position, hpr); + var hpr = new HeadingPitchRoll(0, 0, 0); + var orientation = Transforms.headingPitchRollQuaternion(position, hpr); if (this._data.obb) { orientation = new Quaternion( @@ -1993,7 +2026,7 @@ I3SNode.prototype._create3DTileDefinition = function () { this.inverseGlobalTransform = new Matrix4(); Matrix4.inverse(this._globalTransforms, this.inverseGlobalTransform); - let localTransforms = this._globalTransforms.clone(); + var localTransforms = this._globalTransforms.clone(); if (this._parent._globalTransforms) { Matrix4.multiply( @@ -2004,13 +2037,13 @@ I3SNode.prototype._create3DTileDefinition = function () { } // get children definition - let childrenDefinition = []; - for (let child of this._children) { + var childrenDefinition = []; + for (var child of this._children) { childrenDefinition.push(child._create3DTileDefinition()); } // Create a tile set - let inPlaceTileDefinition = { + var inPlaceTileDefinition = { children: childrenDefinition, refine: "REPLACE", boundingVolume: boundingVolume, @@ -2050,8 +2083,8 @@ I3SNode.prototype._create3DTileDefinition = function () { * @private */ I3SNode.prototype._scheduleCreateContentURL = function () { - let that = this; - return new _Promise((resolve, reject) => { + var that = this; + return new _Promise(function (resolve, reject) { that._createContentURL(resolve, that._tile); }); }; @@ -2060,7 +2093,7 @@ I3SNode.prototype._scheduleCreateContentURL = function () { * @private */ I3SNode.prototype._createContentURL = function (resolve, tile) { - let rawGLTF = { + var rawGLTF = { scene: 0, scenes: [ { @@ -2086,49 +2119,58 @@ I3SNode.prototype._createContentURL = function (resolve, tile) { }; // Feature Table - let featureTableJSON = JSON.stringify({ BATCH_LENGTH: 0 }); + var featureTableJSON = JSON.stringify({ BATCH_LENGTH: 0 }); // Batch Table - let batchTableJSON = JSON.stringify({}); + var batchTableJSON = JSON.stringify({}); // Load the geometry data - let dataPromises = [this._loadFeatureData(), this._loadGeometryData()]; - - _Promise.all(dataPromises).then(() => { - // Binary GLTF - let generateGLTF = new _Promise((resolve, reject) => { - if (this._geometryData && this._geometryData.length > 0) { - let task = this._dataSource._GLTFProcessingQueue.addTask({ - geometryData: this._geometryData[0], - featureData: this._featureData, - defaultGeometrySchema: this._layer._data.store.defaultGeometrySchema, - url: this._geometryData[0]._completeUri, - tile: this._tile, - }); - task.then((data) => { - rawGLTF = data.gltfData; - this._geometryData[0].customAttributes = data.customAttributes; - resolve(); - }); - } else { - resolve(); - } - }); + var dataPromises = [this._loadFeatureData(), this._loadGeometryData()]; + + _Promise.all(dataPromises).then( + function () { + // Binary GLTF + var generateGLTF = new _Promise( + function (resolve, reject) { + if (this._geometryData && this._geometryData.length > 0) { + var task = this._dataSource._GLTFProcessingQueue.addTask({ + geometryData: this._geometryData[0], + featureData: this._featureData, + defaultGeometrySchema: this._layer._data.store + .defaultGeometrySchema, + url: this._geometryData[0]._completeUri, + tile: this._tile, + }); + task.then( + function (data) { + rawGLTF = data.gltfData; + this._geometryData[0].customAttributes = data.customAttributes; + resolve(); + }.bind(this) + ); + } else { + resolve(); + } + }.bind(this) + ); - generateGLTF.then(() => { - let binaryGLTFData = this._dataSource._binarizeGLTF(rawGLTF); - let b3dmRawData = this._dataSource._binarizeB3DM( - featureTableJSON, - batchTableJSON, - binaryGLTFData + generateGLTF.then( + function () { + var binaryGLTFData = this._dataSource._binarizeGLTF(rawGLTF); + var b3dmRawData = this._dataSource._binarizeB3DM( + featureTableJSON, + batchTableJSON, + binaryGLTFData + ); + var b3dmDataBlob = new Blob([b3dmRawData], { + type: "application/binary", + }); + this._b3dmURL = URL.createObjectURL(b3dmDataBlob); + resolve(); + }.bind(this) ); - let b3dmDataBlob = new Blob([b3dmRawData], { - type: "application/binary", - }); - this._b3dmURL = URL.createObjectURL(b3dmDataBlob); - resolve(); - }); - }); + }.bind(this) + ); }; /** @@ -2138,19 +2180,19 @@ I3SNode.prototype._createVisualElements = function () { if (!this._dataSource._traceVisuals) { return; } - let obb = this._data.obb; - let mbs = this._data.mbs; + var obb = this._data.obb; + var mbs = this._data.mbs; if (obb) { // Add an entity for display - let orientation = new Quaternion( + var orientation = new Quaternion( obb.quaternion[0], obb.quaternion[1], obb.quaternion[2], obb.quaternion[3] ); - let position = _WGS84ToCartesian( + var position = _WGS84ToCartesian( obb.center[0], obb.center[1], obb.center[2] @@ -2172,7 +2214,7 @@ I3SNode.prototype._createVisualElements = function () { }, }); } else if (mbs) { - let position = _WGS84ToCartesian(mbs[0], mbs[1], mbs[2]); + var position = _WGS84ToCartesian(mbs[0], mbs[1], mbs[2]); // Add an entity for display this._entities.locator = this._dataSource.entities.add({ @@ -2211,7 +2253,7 @@ function I3SFeature(parent, uri) { this._dataSource = parent._dataSource; this._layer = parent._layer; this._uri = uri; - let query = ""; + var query = ""; if (this._dataSource._query && this._dataSource._query !== "") { query = "?" + this._dataSource._query; } @@ -2261,11 +2303,11 @@ Object.defineProperties(I3SFeature.prototype, { I3SFeature.prototype.load = function () { return this._dataSource._loadJson( this._completeUri, - (data, resolve) => { + function (data, resolve) { this._data = data; resolve(); - }, - (reject) => { + }.bind(this), + function (reject) { reject(); } ); @@ -2293,7 +2335,7 @@ function I3SField(parent, storageInfo) { this._parent = parent; this._dataSource = parent._dataSource; this._uri = "/attributes/" + storageInfo.key + "/0"; - let query = ""; + var query = ""; if (this._dataSource._query && this._dataSource._query !== "") { query = "?" + this._dataSource._query; } @@ -2365,13 +2407,13 @@ Object.defineProperties(I3SField.prototype, { I3SField.prototype.load = function () { return this._dataSource._loadBinary( this._completeUri, - (data, resolve) => { + function (data, resolve) { // check if we have a 404 - let dataView = new DataView(data); - let success = true; + var dataView = new DataView(data); + var success = true; if (dataView.getUint8(0) === "{".charCodeAt(0)) { - let textContent = new TextDecoder(); - let str = textContent.decode(data); + var textContent = new TextDecoder(); + var str = textContent.decode(data); if (str.includes("404")) { success = false; console.error("Failed to load:", this._completeUri); @@ -2380,7 +2422,7 @@ I3SField.prototype.load = function () { if (success) { this._data = data; - let offset = this._parseHeader(dataView); + var offset = this._parseHeader(dataView); // @TODO: find out why we must skip 4 bytes when the value type is float 64 if ( @@ -2395,8 +2437,8 @@ I3SField.prototype.load = function () { } resolve(); - }, - (reject) => { + }.bind(this), + function (reject) { reject(); } ); @@ -2406,7 +2448,7 @@ I3SField.prototype.load = function () { * @private */ I3SField.prototype._parseValue = function (dataView, type, offset) { - let value = null; + var value = null; if (type === "UInt8") { value = dataView.getUint8(offset); offset += 1; @@ -2455,10 +2497,10 @@ I3SField.prototype._parseValue = function (dataView, type, offset) { * @private */ I3SField.prototype._parseHeader = function (dataView) { - let offset = 0; + var offset = 0; this._header = {}; - for (let item of this._storageInfo.header) { - let parsedValue = this._parseValue(dataView, item.valueType, offset); + for (var item of this._storageInfo.header) { + var parsedValue = this._parseValue(dataView, item.valueType, offset); this._header[item.property] = parsedValue.value; offset = parsedValue.offset; } @@ -2470,20 +2512,20 @@ I3SField.prototype._parseHeader = function (dataView) { */ I3SField.prototype._parseBody = function (dataView, offset) { this._values = {}; - for (let item of this._storageInfo.ordering) { - let desc = this._storageInfo[item]; + for (var item of this._storageInfo.ordering) { + var desc = this._storageInfo[item]; if (desc) { this._values[item] = []; - for (let index = 0; index < this._header.count; ++index) { + for (var index = 0; index < this._header.count; ++index) { if (desc.valueType !== "String") { - let parsedValue = this._parseValue(dataView, desc.valueType, offset); + var parsedValue = this._parseValue(dataView, desc.valueType, offset); this._values[item].push(parsedValue.value); offset = parsedValue.offset; } else { - let stringLen = this._values.attributeByteCounts[index]; - let stringContent = ""; - for (let cIndex = 0; cIndex < stringLen; ++cIndex) { - let parsedValue = this._parseValue( + var stringLen = this._values.attributeByteCounts[index]; + var stringContent = ""; + for (var cIndex = 0; cIndex < stringLen; ++cIndex) { + var parsedValue = this._parseValue( dataView, desc.valueType, offset @@ -2521,7 +2563,7 @@ function I3SGeometry(parent, uri) { this._dataSource = parent._dataSource; this._layer = parent._layer; this._uri = uri; - let query = ""; + var query = ""; if (this._dataSource._query && this._dataSource._query !== "") { query = "?" + this._dataSource._query; } @@ -2581,11 +2623,11 @@ Object.defineProperties(I3SGeometry.prototype, { I3SGeometry.prototype.load = function () { return this._dataSource._loadBinary( this._completeUri, - (data, resolve) => { + function (data, resolve) { this._data = data; resolve(); - }, - (reject) => { + }.bind(this), + function (reject) { reject(); } ); @@ -2604,24 +2646,24 @@ I3SGeometry.prototype.load = function () { */ I3SGeometry.prototype.getClosestPointIndex = function (px, py, pz) { if (this.customAttributes && this.customAttributes.positions) { - let transformation = new Matrix4(); + var transformation = new Matrix4(); transformation = Matrix4.inverse( this._parent._tile.computedTransform, transformation ); // convert queried position to local - let position = new Cartesian4(px, py, pz, 1); + var position = new Cartesian4(px, py, pz, 1); position = Matrix4.multiplyByVector(transformation, position, position); // Brute force lookup, @TODO: this can be improved with a spatial partitioning search system - let count = this.customAttributes.positions.length; - let bestIndex = -1; - let bestDistanceSquared = Number.MAX_VALUE; - let bestX, bestY, bestZ; - let x, y, z, distanceSquared; - let positions = this.customAttributes.positions; - for (let loop = 0; loop < count; loop += 3) { + var count = this.customAttributes.positions.length; + var bestIndex = -1; + var bestDistanceSquared = Number.MAX_VALUE; + var bestX, bestY, bestZ; + var x, y, z, distanceSquared; + var positions = this.customAttributes.positions; + for (var loop = 0; loop < count; loop += 3) { x = positions[loop] - position.x; y = positions[loop + 1] - position.y; z = positions[loop + 2] - position.z; @@ -2670,18 +2712,18 @@ I3SGeometry.prototype._generateGLTF = function ( bufferViews, accessors ) { - let query = ""; + var query = ""; if (this._dataSource._query && this._dataSource._query !== "") { query = "?" + this._dataSource._query; } // Get the material definition - let materialInfo = this._parent._data.mesh + var materialInfo = this._parent._data.mesh ? this._parent._data.mesh.material : null; - let materialIndex = 0; - let isTextured = false; - let gltfMaterial = { + var materialIndex = 0; + var isTextured = false; + var gltfMaterial = { pbrMetallicRoughness: { metallicFactor: 0.0, }, @@ -2693,7 +2735,7 @@ I3SGeometry.prototype._generateGLTF = function ( materialIndex = materialInfo.definition; } - let materialDefinition; + var materialDefinition; if (this._layer._data.materialDefinitions) { materialDefinition = this._layer._data.materialDefinitions[materialIndex]; } @@ -2710,7 +2752,7 @@ I3SGeometry.prototype._generateGLTF = function ( } } - let texturePath; + var texturePath; if (this._parent._data.textureData) { texturePath = @@ -2720,12 +2762,12 @@ I3SGeometry.prototype._generateGLTF = function ( query; } else { // Choose the JPG for the texture - let textureName = "0"; + var textureName = "0"; if (this._layer._data.textureSetDefinitions) { - for (let textureSetDefinition of this._layer._data + for (var textureSetDefinition of this._layer._data .textureSetDefinitions) { - for (let textureFormat of textureSetDefinition.formats) { + for (var textureFormat of textureSetDefinition.formats) { if (textureFormat.format === "jpg") { textureName = textureFormat.name; break; @@ -2745,9 +2787,9 @@ I3SGeometry.prototype._generateGLTF = function ( } } - let gltfTextures = []; - let gltfImages = []; - let gltfSamplers = []; + var gltfTextures = []; + var gltfImages = []; + var gltfSamplers = []; if (isTextured) { gltfTextures = [ @@ -2775,7 +2817,7 @@ I3SGeometry.prototype._generateGLTF = function ( gltfMaterial.pbrMetallicRoughness.baseColorTexture.index = 0; } - let gltfData = { + var gltfData = { scene: 0, scenes: [ { @@ -2827,24 +2869,26 @@ Cesium3DTile.prototype._hookedRequestContent = */ Cesium3DTile.prototype._resolveHookedObject = function () { // Keep a handle on the early promises - let _contentReadyToProcessPromise = this._contentReadyToProcessPromise; - let _contentReadyPromise = this._contentReadyPromise; + var _contentReadyToProcessPromise = this._contentReadyToProcessPromise; + var _contentReadyPromise = this._contentReadyPromise; // Call the real requestContent function this._hookedRequestContent(); // Fulfill the promises if (_contentReadyToProcessPromise) { - this._contentReadyToProcessPromise.then(() => { + this._contentReadyToProcessPromise.then(function () { _contentReadyToProcessPromise.resolve(); }); } if (_contentReadyPromise) { - this._contentReadyPromise.then(() => { - this._isLoading = false; - this._content._contentReadyPromise.resolve(); - }); + this._contentReadyPromise.then( + function () { + this._isLoading = false; + this._content._contentReadyPromise.resolve(); + }.bind(this) + ); } }; @@ -2856,7 +2900,7 @@ Cesium3DTile.prototype.requestContent = function () { if (!this._isLoading) { this._isLoading = true; - let key = this._contentResource._url; + var key = this._contentResource._url; if (this._contentResource._originalUrl) { key = this._contentResource._originalUrl; } @@ -2865,7 +2909,7 @@ Cesium3DTile.prototype.requestContent = function () { key = key.slice(5); } - let content = _i3sContentCache[key]; + var content = _i3sContentCache[key]; if (!content) { console.error("invalid key", key, _i3sContentCache); this._resolveHookedObject(); @@ -2878,14 +2922,16 @@ Cesium3DTile.prototype.requestContent = function () { this._contentReadyToProcessPromise = when.defer(); this._contentReadyPromise = when.defer(); - this._i3sNode._scheduleCreateContentURL().then(() => { - if (!this._contentResource._originalUrl) { - this._contentResource._originalUrl = this._contentResource._url; - } + this._i3sNode._scheduleCreateContentURL().then( + function () { + if (!this._contentResource._originalUrl) { + this._contentResource._originalUrl = this._contentResource._url; + } - this._contentResource._url = this._i3sNode._b3dmURL; - this._resolveHookedObject(); - }); + this._contentResource._url = this._i3sNode._b3dmURL; + this._resolveHookedObject(); + }.bind(this) + ); } // Returns the number of requests @@ -2926,38 +2972,38 @@ Object.defineProperties(Batched3DModel3DTileContent.prototype, { ..######...#######..########..######## */ function _workerCode() { - let _tracecode = false; - let traceCode = function () {}; + var _tracecode = false; + var traceCode = function () {}; if (_tracecode) { traceCode = console.log; } // adapted from Ellipsoid.prototype.geodeticSurfaceNormalCartographic in Ellipsoid.js function geodeticSurfaceNormalCartographic(cartographic, result) { - let longitude = cartographic.longitude; - let latitude = cartographic.latitude; - let cosLatitude = Math.cos(latitude); + var longitude = cartographic.longitude; + var latitude = cartographic.latitude; + var cosLatitude = Math.cos(latitude); - let x = cosLatitude * Math.cos(longitude); - let y = cosLatitude * Math.sin(longitude); - let z = Math.sin(latitude); + var x = cosLatitude * Math.cos(longitude); + var y = cosLatitude * Math.sin(longitude); + var z = Math.sin(latitude); // Normalize - let length = Math.sqrt(x * x + y * y + z * z); + var length = Math.sqrt(x * x + y * y + z * z); result.x = x / length; result.y = y / length; result.z = z / length; } // adapted from Ellipsoid.prototype.cartographicToCartesian in Ellipsoid.js - let n = { x: 0, y: 0, z: 0 }; - let k = { x: 0, y: 0, z: 0 }; + var n = { x: 0, y: 0, z: 0 }; + var k = { x: 0, y: 0, z: 0 }; function cartographicToCartesian(cartographic, ellipsoidRadiiSquare, result) { geodeticSurfaceNormalCartographic(cartographic, n); k.x = ellipsoidRadiiSquare.x * n.x; k.y = ellipsoidRadiiSquare.y * n.y; k.z = ellipsoidRadiiSquare.z * n.z; - let gamma = Math.sqrt(n.x * k.x + n.y * k.y + n.z * k.z); + var gamma = Math.sqrt(n.x * k.x + n.y * k.y + n.z * k.z); k.x /= gamma; k.y /= gamma; k.z /= gamma; @@ -2973,9 +3019,9 @@ function _workerCode() { // adapted from Matrix3.multiplyByVector in Matrix3.js function multiplyByVector(matrix, cartesian, result) { - let vX = cartesian.x; - let vY = cartesian.y; - let vZ = cartesian.z; + var vX = cartesian.x; + var vY = cartesian.y; + var vZ = cartesian.z; result.x = matrix[0] * vX + matrix[3] * vY + matrix[6] * vZ; result.y = matrix[1] * vX + matrix[4] * vY + matrix[7] * vZ; @@ -2983,31 +3029,31 @@ function _workerCode() { } const _degToRad = 0.017453292519943; - let cartographic = { + var cartographic = { longitude: 0, latitude: 0, height: 0, }; - let position = { + var position = { x: 0, y: 0, z: 0, }; - let normal = { + var normal = { x: 0, y: 0, z: 0, }; - let rotatedPosition = { + var rotatedPosition = { x: 0, y: 0, z: 0, }; - let rotatedNormal = { + var rotatedNormal = { x: 0, y: 0, z: 0, @@ -3042,7 +3088,7 @@ function _workerCode() { } function geodeticLatitudeToMercatorAngle(latitude) { - let maximumLatitude = mercatorAngleToGeodeticLatitude(Math.PI); + var maximumLatitude = mercatorAngleToGeodeticLatitude(Math.PI); // Clamp the latitude coordinate to the valid Mercator bounds. if (latitude > maximumLatitude) { @@ -3055,36 +3101,36 @@ function _workerCode() { } function geographicToWebMercator(lon, lat, ellipsoid) { - let semimajorAxis = ellipsoid._maximumRadius; + var semimajorAxis = ellipsoid._maximumRadius; var x = lon * semimajorAxis; var y = geodeticLatitudeToMercatorAngle(lat) * semimajorAxis; - let result = { x: x, y: y }; + var result = { x: x, y: y }; return { x: x, y: y }; } function bilinearInterpolate(tx, ty, h00, h10, h01, h11) { - let a = h00 * (1 - tx) + h10 * tx; - let b = h01 * (1 - tx) + h11 * tx; + var a = h00 * (1 - tx) + h10 * tx; + var b = h01 * (1 - tx) + h11 * tx; return a * (1 - ty) + b * ty; } function sampleMap(u, v, width, data) { - let address = u + v * width; + var address = u + v * width; return data[address]; } function sampleGeoid(sampleX, sampleY, geoidData) { - let extent = geoidData.nativeExtent; - let x = + var extent = geoidData.nativeExtent; + var x = ((sampleX - extent.west) / (extent.east - extent.west)) * (geoidData.width - 1); - let y = + var y = ((sampleY - extent.south) / (extent.north - extent.south)) * (geoidData.height - 1); - let xi = Math.floor(x); - let yi = Math.floor(y); + var xi = Math.floor(x); + var yi = Math.floor(y); x -= xi; y -= yi; @@ -3095,29 +3141,29 @@ function _workerCode() { yi = geoidData.height - 1 - yi; yNext = geoidData.height - 1 - yNext; - let h00 = sampleMap(xi, yi, geoidData.width, geoidData.buffer); - let h10 = sampleMap(xNext, yi, geoidData.width, geoidData.buffer); - let h01 = sampleMap(xi, yNext, geoidData.width, geoidData.buffer); - let h11 = sampleMap(xNext, yNext, geoidData.width, geoidData.buffer); + var h00 = sampleMap(xi, yi, geoidData.width, geoidData.buffer); + var h10 = sampleMap(xNext, yi, geoidData.width, geoidData.buffer); + var h01 = sampleMap(xi, yNext, geoidData.width, geoidData.buffer); + var h11 = sampleMap(xNext, yNext, geoidData.width, geoidData.buffer); - let finalHeight = bilinearInterpolate(x, y, h00, h10, h01, h11); + var finalHeight = bilinearInterpolate(x, y, h00, h10, h01, h11); finalHeight = finalHeight * geoidData.scale + geoidData.offset; return finalHeight; } function sampleGeoidFromList(lon, lat, geoidDataList) { - for (let i in geoidDataList) { - let pt = { + for (var i in geoidDataList) { + var pt = { longitude: lon, latitude: lat, height: 0, }; - let localExtent = geoidDataList[i].nativeExtent; - let lonRadian = (lon / 180) * Math.PI; - let latRadian = (lat / 180) * Math.PI; + var localExtent = geoidDataList[i].nativeExtent; + var lonRadian = (lon / 180) * Math.PI; + var latRadian = (lat / 180) * Math.PI; - let localPt = {}; + var localPt = {}; if (geoidDataList[i].projectionType === "WebMercator") { localPt = geographicToWebMercator( lonRadian, @@ -3152,15 +3198,15 @@ function _workerCode() { fast ) { // Fast conversion (using the center of the tile) - let height = sampleGeoidFromList(center.long, center.lat, geoidDataList); + var height = sampleGeoidFromList(center.long, center.lat, geoidDataList); if (fast) { - for (let index = 0; index < vertexCount; ++index) { + for (var index = 0; index < vertexCount; ++index) { position[index * 3 + 2] += height; } } else { - for (let index = 0; index < vertexCount; ++index) { - let height = sampleGeoidFromList( + for (var index = 0; index < vertexCount; ++index) { + var height = sampleGeoidFromList( center.long + scale_x * position[index * 3], center.lat + scale_y * position[index * 3 + 1], geoidDataList @@ -3178,7 +3224,7 @@ function _workerCode() { ....##....##...##...#########.##..####.......##.##.......##.....##.##...##...##.....## ....##....##....##..##.....##.##...###.##....##.##.......##.....##.##....##..##.....## ....##....##.....##.##.....##.##....##..######..##........#######..##.....##.##.....## - + .##........#######...######.....###....##...... .##.......##.....##.##....##...##.##...##...... .##.......##.....##.##........##...##..##...... @@ -3208,10 +3254,10 @@ function _workerCode() { } traceCode("converting " + vertexCount + " vertices "); - for (let i = 0; i < vertexCount; ++i) { - let indexOffset = i * 3; - let indexOffset1 = indexOffset + 1; - let indexOffset2 = indexOffset + 2; + for (var i = 0; i < vertexCount; ++i) { + var indexOffset = i * 3; + var indexOffset1 = indexOffset + 1; + var indexOffset2 = indexOffset + 2; // Convert position from long, lat, height to Cartesian cartographic.longitude = @@ -3260,12 +3306,12 @@ function _workerCode() { */ function cropUVs(vertexCount, uv0s, uvRegions) { - for (let vertexIndex = 0; vertexIndex < vertexCount; ++vertexIndex) { - let minU = uvRegions[vertexIndex * 4] / 65535.0; - let minV = uvRegions[vertexIndex * 4 + 1] / 65535.0; - let scaleU = + for (var vertexIndex = 0; vertexIndex < vertexCount; ++vertexIndex) { + var minU = uvRegions[vertexIndex * 4] / 65535.0; + var minV = uvRegions[vertexIndex * 4 + 1] / 65535.0; + var scaleU = (uvRegions[vertexIndex * 4 + 2] - uvRegions[vertexIndex * 4]) / 65535.0; - let scaleV = + var scaleV = (uvRegions[vertexIndex * 4 + 3] - uvRegions[vertexIndex * 4 + 1]) / 65535.0; @@ -3285,7 +3331,7 @@ function _workerCode() { .##....##..##.......##..####.##.......##...##...#########....##....##...... .##....##..##.......##...###.##.......##....##..##.....##....##....##...... ..######...########.##....##.########.##.....##.##.....##....##....######## - + .####.##....##.########.########.########..##....##....###....##...... ..##..###...##....##....##.......##.....##.###...##...##.##...##...... ..##..####..##....##....##.......##.....##.####..##..##...##..##...... @@ -3293,7 +3339,7 @@ function _workerCode() { ..##..##..####....##....##.......##...##...##..####.#########.##...... ..##..##...###....##....##.......##....##..##...###.##.....##.##...... .####.##....##....##....########.##.....##.##....##.##.....##.######## - + .########..##.....##.########.########.########.########. .##.....##.##.....##.##.......##.......##.......##.....## .##.....##.##.....##.##.......##.......##.......##.....## @@ -3326,12 +3372,12 @@ function _workerCode() { }; } - let buffers = []; - let bufferViews = []; - let accessors = []; - let meshes = []; - let nodes = []; - let nodesInScene = []; + var buffers = []; + var bufferViews = []; + var accessors = []; + var meshes = []; + var nodes = []; + var nodesInScene = []; // if we provide indices, then the vertex count is the length // of that array, otherwise we assume non-indexed triangle @@ -3340,77 +3386,77 @@ function _workerCode() { } // allocate array - let indexArray = new Uint32Array(vertexCount); + var indexArray = new Uint32Array(vertexCount); if (indices) { // set the indices - for (let vertexIndex = 0; vertexIndex < vertexCount; ++vertexIndex) { + for (var vertexIndex = 0; vertexIndex < vertexCount; ++vertexIndex) { indexArray[vertexIndex] = indices[vertexIndex]; } } else { // generate indices - for (let vertexIndex = 0; vertexIndex < vertexCount; ++vertexIndex) { + for (var vertexIndex = 0; vertexIndex < vertexCount; ++vertexIndex) { indexArray[vertexIndex] = vertexIndex; } } // push to the buffers, bufferViews and accessors - let indicesBlob = new Blob([indexArray], { type: "application/binary" }); - let indicesURL = URL.createObjectURL(indicesBlob); + var indicesBlob = new Blob([indexArray], { type: "application/binary" }); + var indicesURL = URL.createObjectURL(indicesBlob); - let endIndex = vertexCount; + var endIndex = vertexCount; // POSITIONS - let meshPositions = positions.subarray(0, endIndex * 3); - let positionsBlob = new Blob([meshPositions], { + var meshPositions = positions.subarray(0, endIndex * 3); + var positionsBlob = new Blob([meshPositions], { type: "application/binary", }); - let positionsURL = URL.createObjectURL(positionsBlob); + var positionsURL = URL.createObjectURL(positionsBlob); // NORMALS - let meshNormals = normals ? normals.subarray(0, endIndex * 3) : null; - let normalsURL = null; + var meshNormals = normals ? normals.subarray(0, endIndex * 3) : null; + var normalsURL = null; if (meshNormals) { - let normalsBlob = new Blob([meshNormals], { + var normalsBlob = new Blob([meshNormals], { type: "application/binary", }); normalsURL = URL.createObjectURL(normalsBlob); } // UV0s - let meshUv0s = uv0s ? uv0s.subarray(0, endIndex * 2) : null; - let uv0URL = null; + var meshUv0s = uv0s ? uv0s.subarray(0, endIndex * 2) : null; + var uv0URL = null; if (meshUv0s) { - let uv0Blob = new Blob([meshUv0s], { type: "application/binary" }); + var uv0Blob = new Blob([meshUv0s], { type: "application/binary" }); uv0URL = URL.createObjectURL(uv0Blob); } // Colors // @TODO: check we can directly import vertex colors as bytes instead // of having to convert to float - let meshColorsInBytes = colors ? colors.subarray(0, endIndex * 4) : null; - let meshColors = null; - let colorsURL = null; + var meshColorsInBytes = colors ? colors.subarray(0, endIndex * 4) : null; + var meshColors = null; + var colorsURL = null; if (meshColorsInBytes) { - let colorCount = meshColorsInBytes.length; + var colorCount = meshColorsInBytes.length; meshColors = new Float32Array(colorCount); - for (let i = 0; i < colorCount; ++i) { + for (var i = 0; i < colorCount; ++i) { meshColors[i] = meshColorsInBytes[i] / 255.0; } - let colorsBlob = new Blob([meshColors], { type: "application/binary" }); + var colorsBlob = new Blob([meshColors], { type: "application/binary" }); colorsURL = URL.createObjectURL(colorsBlob); } - let posIndex = 0; - let normalIndex = 0; - let uv0Index = 0; - let colorIndex = 0; - let indicesIndex = 0; + var posIndex = 0; + var normalIndex = 0; + var uv0Index = 0; + var colorIndex = 0; + var indicesIndex = 0; - let currentIndex = posIndex; + var currentIndex = posIndex; - let attributes = {}; + var attributes = {}; // POSITIONS attributes.POSITION = posIndex; @@ -3566,7 +3612,7 @@ function _workerCode() { .##....##..##.......##.....##.##.....##.##..........##....##...##......##... .##....##..##.......##.....##.##.....##.##..........##....##....##.....##... ..######...########..#######..##.....##.########....##....##.....##....##... - + .########..########..######...#######..########..########.########. .##.....##.##.......##....##.##.....##.##.....##.##.......##.....## .##.....##.##.......##.......##.....##.##.....##.##.......##.....## @@ -3577,7 +3623,7 @@ function _workerCode() { */ function decode(data, schema, bufferInfo, featureData) { - let magicNumber = new Uint8Array(data, 0, 5); + var magicNumber = new Uint8Array(data, 0, 5); if ( magicNumber[0] === "D".charCodeAt() && magicNumber[1] === "R".charCodeAt() && @@ -3598,7 +3644,7 @@ function _workerCode() { .##.....##.##...##...#########.##.......##.....## .##.....##.##....##..##.....##.##....##.##.....## .########..##.....##.##.....##..######...#######. - + .########..########..######...#######..########..########.########. .##.....##.##.......##....##.##.....##.##.....##.##.......##.....## .##.....##.##.......##.......##.....##.##.....##.##.......##.....## @@ -3614,35 +3660,35 @@ function _workerCode() { const dracoDecoderModule = DracoDecoderModule(); const buffer = new dracoDecoderModule.DecoderBuffer(); - let byteArray = new Uint8Array(data); + var byteArray = new Uint8Array(data); buffer.Init(byteArray, byteArray.length); // Create a buffer to hold the encoded data. const dracoDecoder = new dracoDecoderModule.Decoder(); const geometryType = dracoDecoder.GetEncodedGeometryType(buffer); - let metadataQuerier = new dracoDecoderModule.MetadataQuerier(); + var metadataQuerier = new dracoDecoderModule.MetadataQuerier(); // Decode the encoded geometry. // See: https://github.com/google/draco/blob/master/src/draco/javascript/emscripten/draco_web_decoder.idl - let dracoGeometry; - let status; + var dracoGeometry; + var status; if (geometryType === dracoDecoderModule.TRIANGULAR_MESH) { dracoGeometry = new dracoDecoderModule.Mesh(); status = dracoDecoder.DecodeBufferToMesh(buffer, dracoGeometry); } - let decodedGeometry = { + var decodedGeometry = { vertexCount: [0], featureCount: 0, }; // if all is OK if (status && status.ok() && dracoGeometry.ptr !== 0) { - let faceCount = dracoGeometry.num_faces(); - let attributesCount = dracoGeometry.num_attributes(); - let vertexCount = dracoGeometry.num_points(); + var faceCount = dracoGeometry.num_faces(); + var attributesCount = dracoGeometry.num_attributes(); + var vertexCount = dracoGeometry.num_points(); decodedGeometry.indices = new Uint32Array(faceCount * 3); - let faces = decodedGeometry.indices; + var faces = decodedGeometry.indices; decodedGeometry.vertexCount[0] = vertexCount; decodedGeometry.scale_x = 1; @@ -3650,8 +3696,8 @@ function _workerCode() { // Decode faces // @TODO: Replace that code with GetTrianglesUInt32Array for better efficiency - let face = new dracoDecoderModule.DracoInt32Array(3); - for (let index = 0; index < faceCount; ++index) { + var face = new dracoDecoderModule.DracoInt32Array(3); + for (var index = 0; index < faceCount; ++index) { dracoDecoder.GetFaceFromMesh(dracoGeometry, index, face); faces[index * 3] = face.GetValue(0); faces[index * 3 + 1] = face.GetValue(1); @@ -3660,10 +3706,10 @@ function _workerCode() { dracoDecoderModule.destroy(face); - for (let index = 0; index < attributesCount; ++index) { - let dracoAttribute = dracoDecoder.GetAttribute(dracoGeometry, index); + for (var index = 0; index < attributesCount; ++index) { + var dracoAttribute = dracoDecoder.GetAttribute(dracoGeometry, index); - let attributeData = decodeDracoAttribute( + var attributeData = decodeDracoAttribute( dracoDecoderModule, dracoDecoder, dracoGeometry, @@ -3672,8 +3718,8 @@ function _workerCode() { ); // initial mapping - let dracoAttributeType = dracoAttribute.attribute_type(); - let attributei3sName = "unknown"; + var dracoAttributeType = dracoAttribute.attribute_type(); + var attributei3sName = "unknown"; if (dracoAttributeType === dracoDecoderModule.POSITION) { attributei3sName = "positions"; @@ -3686,12 +3732,12 @@ function _workerCode() { } // get the metadata - let metadata = dracoDecoder.GetAttributeMetadata(dracoGeometry, index); + var metadata = dracoDecoder.GetAttributeMetadata(dracoGeometry, index); if (metadata.ptr) { - let numEntries = metadataQuerier.NumEntries(metadata); - for (let entry = 0; entry < numEntries; ++entry) { - let entryName = metadataQuerier.GetEntryName(metadata, entry); + var numEntries = metadataQuerier.NumEntries(metadata); + for (var entry = 0; entry < numEntries; ++entry) { + var entryName = metadataQuerier.GetEntryName(metadata, entry); if (entryName === "i3s-scale_x") { decodedGeometry.scale_x = metadataQuerier.GetDoubleEntry( metadata, @@ -3738,15 +3784,15 @@ function _workerCode() { dracoAttribute, vertexCount ) { - let bufferSize = dracoAttribute.num_components() * vertexCount; - let dracoAttributeData = null; + var bufferSize = dracoAttribute.num_components() * vertexCount; + var dracoAttributeData = null; - let handlers = [ + var handlers = [ function () {}, // DT_INVALID - 0 function () { // DT_INT8 - 1 dracoAttributeData = new dracoDecoderModule.DracoInt8Array(bufferSize); - let success = dracoDecoder.GetAttributeInt8ForAllPoints( + var success = dracoDecoder.GetAttributeInt8ForAllPoints( dracoGeometry, dracoAttribute, dracoAttributeData @@ -3755,8 +3801,8 @@ function _workerCode() { if (!success) { console.error("Bad stream"); } - let attributeData = new Int8Array(bufferSize); - for (let i = 0; i < bufferSize; ++i) { + var attributeData = new Int8Array(bufferSize); + for (var i = 0; i < bufferSize; ++i) { attributeData[i] = dracoAttributeData.GetValue(i); } return attributeData; @@ -3764,7 +3810,7 @@ function _workerCode() { function () { // DT_UINT8 - 2 dracoAttributeData = new dracoDecoderModule.DracoInt8Array(bufferSize); - let success = dracoDecoder.GetAttributeUInt8ForAllPoints( + var success = dracoDecoder.GetAttributeUInt8ForAllPoints( dracoGeometry, dracoAttribute, dracoAttributeData @@ -3773,8 +3819,8 @@ function _workerCode() { if (!success) { console.error("Bad stream"); } - let attributeData = new Uint8Array(bufferSize); - for (let i = 0; i < bufferSize; ++i) { + var attributeData = new Uint8Array(bufferSize); + for (var i = 0; i < bufferSize; ++i) { attributeData[i] = dracoAttributeData.GetValue(i); } return attributeData; @@ -3782,7 +3828,7 @@ function _workerCode() { function () { // DT_INT16 - 3 dracoAttributeData = new dracoDecoderModule.DracoInt16Array(bufferSize); - let success = dracoDecoder.GetAttributeInt16ForAllPoints( + var success = dracoDecoder.GetAttributeInt16ForAllPoints( dracoGeometry, dracoAttribute, dracoAttributeData @@ -3791,8 +3837,8 @@ function _workerCode() { if (!success) { console.error("Bad stream"); } - let attributeData = new Int16Array(bufferSize); - for (let i = 0; i < bufferSize; ++i) { + var attributeData = new Int16Array(bufferSize); + for (var i = 0; i < bufferSize; ++i) { attributeData[i] = dracoAttributeData.GetValue(i); } return attributeData; @@ -3800,7 +3846,7 @@ function _workerCode() { function () { // DT_UINT16 - 4 dracoAttributeData = new dracoDecoderModule.DracoInt16Array(bufferSize); - let success = dracoDecoder.GetAttributeUInt16ForAllPoints( + var success = dracoDecoder.GetAttributeUInt16ForAllPoints( dracoGeometry, dracoAttribute, dracoAttributeData @@ -3809,8 +3855,8 @@ function _workerCode() { if (!success) { console.error("Bad stream"); } - let attributeData = new Uint16Array(bufferSize); - for (let i = 0; i < bufferSize; ++i) { + var attributeData = new Uint16Array(bufferSize); + for (var i = 0; i < bufferSize; ++i) { attributeData[i] = dracoAttributeData.GetValue(i); } return attributeData; @@ -3818,7 +3864,7 @@ function _workerCode() { function () { // DT_INT32 - 5 dracoAttributeData = new dracoDecoderModule.DracoInt32Array(bufferSize); - let success = dracoDecoder.GetAttributeInt32ForAllPoints( + var success = dracoDecoder.GetAttributeInt32ForAllPoints( dracoGeometry, dracoAttribute, dracoAttributeData @@ -3827,8 +3873,8 @@ function _workerCode() { if (!success) { console.error("Bad stream"); } - let attributeData = new Int32Array(bufferSize); - for (let i = 0; i < bufferSize; ++i) { + var attributeData = new Int32Array(bufferSize); + for (var i = 0; i < bufferSize; ++i) { attributeData[i] = dracoAttributeData.GetValue(i); } return attributeData; @@ -3836,7 +3882,7 @@ function _workerCode() { function () { // DT_UINT32 - 6 dracoAttributeData = new dracoDecoderModule.DracoInt32Array(bufferSize); - let success = dracoDecoder.GetAttributeUInt32ForAllPoints( + var success = dracoDecoder.GetAttributeUInt32ForAllPoints( dracoGeometry, dracoAttribute, dracoAttributeData @@ -3845,8 +3891,8 @@ function _workerCode() { if (!success) { console.error("Bad stream"); } - let attributeData = new Uint32Array(bufferSize); - for (let i = 0; i < bufferSize; ++i) { + var attributeData = new Uint32Array(bufferSize); + for (var i = 0; i < bufferSize; ++i) { attributeData[i] = dracoAttributeData.GetValue(i); } return attributeData; @@ -3862,7 +3908,7 @@ function _workerCode() { dracoAttributeData = new dracoDecoderModule.DracoFloat32Array( bufferSize ); - let success = dracoDecoder.GetAttributeFloatForAllPoints( + var success = dracoDecoder.GetAttributeFloatForAllPoints( dracoGeometry, dracoAttribute, dracoAttributeData @@ -3871,8 +3917,8 @@ function _workerCode() { if (!success) { console.error("Bad stream"); } - let attributeData = new Float32Array(bufferSize); - for (let i = 0; i < bufferSize; ++i) { + var attributeData = new Float32Array(bufferSize); + for (var i = 0; i < bufferSize; ++i) { attributeData[i] = dracoAttributeData.GetValue(i); } return attributeData; @@ -3883,7 +3929,7 @@ function _workerCode() { function () { // DT_FLOAT32 - 11 dracoAttributeData = new dracoDecoderModule.DracoUInt8Array(bufferSize); - let success = dracoDecoder.GetAttributeUInt8ForAllPoints( + var success = dracoDecoder.GetAttributeUInt8ForAllPoints( dracoGeometry, dracoAttribute, dracoAttributeData @@ -3892,15 +3938,15 @@ function _workerCode() { if (!success) { console.error("Bad stream"); } - let attributeData = new Uint8Array(bufferSize); - for (let i = 0; i < bufferSize; ++i) { + var attributeData = new Uint8Array(bufferSize); + for (var i = 0; i < bufferSize; ++i) { attributeData[i] = dracoAttributeData.GetValue(i); } return attributeData; }, ]; - let attributeData = handlers[dracoAttribute.data_type()](); + var attributeData = handlers[dracoAttribute.data_type()](); if (dracoAttributeData) { dracoDecoderModule.destroy(dracoAttributeData); @@ -3917,7 +3963,7 @@ function _workerCode() { .##.....##..##..##..####.#########.##...##......##... .##.....##..##..##...###.##.....##.##....##.....##... .########..####.##....##.##.....##.##.....##....##... - + .########..########..######...#######..########..########.########. .##.....##.##.......##....##.##.....##.##.....##.##.......##.....## .##.....##.##.......##.......##.....##.##.....##.##.......##.....## @@ -3927,35 +3973,35 @@ function _workerCode() { .########..########..######...#######..########..########.##.....## */ - let binaryAttributeDecoders = { + var binaryAttributeDecoders = { position: function (decodedGeometry, data, offset) { - let count = decodedGeometry.vertexCount * 3; + var count = decodedGeometry.vertexCount * 3; decodedGeometry.positions = new Float32Array(data, offset, count); offset += count * 4; return offset; }, normal: function (decodedGeometry, data, offset) { - let count = decodedGeometry.vertexCount * 3; + var count = decodedGeometry.vertexCount * 3; decodedGeometry.normals = new Float32Array(data, offset, count); offset += count * 4; return offset; }, uv0: function (decodedGeometry, data, offset) { - let count = decodedGeometry.vertexCount * 2; + var count = decodedGeometry.vertexCount * 2; decodedGeometry.uv0s = new Float32Array(data, offset, count); offset += count * 4; return offset; }, color: function (decodedGeometry, data, offset) { - let count = decodedGeometry.vertexCount * 4; + var count = decodedGeometry.vertexCount * 4; decodedGeometry.colors = new Uint8Array(data, offset, count); offset += count; return offset; }, featureId: function (decodedGeometry, data, offset) { - let count = decodedGeometry.featureCount; - let decodedGeometryBytes = new Uint8Array(data, offset, 8); - let id = decodedGeometryBytes[7]; + var count = decodedGeometry.featureCount; + var decodedGeometryBytes = new Uint8Array(data, offset, 8); + var id = decodedGeometryBytes[7]; id <<= 8; id = decodedGeometryBytes[6]; id <<= 8; @@ -3975,9 +4021,9 @@ function _workerCode() { return offset; }, id: function (decodedGeometry, data, offset) { - let count = decodedGeometry.featureCount; - let decodedGeometryBytes = new Uint8Array(data, offset, 8); - let id = decodedGeometryBytes[7]; + var count = decodedGeometry.featureCount; + var decodedGeometryBytes = new Uint8Array(data, offset, 8); + var id = decodedGeometryBytes[7]; id <<= 8; id = decodedGeometryBytes[6]; id <<= 8; @@ -3997,19 +4043,19 @@ function _workerCode() { return offset; }, faceRange: function (decodedGeometry, data, offset) { - let count = decodedGeometry.featureCount * 2; + var count = decodedGeometry.featureCount * 2; decodedGeometry.faceRange = new Uint32Array(data, offset, count); offset += count * 4; return offset; }, uvRegion: function (decodedGeometry, data, offset) { - let count = decodedGeometry.vertexCount * 4; + var count = decodedGeometry.vertexCount * 4; decodedGeometry["uv-region"] = new Uint16Array(data, offset, count); offset += count * 2; return offset; }, region: function (decodedGeometry, data, offset) { - let count = decodedGeometry.vertexCount * 4; + var count = decodedGeometry.vertexCount * 4; decodedGeometry["uv-region"] = new Uint16Array(data, offset, count); offset += count * 2; return offset; @@ -4019,14 +4065,14 @@ function _workerCode() { function decodeBinaryGeometry(data, schema, bufferInfo, featureData) { // From this spec: // https://github.com/Esri/i3s-spec/blob/master/docs/1.7/defaultGeometrySchema.cmn.md - let decodedGeometry = { + var decodedGeometry = { vertexCount: 0, }; - let dataView = new DataView(data); + var dataView = new DataView(data); try { - let offset = 0; + var offset = 0; decodedGeometry.vertexCount = dataView.getUint32(offset, 1); offset += 4; @@ -4034,7 +4080,7 @@ function _workerCode() { offset += 4; if (bufferInfo) { - for (let item of bufferInfo.attributes) { + for (var item of bufferInfo.attributes) { if (binaryAttributeDecoders[item]) { offset = binaryAttributeDecoders[item]( decodedGeometry, @@ -4046,8 +4092,8 @@ function _workerCode() { } } } else { - let ordering = schema.ordering; - let featureAttributeOrder = schema.featureAttributeOrder; + var ordering = schema.ordering; + var featureAttributeOrder = schema.featureAttributeOrder; if ( featureData && @@ -4064,16 +4110,16 @@ function _workerCode() { } // use default geometry schema - for (let item of ordering) { - let decoder = binaryAttributeDecoders[item]; + for (var item of ordering) { + var decoder = binaryAttributeDecoders[item]; if (!decoder) { console.log(item); } offset = decoder(decodedGeometry, data, offset); } - for (let item of featureAttributeOrder) { - let decoder = binaryAttributeDecoders[item]; + for (var item of featureAttributeOrder) { + var decoder = binaryAttributeDecoders[item]; if (!decoder) { console.log(item); } @@ -4103,7 +4149,7 @@ function _workerCode() { traceCode(e.data.url); // Decode the data into geometry - let geometryData = decode( + var geometryData = decode( e.data.binaryData, e.data.schema, e.data.bufferInfo, @@ -4146,7 +4192,7 @@ function _workerCode() { } // Create the final buffer - let meshData = generateGLTFBuffer( + var meshData = generateGLTFBuffer( geometryData.vertexCount, geometryData.indices, geometryData.positions, @@ -4155,7 +4201,7 @@ function _workerCode() { geometryData.colors ); - let customAttributes = {}; + var customAttributes = {}; if (geometryData["feature-index"]) { customAttributes.positions = geometryData.positions; customAttributes["feature-index"] = geometryData["feature-index"]; @@ -4191,66 +4237,71 @@ function _workerCode() { function I3SGLTFProcessingQueue() { this._queue = []; this._processing = false; - this._createWorkers(() => { - this._process(); - }); + this._createWorkers( + function () { + this._process(); + }.bind(this) + ); } I3SGLTFProcessingQueue.prototype._process = function () { - for (let worker of this._workers) { + for (var worker of this._workers) { if (worker.isReadyToWork) { if (this._queue.length > 0) { - let task = this._queue.shift(); + var task = this._queue.shift(); task.execute(worker); traceCode("Process Queue:" + this._queue.length); } } } - setTimeout(() => { - this._process(); - }, 100); + setTimeout( + function () { + this._process(); + }.bind(this), + 100 + ); }; I3SGLTFProcessingQueue.prototype._createWorkers = function (cb) { - let workerCode = String(_workerCode); + var workerCode = String(_workerCode); - let externalModules = ["/Source/ThirdParty/Workers/draco_decoder.js"]; + var externalModules = ["/Source/ThirdParty/Workers/draco_decoder.js"]; - let externalModuleCode = ""; - let fetchPromises = []; + var externalModuleCode = ""; + var fetchPromises = []; - for (let index = 0; index < externalModules.length; ++index) { - let moduleLoadedResolve; - let moduleLoaded = new _Promise((resolve, reject) => { + for (var index = 0; index < externalModules.length; ++index) { + var moduleLoadedResolve; + var moduleLoaded = new _Promise(function (resolve, reject) { moduleLoadedResolve = resolve; }); fetchPromises.push(moduleLoaded); // eslint-disable-next-line no-loop-func - fetch(externalModules[index]).then((response) => { - response.text().then((data) => { + fetch(externalModules[index]).then(function (response) { + response.text().then(function (data) { externalModuleCode += data; moduleLoadedResolve(); }); }); } - let externalModulesLoaded = _Promise.all(fetchPromises); - let that = this; - externalModulesLoaded.then(() => { + var externalModulesLoaded = _Promise.all(fetchPromises); + var that = this; + externalModulesLoaded.then(function () { workerCode = workerCode.replace("function _workerCode() {", ""); workerCode = workerCode.slice(0, -1); - let blob = new Blob([externalModuleCode + workerCode], { + var blob = new Blob([externalModuleCode + workerCode], { type: "test/javascript", }); // Create the workers - let workerCount = FeatureDetection.hardwareConcurrency - 1; + var workerCount = FeatureDetection.hardwareConcurrency - 1; traceCode("Using " + workerCount + " workers"); that._workers = []; - for (let loop = 0; loop < workerCount; ++loop) { - let worker = new Worker(window.URL.createObjectURL(blob)); + for (var loop = 0; loop < workerCount; ++loop) { + var worker = new Worker(window.URL.createObjectURL(blob)); worker.setTask = function (task) { worker._task = task; @@ -4259,9 +4310,9 @@ I3SGLTFProcessingQueue.prototype._createWorkers = function (cb) { }; worker.onmessage = function (e) { - let task = worker._task; + var task = worker._task; - let gltfData = task._geometry._generateGLTF( + var gltfData = task._geometry._generateGLTF( e.data.nodesInScene, e.data.nodes, e.data.meshes, @@ -4287,7 +4338,7 @@ I3SGLTFProcessingQueue.prototype._createWorkers = function (cb) { }; I3SGLTFProcessingQueue.prototype.addTask = function (data) { - let newTask = { + var newTask = { _geometry: data.geometryData, _featureData: data.featureData && data.featureData[0] ? data.featureData[0] : null, @@ -4296,12 +4347,12 @@ I3SGLTFProcessingQueue.prototype.addTask = function (data) { _geoidDataList: data.geometryData._dataSource._geoidDataList, execute: function (worker) { // Prepare the data to send to the worker - //let geometryData = this._geometry._data; - let parentData = this._geometry._parent._data; - let parentRotationInverseMatrix = this._geometry._parent + //var geometryData = this._geometry._data; + var parentData = this._geometry._parent._data; + var parentRotationInverseMatrix = this._geometry._parent ._inverseRotationMatrix; - let payload = { + var payload = { binaryData: this._geometry._data, featureData: this._featureData ? this._featureData._data : null, schema: this._schema, @@ -4311,7 +4362,7 @@ I3SGLTFProcessingQueue.prototype.addTask = function (data) { geoidDataList: this._geoidDataList, }; - let center = { + var center = { long: 0, lat: 0, alt: 0, @@ -4334,8 +4385,8 @@ I3SGLTFProcessingQueue.prototype.addTask = function (data) { center.alt ); - let axisFlipRotation = Matrix3.fromRotationX(-Math.PI / 2); - let parentRotation = new Matrix3(); + var axisFlipRotation = Matrix3.fromRotationX(-Math.PI / 2); + var parentRotation = new Matrix3(); Matrix3.multiply( axisFlipRotation, @@ -4362,11 +4413,13 @@ I3SGLTFProcessingQueue.prototype.addTask = function (data) { }, }; - return new _Promise((resolve, reject) => { - newTask.resolve = resolve; - newTask.reject = reject; - this._queue.push(newTask); - }); + return new _Promise( + function (resolve, reject) { + newTask.resolve = resolve; + newTask.reject = reject; + this._queue.push(newTask); + }.bind(this) + ); }; /* From dbdb35d33a9b393d7454e4a388b9965e3e380398 Mon Sep 17 00:00:00 2001 From: Tamrat Belayneh Date: Fri, 2 Jul 2021 15:47:51 -0700 Subject: [PATCH 04/57] Additional eslint fixes --- Source/DataSources/I3SDataSource.js | 1025 ++++++++++++++------------- 1 file changed, 540 insertions(+), 485 deletions(-) diff --git a/Source/DataSources/I3SDataSource.js b/Source/DataSources/I3SDataSource.js index 3fd195dbed4f..202a1c4cf6b4 100644 --- a/Source/DataSources/I3SDataSource.js +++ b/Source/DataSources/I3SDataSource.js @@ -76,16 +76,16 @@ // Create a Viewer instances and add the DataSource. var viewer = new Cesium.Viewer("cesiumContainer", { - animation: false, - timeline: false, + animation: false, + timeline: false, }); viewer.clock.shouldAnimate = false; var tours = { - "Frankfurt": "https://tiles.arcgis.com/tiles/u0sSNqDXr7puKJrF/arcgis/rest/services/Frankfurt2017_v17/SceneServer" - }; + "Frankfurt": "https://tiles.arcgis.com/tiles/u0sSNqDXr7puKJrF/arcgis/rest/services/Frankfurt2017_v17/SceneServer" + }; // Initialize the terrain provider which provides the geoid conversion // If this is not specified, or the URL is invalid no geoid conversion will be applied. @@ -101,52 +101,52 @@ var dataSource = new Cesium.I3SDataSource("", viewer.scene, { }); dataSource.camera = viewer.camera; // for debug dataSource - .loadUrl(tours["Frankfurt"]) - .then(function () { + .loadUrl(tours["Frankfurt"]) + .then(function () { - }); + }); viewer.dataSources.add(dataSource); // Silhouette a feature on selection and show metadata in the InfoBox. viewer.screenSpaceEventHandler.setInputAction(function onLeftClick( - movement + movement ) { - // Pick a new feature - var pickedFeature = viewer.scene.pick(movement.position); - if (!Cesium.defined(pickedFeature)) { - return; - } - - var pickedPosition = viewer.scene.pickPosition(movement.position); - - if (pickedFeature && pickedFeature.content && - pickedFeature.content.i3sNode) { - - var i3sNode = pickedFeature.content.i3sNode; - i3sNode.loadFields().then(() => { - console.log(i3sNode); - var geometry = i3sNode.geometryData[0]; - console.log(geometry); - if (pickedPosition) { - var location = geometry.getClosestPointIndex( - pickedPosition.x, pickedPosition.y, pickedPosition.z); - console.log("Location", location); - if (location.index !== -1 && geometry.customAttributes["feature-index"]) { - var featureIndex = geometry.customAttributes["feature-index"][location.index]; - for (var fieldName in i3sNode.fields) { - var field = i3sNode.fields[fieldName]; - console.log(field.name + ": " + field.values[featureIndex]); - } - } - } - }); - } - - console.log(viewer.scene.camera); + // Pick a new feature + var pickedFeature = viewer.scene.pick(movement.position); + if (!Cesium.defined(pickedFeature)) { + return; + } + + var pickedPosition = viewer.scene.pickPosition(movement.position); + + if (pickedFeature && pickedFeature.content && + pickedFeature.content.i3sNode) { + + var i3sNode = pickedFeature.content.i3sNode; + i3sNode.loadFields().then(function() { + console.log(i3sNode); + var geometry = i3sNode.geometryData[0]; + console.log(geometry); + if (pickedPosition) { + var location = geometry.getClosestPointIndex( + pickedPosition.x, pickedPosition.y, pickedPosition.z); + console.log("Location", location); + if (location.index !== -1 && geometry.customAttributes["feature-index"]) { + var featureIndex = geometry.customAttributes["feature-index"][location.index]; + for (var fieldName=0; fieldName < i3sNode.fields.length; fieldName++) { + var field = i3sNode.fields[fieldName]; + console.log(field.name + ": " + field.values[featureIndex]); + } + } + } + }); + } + + console.log(viewer.scene.camera); }, - Cesium.ScreenSpaceEventType.LEFT_CLICK); + Cesium.ScreenSpaceEventType.LEFT_CLICK); */ @@ -159,6 +159,7 @@ viewer.screenSpaceEventHandler.setInputAction(function onLeftClick( ..##..##.....##.##........##.....##.##....##.....##....##....## .####.##.....##.##.........#######..##.....##....##.....######. */ + import Batched3DModel3DTileContent from "../Scene/Batched3DModel3DTileContent.js"; import Cartesian2 from "../Core/Cartesian2.js"; import Cartesian3 from "../Core/Cartesian3.js"; @@ -202,7 +203,7 @@ import when from "../ThirdParty/when.js"; var _i3sContentCache = {}; // Prevent ESLint from issuing warnings about undefined Promise -//eslint-disable-next-line no-undef +// eslint-disable-next-line no-undef var _Promise = Promise; // Code traces @@ -496,7 +497,7 @@ Object.defineProperties(I3SDataSource.prototype, { /** * Asynchronously loads the I3S scene at the provided url, replacing any existing data. * @param {Object} [url] The url to be processed. - * @returns {Promise} a promise that will resolve when the I3S scene is loaded. + * @returns {Promise} a promise that will resolve when the I3S scene is loaded. */ I3SDataSource.prototype.loadUrl = function (url) { var parts = url.split("?"); @@ -555,56 +556,46 @@ I3SDataSource.prototype._setLoading = function (isLoading) { * @private */ I3SDataSource.prototype._loadJson = function (uri, success, fail) { - return new _Promise( - function (resolve, reject) { - if (this._traceFetches) { - console.log("I3S FETCH:", uri); - } - var request = fetch(uri); - request.then( - function (response) { - response.json().then( - function (data) { - if (data.error) { - console.error(this._data.error.message); - fail(reject); - } else { - success(data, resolve); - } - }.bind(this) - ); - }.bind(this) - ); - }.bind(this) - ); + var that = this; + return new _Promise(function (resolve, reject) { + if (that._traceFetches) { + console.log("I3S FETCH:", uri); + } + var request = fetch(uri); + request.then(function (response) { + response.json().then(function (data) { + if (data.error) { + console.error(that._data.error.message); + fail(reject); + } else { + success(data, resolve); + } + }); + }); + }); }; /** * @private */ I3SDataSource.prototype._loadBinary = function (uri, success, fail) { - return new _Promise( - function (resolve, reject) { - if (this._traceFetches) { - traceCode("I3S FETCH:", uri); - } - var request = fetch(uri); - request.then( - function (response) { - response.arrayBuffer().then( - function (data) { - if (data.error) { - console.error(this._data.error.message); - fail(reject); - } else { - success(data, resolve); - } - }.bind(this) - ); - }.bind(this) - ); - }.bind(this) - ); + var that = this; + return new _Promise(function (resolve, reject) { + if (that._traceFetches) { + traceCode("I3S FETCH:", uri); + } + var request = fetch(uri); + request.then(function (response) { + response.arrayBuffer().then(function (data) { + if (data.error) { + console.error(that._data.error.message); + fail(reject); + } else { + success(data, resolve); + } + }); + }); + }); }; /** @@ -820,6 +811,7 @@ function _computeExtent(minLongitude, minLatitude, maxLongitude, maxLatitude) { /** * This class implements an I3S scene server + * @private * @alias I3SSceneServer * @param {I3SDataSource} [dataSource] The data source that is the * owner of this scene server @@ -836,7 +828,7 @@ Object.defineProperties(I3SSceneServer.prototype, { /** * Gets the collection of Layers. * @memberof I3SSceneServer.prototype - * @type {LayerCollection} + * @type {Array} */ layers: { get: function () { @@ -870,37 +862,39 @@ Object.defineProperties(I3SSceneServer.prototype, { * @param {String} [uri] The uri where to fetch the data from. */ I3SSceneServer.prototype.load = function (uri) { + var that = this; this._uri = uri; - return this._dataSource._loadJson( uri, function (data, resolve) { // Success - this._data = data; + that._data = data; var layerPromises = []; - for (var layer of this._data.layers) { + for ( + var layerIndex = 0; + layerIndex < that._data.layers.length; + layerIndex++ + ) { var newLayer = new I3SLayer( - this, - layer, - this._data.layers.indexOf(layer) + that, + that._data.layers[layerIndex], + layerIndex ); - this._layerCollection.push(newLayer); + that._layerCollection.push(newLayer); layerPromises.push(newLayer.load()); } - _Promise.all(layerPromises).then( - function () { - this._computeExtent(); + _Promise.all(layerPromises).then(function () { + that._computeExtent(); - if (this._dataSource._autoCenterCameraOnStart) { - this.centerCamera("topdown"); - } + if (that._dataSource._autoCenterCameraOnStart) { + that.centerCamera("topdown"); + } - resolve(); - this._createVisualElements(); - }.bind(this) - ); - }.bind(this), + resolve(); + that._createVisualElements(); + }); + }, function (reject) { // Fail reject(); @@ -948,9 +942,16 @@ I3SSceneServer.prototype._computeExtent = function () { var maxLatitude = -Number.MAX_VALUE; // Compute the extent from all layers - for (var layer of this._layerCollection) { - if (layer._data.store && layer._data.store.extent) { - var layerExtent = layer._data.store.extent; + for ( + var layerIndex = 0; + layerIndex < this._layerCollection.length; + layerIndex++ + ) { + if ( + this._layerCollection[layerIndex]._data.store && + this._layerCollection[layerIndex]._data.store.extent + ) { + var layerExtent = this._layerCollection[layerIndex]._data.store.extent; minLongitude = Math.min(minLongitude, layerExtent[0]); minLatitude = Math.min(minLatitude, layerExtent[1]); maxLongitude = Math.max(maxLongitude, layerExtent[2]); @@ -1006,13 +1007,14 @@ I3SSceneServer.prototype._createVisualElements = function () { /** * This class implements an I3S layer, in Cesium, each I3SLayer * creates a Cesium3DTileset + * @private * @alias I3SLayer * @constructor * @param {I3SSceneServer} [sceneServer] The scene server that is the * container for this layer * @param {object} [layerData] The layer data that is loaded from the scene * server - * @param {integer} [index] The index of the layer to be reflected + * @param {number} [index] The index of the layer to be reflected */ function I3SLayer(sceneServer, layerData, index) { this._parent = sceneServer; @@ -1105,17 +1107,15 @@ Object.defineProperties(I3SLayer.prototype, { }); /** - * Gets the list of egm files required for a given extent - * @returns The list of required urls to load + * @private */ - -function GetCoveredTiles(terrainProvider, extents) { +function getCoveredTiles(terrainProvider, extents) { return terrainProvider.readyPromise.then(function () { - return GetTiles(terrainProvider, extents); + return getTiles(terrainProvider, extents); }); } -function GetTiles(terrainProvider, extents) { +function getTiles(terrainProvider, extents) { var tilingScheme = terrainProvider.tilingScheme; // Sort points into a set of tiles @@ -1171,8 +1171,7 @@ function GetTiles(terrainProvider, extents) { return when.all(tilePromises).then(function (heightMapBuffers) { var heightMaps = []; - var tilesAreReady = new Array(); - for (var i in heightMapBuffers) { + for (var i = 0; i < heightMapBuffers.length; i++) { var options = { tilingScheme: tilingScheme, x: tileRequests[i].x, @@ -1200,7 +1199,7 @@ function GetTiles(terrainProvider, extents) { offset: heightMap._structure.heightOffset, }; - if (heightMap._encoding == HeightmapEncoding.LERC) { + if (heightMap._encoding === HeightmapEncoding.LERC) { var result = Lerc.decode(heightMap._buffer); heightMapData.buffer = result.pixels[0]; } else { @@ -1216,75 +1215,67 @@ function GetTiles(terrainProvider, extents) { /** * Loads the content, including the root node definition and its children - * @returns {Promise} a promise that is resolved when the layer data is loaded + * @returns {Promise} a promise that is resolved when the layer data is loaded */ I3SLayer.prototype.load = function () { - return new _Promise( - function (resolve, reject) { - this._computeExtent(); + var that = this; + return new _Promise(function (resolve, reject) { + that._computeExtent(); - //Load tiles from arcgis + //Load tiles from arcgis - var geoidTerrainProvider = this._dataSource._geoidTiledTerrainProvider; + var geoidTerrainProvider = that._dataSource._geoidTiledTerrainProvider; - var dataIsReady = new when.defer(); - var geoidDataList = []; - if (defined(geoidTerrainProvider)) { - if (geoidTerrainProvider.ready) { - var tilesReadyPromise = GetCoveredTiles( - geoidTerrainProvider, - this._extent - ); - when(tilesReadyPromise, function (heightMaps) { - geoidDataList = heightMaps; - dataIsReady.resolve(); - }); - } else { - console.log( - "Geoid Terrain service not available - no geoid conversion will be performed." - ); + var dataIsReady = new when.defer(); + var geoidDataList = []; + if (defined(geoidTerrainProvider)) { + if (geoidTerrainProvider.ready) { + var tilesReadyPromise = getCoveredTiles( + geoidTerrainProvider, + that._extent + ); + when(tilesReadyPromise, function (heightMaps) { + geoidDataList = heightMaps; dataIsReady.resolve(); - } + }); } else { console.log( - "No Geoid Terrain service provided - no geoid conversion will be performed." + "Geoid Terrain service not available - no geoid conversion will be performed." ); dataIsReady.resolve(); } - - dataIsReady.then( - function () { - this._dataSource._geoidDataList = geoidDataList; - console.log("Starting to load visual elements"); - this._createVisualElements(); - if (this._data.spatialReference.wkid === 4326) { - this._loadNodePage(0).then( - function () { - this._loadRootNode().then( - function () { - this._create3DTileSet(); - if (this._data.store.version === "1.6") { - this._rootNode._loadChildren().then(function () { - resolve(); - }); - } else { - resolve(); - } - }.bind(this) - ); - }.bind(this) - ); - } else { - console.log( - "Unsupported spatial reference: " + - this._data.spatialReference.wkid - ); - resolve(); - } - }.bind(this) + } else { + console.log( + "No Geoid Terrain service provided - no geoid conversion will be performed." ); - }.bind(this) - ); + dataIsReady.resolve(); + } + + dataIsReady.then(function () { + that._dataSource._geoidDataList = geoidDataList; + console.log("Starting to load visual elements"); + that._createVisualElements(); + if (that._data.spatialReference.wkid === 4326) { + that._loadNodePage(0).then(function () { + that._loadRootNode().then(function () { + that._create3DTileSet(); + if (that._data.store.version === "1.6") { + that._rootNode._loadChildren().then(function () { + resolve(); + }); + } else { + resolve(); + } + }); + }); + } else { + console.log( + "Unsupported spatial reference: " + that._data.spatialReference.wkid + ); + resolve(); + } + }); + }); }; /** @@ -1298,11 +1289,17 @@ I3SLayer.prototype._computeGeometryDefinitions = function (useCompression) { this._geometryDefinitions = []; if (this._data.geometryDefinitions) { - for (var geometryDefinition of this._data.geometryDefinitions) { + for ( + var defIndex = 0; + defIndex < this._data.geometryDefinitions.length; + defIndex++ + ) { var geometryBuffersInfo = []; - var geometryBuffers = geometryDefinition.geometryBuffers; + var geometryBuffers = this._data.geometryDefinitions[defIndex] + .geometryBuffers; - for (var geometryBuffer of geometryBuffers) { + for (var bufIndex = 0; bufIndex < geometryBuffers.length; bufIndex++) { + var geometryBuffer = geometryBuffers[bufIndex]; var collectedAttributes = []; var compressed = false; @@ -1310,8 +1307,8 @@ I3SLayer.prototype._computeGeometryDefinitions = function (useCompression) { // check if compressed compressed = true; var attributes = geometryBuffer.compressedAttributes.attributes; - for (var attribute of attributes) { - collectedAttributes.push(attribute); + for (var i = 0; i < attributes.length; i++) { + collectedAttributes.push(attributes[i]); } } else { // uncompressed attributes @@ -1361,8 +1358,8 @@ I3SLayer.prototype._findBestGeometryBuffers = function ( var geometryBufferInfo = geometryDefinition[index]; var missed = false; var geometryAttributes = geometryBufferInfo.attributes; - for (var attribute of attributes) { - if (!geometryAttributes.includes(attribute)) { + for (var attrIndex = 0; attrIndex < attributes.length; attrIndex++) { + if (!geometryAttributes.includes(attributes[attrIndex])) { missed = true; break; } @@ -1416,62 +1413,60 @@ I3SLayer.prototype._getNodeInNodePages = function (nodeIndex) { */ I3SLayer.prototype._loadNodePage = function (page) { var that = this; - return new _Promise( - function (resolve, reject) { - if (that._nodePages[page] !== undefined) { + return new _Promise(function (resolve, reject) { + if (that._nodePages[page] !== undefined) { + resolve(); + } else if (that._nodePageFetches[page] !== undefined) { + that._nodePageFetches[page]._promise = that._nodePageFetches[ + page + ]._promise.then(function () { resolve(); - } else if (that._nodePageFetches[page] !== undefined) { - that._nodePageFetches[page]._promise = that._nodePageFetches[ - page - ]._promise.then(function () { - resolve(); - }); - } else { - var query = ""; - if (this._dataSource._query && this._dataSource._query !== "") { - query = "?" + this._dataSource._query; - } + }); + } else { + var query = ""; + if (that._dataSource._query && that._dataSource._query !== "") { + query = "?" + that._dataSource._query; + } - var nodePageURI = this._completeUriWithoutQuery + "/nodepages/"; - nodePageURI += page + query; + var nodePageURI = that._completeUriWithoutQuery + "/nodepages/"; + nodePageURI += page + query; - that._nodePageFetches[page] = {}; - that._nodePageFetches[page]._promise = new _Promise(function ( - resolve, - reject - ) { - that._nodePageFetches[page]._resolve = resolve; - }); + that._nodePageFetches[page] = {}; + that._nodePageFetches[page]._promise = new _Promise(function ( + resolve, + reject + ) { + that._nodePageFetches[page]._resolve = resolve; + }); - var _resolve = function () { - // resolve the chain of promises - that._nodePageFetches[page]._resolve(); - delete that._nodePageFetches[page]; - resolve(); - }; + var _resolve = function () { + // resolve the chain of promises + that._nodePageFetches[page]._resolve(); + delete that._nodePageFetches[page]; + resolve(); + }; - fetch(nodePageURI) - .then(function (response) { - response - .json() - .then(function (data) { - if (data.error && data.error.code !== 200) { - _resolve(); - } else { - that._nodePages[page] = data.nodes; - _resolve(); - } - }) - .catch(function () { + fetch(nodePageURI) + .then(function (response) { + response + .json() + .then(function (data) { + if (data.error && data.error.code !== 200) { _resolve(); - }); - }) - .catch(function () { - _resolve(); - }); - } - }.bind(this) - ); + } else { + that._nodePages[page] = data.nodes; + _resolve(); + } + }) + .catch(function () { + _resolve(); + }); + }) + .catch(function () { + _resolve(); + }); + } + }); }; /** @@ -1543,22 +1538,21 @@ I3SLayer.prototype._create3DTileSet = function () { this._tileset._isI3STileSet = true; - this._tileset.readyPromise.then( - function () { - this._tileset.tileLoad.addEventListener(function (tile) {}); + var that = this; + this._tileset.readyPromise.then(function () { + that._tileset.tileLoad.addEventListener(function (tile) {}); - this._tileset.tileUnload.addEventListener(function (tile) { - tile._i3sNode._clearGeometryData(); - tile._contentResource._url = tile._i3sNode._completeUriWithoutQuery; - }); + that._tileset.tileUnload.addEventListener(function (tile) { + tile._i3sNode._clearGeometryData(); + tile._contentResource._url = tile._i3sNode._completeUriWithoutQuery; + }); - this._tileset.tileVisible.addEventListener(function (tile) { - if (tile._i3sNode) { - tile._i3sNode._loadChildren(); - } - }); - }.bind(this) - ); + that._tileset.tileVisible.addEventListener(function (tile) { + if (tile._i3sNode) { + tile._i3sNode._loadChildren(); + } + }); + }); }; /* @@ -1574,6 +1568,7 @@ I3SLayer.prototype._create3DTileSet = function () { /** * This class implements an I3S Node, in Cesium, each I3SNode * creates a Cesium3DTile + * @private * @alias I3SNode * @constructor * @param {I3SLayer|I3SNode} [parent] The parent of that node @@ -1732,7 +1727,7 @@ Object.defineProperties(I3SNode.prototype, { /** * Loads the node definition. - * @returns {Promise} a promise that is resolved when the I3S Node data is loaded + * @returns {Promise} a promise that is resolved when the I3S Node data is loaded */ I3SNode.prototype.load = function (isRoot) { var that = this; @@ -1776,20 +1771,18 @@ I3SNode.prototype.load = function (isRoot) { ); } - return new _Promise( - function (resolve, reject) { - this._layer._getNodeInNodePages(this._nodeIndex).then(function (data) { - that._data = data; - processData(); - resolve(); - }); - }.bind(this) - ); + return new _Promise(function (resolve, reject) { + that._layer._getNodeInNodePages(that._nodeIndex).then(function (data) { + that._data = data; + processData(); + resolve(); + }); + }); }; /** * Loads the node fields. - * @returns {Promise} a promise that is resolved when the I3S Node fields are loaded + * @returns {Promise} a promise that is resolved when the I3S Node fields are loaded */ I3SNode.prototype.loadFields = function () { // check if we must load fields @@ -1817,40 +1810,46 @@ I3SNode.prototype.loadFields = function () { * @private */ I3SNode.prototype._loadChildren = function (waitAllChildren) { - return new _Promise( - function (resolve, reject) { - if (!this._childrenAreLoaded) { - this._childrenAreLoaded = true; - var childPromises = []; - if (this._data.children) { - for (var child of this._data.children) { - var newChild = new I3SNode(this, child.href ? child.href : child); - this._children.push(newChild); - var childIsLoaded = newChild.load(); - if (waitAllChildren) { - childPromises.push(childIsLoaded); - } - childIsLoaded.then( - function () { - this._tile.children.push(newChild._tile); - }.bind(this) - ); - } + var that = this; + return new _Promise(function (resolve, reject) { + if (!that._childrenAreLoaded) { + that._childrenAreLoaded = true; + var childPromises = []; + if (that._data.children) { + for ( + var childIndex = 0; + childIndex < that._data.children.length; + childIndex++ + ) { + var child = that._data.children[childIndex]; + var newChild = new I3SNode(that, child.href ? child.href : child); + that._children.push(newChild); + var childIsLoaded = newChild.load(); if (waitAllChildren) { - _Promise.all(childPromises).then(function () { - resolve(); - }); - } else { - resolve(); + childPromises.push(childIsLoaded); } + childIsLoaded.then( + (function (theChild) { + return function () { + that._tile.children.push(theChild._tile); + }; + })(newChild) + ); + } + if (waitAllChildren) { + _Promise.all(childPromises).then(function () { + resolve(); + }); } else { resolve(); } } else { resolve(); } - }.bind(this) - ); + } else { + resolve(); + } + }); }; /** @@ -1862,10 +1861,17 @@ I3SNode.prototype._loadGeometryData = function () { // To debug decoding for a specific tile, add a condition // that wraps this if/else to match the tile uri if (this._data.geometryData) { - for (var geometry of this._data.geometryData) { - var newGeometryData = new I3SGeometry(this, geometry.href); - this._geometryData.push(newGeometryData); - geometryPromises.push(newGeometryData.load()); + for ( + var geomIndex = 0; + geomIndex < this._data.geometryData.length; + geomIndex++ + ) { + var curGeometryData = new I3SGeometry( + this, + this._data.geometryData[geomIndex].href + ); + this._geometryData.push(curGeometryData); + geometryPromises.push(curGeometryData.load()); } } else if (this._data.mesh) { var geometryDefinition = this._layer._findBestGeometryBuffers( @@ -1893,8 +1899,15 @@ I3SNode.prototype._loadFeatureData = function () { // To debug decoding for a specific tile, add a condition // that wraps this if/else to match the tile uri if (this._data.featureData) { - for (var feature of this._data.featureData) { - var newfeatureData = new I3SFeature(this, feature.href); + for ( + var featureIndex = 0; + featureIndex < this._data.featureData.length; + featureIndex++ + ) { + var newfeatureData = new I3SFeature( + this, + this._data.featureData[featureIndex].href + ); this._featureData.push(newfeatureData); featurePromises.push(newfeatureData.load()); } @@ -1973,9 +1986,15 @@ I3SNode.prototype._create3DTileDefinition = function () { console.error("Unsupported lodSelectionMetricType in Layer"); } } else if (this._data.lodSelection !== undefined) { - for (var lodSelection of this._data.lodSelection) { - if (lodSelection.metricType === "maxScreenThreshold") { - metersPerPixel = span / lodSelection.maxError; + for ( + var lodIndex = 0; + lodIndex < this._data.lodSelection.length; + lodIndex++ + ) { + if ( + this._data.lodSelection[lodIndex].metricType === "maxScreenThreshold" + ) { + metersPerPixel = span / this._data.lodSelection[lodIndex].maxError; } } } @@ -2038,8 +2057,10 @@ I3SNode.prototype._create3DTileDefinition = function () { // get children definition var childrenDefinition = []; - for (var child of this._children) { - childrenDefinition.push(child._create3DTileDefinition()); + for (var childIndex = 0; childIndex < this._children.length; childIndex++) { + childrenDefinition.push( + this._children[childIndex]._create3DTileDefinition() + ); } // Create a tile set @@ -2127,50 +2148,42 @@ I3SNode.prototype._createContentURL = function (resolve, tile) { // Load the geometry data var dataPromises = [this._loadFeatureData(), this._loadGeometryData()]; - _Promise.all(dataPromises).then( - function () { - // Binary GLTF - var generateGLTF = new _Promise( - function (resolve, reject) { - if (this._geometryData && this._geometryData.length > 0) { - var task = this._dataSource._GLTFProcessingQueue.addTask({ - geometryData: this._geometryData[0], - featureData: this._featureData, - defaultGeometrySchema: this._layer._data.store - .defaultGeometrySchema, - url: this._geometryData[0]._completeUri, - tile: this._tile, - }); - task.then( - function (data) { - rawGLTF = data.gltfData; - this._geometryData[0].customAttributes = data.customAttributes; - resolve(); - }.bind(this) - ); - } else { - resolve(); - } - }.bind(this) - ); - - generateGLTF.then( - function () { - var binaryGLTFData = this._dataSource._binarizeGLTF(rawGLTF); - var b3dmRawData = this._dataSource._binarizeB3DM( - featureTableJSON, - batchTableJSON, - binaryGLTFData - ); - var b3dmDataBlob = new Blob([b3dmRawData], { - type: "application/binary", - }); - this._b3dmURL = URL.createObjectURL(b3dmDataBlob); + var that = this; + _Promise.all(dataPromises).then(function () { + // Binary GLTF + var generateGLTF = new _Promise(function (resolve, reject) { + if (that._geometryData && that._geometryData.length > 0) { + var task = that._dataSource._GLTFProcessingQueue.addTask({ + geometryData: that._geometryData[0], + featureData: that._featureData, + defaultGeometrySchema: that._layer._data.store.defaultGeometrySchema, + url: that._geometryData[0]._completeUri, + tile: that._tile, + }); + task.then(function (data) { + rawGLTF = data.gltfData; + that._geometryData[0].customAttributes = data.customAttributes; resolve(); - }.bind(this) + }); + } else { + resolve(); + } + }); + + generateGLTF.then(function () { + var binaryGLTFData = that._dataSource._binarizeGLTF(rawGLTF); + var b3dmRawData = that._dataSource._binarizeB3DM( + featureTableJSON, + batchTableJSON, + binaryGLTFData ); - }.bind(this) - ); + var b3dmDataBlob = new Blob([b3dmRawData], { + type: "application/binary", + }); + that._b3dmURL = URL.createObjectURL(b3dmDataBlob); + resolve(); + }); + }); }; /** @@ -2192,7 +2205,7 @@ I3SNode.prototype._createVisualElements = function () { obb.quaternion[3] ); - var position = _WGS84ToCartesian( + var obbPosition = _WGS84ToCartesian( obb.center[0], obb.center[1], obb.center[2] @@ -2200,7 +2213,7 @@ I3SNode.prototype._createVisualElements = function () { this._entities.locator = this._dataSource.entities.add({ name: "Extent", - position: position, + position: obbPosition, orientation: orientation, box: { dimensions: new Cartesian3( @@ -2214,12 +2227,12 @@ I3SNode.prototype._createVisualElements = function () { }, }); } else if (mbs) { - var position = _WGS84ToCartesian(mbs[0], mbs[1], mbs[2]); + var mbsPosition = _WGS84ToCartesian(mbs[0], mbs[1], mbs[2]); // Add an entity for display this._entities.locator = this._dataSource.entities.add({ name: "Extent", - position: position, + position: mbsPosition, ellipse: { semiMinorAxis: mbs[3], semiMajorAxis: mbs[3], @@ -2243,6 +2256,7 @@ I3SNode.prototype._createVisualElements = function () { /** * This class implements an I3S Feature + * @private * @alias I3SFeature * @constructor * @param {I3SNode} [parent] The parent of that feature @@ -2298,15 +2312,16 @@ Object.defineProperties(I3SFeature.prototype, { /** * Loads the content. - * @returns {Promise} a promise that is resolved when the data of the I3S feature is loaded + * @returns {Promise} a promise that is resolved when the data of the I3S feature is loaded */ I3SFeature.prototype.load = function () { + var that = this; return this._dataSource._loadJson( this._completeUri, function (data, resolve) { - this._data = data; + that._data = data; resolve(); - }.bind(this), + }, function (reject) { reject(); } @@ -2325,6 +2340,7 @@ I3SFeature.prototype.load = function () { /** * This class implements an I3S Field which is custom data attachec * to nodes + * @private * @alias I3SField * @constructor * @param {I3SNode} [parent] The parent of that geometry @@ -2402,9 +2418,10 @@ Object.defineProperties(I3SField.prototype, { /** * Loads the content. - * @returns {Promise} a promise that is resolved when the geometry data is loaded + * @returns {Promise} a promise that is resolved when the geometry data is loaded */ I3SField.prototype.load = function () { + var that = this; return this._dataSource._loadBinary( this._completeUri, function (data, resolve) { @@ -2416,28 +2433,28 @@ I3SField.prototype.load = function () { var str = textContent.decode(data); if (str.includes("404")) { success = false; - console.error("Failed to load:", this._completeUri); + console.error("Failed to load:", that._completeUri); } } if (success) { - this._data = data; - var offset = this._parseHeader(dataView); + that._data = data; + var offset = that._parseHeader(dataView); // @TODO: find out why we must skip 4 bytes when the value type is float 64 if ( - this._storageInfo && - this._storageInfo.attributeValues && - this._storageInfo.attributeValues.valueType === "Float64" + that._storageInfo && + that._storageInfo.attributeValues && + that._storageInfo.attributeValues.valueType === "Float64" ) { offset += 4; } - this._parseBody(dataView, offset); + that._parseBody(dataView, offset); } resolve(); - }.bind(this), + }, function (reject) { reject(); } @@ -2499,7 +2516,12 @@ I3SField.prototype._parseValue = function (dataView, type, offset) { I3SField.prototype._parseHeader = function (dataView) { var offset = 0; this._header = {}; - for (var item of this._storageInfo.header) { + for ( + var itemIndex = 0; + itemIndex < this._storageInfo.header.length; + itemIndex++ + ) { + var item = this._storageInfo.header[itemIndex]; var parsedValue = this._parseValue(dataView, item.valueType, offset); this._header[item.property] = parsedValue.value; offset = parsedValue.offset; @@ -2512,7 +2534,12 @@ I3SField.prototype._parseHeader = function (dataView) { */ I3SField.prototype._parseBody = function (dataView, offset) { this._values = {}; - for (var item of this._storageInfo.ordering) { + for ( + var itemIndex = 0; + itemIndex < this._storageInfo.ordering.length; + itemIndex++ + ) { + var item = this._storageInfo.ordering[itemIndex]; var desc = this._storageInfo[item]; if (desc) { this._values[item] = []; @@ -2525,13 +2552,13 @@ I3SField.prototype._parseBody = function (dataView, offset) { var stringLen = this._values.attributeByteCounts[index]; var stringContent = ""; for (var cIndex = 0; cIndex < stringLen; ++cIndex) { - var parsedValue = this._parseValue( + var curParsedValue = this._parseValue( dataView, desc.valueType, offset ); - stringContent += parsedValue.value; - offset = parsedValue.offset; + stringContent += curParsedValue.value; + offset = curParsedValue.offset; } this._values[item].push(stringContent); } @@ -2553,6 +2580,7 @@ I3SField.prototype._parseBody = function (dataView, offset) { /** * This class implements an I3S Geometry, in Cesium, each I3SGeometry * generates an in memory b3dm so be used as content for a Cesium3DTile + * @private * @alias I3SGeometry * @constructor * @param {I3SNode} [parent] The parent of that geometry @@ -2618,15 +2646,16 @@ Object.defineProperties(I3SGeometry.prototype, { /** * Loads the content. - * @returns {Promise} a promise that is resolved when the geometry data is loaded + * @returns {Promise} a promise that is resolved when the geometry data is loaded */ I3SGeometry.prototype.load = function () { + var that = this; return this._dataSource._loadBinary( this._completeUri, function (data, resolve) { - this._data = data; + that._data = data; resolve(); - }.bind(this), + }, function (reject) { reject(); } @@ -2635,9 +2664,9 @@ I3SGeometry.prototype.load = function () { /** * Gets the closest point to px,py,pz in this geometry - * @param {float} [px] the x component of the point to query - * @param {float} [py] the y component of the point to query - * @param {float} [pz] the z component of the point to query + * @param {number} [px] the x component of the point to query + * @param {number} [py] the y component of the point to query + * @param {number} [pz] the z component of the point to query * @returns {object} a structure containing the index of the closest point, * the squared distance from the queried point to the point that is found * the distance from the queried point to the point that is found @@ -2765,9 +2794,20 @@ I3SGeometry.prototype._generateGLTF = function ( var textureName = "0"; if (this._layer._data.textureSetDefinitions) { - for (var textureSetDefinition of this._layer._data - .textureSetDefinitions) { - for (var textureFormat of textureSetDefinition.formats) { + for ( + var defIndex = 0; + defIndex < this._layer._data.textureSetDefinitions.length; + defIndex++ + ) { + var textureSetDefinition = this._layer._data.textureSetDefinitions[ + defIndex + ]; + for ( + var formatIndex = 0; + formatIndex < textureSetDefinition.formats.length; + formatIndex++ + ) { + var textureFormat = textureSetDefinition.formats[formatIndex]; if (textureFormat.format === "jpg") { textureName = textureFormat.name; break; @@ -2868,6 +2908,7 @@ Cesium3DTile.prototype._hookedRequestContent = * @private */ Cesium3DTile.prototype._resolveHookedObject = function () { + var that = this; // Keep a handle on the early promises var _contentReadyToProcessPromise = this._contentReadyToProcessPromise; var _contentReadyPromise = this._contentReadyPromise; @@ -2883,16 +2924,15 @@ Cesium3DTile.prototype._resolveHookedObject = function () { } if (_contentReadyPromise) { - this._contentReadyPromise.then( - function () { - this._isLoading = false; - this._content._contentReadyPromise.resolve(); - }.bind(this) - ); + this._contentReadyPromise.then(function () { + that._isLoading = false; + that._content._contentReadyPromise.resolve(); + }); } }; Cesium3DTile.prototype.requestContent = function () { + var that = this; if (!this.tileset._isI3STileSet) { return this._hookedRequestContent(); } @@ -2922,16 +2962,14 @@ Cesium3DTile.prototype.requestContent = function () { this._contentReadyToProcessPromise = when.defer(); this._contentReadyPromise = when.defer(); - this._i3sNode._scheduleCreateContentURL().then( - function () { - if (!this._contentResource._originalUrl) { - this._contentResource._originalUrl = this._contentResource._url; - } + this._i3sNode._scheduleCreateContentURL().then(function () { + if (!that._contentResource._originalUrl) { + that._contentResource._originalUrl = that._contentResource._url; + } - this._contentResource._url = this._i3sNode._b3dmURL; - this._resolveHookedObject(); - }.bind(this) - ); + that._contentResource._url = that._i3sNode._b3dmURL; + that._resolveHookedObject(); + }); } // Returns the number of requests @@ -3028,7 +3066,7 @@ function _workerCode() { result.z = matrix[2] * vX + matrix[5] * vY + matrix[8] * vZ; } - const _degToRad = 0.017453292519943; + var _degToRad = 0.017453292519943; var cartographic = { longitude: 0, latitude: 0, @@ -3106,7 +3144,6 @@ function _workerCode() { var x = lon * semimajorAxis; var y = geodeticLatitudeToMercatorAngle(lat) * semimajorAxis; - var result = { x: x, y: y }; return { x: x, y: y }; } @@ -3135,8 +3172,8 @@ function _workerCode() { x -= xi; y -= yi; - xNext = xi < geoidData.width ? xi + 1 : xi; - yNext = yi < geoidData.height ? yi + 1 : yi; + var xNext = xi < geoidData.width ? xi + 1 : xi; + var yNext = yi < geoidData.height ? yi + 1 : yi; yi = geoidData.height - 1 - yi; yNext = geoidData.height - 1 - yNext; @@ -3152,13 +3189,7 @@ function _workerCode() { } function sampleGeoidFromList(lon, lat, geoidDataList) { - for (var i in geoidDataList) { - var pt = { - longitude: lon, - latitude: lat, - height: 0, - }; - + for (var i = 0; i < geoidDataList.length; i++) { var localExtent = geoidDataList[i].nativeExtent; var lonRadian = (lon / 180) * Math.PI; var latRadian = (lat / 180) * Math.PI; @@ -3198,20 +3229,24 @@ function _workerCode() { fast ) { // Fast conversion (using the center of the tile) - var height = sampleGeoidFromList(center.long, center.lat, geoidDataList); + var centerHeight = sampleGeoidFromList( + center.long, + center.lat, + geoidDataList + ); if (fast) { - for (var index = 0; index < vertexCount; ++index) { - position[index * 3 + 2] += height; + for (var i = 0; i < vertexCount; ++i) { + position[i * 3 + 2] += centerHeight; } } else { - for (var index = 0; index < vertexCount; ++index) { + for (var j = 0; j < vertexCount; ++j) { var height = sampleGeoidFromList( - center.long + scale_x * position[index * 3], - center.lat + scale_y * position[index * 3 + 1], + center.long + scale_x * position[j * 3], + center.lat + scale_y * position[j * 3 + 1], geoidDataList ); - position[index * 3 + 2] += height; + position[j * 3 + 2] += height; } } } @@ -3395,8 +3430,12 @@ function _workerCode() { } } else { // generate indices - for (var vertexIndex = 0; vertexIndex < vertexCount; ++vertexIndex) { - indexArray[vertexIndex] = vertexIndex; + for ( + var newVertexIndex = 0; + newVertexIndex < vertexCount; + ++newVertexIndex + ) { + indexArray[newVertexIndex] = newVertexIndex; } } @@ -3657,15 +3696,15 @@ function _workerCode() { function decodeDracoEncodedGeometry(data, bufferInfo) { // Create the Draco decoder. // eslint-disable-next-line new-cap,no-undef - const dracoDecoderModule = DracoDecoderModule(); - const buffer = new dracoDecoderModule.DecoderBuffer(); + var dracoDecoderModule = DracoDecoderModule(); + var buffer = new dracoDecoderModule.DecoderBuffer(); var byteArray = new Uint8Array(data); buffer.Init(byteArray, byteArray.length); // Create a buffer to hold the encoded data. - const dracoDecoder = new dracoDecoderModule.Decoder(); - const geometryType = dracoDecoder.GetEncodedGeometryType(buffer); + var dracoDecoder = new dracoDecoderModule.Decoder(); + var geometryType = dracoDecoder.GetEncodedGeometryType(buffer); var metadataQuerier = new dracoDecoderModule.MetadataQuerier(); // Decode the encoded geometry. @@ -3697,17 +3736,20 @@ function _workerCode() { // Decode faces // @TODO: Replace that code with GetTrianglesUInt32Array for better efficiency var face = new dracoDecoderModule.DracoInt32Array(3); - for (var index = 0; index < faceCount; ++index) { - dracoDecoder.GetFaceFromMesh(dracoGeometry, index, face); - faces[index * 3] = face.GetValue(0); - faces[index * 3 + 1] = face.GetValue(1); - faces[index * 3 + 2] = face.GetValue(2); + for (var faceIndex = 0; faceIndex < faceCount; ++faceIndex) { + dracoDecoder.GetFaceFromMesh(dracoGeometry, faceIndex, face); + faces[faceIndex * 3] = face.GetValue(0); + faces[faceIndex * 3 + 1] = face.GetValue(1); + faces[faceIndex * 3 + 2] = face.GetValue(2); } dracoDecoderModule.destroy(face); - for (var index = 0; index < attributesCount; ++index) { - var dracoAttribute = dracoDecoder.GetAttribute(dracoGeometry, index); + for (var attrIndex = 0; attrIndex < attributesCount; ++attrIndex) { + var dracoAttribute = dracoDecoder.GetAttribute( + dracoGeometry, + attrIndex + ); var attributeData = decodeDracoAttribute( dracoDecoderModule, @@ -3732,7 +3774,10 @@ function _workerCode() { } // get the metadata - var metadata = dracoDecoder.GetAttributeMetadata(dracoGeometry, index); + var metadata = dracoDecoder.GetAttributeMetadata( + dracoGeometry, + attrIndex + ); if (metadata.ptr) { var numEntries = metadataQuerier.NumEntries(metadata); @@ -4080,15 +4125,22 @@ function _workerCode() { offset += 4; if (bufferInfo) { - for (var item of bufferInfo.attributes) { - if (binaryAttributeDecoders[item]) { - offset = binaryAttributeDecoders[item]( + for ( + var attrIndex = 0; + attrIndex < bufferInfo.attributes.length; + attrIndex++ + ) { + if (binaryAttributeDecoders[bufferInfo.attributes[attrIndex]]) { + offset = binaryAttributeDecoders[bufferInfo.attributes[attrIndex]]( decodedGeometry, data, offset ); } else { - console.error("Unknown decoder for", item); + console.error( + "Unknown decoder for", + bufferInfo.attributes[attrIndex] + ); } } } else { @@ -4110,20 +4162,20 @@ function _workerCode() { } // use default geometry schema - for (var item of ordering) { - var decoder = binaryAttributeDecoders[item]; + for (var i = 0; i < ordering.length; i++) { + var decoder = binaryAttributeDecoders[ordering[i]]; if (!decoder) { - console.log(item); + console.log(ordering[i]); } offset = decoder(decodedGeometry, data, offset); } - for (var item of featureAttributeOrder) { - var decoder = binaryAttributeDecoders[item]; - if (!decoder) { - console.log(item); + for (var j = 0; j < featureAttributeOrder.length; j++) { + var curDecoder = binaryAttributeDecoders[featureAttributeOrder[j]]; + if (!curDecoder) { + console.log(featureAttributeOrder[j]); } - offset = decoder(decodedGeometry, data, offset); + offset = curDecoder(decodedGeometry, data, offset); } } } catch (e) { @@ -4235,31 +4287,28 @@ function _workerCode() { ..#####.##..#######..########..#######..######## */ function I3SGLTFProcessingQueue() { + var that = this; this._queue = []; this._processing = false; - this._createWorkers( - function () { - this._process(); - }.bind(this) - ); + this._createWorkers(function () { + that._process(); + }); } I3SGLTFProcessingQueue.prototype._process = function () { - for (var worker of this._workers) { - if (worker.isReadyToWork) { + var that = this; + for (var workerIndex = 0; workerIndex < this._workers.length; workerIndex++) { + if (this._workers[workerIndex].isReadyToWork) { if (this._queue.length > 0) { var task = this._queue.shift(); - task.execute(worker); + task.execute(this._workers[workerIndex]); traceCode("Process Queue:" + this._queue.length); } } } - setTimeout( - function () { - this._process(); - }.bind(this), - 100 - ); + setTimeout(function () { + that._process(); + }, 100); }; I3SGLTFProcessingQueue.prototype._createWorkers = function (cb) { @@ -4267,28 +4316,31 @@ I3SGLTFProcessingQueue.prototype._createWorkers = function (cb) { var externalModules = ["/Source/ThirdParty/Workers/draco_decoder.js"]; - var externalModuleCode = ""; var fetchPromises = []; + var externalModuleData = []; for (var index = 0; index < externalModules.length; ++index) { - var moduleLoadedResolve; - var moduleLoaded = new _Promise(function (resolve, reject) { - moduleLoadedResolve = resolve; - }); - fetchPromises.push(moduleLoaded); - - // eslint-disable-next-line no-loop-func - fetch(externalModules[index]).then(function (response) { - response.text().then(function (data) { - externalModuleCode += data; - moduleLoadedResolve(); + fetchPromises.push(when.defer()); + externalModuleData.push(""); + + var fetchFunction = function (curIndex) { + fetch(externalModules[curIndex]).then(function (response) { + response.text().then(function (data) { + externalModuleData[curIndex] = data; + fetchPromises[curIndex].resolve(); + }); }); - }); + }; + fetchFunction(index); } - var externalModulesLoaded = _Promise.all(fetchPromises); + var externalModulesLoaded = when.all(fetchPromises); var that = this; externalModulesLoaded.then(function () { + var externalModuleCode = ""; + for (var i = 0; i < externalModuleData.length; i++) + externalModuleCode += externalModuleData[i]; + workerCode = workerCode.replace("function _workerCode() {", ""); workerCode = workerCode.slice(0, -1); var blob = new Blob([externalModuleCode + workerCode], { @@ -4303,31 +4355,35 @@ I3SGLTFProcessingQueue.prototype._createWorkers = function (cb) { for (var loop = 0; loop < workerCount; ++loop) { var worker = new Worker(window.URL.createObjectURL(blob)); - worker.setTask = function (task) { - worker._task = task; - worker.isReadyToWork = false; - worker.postMessage(task.payload); - }; - - worker.onmessage = function (e) { - var task = worker._task; - - var gltfData = task._geometry._generateGLTF( - e.data.nodesInScene, - e.data.nodes, - e.data.meshes, - e.data.buffers, - e.data.bufferViews, - e.data.accessors - ); + worker.setTask = (function (thisWorker) { + return function (task) { + thisWorker._task = task; + thisWorker.isReadyToWork = false; + thisWorker.postMessage(task.payload); + }; + })(worker); + + worker.onmessage = (function (thisWorker) { + return function (e) { + var task = thisWorker._task; + + var gltfData = task._geometry._generateGLTF( + e.data.nodesInScene, + e.data.nodes, + e.data.meshes, + e.data.buffers, + e.data.bufferViews, + e.data.accessors + ); - worker._task = null; - worker.isReadyToWork = true; - task.resolve({ - gltfData: gltfData, - customAttributes: e.data.customAttributes, - }); - }; + thisWorker._task = null; + thisWorker.isReadyToWork = true; + task.resolve({ + gltfData: gltfData, + customAttributes: e.data.customAttributes, + }); + }; + })(worker); worker.isReadyToWork = true; that._workers.push(worker); @@ -4413,13 +4469,12 @@ I3SGLTFProcessingQueue.prototype.addTask = function (data) { }, }; - return new _Promise( - function (resolve, reject) { - newTask.resolve = resolve; - newTask.reject = reject; - this._queue.push(newTask); - }.bind(this) - ); + var that = this; + return new _Promise(function (resolve, reject) { + newTask.resolve = resolve; + newTask.reject = reject; + that._queue.push(newTask); + }); }; /* From 882b9b66a0e01492ca234d894876018623224821 Mon Sep 17 00:00:00 2001 From: Tamrat Belayneh Date: Tue, 6 Jul 2021 13:29:47 -0700 Subject: [PATCH 05/57] log clean ups --- Apps/Sandcastle/gallery/I3S Feature Picking.html | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/Apps/Sandcastle/gallery/I3S Feature Picking.html b/Apps/Sandcastle/gallery/I3S Feature Picking.html index 7a2f09a39e8a..241927362108 100644 --- a/Apps/Sandcastle/gallery/I3S Feature Picking.html +++ b/Apps/Sandcastle/gallery/I3S Feature Picking.html @@ -84,16 +84,13 @@

Loading...

) { var i3sNode = pickedFeature.content.i3sNode; i3sNode.loadFields().then(function () { - console.log(i3sNode); var geometry = i3sNode.geometryData[0]; - console.log(geometry); if (pickedPosition) { var location = geometry.getClosestPointIndex( pickedPosition.x, pickedPosition.y, pickedPosition.z ); - console.log("Location", location); var description = "No attributes"; var name = ""; if ( @@ -106,9 +103,8 @@

Loading...

description = ''; for (var fieldName in i3sNode.fields) { - if (fieldName.legnth > 0) { + if (fieldName) { var field = i3sNode.fields[fieldName]; - //console.log(field.name + ": " + field.values[featureIndex]); description += ""; @@ -132,8 +128,6 @@

Loading...

} }); } - - console.log(viewer.scene.camera); }, Cesium.ScreenSpaceEventType.LEFT_CLICK); From 0e566d0f25a68c2bab91f98c319c74ce63609c93 Mon Sep 17 00:00:00 2001 From: Tam Date: Mon, 8 Nov 2021 20:25:45 -0800 Subject: [PATCH 06/57] - Updated i3s support to cesiumjs 1.87 - clamp to default min & max values in ArcGISTiledElevationTerrainProvider if not available - Improved pick accuracy (Find a triangle touching the point px,py,pz, then return the vertex closest to the search point) --- .../gallery/I3S Feature Picking.html | 2 +- .../gallery/I3S IntegratedMesh Layer.html | 5 +- .../ArcGISTiledElevationTerrainProvider.js | 9 +- Source/DataSources/I3SDataSource.js | 906 +++++++++--------- 4 files changed, 474 insertions(+), 448 deletions(-) diff --git a/Apps/Sandcastle/gallery/I3S Feature Picking.html b/Apps/Sandcastle/gallery/I3S Feature Picking.html index 241927362108..3ff17c73b752 100644 --- a/Apps/Sandcastle/gallery/I3S Feature Picking.html +++ b/Apps/Sandcastle/gallery/I3S Feature Picking.html @@ -86,7 +86,7 @@

Loading...

i3sNode.loadFields().then(function () { var geometry = i3sNode.geometryData[0]; if (pickedPosition) { - var location = geometry.getClosestPointIndex( + var location = geometry.getClosestPointIndexOnTriangle( pickedPosition.x, pickedPosition.y, pickedPosition.z diff --git a/Apps/Sandcastle/gallery/I3S IntegratedMesh Layer.html b/Apps/Sandcastle/gallery/I3S IntegratedMesh Layer.html index b07c417507ff..fb2a7f506a6b 100644 --- a/Apps/Sandcastle/gallery/I3S IntegratedMesh Layer.html +++ b/Apps/Sandcastle/gallery/I3S IntegratedMesh Layer.html @@ -38,6 +38,7 @@

Loading...

function startup(Cesium) { "use strict"; //Sandcastle_Begin + Cesium.Ion.defaultAccessToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiI1N2U0ZGUzZS01NTgyLTQxYjYtYWM1ZS1mZDczY2UyNDE4ZWIiLCJpZCI6Mjg0NjcsInNjb3BlcyI6WyJhc3IiLCJnYyJdLCJpYXQiOjE1OTExMzUzNjB9.TVYF3wBTwODi-6HYXABxfYSp7Tfq3zJQvtdd-adBOMM'; // Create a Viewer instances wiht ArcGIS Tiled Elevation Layer var viewer = new Cesium.Viewer("cesiumContainer", { terrainProvider: new Cesium.ArcGISTiledElevationTerrainProvider({ @@ -49,9 +50,9 @@

Loading...

}); //Create an I3S DataSource with the camera autocentered to the I3S layers' extent //No need to do a geoid conversion as both the elevation proivder (ArcGISTiledElevationTerrainProvider) - //and the I3S layer to be added (San Francisco) use orthometric height systems . + //and the I3S layer to be added (Frankfurt) use orthometric height systems . var dataSource = new Cesium.I3SDataSource( - "San Francisco", + "Frankfurt", viewer.scene, { autoCenterCameraOnStart: true, diff --git a/Source/Core/ArcGISTiledElevationTerrainProvider.js b/Source/Core/ArcGISTiledElevationTerrainProvider.js index 437c698e5949..f4a7d7c9fc6e 100644 --- a/Source/Core/ArcGISTiledElevationTerrainProvider.js +++ b/Source/Core/ArcGISTiledElevationTerrainProvider.js @@ -174,15 +174,18 @@ function ArcGISTiledElevationTerrainProvider(options) { ); } - if (metadata.minValues && metadata.maxValues) { + if(metadata.minValues && metadata.maxValues) + { that._terrainDataStructure = { elementMultiplier: 1.0, lowestEncodedHeight: metadata.minValues[0], highestEncodedHeight: metadata.maxValues[0], }; - } else { + } + else + { that._terrainDataStructure = { - elementMultiplier: 1.0, + elementMultiplier: 1.0 }; } diff --git a/Source/DataSources/I3SDataSource.js b/Source/DataSources/I3SDataSource.js index 202a1c4cf6b4..a122364b6ce8 100644 --- a/Source/DataSources/I3SDataSource.js +++ b/Source/DataSources/I3SDataSource.js @@ -130,7 +130,7 @@ viewer.screenSpaceEventHandler.setInputAction(function onLeftClick( var geometry = i3sNode.geometryData[0]; console.log(geometry); if (pickedPosition) { - var location = geometry.getClosestPointIndex( + var location = geometry.getClosestPointIndexOnTriangle( pickedPosition.x, pickedPosition.y, pickedPosition.z); console.log("Location", location); if (location.index !== -1 && geometry.customAttributes["feature-index"]) { @@ -163,7 +163,6 @@ viewer.screenSpaceEventHandler.setInputAction(function onLeftClick( import Batched3DModel3DTileContent from "../Scene/Batched3DModel3DTileContent.js"; import Cartesian2 from "../Core/Cartesian2.js"; import Cartesian3 from "../Core/Cartesian3.js"; -import Cartesian4 from "../Core/Cartesian4.js"; import Cartographic from "../Core/Cartographic.js"; import Cesium3DTile from "../Scene/Cesium3DTile.js"; import Cesium3DTileset from "../Scene/Cesium3DTileset.js"; @@ -285,23 +284,24 @@ function I3SDataSource(name, scene, options) { this._traceVisuals = false; this._autoCenterCameraOnStart = false; this._GLTFProcessingQueue = new I3SGLTFProcessingQueue(); - + options = defaultValue(options, defaultValue.EMPTY_OBJECT); if (defined(options.traceVisuals)) { - this._traceVisuals = options.traceVisuals; + this._traceVisuals = options.traceVisuals; } if (defined(options.traceFetches)) { - this._traceFetches = options.traceFetches; + this._traceFetches = options.traceFetches; } if (defined(options.autoCenterCameraOnStart)) { - this._autoCenterCameraOnStart = options.autoCenterCameraOnStart; + this._autoCenterCameraOnStart = options.autoCenterCameraOnStart; } - + if (defined(options.geoidTiledTerrainProvider)) { - this._geoidTiledTerrainProvider = options.geoidTiledTerrainProvider; + this._geoidTiledTerrainProvider = options.geoidTiledTerrainProvider; } + } /* @@ -468,7 +468,7 @@ Object.defineProperties(I3SDataSource.prototype, { }, }, - /** + /** * The terrain provider referencing the GEOID service to be used for orthometric to ellipsoidal conversion * @memberof Viewer.prototype * @@ -507,8 +507,8 @@ I3SDataSource.prototype.loadUrl = function (url) { var deferredPromise = new when.defer(); this._sceneServer = new I3SSceneServer(this); - this._sceneServer.load(this._completeUrl).then(function () { - deferredPromise.resolve(); + this._sceneServer.load(this._completeUrl).then(function() { + deferredPromise.resolve(); }); return deferredPromise; @@ -557,13 +557,13 @@ I3SDataSource.prototype._setLoading = function (isLoading) { */ I3SDataSource.prototype._loadJson = function (uri, success, fail) { var that = this; - return new _Promise(function (resolve, reject) { + return new _Promise(function(resolve, reject) { if (that._traceFetches) { console.log("I3S FETCH:", uri); } var request = fetch(uri); - request.then(function (response) { - response.json().then(function (data) { + request.then(function(response) { + response.json().then(function(data) { if (data.error) { console.error(that._data.error.message); fail(reject); @@ -580,13 +580,13 @@ I3SDataSource.prototype._loadJson = function (uri, success, fail) { */ I3SDataSource.prototype._loadBinary = function (uri, success, fail) { var that = this; - return new _Promise(function (resolve, reject) { + return new _Promise(function(resolve, reject) { if (that._traceFetches) { traceCode("I3S FETCH:", uri); } var request = fetch(uri); - request.then(function (response) { - response.arrayBuffer().then(function (data) { + request.then(function(response) { + response.arrayBuffer().then(function(data) { if (data.error) { console.error(that._data.error.message); fail(reject); @@ -866,15 +866,11 @@ I3SSceneServer.prototype.load = function (uri) { this._uri = uri; return this._dataSource._loadJson( uri, - function (data, resolve) { + function(data, resolve) { // Success that._data = data; var layerPromises = []; - for ( - var layerIndex = 0; - layerIndex < that._data.layers.length; - layerIndex++ - ) { + for (var layerIndex=0; layerIndex < that._data.layers.length; layerIndex++) { var newLayer = new I3SLayer( that, that._data.layers[layerIndex], @@ -884,7 +880,8 @@ I3SSceneServer.prototype.load = function (uri) { layerPromises.push(newLayer.load()); } - _Promise.all(layerPromises).then(function () { + _Promise.all(layerPromises).then(function() { + that._computeExtent(); if (that._dataSource._autoCenterCameraOnStart) { @@ -895,7 +892,7 @@ I3SSceneServer.prototype.load = function (uri) { that._createVisualElements(); }); }, - function (reject) { + function(reject) { // Fail reject(); } @@ -936,21 +933,15 @@ I3SSceneServer.prototype.centerCamera = function (mode) { * @private */ I3SSceneServer.prototype._computeExtent = function () { + var minLongitude = Number.MAX_VALUE; var maxLongitude = -Number.MAX_VALUE; var minLatitude = Number.MAX_VALUE; var maxLatitude = -Number.MAX_VALUE; // Compute the extent from all layers - for ( - var layerIndex = 0; - layerIndex < this._layerCollection.length; - layerIndex++ - ) { - if ( - this._layerCollection[layerIndex]._data.store && - this._layerCollection[layerIndex]._data.store.extent - ) { + for (var layerIndex=0; layerIndex < this._layerCollection.length; layerIndex++) { + if (this._layerCollection[layerIndex]._data.store && this._layerCollection[layerIndex]._data.store.extent) { var layerExtent = this._layerCollection[layerIndex]._data.store.extent; minLongitude = Math.min(minLongitude, layerExtent[0]); minLatitude = Math.min(minLatitude, layerExtent[1]); @@ -1107,7 +1098,7 @@ Object.defineProperties(I3SLayer.prototype, { }); /** - * @private + * @private */ function getCoveredTiles(terrainProvider, extents) { return terrainProvider.readyPromise.then(function () { @@ -1121,24 +1112,20 @@ function getTiles(terrainProvider, extents) { // Sort points into a set of tiles var tileRequests = []; // Result will be an Array as it's easier to work with var tileRequestSet = {}; // A unique set - + var maxLevel = terrainProvider._lodCount; - - var minCorner = Cartographic.fromDegrees( - extents.minLongitude, - extents.minLatitude - ); - var maxCorner = Cartographic.fromDegrees( - extents.maxLongitude, - extents.maxLatitude - ); + + var minCorner = Cartographic.fromDegrees(extents.minLongitude, extents.minLatitude); + var maxCorner = Cartographic.fromDegrees(extents.maxLongitude, extents.maxLatitude); var minCornerXY = tilingScheme.positionToTileXY(minCorner, maxLevel); var maxCornerXY = tilingScheme.positionToTileXY(maxCorner, maxLevel); - + //Get all the tiles in between - for (var x = minCornerXY.x; x <= maxCornerXY.x; x++) { - for (var y = minCornerXY.y; y <= maxCornerXY.y; y++) { - var xy = new Cartesian2(x, y); + for(var x = minCornerXY.x; x <= maxCornerXY.x; x++) + { + for(var y = minCornerXY.y; y <= maxCornerXY.y; y++) + { + var xy = new Cartesian2(x,y); var key = xy.toString(); if (!tileRequestSet.hasOwnProperty(key)) { // When tile is requested for the first time @@ -1155,7 +1142,7 @@ function getTiles(terrainProvider, extents) { } } } - + // Send request for each required tile var tilePromises = []; for (var i = 0; i < tileRequests.length; ++i) { @@ -1168,47 +1155,46 @@ function getTiles(terrainProvider, extents) { tilePromises.push(requestPromise); } - - return when.all(tilePromises).then(function (heightMapBuffers) { + + return when.all(tilePromises).then(function(heightMapBuffers) { var heightMaps = []; - for (var i = 0; i < heightMapBuffers.length; i++) { + for (var i=0; i< heightMapBuffers.length; i++) + { var options = { tilingScheme: tilingScheme, x: tileRequests[i].x, - y: tileRequests[i].y, - level: tileRequests[i].level, + y: tileRequests[i].y, + level: tileRequests[i].level }; var heightMap = heightMapBuffers[i]; - + var projectionType = "Geographic"; - if (tilingScheme._projection instanceof WebMercatorProjection) { - projectionType = "WebMercator"; + if(tilingScheme._projection instanceof WebMercatorProjection) + { + projectionType = "WebMercator"; } - + var heightMapData = { - projectionType: projectionType, - projection: tilingScheme._projection, - nativeExtent: tilingScheme.tileXYToNativeRectangle( - options.x, - options.y, - options.level - ), - height: heightMap._height, - width: heightMap._width, - scale: heightMap._structure.heightScale, - offset: heightMap._structure.heightOffset, + projectionType: projectionType, + projection: tilingScheme._projection, + nativeExtent : tilingScheme.tileXYToNativeRectangle(options.x, options.y, options.level), + height: heightMap._height, + width: heightMap._width, + scale: heightMap._structure.heightScale, + offset: heightMap._structure.heightOffset }; - - if (heightMap._encoding === HeightmapEncoding.LERC) { + + if(heightMap._encoding === HeightmapEncoding.LERC) { var result = Lerc.decode(heightMap._buffer); heightMapData.buffer = result.pixels[0]; - } else { + } + else { heightMapData.buffer = heightMap._buffer; } - + heightMaps.push(heightMapData); } - + return heightMaps; }); } @@ -1219,61 +1205,55 @@ function getTiles(terrainProvider, extents) { */ I3SLayer.prototype.load = function () { var that = this; - return new _Promise(function (resolve, reject) { + return new _Promise(function(resolve, reject) { that._computeExtent(); - + + //Load tiles from arcgis var geoidTerrainProvider = that._dataSource._geoidTiledTerrainProvider; - + var dataIsReady = new when.defer(); var geoidDataList = []; if (defined(geoidTerrainProvider)) { - if (geoidTerrainProvider.ready) { - var tilesReadyPromise = getCoveredTiles( - geoidTerrainProvider, - that._extent - ); - when(tilesReadyPromise, function (heightMaps) { - geoidDataList = heightMaps; + if (geoidTerrainProvider.ready) { + var tilesReadyPromise = getCoveredTiles(geoidTerrainProvider, that._extent); + when(tilesReadyPromise, function(heightMaps) { + geoidDataList = heightMaps; + dataIsReady.resolve(); + }); + } else { + console.log("Geoid Terrain service not available - no geoid conversion will be performed."); dataIsReady.resolve(); - }); - } else { - console.log( - "Geoid Terrain service not available - no geoid conversion will be performed." - ); - dataIsReady.resolve(); - } + } } else { - console.log( - "No Geoid Terrain service provided - no geoid conversion will be performed." - ); + console.log("No Geoid Terrain service provided - no geoid conversion will be performed."); dataIsReady.resolve(); } - dataIsReady.then(function () { - that._dataSource._geoidDataList = geoidDataList; - console.log("Starting to load visual elements"); - that._createVisualElements(); - if (that._data.spatialReference.wkid === 4326) { - that._loadNodePage(0).then(function () { - that._loadRootNode().then(function () { - that._create3DTileSet(); - if (that._data.store.version === "1.6") { - that._rootNode._loadChildren().then(function () { + dataIsReady.then(function() { + that._dataSource._geoidDataList = geoidDataList; + console.log("Starting to load visual elements"); + that._createVisualElements(); + if (that._data.spatialReference.wkid === 4326) { + that._loadNodePage(0).then(function() { + that._loadRootNode().then(function() { + that._create3DTileSet(); + if (that._data.store.version === "1.6") { + that._rootNode._loadChildren().then(function() { + resolve(); + }); + } else { resolve(); - }); - } else { - resolve(); - } + } + }); }); - }); - } else { - console.log( - "Unsupported spatial reference: " + that._data.spatialReference.wkid - ); - resolve(); - } + } else { + console.log( + "Unsupported spatial reference: " + that._data.spatialReference.wkid + ); + resolve(); + } }); }); }; @@ -1289,16 +1269,11 @@ I3SLayer.prototype._computeGeometryDefinitions = function (useCompression) { this._geometryDefinitions = []; if (this._data.geometryDefinitions) { - for ( - var defIndex = 0; - defIndex < this._data.geometryDefinitions.length; - defIndex++ - ) { + for (var defIndex=0; defIndex < this._data.geometryDefinitions.length; defIndex++) { var geometryBuffersInfo = []; - var geometryBuffers = this._data.geometryDefinitions[defIndex] - .geometryBuffers; + var geometryBuffers = this._data.geometryDefinitions[defIndex].geometryBuffers; - for (var bufIndex = 0; bufIndex < geometryBuffers.length; bufIndex++) { + for (var bufIndex=0; bufIndex < geometryBuffers.length; bufIndex++) { var geometryBuffer = geometryBuffers[bufIndex]; var collectedAttributes = []; var compressed = false; @@ -1307,7 +1282,7 @@ I3SLayer.prototype._computeGeometryDefinitions = function (useCompression) { // check if compressed compressed = true; var attributes = geometryBuffer.compressedAttributes.attributes; - for (var i = 0; i < attributes.length; i++) { + for (var i=0; i < attributes.length; i++) { collectedAttributes.push(attributes[i]); } } else { @@ -1327,7 +1302,7 @@ I3SLayer.prototype._computeGeometryDefinitions = function (useCompression) { } // rank the buffer info - geometryBuffersInfo.sort(function (a, b) { + geometryBuffersInfo.sort(function(a, b) { if (a.compressed && !b.compressed) { return -1; } else if (!a.compressed && b.compressed) { @@ -1358,7 +1333,7 @@ I3SLayer.prototype._findBestGeometryBuffers = function ( var geometryBufferInfo = geometryDefinition[index]; var missed = false; var geometryAttributes = geometryBufferInfo.attributes; - for (var attrIndex = 0; attrIndex < attributes.length; attrIndex++) { + for (var attrIndex=0; attrIndex < attributes.length; attrIndex++) { if (!geometryAttributes.includes(attributes[attrIndex])) { missed = true; break; @@ -1401,8 +1376,8 @@ I3SLayer.prototype._getNodeInNodePages = function (nodeIndex) { var Index = Math.floor(nodeIndex / this._data.nodePages.nodesPerPage); var offsetInPage = nodeIndex % this._data.nodePages.nodesPerPage; var that = this; - return new _Promise(function (resolve, reject) { - that._loadNodePage(Index).then(function () { + return new _Promise(function(resolve, reject) { + that._loadNodePage(Index).then(function() { resolve(that._nodePages[Index][offsetInPage]); }); }); @@ -1413,13 +1388,13 @@ I3SLayer.prototype._getNodeInNodePages = function (nodeIndex) { */ I3SLayer.prototype._loadNodePage = function (page) { var that = this; - return new _Promise(function (resolve, reject) { + return new _Promise(function(resolve, reject) { if (that._nodePages[page] !== undefined) { resolve(); } else if (that._nodePageFetches[page] !== undefined) { that._nodePageFetches[page]._promise = that._nodePageFetches[ page - ]._promise.then(function () { + ]._promise.then(function() { resolve(); }); } else { @@ -1432,10 +1407,7 @@ I3SLayer.prototype._loadNodePage = function (page) { nodePageURI += page + query; that._nodePageFetches[page] = {}; - that._nodePageFetches[page]._promise = new _Promise(function ( - resolve, - reject - ) { + that._nodePageFetches[page]._promise = new _Promise(function(resolve, reject) { that._nodePageFetches[page]._resolve = resolve; }); @@ -1447,10 +1419,10 @@ I3SLayer.prototype._loadNodePage = function (page) { }; fetch(nodePageURI) - .then(function (response) { + .then(function(response) { response .json() - .then(function (data) { + .then(function(data) { if (data.error && data.error.code !== 200) { _resolve(); } else { @@ -1458,11 +1430,11 @@ I3SLayer.prototype._loadNodePage = function (page) { _resolve(); } }) - .catch(function () { + .catch(function() { _resolve(); }); }) - .catch(function () { + .catch(function() { _resolve(); }); } @@ -1473,6 +1445,7 @@ I3SLayer.prototype._loadNodePage = function (page) { * @private */ I3SLayer.prototype._computeExtent = function () { + var layerExtent = this._data.store.extent; this._extent = _computeExtent( layerExtent[0], @@ -1480,6 +1453,7 @@ I3SLayer.prototype._computeExtent = function () { layerExtent[2], layerExtent[3] ); + }; /** @@ -1539,15 +1513,15 @@ I3SLayer.prototype._create3DTileSet = function () { this._tileset._isI3STileSet = true; var that = this; - this._tileset.readyPromise.then(function () { - that._tileset.tileLoad.addEventListener(function (tile) {}); + this._tileset.readyPromise.then(function() { + that._tileset.tileLoad.addEventListener(function(tile) {}); - that._tileset.tileUnload.addEventListener(function (tile) { + that._tileset.tileUnload.addEventListener(function(tile) { tile._i3sNode._clearGeometryData(); tile._contentResource._url = tile._i3sNode._completeUriWithoutQuery; }); - that._tileset.tileVisible.addEventListener(function (tile) { + that._tileset.tileVisible.addEventListener(function(tile){ if (tile._i3sNode) { tile._i3sNode._loadChildren(); } @@ -1594,25 +1568,26 @@ function I3SNode(parent, ref) { if (this._level === 0) { if (typeof ref === "number") { this._nodeIndex = ref; - this._uri = "./nodes/" + ref; } else { this._uri = ref; } } else if (typeof ref === "number") { this._nodeIndex = ref; - this._uri = "../" + ref; } else { this._uri = ref; } - var query = ""; - if (this._dataSource._query && this._dataSource._query !== "") { - query = "?" + this._dataSource._query; - } + if(this._uri !== undefined) + { + var query = ""; + if (this._dataSource._query && this._dataSource._query !== "") { + query = "?" + this._dataSource._query; + } - this._completeUriWithoutQuery = - this._parent._completeUriWithoutQuery + "/" + this._uri; - this._completeUri = this._completeUriWithoutQuery + query; + this._completeUriWithoutQuery = + this._parent._completeUriWithoutQuery + "/" + this._uri; + this._completeUri = this._completeUriWithoutQuery + query; + } this._entities = {}; this._tile = null; @@ -1758,22 +1733,44 @@ I3SNode.prototype.load = function (isRoot) { if (this._nodeIndex === undefined) { return this._dataSource._loadJson( this._completeUri, - function (data, resolve) { + function(data, resolve) { // Success that._data = data; processData(); resolve(); }, - function (reject) { + function(reject) { // Fail reject(); } ); } - return new _Promise(function (resolve, reject) { - that._layer._getNodeInNodePages(that._nodeIndex).then(function (data) { + return new _Promise(function(resolve, reject){ + that._layer._getNodeInNodePages(that._nodeIndex).then(function(data) { that._data = data; + + var pageSize = that._layer._data.nodePages.nodesPerPage; + var node = that._layer._nodePages[Math.floor(that._nodeIndex/pageSize)][that._nodeIndex%pageSize]; + if (isRoot) + { + that._uri = "nodes/root"; + } + else if(node.mesh !== undefined) + { + var uriIndex = that._layer._nodePages[Math.floor(that._nodeIndex/pageSize)][that._nodeIndex%pageSize].mesh.geometry.resource; + that._uri = "../" + uriIndex; + } + if (that._uri !== undefined) + { + that._completeUriWithoutQuery = that._parent._completeUriWithoutQuery + "/" + that._uri; + var query = ""; + if (that._dataSource._query && that._dataSource._query !== "") { + query = "?" + that._dataSource._query; + } + that._completeUri = that._completeUriWithoutQuery + query; + } + processData(); resolve(); }); @@ -1787,23 +1784,22 @@ I3SNode.prototype.load = function (isRoot) { I3SNode.prototype.loadFields = function () { // check if we must load fields var fields = this._layer._data.attributeStorageInfo; - var fieldPromiseReady = when.defer(); var that = this; - function createAndLoadField(fields, index) { - if (!fields || index >= fields.length) { - return fieldPromiseReady.resolve(); - } - + function createAndLoadField(fields, index) + { var newField = new I3SField(that, fields[index]); that._fields[newField._storageInfo.name] = newField; - newField.load().then(function () { - createAndLoadField(fields, index + 1); - }); + return newField.load(); + } + + var promises = []; + for(var i=0; i 0) { var task = that._dataSource._GLTFProcessingQueue.addTask({ geometryData: that._geometryData[0], @@ -2160,7 +2126,7 @@ I3SNode.prototype._createContentURL = function (resolve, tile) { url: that._geometryData[0]._completeUri, tile: that._tile, }); - task.then(function (data) { + task.then(function(data) { rawGLTF = data.gltfData; that._geometryData[0].customAttributes = data.customAttributes; resolve(); @@ -2170,7 +2136,7 @@ I3SNode.prototype._createContentURL = function (resolve, tile) { } }); - generateGLTF.then(function () { + generateGLTF.then(function() { var binaryGLTFData = that._dataSource._binarizeGLTF(rawGLTF); var b3dmRawData = that._dataSource._binarizeB3DM( featureTableJSON, @@ -2318,11 +2284,11 @@ I3SFeature.prototype.load = function () { var that = this; return this._dataSource._loadJson( this._completeUri, - function (data, resolve) { + function(data, resolve) { that._data = data; resolve(); }, - function (reject) { + function(reject) { reject(); } ); @@ -2424,7 +2390,7 @@ I3SField.prototype.load = function () { var that = this; return this._dataSource._loadBinary( this._completeUri, - function (data, resolve) { + function(data, resolve) { // check if we have a 404 var dataView = new DataView(data); var success = true; @@ -2455,7 +2421,7 @@ I3SField.prototype.load = function () { resolve(); }, - function (reject) { + function(reject) { reject(); } ); @@ -2516,11 +2482,7 @@ I3SField.prototype._parseValue = function (dataView, type, offset) { I3SField.prototype._parseHeader = function (dataView) { var offset = 0; this._header = {}; - for ( - var itemIndex = 0; - itemIndex < this._storageInfo.header.length; - itemIndex++ - ) { + for (var itemIndex=0; itemIndex < this._storageInfo.header.length; itemIndex++) { var item = this._storageInfo.header[itemIndex]; var parsedValue = this._parseValue(dataView, item.valueType, offset); this._header[item.property] = parsedValue.value; @@ -2534,11 +2496,7 @@ I3SField.prototype._parseHeader = function (dataView) { */ I3SField.prototype._parseBody = function (dataView, offset) { this._values = {}; - for ( - var itemIndex = 0; - itemIndex < this._storageInfo.ordering.length; - itemIndex++ - ) { + for (var itemIndex=0; itemIndex < this._storageInfo.ordering.length; itemIndex++) { var item = this._storageInfo.ordering[itemIndex]; var desc = this._storageInfo[item]; if (desc) { @@ -2652,18 +2610,42 @@ I3SGeometry.prototype.load = function () { var that = this; return this._dataSource._loadBinary( this._completeUri, - function (data, resolve) { + function(data, resolve) { that._data = data; resolve(); }, - function (reject) { + function(reject) { reject(); } ); }; +function dot(a, b) { + return a.x*b.x + a.y*b.y + a.z*b.z; +} +function cross(a, b) { + return new Cartesian3(a.y*b.z-a.z*b.y, a.z*b.x-a.x*b.z, a.x*b.y-a.y*b.x); +} +function subtract(a,b) { + return new Cartesian3(a.x-b.x, a.y-b.y, a.z-b.z); +} +function scale(v, scalar) { + return new Cartesian3(v.x*scalar, v.y*scalar, v.z*scalar); +} +function normalize(a) { + return Cartesian3.magnitude(a) > 0 ? scale(a, 1/Cartesian3.magnitude(a)) : a; +} + +function sameSide (p1, p2, a, b) +{ + var cp1 = cross(subtract(b, a), subtract(p1, a)); + var cp2 = cross(subtract(b, a), subtract(p2, a)); + return dot(cp1, cp2) >= 0; +} + + /** - * Gets the closest point to px,py,pz in this geometry + * Find a triangle touching the point px,py,pz, then return the vertex closest to the search point * @param {number} [px] the x component of the point to query * @param {number} [py] the y component of the point to query * @param {number} [pz] the z component of the point to query @@ -2673,56 +2655,125 @@ I3SGeometry.prototype.load = function () { * the queried position in local space * the closest position in local space */ -I3SGeometry.prototype.getClosestPointIndex = function (px, py, pz) { - if (this.customAttributes && this.customAttributes.positions) { - var transformation = new Matrix4(); - transformation = Matrix4.inverse( - this._parent._tile.computedTransform, - transformation - ); - +I3SGeometry.prototype.getClosestPointIndexOnTriangle = function (px, py, pz) { + if (this.customAttributes && this.customAttributes.positions) + { // convert queried position to local - var position = new Cartesian4(px, py, pz, 1); - position = Matrix4.multiplyByVector(transformation, position, position); + var position = new Cartesian3(px, py, pz); + + position.x -= this.customAttributes.cartesianCenter.x; + position.y -= this.customAttributes.cartesianCenter.y; + position.z -= this.customAttributes.cartesianCenter.z; + Matrix3.multiplyByVector(this.customAttributes.parentRotation, position, position); + + var bestTriDist = Number.MAX_VALUE; + var bestTri; + var bestDistSq; + var bestIndex; + var bestPt; // Brute force lookup, @TODO: this can be improved with a spatial partitioning search system - var count = this.customAttributes.positions.length; - var bestIndex = -1; - var bestDistanceSquared = Number.MAX_VALUE; - var bestX, bestY, bestZ; - var x, y, z, distanceSquared; var positions = this.customAttributes.positions; - for (var loop = 0; loop < count; loop += 3) { - x = positions[loop] - position.x; - y = positions[loop + 1] - position.y; - z = positions[loop + 2] - position.z; - - distanceSquared = x * x + y * y + z * z; - if (bestDistanceSquared > distanceSquared) { - bestDistanceSquared = distanceSquared; - bestIndex = loop / 3; - bestX = positions[loop]; - bestY = positions[loop + 1]; - bestZ = positions[loop + 2]; + var indices = this.customAttributes.indices; + + //We may have indexed or non-indexed triangles here + var triCount; + if(indices) + { + triCount = indices.length; + } + else + { + triCount = positions.length/3; + } + + for (var triIndex = 0; triIndex < triCount; triIndex++) + { + var v0, v1, v2, i0, i1, i2; + if(indices) + { + i0 = indices[triIndex]; + i1 = indices[triIndex+1]; + i2 = indices[triIndex+2]; + } + else + { + i0 = triIndex*3; + i1 = triIndex*3 + 3; + i2 = triIndex*3 + 6; + } + + + v0 = new Cartesian3(positions[i0*3], positions[i0*3+1], positions[i0*3+2]); + v1 = new Cartesian3(positions[i1*3], positions[i1*3+1], positions[i1*3+2]); + v2 = new Cartesian3(positions[i2*3], positions[i2*3+1], positions[i2*3+2]); + + //Check how the point is positioned relative to the triangle. + //This will tell us whether the projection of the point in the triangle's plane lands in the triangle + if(!sameSide(position, v0, v1, v2) || !sameSide(position, v1, v0, v2) || !sameSide(position, v2, v0, v1)) + continue; + + //Because of precision issues, we can't always reliably tell if the point lands directly on the face, so the most robust way is just to find the closest one + var v0v1 = subtract(v1, v0); + var v0v2 = subtract(v2, v0); + var normal = normalize(cross(v0v1, v0v2)); + //Skip "triangles" with 3 colinear points + if(Cartesian3.magnitude(normal) === 0) + continue; + + var v0p = subtract(position, v0); + var normalDist = Math.abs(dot(v0p, normal)); + if(normalDist < bestTriDist) + { + bestTriDist = normalDist; + bestTri = triIndex; + + //Found a triangle, return the index of the closest point + var d0 = Cartesian3.magnitudeSquared(subtract(position, v0)); + var d1 = Cartesian3.magnitudeSquared(subtract(position, v1)); + var d2 = Cartesian3.magnitudeSquared(subtract(position, v2)); + if(d0 < d1 && d0 < d2) + { + bestIndex = i0; + bestPt = v0; + bestDistSq = d0; + } + else if (d1 maximumLatitude) { - latitude = maximumLatitude; - } else if (latitude < -maximumLatitude) { - latitude = -maximumLatitude; - } - var sinLatitude = Math.sin(latitude); - return 0.5 * Math.log((1.0 + sinLatitude) / (1.0 - sinLatitude)); + + function geodeticLatitudeToMercatorAngle (latitude) { + + var maximumLatitude = mercatorAngleToGeodeticLatitude(Math.PI); + + // Clamp the latitude coordinate to the valid Mercator bounds. + if (latitude > maximumLatitude) { + latitude = maximumLatitude; + } else if (latitude < -maximumLatitude) { + latitude = -maximumLatitude; + } + var sinLatitude = Math.sin(latitude); + return 0.5 * Math.log((1.0 + sinLatitude) / (1.0 - sinLatitude)); } - + function geographicToWebMercator(lon, lat, ellipsoid) { - var semimajorAxis = ellipsoid._maximumRadius; - - var x = lon * semimajorAxis; - var y = geodeticLatitudeToMercatorAngle(lat) * semimajorAxis; - - return { x: x, y: y }; + + var semimajorAxis = ellipsoid._maximumRadius; + + var x = lon * semimajorAxis; + var y = + geodeticLatitudeToMercatorAngle( + lat + ) * semimajorAxis; + + return {x:x, y:y}; } function bilinearInterpolate(tx, ty, h00, h10, h01, h11) { @@ -3158,26 +3205,26 @@ function _workerCode() { return data[address]; } - function sampleGeoid(sampleX, sampleY, geoidData) { + function sampleGeoid( + sampleX, + sampleY, + geoidData + ) { var extent = geoidData.nativeExtent; - var x = - ((sampleX - extent.west) / (extent.east - extent.west)) * - (geoidData.width - 1); - var y = - ((sampleY - extent.south) / (extent.north - extent.south)) * - (geoidData.height - 1); + var x = (sampleX - extent.west) / (extent.east - extent.west) * (geoidData.width-1); + var y = (sampleY - extent.south) / (extent.north - extent.south) * (geoidData.height-1); var xi = Math.floor(x); - var yi = Math.floor(y); + var yi = Math.floor(y); x -= xi; y -= yi; - - var xNext = xi < geoidData.width ? xi + 1 : xi; - var yNext = yi < geoidData.height ? yi + 1 : yi; - + + var xNext = xi < geoidData.width ? xi+1 : xi; + var yNext = yi < geoidData.height ? yi+1 : yi; + yi = geoidData.height - 1 - yi; yNext = geoidData.height - 1 - yNext; - + var h00 = sampleMap(xi, yi, geoidData.width, geoidData.buffer); var h10 = sampleMap(xNext, yi, geoidData.width, geoidData.buffer); var h01 = sampleMap(xi, yNext, geoidData.width, geoidData.buffer); @@ -3187,36 +3234,38 @@ function _workerCode() { finalHeight = finalHeight * geoidData.scale + geoidData.offset; return finalHeight; } - - function sampleGeoidFromList(lon, lat, geoidDataList) { - for (var i = 0; i < geoidDataList.length; i++) { - var localExtent = geoidDataList[i].nativeExtent; - var lonRadian = (lon / 180) * Math.PI; - var latRadian = (lat / 180) * Math.PI; - - var localPt = {}; - if (geoidDataList[i].projectionType === "WebMercator") { - localPt = geographicToWebMercator( - lonRadian, - latRadian, - geoidDataList[i].projection._ellipsoid - ); - } else { - localPt.x = lonRadian; - localPt.y = latRadian; - } - - if ( - localPt.x > localExtent.west && - localPt.x < localExtent.east && - localPt.y > localExtent.south && - localPt.y < localExtent.north - ) { - return sampleGeoid(localPt.x, localPt.y, geoidDataList[i]); + + + function sampleGeoidFromList( + lon, + lat, + geoidDataList + ) { + for(var i=0; i < geoidDataList.length; i++) + { + var localExtent = geoidDataList[i].nativeExtent; + var lonRadian = lon/180*Math.PI; + var latRadian = lat/180*Math.PI; + + var localPt = {}; + if(geoidDataList[i].projectionType === "WebMercator") + { + localPt = geographicToWebMercator(lonRadian, latRadian, geoidDataList[i].projection._ellipsoid); + } + else + { + localPt.x = lonRadian; + localPt.y = latRadian; + } + + if (localPt.x > localExtent.west && localPt.x < localExtent.east + && localPt.y > localExtent.south && localPt.y < localExtent.north) + { + return sampleGeoid(localPt.x, localPt.y, geoidDataList[i]); + } } - } - - return 0; + + return 0; } function orthometricToEllipsoidal( @@ -3234,7 +3283,7 @@ function _workerCode() { center.lat, geoidDataList ); - + if (fast) { for (var i = 0; i < vertexCount; ++i) { position[i * 3 + 2] += centerHeight; @@ -3259,7 +3308,7 @@ function _workerCode() { ....##....##...##...#########.##..####.......##.##.......##.....##.##...##...##.....## ....##....##....##..##.....##.##...###.##....##.##.......##.....##.##....##..##.....## ....##....##.....##.##.....##.##....##..######..##........#######..##.....##.##.....## - + .##........#######...######.....###....##...... .##.......##.....##.##....##...##.##...##...... .##.......##.....##.##........##...##..##...... @@ -3366,7 +3415,7 @@ function _workerCode() { .##....##..##.......##..####.##.......##...##...#########....##....##...... .##....##..##.......##...###.##.......##....##..##.....##....##....##...... ..######...########.##....##.########.##.....##.##.....##....##....######## - + .####.##....##.########.########.########..##....##....###....##...... ..##..###...##....##....##.......##.....##.###...##...##.##...##...... ..##..####..##....##....##.......##.....##.####..##..##...##..##...... @@ -3374,7 +3423,7 @@ function _workerCode() { ..##..##..####....##....##.......##...##...##..####.#########.##...... ..##..##...###....##....##.......##....##..##...###.##.....##.##...... .####.##....##....##....########.##.....##.##....##.##.....##.######## - + .########..##.....##.########.########.########.########. .##.....##.##.....##.##.......##.......##.......##.....## .##.....##.##.....##.##.......##.......##.......##.....## @@ -3430,11 +3479,7 @@ function _workerCode() { } } else { // generate indices - for ( - var newVertexIndex = 0; - newVertexIndex < vertexCount; - ++newVertexIndex - ) { + for (var newVertexIndex = 0; newVertexIndex < vertexCount; ++newVertexIndex) { indexArray[newVertexIndex] = newVertexIndex; } } @@ -3651,7 +3696,7 @@ function _workerCode() { .##....##..##.......##.....##.##.....##.##..........##....##...##......##... .##....##..##.......##.....##.##.....##.##..........##....##....##.....##... ..######...########..#######..##.....##.########....##....##.....##....##... - + .########..########..######...#######..########..########.########. .##.....##.##.......##....##.##.....##.##.....##.##.......##.....## .##.....##.##.......##.......##.....##.##.....##.##.......##.....## @@ -3683,7 +3728,7 @@ function _workerCode() { .##.....##.##...##...#########.##.......##.....## .##.....##.##....##..##.....##.##....##.##.....## .########..##.....##.##.....##..######...#######. - + .########..########..######...#######..########..########.########. .##.....##.##.......##....##.##.....##.##.....##.##.......##.....## .##.....##.##.......##.......##.....##.##.....##.##.......##.....## @@ -3746,10 +3791,7 @@ function _workerCode() { dracoDecoderModule.destroy(face); for (var attrIndex = 0; attrIndex < attributesCount; ++attrIndex) { - var dracoAttribute = dracoDecoder.GetAttribute( - dracoGeometry, - attrIndex - ); + var dracoAttribute = dracoDecoder.GetAttribute(dracoGeometry, attrIndex); var attributeData = decodeDracoAttribute( dracoDecoderModule, @@ -3774,10 +3816,7 @@ function _workerCode() { } // get the metadata - var metadata = dracoDecoder.GetAttributeMetadata( - dracoGeometry, - attrIndex - ); + var metadata = dracoDecoder.GetAttributeMetadata(dracoGeometry, attrIndex); if (metadata.ptr) { var numEntries = metadataQuerier.NumEntries(metadata); @@ -4008,7 +4047,7 @@ function _workerCode() { .##.....##..##..##..####.#########.##...##......##... .##.....##..##..##...###.##.....##.##....##.....##... .########..####.##....##.##.....##.##.....##....##... - + .########..########..######...#######..########..########.########. .##.....##.##.......##....##.##.....##.##.....##.##.......##.....## .##.....##.##.......##.......##.....##.##.....##.##.......##.....## @@ -4044,46 +4083,14 @@ function _workerCode() { return offset; }, featureId: function (decodedGeometry, data, offset) { + //We don't need to use this for anything so just increment the offset var count = decodedGeometry.featureCount; - var decodedGeometryBytes = new Uint8Array(data, offset, 8); - var id = decodedGeometryBytes[7]; - id <<= 8; - id = decodedGeometryBytes[6]; - id <<= 8; - id = decodedGeometryBytes[5]; - id <<= 8; - id = decodedGeometryBytes[4]; - id <<= 8; - id = decodedGeometryBytes[3]; - id <<= 8; - id = decodedGeometryBytes[2]; - id <<= 8; - id = decodedGeometryBytes[1]; - id <<= 8; - id = decodedGeometryBytes[0]; - decodedGeometry.featureId = id; offset += count * 8; return offset; }, id: function (decodedGeometry, data, offset) { + //We don't need to use this for anything so just increment the offset var count = decodedGeometry.featureCount; - var decodedGeometryBytes = new Uint8Array(data, offset, 8); - var id = decodedGeometryBytes[7]; - id <<= 8; - id = decodedGeometryBytes[6]; - id <<= 8; - id = decodedGeometryBytes[5]; - id <<= 8; - id = decodedGeometryBytes[4]; - id <<= 8; - id = decodedGeometryBytes[3]; - id <<= 8; - id = decodedGeometryBytes[2]; - id <<= 8; - id = decodedGeometryBytes[1]; - id <<= 8; - id = decodedGeometryBytes[0]; - decodedGeometry.featureId = id; offset += count * 8; return offset; }, @@ -4125,11 +4132,7 @@ function _workerCode() { offset += 4; if (bufferInfo) { - for ( - var attrIndex = 0; - attrIndex < bufferInfo.attributes.length; - attrIndex++ - ) { + for (var attrIndex=0; attrIndex < bufferInfo.attributes.length; attrIndex++) { if (binaryAttributeDecoders[bufferInfo.attributes[attrIndex]]) { offset = binaryAttributeDecoders[bufferInfo.attributes[attrIndex]]( decodedGeometry, @@ -4137,10 +4140,7 @@ function _workerCode() { offset ); } else { - console.error( - "Unknown decoder for", - bufferInfo.attributes[attrIndex] - ); + console.error("Unknown decoder for", bufferInfo.attributes[attrIndex]); } } } else { @@ -4162,7 +4162,7 @@ function _workerCode() { } // use default geometry schema - for (var i = 0; i < ordering.length; i++) { + for (var i=0; i < ordering.length; i++) { var decoder = binaryAttributeDecoders[ordering[i]]; if (!decoder) { console.log(ordering[i]); @@ -4170,7 +4170,7 @@ function _workerCode() { offset = decoder(decodedGeometry, data, offset); } - for (var j = 0; j < featureAttributeOrder.length; j++) { + for (var j=0; j < featureAttributeOrder.length; j++) { var curDecoder = binaryAttributeDecoders[featureAttributeOrder[j]]; if (!curDecoder) { console.log(featureAttributeOrder[j]); @@ -4256,12 +4256,32 @@ function _workerCode() { var customAttributes = {}; if (geometryData["feature-index"]) { customAttributes.positions = geometryData.positions; + customAttributes.indices = geometryData.indices; customAttributes["feature-index"] = geometryData["feature-index"]; + customAttributes.cartesianCenter = e.data.cartesianCenter; + customAttributes.parentRotation = e.data.parentRotation; } - - if (geometryData["featureId"]) { + else if (geometryData["faceRange"]) { customAttributes.positions = geometryData.positions; - customAttributes["featureId"] = geometryData["featureId"]; + customAttributes.indices = geometryData.indices; + customAttributes.sourceURL = e.data.url; + customAttributes.cartesianCenter = e.data.cartesianCenter; + customAttributes.parentRotation = e.data.parentRotation; + + //Build the feature index array from the faceRange. This should store the + customAttributes["feature-index"] = new Array(geometryData.positions.length); + for(var range=0; range< geometryData["faceRange"].length-1; range+=2) + { + var curIndex = range/2; + var rangeStart = geometryData["faceRange"][range]; + var rangeEnd = geometryData["faceRange"][range+1]; + for(var i=rangeStart; i <= rangeEnd; i++) + { + customAttributes["feature-index"][i*3] = curIndex; + customAttributes["feature-index"][i*3 + 1] = curIndex; + customAttributes["feature-index"][i*3 + 2] = curIndex; + } + } } meshData.customAttributes = customAttributes; @@ -4290,14 +4310,14 @@ function I3SGLTFProcessingQueue() { var that = this; this._queue = []; this._processing = false; - this._createWorkers(function () { + this._createWorkers(function() { that._process(); }); } I3SGLTFProcessingQueue.prototype._process = function () { var that = this; - for (var workerIndex = 0; workerIndex < this._workers.length; workerIndex++) { + for (var workerIndex=0; workerIndex < this._workers.length; workerIndex++) { if (this._workers[workerIndex].isReadyToWork) { if (this._queue.length > 0) { var task = this._queue.shift(); @@ -4306,7 +4326,7 @@ I3SGLTFProcessingQueue.prototype._process = function () { } } } - setTimeout(function () { + setTimeout(function() { that._process(); }, 100); }; @@ -4322,10 +4342,10 @@ I3SGLTFProcessingQueue.prototype._createWorkers = function (cb) { for (var index = 0; index < externalModules.length; ++index) { fetchPromises.push(when.defer()); externalModuleData.push(""); - - var fetchFunction = function (curIndex) { - fetch(externalModules[curIndex]).then(function (response) { - response.text().then(function (data) { + + var fetchFunction = function(curIndex) { + fetch(externalModules[curIndex]).then(function(response) { + response.text().then(function(data) { externalModuleData[curIndex] = data; fetchPromises[curIndex].resolve(); }); @@ -4333,14 +4353,16 @@ I3SGLTFProcessingQueue.prototype._createWorkers = function (cb) { }; fetchFunction(index); } + var externalModulesLoaded = when.all(fetchPromises); var that = this; - externalModulesLoaded.then(function () { + externalModulesLoaded.then(function() { + var externalModuleCode = ""; - for (var i = 0; i < externalModuleData.length; i++) + for(var i=0; i Date: Mon, 8 Nov 2021 20:37:22 -0800 Subject: [PATCH 07/57] -Removed default access tokens --- Apps/Sandcastle/gallery/I3S 3D Object Layer.html | 1 + Apps/Sandcastle/gallery/I3S IntegratedMesh Layer.html | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Apps/Sandcastle/gallery/I3S 3D Object Layer.html b/Apps/Sandcastle/gallery/I3S 3D Object Layer.html index 434b74fef0cb..2138f2d564dd 100644 --- a/Apps/Sandcastle/gallery/I3S 3D Object Layer.html +++ b/Apps/Sandcastle/gallery/I3S 3D Object Layer.html @@ -38,6 +38,7 @@

Loading...

function startup(Cesium) { "use strict"; //Sandcastle_Begin + Cesium.Ion.defaultAccessToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiI1N2U0ZGUzZS01NTgyLTQxYjYtYWM1ZS1mZDczY2UyNDE4ZWIiLCJpZCI6Mjg0NjcsInNjb3BlcyI6WyJhc3IiLCJnYyJdLCJpYXQiOjE1OTExMzUzNjB9.TVYF3wBTwODi-6HYXABxfYSp7Tfq3zJQvtdd-adBOMM'; var viewer = new Cesium.Viewer("cesiumContainer", { terrainProvider: new Cesium.createWorldTerrain({}), animation: false, diff --git a/Apps/Sandcastle/gallery/I3S IntegratedMesh Layer.html b/Apps/Sandcastle/gallery/I3S IntegratedMesh Layer.html index fb2a7f506a6b..599fe9215531 100644 --- a/Apps/Sandcastle/gallery/I3S IntegratedMesh Layer.html +++ b/Apps/Sandcastle/gallery/I3S IntegratedMesh Layer.html @@ -37,8 +37,7 @@

Loading...

diff --git a/Apps/Sandcastle/gallery/I3S Feature Picking.html b/Apps/Sandcastle/gallery/I3S Feature Picking.html index 0f8877301acf..6145452b13d0 100644 --- a/Apps/Sandcastle/gallery/I3S Feature Picking.html +++ b/Apps/Sandcastle/gallery/I3S Feature Picking.html @@ -35,95 +35,102 @@

Loading...

diff --git a/Apps/Sandcastle/gallery/I3S IntegratedMesh Layer.html b/Apps/Sandcastle/gallery/I3S IntegratedMesh Layer.html index 68f03a4f6455..0646446d4733 100644 --- a/Apps/Sandcastle/gallery/I3S IntegratedMesh Layer.html +++ b/Apps/Sandcastle/gallery/I3S IntegratedMesh Layer.html @@ -35,51 +35,42 @@

Loading...

From 4240326e5c42a3c55e8836f247a12e024bd83164 Mon Sep 17 00:00:00 2001 From: Tamrat Belayneh Date: Fri, 8 Jul 2022 12:31:06 -0700 Subject: [PATCH 12/57] Refactoring to satisfy Travis CI errors --- Source/Scene/I3SDataProvider.js | 1569 +------------------------------ Source/Scene/I3SFeature.js | 74 ++ Source/Scene/I3SNode.js | 1422 ++++++++++++++++++++++++++++ 3 files changed, 1541 insertions(+), 1524 deletions(-) create mode 100644 Source/Scene/I3SFeature.js create mode 100644 Source/Scene/I3SNode.js diff --git a/Source/Scene/I3SDataProvider.js b/Source/Scene/I3SDataProvider.js index ecac6c5f4d3f..c2b48f7b4633 100644 --- a/Source/Scene/I3SDataProvider.js +++ b/Source/Scene/I3SDataProvider.js @@ -118,15 +118,11 @@ import defined from "../Core/defined.js"; import DeveloperError from "../Core/DeveloperError.js"; import Ellipsoid from "../Core/Ellipsoid.js"; import Event from "../Core/Event.js"; -import HeadingPitchRoll from "../Core/HeadingPitchRoll.js"; import HeightmapEncoding from "../Core/HeightmapEncoding.js"; +import I3SNode from "../Scene/I3SNode.js"; import Lerc from "../ThirdParty/lerc.js"; -import Matrix3 from "../Core/Matrix3.js"; -import Matrix4 from "../Core/Matrix4.js"; -import Quaternion from "../Core/Quaternion.js"; import Resource from "../Core/Resource.js"; import TaskProcessor from "../Core/TaskProcessor.js"; -import Transforms from "../Core/Transforms.js"; import WebMercatorProjection from "../Core/WebMercatorProjection.js"; // Maps i3Snode by URI @@ -552,20 +548,18 @@ I3SDataProvider.prototype._binarizeGLTF = function (rawGLTF) { /** * @private */ -I3SDataProvider._getDecoderTaskProcessor = function () { - if (!defined(I3SDataProvider._decoderTaskProcessor)) { +I3SDataProvider.prototype._getDecoderTaskProcessor = function () { + if (!defined(this._decoderTaskProcessor)) { const processor = new TaskProcessor("decodeI3S"); - I3SDataProvider._taskProcessorReadyPromise = processor.initWebAssemblyModule( - { - modulePath: "ThirdParty/Workers/draco_decoder_nodejs.js", - wasmBinaryFile: "ThirdParty/draco_decoder.wasm", - } - ); + this._taskProcessorReadyPromise = processor.initWebAssemblyModule({ + modulePath: "ThirdParty/Workers/draco_decoder_nodejs.js", + wasmBinaryFile: "ThirdParty/draco_decoder.wasm", + }); - I3SDataProvider._decoderTaskProcessor = processor; + this._decoderTaskProcessor = processor; } - return I3SDataProvider._decoderTaskProcessor; + return this._decoderTaskProcessor; }; /** @@ -591,80 +585,6 @@ function longLatsToMeter(longitude1, latitude1, longitude2, latitude2) { return Cartesian3.distance(p1, p2); } -/** - * @private - */ -function computeExtent(minLongitude, minLatitude, maxLongitude, maxLatitude) { - const extent = { - minLongitude: minLongitude, - maxLongitude: maxLongitude, - minLatitude: minLatitude, - maxLatitude: maxLatitude, - }; - - // Compute the center - extent.centerLongitude = (extent.maxLongitude + extent.minLongitude) / 2; - extent.centerLatitude = (extent.maxLatitude + extent.minLatitude) / 2; - - // Compute the spans - extent.longitudeSpan = longLatsToMeter( - extent.minLongitude, - extent.minLatitude, - extent.maxLongitude, - extent.minLatitude - ); - - extent.latitudeSpan = longLatsToMeter( - extent.minLongitude, - extent.minLatitude, - extent.minLongitude, - extent.maxLatitude - ); - - return extent; -} - -function bilinearInterpolate(tx, ty, h00, h10, h01, h11) { - const a = h00 * (1 - tx) + h10 * tx; - const b = h01 * (1 - tx) + h11 * tx; - return a * (1 - ty) + b * ty; -} - -function sampleMap(u, v, width, data) { - const address = u + v * width; - return data[address]; -} - -function sampleGeoid(sampleX, sampleY, geoidData) { - const extent = geoidData.nativeExtent; - let x = - ((sampleX - extent.west) / (extent.east - extent.west)) * - (geoidData.width - 1); - let y = - ((sampleY - extent.south) / (extent.north - extent.south)) * - (geoidData.height - 1); - const xi = Math.floor(x); - let yi = Math.floor(y); - - x -= xi; - y -= yi; - - const xNext = xi < geoidData.width ? xi + 1 : xi; - let yNext = yi < geoidData.height ? yi + 1 : yi; - - yi = geoidData.height - 1 - yi; - yNext = geoidData.height - 1 - yNext; - - const h00 = sampleMap(xi, yi, geoidData.width, geoidData.buffer); - const h10 = sampleMap(xNext, yi, geoidData.width, geoidData.buffer); - const h01 = sampleMap(xi, yNext, geoidData.width, geoidData.buffer); - const h11 = sampleMap(xNext, yNext, geoidData.width, geoidData.buffer); - - let finalHeight = bilinearInterpolate(x, y, h00, h10, h01, h11); - finalHeight = finalHeight * geoidData.scale + geoidData.offset; - return finalHeight; -} - /** * This class implements an I3S scene server * @private @@ -787,6 +707,39 @@ I3SSceneServer.prototype.centerCamera = function (mode) { } }; +/** + * @private + */ +function computeExtent(minLongitude, minLatitude, maxLongitude, maxLatitude) { + const extent = { + minLongitude: minLongitude, + maxLongitude: maxLongitude, + minLatitude: minLatitude, + maxLatitude: maxLatitude, + }; + + // Compute the center + extent.centerLongitude = (extent.maxLongitude + extent.minLongitude) / 2; + extent.centerLatitude = (extent.maxLatitude + extent.minLatitude) / 2; + + // Compute the spans + extent.longitudeSpan = longLatsToMeter( + extent.minLongitude, + extent.minLatitude, + extent.maxLongitude, + extent.minLatitude + ); + + extent.latitudeSpan = longLatsToMeter( + extent.minLongitude, + extent.minLatitude, + extent.minLongitude, + extent.maxLatitude + ); + + return extent; +} + /** * @private */ @@ -1201,9 +1154,9 @@ I3SLayer.prototype._loadRootNode = function () { if (this._data.nodePages.rootIndex !== undefined) { rootIndex = this._data.nodePages.rootIndex; } - this._rootNode = new I3SNode(this, rootIndex); + this._rootNode = new I3SNode(this, rootIndex, true); } else { - this._rootNode = new I3SNode(this, this._data.store.rootNode); + this._rootNode = new I3SNode(this, this._data.store.rootNode, true); } return this._rootNode.load(true); @@ -1334,1440 +1287,8 @@ I3SLayer.prototype._create3DTileSet = function () { }); }); }; - -/** - * This class implements an I3S Node, in Cesium, each I3SNode - * creates a Cesium3DTile - * @private - * @alias I3SNode - * @constructor - * @param {I3SLayer|I3SNode} [parent] The parent of that node - * @param {String|Number} [ref] The uri or nodepage index to load the data from - */ -function I3SNode(parent, ref) { - this._parent = parent; - this._dataProvider = parent._dataProvider; - - if (this._parent instanceof I3SNode) { - this._level = this._parent._level + 1; - } else { - this._level = 0; - } - - // attach the current layer - if (this._parent instanceof I3SLayer) { - this._layer = this._parent; - } else { - this._layer = this._parent._layer; - } - - if (this._level === 0) { - if (typeof ref === "number") { - this._nodeIndex = ref; - } else { - this._uri = ref; - } - } else if (typeof ref === "number") { - this._nodeIndex = ref; - } else { - this._uri = ref; - } - - if (this._uri !== undefined) { - let query = ""; - if (this._dataProvider._query && this._dataProvider._query !== "") { - query = `?${this._dataProvider._query}`; - } - - this._completeUriWithoutQuery = `${this._parent._completeUriWithoutQuery}/${this._uri}`; - this._completeUri = this._completeUriWithoutQuery + query; - } - - this._entities = {}; - this._tile = null; - this._geometryData = []; - this._featureData = []; - this._fields = {}; - this._children = []; -} - -Object.defineProperties(I3SNode.prototype, { - /** - * Gets the uri for the node. - * @memberof I3SNode.prototype - * @type {string} - */ - uri: { - get: function () { - return this._uri; - }, - }, - /** - * Gets the complete uri for the node. - * @memberof I3SNode.prototype - * @type {string} - */ - completeUri: { - get: function () { - return this._completeUri; - }, - }, - /** - * Gets the parent layer - * @memberof I3SNode.prototype - * @type {Object} - */ - layer: { - get: function () { - return this._layer; - }, - }, - /** - * Gets the parent node - * @memberof I3SNode.prototype - * @type {Object} - */ - parent: { - get: function () { - return this._parent; - }, - }, - /** - * Gets the children nodes - * @memberof I3SNode.prototype - * @type {Array} - */ - children: { - get: function () { - return this._children; - }, - }, - /** - * Gets the collection of geometries - * @memberof I3SNode.prototype - * @type {Array} - */ - geometryData: { - get: function () { - return this._geometryData; - }, - }, - /** - * Gets the collection of features - * @memberof I3SNode.prototype - * @type {Array} - */ - featureData: { - get: function () { - return this._featureData; - }, - }, - /** - * Gets the collection of fields - * @memberof I3SNode.prototype - * @type {Array} - */ - fields: { - get: function () { - return this._fields; - }, - }, - /** - * Gets the Cesium3DTileSet for this layer. - * @memberof I3SNode.prototype - * @type {I3SNode} - */ - tile: { - get: function () { - return this._tile; - }, - }, - /** - * Gets the I3S data for this object. - * @memberof I3SNode.prototype - * @type {Object} - */ - data: { - get: function () { - return this._data; - }, - }, -}); - -/** - * Loads the node definition. - * @returns {Promise} a promise that is resolved when the I3S Node data is loaded - */ -I3SNode.prototype.load = function (isRoot) { - const that = this; - - function processData() { - if (!isRoot) { - // Create a new tile - const tileDefinition = that._create3DTileDefinition(); - - const tileBlob = new Blob([JSON.stringify(tileDefinition)], { - type: "application/json", - }); - - const inPlaceTileURL = URL.createObjectURL(tileBlob); - const resource = Resource.createIfNeeded(inPlaceTileURL); - - that._tile = new Cesium3DTile( - that._layer._tileset, - resource, - tileDefinition, - that._parent._tile - ); - - that._tile._i3sNode = that; - } - } - - // if we don't have a nodepage index load from json - if (this._nodeIndex === undefined) { - return this._dataProvider._loadJson( - this._completeUri, - function (data, resolve) { - // Success - that._data = data; - processData(); - resolve(); - }, - function (reject) { - // Fail - reject(); - } - ); - } - - return new Promise(function (resolve, reject) { - that._layer._getNodeInNodePages(that._nodeIndex).then(function (data) { - that._data = data; - - const pageSize = that._layer._data.nodePages.nodesPerPage; - const node = - that._layer._nodePages[Math.floor(that._nodeIndex / pageSize)][ - that._nodeIndex % pageSize - ]; - if (isRoot) { - that._uri = "nodes/root"; - } else if (node.mesh !== undefined) { - const uriIndex = - that._layer._nodePages[Math.floor(that._nodeIndex / pageSize)][ - that._nodeIndex % pageSize - ].mesh.geometry.resource; - that._uri = `../${uriIndex}`; - } - if (that._uri !== undefined) { - that._completeUriWithoutQuery = `${that._parent._completeUriWithoutQuery}/${that._uri}`; - let query = ""; - if (that._dataProvider._query && that._dataProvider._query !== "") { - query = `?${that._dataProvider._query}`; - } - that._completeUri = that._completeUriWithoutQuery + query; - } - - processData(); - resolve(); - }); - }); -}; - -/** - * Loads the node fields. - * @returns {Promise} a promise that is resolved when the I3S Node fields are loaded - */ -I3SNode.prototype.loadFields = function () { - // check if we must load fields - const fields = this._layer._data.attributeStorageInfo; - - const that = this; - function createAndLoadField(fields, index) { - const newField = new I3SField(that, fields[index]); - that._fields[newField._storageInfo.name] = newField; - return newField.load(); - } - - const promises = []; - if (fields) { - for (let i = 0; i < fields.length; i++) { - promises.push(createAndLoadField(fields, i)); - } - } - - return Promise.all(promises); -}; - -/** - * @private - */ -I3SNode.prototype._loadChildren = function (waitAllChildren) { - const that = this; - return new Promise(function (resolve, reject) { - if (!that._childrenAreLoaded) { - that._childrenAreLoaded = true; - const childPromises = []; - if (that._data.children) { - for ( - let childIndex = 0; - childIndex < that._data.children.length; - childIndex++ - ) { - const child = that._data.children[childIndex]; - const newChild = new I3SNode(that, child.href ? child.href : child); - that._children.push(newChild); - const childIsLoaded = newChild.load(); - if (waitAllChildren) { - childPromises.push(childIsLoaded); - } - childIsLoaded.then( - (function (theChild) { - return function () { - that._tile.children.push(theChild._tile); - }; - })(newChild) - ); - } - if (waitAllChildren) { - Promise.all(childPromises).then(function () { - resolve(); - }); - } else { - resolve(); - } - } else { - resolve(); - } - } else { - resolve(); - } - }); -}; - -/** - * @private - */ -I3SNode.prototype._loadGeometryData = function () { - const geometryPromises = []; - - // To debug decoding for a specific tile, add a condition - // that wraps this if/else to match the tile uri - if (this._data.geometryData) { - for ( - let geomIndex = 0; - geomIndex < this._data.geometryData.length; - geomIndex++ - ) { - const curGeometryData = new I3SGeometry( - this, - this._data.geometryData[geomIndex].href - ); - this._geometryData.push(curGeometryData); - geometryPromises.push(curGeometryData.load()); - } - } else if (this._data.mesh) { - const geometryDefinition = this._layer._findBestGeometryBuffers( - this._data.mesh.geometry.definition, - ["position", "uv0"] - ); - - const geometryURI = `./geometries/${geometryDefinition.bufferIndex}`; - const newGeometryData = new I3SGeometry(this, geometryURI); - newGeometryData._geometryDefinitions = geometryDefinition.definition; - newGeometryData._geometryBufferInfo = geometryDefinition.geometryBufferInfo; - this._geometryData.push(newGeometryData); - geometryPromises.push(newGeometryData.load()); - } - - return Promise.all(geometryPromises); -}; - -/** - * @private - */ -I3SNode.prototype._loadFeatureData = function () { - const featurePromises = []; - - // To debug decoding for a specific tile, add a condition - // that wraps this if/else to match the tile uri - if (this._data.featureData) { - for ( - let featureIndex = 0; - featureIndex < this._data.featureData.length; - featureIndex++ - ) { - const newfeatureData = new I3SFeature( - this, - this._data.featureData[featureIndex].href - ); - this._featureData.push(newfeatureData); - featurePromises.push(newfeatureData.load()); - } - } - - return Promise.all(featurePromises); -}; - -/** - * @private - */ -I3SNode.prototype._clearGeometryData = function () { - this._geometryData = []; -}; - -/** - * @private - */ -I3SNode.prototype._create3DTileDefinition = function () { - const obb = this._data.obb; - const mbs = this._data.mbs; - - let boundingVolume = {}; - let geoPosition; - let position; - - if (obb) { - geoPosition = Cartographic.fromDegrees( - obb.center[0], - obb.center[1], - obb.center[2] - ); - } else if (mbs) { - geoPosition = Cartographic.fromDegrees(mbs[0], mbs[1], mbs[2]); - } - - //Offset bounding box position if we have a geoid service defined - if ( - defined(this._dataProvider._geoidTiledTerrainProvider) && - defined(geoPosition) - ) { - for (let i = 0; i < this._dataProvider._geoidDataList.length; i++) { - const tile = this._dataProvider._geoidDataList[i]; - const projectedPos = tile.projection.project(geoPosition); - if ( - projectedPos.x > tile.nativeExtent.west && - projectedPos.x < tile.nativeExtent.east && - projectedPos.y > tile.nativeExtent.south && - projectedPos.y < tile.nativeExtent.north - ) { - geoPosition.height += sampleGeoid(projectedPos.x, projectedPos.y, tile); - break; - } - } - } - - if (obb) { - boundingVolume = { - box: [ - 0, - 0, - 0, - obb.halfSize[0], - 0, - 0, - 0, - obb.halfSize[1], - 0, - 0, - 0, - obb.halfSize[2], - ], - }; - position = Ellipsoid.WGS84.cartographicToCartesian(geoPosition); - } else if (mbs) { - boundingVolume = { - sphere: [0, 0, 0, mbs[3]], - }; - position = Ellipsoid.WGS84.cartographicToCartesian(geoPosition); - } else { - console.error(this); - } - - // compute the geometric error - let metersPerPixel = Infinity; - - let span = 0; - if (this._data.mbs) { - span = this._data.mbs[3]; - } else if (this._data.obb) { - span = Math.max( - Math.max(this._data.obb.halfSize[0], this._data.obb.halfSize[1]), - this._data.obb.halfSize[2] - ); - } - - // get the meters/pixel density required to pop the next LOD - if (this._data.lodThreshold !== undefined) { - if ( - this._layer._data.nodePages.lodSelectionMetricType === - "maxScreenThresholdSQ" - ) { - const maxScreenThreshold = - Math.sqrt(this._data.lodThreshold) / (Math.PI * 0.25); - metersPerPixel = span / maxScreenThreshold; - } else if ( - this._layer._data.nodePages.lodSelectionMetricType === - "maxScreenThreshold" - ) { - const maxScreenThreshold = this._data.lodThreshold; - metersPerPixel = span / maxScreenThreshold; - } else { - //Other LOD selection types can only be used for point cloud data - console.error("Invalid lodSelectionMetricType in Layer"); - } - } else if (this._data.lodSelection !== undefined) { - for ( - let lodIndex = 0; - lodIndex < this._data.lodSelection.length; - lodIndex++ - ) { - if ( - this._data.lodSelection[lodIndex].metricType === "maxScreenThreshold" - ) { - metersPerPixel = span / this._data.lodSelection[lodIndex].maxError; - } - } - } - - if (metersPerPixel === Infinity) { - metersPerPixel = 100000; - } - - // calculate the length of 16 pixels in order to trigger the screen space error - const geometricError = metersPerPixel * 16; - - // transformations - const hpr = new HeadingPitchRoll(0, 0, 0); - let orientation = Transforms.headingPitchRollQuaternion(position, hpr); - - if (this._data.obb) { - orientation = new Quaternion( - this._data.obb.quaternion[0], - this._data.obb.quaternion[1], - this._data.obb.quaternion[2], - this._data.obb.quaternion[3] - ); - } - - this._rotationMatrix = Matrix3.fromQuaternion(orientation); - this._inverseRotationMatrix = new Matrix3(); - Matrix3.inverse(this._rotationMatrix, this._inverseRotationMatrix); - - this._globalTransforms = new Matrix4( - this._rotationMatrix[0], - this._rotationMatrix[1], - this._rotationMatrix[2], - 0, - this._rotationMatrix[3], - this._rotationMatrix[4], - this._rotationMatrix[5], - 0, - this._rotationMatrix[6], - this._rotationMatrix[7], - this._rotationMatrix[8], - 0, - position.x, - position.y, - position.z, - 1 - ); - - this.inverseGlobalTransform = new Matrix4(); - Matrix4.inverse(this._globalTransforms, this.inverseGlobalTransform); - - const localTransforms = this._globalTransforms.clone(); - - if (this._parent._globalTransforms) { - Matrix4.multiply( - this._globalTransforms, - this._parent.inverseGlobalTransform, - localTransforms - ); - } - - // get children definition - const childrenDefinition = []; - for (let childIndex = 0; childIndex < this._children.length; childIndex++) { - childrenDefinition.push( - this._children[childIndex]._create3DTileDefinition() - ); - } - - // Create a tile set - const inPlaceTileDefinition = { - children: childrenDefinition, - refine: "REPLACE", - boundingVolume: boundingVolume, - transform: [ - localTransforms[0], - localTransforms[4], - localTransforms[8], - localTransforms[12], - localTransforms[1], - localTransforms[5], - localTransforms[9], - localTransforms[13], - localTransforms[2], - localTransforms[6], - localTransforms[10], - localTransforms[14], - localTransforms[3], - localTransforms[7], - localTransforms[11], - localTransforms[15], - ], - content: { - uri: this._completeUriWithoutQuery, - }, - geometricError: geometricError, - }; - - return inPlaceTileDefinition; -}; - -/** - * @private - */ -I3SNode.prototype._scheduleCreateContentURL = function () { - const that = this; - return new Promise(function (resolve, reject) { - that._createContentURL(resolve, that._tile); - }); -}; - -function createI3SDecoderTask(data) { - // Prepare the data to send to the worker - const parentData = data.geometryData._parent._data; - const parentRotationInverseMatrix = - data.geometryData._parent._inverseRotationMatrix; - - const center = { - long: 0, - lat: 0, - alt: 0, - }; - - if (parentData.obb) { - center.long = parentData.obb.center[0]; - center.lat = parentData.obb.center[1]; - center.alt = parentData.obb.center[2]; - } else if (parentData.mbs) { - center.long = parentData.mbs[0]; - center.lat = parentData.mbs[1]; - center.alt = parentData.mbs[2]; - } - - const axisFlipRotation = Matrix3.fromRotationX(-Math.PI / 2); - const parentRotation = new Matrix3(); - - Matrix3.multiply( - axisFlipRotation, - parentRotationInverseMatrix, - parentRotation - ); - - const payload = { - binaryData: data.geometryData._data, - featureData: - data.featureData && data.featureData[0] ? data.featureData[0].data : null, - schema: data.defaultGeometrySchema, - bufferInfo: data.geometryData._geometryBufferInfo, - ellipsoidRadiiSquare: Ellipsoid.WGS84._radiiSquared, - url: data.url, - geoidDataList: data.geometryData._dataProvider._geoidDataList, - cartographicCenter: center, - cartesianCenter: wgs84ToCartesian(center.long, center.lat, center.alt), - parentRotation: parentRotation, - }; - - const decodeI3STaskProcessor = I3SDataProvider._getDecoderTaskProcessor(); - - const transferrableObjects = []; - return I3SDataProvider._taskProcessorReadyPromise.then(function () { - return decodeI3STaskProcessor.scheduleTask(payload, transferrableObjects); - }); -} - -/** - * @private - */ -I3SNode.prototype._createContentURL = function (resolve, tile) { - let rawGLTF = { - scene: 0, - scenes: [ - { - nodes: [0], - }, - ], - nodes: [ - { - name: "singleNode", - }, - ], - meshes: [], - buffers: [], - bufferViews: [], - accessors: [], - materials: [], - textures: [], - images: [], - samplers: [], - asset: { - version: "2.0", - }, - }; - - // Load the geometry data - const dataPromises = [this._loadFeatureData(), this._loadGeometryData()]; - - const that = this; - Promise.all(dataPromises).then(function () { - // Binary GLTF - const generateGLTF = new Promise(function (resolve, reject) { - if (that._geometryData && that._geometryData.length > 0) { - const parameters = { - geometryData: that._geometryData[0], - featureData: that._featureData, - defaultGeometrySchema: that._layer._data.store.defaultGeometrySchema, - url: that._geometryData[0]._completeUri, - tile: that._tile, - }; - - const task = createI3SDecoderTask(parameters); - if (!defined(task)) { - // Postponed - resolve(); - return; - } - - task.then(function (result) { - rawGLTF = parameters.geometryData._generateGLTF( - result.meshData.nodesInScene, - result.meshData.nodes, - result.meshData.meshes, - result.meshData.buffers, - result.meshData.bufferViews, - result.meshData.accessors - ); - - that._geometryData[0].customAttributes = - result.meshData.customAttributes; - resolve(); - }); - } else { - resolve(); - } - }); - - generateGLTF.then(function () { - const binaryGLTFData = that._dataProvider._binarizeGLTF(rawGLTF); - const glbDataBlob = new Blob([binaryGLTFData], { - type: "application/binary", - }); - that._glbURL = URL.createObjectURL(glbDataBlob); - resolve(); - }); - }); -}; - -/** - * This class implements an I3S Feature - * @public - * @alias I3SFeature - * @constructor - * @param {I3SNode} [parent] The parent of that feature - * @param {String} [uri] The uri to load the data from - */ -function I3SFeature(parent, uri) { - this._parent = parent; - this._dataProvider = parent._dataProvider; - this._layer = parent._layer; - this._uri = uri; - let query = ""; - if (this._dataProvider._query && this._dataProvider._query !== "") { - query = `?${this._dataProvider._query}`; - } - - this._completeUriWithoutQuery = `${this._parent._completeUriWithoutQuery}/${this._uri}`; - this._completeUri = this._completeUriWithoutQuery + query; -} - -Object.defineProperties(I3SFeature.prototype, { - /** - * Gets the uri for the feature. - * @memberof I3SFeature.prototype - * @type {string} - */ - uri: { - get: function () { - return this._uri; - }, - }, - /** - * Gets the complete uri for the feature. - * @memberof I3SFeature.prototype - * @type {string} - */ - completeUri: { - get: function () { - return this._completeUri; - }, - }, - /** - * Gets the I3S data for this object. - * @memberof I3SFeature.prototype - * @type {Object} - */ - data: { - get: function () { - return this._data; - }, - }, -}); - -/** - * Loads the content. - * @returns {Promise} a promise that is resolved when the data of the I3S feature is loaded - */ -I3SFeature.prototype.load = function () { - const that = this; - return this._dataProvider._loadJson( - this._completeUri, - function (data, resolve) { - that._data = data; - resolve(); - }, - function (reject) { - reject(); - } - ); -}; - -/** - * This class implements an I3S Field which is custom data attachec - * to nodes - * @private - * @alias I3SField - * @constructor - * @param {I3SNode} [parent] The parent of that geometry - * @param {Object} [storageInfo] The structure containing the storage info of the field - */ -function I3SField(parent, storageInfo) { - this._storageInfo = storageInfo; - this._parent = parent; - this._dataProvider = parent._dataProvider; - this._uri = `/attributes/${storageInfo.key}/0`; - let query = ""; - if (this._dataProvider._query && this._dataProvider._query !== "") { - query = `?${this._dataProvider._query}`; - } - - this._completeUriWithoutQuery = - this._parent._completeUriWithoutQuery + this._uri; - this._completeUri = this._completeUriWithoutQuery + query; -} - -Object.defineProperties(I3SField.prototype, { - /** - * Gets the uri for the field. - * @memberof I3SField.prototype - * @type {string} - */ - uri: { - get: function () { - return this._uri; - }, - }, - /** - * Gets the complete uri for the field. - * @memberof I3SField.prototype - * @type {string} - */ - completeUri: { - get: function () { - return this._completeUri; - }, - }, - /** - * Gets the header for this field. - * @memberof I3SField.prototype - * @type {Object} - */ - header: { - get: function () { - return this._header; - }, - }, - /** - * Gets the values for this field. - * @memberof I3SField.prototype - * @type {Object} - */ - values: { - get: function () { - return this._values && this._values.attributeValues - ? this._values.attributeValues - : []; - }, - }, - /** - * Gets the name for the field. - * @memberof I3SField.prototype - * @type {string} - */ - name: { - get: function () { - return this._storageInfo.name; - }, - }, -}); - -/** - * Loads the content. - * @returns {Promise} a promise that is resolved when the geometry data is loaded - */ -I3SField.prototype.load = function () { - const that = this; - return this._dataProvider._loadBinary( - this._completeUri, - function (data, resolve) { - // check if we have a 404 - const dataView = new DataView(data); - let success = true; - if (dataView.getUint8(0) === "{".charCodeAt(0)) { - const textContent = new TextDecoder(); - const str = textContent.decode(data); - if (str.includes("404")) { - success = false; - console.error("Failed to load:", that._completeUri); - } - } - - if (success) { - that._data = data; - let offset = that._parseHeader(dataView); - - // @TODO: find out why we must skip 4 bytes when the value type is float 64 - if ( - that._storageInfo && - that._storageInfo.attributeValues && - that._storageInfo.attributeValues.valueType === "Float64" - ) { - offset += 4; - } - - that._parseBody(dataView, offset); - } - - resolve(); - }, - function (reject) { - reject(); - } - ); -}; - -/** - * @private - */ -I3SField.prototype._parseValue = function (dataView, type, offset) { - let value = null; - if (type === "UInt8") { - value = dataView.getUint8(offset); - offset += 1; - } else if (type === "Int8") { - value = dataView.getInt8(offset); - offset += 1; - } else if (type === "UInt16") { - value = dataView.getUint16(offset, true); - offset += 2; - } else if (type === "Int16") { - value = dataView.getInt16(offset, true); - offset += 2; - } else if (type === "UInt32") { - value = dataView.getUint32(offset, true); - offset += 4; - } else if (type === "Oid32") { - value = dataView.getUint32(offset, true); - offset += 4; - } else if (type === "Int32") { - value = dataView.getInt32(offset, true); - offset += 4; - } else if (type === "Float32") { - value = dataView.getFloat32(offset, true); - offset += 4; - } else if (type === "UInt64") { - value = dataView.getUint64(offset, true); - offset += 8; - } else if (type === "Int64") { - value = dataView.getInt64(offset, true); - offset += 8; - } else if (type === "Float64") { - value = dataView.getFloat64(offset, true); - offset += 8; - } else if (type === "String") { - value = String.fromCharCode(dataView.getUint8(offset)); - offset += 1; - } - - return { - value: value, - offset: offset, - }; -}; - -/** - * @private - */ -I3SField.prototype._parseHeader = function (dataView) { - let offset = 0; - this._header = {}; - for ( - let itemIndex = 0; - itemIndex < this._storageInfo.header.length; - itemIndex++ - ) { - const item = this._storageInfo.header[itemIndex]; - const parsedValue = this._parseValue(dataView, item.valueType, offset); - this._header[item.property] = parsedValue.value; - offset = parsedValue.offset; - } - return offset; -}; - -/** - * @private - */ -I3SField.prototype._parseBody = function (dataView, offset) { - this._values = {}; - for ( - let itemIndex = 0; - itemIndex < this._storageInfo.ordering.length; - itemIndex++ - ) { - const item = this._storageInfo.ordering[itemIndex]; - const desc = this._storageInfo[item]; - if (desc) { - this._values[item] = []; - for (let index = 0; index < this._header.count; ++index) { - if (desc.valueType !== "String") { - const parsedValue = this._parseValue( - dataView, - desc.valueType, - offset - ); - this._values[item].push(parsedValue.value); - offset = parsedValue.offset; - } else { - const stringLen = this._values.attributeByteCounts[index]; - let stringContent = ""; - for (let cIndex = 0; cIndex < stringLen; ++cIndex) { - const curParsedValue = this._parseValue( - dataView, - desc.valueType, - offset - ); - stringContent += curParsedValue.value; - offset = curParsedValue.offset; - } - this._values[item].push(stringContent); - } - } - } - } -}; - -/** - * This class implements an I3S Geometry, in Cesium, each I3SGeometry - * generates an in memory b3dm so be used as content for a Cesium3DTile - * @private - * @alias I3SGeometry - * @constructor - * @param {I3SNode} [parent] The parent of that geometry - * @param {String} [uri] The uri to load the data from - */ -function I3SGeometry(parent, uri) { - this._parent = parent; - this._dataProvider = parent._dataProvider; - this._layer = parent._layer; - this._uri = uri; - let query = ""; - if (this._dataProvider._query && this._dataProvider._query !== "") { - query = `?${this._dataProvider._query}`; - } - - if (this._parent._nodeIndex) { - this._completeUriWithoutQuery = `${this._layer._completeUriWithoutQuery}/nodes/${this._parent._data.mesh.geometry.resource}/${this._uri}`; - this._completeUri = this._completeUriWithoutQuery + query; - } else { - this._completeUriWithoutQuery = `${this._parent._completeUriWithoutQuery}/${this._uri}`; - this._completeUri = this._completeUriWithoutQuery + query; - } -} - -Object.defineProperties(I3SGeometry.prototype, { - /** - * Gets the uri for the geometry. - * @memberof I3SGeometry.prototype - * @type {string} - */ - uri: { - get: function () { - return this._uri; - }, - }, - /** - * Gets the complete uri for the geometry. - * @memberof I3SGeometry.prototype - * @type {string} - */ - completeUri: { - get: function () { - return this._completeUri; - }, - }, - /** - * Gets the I3S data for this object. - * @memberof I3SGeometry.prototype - * @type {Object} - */ - data: { - get: function () { - return this._data; - }, - }, -}); - -/** - * Loads the content. - * @returns {Promise} a promise that is resolved when the geometry data is loaded - */ -I3SGeometry.prototype.load = function () { - const that = this; - return this._dataProvider._loadBinary( - this._completeUri, - function (data, resolve) { - that._data = data; - resolve(); - }, - function (reject) { - reject(); - } - ); -}; - -function sameSide(p1, p2, a, b) { - const ab = {}; - const ap1 = {}; - const ap2 = {}; - const cp1 = {}; - const cp2 = {}; - Cartesian3.subtract(b, a, ab); - Cartesian3.cross(ab, Cartesian3.subtract(p1, a, ap1), cp1); - Cartesian3.cross(ab, Cartesian3.subtract(p2, a, ap2), cp2); - return Cartesian3.dot(cp1, cp2) >= 0; -} - -/** - * Find a triangle touching the point px,py,pz, then return the vertex closest to the search point - * @param {number} [px] the x component of the point to query - * @param {number} [py] the y component of the point to query - * @param {number} [pz] the z component of the point to query - * @returns {object} a structure containing the index of the closest point, - * the squared distance from the queried point to the point that is found - * the distance from the queried point to the point that is found - * the queried position in local space - * the closest position in local space - */ -I3SGeometry.prototype.getClosestPointIndexOnTriangle = function (px, py, pz) { - if (this.customAttributes && this.customAttributes.positions) { - // convert queried position to local - const position = new Cartesian3(px, py, pz); - - position.x -= this.customAttributes.cartesianCenter.x; - position.y -= this.customAttributes.cartesianCenter.y; - position.z -= this.customAttributes.cartesianCenter.z; - Matrix3.multiplyByVector( - this.customAttributes.parentRotation, - position, - position - ); - - let bestTriDist = Number.MAX_VALUE; - let bestTri; - let bestDistSq; - let bestIndex; - let bestPt; - - // Brute force lookup, @TODO: this can be improved with a spatial partitioning search system - const positions = this.customAttributes.positions; - const indices = this.customAttributes.indices; - - //We may have indexed or non-indexed triangles here - let triCount; - if (indices) { - triCount = indices.length; - } else { - triCount = positions.length / 3; - } - - for (let triIndex = 0; triIndex < triCount; triIndex++) { - let i0, i1, i2; - if (indices) { - i0 = indices[triIndex]; - i1 = indices[triIndex + 1]; - i2 = indices[triIndex + 2]; - } else { - i0 = triIndex * 3; - i1 = triIndex * 3 + 3; - i2 = triIndex * 3 + 6; - } - - const v0 = new Cartesian3( - positions[i0 * 3], - positions[i0 * 3 + 1], - positions[i0 * 3 + 2] - ); - const v1 = new Cartesian3( - positions[i1 * 3], - positions[i1 * 3 + 1], - positions[i1 * 3 + 2] - ); - const v2 = new Cartesian3( - positions[i2 * 3], - positions[i2 * 3 + 1], - positions[i2 * 3 + 2] - ); - - //Check how the point is positioned relative to the triangle. - //This will tell us whether the projection of the point in the triangle's plane lands in the triangle - if ( - !sameSide(position, v0, v1, v2) || - !sameSide(position, v1, v0, v2) || - !sameSide(position, v2, v0, v1) - ) - continue; - - //Because of precision issues, we can't always reliably tell if the point lands directly on the face, so the most robust way is just to find the closest one - const v0v1 = {}, - v0v2 = {}, - crossProd = {}, - normal = {}; - Cartesian3.subtract(v1, v0, v0v1); - Cartesian3.subtract(v2, v0, v0v2); - Cartesian3.cross(v0v1, v0v2, crossProd); - - //Skip "triangles" with 3 colinear points - if (Cartesian3.magnitude(crossProd) === 0) continue; - - Cartesian3.normalize(crossProd, normal); - - const v0p = {}, - v1p = {}, - v2p = {}; - Cartesian3.subtract(position, v0, v0p); - const normalDist = Math.abs(Cartesian3.dot(v0p, normal)); - if (normalDist < bestTriDist) { - bestTriDist = normalDist; - bestTri = triIndex; - - //Found a triangle, return the index of the closest point - const d0 = Cartesian3.magnitudeSquared( - Cartesian3.subtract(position, v0, v0p) - ); - const d1 = Cartesian3.magnitudeSquared( - Cartesian3.subtract(position, v1, v1p) - ); - const d2 = Cartesian3.magnitudeSquared( - Cartesian3.subtract(position, v2, v2p) - ); - if (d0 < d1 && d0 < d2) { - bestIndex = i0; - bestPt = v0; - bestDistSq = d0; - } else if (d1 < d2) { - bestIndex = i1; - bestPt = v1; - bestDistSq = d1; - } else { - bestIndex = i2; - bestPt = v2; - bestDistSq = d2; - } - } - } - - if (bestTri !== undefined) { - return { - index: bestIndex, - distanceSquared: bestDistSq, - distance: Math.sqrt(bestDistSq), - queriedPosition: { - x: position.x, - y: position.y, - z: position.z, - }, - closestPosition: { - x: bestPt.x, - y: bestPt.y, - z: bestPt.z, - }, - }; - } - } - - //No hits found - return { - index: -1, - distanceSquared: Number.Infinity, - distance: Number.Infinity, - }; -}; - -/** - * @private - */ -I3SGeometry.prototype._generateGLTF = function ( - nodesInScene, - nodes, - meshes, - buffers, - bufferViews, - accessors -) { - let query = ""; - if (this._dataProvider._query && this._dataProvider._query !== "") { - query = `?${this._dataProvider._query}`; - } - - // Get the material definition - const materialInfo = this._parent._data.mesh - ? this._parent._data.mesh.material - : null; - let materialIndex = 0; - let isTextured = false; - let gltfMaterial = { - pbrMetallicRoughness: { - metallicFactor: 0.0, - }, - doubleSided: true, - name: "Material", - }; - - if (materialInfo) { - materialIndex = materialInfo.definition; - } - - let materialDefinition; - if (this._layer._data.materialDefinitions) { - materialDefinition = this._layer._data.materialDefinitions[materialIndex]; - } - - if (materialDefinition) { - gltfMaterial = materialDefinition; - - // Textured. @TODO: extend to other textured cases - if ( - materialDefinition.pbrMetallicRoughness && - materialDefinition.pbrMetallicRoughness.baseColorTexture - ) { - isTextured = true; - } - } - - let texturePath; - - if (this._parent._data.textureData) { - texturePath = `${this._parent._completeUriWithoutQuery}/${this._parent._data.textureData[0].href}${query}`; - } else { - // Choose the JPG for the texture - let textureName = "0"; - - if (this._layer._data.textureSetDefinitions) { - for ( - let defIndex = 0; - defIndex < this._layer._data.textureSetDefinitions.length; - defIndex++ - ) { - const textureSetDefinition = this._layer._data.textureSetDefinitions[ - defIndex - ]; - for ( - let formatIndex = 0; - formatIndex < textureSetDefinition.formats.length; - formatIndex++ - ) { - const textureFormat = textureSetDefinition.formats[formatIndex]; - if (textureFormat.format === "jpg") { - textureName = textureFormat.name; - break; - } - } - } - } - - if (this._parent._data.mesh) { - texturePath = `${this._layer._completeUriWithoutQuery}/nodes/${this._parent._data.mesh.material.resource}/textures/${textureName}${query}`; - } - } - - let gltfTextures = []; - let gltfImages = []; - let gltfSamplers = []; - - if (isTextured) { - gltfTextures = [ - { - sampler: 0, - source: 0, - }, - ]; - - gltfImages = [ - { - uri: texturePath, - }, - ]; - - gltfSamplers = [ - { - magFilter: 9729, - minFilter: 9986, - wrapS: 10497, - wrapT: 10497, - }, - ]; - - gltfMaterial.pbrMetallicRoughness.baseColorTexture.index = 0; - } - - const gltfData = { - scene: 0, - scenes: [ - { - nodes: nodesInScene, - }, - ], - nodes: nodes, - meshes: meshes, - buffers: buffers, - bufferViews: bufferViews, - accessors: accessors, - materials: [gltfMaterial], - textures: gltfTextures, - images: gltfImages, - samplers: gltfSamplers, - asset: { - version: "2.0", - }, - }; - - return gltfData; -}; - // Reimplement Cesium3DTile.prototype.requestContent so that -// We get a chance to load our own b3dm from I3S data +// We get a chance to load our own gltf from I3S data Cesium3DTile.prototype._hookedRequestContent = Cesium3DTile.prototype.requestContent; diff --git a/Source/Scene/I3SFeature.js b/Source/Scene/I3SFeature.js new file mode 100644 index 000000000000..8ea02e55fc30 --- /dev/null +++ b/Source/Scene/I3SFeature.js @@ -0,0 +1,74 @@ +/** + * This class implements an I3S Feature + *

+ * Do not construct this directly, instead access tiles through {@link I3SDataProvider}. + *

+ * @alias I3SFeature + * @constructor + */ +function I3SFeature(parent, uri) { + this._parent = parent; + this._dataProvider = parent._dataProvider; + this._layer = parent._layer; + this._uri = uri; + let query = ""; + if (this._dataProvider._query && this._dataProvider._query !== "") { + query = `?${this._dataProvider._query}`; + } + + this._completeUriWithoutQuery = `${this._parent._completeUriWithoutQuery}/${this._uri}`; + this._completeUri = this._completeUriWithoutQuery + query; +} + +Object.defineProperties(I3SFeature.prototype, { + /** + * Gets the uri for the feature. + * @memberof I3SFeature.prototype + * @type {string} + */ + uri: { + get: function () { + return this._uri; + }, + }, + /** + * Gets the complete uri for the feature. + * @memberof I3SFeature.prototype + * @type {string} + */ + completeUri: { + get: function () { + return this._completeUri; + }, + }, + /** + * Gets the I3S data for this object. + * @memberof I3SFeature.prototype + * @type {Object} + */ + data: { + get: function () { + return this._data; + }, + }, +}); + +/** + * Loads the content. + * @returns {Promise} a promise that is resolved when the data of the I3S feature is loaded + */ +I3SFeature.prototype.load = function () { + const that = this; + return this._dataProvider._loadJson( + this._completeUri, + function (data, resolve) { + that._data = data; + resolve(); + }, + function (reject) { + reject(); + } + ); +}; + +export default I3SFeature; diff --git a/Source/Scene/I3SNode.js b/Source/Scene/I3SNode.js new file mode 100644 index 000000000000..7d10d785e3e9 --- /dev/null +++ b/Source/Scene/I3SNode.js @@ -0,0 +1,1422 @@ +import Cartesian3 from "../Core/Cartesian3.js"; +import Cartographic from "../Core/Cartographic.js"; +import Cesium3DTile from "../Scene/Cesium3DTile.js"; +import CesiumMath from "../Core/Math.js"; +import defined from "../Core/defined.js"; +import Ellipsoid from "../Core/Ellipsoid.js"; +import HeadingPitchRoll from "../Core/HeadingPitchRoll.js"; +import I3SFeature from "../Scene/I3SFeature.js"; +import Matrix3 from "../Core/Matrix3.js"; +import Matrix4 from "../Core/Matrix4.js"; +import Resource from "../Core/Resource.js"; +import Transforms from "../Core/Transforms.js"; +import Quaternion from "../Core/Quaternion.js"; + +/** + * This class implements an I3S Node, in Cesium, each I3SNode + * creates a Cesium3DTile + *

+ * Do not construct this directly, instead access tiles through {@link I3SDataProvider}. + *

+ * @alias I3SNode + * @constructor + */ +function I3SNode(parent, ref, isRoot) { + this._parent = parent; + this._dataProvider = parent._dataProvider; + + if (isRoot) { + this._level = 0; + this._layer = this._parent; + } else { + this._level = this._parent._level + 1; + this._layer = this._parent._layer; + } + + if (this._level === 0) { + if (typeof ref === "number") { + this._nodeIndex = ref; + } else { + this._uri = ref; + } + } else if (typeof ref === "number") { + this._nodeIndex = ref; + } else { + this._uri = ref; + } + + if (this._uri !== undefined) { + let query = ""; + if (this._dataProvider._query && this._dataProvider._query !== "") { + query = `?${this._dataProvider._query}`; + } + + this._completeUriWithoutQuery = `${this._parent._completeUriWithoutQuery}/${this._uri}`; + this._completeUri = this._completeUriWithoutQuery + query; + } + + this._entities = {}; + this._tile = null; + this._geometryData = []; + this._featureData = []; + this._fields = {}; + this._children = []; +} + +Object.defineProperties(I3SNode.prototype, { + /** + * Gets the uri for the node. + * @memberof I3SNode.prototype + * @type {string} + */ + uri: { + get: function () { + return this._uri; + }, + }, + /** + * Gets the complete uri for the node. + * @memberof I3SNode.prototype + * @type {string} + */ + completeUri: { + get: function () { + return this._completeUri; + }, + }, + /** + * Gets the parent layer + * @memberof I3SNode.prototype + * @type {Object} + */ + layer: { + get: function () { + return this._layer; + }, + }, + /** + * Gets the parent node + * @memberof I3SNode.prototype + * @type {Object} + */ + parent: { + get: function () { + return this._parent; + }, + }, + /** + * Gets the children nodes + * @memberof I3SNode.prototype + * @type {Array} + */ + children: { + get: function () { + return this._children; + }, + }, + /** + * Gets the collection of geometries + * @memberof I3SNode.prototype + * @type {Array} + */ + geometryData: { + get: function () { + return this._geometryData; + }, + }, + /** + * Gets the collection of features + * @memberof I3SNode.prototype + * @type {Array} + */ + featureData: { + get: function () { + return this._featureData; + }, + }, + /** + * Gets the collection of fields + * @memberof I3SNode.prototype + * @type {Array} + */ + fields: { + get: function () { + return this._fields; + }, + }, + /** + * Gets the Cesium3DTileSet for this layer. + * @memberof I3SNode.prototype + * @type {I3SNode} + */ + tile: { + get: function () { + return this._tile; + }, + }, + /** + * Gets the I3S data for this object. + * @memberof I3SNode.prototype + * @type {Object} + */ + data: { + get: function () { + return this._data; + }, + }, +}); + +/** + * Loads the node definition. + * @returns {Promise} a promise that is resolved when the I3S Node data is loaded + */ +I3SNode.prototype.load = function (isRoot) { + const that = this; + + function processData() { + if (!isRoot) { + // Create a new tile + const tileDefinition = that._create3DTileDefinition(); + + const tileBlob = new Blob([JSON.stringify(tileDefinition)], { + type: "application/json", + }); + + const inPlaceTileURL = URL.createObjectURL(tileBlob); + const resource = Resource.createIfNeeded(inPlaceTileURL); + + that._tile = new Cesium3DTile( + that._layer._tileset, + resource, + tileDefinition, + that._parent._tile + ); + + that._tile._i3sNode = that; + } + } + + // if we don't have a nodepage index load from json + if (this._nodeIndex === undefined) { + return this._dataProvider._loadJson( + this._completeUri, + function (data, resolve) { + // Success + that._data = data; + processData(); + resolve(); + }, + function (reject) { + // Fail + reject(); + } + ); + } + + return new Promise(function (resolve, reject) { + that._layer._getNodeInNodePages(that._nodeIndex).then(function (data) { + that._data = data; + + const pageSize = that._layer._data.nodePages.nodesPerPage; + const node = + that._layer._nodePages[Math.floor(that._nodeIndex / pageSize)][ + that._nodeIndex % pageSize + ]; + if (isRoot) { + that._uri = "nodes/root"; + } else if (node.mesh !== undefined) { + const uriIndex = + that._layer._nodePages[Math.floor(that._nodeIndex / pageSize)][ + that._nodeIndex % pageSize + ].mesh.geometry.resource; + that._uri = `../${uriIndex}`; + } + if (that._uri !== undefined) { + that._completeUriWithoutQuery = `${that._parent._completeUriWithoutQuery}/${that._uri}`; + let query = ""; + if (that._dataProvider._query && that._dataProvider._query !== "") { + query = `?${that._dataProvider._query}`; + } + that._completeUri = that._completeUriWithoutQuery + query; + } + + processData(); + resolve(); + }); + }); +}; + +/** + * Loads the node fields. + * @returns {Promise} a promise that is resolved when the I3S Node fields are loaded + */ +I3SNode.prototype.loadFields = function () { + // check if we must load fields + const fields = this._layer._data.attributeStorageInfo; + + const that = this; + function createAndLoadField(fields, index) { + const newField = new I3SField(that, fields[index]); + that._fields[newField._storageInfo.name] = newField; + return newField.load(); + } + + const promises = []; + if (fields) { + for (let i = 0; i < fields.length; i++) { + promises.push(createAndLoadField(fields, i)); + } + } + + return Promise.all(promises); +}; + +/** + * @private + */ +I3SNode.prototype._loadChildren = function (waitAllChildren) { + const that = this; + return new Promise(function (resolve, reject) { + if (!that._childrenAreLoaded) { + that._childrenAreLoaded = true; + const childPromises = []; + if (that._data.children) { + for ( + let childIndex = 0; + childIndex < that._data.children.length; + childIndex++ + ) { + const child = that._data.children[childIndex]; + const newChild = new I3SNode( + that, + child.href ? child.href : child, + false + ); + that._children.push(newChild); + const childIsLoaded = newChild.load(); + if (waitAllChildren) { + childPromises.push(childIsLoaded); + } + childIsLoaded.then( + (function (theChild) { + return function () { + that._tile.children.push(theChild._tile); + }; + })(newChild) + ); + } + if (waitAllChildren) { + Promise.all(childPromises).then(function () { + resolve(); + }); + } else { + resolve(); + } + } else { + resolve(); + } + } else { + resolve(); + } + }); +}; + +/** + * @private + */ +I3SNode.prototype._loadGeometryData = function () { + const geometryPromises = []; + + // To debug decoding for a specific tile, add a condition + // that wraps this if/else to match the tile uri + if (this._data.geometryData) { + for ( + let geomIndex = 0; + geomIndex < this._data.geometryData.length; + geomIndex++ + ) { + const curGeometryData = new I3SGeometry( + this, + this._data.geometryData[geomIndex].href + ); + this._geometryData.push(curGeometryData); + geometryPromises.push(curGeometryData.load()); + } + } else if (this._data.mesh) { + const geometryDefinition = this._layer._findBestGeometryBuffers( + this._data.mesh.geometry.definition, + ["position", "uv0"] + ); + + const geometryURI = `./geometries/${geometryDefinition.bufferIndex}`; + const newGeometryData = new I3SGeometry(this, geometryURI); + newGeometryData._geometryDefinitions = geometryDefinition.definition; + newGeometryData._geometryBufferInfo = geometryDefinition.geometryBufferInfo; + this._geometryData.push(newGeometryData); + geometryPromises.push(newGeometryData.load()); + } + + return Promise.all(geometryPromises); +}; + +/** + * @private + */ +I3SNode.prototype._loadFeatureData = function () { + const featurePromises = []; + + // To debug decoding for a specific tile, add a condition + // that wraps this if/else to match the tile uri + if (this._data.featureData) { + for ( + let featureIndex = 0; + featureIndex < this._data.featureData.length; + featureIndex++ + ) { + const newfeatureData = new I3SFeature( + this, + this._data.featureData[featureIndex].href + ); + this._featureData.push(newfeatureData); + featurePromises.push(newfeatureData.load()); + } + } + + return Promise.all(featurePromises); +}; + +/** + * @private + */ +I3SNode.prototype._clearGeometryData = function () { + this._geometryData = []; +}; + +/** + * @private + */ +I3SNode.prototype._create3DTileDefinition = function () { + const obb = this._data.obb; + const mbs = this._data.mbs; + + let boundingVolume = {}; + let geoPosition; + let position; + + if (obb) { + geoPosition = Cartographic.fromDegrees( + obb.center[0], + obb.center[1], + obb.center[2] + ); + } else if (mbs) { + geoPosition = Cartographic.fromDegrees(mbs[0], mbs[1], mbs[2]); + } + + //Offset bounding box position if we have a geoid service defined + if ( + defined(this._dataProvider._geoidTiledTerrainProvider) && + defined(geoPosition) + ) { + for (let i = 0; i < this._dataProvider._geoidDataList.length; i++) { + const tile = this._dataProvider._geoidDataList[i]; + const projectedPos = tile.projection.project(geoPosition); + if ( + projectedPos.x > tile.nativeExtent.west && + projectedPos.x < tile.nativeExtent.east && + projectedPos.y > tile.nativeExtent.south && + projectedPos.y < tile.nativeExtent.north + ) { + geoPosition.height += sampleGeoid(projectedPos.x, projectedPos.y, tile); + break; + } + } + } + + if (obb) { + boundingVolume = { + box: [ + 0, + 0, + 0, + obb.halfSize[0], + 0, + 0, + 0, + obb.halfSize[1], + 0, + 0, + 0, + obb.halfSize[2], + ], + }; + position = Ellipsoid.WGS84.cartographicToCartesian(geoPosition); + } else if (mbs) { + boundingVolume = { + sphere: [0, 0, 0, mbs[3]], + }; + position = Ellipsoid.WGS84.cartographicToCartesian(geoPosition); + } else { + console.error(this); + } + + // compute the geometric error + let metersPerPixel = Infinity; + + let span = 0; + if (this._data.mbs) { + span = this._data.mbs[3]; + } else if (this._data.obb) { + span = Math.max( + Math.max(this._data.obb.halfSize[0], this._data.obb.halfSize[1]), + this._data.obb.halfSize[2] + ); + } + + // get the meters/pixel density required to pop the next LOD + if (this._data.lodThreshold !== undefined) { + if ( + this._layer._data.nodePages.lodSelectionMetricType === + "maxScreenThresholdSQ" + ) { + const maxScreenThreshold = + Math.sqrt(this._data.lodThreshold) / (Math.PI * 0.25); + metersPerPixel = span / maxScreenThreshold; + } else if ( + this._layer._data.nodePages.lodSelectionMetricType === + "maxScreenThreshold" + ) { + const maxScreenThreshold = this._data.lodThreshold; + metersPerPixel = span / maxScreenThreshold; + } else { + //Other LOD selection types can only be used for point cloud data + console.error("Invalid lodSelectionMetricType in Layer"); + } + } else if (this._data.lodSelection !== undefined) { + for ( + let lodIndex = 0; + lodIndex < this._data.lodSelection.length; + lodIndex++ + ) { + if ( + this._data.lodSelection[lodIndex].metricType === "maxScreenThreshold" + ) { + metersPerPixel = span / this._data.lodSelection[lodIndex].maxError; + } + } + } + + if (metersPerPixel === Infinity) { + metersPerPixel = 100000; + } + + // calculate the length of 16 pixels in order to trigger the screen space error + const geometricError = metersPerPixel * 16; + + // transformations + const hpr = new HeadingPitchRoll(0, 0, 0); + let orientation = Transforms.headingPitchRollQuaternion(position, hpr); + + if (this._data.obb) { + orientation = new Quaternion( + this._data.obb.quaternion[0], + this._data.obb.quaternion[1], + this._data.obb.quaternion[2], + this._data.obb.quaternion[3] + ); + } + + this._rotationMatrix = Matrix3.fromQuaternion(orientation); + this._inverseRotationMatrix = new Matrix3(); + Matrix3.inverse(this._rotationMatrix, this._inverseRotationMatrix); + + this._globalTransforms = new Matrix4( + this._rotationMatrix[0], + this._rotationMatrix[1], + this._rotationMatrix[2], + 0, + this._rotationMatrix[3], + this._rotationMatrix[4], + this._rotationMatrix[5], + 0, + this._rotationMatrix[6], + this._rotationMatrix[7], + this._rotationMatrix[8], + 0, + position.x, + position.y, + position.z, + 1 + ); + + this.inverseGlobalTransform = new Matrix4(); + Matrix4.inverse(this._globalTransforms, this.inverseGlobalTransform); + + const localTransforms = this._globalTransforms.clone(); + + if (this._parent._globalTransforms) { + Matrix4.multiply( + this._globalTransforms, + this._parent.inverseGlobalTransform, + localTransforms + ); + } + + // get children definition + const childrenDefinition = []; + for (let childIndex = 0; childIndex < this._children.length; childIndex++) { + childrenDefinition.push( + this._children[childIndex]._create3DTileDefinition() + ); + } + + // Create a tile set + const inPlaceTileDefinition = { + children: childrenDefinition, + refine: "REPLACE", + boundingVolume: boundingVolume, + transform: [ + localTransforms[0], + localTransforms[4], + localTransforms[8], + localTransforms[12], + localTransforms[1], + localTransforms[5], + localTransforms[9], + localTransforms[13], + localTransforms[2], + localTransforms[6], + localTransforms[10], + localTransforms[14], + localTransforms[3], + localTransforms[7], + localTransforms[11], + localTransforms[15], + ], + content: { + uri: this._completeUriWithoutQuery, + }, + geometricError: geometricError, + }; + + return inPlaceTileDefinition; +}; + +/** + * @private + */ +I3SNode.prototype._scheduleCreateContentURL = function () { + const that = this; + return new Promise(function (resolve, reject) { + that._createContentURL(resolve, that._tile); + }); +}; + +function createI3SDecoderTask(dataProvider, data) { + // Prepare the data to send to the worker + const parentData = data.geometryData._parent._data; + const parentRotationInverseMatrix = + data.geometryData._parent._inverseRotationMatrix; + + const center = { + long: 0, + lat: 0, + alt: 0, + }; + + if (parentData.obb) { + center.long = parentData.obb.center[0]; + center.lat = parentData.obb.center[1]; + center.alt = parentData.obb.center[2]; + } else if (parentData.mbs) { + center.long = parentData.mbs[0]; + center.lat = parentData.mbs[1]; + center.alt = parentData.mbs[2]; + } + + const axisFlipRotation = Matrix3.fromRotationX(-Math.PI / 2); + const parentRotation = new Matrix3(); + + Matrix3.multiply( + axisFlipRotation, + parentRotationInverseMatrix, + parentRotation + ); + + const cartographicCenter = new Cartographic( + CesiumMath.toRadians(center.long), + CesiumMath.toRadians(center.lat), + center.alt + ); + const cartesianCenter = Ellipsoid.WGS84.cartographicToCartesian( + cartographicCenter + ); + + const payload = { + binaryData: data.geometryData._data, + featureData: + data.featureData && data.featureData[0] ? data.featureData[0].data : null, + schema: data.defaultGeometrySchema, + bufferInfo: data.geometryData._geometryBufferInfo, + ellipsoidRadiiSquare: Ellipsoid.WGS84._radiiSquared, + url: data.url, + geoidDataList: data.geometryData._dataProvider._geoidDataList, + cartographicCenter: center, + cartesianCenter: cartesianCenter, + parentRotation: parentRotation, + }; + + const decodeI3STaskProcessor = dataProvider._getDecoderTaskProcessor(); + + const transferrableObjects = []; + return dataProvider._taskProcessorReadyPromise.then(function () { + return decodeI3STaskProcessor.scheduleTask(payload, transferrableObjects); + }); +} + +/** + * @private + */ +I3SNode.prototype._createContentURL = function (resolve, tile) { + let rawGLTF = { + scene: 0, + scenes: [ + { + nodes: [0], + }, + ], + nodes: [ + { + name: "singleNode", + }, + ], + meshes: [], + buffers: [], + bufferViews: [], + accessors: [], + materials: [], + textures: [], + images: [], + samplers: [], + asset: { + version: "2.0", + }, + }; + + // Load the geometry data + const dataPromises = [this._loadFeatureData(), this._loadGeometryData()]; + + const that = this; + Promise.all(dataPromises).then(function () { + // Binary GLTF + const generateGLTF = new Promise(function (resolve, reject) { + if (that._geometryData && that._geometryData.length > 0) { + const parameters = { + geometryData: that._geometryData[0], + featureData: that._featureData, + defaultGeometrySchema: that._layer._data.store.defaultGeometrySchema, + url: that._geometryData[0]._completeUri, + tile: that._tile, + }; + + const task = createI3SDecoderTask(that._dataProvider, parameters); + if (!defined(task)) { + // Postponed + resolve(); + return; + } + + task.then(function (result) { + rawGLTF = parameters.geometryData._generateGLTF( + result.meshData.nodesInScene, + result.meshData.nodes, + result.meshData.meshes, + result.meshData.buffers, + result.meshData.bufferViews, + result.meshData.accessors + ); + + that._geometryData[0].customAttributes = + result.meshData.customAttributes; + resolve(); + }); + } else { + resolve(); + } + }); + + generateGLTF.then(function () { + const binaryGLTFData = that._dataProvider._binarizeGLTF(rawGLTF); + const glbDataBlob = new Blob([binaryGLTFData], { + type: "application/binary", + }); + that._glbURL = URL.createObjectURL(glbDataBlob); + resolve(); + }); + }); +}; + +/** + * This class implements an I3S Geometry, in Cesium, each I3SGeometry + * generates an in memory gltf to be used as content for a Cesium3DTile + * @private + * @alias I3SGeometry + * @constructor + * @param {I3SNode} [parent] The parent of that geometry + * @param {String} [uri] The uri to load the data from + */ +function I3SGeometry(parent, uri) { + this._parent = parent; + this._dataProvider = parent._dataProvider; + this._layer = parent._layer; + this._uri = uri; + let query = ""; + if (this._dataProvider._query && this._dataProvider._query !== "") { + query = `?${this._dataProvider._query}`; + } + + if (this._parent._nodeIndex) { + this._completeUriWithoutQuery = `${this._layer._completeUriWithoutQuery}/nodes/${this._parent._data.mesh.geometry.resource}/${this._uri}`; + this._completeUri = this._completeUriWithoutQuery + query; + } else { + this._completeUriWithoutQuery = `${this._parent._completeUriWithoutQuery}/${this._uri}`; + this._completeUri = this._completeUriWithoutQuery + query; + } +} + +Object.defineProperties(I3SGeometry.prototype, { + /** + * Gets the uri for the geometry. + * @memberof I3SGeometry.prototype + * @type {string} + */ + uri: { + get: function () { + return this._uri; + }, + }, + /** + * Gets the complete uri for the geometry. + * @memberof I3SGeometry.prototype + * @type {string} + */ + completeUri: { + get: function () { + return this._completeUri; + }, + }, + /** + * Gets the I3S data for this object. + * @memberof I3SGeometry.prototype + * @type {Object} + */ + data: { + get: function () { + return this._data; + }, + }, +}); + +/** + * Loads the content. + * @returns {Promise} a promise that is resolved when the geometry data is loaded + */ +I3SGeometry.prototype.load = function () { + const that = this; + return this._dataProvider._loadBinary( + this._completeUri, + function (data, resolve) { + that._data = data; + resolve(); + }, + function (reject) { + reject(); + } + ); +}; + +function sameSide(p1, p2, a, b) { + const ab = {}; + const ap1 = {}; + const ap2 = {}; + const cp1 = {}; + const cp2 = {}; + Cartesian3.subtract(b, a, ab); + Cartesian3.cross(ab, Cartesian3.subtract(p1, a, ap1), cp1); + Cartesian3.cross(ab, Cartesian3.subtract(p2, a, ap2), cp2); + return Cartesian3.dot(cp1, cp2) >= 0; +} + +/** + * Find a triangle touching the point px,py,pz, then return the vertex closest to the search point + * @param {number} [px] the x component of the point to query + * @param {number} [py] the y component of the point to query + * @param {number} [pz] the z component of the point to query + * @returns {object} a structure containing the index of the closest point, + * the squared distance from the queried point to the point that is found + * the distance from the queried point to the point that is found + * the queried position in local space + * the closest position in local space + */ +I3SGeometry.prototype.getClosestPointIndexOnTriangle = function (px, py, pz) { + if (this.customAttributes && this.customAttributes.positions) { + // convert queried position to local + const position = new Cartesian3(px, py, pz); + + position.x -= this.customAttributes.cartesianCenter.x; + position.y -= this.customAttributes.cartesianCenter.y; + position.z -= this.customAttributes.cartesianCenter.z; + Matrix3.multiplyByVector( + this.customAttributes.parentRotation, + position, + position + ); + + let bestTriDist = Number.MAX_VALUE; + let bestTri; + let bestDistSq; + let bestIndex; + let bestPt; + + // Brute force lookup, @TODO: this can be improved with a spatial partitioning search system + const positions = this.customAttributes.positions; + const indices = this.customAttributes.indices; + + //We may have indexed or non-indexed triangles here + let triCount; + if (indices) { + triCount = indices.length; + } else { + triCount = positions.length / 3; + } + + for (let triIndex = 0; triIndex < triCount; triIndex++) { + let i0, i1, i2; + if (indices) { + i0 = indices[triIndex]; + i1 = indices[triIndex + 1]; + i2 = indices[triIndex + 2]; + } else { + i0 = triIndex * 3; + i1 = triIndex * 3 + 3; + i2 = triIndex * 3 + 6; + } + + const v0 = new Cartesian3( + positions[i0 * 3], + positions[i0 * 3 + 1], + positions[i0 * 3 + 2] + ); + const v1 = new Cartesian3( + positions[i1 * 3], + positions[i1 * 3 + 1], + positions[i1 * 3 + 2] + ); + const v2 = new Cartesian3( + positions[i2 * 3], + positions[i2 * 3 + 1], + positions[i2 * 3 + 2] + ); + + //Check how the point is positioned relative to the triangle. + //This will tell us whether the projection of the point in the triangle's plane lands in the triangle + if ( + !sameSide(position, v0, v1, v2) || + !sameSide(position, v1, v0, v2) || + !sameSide(position, v2, v0, v1) + ) + continue; + + //Because of precision issues, we can't always reliably tell if the point lands directly on the face, so the most robust way is just to find the closest one + const v0v1 = {}, + v0v2 = {}, + crossProd = {}, + normal = {}; + Cartesian3.subtract(v1, v0, v0v1); + Cartesian3.subtract(v2, v0, v0v2); + Cartesian3.cross(v0v1, v0v2, crossProd); + + //Skip "triangles" with 3 colinear points + if (Cartesian3.magnitude(crossProd) === 0) continue; + + Cartesian3.normalize(crossProd, normal); + + const v0p = {}, + v1p = {}, + v2p = {}; + Cartesian3.subtract(position, v0, v0p); + const normalDist = Math.abs(Cartesian3.dot(v0p, normal)); + if (normalDist < bestTriDist) { + bestTriDist = normalDist; + bestTri = triIndex; + + //Found a triangle, return the index of the closest point + const d0 = Cartesian3.magnitudeSquared( + Cartesian3.subtract(position, v0, v0p) + ); + const d1 = Cartesian3.magnitudeSquared( + Cartesian3.subtract(position, v1, v1p) + ); + const d2 = Cartesian3.magnitudeSquared( + Cartesian3.subtract(position, v2, v2p) + ); + if (d0 < d1 && d0 < d2) { + bestIndex = i0; + bestPt = v0; + bestDistSq = d0; + } else if (d1 < d2) { + bestIndex = i1; + bestPt = v1; + bestDistSq = d1; + } else { + bestIndex = i2; + bestPt = v2; + bestDistSq = d2; + } + } + } + + if (bestTri !== undefined) { + return { + index: bestIndex, + distanceSquared: bestDistSq, + distance: Math.sqrt(bestDistSq), + queriedPosition: { + x: position.x, + y: position.y, + z: position.z, + }, + closestPosition: { + x: bestPt.x, + y: bestPt.y, + z: bestPt.z, + }, + }; + } + } + + //No hits found + return { + index: -1, + distanceSquared: Number.Infinity, + distance: Number.Infinity, + }; +}; + +/** + * @private + */ +I3SGeometry.prototype._generateGLTF = function ( + nodesInScene, + nodes, + meshes, + buffers, + bufferViews, + accessors +) { + let query = ""; + if (this._dataProvider._query && this._dataProvider._query !== "") { + query = `?${this._dataProvider._query}`; + } + + // Get the material definition + const materialInfo = this._parent._data.mesh + ? this._parent._data.mesh.material + : null; + let materialIndex = 0; + let isTextured = false; + let gltfMaterial = { + pbrMetallicRoughness: { + metallicFactor: 0.0, + }, + doubleSided: true, + name: "Material", + }; + + if (materialInfo) { + materialIndex = materialInfo.definition; + } + + let materialDefinition; + if (this._layer._data.materialDefinitions) { + materialDefinition = this._layer._data.materialDefinitions[materialIndex]; + } + + if (materialDefinition) { + gltfMaterial = materialDefinition; + + // Textured. @TODO: extend to other textured cases + if ( + materialDefinition.pbrMetallicRoughness && + materialDefinition.pbrMetallicRoughness.baseColorTexture + ) { + isTextured = true; + } + } + + let texturePath; + + if (this._parent._data.textureData) { + texturePath = `${this._parent._completeUriWithoutQuery}/${this._parent._data.textureData[0].href}${query}`; + } else { + // Choose the JPG for the texture + let textureName = "0"; + + if (this._layer._data.textureSetDefinitions) { + for ( + let defIndex = 0; + defIndex < this._layer._data.textureSetDefinitions.length; + defIndex++ + ) { + const textureSetDefinition = this._layer._data.textureSetDefinitions[ + defIndex + ]; + for ( + let formatIndex = 0; + formatIndex < textureSetDefinition.formats.length; + formatIndex++ + ) { + const textureFormat = textureSetDefinition.formats[formatIndex]; + if (textureFormat.format === "jpg") { + textureName = textureFormat.name; + break; + } + } + } + } + + if (this._parent._data.mesh) { + texturePath = `${this._layer._completeUriWithoutQuery}/nodes/${this._parent._data.mesh.material.resource}/textures/${textureName}${query}`; + } + } + + let gltfTextures = []; + let gltfImages = []; + let gltfSamplers = []; + + if (isTextured) { + gltfTextures = [ + { + sampler: 0, + source: 0, + }, + ]; + + gltfImages = [ + { + uri: texturePath, + }, + ]; + + gltfSamplers = [ + { + magFilter: 9729, + minFilter: 9986, + wrapS: 10497, + wrapT: 10497, + }, + ]; + + gltfMaterial.pbrMetallicRoughness.baseColorTexture.index = 0; + } + + const gltfData = { + scene: 0, + scenes: [ + { + nodes: nodesInScene, + }, + ], + nodes: nodes, + meshes: meshes, + buffers: buffers, + bufferViews: bufferViews, + accessors: accessors, + materials: [gltfMaterial], + textures: gltfTextures, + images: gltfImages, + samplers: gltfSamplers, + asset: { + version: "2.0", + }, + }; + + return gltfData; +}; + +function bilinearInterpolate(tx, ty, h00, h10, h01, h11) { + const a = h00 * (1 - tx) + h10 * tx; + const b = h01 * (1 - tx) + h11 * tx; + return a * (1 - ty) + b * ty; +} + +function sampleMap(u, v, width, data) { + const address = u + v * width; + return data[address]; +} + +function sampleGeoid(sampleX, sampleY, geoidData) { + const extent = geoidData.nativeExtent; + let x = + ((sampleX - extent.west) / (extent.east - extent.west)) * + (geoidData.width - 1); + let y = + ((sampleY - extent.south) / (extent.north - extent.south)) * + (geoidData.height - 1); + const xi = Math.floor(x); + let yi = Math.floor(y); + + x -= xi; + y -= yi; + + const xNext = xi < geoidData.width ? xi + 1 : xi; + let yNext = yi < geoidData.height ? yi + 1 : yi; + + yi = geoidData.height - 1 - yi; + yNext = geoidData.height - 1 - yNext; + + const h00 = sampleMap(xi, yi, geoidData.width, geoidData.buffer); + const h10 = sampleMap(xNext, yi, geoidData.width, geoidData.buffer); + const h01 = sampleMap(xi, yNext, geoidData.width, geoidData.buffer); + const h11 = sampleMap(xNext, yNext, geoidData.width, geoidData.buffer); + + let finalHeight = bilinearInterpolate(x, y, h00, h10, h01, h11); + finalHeight = finalHeight * geoidData.scale + geoidData.offset; + return finalHeight; +} + +/** + * This class implements an I3S Field which is custom data attachec + * to nodes + * @private + * @alias I3SField + * @constructor + * @param {I3SNode} [parent] The parent of that geometry + * @param {Object} [storageInfo] The structure containing the storage info of the field + */ +function I3SField(parent, storageInfo) { + this._storageInfo = storageInfo; + this._parent = parent; + this._dataProvider = parent._dataProvider; + this._uri = `/attributes/${storageInfo.key}/0`; + let query = ""; + if (this._dataProvider._query && this._dataProvider._query !== "") { + query = `?${this._dataProvider._query}`; + } + + this._completeUriWithoutQuery = + this._parent._completeUriWithoutQuery + this._uri; + this._completeUri = this._completeUriWithoutQuery + query; +} + +Object.defineProperties(I3SField.prototype, { + /** + * Gets the uri for the field. + * @memberof I3SField.prototype + * @type {string} + */ + uri: { + get: function () { + return this._uri; + }, + }, + /** + * Gets the complete uri for the field. + * @memberof I3SField.prototype + * @type {string} + */ + completeUri: { + get: function () { + return this._completeUri; + }, + }, + /** + * Gets the header for this field. + * @memberof I3SField.prototype + * @type {Object} + */ + header: { + get: function () { + return this._header; + }, + }, + /** + * Gets the values for this field. + * @memberof I3SField.prototype + * @type {Object} + */ + values: { + get: function () { + return this._values && this._values.attributeValues + ? this._values.attributeValues + : []; + }, + }, + /** + * Gets the name for the field. + * @memberof I3SField.prototype + * @type {string} + */ + name: { + get: function () { + return this._storageInfo.name; + }, + }, +}); + +/** + * Loads the content. + * @returns {Promise} a promise that is resolved when the geometry data is loaded + */ +I3SField.prototype.load = function () { + const that = this; + return this._dataProvider._loadBinary( + this._completeUri, + function (data, resolve) { + // check if we have a 404 + const dataView = new DataView(data); + let success = true; + if (dataView.getUint8(0) === "{".charCodeAt(0)) { + const textContent = new TextDecoder(); + const str = textContent.decode(data); + if (str.includes("404")) { + success = false; + console.error("Failed to load:", that._completeUri); + } + } + + if (success) { + that._data = data; + let offset = that._parseHeader(dataView); + + // @TODO: find out why we must skip 4 bytes when the value type is float 64 + if ( + that._storageInfo && + that._storageInfo.attributeValues && + that._storageInfo.attributeValues.valueType === "Float64" + ) { + offset += 4; + } + + that._parseBody(dataView, offset); + } + + resolve(); + }, + function (reject) { + reject(); + } + ); +}; + +/** + * @private + */ +I3SField.prototype._parseValue = function (dataView, type, offset) { + let value = null; + if (type === "UInt8") { + value = dataView.getUint8(offset); + offset += 1; + } else if (type === "Int8") { + value = dataView.getInt8(offset); + offset += 1; + } else if (type === "UInt16") { + value = dataView.getUint16(offset, true); + offset += 2; + } else if (type === "Int16") { + value = dataView.getInt16(offset, true); + offset += 2; + } else if (type === "UInt32") { + value = dataView.getUint32(offset, true); + offset += 4; + } else if (type === "Oid32") { + value = dataView.getUint32(offset, true); + offset += 4; + } else if (type === "Int32") { + value = dataView.getInt32(offset, true); + offset += 4; + } else if (type === "Float32") { + value = dataView.getFloat32(offset, true); + offset += 4; + } else if (type === "UInt64") { + value = dataView.getUint64(offset, true); + offset += 8; + } else if (type === "Int64") { + value = dataView.getInt64(offset, true); + offset += 8; + } else if (type === "Float64") { + value = dataView.getFloat64(offset, true); + offset += 8; + } else if (type === "String") { + value = String.fromCharCode(dataView.getUint8(offset)); + offset += 1; + } + + return { + value: value, + offset: offset, + }; +}; + +/** + * @private + */ +I3SField.prototype._parseHeader = function (dataView) { + let offset = 0; + this._header = {}; + for ( + let itemIndex = 0; + itemIndex < this._storageInfo.header.length; + itemIndex++ + ) { + const item = this._storageInfo.header[itemIndex]; + const parsedValue = this._parseValue(dataView, item.valueType, offset); + this._header[item.property] = parsedValue.value; + offset = parsedValue.offset; + } + return offset; +}; + +/** + * @private + */ +I3SField.prototype._parseBody = function (dataView, offset) { + this._values = {}; + for ( + let itemIndex = 0; + itemIndex < this._storageInfo.ordering.length; + itemIndex++ + ) { + const item = this._storageInfo.ordering[itemIndex]; + const desc = this._storageInfo[item]; + if (desc) { + this._values[item] = []; + for (let index = 0; index < this._header.count; ++index) { + if (desc.valueType !== "String") { + const parsedValue = this._parseValue( + dataView, + desc.valueType, + offset + ); + this._values[item].push(parsedValue.value); + offset = parsedValue.offset; + } else { + const stringLen = this._values.attributeByteCounts[index]; + let stringContent = ""; + for (let cIndex = 0; cIndex < stringLen; ++cIndex) { + const curParsedValue = this._parseValue( + dataView, + desc.valueType, + offset + ); + stringContent += curParsedValue.value; + offset = curParsedValue.offset; + } + this._values[item].push(stringContent); + } + } + } + } +}; + +export default I3SNode; From 0d887a6c11ab8b6137f62d82162bf283a5aa91f7 Mon Sep 17 00:00:00 2001 From: Tamrat Belayneh Date: Fri, 8 Jul 2022 13:06:44 -0700 Subject: [PATCH 13/57] eslint fixes... --- Source/Scene/I3SNode.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Source/Scene/I3SNode.js b/Source/Scene/I3SNode.js index 7d10d785e3e9..0fa26e8b5ad4 100644 --- a/Source/Scene/I3SNode.js +++ b/Source/Scene/I3SNode.js @@ -924,9 +924,9 @@ I3SGeometry.prototype.getClosestPointIndexOnTriangle = function (px, py, pz) { !sameSide(position, v0, v1, v2) || !sameSide(position, v1, v0, v2) || !sameSide(position, v2, v0, v1) - ) + ) { continue; - + } //Because of precision issues, we can't always reliably tell if the point lands directly on the face, so the most robust way is just to find the closest one const v0v1 = {}, v0v2 = {}, @@ -937,8 +937,9 @@ I3SGeometry.prototype.getClosestPointIndexOnTriangle = function (px, py, pz) { Cartesian3.cross(v0v1, v0v2, crossProd); //Skip "triangles" with 3 colinear points - if (Cartesian3.magnitude(crossProd) === 0) continue; - + if (Cartesian3.magnitude(crossProd) === 0) { + continue; + } Cartesian3.normalize(crossProd, normal); const v0p = {}, From 9dbd070926ae876a7fe2727a301ed0dfdba999ad Mon Sep 17 00:00:00 2001 From: Tamrat Belayneh Date: Sat, 9 Jul 2022 13:32:02 -0700 Subject: [PATCH 14/57] Updated samples, added ability to consume an i3s layer from service as well as layer urls --- .../gallery/I3S 3D Object Layer.html | 92 ++++++- .../gallery/I3S Feature Picking.html | 3 +- .../gallery/I3S IntegratedMesh Layer.html | 3 +- Source/Scene/I3SDataProvider.js | 258 +++++++++++------- 4 files changed, 245 insertions(+), 111 deletions(-) diff --git a/Apps/Sandcastle/gallery/I3S 3D Object Layer.html b/Apps/Sandcastle/gallery/I3S 3D Object Layer.html index 697cdca9a387..404e3cf83bab 100644 --- a/Apps/Sandcastle/gallery/I3S 3D Object Layer.html +++ b/Apps/Sandcastle/gallery/I3S 3D Object Layer.html @@ -43,10 +43,11 @@

Loading...

animation: false, timeline: false, }); - //More datasets to tour can be added here.. + //More datasets to tour can be added here... + //I3SDataProvider.loadurl api supports loading a single Indexed 3D Scene (I3S) layer (./SceneServer/layers/) or a collection of scene layers (./SceneServer) from a scene Scene SceneServer. const tours = { "San Francisco": - "https://tiles.arcgis.com/tiles/z2tnIkrLQ2BRzr6P/arcgis/rest/services/SanFrancisco_3DObjects_1_7/SceneServer", + "https://tiles.arcgis.com/tiles/z2tnIkrLQ2BRzr6P/arcgis/rest/services/SanFrancisco_3DObjects_1_7/SceneServer/layers/0", }; // Initialize a terrain provider which provides geoid conversion between gravity releated (typically I3S datasets) and ellipsoidiaL based // height systems (cesium world terrain). @@ -65,7 +66,7 @@

Loading...

const cesiumTilesetOptions = { show: true, - skipLevelOfDetail: false, + skipLevelOfDetail: true, debugShowBoundingVolume: false, }; //Create I3S data provider. autoCenterCameraOnStart i3s option autocenters the camera to the I3S layers' extent @@ -79,6 +80,91 @@

Loading...

i3sProvider.loadUrl(tours["San Francisco"]).then(function () {}); //add the i3s layer provider as a primitive data type viewer.scene.primitives.add(i3sProvider); + // An entity object which will hold info about the currently selected feature for infobox display + const selectedEntity = new Cesium.Entity(); + // Silhouette a feature on selection and show metadata in the InfoBox. + viewer.screenSpaceEventHandler.setInputAction(function onLeftClick( + movement + ) { + // Pick a new feature + const pickedFeature = viewer.scene.pick(movement.position); + if (!Cesium.defined(pickedFeature)) { + return; + } + + const pickedPosition = viewer.scene.pickPosition(movement.position); + + if ( + pickedFeature && + pickedFeature.content && + pickedFeature.content.tile.i3sNode + ) { + const i3sNode = pickedFeature.content.tile.i3sNode; + i3sNode.loadFields().then(function () { + const geometry = i3sNode.geometryData[0]; + if (pickedPosition) { + const location = geometry.getClosestPointIndexOnTriangle( + pickedPosition.x, + pickedPosition.y, + pickedPosition.z + ); + let description = "No attributes"; + let name = ""; + if ( + location.index !== -1 && + geometry.customAttributes["feature-index"] + ) { + console.log("Location", location); + const featureIndex = + geometry.customAttributes["feature-index"][location.index]; + if (Object.keys(i3sNode.fields).length > 0) { + description = + '
" + field.name + ""; description += field.values[featureIndex] + "
'; + for (const fieldName in i3sNode.fields) { + if (fieldName) { + const field = i3sNode.fields[fieldName]; + description += + ``; + console.log( + `${field.name}` + + `: ` + + `${field.values[featureIndex]}` + ); + if ( + (!name || 0 === name.length) && + isNameProperty(field.name) + ) { + name = field.values[featureIndex]; + } + } + } + description += `
` + `${field.name}` + ``; + description += + `${field.values[featureIndex]}` + `
`; + } + } + if (!name || 0 === name.length) { + name = "unknown"; + } + selectedEntity.name = name; + selectedEntity.description = description; + viewer.selectedEntity = selectedEntity; + } + }); + } + }, + Cesium.ScreenSpaceEventType.LEFT_CLICK); + + function isNameProperty(propertyName) { + const name = propertyName.toLowerCase(); + if ( + name.localeCompare("name") === 0 || + name.localeCompare("objname") === 0 + ) { + return true; + } + return false; + } //Sandcastle_End if (typeof Cesium !== "undefined") { window.startupCalled = true; diff --git a/Apps/Sandcastle/gallery/I3S Feature Picking.html b/Apps/Sandcastle/gallery/I3S Feature Picking.html index 6145452b13d0..1218fff1f101 100644 --- a/Apps/Sandcastle/gallery/I3S Feature Picking.html +++ b/Apps/Sandcastle/gallery/I3S Feature Picking.html @@ -44,6 +44,7 @@

Loading...

timeline: false, }); //More datasets to tour can be added here.. + //I3SDataProvider.loadurl api supports loading a single Indexed 3D Scene (I3S) layer (./SceneServer/layers/) or a collection of scene layers (./SceneServer) from a scene Scene SceneServer. const tours = { "New York": "https://tiles.arcgis.com/tiles/z2tnIkrLQ2BRzr6P/arcgis/rest/services/NYC_Attributed_v17/SceneServer", @@ -65,7 +66,7 @@

Loading...

const cesiumTilesetOptions = { show: true, - skipLevelOfDetail: false, + skipLevelOfDetail: true, debugShowBoundingVolume: false, }; //Create I3S data provider. autoCenterCameraOnStart i3s option autocenters the camera to the I3S layers' extent diff --git a/Apps/Sandcastle/gallery/I3S IntegratedMesh Layer.html b/Apps/Sandcastle/gallery/I3S IntegratedMesh Layer.html index 0646446d4733..5289071337e4 100644 --- a/Apps/Sandcastle/gallery/I3S IntegratedMesh Layer.html +++ b/Apps/Sandcastle/gallery/I3S IntegratedMesh Layer.html @@ -45,9 +45,10 @@

Loading...

timeline: false, }); //More datasets to tour can be added here.. + //I3SDataProvider.loadurl api supports loading a single Indexed 3D Scene (I3S) layer (./SceneServer/layers/) or a collection of scene layers (./SceneServer) from a scene Scene SceneServer. const tours = { Frankfurt: - "https://tiles.arcgis.com/tiles/z2tnIkrLQ2BRzr6P/arcgis/rest/services/Frankfurt2017_vi3s_18/SceneServer", + "https://tiles.arcgis.com/tiles/z2tnIkrLQ2BRzr6P/arcgis/rest/services/Frankfurt2017_vi3s_18/SceneServer/layers/0", }; // Initialize a terrain provider which provides geoid conversion between gravity releated (typically I3S datasets) and ellipsoidiaL based // height systems (cesium world terrain). diff --git a/Source/Scene/I3SDataProvider.js b/Source/Scene/I3SDataProvider.js index c2b48f7b4633..624b844dd275 100644 --- a/Source/Scene/I3SDataProvider.js +++ b/Source/Scene/I3SDataProvider.js @@ -1,39 +1,51 @@ -/** - * This code implements support for I3S 1.6 and 1.7 in the Cesium viewer. It allows the user - * to access a scene server URL and load it inside of the Cesium viewer. +/* +* Esri Contribution: This code implements support for I3S (Indexed 3D Scene Layers), an OGC Community Standard. +* Co-authored-by: Alexandre Jean-Claude ajeanclaude@spiria.com +* Co-authored-by: Anthony Mirabeau anthony.mirabeau@presagis.com +* Co-authored-by: Elizabeth Rudkin elizabeth.rudkin@presagis.com +* Co-authored-by: Tamrat Belayneh tbelayneh@esri.com + + * The I3S format has been developed by Esri and is shared under an Apache 2.0 license and is maintained @ https://github.com/Esri/i3s-spec. + * This implementation supports loading, displaying, and querying an I3S layer (supported versions include Esri github I3S versions 1.6, 1.7/1.8 - + * whose OGC equivalent are I3S Community Standard Version 1.1 & 1.2) in the Cesium viewer. + * It enables the user to access an I3S layer via its URL and load it inside of the Cesium viewer. * - * When the scene server is initialized, and the loadURL function invoked, it do the following: + * When a scene layer is initialized, and the I3SDataProvider.loadURL function is invoked, it accomplishes the following: * - * Load the data at the scene server level - * Load all layers data - * For each layer, create a Cesium 3D Tile Set and load the root node - * When the root node is imported, it creates a Cesium 3D tile that is parented to the Cesium 3D tile set - * Load all children of the root node + * It processes the 3D Scene Layer resource (https://github.com/Esri/i3s-spec/blob/master/docs/1.8/3DSceneLayer.cmn.md) of an I3S dataset + * and loads the layers data. It does so by creating a Cesium 3D Tile Set for the given i3s layer and loads the root node. + * When the root node is imported, it creates a Cesium 3D tile that is parented to the Cesium 3D tile set + * and loads all children of the root node: * for each children - * Create a place holder 3D tile so that the LOD display can know about it and display as needed - * When the LOD display decides that a Cesium 3D tile is visible, it invokes requestContent on it + * Create a place holder 3D tile so that the LOD display can use the nodes' selection criteria (maximumScreenSpaceError) to select the appropriate node + * based on the current LOD display & evaluation. If the Cesium 3D tile is visible, it invokes requestContent on it. * At that moment, we intercept the call to requestContent, and we load the geometry in I3S format * That geometry is transcoded on the fly to gltf format and ingested by Cesium * When the geometry is loaded, we then load all children of this node as placeholders so that the LOD - * can know about them too + * can know about them too. * * About transcoding: * - * We use web workers to transcode I3S geometries into Cesium GLTF + * We use web workers to transcode I3S geometries into glTF * The steps are: * - * Decode geometry attributes (positions, normals, etc..) either from DRACO or Binary format - * Convert heights for all vertices from Orthometric to Ellipsoidal - * Transform vertex coordinates from LONG/LAT/HEIGHT to Cartesian in local space and + * Decode geometry attributes (positions, normals, etc..) either from DRACO or Binary format. + * If requested, (when creating an I3SDataProvider the user has the option to specify a tiled elevation terrain provider + * (geoidTiledTerrainProvider) such as the one shown in the sandbox example based on ArcGISTiledElevationTerrainProvider, that allows + * conversion of heights for all vertices & bounding boxes of an I3S layer from (typically) gravity related (Orthometric) heights to Ellipsoidal. + * This step is essential when fusing data with varying height sources (as is the case when fusing the I3S dataset (gravity related) in the sandcastle examples with the cesium world terrain (ellipsoidal)). + * We then transform vertex coordinates from LONG/LAT/HEIGHT to Cartesian in local space and * scale appropriately if specified in the attribute metadata * Crop UVs if UV regions are defined in the attribute metadata - * Create a GLTF document in memory that will be ingested as part of a glb payload + * Create a glTF document in memory that will be ingested as part of a glb payload * * About GEOID data: * - * We provide the ability to use GEOID data to convert heights from orthometric to ellipsoidal. - * The i3S data source uses a tiled elevation terrain provider to access the geoid tiles. - * The sandcastle example below shows how to set the terrain provider service if required. + * We provide the ability to use GEOID data to convert heights from gravity related (orthometric) height systems to ellipsoidal. + * We employ a service architecture to get the conversion factor for a given long lat values, leveraging existing implementation based on ArcGISTiledElevationTerrainProvider + * to avoid requiring bloated look up files. The source Data used in this transcoding service was compiled from https://earth-info.nga.mil/#tab_wgs84-data and is based on + * EGM2008 Gravity Model. The Sandbox examples show how to set the terrain provider service if required. + * * */ @@ -46,7 +58,8 @@ let viewer = new Cesium.Viewer("cesiumContainer", { }); viewer.clock.shouldAnimate = false; let tours = { - "Frankfurt": "https://tiles.arcgis.com/tiles/u0sSNqDXr7puKJrF/arcgis/rest/services/Frankfurt2017_v17/SceneServer" + "Frankfurt": "https://tiles.arcgis.com/tiles/z2tnIkrLQ2BRzr6P/arcgis/rest/services/Frankfurt2017_vi3s_18/SceneServer/layers/0; +/layers/0" }; // Initialize the terrain provider which provides the geoid conversion // If this is not specified, or the URL is invalid no geoid conversion will be applied. @@ -136,12 +149,11 @@ if (_tracecode) { } /** - * This class implements using an I3S scene server as a Cesium data source. The URL + * This class implements using an I3S Scene Layer as a Cesium data source. The URL * that is used for loadUrl should return a scene object. Currently supported I3S - * versions are 1.6 and 1.7. I3SDataProvider is the main public class for I3S support. - * All other classes in this source file implement the Object Model for the I3S entities, - * which may at some point have more public interfaces if further introspection or - * customization need to be added. + * versions are 1.6 and 1.7/1.8 (OGC I3S 1.2). An I3SDataProvider is the main public class for I3S support. + * I3SFeature and I3SNode classes implement the Object Model for I3S entities, with public interfaces + * @alias I3SDataProvider * @constructor * @@ -152,7 +164,8 @@ if (_tracecode) { * * @example * let i3sData = new I3SDataProvider(); - * i3sData.loadUrl('https://tiles.arcgis.com/tiles/u0sSNqDXr7puKJrF/arcgis/rest/services/Frankfurt2017_v17/SceneServer'); + + * i3sData.loadUrl('https://tiles.arcgis.com/tiles/z2tnIkrLQ2BRzr6P/arcgis/rest/services/Frankfurt2017_vi3s_18/SceneServer/layers/0'); * viewer.scene.primitives.add(i3sData); * * @example @@ -310,8 +323,8 @@ I3SDataProvider.prototype.loadUrl = function (url) { this._query = parts[1]; this._completeUrl = url; - this._sceneServer = new I3SSceneServer(this); - return this._sceneServer.load(this._completeUrl); + this._sceneLayer = new I3SSceneLayer(this); + return this._sceneLayer.load(this._completeUrl); }; /** @@ -343,11 +356,11 @@ I3SDataProvider.prototype.load = function (data) { }; /** - * Destroys the WebGL resources held by this object. Destroying an object allows for deterministic + * Destroys the WebGL resources held by this object. Destroying an object allows for deterministic * release of WebGL resources, instead of relying on the garbage collector to destroy this object. *

* Once an object is destroyed, it should not be used; calling any function other than - * isDestroyed will result in a {@link DeveloperError} exception. Therefore, + * isDestroyed will result in a {@link DeveloperError} exception. Therefore, * assign the return value (undefined) to the object as done in the example. *

* @@ -356,8 +369,8 @@ I3SDataProvider.prototype.load = function (data) { * @see Primitive#isDestroyed */ I3SDataProvider.prototype.destroy = function () { - for (let i = 0; i < this._sceneServer._layerCollection.length; i++) { - this._sceneServer._layerCollection[i]._tileset.destroy(); + for (let i = 0; i < this._sceneLayer._layerCollection.length; i++) { + this._sceneLayer._layerCollection[i]._tileset.destroy(); } }; @@ -373,8 +386,8 @@ I3SDataProvider.prototype.destroy = function () { * @see Primitive#destroy */ I3SDataProvider.prototype.isDestroyed = function () { - if (this._sceneServer._layerCollection.length !== 0) { - return this._sceneServer._layerCollection[0]._tileset.isDestroyed(); + if (this._sceneLayer._layerCollection.length !== 0) { + return this._sceneLayer._layerCollection[0]._tileset.isDestroyed(); } return false; @@ -394,12 +407,12 @@ I3SDataProvider.prototype.isDestroyed = function () { * @exception {RuntimeError} Vertex texture fetch support is required to render primitives with per-instance attributes. The maximum number of vertex texture image units must be greater than zero. */ I3SDataProvider.prototype.update = function (frameState) { - for (let i = 0; i < this._sceneServer._layerCollection.length; i++) { + for (let i = 0; i < this._sceneLayer._layerCollection.length; i++) { if ( - typeof this._sceneServer._layerCollection[i]._tileset !== "undefined" && - this._sceneServer._layerCollection[i]._tileset.ready + typeof this._sceneLayer._layerCollection[i]._tileset !== "undefined" && + this._sceneLayer._layerCollection[i]._tileset.ready ) { - this._sceneServer._layerCollection[i]._tileset.update(frameState); + this._sceneLayer._layerCollection[i]._tileset.update(frameState); } } }; @@ -408,14 +421,12 @@ I3SDataProvider.prototype.update = function (frameState) { * @private */ I3SDataProvider.prototype.prePassesUpdate = function (frameState) { - for (let i = 0; i < this._sceneServer._layerCollection.length; i++) { + for (let i = 0; i < this._sceneLayer._layerCollection.length; i++) { if ( - typeof this._sceneServer._layerCollection[i]._tileset !== "undefined" && - this._sceneServer._layerCollection[i]._tileset.ready + typeof this._sceneLayer._layerCollection[i]._tileset !== "undefined" && + this._sceneLayer._layerCollection[i]._tileset.ready ) { - this._sceneServer._layerCollection[i]._tileset.prePassesUpdate( - frameState - ); + this._sceneLayer._layerCollection[i]._tileset.prePassesUpdate(frameState); } } }; @@ -424,12 +435,12 @@ I3SDataProvider.prototype.prePassesUpdate = function (frameState) { * @private */ I3SDataProvider.prototype.postPassesUpdate = function (frameState) { - for (let i = 0; i < this._sceneServer._layerCollection.length; i++) { + for (let i = 0; i < this._sceneLayer._layerCollection.length; i++) { if ( - typeof this._sceneServer._layerCollection[i]._tileset !== "undefined" && - this._sceneServer._layerCollection[i]._tileset.ready + typeof this._sceneLayer._layerCollection[i]._tileset !== "undefined" && + this._sceneLayer._layerCollection[i]._tileset.ready ) { - this._sceneServer._layerCollection[i]._tileset.postPassesUpdate( + this._sceneLayer._layerCollection[i]._tileset.postPassesUpdate( frameState ); } @@ -440,12 +451,12 @@ I3SDataProvider.prototype.postPassesUpdate = function (frameState) { * @private */ I3SDataProvider.prototype.updateForPass = function (frameState, passState) { - for (let i = 0; i < this._sceneServer._layerCollection.length; i++) { + for (let i = 0; i < this._sceneLayer._layerCollection.length; i++) { if ( - typeof this._sceneServer._layerCollection[i]._tileset !== "undefined" && - this._sceneServer._layerCollection[i]._tileset.ready + typeof this._sceneLayer._layerCollection[i]._tileset !== "undefined" && + this._sceneLayer._layerCollection[i]._tileset.ready ) { - this._sceneServer._layerCollection[i]._tileset.updateForPass( + this._sceneLayer._layerCollection[i]._tileset.updateForPass( frameState, passState ); @@ -586,24 +597,24 @@ function longLatsToMeter(longitude1, latitude1, longitude2, latitude2) { } /** - * This class implements an I3S scene server + * This class implements an I3S scene layer * @private - * @alias I3SSceneServer + * @alias I3SSceneLayer * @param {I3SDataProvider} [dataProvider] The data source that is the - * owner of this scene server + * owner of this scene layer * @constructor */ -function I3SSceneServer(dataProvider) { +function I3SSceneLayer(dataProvider) { this._dataProvider = dataProvider; this._entities = {}; this._layerCollection = []; this._uri = ""; } -Object.defineProperties(I3SSceneServer.prototype, { +Object.defineProperties(I3SSceneLayer.prototype, { /** * Gets the collection of Layers. - * @memberof I3SSceneServer.prototype + * @memberof I3SSceneLayer.prototype * @type {Array} */ layers: { @@ -612,8 +623,8 @@ Object.defineProperties(I3SSceneServer.prototype, { }, }, /** - * Gets the URI of the scene server. - * @memberof I3SSceneServer.prototype + * Gets the URI of the scene layer. + * @memberof I3SSceneLayer.prototype * @type {String} */ uri: { @@ -623,7 +634,7 @@ Object.defineProperties(I3SSceneServer.prototype, { }, /** * Gets the I3S data for this object. - * @memberof I3SSceneServer.prototype + * @memberof I3SSceneLayer.prototype * @type {Object} */ data: { @@ -634,10 +645,10 @@ Object.defineProperties(I3SSceneServer.prototype, { }); /** - * Loads the provided data on the server. + * Loads the data from the provided I3S Scene layer. * @param {String} [uri] The uri where to fetch the data from. */ -I3SSceneServer.prototype.load = function (uri) { +I3SSceneLayer.prototype.load = function (uri) { const that = this; this._uri = uri; return this._dataProvider._loadJson( @@ -646,20 +657,25 @@ I3SSceneServer.prototype.load = function (uri) { // Success that._data = data; const layerPromises = []; - for ( - let layerIndex = 0; - layerIndex < that._data.layers.length; - layerIndex++ - ) { - const newLayer = new I3SLayer( - that, - that._data.layers[layerIndex], - layerIndex - ); + if (that._data.layers) { + for ( + let layerIndex = 0; + layerIndex < that._data.layers.length; + layerIndex++ + ) { + const newLayer = new I3SLayer( + that, + that._data.layers[layerIndex], + layerIndex + ); + that._layerCollection.push(newLayer); + layerPromises.push(newLayer.load()); + } + } else { + const newLayer = new I3SLayer(that, that._data, that._data.id); that._layerCollection.push(newLayer); layerPromises.push(newLayer.load()); } - Promise.all(layerPromises).then(function () { that._computeExtent(); @@ -682,7 +698,7 @@ I3SSceneServer.prototype.load = function (uri) { * Or 1000m when in oblique. * @param {String} [mode] Use "topdown" to set the camera be top down, otherwise, the camera is set with a pitch 0f 0.2 radians. */ -I3SSceneServer.prototype.centerCamera = function (mode) { +I3SSceneLayer.prototype.centerCamera = function (mode) { if (mode === "topdown") { this._dataProvider.camera.setView({ destination: wgs84ToCartesian( @@ -743,7 +759,7 @@ function computeExtent(minLongitude, minLatitude, maxLongitude, maxLatitude) { /** * @private */ -I3SSceneServer.prototype._computeExtent = function () { +I3SSceneLayer.prototype._computeExtent = function () { let minLongitude = Number.MAX_VALUE; let maxLongitude = -Number.MAX_VALUE; let minLatitude = Number.MAX_VALUE; @@ -756,39 +772,50 @@ I3SSceneServer.prototype._computeExtent = function () { layerIndex++ ) { if ( - this._layerCollection[layerIndex]._data.store && - this._layerCollection[layerIndex]._data.store.extent + (this._layerCollection[layerIndex]._data.store && + this._layerCollection[layerIndex]._data.store.extent) || + this._layerCollection[layerIndex]._data.fullExtent ) { - const layerExtent = this._layerCollection[layerIndex]._data.store.extent; - minLongitude = Math.min(minLongitude, layerExtent[0]); - minLatitude = Math.min(minLatitude, layerExtent[1]); - maxLongitude = Math.max(maxLongitude, layerExtent[2]); - maxLatitude = Math.max(maxLatitude, layerExtent[3]); + const layerExtent = this._layerCollection[layerIndex]._data.fullExtent + ? this._layerCollection[layerIndex]._data.fullExtent + : this._layerCollection[layerIndex]._data.store.extent; + if (layerExtent && this._layerCollection[layerIndex]._data.fullExtent) { + minLongitude = Math.min(minLongitude, layerExtent.xmin); + minLatitude = Math.min(minLatitude, layerExtent.ymin); + maxLongitude = Math.max(maxLongitude, layerExtent.xmax); + maxLatitude = Math.max(maxLatitude, layerExtent.ymax); + } else if ( + layerExtent && + this._layerCollection[layerIndex]._data.store.extent + ) { + minLongitude = Math.min(minLongitude, layerExtent[0]); + minLatitude = Math.min(minLatitude, layerExtent[1]); + maxLongitude = Math.max(maxLongitude, layerExtent[2]); + maxLatitude = Math.max(maxLatitude, layerExtent[3]); + } } + this._extent = computeExtent( + minLongitude, + minLatitude, + maxLongitude, + maxLatitude + ); } - this._extent = computeExtent( - minLongitude, - minLatitude, - maxLongitude, - maxLatitude - ); }; - /** * This class implements an I3S layer, in Cesium, each I3SLayer * creates a Cesium3DTileset * @private * @alias I3SLayer * @constructor - * @param {I3SSceneServer} [sceneServer] The scene server that is the - * container for this layer + * @param {I3SSceneLayer} [sceneLayer] The scene layer * @param {object} [layerData] The layer data that is loaded from the scene - * server + * layer * @param {number} [index] The index of the layer to be reflected */ -function I3SLayer(sceneServer, layerData, index) { - this._parent = sceneServer; - this._dataProvider = sceneServer._dataProvider; +function I3SLayer(sceneLayer, layerData, index) { + this._parent = sceneLayer; + this._dataProvider = sceneLayer._dataProvider; if (layerData.href === undefined) { // assign a default layer @@ -800,9 +827,17 @@ function I3SLayer(sceneServer, layerData, index) { if (this._dataProvider._query && this._dataProvider._query !== "") { query = `?${this._dataProvider._query}`; } - this._completeUriWithoutQuery = `${sceneServer._dataProvider._url}/${this._uri}`; + let tilesetUrl = ""; + if (`${sceneLayer._dataProvider._url}`.match(/layers\/\d/)) { + tilesetUrl = `${sceneLayer._dataProvider._url}`.replace(/\/+$/, ""); + } else { + // Add '/' to url if needed + `${this._uri}` if tileseturl not already in ../layers/[id] foramt + tilesetUrl = `${sceneLayer._dataProvider._url}` + .replace(/\/?$/, "/") + .concat(`${this._uri}`); + } + this._completeUriWithoutQuery = tilesetUrl; this._completeUri = this._completeUriWithoutQuery + query; - this._data = layerData; this._entities = {}; this._rootNode = null; @@ -1233,13 +1268,24 @@ I3SLayer.prototype._loadNodePage = function (page) { * @private */ I3SLayer.prototype._computeExtent = function () { - const layerExtent = this._data.store.extent; - this._extent = computeExtent( - layerExtent[0], - layerExtent[1], - layerExtent[2], - layerExtent[3] - ); + const layerExtent = this._data.fullExtent + ? this._data.fullExtent + : this._data.store.extent; + if (layerExtent && this._data.fullExtent) { + this._extent = computeExtent( + layerExtent.xmin, + layerExtent.ymin, + layerExtent.xmax, + layerExtent.ymax + ); + } else if (layerExtent && this._data.store.extent) { + this._extent = computeExtent( + layerExtent[0], + layerExtent[1], + layerExtent[2], + layerExtent[3] + ); + } }; /** From 6a312157916b8d2a8a0fd2a8b5266493c849601d Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Wed, 31 Aug 2022 14:43:10 -0400 Subject: [PATCH 15/57] Fix Lerc include, after https://github.com/CesiumGS/cesium/pull/10674 --- Source/Scene/I3SDataProvider.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Scene/I3SDataProvider.js b/Source/Scene/I3SDataProvider.js index 624b844dd275..ab64d612acba 100644 --- a/Source/Scene/I3SDataProvider.js +++ b/Source/Scene/I3SDataProvider.js @@ -133,7 +133,7 @@ import Ellipsoid from "../Core/Ellipsoid.js"; import Event from "../Core/Event.js"; import HeightmapEncoding from "../Core/HeightmapEncoding.js"; import I3SNode from "../Scene/I3SNode.js"; -import Lerc from "../ThirdParty/lerc.js"; +import Lerc from "lerc"; import Resource from "../Core/Resource.js"; import TaskProcessor from "../Core/TaskProcessor.js"; import WebMercatorProjection from "../Core/WebMercatorProjection.js"; From 405e5790a721c4c428c95d64636413eb5c9b85e5 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Wed, 31 Aug 2022 14:47:33 -0400 Subject: [PATCH 16/57] Fixes in ArcGISTiledElevationTerrainProvider --- Source/Core/ArcGISTiledElevationTerrainProvider.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Source/Core/ArcGISTiledElevationTerrainProvider.js b/Source/Core/ArcGISTiledElevationTerrainProvider.js index a0715d279802..5d20a5b5eb26 100644 --- a/Source/Core/ArcGISTiledElevationTerrainProvider.js +++ b/Source/Core/ArcGISTiledElevationTerrainProvider.js @@ -115,8 +115,8 @@ function ArcGISTiledElevationTerrainProvider(options) { ); that._tilingScheme = new GeographicTilingScheme(tilingSchemeOptions); } else if (wkid === 3857) { - //clamp extent to EPSG 3857 bounds - const epsg3857Bounds = Math.PI * ellipsoid._maximumRadius; + // Clamp extent to EPSG 3857 bounds + const epsg3857Bounds = Math.PI * ellipsoid.maximumRadius; if (metadata.extent.xmax > epsg3857Bounds) { metadata.extent.xmax = epsg3857Bounds; } @@ -126,7 +126,7 @@ function ArcGISTiledElevationTerrainProvider(options) { if (metadata.extent.xmin < -epsg3857Bounds) { metadata.extent.xmin = -epsg3857Bounds; } - if (metadata.extent.ymin < epsg3857Bounds) { + if (metadata.extent.ymin < -epsg3857Bounds) { metadata.extent.ymin = -epsg3857Bounds; } @@ -188,7 +188,7 @@ function ArcGISTiledElevationTerrainProvider(options) { ); } - if (metadata.minValues && metadata.maxValues) { + if (defined(metadata.minValues) && defined(metadata.maxValues)) { that._terrainDataStructure = { elementMultiplier: 1.0, lowestEncodedHeight: metadata.minValues[0], From 65c9803a207353197db867c2ccdf4dfcdf8b4a84 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Wed, 31 Aug 2022 15:16:20 -0400 Subject: [PATCH 17/57] Clean up I3S 3D Object Layer sandcastle --- .../gallery/I3S 3D Object Layer.html | 45 +++++++++---------- 1 file changed, 20 insertions(+), 25 deletions(-) diff --git a/Apps/Sandcastle/gallery/I3S 3D Object Layer.html b/Apps/Sandcastle/gallery/I3S 3D Object Layer.html index 404e3cf83bab..64a884630a55 100644 --- a/Apps/Sandcastle/gallery/I3S 3D Object Layer.html +++ b/Apps/Sandcastle/gallery/I3S 3D Object Layer.html @@ -39,25 +39,25 @@

Loading...

"use strict"; //Sandcastle_Begin const viewer = new Cesium.Viewer("cesiumContainer", { - terrainProvider: new Cesium.createWorldTerrain({}), + terrainProvider: new Cesium.createWorldTerrain(), animation: false, timeline: false, }); - //More datasets to tour can be added here... - //I3SDataProvider.loadurl api supports loading a single Indexed 3D Scene (I3S) layer (./SceneServer/layers/) or a collection of scene layers (./SceneServer) from a scene Scene SceneServer. + // More datasets to tour can be added here... + // I3SDataProvider.loadUrl api supports loading a single Indexed 3D Scene (I3S) layer (./SceneServer/layers/) or a collection of scene layers (./SceneServer) from a SceneServer. const tours = { "San Francisco": "https://tiles.arcgis.com/tiles/z2tnIkrLQ2BRzr6P/arcgis/rest/services/SanFrancisco_3DObjects_1_7/SceneServer/layers/0", }; - // Initialize a terrain provider which provides geoid conversion between gravity releated (typically I3S datasets) and ellipsoidiaL based - // height systems (cesium world terrain). + // Initialize a terrain provider which provides geoid conversion between gravity related (typically I3S datasets) and ellipsoidal based + // height systems (Cesium World Terrain). // If this is not specified, or the URL is invalid no geoid conversion will be applied. - // The source Data used in this transcoding service was compiled from https://earth-info.nga.mil/#tab_wgs84-data and is based on EGM2008 Gravity Model + // The source data used in this transcoding service was compiled from https://earth-info.nga.mil/#tab_wgs84-data and is based on EGM2008 Gravity Model const geoidService = new Cesium.ArcGISTiledElevationTerrainProvider({ url: "https://tiles.arcgis.com/tiles/z2tnIkrLQ2BRzr6P/arcgis/rest/services/EGM2008/ImageServer", }); - // Create i3s and cesiumTileset options to pass optional parameters useful for debuging and visualizing + // Create i3s and Cesium3DTileset options to pass optional parameters useful for debugging and visualizing const i3sOptions = { traceFetches: false, // for tracing I3S fetches autoCenterCameraOnStart: true, // auto center to the location of the i3s @@ -69,7 +69,7 @@

Loading...

skipLevelOfDetail: true, debugShowBoundingVolume: false, }; - //Create I3S data provider. autoCenterCameraOnStart i3s option autocenters the camera to the I3S layers' extent + // Create I3S data provider const i3sProvider = new Cesium.I3SDataProvider( "", viewer.scene, @@ -77,12 +77,12 @@

Loading...

cesiumTilesetOptions ); i3sProvider.camera = viewer.camera; // for debug - i3sProvider.loadUrl(tours["San Francisco"]).then(function () {}); - //add the i3s layer provider as a primitive data type + i3sProvider.loadUrl(tours["San Francisco"]); + // Add the i3s layer provider as a primitive data type viewer.scene.primitives.add(i3sProvider); // An entity object which will hold info about the currently selected feature for infobox display const selectedEntity = new Cesium.Entity(); - // Silhouette a feature on selection and show metadata in the InfoBox. + // Show metadata in the InfoBox. viewer.screenSpaceEventHandler.setInputAction(function onLeftClick( movement ) { @@ -95,9 +95,8 @@

Loading...

const pickedPosition = viewer.scene.pickPosition(movement.position); if ( - pickedFeature && - pickedFeature.content && - pickedFeature.content.tile.i3sNode + Cesium.defined(pickedFeature.content) && + Cesium.defined(pickedFeature.content.tile.i3sNode) ) { const i3sNode = pickedFeature.content.tile.i3sNode; i3sNode.loadFields().then(function () { @@ -109,7 +108,7 @@

Loading...

pickedPosition.z ); let description = "No attributes"; - let name = ""; + let name; if ( location.index !== -1 && geometry.customAttributes["feature-index"] @@ -121,19 +120,15 @@

Loading...

description = ''; for (const fieldName in i3sNode.fields) { - if (fieldName) { + if (i3sNode.fields.hasOwnProperty(fieldName)) { const field = i3sNode.fields[fieldName]; - description += - ``; + description += ``; console.log( - `${field.name}` + - `: ` + - `${field.values[featureIndex]}` + `${field.name}: ${field.values[featureIndex]}` ); if ( - (!name || 0 === name.length) && + !Cesium.defined(name) && isNameProperty(field.name) ) { name = field.values[featureIndex]; @@ -143,7 +138,7 @@

Loading...

description += `
` + `${field.name}` + ``; - description += - `${field.values[featureIndex]}` + `
${field.name}`; + description += `${field.values[featureIndex]}
`; } } - if (!name || 0 === name.length) { + if (!Cesium.defined(name)) { name = "unknown"; } selectedEntity.name = name; From 2f3324e284db44cccc1cd5b1e9d7193c790bd75e Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Wed, 31 Aug 2022 15:16:38 -0400 Subject: [PATCH 18/57] Update thumbnail dimensions to 225x150 --- .../gallery/I3S Feature Picking.jpg | Bin 19393 -> 15763 bytes .../gallery/I3S IntegratedMesh Layer.jpg | Bin 15995 -> 13818 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/Apps/Sandcastle/gallery/I3S Feature Picking.jpg b/Apps/Sandcastle/gallery/I3S Feature Picking.jpg index 30e841384a6f98f6ae991542cfd8ffd696284efb..d7c4cfeabdfd220e24b5aecf9ad1e086fe9a298e 100644 GIT binary patch literal 15763 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 YzoFaCK66kAl(FO1iYw+G$BKXd+35yJ$p8QV diff --git a/Apps/Sandcastle/gallery/I3S IntegratedMesh Layer.jpg b/Apps/Sandcastle/gallery/I3S IntegratedMesh Layer.jpg index 5c73ec0467e6f00a14b31feb7513b38bb37392cb..682f972ca75a170420e302b774706acb9607e6be 100644 GIT binary patch literal 13818 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 BXvoDt*hUVi* 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 j^qA1uL`+TV|N6iOLkB Date: Wed, 31 Aug 2022 15:52:18 -0400 Subject: [PATCH 19/57] Tweak sandcastle labels --- Apps/Sandcastle/gallery/I3S 3D Object Layer.html | 2 +- Apps/Sandcastle/gallery/I3S Feature Picking.html | 2 +- Apps/Sandcastle/gallery/I3S IntegratedMesh Layer.html | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Apps/Sandcastle/gallery/I3S 3D Object Layer.html b/Apps/Sandcastle/gallery/I3S 3D Object Layer.html index 64a884630a55..81e30ba42c89 100644 --- a/Apps/Sandcastle/gallery/I3S 3D Object Layer.html +++ b/Apps/Sandcastle/gallery/I3S 3D Object Layer.html @@ -11,7 +11,7 @@ name="description" content="Add I3S 3D Object Layer from a service." /> - + I3S Consumption Cesium Demo