From adea4beecc288d12bbee02ab7916841cff04e28f Mon Sep 17 00:00:00 2001 From: Gian Marco Di Francesco Date: Wed, 15 Nov 2023 03:08:57 +0100 Subject: [PATCH 1/6] Working on skeleton to support next features --- .../sceneview_flutter/ARSceneViewConfig.kt | 12 ++ .../sceneview/sceneview_flutter/Convert.kt | 39 ++++ .../SceneViewAugmentedImage.kt | 8 + .../sceneview_flutter/SceneViewBuilder.kt | 35 ++++ .../sceneview_flutter/SceneViewFactory.kt | 24 ++- .../sceneview_flutter/SceneViewWrapper.kt | 12 +- example/assets/augmentedimages/qrcode.png | Bin 0 -> 7818 bytes example/assets/augmentedimages/rabbit.jpg | Bin 0 -> 42360 bytes example/lib/main.dart | 18 +- example/pubspec.lock | 20 +- example/pubspec.yaml | 1 + lib/augmented_image.dart | 16 ++ lib/augmented_image.freezed.dart | 171 ++++++++++++++++++ lib/augmented_image.g.dart | 21 +++ lib/light_estimation_mode.dart | 17 ++ lib/scene_view.dart | 22 ++- pubspec.yaml | 7 +- 17 files changed, 409 insertions(+), 14 deletions(-) create mode 100644 android/src/main/kotlin/io/github/sceneview/sceneview_flutter/ARSceneViewConfig.kt create mode 100644 android/src/main/kotlin/io/github/sceneview/sceneview_flutter/Convert.kt create mode 100644 android/src/main/kotlin/io/github/sceneview/sceneview_flutter/SceneViewAugmentedImage.kt create mode 100644 android/src/main/kotlin/io/github/sceneview/sceneview_flutter/SceneViewBuilder.kt create mode 100644 example/assets/augmentedimages/qrcode.png create mode 100644 example/assets/augmentedimages/rabbit.jpg create mode 100644 lib/augmented_image.dart create mode 100644 lib/augmented_image.freezed.dart create mode 100644 lib/augmented_image.g.dart create mode 100644 lib/light_estimation_mode.dart diff --git a/android/src/main/kotlin/io/github/sceneview/sceneview_flutter/ARSceneViewConfig.kt b/android/src/main/kotlin/io/github/sceneview/sceneview_flutter/ARSceneViewConfig.kt new file mode 100644 index 0000000..6ad7b83 --- /dev/null +++ b/android/src/main/kotlin/io/github/sceneview/sceneview_flutter/ARSceneViewConfig.kt @@ -0,0 +1,12 @@ +package io.github.sceneview.sceneview_flutter + +import com.google.ar.core.Config + +class ARSceneViewConfig { + + var augmentedImages = listOf() + + var lightEstimationMode = Config.LightEstimationMode.DISABLED + + +} \ No newline at end of file diff --git a/android/src/main/kotlin/io/github/sceneview/sceneview_flutter/Convert.kt b/android/src/main/kotlin/io/github/sceneview/sceneview_flutter/Convert.kt new file mode 100644 index 0000000..7e5c085 --- /dev/null +++ b/android/src/main/kotlin/io/github/sceneview/sceneview_flutter/Convert.kt @@ -0,0 +1,39 @@ +package io.github.sceneview.sceneview_flutter + +import android.content.Context +import android.graphics.BitmapFactory +import android.util.Log + +class Convert { + + companion object { + fun toAugmentedImages( + context: Context, + list: List> + ): List { + val output = mutableListOf() + try { + list.forEach { map -> + if (map.containsKey("location") && map.containsKey("name")) { + val a = SceneViewAugmentedImage( + map["name"] as String, + context.assets.open( + Utils.getFlutterAssetKey( + context, + map["location"] as String, + ) + ) + .use(BitmapFactory::decodeStream) + ) + + output.add(a) + } + } + } catch (ex: Exception) { + Log.e("Convert.toAugmentedImages", ex.toString()); + } + return output + } + } + +} \ No newline at end of file diff --git a/android/src/main/kotlin/io/github/sceneview/sceneview_flutter/SceneViewAugmentedImage.kt b/android/src/main/kotlin/io/github/sceneview/sceneview_flutter/SceneViewAugmentedImage.kt new file mode 100644 index 0000000..45e3f56 --- /dev/null +++ b/android/src/main/kotlin/io/github/sceneview/sceneview_flutter/SceneViewAugmentedImage.kt @@ -0,0 +1,8 @@ +package io.github.sceneview.sceneview_flutter + +import android.graphics.Bitmap + +class SceneViewAugmentedImage( + val name: String, + val bitmap: Bitmap, +) \ No newline at end of file diff --git a/android/src/main/kotlin/io/github/sceneview/sceneview_flutter/SceneViewBuilder.kt b/android/src/main/kotlin/io/github/sceneview/sceneview_flutter/SceneViewBuilder.kt new file mode 100644 index 0000000..7ce7e09 --- /dev/null +++ b/android/src/main/kotlin/io/github/sceneview/sceneview_flutter/SceneViewBuilder.kt @@ -0,0 +1,35 @@ +package io.github.sceneview.sceneview_flutter + +import android.app.Activity +import android.content.Context +import androidx.lifecycle.Lifecycle +import com.google.ar.core.Config.LightEstimationMode +import io.flutter.plugin.common.BinaryMessenger + +class SceneViewBuilder { + + + var config = ARSceneViewConfig() + + fun build( + context: Context, + activity: Activity, + messenger: BinaryMessenger, + lifecycle: Lifecycle, + viewId: Int + ): SceneViewWrapper { + val controller = + SceneViewWrapper(context, activity, lifecycle, messenger, viewId, config); + //controller.init() + //controller.setMyLocationEnabled(myLocationEnabled) + return controller + } + + fun setAugmentedImages(images: List) { + config.augmentedImages = images + } + + fun setLightEstimationMode(lightEstimationMode: LightEstimationMode) { + config.lightEstimationMode = lightEstimationMode + } +} \ No newline at end of file diff --git a/android/src/main/kotlin/io/github/sceneview/sceneview_flutter/SceneViewFactory.kt b/android/src/main/kotlin/io/github/sceneview/sceneview_flutter/SceneViewFactory.kt index 766621d..83ebc34 100644 --- a/android/src/main/kotlin/io/github/sceneview/sceneview_flutter/SceneViewFactory.kt +++ b/android/src/main/kotlin/io/github/sceneview/sceneview_flutter/SceneViewFactory.kt @@ -4,6 +4,7 @@ import android.app.Activity import android.content.Context import android.util.Log import androidx.lifecycle.Lifecycle +import com.google.ar.core.Config import io.flutter.plugin.common.BinaryMessenger import io.flutter.plugin.common.StandardMessageCodec import io.flutter.plugin.platform.PlatformView @@ -14,8 +15,27 @@ class SceneViewFactory( private val messenger: BinaryMessenger, private val lifecycle: Lifecycle, ) : PlatformViewFactory(StandardMessageCodec.INSTANCE) { - override fun create(context: Context, viewId: Int, args: Any?): PlatformView { + override fun create(context: Context, viewId: Int, params: Any?): PlatformView { Log.d("Factory", "Creating new view instance") - return SceneViewWrapper(context, activity, lifecycle, messenger, viewId); + + val p = params as Map + val builder = SceneViewBuilder() + if (p.containsKey("augmentedImages")) { + builder.config.augmentedImages = + Convert.toAugmentedImages( + context, + p["augmentedImages"] as List> + ) + } + if (p.containsKey("lightEstimationMode")) { + Log.i( + "SceneViewFactory", + "Config contains lightEstimationMode " + p["lightEstimationMode"] + ) + builder.config.lightEstimationMode = + Config.LightEstimationMode.values()[p["lightEstimationMode"] as Int] + } + return builder.build(context, activity, messenger, lifecycle, viewId); + //return SceneViewWrapper(context, activity, lifecycle, messenger, viewId); } } \ No newline at end of file diff --git a/android/src/main/kotlin/io/github/sceneview/sceneview_flutter/SceneViewWrapper.kt b/android/src/main/kotlin/io/github/sceneview/sceneview_flutter/SceneViewWrapper.kt index d10935e..521da29 100644 --- a/android/src/main/kotlin/io/github/sceneview/sceneview_flutter/SceneViewWrapper.kt +++ b/android/src/main/kotlin/io/github/sceneview/sceneview_flutter/SceneViewWrapper.kt @@ -13,6 +13,8 @@ import io.flutter.plugin.common.MethodChannel import io.flutter.plugin.common.MethodChannel.MethodCallHandler import io.flutter.plugin.platform.PlatformView import io.github.sceneview.ar.ARSceneView +import io.github.sceneview.ar.arcore.addAugmentedImage +import io.github.sceneview.ar.node.AugmentedImageNode import io.github.sceneview.model.ModelInstance import io.github.sceneview.node.ModelNode import kotlinx.coroutines.CoroutineScope @@ -25,11 +27,13 @@ class SceneViewWrapper( lifecycle: Lifecycle, messenger: BinaryMessenger, id: Int, + arConfig: ARSceneViewConfig, ) : PlatformView, MethodCallHandler { private val TAG = "SceneViewWrapper" private var sceneView: ARSceneView private val _mainScope = CoroutineScope(Dispatchers.Main) private val _channel = MethodChannel(messenger, "scene_view_$id") + val augmentedImageNodes = mutableListOf() override fun getView(): View { Log.i(TAG, "getView:") @@ -42,10 +46,16 @@ class SceneViewWrapper( init { Log.i(TAG, "init") + Log.i(TAG, "there are " + arConfig.augmentedImages.size.toString() + " augmentedImages") + sceneView = ARSceneView(context, sharedLifecycle = lifecycle) sceneView.apply { configureSession { session, config -> - config.lightEstimationMode = Config.LightEstimationMode.ENVIRONMENTAL_HDR + arConfig.augmentedImages.forEach { + config.addAugmentedImage(session, it.name, it.bitmap) + } + + config.lightEstimationMode = arConfig.lightEstimationMode config.depthMode = when (session.isDepthModeSupported(Config.DepthMode.AUTOMATIC)) { true -> Config.DepthMode.AUTOMATIC else -> Config.DepthMode.DISABLED diff --git a/example/assets/augmentedimages/qrcode.png b/example/assets/augmentedimages/qrcode.png new file mode 100644 index 0000000000000000000000000000000000000000..86d8587ef2a9f308b4c6b1db278bfb0adbfb382d GIT binary patch literal 7818 zcmeHMeNYo;9$t%9PivW4O03?QnL4Q03mL9ukdFW@SPHoYl}!frnUuUEC|b2GT!CVSVAcAVD8uu3?XZZYzPTvBl);@Lp#%H`&au< zcBXG8v+rbfcJq7R-~0TY=lPvG#L3;ddBECAe{z5SqD$AgP>PqB5Qg|wUIbC$yMc6y+lTzzI(bte>1)Kp*5d= z57nO;LUJ#%ANViPrsjPA-T*;|4)ee%dP*Cq)63J2b#>}DPqgl;4^7Mr7NxTod&G&b zw;H!;r1Lu?YwdqI6~K-_!S|ds!n4PFCR16DggQJ5l}fv{CSCOS`8leDK%9FpqspyA zxeK060=Xy~<0$j{j^QZ<26JrlJ#AH6`X4O)AeHtvY=NM*_Q;yv0RR1Xzlr7OO)4LfK89^GkG5^A-aO<|G2F4ec}2b8@4n!Y-2vj710=W16z7!BoZO+r^jsm@4~v(&fWp7cgAdW*y5*` zs~-4W6Rd(W&yH-5ZrYpZ-S?^e)HmqPRBsPK3DE2HX0ZM#{c7^N*n&0G_VkR-E7aRD zzsr9`G=yN)cxj%&EJK>VPNo9q0n9=pKH2#n#)ub>E$EoOBp<{aHke-WH+<#lY8Ey# zj@Xs5riBopErh)aHf7T8Y6+l8Ba=G@s`i`RcTkXWZPI2AUSBL9WWWZq8);P8gI24S z8fgb=E#3!`&LR>>RE=;cu2khFUdDasg0p`4$!~MMG(U@)U@%)z|66YZl^utkW;2>8N{d)QMrnA zAMob|#zFuL`&kT!vIT4pjBDT5A0D#x@NQtu_EQpXN@$EKv}fs)A4*W=GIH`8$?0rn zN2Adkn_yry>=CC=-%`We1ZvcvFgpOm^j51qto;C;16oLge^^|RefNrCILBd|0sEtr zA=Ge){wKPsy!$k%8x~Uvo?&6}K*o;N%n#)F(=de?0~~JEN~QX<#;!4!LPl%M7)K_q z-P`t-Jk7_uV2$HzbuA)pRXQbzTUe|^!IE%6?QqH}i|iv$VanLzQ6R&Yt->LMe=yoZ zDM}4opP}L5;qTc&MY89}00Wjj)68pU-<)?1^(Pow6*@OnxDKQvNx_=f1|mlW#A%Xu z8vrz3MK&`9lO}uj?eEGIip3#%8u{Kd`J+)%EFW*p6D_f3xM^g ziKmdEdabeu94$rW3?K~od;0+5jTkYYk@T2c6u>wc&HTuARM)&-1awb`mfqL;2w
    7qRUy;4twL(R1<-j=`DmEinHkC(4CZ@yRBPIq+rab&(gJWHV46^) zAc2-e=C?Deo=xX3DrLQs<62W0Nf)AHlxN{u{oC#A;2R_Ziriqxri+nmrnud1PfYa` z7>*|(h(IPmRF4Tr$+vC>3|>3QOJM_xaow};Q&EO@TS@~!4TZ{H+}=c+inH|3UN-~^ zo0eb1@(whRluu7r6KEN*yxa)0t0|<5)ON*>8Y!1aof`!~niJF@5x?EH5AoF(z{Y?c zudhgY^zAp_C4rDeb_@HrgGP-;OEv`(NCUP!k#sF3q`AyH4;23F(O-XS+;JA?#Q6Qe QAo=FJgPfeM7vK5d|F&hq(EtDd literal 0 HcmV?d00001 diff --git a/example/assets/augmentedimages/rabbit.jpg b/example/assets/augmentedimages/rabbit.jpg new file mode 100644 index 0000000000000000000000000000000000000000..25e380d57baceedf87b075d14797de507a9bf97c GIT binary patch literal 42360 zcmeFZcT|&Gw>KI^L_ttQL@DYHAWD@I5Rjq

    `MwT3|~NNl0jcAWhLN8-k#Kz&11u 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%0Aex=5~Gab~$5*;IY(Zglv1OIep19 zxu-!#R&yGj=ldN~Qo z6%r48ARQclnxEBLD?fDYq5$oK3LU zsG2P`n0hqo&KK-Mxe^ct?*(}xJP~Ka!J#e%yxZlR^^2?Z1`@E<_nuBGl16sfY2f#L>WUt4GJW8-9uw2HVPFTmbUH}`+6)tf<*XA%cRRA z0K!p)kHYl+Fyz0bxO;q~_*~KI7Wj zw^ipNc3tw^J(8;^51(NZm~`u5SEr7vN;#6&HkR=tt}$Eh^gZV}TRb;f9Jb)eRoB5g zGnyUb7Zyr+4^#Gj>7`R0G;uO9_Fq-ug?QO7N=jIK_UvnC$U*%?v>WI_Nm}!JfNzSi zXzh_tyoHP}?@$zG(;MvDxrqVBb*+R_rdQVKBCt?g5y{Lc`RcO%e&5A*gV?nyIp;R@ z69S*^ZG>Q1J?JsbnM}WFC&_QSm(@^0D>oU@a92f#8lmkHM?x97*9(&u#orerH|evB zJAe*gF${ax-v4g=gdJO3C~P~BQ&KY|H(@-Bp9wLqbzTA#dmZ{omU;gC(7Q)}w4JDi z@Fd>u`6N}7;Zs6k^wNRKb>Fm>f!eDH99#W;?#t$x3KzV)MdAuXlA2n)l zbD}F=Cj7jVfzDTr?nAkMSo0q#Z9#zQ%By8M9V7-&koQa}EQI7q#;HkA{{~V2yPDa_ z*};c+*I_n({;KP{E=x}{x1+S9nPeA-rfFxO?ScX{50_2f1sjHUK6Ypuo8KR}zO*sB zHZ!Gu3>{G8VC0oz;KHi}`Bqrqno!Dj6&^*bKE{4qn*;kC$kk$6@Z@Ncz$bXt;>4yB z_~4Z^qRZhS@&Z~%j}=h8P6NG}+udPGDZMxG$O_VQCbW}olkZvE zOPSkernc-z4!oZmm*c6{CH@I|3Ls*B{RY3Rjz9+>tEr4QG@{bPCH$AVKd63$Pc04~ zmR4pCW~dfomRN!3PUqo@(u$t>y0)mTYm#S*H-`B96COL)B^| z7pT(_(!;!1qPoVO8z8nTzEmy_H+|8qmfCJLx#>Q|Ss?4)u7G^FGZcqN+max&xN^1` z%&*9L=B*Xb$Yd}q%biu1s;6p+s&PO)W}4V$s0}a_wsqZ$3=jm>Hu!n$8YAp;^X&3YINil%GGfzrcNKhK59PR&k_WVFHgC?=yeT}=>z2@ZmGb1z&^AghrQNM}WaW1W%u`f!;8`udA{*Z$PMfA8r7 zXG55d0;^|Ew@d-!w+C_S56aq<$g)jmwM5o64|8L)Ur?WTWVKA($tPVB4JL?KH=Xyb zScWUCp6n(GXDi<9G}z|TtN3Hoa2Q25QMq#>_qKCQVj2wKsi&AS1`}PLyisQbp~=`q zDQ7c~VgYt8#Q*+#qWj6sj1aknEL>s1hVwACa#wFy%LJ?%M6i<4R@_ddcD%|+2?^TSdgp#%U5y^siw2}^pi^D zB4MCDx&V8_4Jq%pl6oeYKVrz$Mxrnfau6{Jv`kvvM<1;5So`2HFT#ttSE6QX7hY4^ z3PzEw%4mp~fk6=k1GtXSXPOUvKqlh+*&ke1_9p+T<2jXuAkrNL5+!5eOG=wNMV~b< zWWq`Vfes*ncAHWOwmoPRSZ_8+X9Br53B^>gC-1womAons(+2yekcIn3llkqR(S)=L z{Kn9a2mjI|nze{DClo5$q1@xc!oip`4esN_nn4__OZ2jSI&PZ|;dG=RSl{ftl~&D{ z72o4?<(pZe%l&H@k)njxijug}8sU{)B-Gq5bP0`U90xtE)r@>FC)b7EW`+TePkQ?K zzy8u<0VosR0W0D8GBXSv^s zNBgGN3h$aoJmwb9+H_lV3(ny8@sTYX3r%d~z;h(*FS$}?YR7Fl^r(9O%I;_4m-Pkc zThS9$a(*^qwS}URf$Yl{PJe!1>i-_PAHXW08n#EV4ICo$H~P1Y7av1;zt8nnW)IKK z{MsHcGfEC(j$>LClxekeE$Bvu`B9Ct@6AJ;Sw2Cz92~M^OWBTQ3H?luaZo*Ch2?Kh zR)&s*T{DOA^v#DaswCrS$HzPp%b>1XQ&vk?CLj*@g56UlfYSeCWOPdW@MVcZMnA@F6}iHpyl-0kDxZS=#ks zUNou2Oy|6*l2>*?4aw_sch7y5!|ZIQ+t2&HR~K}2skjlp6&4%GpHe!`J7W~ybn3`~ zMXOKEtU?$4Q!-Y*`r|0kn9(@TqFj`}o(7$x{dtwnwL>CxThH7_Cm58A7w}a!$+ERq=k9bdq$H)`E1Ux~o?>}J&b|R) zKfVjzSCjQ6@O`aVmvu8R%^rI1{hKToGIB7jJ#tVO&{9f!0}jDC!^1VMBI--L&Z}>C zWOz_qd0Sg4+OPcAkonmk6$muK8)9?{+rF$}`s{wl?{QCzH0+}m(JQmx-4Dk7rGw>A zGgE{4{r1pv$TQ&jwuZO;d3dJnYw@CiR*Z?JEWpZMz7U?5nM0^6J4DzDT5~ZUlpb#m zZIwvjeZ$y#?$!$D6Ffa(LDLa&1})C&&m>#M*nG@L&?6_KJCJV~3O!JTS{^XE3nV60 z#<DA^XetR3#K-lr?0GePI3?lBld!OczKjjuIcCGZQBc<5z%`9CEmOZN? zC$uGaNeiq!z@1-JGM%ORz#8K5g}rOX!n3^R(8-d$Ke!5$Ukw!2t{XnOL0(*&t!D6` zib_2r5BoMc@Mnz=o)!Xn6*($qI}3WFKdlj|{_EUZv)1zP zCuS$`WhPvjT9(jX><~UQHX2R{8yX`xc~b0rFFzKJMJc8fmKk)_Unocv6AXWMC9JBa zD~L5_$Rs~JRasa6oo;Q3S(2ze*vk2NM$($0_V5(tCia7|EFRA9P;SlrL}dYTe8I;- zMGH12_VNBuFCfaDL~`+h#HWET>{xz$Bn+wC{pb}ruiZ)&DM)a6pe-7gi!VvIQt=x!)&R1(z}?A34BJODyS43_Rh=;vG?+h zBCmFx!u%=r>Cti3nSShIw0Py*+nX`CF#VV=DHF`hjzaJ1%OkHzw#XDMe?fGcS7F6K zNr##+4^s42#4k=>(ncbD9B{4U`E&Yz>U-|rt6~SY13u0bj(TR*+u~zvT~qqaaL>zo zTe86yE)ioUeQzH%*SwYIcZC#6Oy7Jx)k}(N=-mj~vdOA($4b$J-6i$YUP)ScA3A zuccdiFLkE>$n_toG5ztwZB;fN>sx1$sjXnK$w5sy*IYt`=5zb#bXiT#rwMV1yRa&j z)juFUQHv{_t?*M5iFI5B`Zc-{0*}wyku(pCAc&)a+Bj2px?C z03+BtR&9;%#^9w(9ycOr}m%pC+A9T5+ zOjrbe>bI!}0L{hFn25*ydjDq5zC0alMvP+E&}m=&{+$8dI&^M>0^11wX-Q%$Em`#& zG2`~QLwWppslbjL1mne=v<($(#YTpYIiHA(1B2qHkseFwQS*MpkllB;{)ad1Voa5H zuojkj8*?D$QnfPw7N9IwNSE!1l|#Y%G*hVdiFeN|$)%=K$s4wH1zhcS|INmqkD?lU z=H}*tf~r;yCofIS$S}IIYS(36bwHo419>w`=S|bDRtKd2ircU7^JC$X3su&%0aQ$Yg@&|=gi4+d+U6D zTf|W}A=Sh;K!Gde*JI3ktd*gi6WP={Fy}0&7_W3v&F|kz`p2Vy#Zcc)*2LiRy2)jp zv4@GZcNNd}TeX@G1bTJiX6oJt^n`tmf1~@F_sxI%f9|v4*g=|cO<1+HOV800*tRwA zrC~N!#6Jby-%;=-jV6uXCg(S0%ZW#QK02~Stc92js(0-lJbEPi)oEN$m-9-jy`Xh^ ziA3>JOAad25I4CvGH$TAZ%*hqq<9)NAu70ro9?v+f-Hqi1!5hX(#LP> z6#dPh!@{|ukN$2N_Y3v%Uz7Oz*`3Ybd{hvJE(H;v&NiPrC`$BV>0Ru-o0mBI(nf)8 zkM)Ech5LcW@P*06d;a@2Dr?G@>=0t*4?r!*SFda(?(gc$$M0P{7xdq1%*7=D88#vV zX9s)BV_<2+VEp{n=A+6J&I-O-#_U}JP-sK*Y50VJ)!qZO%>S0b;X-t_aFKK&HY%yh zs$m*;K14EoO&ZjvC>EC{D36YwWj0_1gluy#Nk(Iwv_d60ix=@(hGVE(Nd|;pR zqo0)t^*i|MgShQXlx%?C!`U~2 z@C_ci-!4$38>RWgRCaxlUqaMN|gm?pcLK%j&*<&Ia^zDMMLAiY@CEPvY8R!wT&Z&iFUO zq6?3>B!RYU4UK{AcBRb~J1K?XlKtYNw~&zt;4re-X`aL}bVf4YbE zR0s&RZ&)qWg%xF=x7NKIm?6nt4ty}!)sUa4`Pr2kU-^~P($jOhsYcMROorFuCcPQG z@tredq285Ozs!vEk-vST9I`8|CJ*y|=(nh+o0k<5Vp$^_gLu*GqtS=|HmZJA?@_PB zdTZA;RG}L2)%b={Sy(wZc33nZ#8@@r@cnP*S|O=}=StrhxM9>ie{lWa%B@Olz8l~{*}UP1*2`QN{${6N0BG$J>NC1e{5wB6$jQFNESn{ z^NG3>9xejBA2z+B}*AlcF9|TTvG=8d!r@qIVfAM=HI^X z&$Z#$C1z|wzl;}yelJcPsL*w!_WH3~qf^@57Axp5e6GN&5A&DYJ}YZ+-pLB7>B1Nd z4fKuMjl^Z{Py{#>0(Bv?_O0IVDTu@g?xwwt2Yt;A`}cPK`?HQX$d=)${*t3zs4|Si zV?(ylPZv1DAQGTLBQKa=VG_e&(4i?ML2#l0?BPwJl7W^373@eRgRkZYvYstaqth-O zEq#BBYXenPt7Tq#he@@bne3VSt-FVMKMgDc23roKZ~9e~t^%+0_B9ae^nSGr4ZA-H z>FM7NWu(-L$3-7&Rouv5yYP~M9qvr`1*t$#jFAIS&l9NKo_Xih#PMxLLZhiIVHs(i zo=i5EP|V@EKiw%U5%%tG{UC3PUp9iWU&01JcZOzJ({^j`o_)A~L53zqPD$R#UUyNg zScx+-^1zpbg$Ym7rnlfkHFb$?!CVu=_aW`WSl2hD>w-@{2EGA%SWaCnKXsHWfeT+k zp#~?6*$$UJuLC#~m*T;}%AsPjXKIXNWzQ5F5ecgJyoZg)(&436zl|IJnoui`GQWN7 z-yM$o3ahz3ZTFT38SWy#7?Cx0siZa0h%DPA3HA0otk)Shi&<2oqQ4M@U-tp;%iiq_ zAXV1j=fzyb(r(w{8o`niAIs0=1$MyPS2ye%mpo$4P8n9)*0Kd}1XYQx>NW-Fq|iy* z%;f&KUcJfpuFYo%*|SbD5f{P~>F`SfnQ25Q{}at5!^h6WJl>bHzBZI>Mh-jo?39br z3==J23d?HNiddp)hmf^~hMe8=qUx{5r@4M9S#_D`R>M&08F+1r`V_AV3nWU5kK{;2 z*Ge-~cDiVMmR>5cF*9=hL|IAn!w0(!$hF~aOIoOpEb)skKz{~-w4U%NCvBn87w}y| z=X&?C1Qu@q4_K{yFB1+4P`mo8qoi$*GG7UtL;BPQ{tK< zA?NBpc8XlQbS1IvfI@`BhIi4~zkay?a|Rp()v3Q$X1^Tb8NAgD;w|T??Uw%n;}d+v z@AsV}RkJU?uSBJHB4|SP%4geF(gkB-Y1(EINwupHrdM7UlAV7h2PvR9kcE$nxiTcH zd6k7oZk>e6A5B23&m_leH+NjkzgVqNV;OPL`lB`z+04=T$dhST(IWe&4!N?TR!@ZK zXIW@{i%2=Nqg*QV#3{BYYUI()XP)*+(5b456&uA#D|J`zquEi8krPMTL$FZ;iltxC zloq;%{1^B}9+>O30&a!KKIeJ=jsMgM&aq6Z6NdqpW?(rUYafH7zIru#Ou=AHLu~DF zMQK1OLtoV))#$BA$g3=EF`hW69h*;ev*%zy?XLfRJBoT2PX%v%esV=s0K1kZ=P*9_ z-6ZWIv$rad!&6JiYKmzDDd>Ft6-CQ2*&Xh~Y0kAGaV~@G+I%BZyNU<$ff=DdAb3>( z2?;LN?}dEAW7GjfhZ5$_d23-9)`3Rp?3yh^=wegPure!imBJh`*kH|G>S*ef&dgc{ z1d+`3DCgkX-FL%!ku_?FXbpwbmV-z|w`Xki6LtdY@r=W<+F^W`Ahg%?F-ThUX5c^V-@=LesGPIAy=2oUKg~o zfnnfmBc%hOrJb_M@)71WAs>&G^_{~?6%%VAg_7SYZE<;6*BJIR*m^hm zk<|~b?vG)XA=__S?vky;%r23!J96=IVsw!~lL^~q=&lC6c};>_RwouJBrZqNu-+If z+ERih-64Mtfl!Q8Iy^&2Ep5RU=wc?d-A;PxmpSdkjpL$oZikN^)UGiJ7~J+6IzHW1 zmcwFi{{CtcSfDyGJ#W6Z+Pv0nbdmr1^eU}%5=LeQ?BU*=^B-W0uM|hYzK0$BxZS3i z2YD}Erg{Ya&$j=|og+;AgRAy>e1LZyef87@j~o#w2b&0Vcu*9X5WC&utaquv018GH z7ZBeRg(>(SD9%pA0a&3v4MmgM_>mGTI#&~l1D=km6N&No@D}f4Z<9We?1^HoYs}Im zX;XN7+Q3RF4L_dHx}`^wans4sf(@_s^X)>1dm0h)8y;=>rg%gCi{(u(366{ge+gjF`Jl2}B%rX#khW=N$gL zTIZ{J3`$$0Dx?4Tsns1Z>5iy$z7$|FVAA zgRnsOs8Th=AB;hzX_e5gbeP0#+Vu<9d)C$)oj;c<)VP3(&Aj8CE>uhTwT-B{a8|xSy+!7Z&cw#li{<7qgt34HC5BkUICY^$-Vp9ZW6m$IG{LQ^c^|@w{YC9 zy7hY3Dp5m3AW$GM?Ybn1!?XoD^{%}mpZc3#;=lI{7Z+c`%(pO8Be%1>C$bZCFEq`O zS7KnN^_{=qtkFTNmFZmY(UcT48GY2`W{x=+Ect68CKfl|+Ma#{#c-I7 z8E%Me8{>eci|Cx7D>LqhE2c5&(OYLSyF;xj?W)b+&ThBq+y^WgKK}J)dRX=^e{(b) z#<@EdMc_@07a`1zWXy5po%@xO%x{don5`d%{WA zRrpn4|KxdomB0G8IF0c>>x;wL*nrtuA*$3(==CSVqP;kDCZi-simz?1X zw|nwn5h@YdTO7$SSnmcKo!hyq^hESl^u;n-4<SryoaaU)c5xXL;E%YF;u@?9|<8vdE>I!-H1CuDz@~ z>B~zZ#dwUA;p}G~BToLuENiO3jzetRmwKy&vG(1aA(RHVkZq8`1ww~5h2vVZdjJ!*D$L4eScQ(KQ4ErB6axYLV(u2=x;%% zjojZ_kF76!kvki-oWW~qk$v+{L+$Ar+Y%eWAd+5e_}5(}^>|NI$|sc)cDW6X^GSR# zv=?W)jr5iMRzkJBzrrVPhLF7ime=kGkz`v>4RQr8wpjaV;bgi*Z>hd*ET z$XRN6MoOo;FPzk}$1Ll6RDABxChSw)mA(?CEWW7Rsx>nRyq?|fnPO6D9)8yT<*psC z)!OD;bv=dnPk&}X9S7{5cd}au4#@Dhp986c#>aj8+K8@NhQ?}N*;wj`57F>nKQEk@ z6p|TW^WxT4uzI)_vlN%d0dJ9+a%ni!g}9tI!w9=vKS)Srz}?=JLP&yk9UqxFl3EHi3OK*q3dnYP{A;J1~$4_3Js+V83#CfQMV`ga@NeD=AvM~Lh) z_rSyzW*4SMC8^=ifsN(@W`=tc#S@K1zl6^n_9|dz&y%fN1_X$2NcN2IYQu%(SL8^| zlJ>wVoEjQM=b|PqjU2Sybt>zw4K#o^N~U$*h#a-Ltf}pfkS(4z493|#i1vTe**ev)D?aFNs5+)t|FhN_gjI0WQ*(o}2e zhUeo}S4z`-PB9g@P1AH9jrrb!C1i&9J8)fs1<@u&YAV6Ks3$m;Yhw=d|D&Rp43@;wn=|h;9-)PHpAwLW8HHYW-Cxcu)Ua~pKkQPP(8Xf>#8wE&AsmT zUtHNYXvf+~+MRFOy|eL8(@7#m>5lOsWRa{IyXZ-jhJW1H<>K`w8-1-CYuB@a=9tbTbfgyvDq#9wsQ1qe>SPI7W+(X%6=NKNaHmSTh;C zsx=AotvEhF1p1@YmDF_O&3aDw1d<@q?Q>8{AHNy1D;QxFIK6Ko_h%LV zejSn-g3U5Yet%EMcuJ)nP!l;w5`0S<7Bq0)k}XS55!fh5-g1uxk^IcgTT`g<;MkSH zt(e$nh#8lD<{^mAMW|PZ!V*D{&IgyX1V6RoXvOfV?)vXjy@zR8j`^5!##|dOR?$Wy z+20p42y21rfcy#)aQ%`yZ9~rTAxhD|z1WY^1mtTZw57g;vrEA&YUkorQBGFXc zqg`G8c!>Qu@kd67d}r@dx&;AN3{-h)b>ziWB%op0<{&ju)+aFel$Ex~+H~$+sApTebzh}6N!#3CsvTzglQ;WH+XMwi^&zFKy!6SoTa!3h zB?0@p`kp%z8Q`pW!7inNYqH-cS9e#faaAXZi=uJDn$3bRS#x7yfQ2BKUg85Evb%jk z`^CISfR^89$G*C13G_sTL)P`V6+Tg*963bTBHTeRo)!RjF{bj!KV1 zy5IgM3Um`FPy7%CksGoh%JwBadj4 zw5_^4*>}16pn^!r9mtUD;O8~!-TF(4sm+-j@=C=@ymOfSM51Bt>8U?W@Ev-x---YU zOixlvkBb&+2b)VwbCjZSQ`DB~1U`;pch|6+f9m{nVB&YX*56++JbuHWJcsV|P299d zS6|T~u?;#pyX8WXTbpT|7>FX*`yLh|1NImf)e*sNB@JtYg598r$c%Le&#W*_zQ)sF z>HaE^Z@^!7aXM44)UanRpwpuUEuz?m+|4+rRsOt;2by+i0djQ_S0Ob zt{M?zkm@?cYRfy`)gg%{jQXJxmgwypr{Gf>kj9_mY>~5CfU1VA2HS^tVuz3Qo3yl5 zFismwX(@IS?hpgr6Tn#cB+6)^(`KHeRY1DGRywzk+N_#G=AfGknjYA?a0O><@#<~6 zgT;i;PW!a+x%yD6;|n%#MJBwWq>BecKV-{DXo$}OT%%*gKAhM5#Bieu%yyYmHZSnZ zC3fAR?<$`^XZx{iyCw;8!}BdN($c>qE5tE^2WUG}O)U@rZEeZ|ARxM2}Og=*`K=U;EuvDHH=tSuTb(SU)b@m=d8 zfeIRPVKe)>Up3Pe$AJrpIj{318^)!CM5i%Xt_kJ<9~=%>bQVwEo1#ETI~{2Oj6wP< zI*e~Th-6D$n=R0Sfa8J^f;4oG#crfmrvlA?YaOI)r3QSj6A(}ps#?(kXV;Vbsk0Z&Hy=U_~2JgV3_1rp&L$hM9+hP(qBtUw15DPpnW(aDBr&S~2}@(PiI!60q;z zmd0Bl09HLlIDmF8M2Y&)68VE`xLNVEXu#-h5u+v9?^a{6r3Q(ltXPx-vCmS`!m83s zS19cLHy?-*nC!utS~{iDWmdEL^Q!+1h3+LQoCXuz*@`js?}gcocON;!h&P&_=D7Co zt#I$Q^86I(W)V++vtk@Qn6zk4HcJMf9`)Pm-q?=lQuY*CHyfyj2V+65C=QM=E_$09@_p>-u?eiivD%TVXVf5bY}Ob3;M@Si<+3n0f-7YHs=N z_^1D}sQmpWZyYz+%bL^t=t08Bw|C!XRtODho++kUg;Be=TLZJ2wx48-;OY_gz-y&? zOkD%*w3N6mn$~w;jPI$rjqmc-MgUd6%Niue5n+7e)RJvK5K2`dw4vjKd#Uf%z&|6-k zImt4ux`4C-mzG^i?HKn(7J5&i`a0gNByCtkVI|J0Lwsd)eecjVor@&i<3aBAAn2OL zBEtm{pA`J&4C|&x;2_QE^xEKFn+v|l08Ui$=Vx!85Ofk-Ru)@BUpxQ@sKO;Jnd#gu ze`}U+LEP5u)@GBCfIzfDe#5*%(R*^(k|kJPu}2m^R}=-2z&-HMd!C>kuNqi!EN^<9 zacUwhd7-kptgqkuv$9nANP?BJXiG-%oHgA_(k>GU;-T>c%VpOp?Ds&uTgm>YZdS<7 z@eni+ADZ%y-b&xmalSJbcs3I^rIGau-k2j7fH%K`->`fwI67E@ZeKG3P?2ksce3JK z0ve1|*QQKGo2Fe2?i6?=XtZ(OUe+7ahi|@lQ{^x_t)R9QQdQqcHnbREch80CFL9KG z5^jk2bJmjS=g$vJuEnzWAc2JxH`v!U03k7;3_VB+*bI@)r0e^HyI9pbDD6c9dIuJ` zWS(&`?068$TXs;-?{fC%?&ZPNnOVk@cQGyj-MxKSI5}<@#C*` zL|*G{c@PE4$6wy8kL_Si=W;akCZi@x2g2UneS2TJTD=e*`WqdZtHm$WuXNDHGOHiBY6Tqv2 z{wGF@ng-%f%-VK$A@jbP+(tE#`-@qadQxKVGe(-p))YxZ3T8=4EX3)9>QxE8^lN~w zMDCPF|C+7`Y(whEfdNl#r&pKKl5JA*%xvZ3^?Mh-{$je)5OyJKnk_^3MG=67W0(c& zNY9*(qSHUP_Am8-jSP&zHY&Iq#ypeBmhVeRcC@gjgMt*4DuEf+!@M;^4wjuMW5$j7 zEZgG<*4luup%jr~BsQ%RH4yLd z(CIdB=|61Te+$K)w}>Z)J!x7Bfs>6gor}Zby{75m>X2?_b2uNsfQ4}_<8V+04jPcR z54BQqe3Y5zM5Cb%uxwbG=`kW0m8D?1nvRC}vK-~|LpTs=+I$0BTB8&Se#*92;)fzX zm2_CNTEw#affg7T&~%rO0`~1?@8?}F83bgI6f!F(K{pZQh*1tDAmCC^0kHmaZyP5r zT(K(ad-J+_#>=A-%z5au8b%Dj-;W|X>%G2jw$ThbGmC)diSvYOS0T}Zx44N^B;KBCg0`|{6U z+yCmdKP3)ZeJ+2Y>m?7M>i8!yysw1})rY}GS>|G@^7=qOBW+7;>qu06=*eGH=7;I? zf{{}ZoWK=17h)6Bs{#m@@q9(tPCOz%fzN*`zYMct)BNuGntJTK^qSjO8+ieByX^UHHa~(qsC(kQ<{8ky~KfvJE%@*|H6o z?ZzY0NGDkOlSK`jgB+YT*;8J`vri7~bUyXASqo0&cjfMm?uMFMiI;5Sod&RNqS4Wh!sVm0ZL1D0B_6_0VJY49{Fdp| z?;1r>>qP>pC77ZL2U!;0(a&G4Wm5wGlquT#QI^EET0@g=^Ry!mu04|(h^=TUAsS!w zx^_V>T#=VxWE6GEffal$$0!Kn+V)Cfs) z0QmML4)m9QPs-hJG<$BJ~-JKN7^cC_0of?H}YK`h_G=KIvTZUzIYz>!j$y3Gy~ z(+}BI;1_f%s-!qHUP<6EFFDMQD6XvYf9kCNNd8o1@i}~_`HG$H@jU-CuXBdKn=S_WM@@3bg@B)w5g*4_Cml<7VgJF0H?lW2+ zXH6>u$#iIdsprMZGzjLF#_1a}lVDamL@izMxg<=R@bZ_`LEy|_)WTxJWTLf3=m!diR(T3Z0|o`0Q}d<;0D_fyl7=+fkilv#(adfPHdbBP#yw2`o6 z7)RgoFK{334bOlrEmCbQ-5Z01)lLBwq{8H*@E0Oo@fVzu>P3GtbL!V<# zl%Ug;c}V)b+BMa9NHd;nvTChNoajAN3uUwk0>+CDL~ey|Wqd`Ja74VbKJ=K)8TRya zenUmZ`n6NuH^1_qgUe;{YpCecZ8JsFrE~8no&2&`^PY4)^n(iC-o_2@Xv`Z%z{#e_ zLFz*(vY5VH%jG&`TBS$4A2jD~dhE*DwUh~IP*k^UDF1DkJ!|h^7iiF=Ouu&RF+e9t zo8EtA5S!m8n*Ci+X9W4t+4YL4#fnF&oLnZ+Es?M}d}~V1ZqT2_vbTM#{P!^+JW(^) zHz(tjvN=OhdVuVieE|eNIh&s#JD;QhFiWxcjSx=5s7uIHADvrIm_bx#)mzxDZF^y* z8DWKZpL*D>e%)n6^~E{`=MF-`r-d_lFrlQ(@sUHcHQ#{%r$PH^sP{HfU?oT}8+~d0 zgrcGBbtA**I6vhYKO>NejHz&@rG3H3BRgM#`r@l*0LM?tC_n2Lfx>Sh2VKd(rK@nx zs3p&IgG#RwqrdMjTu6Z^@|24Z(=3~&;iWGOm%Y&mcyxh*wgB;&ftiEwc>yo71?XWN zbQ&f(fvthm?=J|!XHIpS#6o0=f?x>z;2e4V7#TUq^XzVlJMJc`xx9ky5$a3 z$YPsWGV?R!ZRMk%ZF9BzgcbdyW%=i#w3k=vmo$W<)(MY7Q=K=u05eU)lS7+r|aE3vC|C#L`|_tG!^ZoiEH9s#|-`7;@XQB4^++ z3)aby-eE+&z%T;x_PS4;`wsQc-D_yjU5D$k^&Uo7-TT3{rl$OZ>!(f!{BXKwN`KV^ z^VvGw{d4b^p?ui{_oMMB-I^_=%!nzwfeNCC1R@Lh!xDGIL5i7y_;KGGl2Zh22&cI2I7e&Ry1kDhI+80qo4KE1_wn>*uCdaN)50VA{*LN#GtHV;_2H_OE%+E$3e%gGukKQ7+$l=kjzaT6QMDqn=LZO^{*;eokc z;}Ld@3;o}SWvdih)c+P_GZI!2m@>OyHX)nVQQlvjdPNy_q^rtk&7X{X5g!KIZDCa z>4Qc6f#0`8P^3K{u~cPf>Y8Q?4QdZCal`x0#)I1%GiQTjvZh|LlATKnp^(5$dqdYI zsG`|6tFTPvv_fzQ=LyOHW2H;a$#vJkJjNVRt*9|@v4_ECGP@V6C?GTlR0A}(HSz$b zO<#c0UMam@rG=~Ua4%|{h|OY^zZB;ChgfzgNlOC{0NqDQ#OO&dPT#KEqH6*Dv`R1B$>SwrVu&R_NJiZK?!a~BZz@MtQIu6bb z#EdVOaP?JYiTKa;FYSwR8?+Ld0Z)#NZ)vZNp}h$&!Z8fVK1W)rm50VRwuV0L_l?S& z`>Ajfz=){0VO~(ojVv~1C~}a}*VKILmn=gTOF5m@xdB4gqdf$PdW3JAw|kgeL9Y)& zRbJJc-4}kW=K`A1-<8w&gl_^C!k{#H?J)Bpw>AH`+d6^auK*Jai^+tQmJ62HS^c;C zg@kmR`ctg|-C<2~mL+W6WSPhjN{j0>UU~HHwXs^`33H_%Z!n0GO;F@|nSrCDU2nI; zBY}se>mhk04RU|<>Q7L0@zh3z*Ot1~!-`caSy_V2hVCXiJ7{5i_{zgoQ6%?KTVQ0R z&{JE6)6DggbqG=AfKfUDLi&PVz5`*ws|1nnrO5*qg>vK8!*Flw*FrBmHg`7cD^yew z6k8Wg`B58H>Z*rn{yF4ipP}}-z^eC*rKUN^T)x$*CGf}i61nu{z7I}fAIjAY=(Tfh zFZcveX{q5n7JCm@po$l?hAm4MF{43#qt};e3lQv`Oi3~)DsLlzLD=MUDr8E=r0Mpl zY=PEvRI>1EQ?f)cv=Mp41rNJB-r;!|WKn6uX}!bkLc8}&=W`|v-szR23XCZ05D8`y zTlf^C&H<%$jnoum;Sdfl#b#z>Cq``cdU8eVqo-Jkgq|>BIs^?t4Qo<+pJSXFYNqvk zS=-%f8}YL=h}J2*FK2mPxS*sWf`=2#q{TfsfG<|jZIQO`wI&2Znr;zr79)058q6y@ z0Hle`*C}ux3X&&a2Y@-@&bw+OdB+6?3QlVqLbIEhNdm*%-@yG5yMRbN7$569vh60! zrN=I2@IFiElfc6_3!>s?mq8;u-5!2eTr|X)_sNqZC>+m8;0XtSCM!N~dR}M&LS64a z05tcCm_ndCXTrdfMb9Kq;f=Xe8kA7Xf2`%Xa2fpIcBPGJzJ(zwhnRp8gj&X?$Auf2 zluSagioYZCo!wIBs<+`wKg4XeKM&7|Me%9(mdyInBz?e-j|5IyER)Sg7f@^_s#g)&JV zX(m%+4GN@^4)(fL8FaDfOsaPj{p2kVfycRC#*o&1(D)N-u((=)d(1iflA0ROCY1av zF3?|%nNhQeu+;Zzij-er!!?l74Okpj#kf;WX*8fPx)pg?e#v2CP~}V*%?qgB?q63j zozGar8ECO0L)5JWHEFwX=s5EA4Rm|>&&RtKibIXED58GG%Sz!m_{1Dy-kh@~gtH@A z6xsbCn^dT0s2BxUH2bU6`rCR+6K*I?NF+H94S1X@Zvos<=hnnbMwr3~uShwadJq92m9n^OW(4J%h+A7BnWz=kWBIcPZhUfLa8?^2RGbYi83W0 z#zP`YckmRbx!I80oph<6b97TNh9;y?HRthf?Ss~e#MfrG^fDVwawop9jL@c8V-cfc zp$NPC7GxXkBl2aRXi2M=b%Q}{U?amc@^n4h!`yXzhi6@San-IK><9Byle zTAH3Rb=pLp>h{p6A|SSaFj%?FHtZ59f+>tR?`h%jY3*G}hF;(LP4UzqJ$XrM6h6uD z005El_9i#>b6e9)h?^lRtRGyBR^E1N(ThTpWS4Q*eXWr7!^*hLH#S^aaQ$$(B(x4{ zSVOlAL>3{>8*2iuBo)yEi&JG3+N500nz3RULTHzJ1WMxec=t6suyy~toj#>$_xP^q zr!Lwq1tA0Bd`2f4eHKNiEGHB`Z1TrKJnAeumJu~W(!jVY*2m~-?W?ayltpyMnj`r( zp#=wHf_%b&$qK_27*N!4u|=yXF#4HpMN#ppgwZcq`bsiV!FonYcDiQP9)}IPBkJx` zdi)B`PO1~O)O$uiH23AHRPzOFvU2i%9Eyl8QtCxOZD2JFidx=Py4>mF1bELB=K)FZ zgQyS^X(PKxEj@XeuV_a>!MGjx4sis_jwt=U&7mK9Q)vPO`d75Z0s%|Z2w<^hiyH| zhJRGz52W-5O8yJo*OW^nUve63dG9KVJ^&<&B+Oj&{?ZXh-8mY|ZvE^A{LbS(Iwf{< zFPPoB&i|$@J0Ixf$ur8~p1&0PX8%~^jJ^b~Hv3Q1FBJK4XYT;8^o%+Y-?bA_cL3N2 zo;m=~RlyV?zWmA`>i$Dr)a_mycI$>UwUxdxqkou}^}&$WpxRo9*$Fn^_Ax9^8Mqvg3(RJwOL;z!keC5xH z&EKAge{O|S{_Ji#{?`fE`y}U%z7^`nfK_ptkBpxg+v2+Wz1opJhvD4nJeLI(#>ex} zW@D~)SVOi~#kmGnp%GqcR}u+W86WfIm3^`|MQFBrtz_;YG~64E#+36r@n2BfxOcT; zYT})$!x<+!kteovP{X3g literal 0 HcmV?d00001 diff --git a/example/lib/main.dart b/example/lib/main.dart index d3ee098..8dd1a19 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,4 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:sceneview_flutter/augmented_image.dart'; +import 'package:sceneview_flutter/light_estimation_mode.dart'; import 'dart:async'; import 'package:sceneview_flutter/sceneview_flutter.dart'; @@ -26,13 +28,19 @@ class _MyAppState extends State { body: Stack( children: [ SceneView( + lightEstimationMode: LightEstimationMode.AMBIENT_INTENSITY, + augmentedImages: [ + AugmentedImage(name: 'rabbit',location: 'assets/augmentedimages/rabbit.jpg', ), + ], onViewCreated: (controller) { print('flutter: onViewCreated'); - controller.addNode(SceneViewNode( - fileLocation: 'assets/models/MaterialSuite.glb', - position: KotlinFloat3(z: -1.0), - rotation: KotlinFloat3(x: 15), - )); + controller.addNode( + SceneViewNode( + fileLocation: 'assets/models/MaterialSuite.glb', + position: KotlinFloat3(z: -1.0), + rotation: KotlinFloat3(x: 15), + ), + ); }, ), ], diff --git a/example/pubspec.lock b/example/pubspec.lock index bd05899..9b787e7 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -59,6 +59,22 @@ packages: description: flutter source: sdk version: "0.0.0" + freezed_annotation: + dependency: transitive + description: + name: freezed_annotation + sha256: c3fd9336eb55a38cc1bbd79ab17573113a8deccd0ecbbf926cca3c62803b5c2d + url: "https://pub.dev" + source: hosted + version: "2.4.1" + json_annotation: + dependency: transitive + description: + name: json_annotation + sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467 + url: "https://pub.dev" + source: hosted + version: "4.8.1" matcher: dependency: transitive description: @@ -95,10 +111,10 @@ packages: dependency: transitive description: name: plugin_platform_interface - sha256: dbf0f707c78beedc9200146ad3cb0ab4d5da13c246336987be6940f026500d3a + sha256: da3fdfeccc4d4ff2da8f8c556704c08f912542c5fb3cf2233ed75372384a034d url: "https://pub.dev" source: hosted - version: "2.1.3" + version: "2.1.6" sceneview_flutter: dependency: "direct main" description: diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 0acb0f0..7912a9c 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -19,3 +19,4 @@ dev_dependencies: flutter: assets: - assets/models/MaterialSuite.glb + - assets/augmentedimages/ diff --git a/lib/augmented_image.dart b/lib/augmented_image.dart new file mode 100644 index 0000000..b5879c3 --- /dev/null +++ b/lib/augmented_image.dart @@ -0,0 +1,16 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'augmented_image.freezed.dart'; + +part 'augmented_image.g.dart'; + +@freezed +class AugmentedImage with _$AugmentedImage { + factory AugmentedImage({ + required String name, + required String location, + }) = _AuAugmentedImage; + + factory AugmentedImage.fromJson(Map json) => + _$AugmentedImageFromJson(json); +} \ No newline at end of file diff --git a/lib/augmented_image.freezed.dart b/lib/augmented_image.freezed.dart new file mode 100644 index 0000000..ec1dad8 --- /dev/null +++ b/lib/augmented_image.freezed.dart @@ -0,0 +1,171 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'augmented_image.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); + +AugmentedImage _$AugmentedImageFromJson(Map json) { + return _AuAugmentedImage.fromJson(json); +} + +/// @nodoc +mixin _$AugmentedImage { + String get name => throw _privateConstructorUsedError; + String get location => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $AugmentedImageCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $AugmentedImageCopyWith<$Res> { + factory $AugmentedImageCopyWith( + AugmentedImage value, $Res Function(AugmentedImage) then) = + _$AugmentedImageCopyWithImpl<$Res, AugmentedImage>; + @useResult + $Res call({String name, String location}); +} + +/// @nodoc +class _$AugmentedImageCopyWithImpl<$Res, $Val extends AugmentedImage> + implements $AugmentedImageCopyWith<$Res> { + _$AugmentedImageCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? name = null, + Object? location = null, + }) { + return _then(_value.copyWith( + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + location: null == location + ? _value.location + : location // ignore: cast_nullable_to_non_nullable + as String, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$AuAugmentedImageImplCopyWith<$Res> + implements $AugmentedImageCopyWith<$Res> { + factory _$$AuAugmentedImageImplCopyWith(_$AuAugmentedImageImpl value, + $Res Function(_$AuAugmentedImageImpl) then) = + __$$AuAugmentedImageImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({String name, String location}); +} + +/// @nodoc +class __$$AuAugmentedImageImplCopyWithImpl<$Res> + extends _$AugmentedImageCopyWithImpl<$Res, _$AuAugmentedImageImpl> + implements _$$AuAugmentedImageImplCopyWith<$Res> { + __$$AuAugmentedImageImplCopyWithImpl(_$AuAugmentedImageImpl _value, + $Res Function(_$AuAugmentedImageImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? name = null, + Object? location = null, + }) { + return _then(_$AuAugmentedImageImpl( + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + location: null == location + ? _value.location + : location // ignore: cast_nullable_to_non_nullable + as String, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$AuAugmentedImageImpl implements _AuAugmentedImage { + _$AuAugmentedImageImpl({required this.name, required this.location}); + + factory _$AuAugmentedImageImpl.fromJson(Map json) => + _$$AuAugmentedImageImplFromJson(json); + + @override + final String name; + @override + final String location; + + @override + String toString() { + return 'AugmentedImage(name: $name, location: $location)'; + } + + @override + bool operator ==(dynamic other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$AuAugmentedImageImpl && + (identical(other.name, name) || other.name == name) && + (identical(other.location, location) || + other.location == location)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash(runtimeType, name, location); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$AuAugmentedImageImplCopyWith<_$AuAugmentedImageImpl> get copyWith => + __$$AuAugmentedImageImplCopyWithImpl<_$AuAugmentedImageImpl>( + this, _$identity); + + @override + Map toJson() { + return _$$AuAugmentedImageImplToJson( + this, + ); + } +} + +abstract class _AuAugmentedImage implements AugmentedImage { + factory _AuAugmentedImage( + {required final String name, + required final String location}) = _$AuAugmentedImageImpl; + + factory _AuAugmentedImage.fromJson(Map json) = + _$AuAugmentedImageImpl.fromJson; + + @override + String get name; + @override + String get location; + @override + @JsonKey(ignore: true) + _$$AuAugmentedImageImplCopyWith<_$AuAugmentedImageImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/augmented_image.g.dart b/lib/augmented_image.g.dart new file mode 100644 index 0000000..85ad131 --- /dev/null +++ b/lib/augmented_image.g.dart @@ -0,0 +1,21 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'augmented_image.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$AuAugmentedImageImpl _$$AuAugmentedImageImplFromJson( + Map json) => + _$AuAugmentedImageImpl( + name: json['name'] as String, + location: json['location'] as String, + ); + +Map _$$AuAugmentedImageImplToJson( + _$AuAugmentedImageImpl instance) => + { + 'name': instance.name, + 'location': instance.location, + }; diff --git a/lib/light_estimation_mode.dart b/lib/light_estimation_mode.dart new file mode 100644 index 0000000..73cff87 --- /dev/null +++ b/lib/light_estimation_mode.dart @@ -0,0 +1,17 @@ +enum LightEstimationMode { + /** Lighting estimation is disabled. */ + DISABLED, + /** + * Lighting estimation is enabled, generating a single-value intensity estimate and three (R, G, + * B) color correction values. + */ + AMBIENT_INTENSITY, + /** + * Lighting estimation is enabled, generating inferred Environmental HDR lighting estimation in + * linear color space. + * + *

    This mode is incompatible with the front-facing (selfie) camera. If set on a Session + * created for the front-facing camera, the call to configure will fail. + */ + ENVIRONMENTAL_HDR; +} \ No newline at end of file diff --git a/lib/scene_view.dart b/lib/scene_view.dart index cdc4931..70de77e 100644 --- a/lib/scene_view.dart +++ b/lib/scene_view.dart @@ -5,14 +5,20 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; +import 'package:sceneview_flutter/augmented_image.dart'; +import 'package:sceneview_flutter/light_estimation_mode.dart'; import 'package:sceneview_flutter/sceneview_controller.dart'; class SceneView extends StatefulWidget { const SceneView({ super.key, this.onViewCreated, + this.augmentedImages, + this.lightEstimationMode, }); + final List? augmentedImages; + final LightEstimationMode? lightEstimationMode; final Function(SceneViewController)? onViewCreated; @override @@ -23,12 +29,22 @@ class _SceneViewState extends State { final Completer _controller = Completer(); + final Map creationParams = {}; + + @override + void initState() { + super.initState(); + if (widget.augmentedImages != null) { + creationParams['augmentedImages'] = + widget.augmentedImages!.map((e) => e.toJson()).toList(); + } + creationParams['lightEstimationMode'] = widget.lightEstimationMode?.index; + } + @override Widget build(BuildContext context) { // This is used in the platform side to register the view. const String viewType = 'SceneView'; - // Pass parameters to the platform side. - const Map creationParams = {}; return PlatformViewLink( viewType: viewType, @@ -71,7 +87,7 @@ class _SceneViewState extends State { } Future _disposeController() async { - final SceneViewController controller = await _controller.future; + final controller = await _controller.future; controller.dispose(); } } diff --git a/pubspec.yaml b/pubspec.yaml index 2e987b1..67b5b98 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -10,12 +10,17 @@ environment: dependencies: flutter: sdk: flutter - plugin_platform_interface: ^2.1.3 + plugin_platform_interface: ^2.1.6 + freezed_annotation: ^2.4.1 + json_annotation: ^4.8.1 dev_dependencies: flutter_test: sdk: flutter flutter_lints: ^2.0.1 + build_runner: + freezed: ^2.4.5 + json_serializable: ^6.7.1 flutter: plugin: From 945a3e4788610d9f9f4a0ab2e23ae407b53b3cd3 Mon Sep 17 00:00:00 2001 From: Gian Marco Di Francesco Date: Thu, 16 Nov 2023 02:00:43 +0100 Subject: [PATCH 2/6] Added stream to map event from native side --- .../sceneview_flutter/SceneViewWrapper.kt | 4 ++ example/lib/main.dart | 7 +++- example/pubspec.lock | 8 ++++ lib/scene_view.dart | 18 ++++----- lib/sceneview_controller.dart | 36 +++++++++++++----- lib/sceneview_flutter.dart | 19 ++++++++-- lib/sceneview_flutter_method_channel.dart | 27 +++++++++++++- lib/sceneview_flutter_platform_interface.dart | 12 +++++- lib/tracking_failure_reason.dart | 37 +++++++++++++++++++ pubspec.yaml | 1 + 10 files changed, 142 insertions(+), 27 deletions(-) create mode 100644 lib/tracking_failure_reason.dart diff --git a/android/src/main/kotlin/io/github/sceneview/sceneview_flutter/SceneViewWrapper.kt b/android/src/main/kotlin/io/github/sceneview/sceneview_flutter/SceneViewWrapper.kt index 521da29..d2788fe 100644 --- a/android/src/main/kotlin/io/github/sceneview/sceneview_flutter/SceneViewWrapper.kt +++ b/android/src/main/kotlin/io/github/sceneview/sceneview_flutter/SceneViewWrapper.kt @@ -62,6 +62,9 @@ class SceneViewWrapper( } config.instantPlacementMode = Config.InstantPlacementMode.DISABLED } + onSessionUpdated = { session, frame -> + _channel.invokeMethod("onSessionUpdated#onMove", frame.timestamp.toString()); + } onSessionResumed = { session -> Log.i(TAG, "onSessionCreated") } @@ -73,6 +76,7 @@ class SceneViewWrapper( } onTrackingFailureChanged = { reason -> Log.i(TAG, "onTrackingFailureChanged: $reason"); + _channel.invokeMethod("onTrackingFailureChanged", reason?.ordinal); } } sceneView.layoutParams = FrameLayout.LayoutParams( diff --git a/example/lib/main.dart b/example/lib/main.dart index 8dd1a19..42e3883 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,7 +1,6 @@ import 'package:flutter/material.dart'; import 'package:sceneview_flutter/augmented_image.dart'; import 'package:sceneview_flutter/light_estimation_mode.dart'; -import 'dart:async'; import 'package:sceneview_flutter/sceneview_flutter.dart'; import 'package:sceneview_flutter/sceneview_node.dart'; @@ -42,6 +41,12 @@ class _MyAppState extends State { ), ); }, + onSessionUpdated: (text){ + print('onSessionUpdated: $text'); + }, + onTrackingFailureChanged: (reason){ + print('onTrackingFailureChanged: $reason'); + }, ), ], ), diff --git a/example/pubspec.lock b/example/pubspec.lock index 9b787e7..91b1769 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -151,6 +151,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.1" + stream_transform: + dependency: transitive + description: + name: stream_transform + sha256: "14a00e794c7c11aa145a170587321aedce29769c08d7f58b1d141da75e3b1c6f" + url: "https://pub.dev" + source: hosted + version: "2.1.0" string_scanner: dependency: transitive description: diff --git a/lib/scene_view.dart b/lib/scene_view.dart index 70de77e..cc385da 100644 --- a/lib/scene_view.dart +++ b/lib/scene_view.dart @@ -1,22 +1,18 @@ -import 'dart:async'; - -import 'package:flutter/foundation.dart'; -import 'package:flutter/gestures.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/rendering.dart'; -import 'package:flutter/services.dart'; -import 'package:sceneview_flutter/augmented_image.dart'; -import 'package:sceneview_flutter/light_estimation_mode.dart'; -import 'package:sceneview_flutter/sceneview_controller.dart'; +part of scene_view_flutter; class SceneView extends StatefulWidget { const SceneView({ super.key, this.onViewCreated, + this.onSessionUpdated, + this.onTrackingFailureChanged, this.augmentedImages, this.lightEstimationMode, }); + final Function(String)? onSessionUpdated; + + final Function(TrackingFailureReason)? onTrackingFailureChanged; final List? augmentedImages; final LightEstimationMode? lightEstimationMode; final Function(SceneViewController)? onViewCreated; @@ -75,7 +71,7 @@ class _SceneViewState extends State { } Future onPlatformViewCreated(int id) async { - final controller = await SceneViewController.init(id); + final controller = await SceneViewController.init(id, this); _controller.complete(controller); widget.onViewCreated?.call(controller); } diff --git a/lib/sceneview_controller.dart b/lib/sceneview_controller.dart index 1cb78c8..3afbf75 100644 --- a/lib/sceneview_controller.dart +++ b/lib/sceneview_controller.dart @@ -1,19 +1,38 @@ -import 'package:flutter/services.dart'; -import 'package:sceneview_flutter/sceneview_flutter_platform_interface.dart'; -import 'package:sceneview_flutter/sceneview_node.dart'; +part of scene_view_flutter; class SceneViewController { - SceneViewController._({ + SceneViewController._( + this._sceneViewState, { required this.sceneId, - }); + }) { + _connectStream(); + } final int sceneId; + final _SceneViewState _sceneViewState; static Future init( - int sceneId, - ) async { + int sceneId, _SceneViewState state) async { await SceneviewFlutterPlatform.instance.init(sceneId); - return SceneViewController._(sceneId: sceneId); + return SceneViewController._(state, sceneId: sceneId); + } + + _connectStream() { + if (_sceneViewState.widget.onSessionUpdated != null) { + SceneviewFlutterPlatform.instance.onSessionUpdated().listen( + (event) { + _sceneViewState.widget.onSessionUpdated!(event); + }, + ); + } + + if (_sceneViewState.widget.onSessionUpdated != null) { + SceneviewFlutterPlatform.instance.onTrackingFailureChanged().listen( + (event) { + _sceneViewState.widget.onTrackingFailureChanged!(event); + }, + ); + } } void addNode(SceneViewNode node) { @@ -23,5 +42,4 @@ class SceneViewController { void dispose() { SceneviewFlutterPlatform.instance.dispose(sceneId); } - } diff --git a/lib/sceneview_flutter.dart b/lib/sceneview_flutter.dart index 8266837..44e2305 100644 --- a/lib/sceneview_flutter.dart +++ b/lib/sceneview_flutter.dart @@ -1,6 +1,17 @@ -import 'sceneview_flutter_platform_interface.dart'; +library scene_view_flutter; -export 'sceneview_controller.dart'; -export 'scene_view.dart'; +import 'package:flutter/material.dart'; +import 'dart:async'; -class SceneviewFlutter {} +import 'package:flutter/foundation.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter/services.dart'; +import 'package:sceneview_flutter/augmented_image.dart'; +import 'package:sceneview_flutter/light_estimation_mode.dart'; +import 'package:sceneview_flutter/sceneview_flutter_platform_interface.dart'; +import 'package:sceneview_flutter/sceneview_node.dart'; +import 'package:sceneview_flutter/tracking_failure_reason.dart'; + +part 'sceneview_controller.dart'; +part 'scene_view.dart'; diff --git a/lib/sceneview_flutter_method_channel.dart b/lib/sceneview_flutter_method_channel.dart index f4219c8..982c0bd 100644 --- a/lib/sceneview_flutter_method_channel.dart +++ b/lib/sceneview_flutter_method_channel.dart @@ -1,8 +1,12 @@ +import 'dart:async'; + import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'package:sceneview_flutter/sceneview_node.dart'; +import 'package:sceneview_flutter/tracking_failure_reason.dart'; import 'sceneview_flutter_platform_interface.dart'; +import 'package:stream_transform/stream_transform.dart'; /// An implementation of [SceneviewFlutterPlatform] that uses method channels. class MethodChannelSceneViewFlutter extends SceneviewFlutterPlatform { @@ -22,12 +26,16 @@ class MethodChannelSceneViewFlutter extends SceneviewFlutterPlatform { if (channel == null) { channel = MethodChannel('scene_view_$sceneId'); channel.setMethodCallHandler( - (MethodCall call) => _handleMethodCall(call, sceneId)); + (MethodCall call) => _handleMethodCall(call, sceneId)); _channel = channel; } return channel; } + final StreamController _mapEventStreamController = + StreamController.broadcast(); + + Stream _events() => _mapEventStreamController.stream; @override Future init(int sceneId) async { @@ -40,8 +48,25 @@ class MethodChannelSceneViewFlutter extends SceneviewFlutterPlatform { _channel?.invokeMethod('addNode', node.toMap()); } + @override + Stream onSessionUpdated() { + return _events().whereType(); + } + + @override + Stream onTrackingFailureChanged() { + return _events().whereType(); + } + Future _handleMethodCall(MethodCall call, int mapId) async { switch (call.method) { + case 'onTrackingFailureChanged': + _mapEventStreamController + .add(TrackingFailureReason.values[call.arguments as int]); + break; + case 'onSessionUpdated': + _mapEventStreamController.add(call.arguments); + break; default: throw MissingPluginException(); } diff --git a/lib/sceneview_flutter_platform_interface.dart b/lib/sceneview_flutter_platform_interface.dart index fdbfa19..acb7ea7 100644 --- a/lib/sceneview_flutter_platform_interface.dart +++ b/lib/sceneview_flutter_platform_interface.dart @@ -1,5 +1,6 @@ import 'package:plugin_platform_interface/plugin_platform_interface.dart'; import 'package:sceneview_flutter/sceneview_node.dart'; +import 'package:sceneview_flutter/tracking_failure_reason.dart'; import 'sceneview_flutter_method_channel.dart'; @@ -32,7 +33,16 @@ abstract class SceneviewFlutterPlatform extends PlatformInterface { throw UnimplementedError('addNode() has not been implemented.'); } - void dispose(int sceneId){ + Stream onSessionUpdated() { + throw UnimplementedError('onSessionUpdated() has not been implemented.'); + } + + Stream onTrackingFailureChanged() { + throw UnimplementedError( + 'onTrackingFailureChanged() has not been implemented.'); + } + + void dispose(int sceneId) { throw UnimplementedError('dispose() has not been implemented.'); } } diff --git a/lib/tracking_failure_reason.dart b/lib/tracking_failure_reason.dart new file mode 100644 index 0000000..3b672cd --- /dev/null +++ b/lib/tracking_failure_reason.dart @@ -0,0 +1,37 @@ +enum TrackingFailureReason { + /** + * Indicates expected motion tracking behavior. Always returned when {@link + * com.google.ar.core.Camera#getTrackingState() } is {@link + * com.google.ar.core.TrackingState#TRACKING TrackingState#TRACKING}. When {@link + * com.google.ar.core.Camera#getTrackingState() } is {@link + * com.google.ar.core.TrackingState#PAUSED TrackingState#PAUSED}, indicates that the session is + * initializing normally. + */ + NONE, + /** + * Motion tracking lost due to bad internal state. No specific user action is likely to resolve + * this issue. + */ + BAD_STATE, + /** + * Motion tracking lost due to poor lighting conditions. Ask the user to move to a more brightly + * lit area. Android 12 (API level 31) or later, the user may have + * disabled camera access, causing ARCore to receive a blank camera feed. + */ + INSUFFICIENT_LIGHT, + /** Motion tracking lost due to excessive motion. Ask the user to move the device more slowly. */ + EXCESSIVE_MOTION, + /** + * Motion tracking lost due to insufficient visual features. Ask the user to move to a different + * area and to avoid blank walls and surfaces without detail. + */ + INSUFFICIENT_FEATURES, + /** + * Motion tracking paused because the camera is in use by another application. Tracking will + * resume once this app regains priority, or once all apps with higher priority have stopped using + * the camera. Prior to ARCore SDK 1.13, {@link com.google.ar.core.TrackingFailureReason#NONE + * TrackingFailureReason#NONE} is returned in this case instead. + */ + CAMERA_UNAVAILABLE; +} diff --git a/pubspec.yaml b/pubspec.yaml index 67b5b98..f6e6b0e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -13,6 +13,7 @@ dependencies: plugin_platform_interface: ^2.1.6 freezed_annotation: ^2.4.1 json_annotation: ^4.8.1 + stream_transform: ^2.1.0 dev_dependencies: flutter_test: From 1579a695a734ea8b23a80d9c2952ef879132dc6e Mon Sep 17 00:00:00 2001 From: Gian Marco Di Francesco Date: Thu, 16 Nov 2023 02:02:20 +0100 Subject: [PATCH 3/6] Fixed wrong method name --- .../io/github/sceneview/sceneview_flutter/SceneViewWrapper.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/src/main/kotlin/io/github/sceneview/sceneview_flutter/SceneViewWrapper.kt b/android/src/main/kotlin/io/github/sceneview/sceneview_flutter/SceneViewWrapper.kt index d2788fe..e104f36 100644 --- a/android/src/main/kotlin/io/github/sceneview/sceneview_flutter/SceneViewWrapper.kt +++ b/android/src/main/kotlin/io/github/sceneview/sceneview_flutter/SceneViewWrapper.kt @@ -63,7 +63,7 @@ class SceneViewWrapper( config.instantPlacementMode = Config.InstantPlacementMode.DISABLED } onSessionUpdated = { session, frame -> - _channel.invokeMethod("onSessionUpdated#onMove", frame.timestamp.toString()); + _channel.invokeMethod("onSessionUpdated", frame.timestamp.toString()); } onSessionResumed = { session -> Log.i(TAG, "onSessionCreated") From 0718ee9d44ae4c305ea7c8a1ec55f631794fff1a Mon Sep 17 00:00:00 2001 From: Gian Marco Di Francesco Date: Thu, 16 Nov 2023 17:03:28 +0100 Subject: [PATCH 4/6] Added arsceneview configs --- .../sceneview_flutter/ARSceneViewConfig.kt | 28 +- .../sceneview_flutter/PlaneRenderer.kt | 19 ++ .../sceneview_flutter/SceneViewBuilder.kt | 17 +- .../sceneview_flutter/SceneViewFactory.kt | 17 +- .../sceneview_flutter/SceneViewWrapper.kt | 15 +- build.yaml | 6 + example/lib/main.dart | 34 ++- lib/arsceneview_config.dart | 25 ++ lib/arsceneview_config.freezed.dart | 254 ++++++++++++++++++ lib/arsceneview_config.g.dart | 52 ++++ lib/augmented_image.dart | 2 +- lib/augmented_image.freezed.dart | 4 +- lib/depth_mode.dart | 51 ++++ lib/enum_converter.dart | 18 ++ lib/instant_placement_mode.dart | 50 ++++ lib/light_estimation_mode.dart | 16 +- lib/plane_renderer.dart | 16 ++ lib/plane_renderer.freezed.dart | 172 ++++++++++++ lib/plane_renderer.g.dart | 19 ++ lib/scene_view.dart | 7 +- lib/sceneview_flutter.dart | 2 +- 21 files changed, 776 insertions(+), 48 deletions(-) create mode 100644 android/src/main/kotlin/io/github/sceneview/sceneview_flutter/PlaneRenderer.kt create mode 100644 build.yaml create mode 100644 lib/arsceneview_config.dart create mode 100644 lib/arsceneview_config.freezed.dart create mode 100644 lib/arsceneview_config.g.dart create mode 100644 lib/depth_mode.dart create mode 100644 lib/enum_converter.dart create mode 100644 lib/instant_placement_mode.dart create mode 100644 lib/plane_renderer.dart create mode 100644 lib/plane_renderer.freezed.dart create mode 100644 lib/plane_renderer.g.dart diff --git a/android/src/main/kotlin/io/github/sceneview/sceneview_flutter/ARSceneViewConfig.kt b/android/src/main/kotlin/io/github/sceneview/sceneview_flutter/ARSceneViewConfig.kt index 6ad7b83..b019203 100644 --- a/android/src/main/kotlin/io/github/sceneview/sceneview_flutter/ARSceneViewConfig.kt +++ b/android/src/main/kotlin/io/github/sceneview/sceneview_flutter/ARSceneViewConfig.kt @@ -2,11 +2,27 @@ package io.github.sceneview.sceneview_flutter import com.google.ar.core.Config -class ARSceneViewConfig { - - var augmentedImages = listOf() - - var lightEstimationMode = Config.LightEstimationMode.DISABLED - +class ARSceneViewConfig( + val lightEstimationMode: Config.LightEstimationMode, + val instantPlacementMode: Config.InstantPlacementMode, + val depthMode: Config.DepthMode, + val planeRenderer: PlaneRenderer, +) { + companion object { + fun from(map: Map): ARSceneViewConfig { + return ARSceneViewConfig( + Config.LightEstimationMode.values()[map["lightEstimationMode"] as Int], + Config.InstantPlacementMode.values()[map["instantPlacementMode"] as Int], + Config.DepthMode.values()[map["depthMode"] as Int], + PlaneRenderer.from(map["planeRenderer"] as Map) + ) + } + } + override fun toString(): String { + return "PlaneRenderer: ${planeRenderer}\n" + + "instantPlacementMode: $instantPlacementMode\n" + + "lightEstimationMode: $lightEstimationMode\n" + + "depthMode: $depthMode\n" + } } \ No newline at end of file diff --git a/android/src/main/kotlin/io/github/sceneview/sceneview_flutter/PlaneRenderer.kt b/android/src/main/kotlin/io/github/sceneview/sceneview_flutter/PlaneRenderer.kt new file mode 100644 index 0000000..fc399de --- /dev/null +++ b/android/src/main/kotlin/io/github/sceneview/sceneview_flutter/PlaneRenderer.kt @@ -0,0 +1,19 @@ +package io.github.sceneview.sceneview_flutter + +class PlaneRenderer(val isEnabled: Boolean, val isVisible: Boolean) { + + companion object { + + fun from(map: Map): PlaneRenderer { + return PlaneRenderer( + map["isEnabled"] as Boolean, + map["isVisible"] as Boolean, + ) + } + } + + + override fun toString(): String { + return "isVisible: $isVisible, isEnabled: $isEnabled" + } +} \ No newline at end of file diff --git a/android/src/main/kotlin/io/github/sceneview/sceneview_flutter/SceneViewBuilder.kt b/android/src/main/kotlin/io/github/sceneview/sceneview_flutter/SceneViewBuilder.kt index 7ce7e09..c3dc807 100644 --- a/android/src/main/kotlin/io/github/sceneview/sceneview_flutter/SceneViewBuilder.kt +++ b/android/src/main/kotlin/io/github/sceneview/sceneview_flutter/SceneViewBuilder.kt @@ -2,14 +2,14 @@ package io.github.sceneview.sceneview_flutter import android.app.Activity import android.content.Context +import android.util.Log import androidx.lifecycle.Lifecycle -import com.google.ar.core.Config.LightEstimationMode import io.flutter.plugin.common.BinaryMessenger class SceneViewBuilder { - - var config = ARSceneViewConfig() + var augmentedImages = listOf() + lateinit var config: ARSceneViewConfig fun build( context: Context, @@ -18,18 +18,11 @@ class SceneViewBuilder { lifecycle: Lifecycle, viewId: Int ): SceneViewWrapper { + Log.i("SceneViewBuilder", config.toString()); val controller = - SceneViewWrapper(context, activity, lifecycle, messenger, viewId, config); + SceneViewWrapper(context, activity, lifecycle, messenger, viewId, config, augmentedImages); //controller.init() //controller.setMyLocationEnabled(myLocationEnabled) return controller } - - fun setAugmentedImages(images: List) { - config.augmentedImages = images - } - - fun setLightEstimationMode(lightEstimationMode: LightEstimationMode) { - config.lightEstimationMode = lightEstimationMode - } } \ No newline at end of file diff --git a/android/src/main/kotlin/io/github/sceneview/sceneview_flutter/SceneViewFactory.kt b/android/src/main/kotlin/io/github/sceneview/sceneview_flutter/SceneViewFactory.kt index 83ebc34..cb41194 100644 --- a/android/src/main/kotlin/io/github/sceneview/sceneview_flutter/SceneViewFactory.kt +++ b/android/src/main/kotlin/io/github/sceneview/sceneview_flutter/SceneViewFactory.kt @@ -4,7 +4,6 @@ import android.app.Activity import android.content.Context import android.util.Log import androidx.lifecycle.Lifecycle -import com.google.ar.core.Config import io.flutter.plugin.common.BinaryMessenger import io.flutter.plugin.common.StandardMessageCodec import io.flutter.plugin.platform.PlatformView @@ -17,24 +16,20 @@ class SceneViewFactory( ) : PlatformViewFactory(StandardMessageCodec.INSTANCE) { override fun create(context: Context, viewId: Int, params: Any?): PlatformView { Log.d("Factory", "Creating new view instance") - val p = params as Map val builder = SceneViewBuilder() + if(p.containsKey("arSceneviewConfig")){ + val c = p["arSceneviewConfig"] as Map + builder.config = ARSceneViewConfig.from(c) + } if (p.containsKey("augmentedImages")) { - builder.config.augmentedImages = + builder.augmentedImages = Convert.toAugmentedImages( context, p["augmentedImages"] as List> ) } - if (p.containsKey("lightEstimationMode")) { - Log.i( - "SceneViewFactory", - "Config contains lightEstimationMode " + p["lightEstimationMode"] - ) - builder.config.lightEstimationMode = - Config.LightEstimationMode.values()[p["lightEstimationMode"] as Int] - } + return builder.build(context, activity, messenger, lifecycle, viewId); //return SceneViewWrapper(context, activity, lifecycle, messenger, viewId); } diff --git a/android/src/main/kotlin/io/github/sceneview/sceneview_flutter/SceneViewWrapper.kt b/android/src/main/kotlin/io/github/sceneview/sceneview_flutter/SceneViewWrapper.kt index e104f36..eed1a18 100644 --- a/android/src/main/kotlin/io/github/sceneview/sceneview_flutter/SceneViewWrapper.kt +++ b/android/src/main/kotlin/io/github/sceneview/sceneview_flutter/SceneViewWrapper.kt @@ -28,6 +28,7 @@ class SceneViewWrapper( messenger: BinaryMessenger, id: Int, arConfig: ARSceneViewConfig, + private val augmentedImages: List, ) : PlatformView, MethodCallHandler { private val TAG = "SceneViewWrapper" private var sceneView: ARSceneView @@ -46,21 +47,25 @@ class SceneViewWrapper( init { Log.i(TAG, "init") - Log.i(TAG, "there are " + arConfig.augmentedImages.size.toString() + " augmentedImages") + Log.i(TAG, "there are " + augmentedImages.size.toString() + " augmentedImages") sceneView = ARSceneView(context, sharedLifecycle = lifecycle) sceneView.apply { + + planeRenderer.isEnabled = arConfig.planeRenderer.isEnabled; + planeRenderer.isVisible = arConfig.planeRenderer.isVisible; + configureSession { session, config -> - arConfig.augmentedImages.forEach { + augmentedImages.forEach { config.addAugmentedImage(session, it.name, it.bitmap) } config.lightEstimationMode = arConfig.lightEstimationMode - config.depthMode = when (session.isDepthModeSupported(Config.DepthMode.AUTOMATIC)) { - true -> Config.DepthMode.AUTOMATIC + config.depthMode = when (session.isDepthModeSupported(arConfig.depthMode)) { + true -> arConfig.depthMode else -> Config.DepthMode.DISABLED } - config.instantPlacementMode = Config.InstantPlacementMode.DISABLED + config.instantPlacementMode = arConfig.instantPlacementMode } onSessionUpdated = { session, frame -> _channel.invokeMethod("onSessionUpdated", frame.timestamp.toString()); diff --git a/build.yaml b/build.yaml new file mode 100644 index 0000000..aaa6e0d --- /dev/null +++ b/build.yaml @@ -0,0 +1,6 @@ +targets: + $default: + builders: + json_serializable: + options: + explicit_to_json: true \ No newline at end of file diff --git a/example/lib/main.dart b/example/lib/main.dart index 42e3883..ee04861 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,9 +1,13 @@ import 'package:flutter/material.dart'; +import 'package:sceneview_flutter/arsceneview_config.dart'; import 'package:sceneview_flutter/augmented_image.dart'; +import 'package:sceneview_flutter/depth_mode.dart'; +import 'package:sceneview_flutter/instant_placement_mode.dart'; import 'package:sceneview_flutter/light_estimation_mode.dart'; import 'package:sceneview_flutter/sceneview_flutter.dart'; import 'package:sceneview_flutter/sceneview_node.dart'; +import 'package:sceneview_flutter/tracking_failure_reason.dart'; void main() { runApp(const MyApp()); @@ -17,6 +21,8 @@ class MyApp extends StatefulWidget { } class _MyAppState extends State { + TrackingFailureReason? reason; + @override Widget build(BuildContext context) { return MaterialApp( @@ -27,9 +33,16 @@ class _MyAppState extends State { body: Stack( children: [ SceneView( - lightEstimationMode: LightEstimationMode.AMBIENT_INTENSITY, + arSceneviewConfig: ARSceneviewConfig( + lightEstimationMode: LightEstimationMode.ambientIntensity, + instantPlacementMode: InstantPlacementMode.disabled, + depthMode: DepthMode.rawDepthOnly, + ), augmentedImages: [ - AugmentedImage(name: 'rabbit',location: 'assets/augmentedimages/rabbit.jpg', ), + AugmentedImage( + name: 'rabbit', + location: 'assets/augmentedimages/rabbit.jpg', + ), ], onViewCreated: (controller) { print('flutter: onViewCreated'); @@ -41,13 +54,26 @@ class _MyAppState extends State { ), ); }, - onSessionUpdated: (text){ + onSessionUpdated: (text) { print('onSessionUpdated: $text'); }, - onTrackingFailureChanged: (reason){ + onTrackingFailureChanged: (reason) { print('onTrackingFailureChanged: $reason'); + if (this.reason != reason) { + setState(() { + this.reason = reason; + }); + } }, ), + if (reason != null && reason != TrackingFailureReason.NONE) + Align( + alignment: Alignment.bottomCenter, + child: Text( + reason!.name, + style: TextStyle(color: Colors.white, fontSize: 30), + ), + ), ], ), ), diff --git a/lib/arsceneview_config.dart b/lib/arsceneview_config.dart new file mode 100644 index 0000000..73e2a4d --- /dev/null +++ b/lib/arsceneview_config.dart @@ -0,0 +1,25 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:sceneview_flutter/depth_mode.dart'; +import 'package:sceneview_flutter/instant_placement_mode.dart'; +import 'package:sceneview_flutter/light_estimation_mode.dart'; +import 'package:sceneview_flutter/plane_renderer.dart'; + +part 'arsceneview_config.freezed.dart'; + +part 'arsceneview_config.g.dart'; + +@freezed +class ARSceneviewConfig with _$ARSceneviewConfig { + const factory ARSceneviewConfig({ + @Default(PlaneRenderer()) PlaneRenderer? planeRenderer, + @JsonEnum() + @Default(LightEstimationMode.disabled) + // @LightEstimationModeConverter() + LightEstimationMode lightEstimationMode, + @JsonEnum() @Default(DepthMode.disabled) DepthMode depthMode, + @JsonEnum() @Default(InstantPlacementMode.disabled) InstantPlacementMode instantPlacementMode, + }) = _ARSceneviewConfig; + + factory ARSceneviewConfig.fromJson(Map json) => + _$ARSceneviewConfigFromJson(json); +} diff --git a/lib/arsceneview_config.freezed.dart b/lib/arsceneview_config.freezed.dart new file mode 100644 index 0000000..80a41bc --- /dev/null +++ b/lib/arsceneview_config.freezed.dart @@ -0,0 +1,254 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'arsceneview_config.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); + +ARSceneviewConfig _$ARSceneviewConfigFromJson(Map json) { + return _ARSceneviewConfig.fromJson(json); +} + +/// @nodoc +mixin _$ARSceneviewConfig { + PlaneRenderer? get planeRenderer => throw _privateConstructorUsedError; + @JsonEnum() + LightEstimationMode get lightEstimationMode => + throw _privateConstructorUsedError; + @JsonEnum() + DepthMode get depthMode => throw _privateConstructorUsedError; + @JsonEnum() + InstantPlacementMode get instantPlacementMode => + throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $ARSceneviewConfigCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ARSceneviewConfigCopyWith<$Res> { + factory $ARSceneviewConfigCopyWith( + ARSceneviewConfig value, $Res Function(ARSceneviewConfig) then) = + _$ARSceneviewConfigCopyWithImpl<$Res, ARSceneviewConfig>; + @useResult + $Res call( + {PlaneRenderer? planeRenderer, + @JsonEnum() LightEstimationMode lightEstimationMode, + @JsonEnum() DepthMode depthMode, + @JsonEnum() InstantPlacementMode instantPlacementMode}); + + $PlaneRendererCopyWith<$Res>? get planeRenderer; +} + +/// @nodoc +class _$ARSceneviewConfigCopyWithImpl<$Res, $Val extends ARSceneviewConfig> + implements $ARSceneviewConfigCopyWith<$Res> { + _$ARSceneviewConfigCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? planeRenderer = freezed, + Object? lightEstimationMode = null, + Object? depthMode = null, + Object? instantPlacementMode = null, + }) { + return _then(_value.copyWith( + planeRenderer: freezed == planeRenderer + ? _value.planeRenderer + : planeRenderer // ignore: cast_nullable_to_non_nullable + as PlaneRenderer?, + lightEstimationMode: null == lightEstimationMode + ? _value.lightEstimationMode + : lightEstimationMode // ignore: cast_nullable_to_non_nullable + as LightEstimationMode, + depthMode: null == depthMode + ? _value.depthMode + : depthMode // ignore: cast_nullable_to_non_nullable + as DepthMode, + instantPlacementMode: null == instantPlacementMode + ? _value.instantPlacementMode + : instantPlacementMode // ignore: cast_nullable_to_non_nullable + as InstantPlacementMode, + ) as $Val); + } + + @override + @pragma('vm:prefer-inline') + $PlaneRendererCopyWith<$Res>? get planeRenderer { + if (_value.planeRenderer == null) { + return null; + } + + return $PlaneRendererCopyWith<$Res>(_value.planeRenderer!, (value) { + return _then(_value.copyWith(planeRenderer: value) as $Val); + }); + } +} + +/// @nodoc +abstract class _$$ARSceneviewConfigImplCopyWith<$Res> + implements $ARSceneviewConfigCopyWith<$Res> { + factory _$$ARSceneviewConfigImplCopyWith(_$ARSceneviewConfigImpl value, + $Res Function(_$ARSceneviewConfigImpl) then) = + __$$ARSceneviewConfigImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {PlaneRenderer? planeRenderer, + @JsonEnum() LightEstimationMode lightEstimationMode, + @JsonEnum() DepthMode depthMode, + @JsonEnum() InstantPlacementMode instantPlacementMode}); + + @override + $PlaneRendererCopyWith<$Res>? get planeRenderer; +} + +/// @nodoc +class __$$ARSceneviewConfigImplCopyWithImpl<$Res> + extends _$ARSceneviewConfigCopyWithImpl<$Res, _$ARSceneviewConfigImpl> + implements _$$ARSceneviewConfigImplCopyWith<$Res> { + __$$ARSceneviewConfigImplCopyWithImpl(_$ARSceneviewConfigImpl _value, + $Res Function(_$ARSceneviewConfigImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? planeRenderer = freezed, + Object? lightEstimationMode = null, + Object? depthMode = null, + Object? instantPlacementMode = null, + }) { + return _then(_$ARSceneviewConfigImpl( + planeRenderer: freezed == planeRenderer + ? _value.planeRenderer + : planeRenderer // ignore: cast_nullable_to_non_nullable + as PlaneRenderer?, + lightEstimationMode: null == lightEstimationMode + ? _value.lightEstimationMode + : lightEstimationMode // ignore: cast_nullable_to_non_nullable + as LightEstimationMode, + depthMode: null == depthMode + ? _value.depthMode + : depthMode // ignore: cast_nullable_to_non_nullable + as DepthMode, + instantPlacementMode: null == instantPlacementMode + ? _value.instantPlacementMode + : instantPlacementMode // ignore: cast_nullable_to_non_nullable + as InstantPlacementMode, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$ARSceneviewConfigImpl implements _ARSceneviewConfig { + const _$ARSceneviewConfigImpl( + {this.planeRenderer = const PlaneRenderer(), + @JsonEnum() this.lightEstimationMode = LightEstimationMode.disabled, + @JsonEnum() this.depthMode = DepthMode.disabled, + @JsonEnum() this.instantPlacementMode = InstantPlacementMode.disabled}); + + factory _$ARSceneviewConfigImpl.fromJson(Map json) => + _$$ARSceneviewConfigImplFromJson(json); + + @override + @JsonKey() + final PlaneRenderer? planeRenderer; + @override + @JsonKey() + @JsonEnum() + final LightEstimationMode lightEstimationMode; + @override + @JsonKey() + @JsonEnum() + final DepthMode depthMode; + @override + @JsonKey() + @JsonEnum() + final InstantPlacementMode instantPlacementMode; + + @override + String toString() { + return 'ARSceneviewConfig(planeRenderer: $planeRenderer, lightEstimationMode: $lightEstimationMode, depthMode: $depthMode, instantPlacementMode: $instantPlacementMode)'; + } + + @override + bool operator ==(dynamic other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ARSceneviewConfigImpl && + (identical(other.planeRenderer, planeRenderer) || + other.planeRenderer == planeRenderer) && + (identical(other.lightEstimationMode, lightEstimationMode) || + other.lightEstimationMode == lightEstimationMode) && + (identical(other.depthMode, depthMode) || + other.depthMode == depthMode) && + (identical(other.instantPlacementMode, instantPlacementMode) || + other.instantPlacementMode == instantPlacementMode)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash(runtimeType, planeRenderer, + lightEstimationMode, depthMode, instantPlacementMode); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$ARSceneviewConfigImplCopyWith<_$ARSceneviewConfigImpl> get copyWith => + __$$ARSceneviewConfigImplCopyWithImpl<_$ARSceneviewConfigImpl>( + this, _$identity); + + @override + Map toJson() { + return _$$ARSceneviewConfigImplToJson( + this, + ); + } +} + +abstract class _ARSceneviewConfig implements ARSceneviewConfig { + const factory _ARSceneviewConfig( + {final PlaneRenderer? planeRenderer, + @JsonEnum() final LightEstimationMode lightEstimationMode, + @JsonEnum() final DepthMode depthMode, + @JsonEnum() final InstantPlacementMode instantPlacementMode}) = + _$ARSceneviewConfigImpl; + + factory _ARSceneviewConfig.fromJson(Map json) = + _$ARSceneviewConfigImpl.fromJson; + + @override + PlaneRenderer? get planeRenderer; + @override + @JsonEnum() + LightEstimationMode get lightEstimationMode; + @override + @JsonEnum() + DepthMode get depthMode; + @override + @JsonEnum() + InstantPlacementMode get instantPlacementMode; + @override + @JsonKey(ignore: true) + _$$ARSceneviewConfigImplCopyWith<_$ARSceneviewConfigImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/arsceneview_config.g.dart b/lib/arsceneview_config.g.dart new file mode 100644 index 0000000..a66b2c4 --- /dev/null +++ b/lib/arsceneview_config.g.dart @@ -0,0 +1,52 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'arsceneview_config.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$ARSceneviewConfigImpl _$$ARSceneviewConfigImplFromJson( + Map json) => + _$ARSceneviewConfigImpl( + planeRenderer: json['planeRenderer'] == null + ? const PlaneRenderer() + : PlaneRenderer.fromJson( + json['planeRenderer'] as Map), + lightEstimationMode: $enumDecodeNullable( + _$LightEstimationModeEnumMap, json['lightEstimationMode']) ?? + LightEstimationMode.disabled, + depthMode: $enumDecodeNullable(_$DepthModeEnumMap, json['depthMode']) ?? + DepthMode.disabled, + instantPlacementMode: $enumDecodeNullable( + _$InstantPlacementModeEnumMap, json['instantPlacementMode']) ?? + InstantPlacementMode.disabled, + ); + +Map _$$ARSceneviewConfigImplToJson( + _$ARSceneviewConfigImpl instance) => + { + 'planeRenderer': instance.planeRenderer?.toJson(), + 'lightEstimationMode': + _$LightEstimationModeEnumMap[instance.lightEstimationMode]!, + 'depthMode': _$DepthModeEnumMap[instance.depthMode]!, + 'instantPlacementMode': + _$InstantPlacementModeEnumMap[instance.instantPlacementMode]!, + }; + +const _$LightEstimationModeEnumMap = { + LightEstimationMode.disabled: 0, + LightEstimationMode.ambientIntensity: 1, + LightEstimationMode.environmentalHdr: 2, +}; + +const _$DepthModeEnumMap = { + DepthMode.disabled: 0, + DepthMode.automatic: 1, + DepthMode.rawDepthOnly: 2, +}; + +const _$InstantPlacementModeEnumMap = { + InstantPlacementMode.disabled: 0, + InstantPlacementMode.localYUp: 1, +}; diff --git a/lib/augmented_image.dart b/lib/augmented_image.dart index b5879c3..57f8b13 100644 --- a/lib/augmented_image.dart +++ b/lib/augmented_image.dart @@ -6,7 +6,7 @@ part 'augmented_image.g.dart'; @freezed class AugmentedImage with _$AugmentedImage { - factory AugmentedImage({ + const factory AugmentedImage({ required String name, required String location, }) = _AuAugmentedImage; diff --git a/lib/augmented_image.freezed.dart b/lib/augmented_image.freezed.dart index ec1dad8..140f798 100644 --- a/lib/augmented_image.freezed.dart +++ b/lib/augmented_image.freezed.dart @@ -108,7 +108,7 @@ class __$$AuAugmentedImageImplCopyWithImpl<$Res> /// @nodoc @JsonSerializable() class _$AuAugmentedImageImpl implements _AuAugmentedImage { - _$AuAugmentedImageImpl({required this.name, required this.location}); + const _$AuAugmentedImageImpl({required this.name, required this.location}); factory _$AuAugmentedImageImpl.fromJson(Map json) => _$$AuAugmentedImageImplFromJson(json); @@ -153,7 +153,7 @@ class _$AuAugmentedImageImpl implements _AuAugmentedImage { } abstract class _AuAugmentedImage implements AugmentedImage { - factory _AuAugmentedImage( + const factory _AuAugmentedImage( {required final String name, required final String location}) = _$AuAugmentedImageImpl; diff --git a/lib/depth_mode.dart b/lib/depth_mode.dart new file mode 100644 index 0000000..e43b7dd --- /dev/null +++ b/lib/depth_mode.dart @@ -0,0 +1,51 @@ +import 'package:json_annotation/json_annotation.dart'; +// +// part 'depth_mode.g.dart'; +// +// @JsonEnum() +enum DepthMode { + /** + * No depth information will be provided. Calling {@link + * com.google.ar.core.Frame#acquireDepthImage16Bits Frame#acquireDepthImage16Bits} throws {@link + * java.lang.IllegalStateException}. + */ + @JsonValue(0) + disabled, + /** + * On supported devices, the best + * possible depth is estimated based on hardware and software sources. Available sources of + * automatic depth are: + * + *

      + *
    • Depth from motion, using the main RGB camera + *
    • Hardware depth sensor, such as a time-of-flight sensor (or ToF sensor) + *
    + * + * Provides depth estimation for every pixel in the image, and works best for static scenes. + * Adds significant computational load. + * + *

    With this mode enabled, {@link com.google.ar.core.Frame#hitTest Frame#hitTest} also + * returns {@link com.google.ar.core.DepthPoint DepthPoint} in the output {@code + * List}, which are sampled from the generated depth image for the current frame if + * available. + */ + @JsonValue(1) + automatic, + /** + * On ARCore supported devices that also + * support the Depth API, provides a "raw", mostly unfiltered, depth image ({@link + * com.google.ar.core.Frame#acquireRawDepthImage16Bits }) and depth confidence image ({@link + * com.google.ar.core.Frame#acquireRawDepthConfidenceImage }). + * + *

    The raw depth image is sparse and does not provide valid depth for all pixels. Pixels + * without a valid depth estimate have a pixel value of 0. + * + *

    Raw depth data is also available when {@link com.google.ar.core.Config.DepthMode#AUTOMATIC + * DepthMode#AUTOMATIC} is selected. + * + *

    Raw depth is intended to be used in cases that involve understanding of the geometry in + * the environment. + */ + @JsonValue(2) + rawDepthOnly; +} diff --git a/lib/enum_converter.dart b/lib/enum_converter.dart new file mode 100644 index 0000000..ea95b91 --- /dev/null +++ b/lib/enum_converter.dart @@ -0,0 +1,18 @@ +import 'package:json_annotation/json_annotation.dart'; +import 'package:sceneview_flutter/light_estimation_mode.dart'; + +class LightEstimationModeConverter implements JsonConverter{ + + const LightEstimationModeConverter(); + + @override + LightEstimationMode fromJson(json) { + return LightEstimationMode.values[json]; + } + + @override + int toJson(LightEstimationMode object) { + return object.index; + } + +} \ No newline at end of file diff --git a/lib/instant_placement_mode.dart b/lib/instant_placement_mode.dart new file mode 100644 index 0000000..37bdd5e --- /dev/null +++ b/lib/instant_placement_mode.dart @@ -0,0 +1,50 @@ +import 'package:json_annotation/json_annotation.dart'; + +/** + * Select the behavior of Instant Placement. Default value is {@link #DISABLED}. + * + *

    Use {@link + * com.google.ar.core.Config#setInstantPlacementMode(com.google.ar.core.Config.InstantPlacementMode) + * Config#setInstantPlacementMode(InstantPlacementMode)} to set the desired mode. + */ +enum InstantPlacementMode { + /** + * Disable Instant Placement. {@link + * com.google.ar.core.Frame#hitTestInstantPlacement(float,float,float) + * Frame#hitTestInstantPlacement(float, float, float)} will return an empty list. + * + *

    When Instant Placement is disabled, any {@link com.google.ar.core.InstantPlacementPoint + * InstantPlacementPoint} that has {@link + * com.google.ar.core.InstantPlacementPoint.TrackingMethod#SCREENSPACE_WITH_APPROXIMATE_DISTANCE + * } tracking method will result in tracking state becoming permanently {@link + * com.google.ar.core.TrackingState#STOPPED TrackingState#STOPPED}. + */ + @JsonValue(0) + disabled, + /** + * Enable Instant Placement. If the hit test is successful, {@link + * com.google.ar.core.Frame#hitTestInstantPlacement(float,float,float) } will return a single + * {@link com.google.ar.core.InstantPlacementPoint InstantPlacementPoint} with the +Y pointing + * upward, against gravity. Otherwise, returns an empty result set. + * + *

    This mode is currently intended to be used with hit tests against horizontal surfaces. + * + *

    Hit tests may also be performed against surfaces with any orientation, however: + * + *

      + *
    • The resulting Instant Placement point will always have a pose with +Y pointing upward, + * against gravity. + *
    • No guarantees are made with respect to orientation of +X and +Z. Specifically, a hit + * test against a vertical surface, such as a wall, will not result in a pose that's in + * any way aligned to the plane of the wall, other than +Y being up, against gravity. + *
    • The {@link com.google.ar.core.InstantPlacementPoint InstantPlacementPoint}'s tracking + * method may never become {@link + * com.google.ar.core.InstantPlacementPoint.TrackingMethod#FULL_TRACKING } or may take a + * long time to reach this state. The tracking method remains {@link + * com.google.ar.core.InstantPlacementPoint.TrackingMethod#SCREENSPACE_WITH_APPROXIMATE_DISTANCE + * } until a (tiny) horizontal plane is fitted at the point of the hit test. + *
    + */ + @JsonValue(1) + localYUp; +} diff --git a/lib/light_estimation_mode.dart b/lib/light_estimation_mode.dart index 73cff87..e88c630 100644 --- a/lib/light_estimation_mode.dart +++ b/lib/light_estimation_mode.dart @@ -1,11 +1,18 @@ +import 'package:json_annotation/json_annotation.dart'; +// +//part 'light_estimation_mode.g.dart'; +// +// @JsonEnum() enum LightEstimationMode { /** Lighting estimation is disabled. */ - DISABLED, + @JsonValue(0) + disabled, /** * Lighting estimation is enabled, generating a single-value intensity estimate and three (R, G, * B) color correction values. */ - AMBIENT_INTENSITY, + @JsonValue(1) + ambientIntensity, /** * Lighting estimation is enabled, generating inferred Environmental HDR lighting estimation in * linear color space. @@ -13,5 +20,8 @@ enum LightEstimationMode { *

    This mode is incompatible with the front-facing (selfie) camera. If set on a Session * created for the front-facing camera, the call to configure will fail. */ - ENVIRONMENTAL_HDR; + @JsonValue(2) + environmentalHdr; + + // String toJson() => _$AEnumMap[this]!; } \ No newline at end of file diff --git a/lib/plane_renderer.dart b/lib/plane_renderer.dart new file mode 100644 index 0000000..3080e22 --- /dev/null +++ b/lib/plane_renderer.dart @@ -0,0 +1,16 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'plane_renderer.freezed.dart'; + +part 'plane_renderer.g.dart'; + +@freezed +class PlaneRenderer with _$PlaneRenderer { + const factory PlaneRenderer({ + @Default(true) bool? isVisible, + @Default(true) bool? isEnabled, + }) = _PlaneRenderer; + + factory PlaneRenderer.fromJson(Map json) => + _$PlaneRendererFromJson(json); +} diff --git a/lib/plane_renderer.freezed.dart b/lib/plane_renderer.freezed.dart new file mode 100644 index 0000000..16a2fe4 --- /dev/null +++ b/lib/plane_renderer.freezed.dart @@ -0,0 +1,172 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'plane_renderer.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); + +PlaneRenderer _$PlaneRendererFromJson(Map json) { + return _PlaneRenderer.fromJson(json); +} + +/// @nodoc +mixin _$PlaneRenderer { + bool? get isVisible => throw _privateConstructorUsedError; + bool? get isEnabled => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $PlaneRendererCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $PlaneRendererCopyWith<$Res> { + factory $PlaneRendererCopyWith( + PlaneRenderer value, $Res Function(PlaneRenderer) then) = + _$PlaneRendererCopyWithImpl<$Res, PlaneRenderer>; + @useResult + $Res call({bool? isVisible, bool? isEnabled}); +} + +/// @nodoc +class _$PlaneRendererCopyWithImpl<$Res, $Val extends PlaneRenderer> + implements $PlaneRendererCopyWith<$Res> { + _$PlaneRendererCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? isVisible = freezed, + Object? isEnabled = freezed, + }) { + return _then(_value.copyWith( + isVisible: freezed == isVisible + ? _value.isVisible + : isVisible // ignore: cast_nullable_to_non_nullable + as bool?, + isEnabled: freezed == isEnabled + ? _value.isEnabled + : isEnabled // ignore: cast_nullable_to_non_nullable + as bool?, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$PlaneRendererImplCopyWith<$Res> + implements $PlaneRendererCopyWith<$Res> { + factory _$$PlaneRendererImplCopyWith( + _$PlaneRendererImpl value, $Res Function(_$PlaneRendererImpl) then) = + __$$PlaneRendererImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({bool? isVisible, bool? isEnabled}); +} + +/// @nodoc +class __$$PlaneRendererImplCopyWithImpl<$Res> + extends _$PlaneRendererCopyWithImpl<$Res, _$PlaneRendererImpl> + implements _$$PlaneRendererImplCopyWith<$Res> { + __$$PlaneRendererImplCopyWithImpl( + _$PlaneRendererImpl _value, $Res Function(_$PlaneRendererImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? isVisible = freezed, + Object? isEnabled = freezed, + }) { + return _then(_$PlaneRendererImpl( + isVisible: freezed == isVisible + ? _value.isVisible + : isVisible // ignore: cast_nullable_to_non_nullable + as bool?, + isEnabled: freezed == isEnabled + ? _value.isEnabled + : isEnabled // ignore: cast_nullable_to_non_nullable + as bool?, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$PlaneRendererImpl implements _PlaneRenderer { + const _$PlaneRendererImpl({this.isVisible = true, this.isEnabled = true}); + + factory _$PlaneRendererImpl.fromJson(Map json) => + _$$PlaneRendererImplFromJson(json); + + @override + @JsonKey() + final bool? isVisible; + @override + @JsonKey() + final bool? isEnabled; + + @override + String toString() { + return 'PlaneRenderer(isVisible: $isVisible, isEnabled: $isEnabled)'; + } + + @override + bool operator ==(dynamic other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$PlaneRendererImpl && + (identical(other.isVisible, isVisible) || + other.isVisible == isVisible) && + (identical(other.isEnabled, isEnabled) || + other.isEnabled == isEnabled)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash(runtimeType, isVisible, isEnabled); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$PlaneRendererImplCopyWith<_$PlaneRendererImpl> get copyWith => + __$$PlaneRendererImplCopyWithImpl<_$PlaneRendererImpl>(this, _$identity); + + @override + Map toJson() { + return _$$PlaneRendererImplToJson( + this, + ); + } +} + +abstract class _PlaneRenderer implements PlaneRenderer { + const factory _PlaneRenderer({final bool? isVisible, final bool? isEnabled}) = + _$PlaneRendererImpl; + + factory _PlaneRenderer.fromJson(Map json) = + _$PlaneRendererImpl.fromJson; + + @override + bool? get isVisible; + @override + bool? get isEnabled; + @override + @JsonKey(ignore: true) + _$$PlaneRendererImplCopyWith<_$PlaneRendererImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/plane_renderer.g.dart b/lib/plane_renderer.g.dart new file mode 100644 index 0000000..d3d75ca --- /dev/null +++ b/lib/plane_renderer.g.dart @@ -0,0 +1,19 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'plane_renderer.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$PlaneRendererImpl _$$PlaneRendererImplFromJson(Map json) => + _$PlaneRendererImpl( + isVisible: json['isVisible'] as bool? ?? true, + isEnabled: json['isEnabled'] as bool? ?? true, + ); + +Map _$$PlaneRendererImplToJson(_$PlaneRendererImpl instance) => + { + 'isVisible': instance.isVisible, + 'isEnabled': instance.isEnabled, + }; diff --git a/lib/scene_view.dart b/lib/scene_view.dart index cc385da..4d0cb9c 100644 --- a/lib/scene_view.dart +++ b/lib/scene_view.dart @@ -3,18 +3,19 @@ part of scene_view_flutter; class SceneView extends StatefulWidget { const SceneView({ super.key, + this.arSceneviewConfig = const ARSceneviewConfig(), this.onViewCreated, this.onSessionUpdated, this.onTrackingFailureChanged, this.augmentedImages, - this.lightEstimationMode, }); + final ARSceneviewConfig arSceneviewConfig; + final Function(String)? onSessionUpdated; final Function(TrackingFailureReason)? onTrackingFailureChanged; final List? augmentedImages; - final LightEstimationMode? lightEstimationMode; final Function(SceneViewController)? onViewCreated; @override @@ -34,7 +35,7 @@ class _SceneViewState extends State { creationParams['augmentedImages'] = widget.augmentedImages!.map((e) => e.toJson()).toList(); } - creationParams['lightEstimationMode'] = widget.lightEstimationMode?.index; + creationParams['arSceneviewConfig'] = widget.arSceneviewConfig.toJson(); } @override diff --git a/lib/sceneview_flutter.dart b/lib/sceneview_flutter.dart index 44e2305..9d6d832 100644 --- a/lib/sceneview_flutter.dart +++ b/lib/sceneview_flutter.dart @@ -7,8 +7,8 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; +import 'package:sceneview_flutter/arsceneview_config.dart'; import 'package:sceneview_flutter/augmented_image.dart'; -import 'package:sceneview_flutter/light_estimation_mode.dart'; import 'package:sceneview_flutter/sceneview_flutter_platform_interface.dart'; import 'package:sceneview_flutter/sceneview_node.dart'; import 'package:sceneview_flutter/tracking_failure_reason.dart'; From af66bf49219c93a7d2920678ac8313f087de7fa5 Mon Sep 17 00:00:00 2001 From: Gian Marco Di Francesco Date: Thu, 16 Nov 2023 19:56:15 +0100 Subject: [PATCH 5/6] Added more info (centerPose, plane) on frame from onSessionUpdated listener --- .../sceneview_flutter/SceneViewWrapper.kt | 27 ++- .../flutter_models/FlutterPose.kt | 28 +++ build.yaml | 3 +- example/lib/main.dart | 28 ++- lib/arsceneview_config.g.dart | 5 +- lib/augmented_image.g.dart | 3 +- lib/plane.dart | 15 ++ lib/plane.freezed.dart | 150 +++++++++++++++ lib/plane.g.dart | 22 +++ lib/plane_renderer.g.dart | 2 +- lib/plane_type.dart | 13 ++ lib/pose.dart | 26 +++ lib/pose.g.dart | 17 ++ lib/scene_view.dart | 2 +- lib/sceneview_flutter.dart | 1 + lib/sceneview_flutter_method_channel.dart | 25 ++- lib/sceneview_flutter_platform_interface.dart | 3 +- lib/session_frame.dart | 18 ++ lib/session_frame.freezed.dart | 179 ++++++++++++++++++ lib/session_frame.g.dart | 23 +++ lib/vector_converter.dart | 34 ++++ pubspec.yaml | 1 + 22 files changed, 602 insertions(+), 23 deletions(-) create mode 100644 android/src/main/kotlin/io/github/sceneview/sceneview_flutter/flutter_models/FlutterPose.kt create mode 100644 lib/plane.dart create mode 100644 lib/plane.freezed.dart create mode 100644 lib/plane.g.dart create mode 100644 lib/plane_type.dart create mode 100644 lib/pose.dart create mode 100644 lib/pose.g.dart create mode 100644 lib/session_frame.dart create mode 100644 lib/session_frame.freezed.dart create mode 100644 lib/session_frame.g.dart create mode 100644 lib/vector_converter.dart diff --git a/android/src/main/kotlin/io/github/sceneview/sceneview_flutter/SceneViewWrapper.kt b/android/src/main/kotlin/io/github/sceneview/sceneview_flutter/SceneViewWrapper.kt index eed1a18..e354b7a 100644 --- a/android/src/main/kotlin/io/github/sceneview/sceneview_flutter/SceneViewWrapper.kt +++ b/android/src/main/kotlin/io/github/sceneview/sceneview_flutter/SceneViewWrapper.kt @@ -14,9 +14,11 @@ import io.flutter.plugin.common.MethodChannel.MethodCallHandler import io.flutter.plugin.platform.PlatformView import io.github.sceneview.ar.ARSceneView import io.github.sceneview.ar.arcore.addAugmentedImage +import io.github.sceneview.ar.arcore.getUpdatedPlanes import io.github.sceneview.ar.node.AugmentedImageNode import io.github.sceneview.model.ModelInstance import io.github.sceneview.node.ModelNode +import io.github.sceneview.sceneview_flutter.flutter_models.FlutterPose import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -34,7 +36,7 @@ class SceneViewWrapper( private var sceneView: ARSceneView private val _mainScope = CoroutineScope(Dispatchers.Main) private val _channel = MethodChannel(messenger, "scene_view_$id") - val augmentedImageNodes = mutableListOf() + val _augmentedImageNodes = mutableListOf() override fun getView(): View { Log.i(TAG, "getView:") @@ -68,7 +70,28 @@ class SceneViewWrapper( config.instantPlacementMode = arConfig.instantPlacementMode } onSessionUpdated = { session, frame -> - _channel.invokeMethod("onSessionUpdated", frame.timestamp.toString()); + val map = HashMap() + val list = ArrayList>() + //map["planes"] = + //frame.getUpdatedPlanes() + // .map { p -> hashMapOf("type" to p.type.ordinal) }.toList() + + frame.getUpdatedPlanes().forEach { p -> + val m = HashMap() + m["type"] = p.type.ordinal + m["centerPose"] = FlutterPose.fromPose(p.centerPose).toHashMap() + list.add(m) + } + + map["planes"] = list; + + Log.i(TAG, map.toString()); + _channel.invokeMethod("onSessionUpdated", map); + /*frame.getUpdatedPlanes() + .firstOrNull { it.type == Plane.Type.HORIZONTAL_UPWARD_FACING } + ?.let { plane -> + addAnchorNode(plane.createAnchor(plane.centerPose)) + }*/ } onSessionResumed = { session -> Log.i(TAG, "onSessionCreated") diff --git a/android/src/main/kotlin/io/github/sceneview/sceneview_flutter/flutter_models/FlutterPose.kt b/android/src/main/kotlin/io/github/sceneview/sceneview_flutter/flutter_models/FlutterPose.kt new file mode 100644 index 0000000..254e66e --- /dev/null +++ b/android/src/main/kotlin/io/github/sceneview/sceneview_flutter/flutter_models/FlutterPose.kt @@ -0,0 +1,28 @@ +package io.github.sceneview.sceneview_flutter.flutter_models + +import com.google.ar.core.Pose + +class FlutterPose(private val translation: FloatArray, private val rotation: FloatArray) { + + fun toHashMap(): HashMap { + val map: HashMap = HashMap() + map["translation"] = convertFloatArray(translation) + map["rotation"] = convertFloatArray(rotation) + return map + } + + private fun convertFloatArray(array: FloatArray): DoubleArray { + val doubleArray = DoubleArray(array.size) + for ((i, a) in array.withIndex()) { + doubleArray[i] = a.toDouble() + } + return doubleArray + } + + companion object{ + fun fromPose(pose: Pose): FlutterPose { + return FlutterPose(pose.translation, pose.rotationQuaternion) + } + } + +} \ No newline at end of file diff --git a/build.yaml b/build.yaml index aaa6e0d..36f5477 100644 --- a/build.yaml +++ b/build.yaml @@ -3,4 +3,5 @@ targets: builders: json_serializable: options: - explicit_to_json: true \ No newline at end of file + explicit_to_json: true + any_map: true \ No newline at end of file diff --git a/example/lib/main.dart b/example/lib/main.dart index ee04861..e359da4 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -23,6 +23,7 @@ class MyApp extends StatefulWidget { class _MyAppState extends State { TrackingFailureReason? reason; + bool posed = false; @override Widget build(BuildContext context) { return MaterialApp( @@ -46,16 +47,25 @@ class _MyAppState extends State { ], onViewCreated: (controller) { print('flutter: onViewCreated'); - controller.addNode( - SceneViewNode( - fileLocation: 'assets/models/MaterialSuite.glb', - position: KotlinFloat3(z: -1.0), - rotation: KotlinFloat3(x: 15), - ), - ); + // controller.addNode( + // SceneViewNode( + // fileLocation: 'assets/models/MaterialSuite.glb', + // position: KotlinFloat3(z: -1.0), + // rotation: KotlinFloat3(x: 15), + // ), + // ); }, - onSessionUpdated: (text) { - print('onSessionUpdated: $text'); + onSessionUpdated: (frame) { + print('onSessionUpdated: $frame'); + // if(!posed){ + // controller.addNode( + // SceneViewNode( + // fileLocation: 'assets/models/MaterialSuite.glb', + // // position: frame.centerPose. + // // rotation: KotlinFloat3(x: 15), + // ), + // ); + // } }, onTrackingFailureChanged: (reason) { print('onTrackingFailureChanged: $reason'); diff --git a/lib/arsceneview_config.g.dart b/lib/arsceneview_config.g.dart index a66b2c4..57a8cdd 100644 --- a/lib/arsceneview_config.g.dart +++ b/lib/arsceneview_config.g.dart @@ -6,13 +6,12 @@ part of 'arsceneview_config.dart'; // JsonSerializableGenerator // ************************************************************************** -_$ARSceneviewConfigImpl _$$ARSceneviewConfigImplFromJson( - Map json) => +_$ARSceneviewConfigImpl _$$ARSceneviewConfigImplFromJson(Map json) => _$ARSceneviewConfigImpl( planeRenderer: json['planeRenderer'] == null ? const PlaneRenderer() : PlaneRenderer.fromJson( - json['planeRenderer'] as Map), + Map.from(json['planeRenderer'] as Map)), lightEstimationMode: $enumDecodeNullable( _$LightEstimationModeEnumMap, json['lightEstimationMode']) ?? LightEstimationMode.disabled, diff --git a/lib/augmented_image.g.dart b/lib/augmented_image.g.dart index 85ad131..c9e5eb6 100644 --- a/lib/augmented_image.g.dart +++ b/lib/augmented_image.g.dart @@ -6,8 +6,7 @@ part of 'augmented_image.dart'; // JsonSerializableGenerator // ************************************************************************** -_$AuAugmentedImageImpl _$$AuAugmentedImageImplFromJson( - Map json) => +_$AuAugmentedImageImpl _$$AuAugmentedImageImplFromJson(Map json) => _$AuAugmentedImageImpl( name: json['name'] as String, location: json['location'] as String, diff --git a/lib/plane.dart b/lib/plane.dart new file mode 100644 index 0000000..44b1a46 --- /dev/null +++ b/lib/plane.dart @@ -0,0 +1,15 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:sceneview_flutter/plane_type.dart'; + +part 'plane.freezed.dart'; + +part 'plane.g.dart'; + +@freezed +class Plane with _$Plane { + const factory Plane({ + @JsonEnum() PlaneType? type, + }) = _Plane; + + factory Plane.fromJson(Map json) => _$PlaneFromJson(json); +} diff --git a/lib/plane.freezed.dart b/lib/plane.freezed.dart new file mode 100644 index 0000000..80457e3 --- /dev/null +++ b/lib/plane.freezed.dart @@ -0,0 +1,150 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'plane.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); + +Plane _$PlaneFromJson(Map json) { + return _Plane.fromJson(json); +} + +/// @nodoc +mixin _$Plane { + @JsonEnum() + PlaneType? get type => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $PlaneCopyWith get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $PlaneCopyWith<$Res> { + factory $PlaneCopyWith(Plane value, $Res Function(Plane) then) = + _$PlaneCopyWithImpl<$Res, Plane>; + @useResult + $Res call({@JsonEnum() PlaneType? type}); +} + +/// @nodoc +class _$PlaneCopyWithImpl<$Res, $Val extends Plane> + implements $PlaneCopyWith<$Res> { + _$PlaneCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? type = freezed, + }) { + return _then(_value.copyWith( + type: freezed == type + ? _value.type + : type // ignore: cast_nullable_to_non_nullable + as PlaneType?, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$PlaneImplCopyWith<$Res> implements $PlaneCopyWith<$Res> { + factory _$$PlaneImplCopyWith( + _$PlaneImpl value, $Res Function(_$PlaneImpl) then) = + __$$PlaneImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({@JsonEnum() PlaneType? type}); +} + +/// @nodoc +class __$$PlaneImplCopyWithImpl<$Res> + extends _$PlaneCopyWithImpl<$Res, _$PlaneImpl> + implements _$$PlaneImplCopyWith<$Res> { + __$$PlaneImplCopyWithImpl( + _$PlaneImpl _value, $Res Function(_$PlaneImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? type = freezed, + }) { + return _then(_$PlaneImpl( + type: freezed == type + ? _value.type + : type // ignore: cast_nullable_to_non_nullable + as PlaneType?, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$PlaneImpl implements _Plane { + const _$PlaneImpl({@JsonEnum() this.type}); + + factory _$PlaneImpl.fromJson(Map json) => + _$$PlaneImplFromJson(json); + + @override + @JsonEnum() + final PlaneType? type; + + @override + String toString() { + return 'Plane(type: $type)'; + } + + @override + bool operator ==(dynamic other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$PlaneImpl && + (identical(other.type, type) || other.type == type)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash(runtimeType, type); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$PlaneImplCopyWith<_$PlaneImpl> get copyWith => + __$$PlaneImplCopyWithImpl<_$PlaneImpl>(this, _$identity); + + @override + Map toJson() { + return _$$PlaneImplToJson( + this, + ); + } +} + +abstract class _Plane implements Plane { + const factory _Plane({@JsonEnum() final PlaneType? type}) = _$PlaneImpl; + + factory _Plane.fromJson(Map json) = _$PlaneImpl.fromJson; + + @override + @JsonEnum() + PlaneType? get type; + @override + @JsonKey(ignore: true) + _$$PlaneImplCopyWith<_$PlaneImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/plane.g.dart b/lib/plane.g.dart new file mode 100644 index 0000000..28a88aa --- /dev/null +++ b/lib/plane.g.dart @@ -0,0 +1,22 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'plane.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$PlaneImpl _$$PlaneImplFromJson(Map json) => _$PlaneImpl( + type: $enumDecodeNullable(_$PlaneTypeEnumMap, json['type']), + ); + +Map _$$PlaneImplToJson(_$PlaneImpl instance) => + { + 'type': _$PlaneTypeEnumMap[instance.type], + }; + +const _$PlaneTypeEnumMap = { + PlaneType.horizontalUpwardFacing: 0, + PlaneType.horizontalDownwardFacing: 1, + PlaneType.vertical: 2, +}; diff --git a/lib/plane_renderer.g.dart b/lib/plane_renderer.g.dart index d3d75ca..f2ebd1d 100644 --- a/lib/plane_renderer.g.dart +++ b/lib/plane_renderer.g.dart @@ -6,7 +6,7 @@ part of 'plane_renderer.dart'; // JsonSerializableGenerator // ************************************************************************** -_$PlaneRendererImpl _$$PlaneRendererImplFromJson(Map json) => +_$PlaneRendererImpl _$$PlaneRendererImplFromJson(Map json) => _$PlaneRendererImpl( isVisible: json['isVisible'] as bool? ?? true, isEnabled: json['isEnabled'] as bool? ?? true, diff --git a/lib/plane_type.dart b/lib/plane_type.dart new file mode 100644 index 0000000..36f587e --- /dev/null +++ b/lib/plane_type.dart @@ -0,0 +1,13 @@ +import 'package:json_annotation/json_annotation.dart'; + +enum PlaneType { + /** A horizontal plane facing upward (e.g. floor or tabletop). */ + @JsonValue(0) + horizontalUpwardFacing, + /** A horizontal plane facing downward (e.g. a ceiling). */ + @JsonValue(1) + horizontalDownwardFacing, + /** A vertical plane (e.g. a wall). */ + @JsonValue(2) + vertical; +} \ No newline at end of file diff --git a/lib/pose.dart b/lib/pose.dart new file mode 100644 index 0000000..5cb52a8 --- /dev/null +++ b/lib/pose.dart @@ -0,0 +1,26 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:sceneview_flutter/vector_converter.dart'; +import 'package:vector_math/vector_math.dart'; + +part 'pose.g.dart'; + +@JsonSerializable() +class Pose { + @Vector3Converter() + final Vector3 translation; + @Vector4Converter() + final Vector4 rotation; + + Pose(this.translation, this.rotation); + + @override + factory Pose.fromJson(Map map) { + return Pose( + Vector3.array(map["translation"]), + Vector4.array(map["rotation"]), + ); + } + + @override + Map toJson() => _$PoseToJson(this); +} diff --git a/lib/pose.g.dart b/lib/pose.g.dart new file mode 100644 index 0000000..428a0b7 --- /dev/null +++ b/lib/pose.g.dart @@ -0,0 +1,17 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'pose.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +Pose _$PoseFromJson(Map json) => Pose( + const Vector3Converter().fromJson(json['translation'] as List), + const Vector4Converter().fromJson(json['rotation'] as List), + ); + +Map _$PoseToJson(Pose instance) => { + 'translation': const Vector3Converter().toJson(instance.translation), + 'rotation': const Vector4Converter().toJson(instance.rotation), + }; diff --git a/lib/scene_view.dart b/lib/scene_view.dart index 4d0cb9c..58cf75d 100644 --- a/lib/scene_view.dart +++ b/lib/scene_view.dart @@ -12,7 +12,7 @@ class SceneView extends StatefulWidget { final ARSceneviewConfig arSceneviewConfig; - final Function(String)? onSessionUpdated; + final Function(SessionFrame)? onSessionUpdated; final Function(TrackingFailureReason)? onTrackingFailureChanged; final List? augmentedImages; diff --git a/lib/sceneview_flutter.dart b/lib/sceneview_flutter.dart index 9d6d832..8b84c10 100644 --- a/lib/sceneview_flutter.dart +++ b/lib/sceneview_flutter.dart @@ -11,6 +11,7 @@ import 'package:sceneview_flutter/arsceneview_config.dart'; import 'package:sceneview_flutter/augmented_image.dart'; import 'package:sceneview_flutter/sceneview_flutter_platform_interface.dart'; import 'package:sceneview_flutter/sceneview_node.dart'; +import 'package:sceneview_flutter/session_frame.dart'; import 'package:sceneview_flutter/tracking_failure_reason.dart'; part 'sceneview_controller.dart'; diff --git a/lib/sceneview_flutter_method_channel.dart b/lib/sceneview_flutter_method_channel.dart index 982c0bd..3247e29 100644 --- a/lib/sceneview_flutter_method_channel.dart +++ b/lib/sceneview_flutter_method_channel.dart @@ -3,6 +3,7 @@ import 'dart:async'; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'package:sceneview_flutter/sceneview_node.dart'; +import 'package:sceneview_flutter/session_frame.dart'; import 'package:sceneview_flutter/tracking_failure_reason.dart'; import 'sceneview_flutter_platform_interface.dart'; @@ -49,8 +50,8 @@ class MethodChannelSceneViewFlutter extends SceneviewFlutterPlatform { } @override - Stream onSessionUpdated() { - return _events().whereType(); + Stream onSessionUpdated() { + return _events().whereType(); } @override @@ -65,13 +66,31 @@ class MethodChannelSceneViewFlutter extends SceneviewFlutterPlatform { .add(TrackingFailureReason.values[call.arguments as int]); break; case 'onSessionUpdated': - _mapEventStreamController.add(call.arguments); + try { + final map = _getArgumentDictionary(call); + + if (map.containsKey('planes')) { + print('----------- contains planes'); + if ((map['planes'] as List).isNotEmpty) { + print('-----------$map'); + + _mapEventStreamController.add(SessionFrame.fromJson(map)); + } + } + } catch (ex, st) { + print('ERROR: $ex'); + print('ERROR: $st'); + } break; default: throw MissingPluginException(); } } + Map _getArgumentDictionary(MethodCall call) { + return Map.from(call.arguments); + } + @override void dispose(int sceneId) {} } diff --git a/lib/sceneview_flutter_platform_interface.dart b/lib/sceneview_flutter_platform_interface.dart index acb7ea7..edd0b37 100644 --- a/lib/sceneview_flutter_platform_interface.dart +++ b/lib/sceneview_flutter_platform_interface.dart @@ -1,5 +1,6 @@ import 'package:plugin_platform_interface/plugin_platform_interface.dart'; import 'package:sceneview_flutter/sceneview_node.dart'; +import 'package:sceneview_flutter/session_frame.dart'; import 'package:sceneview_flutter/tracking_failure_reason.dart'; import 'sceneview_flutter_method_channel.dart'; @@ -33,7 +34,7 @@ abstract class SceneviewFlutterPlatform extends PlatformInterface { throw UnimplementedError('addNode() has not been implemented.'); } - Stream onSessionUpdated() { + Stream onSessionUpdated() { throw UnimplementedError('onSessionUpdated() has not been implemented.'); } diff --git a/lib/session_frame.dart b/lib/session_frame.dart new file mode 100644 index 0000000..b078f95 --- /dev/null +++ b/lib/session_frame.dart @@ -0,0 +1,18 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:sceneview_flutter/plane.dart'; +import 'package:sceneview_flutter/pose.dart'; + +part 'session_frame.freezed.dart'; + +part 'session_frame.g.dart'; + +@freezed +class SessionFrame with _$SessionFrame { + const factory SessionFrame({ + @Default([]) List planes, + Pose? centerPose, + }) = _SessionFrame; + + factory SessionFrame.fromJson(Map json) => + _$SessionFrameFromJson(json); +} diff --git a/lib/session_frame.freezed.dart b/lib/session_frame.freezed.dart new file mode 100644 index 0000000..1dfa6c7 --- /dev/null +++ b/lib/session_frame.freezed.dart @@ -0,0 +1,179 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'session_frame.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); + +SessionFrame _$SessionFrameFromJson(Map json) { + return _SessionFrame.fromJson(json); +} + +/// @nodoc +mixin _$SessionFrame { + List get planes => throw _privateConstructorUsedError; + Pose? get centerPose => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $SessionFrameCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $SessionFrameCopyWith<$Res> { + factory $SessionFrameCopyWith( + SessionFrame value, $Res Function(SessionFrame) then) = + _$SessionFrameCopyWithImpl<$Res, SessionFrame>; + @useResult + $Res call({List planes, Pose? centerPose}); +} + +/// @nodoc +class _$SessionFrameCopyWithImpl<$Res, $Val extends SessionFrame> + implements $SessionFrameCopyWith<$Res> { + _$SessionFrameCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? planes = null, + Object? centerPose = freezed, + }) { + return _then(_value.copyWith( + planes: null == planes + ? _value.planes + : planes // ignore: cast_nullable_to_non_nullable + as List, + centerPose: freezed == centerPose + ? _value.centerPose + : centerPose // ignore: cast_nullable_to_non_nullable + as Pose?, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$SessionFrameImplCopyWith<$Res> + implements $SessionFrameCopyWith<$Res> { + factory _$$SessionFrameImplCopyWith( + _$SessionFrameImpl value, $Res Function(_$SessionFrameImpl) then) = + __$$SessionFrameImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({List planes, Pose? centerPose}); +} + +/// @nodoc +class __$$SessionFrameImplCopyWithImpl<$Res> + extends _$SessionFrameCopyWithImpl<$Res, _$SessionFrameImpl> + implements _$$SessionFrameImplCopyWith<$Res> { + __$$SessionFrameImplCopyWithImpl( + _$SessionFrameImpl _value, $Res Function(_$SessionFrameImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? planes = null, + Object? centerPose = freezed, + }) { + return _then(_$SessionFrameImpl( + planes: null == planes + ? _value._planes + : planes // ignore: cast_nullable_to_non_nullable + as List, + centerPose: freezed == centerPose + ? _value.centerPose + : centerPose // ignore: cast_nullable_to_non_nullable + as Pose?, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$SessionFrameImpl implements _SessionFrame { + const _$SessionFrameImpl( + {final List planes = const [], this.centerPose}) + : _planes = planes; + + factory _$SessionFrameImpl.fromJson(Map json) => + _$$SessionFrameImplFromJson(json); + + final List _planes; + @override + @JsonKey() + List get planes { + if (_planes is EqualUnmodifiableListView) return _planes; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_planes); + } + + @override + final Pose? centerPose; + + @override + String toString() { + return 'SessionFrame(planes: $planes, centerPose: $centerPose)'; + } + + @override + bool operator ==(dynamic other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$SessionFrameImpl && + const DeepCollectionEquality().equals(other._planes, _planes) && + (identical(other.centerPose, centerPose) || + other.centerPose == centerPose)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash( + runtimeType, const DeepCollectionEquality().hash(_planes), centerPose); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$SessionFrameImplCopyWith<_$SessionFrameImpl> get copyWith => + __$$SessionFrameImplCopyWithImpl<_$SessionFrameImpl>(this, _$identity); + + @override + Map toJson() { + return _$$SessionFrameImplToJson( + this, + ); + } +} + +abstract class _SessionFrame implements SessionFrame { + const factory _SessionFrame( + {final List planes, final Pose? centerPose}) = _$SessionFrameImpl; + + factory _SessionFrame.fromJson(Map json) = + _$SessionFrameImpl.fromJson; + + @override + List get planes; + @override + Pose? get centerPose; + @override + @JsonKey(ignore: true) + _$$SessionFrameImplCopyWith<_$SessionFrameImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/session_frame.g.dart b/lib/session_frame.g.dart new file mode 100644 index 0000000..36b6bf4 --- /dev/null +++ b/lib/session_frame.g.dart @@ -0,0 +1,23 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'session_frame.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$SessionFrameImpl _$$SessionFrameImplFromJson(Map json) => _$SessionFrameImpl( + planes: (json['planes'] as List?) + ?.map((e) => Plane.fromJson(Map.from(e as Map))) + .toList() ?? + const [], + centerPose: json['centerPose'] == null + ? null + : Pose.fromJson(Map.from(json['centerPose'] as Map)), + ); + +Map _$$SessionFrameImplToJson(_$SessionFrameImpl instance) => + { + 'planes': instance.planes.map((e) => e.toJson()).toList(), + 'centerPose': instance.centerPose?.toJson(), + }; diff --git a/lib/vector_converter.dart b/lib/vector_converter.dart new file mode 100644 index 0000000..f9e40e2 --- /dev/null +++ b/lib/vector_converter.dart @@ -0,0 +1,34 @@ +import 'package:json_annotation/json_annotation.dart'; +import 'package:vector_math/vector_math.dart'; + +class Vector3Converter implements JsonConverter> { + const Vector3Converter(); + + @override + Vector3 fromJson(List json) { + return Vector3(json[0], json[1], json[2]); + } + + @override + List toJson(Vector3 object) { + final list = List.filled(3, 0.0); + object.copyIntoArray(list); + return list; + } +} + +class Vector4Converter implements JsonConverter> { + const Vector4Converter(); + + @override + Vector4 fromJson(List json) { + return Vector4(json[0], json[1], json[2], json[3]); + } + + @override + List toJson(Vector4 object) { + final list = List.filled(4, 0.0); + object.copyIntoArray(list); + return list; + } +} \ No newline at end of file diff --git a/pubspec.yaml b/pubspec.yaml index f6e6b0e..4bc16f6 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -14,6 +14,7 @@ dependencies: freezed_annotation: ^2.4.1 json_annotation: ^4.8.1 stream_transform: ^2.1.0 + vector_math: ^2.1.4 dev_dependencies: flutter_test: From bdcc5fa08d63bbbf9e8fc4892a193bf2fb087721 Mon Sep 17 00:00:00 2001 From: Gian Marco Di Francesco Date: Thu, 16 Nov 2023 20:33:27 +0100 Subject: [PATCH 6/6] Fixed example to pose a object into center pose of a detected plane --- .../sceneview_flutter/FlutterSceneViewNode.kt | 48 ++-- .../sceneview_flutter/SceneViewWrapper.kt | 7 +- example/lib/main.dart | 23 +- lib/plane.dart | 2 + lib/plane.freezed.dart | 33 ++- lib/plane.g.dart | 4 + lib/sceneview_flutter_method_channel.dart | 2 +- lib/sceneview_node.dart | 41 ++-- lib/sceneview_node.freezed.dart | 225 ++++++++++++++++++ lib/sceneview_node.g.dart | 39 +++ lib/session_frame.dart | 1 - lib/session_frame.freezed.dart | 36 +-- lib/session_frame.g.dart | 4 - 13 files changed, 374 insertions(+), 91 deletions(-) create mode 100644 lib/sceneview_node.freezed.dart create mode 100644 lib/sceneview_node.g.dart diff --git a/android/src/main/kotlin/io/github/sceneview/sceneview_flutter/FlutterSceneViewNode.kt b/android/src/main/kotlin/io/github/sceneview/sceneview_flutter/FlutterSceneViewNode.kt index 7a70738..fcfcd7d 100644 --- a/android/src/main/kotlin/io/github/sceneview/sceneview_flutter/FlutterSceneViewNode.kt +++ b/android/src/main/kotlin/io/github/sceneview/sceneview_flutter/FlutterSceneViewNode.kt @@ -1,10 +1,11 @@ package io.github.sceneview.sceneview_flutter import dev.romainguy.kotlin.math.Float3 +import dev.romainguy.kotlin.math.Quaternion abstract class FlutterSceneViewNode( val position: Float3 = Float3(0f, 0f, 0f), - val rotation: Float3 = Float3(0f, 0f, 0f), + val rotation: Quaternion = Quaternion(0f, 0f, 0f), val scale: Float3 = Float3(0f, 0f, 0f), val scaleUnits: Float = 1.0f, ) { @@ -13,9 +14,9 @@ abstract class FlutterSceneViewNode( fun from(map: Map): FlutterSceneViewNode { val fileLocation = map["fileLocation"] as String? if (fileLocation != null) { - val p = FlutterPosition.from(map["position"] as Map?) - val r = FlutterRotation.from(map["rotation"] as Map?) - val s = FlutterScale.from(map["scale"] as Map?) + val p = FlutterPosition.from(map["position"] as List?) + val r = FlutterRotation.from(map["rotation"] as List?) + val s = FlutterScale.from(map["scale"] as List?) val scaleUnits = map["scaleUnits"] as Float? return FlutterReferenceNode( fileLocation, @@ -34,7 +35,7 @@ abstract class FlutterSceneViewNode( class FlutterReferenceNode( val fileLocation: String, position: Float3, - rotation: Float3, + rotation: Quaternion, scale: Float3, scaleUnits: Float ) : @@ -42,41 +43,42 @@ class FlutterReferenceNode( class FlutterPosition(val position: Float3) { companion object { - fun from(map: Map?): FlutterPosition { - if (map == null) { + fun from(list: List?): FlutterPosition { + if (list == null) { return FlutterPosition(Float3(0f, 0f, 0f)) } - val x = (map["x"] as Double).toFloat() - val y = (map["y"] as Double).toFloat() - val z = (map["z"] as Double).toFloat() + val x = (list[0] as Double).toFloat() + val y = (list[1] as Double).toFloat() + val z = (list[2] as Double).toFloat() return FlutterPosition(Float3(x, y, z)) } } } -class FlutterRotation(val rotation: Float3) { +class FlutterRotation(val rotation: Quaternion) { companion object { - fun from(map: Map?): FlutterRotation { - if (map == null) { - return FlutterRotation(Float3(0f, 0f, 0f)) + fun from(list: List?): FlutterRotation { + if (list == null) { + return FlutterRotation(Quaternion(0f, 0f, 0f, 0f)) } - val x = (map["x"] as Double).toFloat() - val y = (map["y"] as Double).toFloat() - val z = (map["z"] as Double).toFloat() - return FlutterRotation(Float3(x, y, z)) + val x = (list[0] as Double).toFloat() + val y = (list[1] as Double).toFloat() + val z = (list[2] as Double).toFloat() + val w = (list[3] as Double).toFloat() + return FlutterRotation(Quaternion(x, y, z, w)) } } } class FlutterScale(val scale: Float3) { companion object { - fun from(map: Map?): FlutterScale { - if (map == null) { + fun from(list: List?): FlutterScale { + if (list == null) { return FlutterScale(Float3(0f, 0f, 0f)) } - val x = (map["x"] as Double).toFloat() - val y = (map["y"] as Double).toFloat() - val z = (map["z"] as Double).toFloat() + val x = (list[0] as Double).toFloat() + val y = (list[1] as Double).toFloat() + val z = (list[2] as Double).toFloat() return FlutterScale(Float3(x, y, z)) } } diff --git a/android/src/main/kotlin/io/github/sceneview/sceneview_flutter/SceneViewWrapper.kt b/android/src/main/kotlin/io/github/sceneview/sceneview_flutter/SceneViewWrapper.kt index e354b7a..41a5630 100644 --- a/android/src/main/kotlin/io/github/sceneview/sceneview_flutter/SceneViewWrapper.kt +++ b/android/src/main/kotlin/io/github/sceneview/sceneview_flutter/SceneViewWrapper.kt @@ -7,6 +7,7 @@ import android.view.View import android.widget.FrameLayout import androidx.lifecycle.Lifecycle import com.google.ar.core.Config +import dev.romainguy.kotlin.math.Float3 import io.flutter.plugin.common.BinaryMessenger import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodChannel @@ -161,7 +162,11 @@ class SceneViewWrapper( val modelNode = ModelNode(modelInstance = model, scaleToUnits = 1.0f).apply { transform( position = flutterNode.position, - rotation = flutterNode.rotation, + rotation = Float3( + flutterNode.rotation.x, + flutterNode.rotation.y, + flutterNode.rotation.z + ), //scale = flutterNode.scale, ) //scaleToUnitsCube(flutterNode.scaleUnits) diff --git a/example/lib/main.dart b/example/lib/main.dart index e359da4..de78ef8 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -24,6 +24,9 @@ class _MyAppState extends State { TrackingFailureReason? reason; bool posed = false; + + late SceneViewController sceneViewController; + @override Widget build(BuildContext context) { return MaterialApp( @@ -47,6 +50,7 @@ class _MyAppState extends State { ], onViewCreated: (controller) { print('flutter: onViewCreated'); + sceneViewController = controller; // controller.addNode( // SceneViewNode( // fileLocation: 'assets/models/MaterialSuite.glb', @@ -57,15 +61,16 @@ class _MyAppState extends State { }, onSessionUpdated: (frame) { print('onSessionUpdated: $frame'); - // if(!posed){ - // controller.addNode( - // SceneViewNode( - // fileLocation: 'assets/models/MaterialSuite.glb', - // // position: frame.centerPose. - // // rotation: KotlinFloat3(x: 15), - // ), - // ); - // } + if (!posed && frame.planes.isNotEmpty) { + sceneViewController.addNode( + SceneViewNode( + fileLocation: 'assets/models/MaterialSuite.glb', + position: frame.planes.first.centerPose?.translation, + rotation: frame.planes.first.centerPose?.rotation, + ), + ); + posed = true; + } }, onTrackingFailureChanged: (reason) { print('onTrackingFailureChanged: $reason'); diff --git a/lib/plane.dart b/lib/plane.dart index 44b1a46..f92e969 100644 --- a/lib/plane.dart +++ b/lib/plane.dart @@ -1,5 +1,6 @@ import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:sceneview_flutter/plane_type.dart'; +import 'package:sceneview_flutter/pose.dart'; part 'plane.freezed.dart'; @@ -9,6 +10,7 @@ part 'plane.g.dart'; class Plane with _$Plane { const factory Plane({ @JsonEnum() PlaneType? type, + Pose? centerPose, }) = _Plane; factory Plane.fromJson(Map json) => _$PlaneFromJson(json); diff --git a/lib/plane.freezed.dart b/lib/plane.freezed.dart index 80457e3..fa265ef 100644 --- a/lib/plane.freezed.dart +++ b/lib/plane.freezed.dart @@ -22,6 +22,7 @@ Plane _$PlaneFromJson(Map json) { mixin _$Plane { @JsonEnum() PlaneType? get type => throw _privateConstructorUsedError; + Pose? get centerPose => throw _privateConstructorUsedError; Map toJson() => throw _privateConstructorUsedError; @JsonKey(ignore: true) @@ -33,7 +34,7 @@ abstract class $PlaneCopyWith<$Res> { factory $PlaneCopyWith(Plane value, $Res Function(Plane) then) = _$PlaneCopyWithImpl<$Res, Plane>; @useResult - $Res call({@JsonEnum() PlaneType? type}); + $Res call({@JsonEnum() PlaneType? type, Pose? centerPose}); } /// @nodoc @@ -50,12 +51,17 @@ class _$PlaneCopyWithImpl<$Res, $Val extends Plane> @override $Res call({ Object? type = freezed, + Object? centerPose = freezed, }) { return _then(_value.copyWith( type: freezed == type ? _value.type : type // ignore: cast_nullable_to_non_nullable as PlaneType?, + centerPose: freezed == centerPose + ? _value.centerPose + : centerPose // ignore: cast_nullable_to_non_nullable + as Pose?, ) as $Val); } } @@ -67,7 +73,7 @@ abstract class _$$PlaneImplCopyWith<$Res> implements $PlaneCopyWith<$Res> { __$$PlaneImplCopyWithImpl<$Res>; @override @useResult - $Res call({@JsonEnum() PlaneType? type}); + $Res call({@JsonEnum() PlaneType? type, Pose? centerPose}); } /// @nodoc @@ -82,12 +88,17 @@ class __$$PlaneImplCopyWithImpl<$Res> @override $Res call({ Object? type = freezed, + Object? centerPose = freezed, }) { return _then(_$PlaneImpl( type: freezed == type ? _value.type : type // ignore: cast_nullable_to_non_nullable as PlaneType?, + centerPose: freezed == centerPose + ? _value.centerPose + : centerPose // ignore: cast_nullable_to_non_nullable + as Pose?, )); } } @@ -95,7 +106,7 @@ class __$$PlaneImplCopyWithImpl<$Res> /// @nodoc @JsonSerializable() class _$PlaneImpl implements _Plane { - const _$PlaneImpl({@JsonEnum() this.type}); + const _$PlaneImpl({@JsonEnum() this.type, this.centerPose}); factory _$PlaneImpl.fromJson(Map json) => _$$PlaneImplFromJson(json); @@ -103,10 +114,12 @@ class _$PlaneImpl implements _Plane { @override @JsonEnum() final PlaneType? type; + @override + final Pose? centerPose; @override String toString() { - return 'Plane(type: $type)'; + return 'Plane(type: $type, centerPose: $centerPose)'; } @override @@ -114,12 +127,14 @@ class _$PlaneImpl implements _Plane { return identical(this, other) || (other.runtimeType == runtimeType && other is _$PlaneImpl && - (identical(other.type, type) || other.type == type)); + (identical(other.type, type) || other.type == type) && + (identical(other.centerPose, centerPose) || + other.centerPose == centerPose)); } @JsonKey(ignore: true) @override - int get hashCode => Object.hash(runtimeType, type); + int get hashCode => Object.hash(runtimeType, type, centerPose); @JsonKey(ignore: true) @override @@ -136,7 +151,9 @@ class _$PlaneImpl implements _Plane { } abstract class _Plane implements Plane { - const factory _Plane({@JsonEnum() final PlaneType? type}) = _$PlaneImpl; + const factory _Plane( + {@JsonEnum() final PlaneType? type, + final Pose? centerPose}) = _$PlaneImpl; factory _Plane.fromJson(Map json) = _$PlaneImpl.fromJson; @@ -144,6 +161,8 @@ abstract class _Plane implements Plane { @JsonEnum() PlaneType? get type; @override + Pose? get centerPose; + @override @JsonKey(ignore: true) _$$PlaneImplCopyWith<_$PlaneImpl> get copyWith => throw _privateConstructorUsedError; diff --git a/lib/plane.g.dart b/lib/plane.g.dart index 28a88aa..ed3a4c7 100644 --- a/lib/plane.g.dart +++ b/lib/plane.g.dart @@ -8,11 +8,15 @@ part of 'plane.dart'; _$PlaneImpl _$$PlaneImplFromJson(Map json) => _$PlaneImpl( type: $enumDecodeNullable(_$PlaneTypeEnumMap, json['type']), + centerPose: json['centerPose'] == null + ? null + : Pose.fromJson(Map.from(json['centerPose'] as Map)), ); Map _$$PlaneImplToJson(_$PlaneImpl instance) => { 'type': _$PlaneTypeEnumMap[instance.type], + 'centerPose': instance.centerPose?.toJson(), }; const _$PlaneTypeEnumMap = { diff --git a/lib/sceneview_flutter_method_channel.dart b/lib/sceneview_flutter_method_channel.dart index 3247e29..ea4ed3e 100644 --- a/lib/sceneview_flutter_method_channel.dart +++ b/lib/sceneview_flutter_method_channel.dart @@ -46,7 +46,7 @@ class MethodChannelSceneViewFlutter extends SceneviewFlutterPlatform { @override void addNode(SceneViewNode node) { - _channel?.invokeMethod('addNode', node.toMap()); + _channel?.invokeMethod('addNode', node.toJson()); } @override diff --git a/lib/sceneview_node.dart b/lib/sceneview_node.dart index f4ed406..0afb683 100644 --- a/lib/sceneview_node.dart +++ b/lib/sceneview_node.dart @@ -1,17 +1,24 @@ -class SceneViewNode { - final String fileLocation; - final KotlinFloat3? position; - final KotlinFloat3? rotation; - final double? scale; - - SceneViewNode({ - required this.fileLocation, - this.position, - this.rotation, - this.scale, - }); - - Map toMap() { +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:sceneview_flutter/vector_converter.dart'; +import 'package:vector_math/vector_math.dart'; + +part 'sceneview_node.freezed.dart'; + +part 'sceneview_node.g.dart'; + +@freezed +class SceneViewNode with _$SceneViewNode { + const factory SceneViewNode({ + required String fileLocation, + @Vector3Converter() Vector3? position, + @Vector4Converter() Vector4? rotation, + double? scale, + }) = _SceneViewNode; + + factory SceneViewNode.fromJson(Map json) => + _$SceneViewNodeFromJson(json); + +/* Map toMap() { final map = { 'fileLocation': fileLocation, 'position': position?.toMap(), @@ -20,10 +27,10 @@ class SceneViewNode { }; map.removeWhere((key, value) => value == null); return map; - } + }*/ } -class KotlinFloat3 { +/*class KotlinFloat3 { final double x; final double y; final double z; @@ -37,4 +44,4 @@ class KotlinFloat3 { 'z': z, }; } -} +}*/ diff --git a/lib/sceneview_node.freezed.dart b/lib/sceneview_node.freezed.dart new file mode 100644 index 0000000..fc200c8 --- /dev/null +++ b/lib/sceneview_node.freezed.dart @@ -0,0 +1,225 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'sceneview_node.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); + +SceneViewNode _$SceneViewNodeFromJson(Map json) { + return _SceneViewNode.fromJson(json); +} + +/// @nodoc +mixin _$SceneViewNode { + String get fileLocation => throw _privateConstructorUsedError; + @Vector3Converter() + Vector3? get position => throw _privateConstructorUsedError; + @Vector4Converter() + Vector4? get rotation => throw _privateConstructorUsedError; + double? get scale => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $SceneViewNodeCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $SceneViewNodeCopyWith<$Res> { + factory $SceneViewNodeCopyWith( + SceneViewNode value, $Res Function(SceneViewNode) then) = + _$SceneViewNodeCopyWithImpl<$Res, SceneViewNode>; + @useResult + $Res call( + {String fileLocation, + @Vector3Converter() Vector3? position, + @Vector4Converter() Vector4? rotation, + double? scale}); +} + +/// @nodoc +class _$SceneViewNodeCopyWithImpl<$Res, $Val extends SceneViewNode> + implements $SceneViewNodeCopyWith<$Res> { + _$SceneViewNodeCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? fileLocation = null, + Object? position = freezed, + Object? rotation = freezed, + Object? scale = freezed, + }) { + return _then(_value.copyWith( + fileLocation: null == fileLocation + ? _value.fileLocation + : fileLocation // ignore: cast_nullable_to_non_nullable + as String, + position: freezed == position + ? _value.position + : position // ignore: cast_nullable_to_non_nullable + as Vector3?, + rotation: freezed == rotation + ? _value.rotation + : rotation // ignore: cast_nullable_to_non_nullable + as Vector4?, + scale: freezed == scale + ? _value.scale + : scale // ignore: cast_nullable_to_non_nullable + as double?, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$SceneViewNodeImplCopyWith<$Res> + implements $SceneViewNodeCopyWith<$Res> { + factory _$$SceneViewNodeImplCopyWith( + _$SceneViewNodeImpl value, $Res Function(_$SceneViewNodeImpl) then) = + __$$SceneViewNodeImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {String fileLocation, + @Vector3Converter() Vector3? position, + @Vector4Converter() Vector4? rotation, + double? scale}); +} + +/// @nodoc +class __$$SceneViewNodeImplCopyWithImpl<$Res> + extends _$SceneViewNodeCopyWithImpl<$Res, _$SceneViewNodeImpl> + implements _$$SceneViewNodeImplCopyWith<$Res> { + __$$SceneViewNodeImplCopyWithImpl( + _$SceneViewNodeImpl _value, $Res Function(_$SceneViewNodeImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? fileLocation = null, + Object? position = freezed, + Object? rotation = freezed, + Object? scale = freezed, + }) { + return _then(_$SceneViewNodeImpl( + fileLocation: null == fileLocation + ? _value.fileLocation + : fileLocation // ignore: cast_nullable_to_non_nullable + as String, + position: freezed == position + ? _value.position + : position // ignore: cast_nullable_to_non_nullable + as Vector3?, + rotation: freezed == rotation + ? _value.rotation + : rotation // ignore: cast_nullable_to_non_nullable + as Vector4?, + scale: freezed == scale + ? _value.scale + : scale // ignore: cast_nullable_to_non_nullable + as double?, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$SceneViewNodeImpl implements _SceneViewNode { + const _$SceneViewNodeImpl( + {required this.fileLocation, + @Vector3Converter() this.position, + @Vector4Converter() this.rotation, + this.scale}); + + factory _$SceneViewNodeImpl.fromJson(Map json) => + _$$SceneViewNodeImplFromJson(json); + + @override + final String fileLocation; + @override + @Vector3Converter() + final Vector3? position; + @override + @Vector4Converter() + final Vector4? rotation; + @override + final double? scale; + + @override + String toString() { + return 'SceneViewNode(fileLocation: $fileLocation, position: $position, rotation: $rotation, scale: $scale)'; + } + + @override + bool operator ==(dynamic other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$SceneViewNodeImpl && + (identical(other.fileLocation, fileLocation) || + other.fileLocation == fileLocation) && + (identical(other.position, position) || + other.position == position) && + (identical(other.rotation, rotation) || + other.rotation == rotation) && + (identical(other.scale, scale) || other.scale == scale)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => + Object.hash(runtimeType, fileLocation, position, rotation, scale); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$SceneViewNodeImplCopyWith<_$SceneViewNodeImpl> get copyWith => + __$$SceneViewNodeImplCopyWithImpl<_$SceneViewNodeImpl>(this, _$identity); + + @override + Map toJson() { + return _$$SceneViewNodeImplToJson( + this, + ); + } +} + +abstract class _SceneViewNode implements SceneViewNode { + const factory _SceneViewNode( + {required final String fileLocation, + @Vector3Converter() final Vector3? position, + @Vector4Converter() final Vector4? rotation, + final double? scale}) = _$SceneViewNodeImpl; + + factory _SceneViewNode.fromJson(Map json) = + _$SceneViewNodeImpl.fromJson; + + @override + String get fileLocation; + @override + @Vector3Converter() + Vector3? get position; + @override + @Vector4Converter() + Vector4? get rotation; + @override + double? get scale; + @override + @JsonKey(ignore: true) + _$$SceneViewNodeImplCopyWith<_$SceneViewNodeImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/sceneview_node.g.dart b/lib/sceneview_node.g.dart new file mode 100644 index 0000000..22cc481 --- /dev/null +++ b/lib/sceneview_node.g.dart @@ -0,0 +1,39 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'sceneview_node.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$SceneViewNodeImpl _$$SceneViewNodeImplFromJson(Map json) => + _$SceneViewNodeImpl( + fileLocation: json['fileLocation'] as String, + position: _$JsonConverterFromJson, Vector3>( + json['position'], const Vector3Converter().fromJson), + rotation: _$JsonConverterFromJson, Vector4>( + json['rotation'], const Vector4Converter().fromJson), + scale: (json['scale'] as num?)?.toDouble(), + ); + +Map _$$SceneViewNodeImplToJson(_$SceneViewNodeImpl instance) => + { + 'fileLocation': instance.fileLocation, + 'position': _$JsonConverterToJson, Vector3>( + instance.position, const Vector3Converter().toJson), + 'rotation': _$JsonConverterToJson, Vector4>( + instance.rotation, const Vector4Converter().toJson), + 'scale': instance.scale, + }; + +Value? _$JsonConverterFromJson( + Object? json, + Value? Function(Json json) fromJson, +) => + json == null ? null : fromJson(json as Json); + +Json? _$JsonConverterToJson( + Value? value, + Json? Function(Value value) toJson, +) => + value == null ? null : toJson(value); diff --git a/lib/session_frame.dart b/lib/session_frame.dart index b078f95..41fef66 100644 --- a/lib/session_frame.dart +++ b/lib/session_frame.dart @@ -10,7 +10,6 @@ part 'session_frame.g.dart'; class SessionFrame with _$SessionFrame { const factory SessionFrame({ @Default([]) List planes, - Pose? centerPose, }) = _SessionFrame; factory SessionFrame.fromJson(Map json) => diff --git a/lib/session_frame.freezed.dart b/lib/session_frame.freezed.dart index 1dfa6c7..3d0a961 100644 --- a/lib/session_frame.freezed.dart +++ b/lib/session_frame.freezed.dart @@ -21,7 +21,6 @@ SessionFrame _$SessionFrameFromJson(Map json) { /// @nodoc mixin _$SessionFrame { List get planes => throw _privateConstructorUsedError; - Pose? get centerPose => throw _privateConstructorUsedError; Map toJson() => throw _privateConstructorUsedError; @JsonKey(ignore: true) @@ -35,7 +34,7 @@ abstract class $SessionFrameCopyWith<$Res> { SessionFrame value, $Res Function(SessionFrame) then) = _$SessionFrameCopyWithImpl<$Res, SessionFrame>; @useResult - $Res call({List planes, Pose? centerPose}); + $Res call({List planes}); } /// @nodoc @@ -52,17 +51,12 @@ class _$SessionFrameCopyWithImpl<$Res, $Val extends SessionFrame> @override $Res call({ Object? planes = null, - Object? centerPose = freezed, }) { return _then(_value.copyWith( planes: null == planes ? _value.planes : planes // ignore: cast_nullable_to_non_nullable as List, - centerPose: freezed == centerPose - ? _value.centerPose - : centerPose // ignore: cast_nullable_to_non_nullable - as Pose?, ) as $Val); } } @@ -75,7 +69,7 @@ abstract class _$$SessionFrameImplCopyWith<$Res> __$$SessionFrameImplCopyWithImpl<$Res>; @override @useResult - $Res call({List planes, Pose? centerPose}); + $Res call({List planes}); } /// @nodoc @@ -90,17 +84,12 @@ class __$$SessionFrameImplCopyWithImpl<$Res> @override $Res call({ Object? planes = null, - Object? centerPose = freezed, }) { return _then(_$SessionFrameImpl( planes: null == planes ? _value._planes : planes // ignore: cast_nullable_to_non_nullable as List, - centerPose: freezed == centerPose - ? _value.centerPose - : centerPose // ignore: cast_nullable_to_non_nullable - as Pose?, )); } } @@ -108,8 +97,7 @@ class __$$SessionFrameImplCopyWithImpl<$Res> /// @nodoc @JsonSerializable() class _$SessionFrameImpl implements _SessionFrame { - const _$SessionFrameImpl( - {final List planes = const [], this.centerPose}) + const _$SessionFrameImpl({final List planes = const []}) : _planes = planes; factory _$SessionFrameImpl.fromJson(Map json) => @@ -124,12 +112,9 @@ class _$SessionFrameImpl implements _SessionFrame { return EqualUnmodifiableListView(_planes); } - @override - final Pose? centerPose; - @override String toString() { - return 'SessionFrame(planes: $planes, centerPose: $centerPose)'; + return 'SessionFrame(planes: $planes)'; } @override @@ -137,15 +122,13 @@ class _$SessionFrameImpl implements _SessionFrame { return identical(this, other) || (other.runtimeType == runtimeType && other is _$SessionFrameImpl && - const DeepCollectionEquality().equals(other._planes, _planes) && - (identical(other.centerPose, centerPose) || - other.centerPose == centerPose)); + const DeepCollectionEquality().equals(other._planes, _planes)); } @JsonKey(ignore: true) @override - int get hashCode => Object.hash( - runtimeType, const DeepCollectionEquality().hash(_planes), centerPose); + int get hashCode => + Object.hash(runtimeType, const DeepCollectionEquality().hash(_planes)); @JsonKey(ignore: true) @override @@ -162,8 +145,7 @@ class _$SessionFrameImpl implements _SessionFrame { } abstract class _SessionFrame implements SessionFrame { - const factory _SessionFrame( - {final List planes, final Pose? centerPose}) = _$SessionFrameImpl; + const factory _SessionFrame({final List planes}) = _$SessionFrameImpl; factory _SessionFrame.fromJson(Map json) = _$SessionFrameImpl.fromJson; @@ -171,8 +153,6 @@ abstract class _SessionFrame implements SessionFrame { @override List get planes; @override - Pose? get centerPose; - @override @JsonKey(ignore: true) _$$SessionFrameImplCopyWith<_$SessionFrameImpl> get copyWith => throw _privateConstructorUsedError; diff --git a/lib/session_frame.g.dart b/lib/session_frame.g.dart index 36b6bf4..ce91dcc 100644 --- a/lib/session_frame.g.dart +++ b/lib/session_frame.g.dart @@ -11,13 +11,9 @@ _$SessionFrameImpl _$$SessionFrameImplFromJson(Map json) => _$SessionFrameImpl( ?.map((e) => Plane.fromJson(Map.from(e as Map))) .toList() ?? const [], - centerPose: json['centerPose'] == null - ? null - : Pose.fromJson(Map.from(json['centerPose'] as Map)), ); Map _$$SessionFrameImplToJson(_$SessionFrameImpl instance) => { 'planes': instance.planes.map((e) => e.toJson()).toList(), - 'centerPose': instance.centerPose?.toJson(), };