1use core::ops::Mul;
2
3use super::Transform;
4use bevy_math::{ops, Affine3A, Dir3, Isometry3d, Mat4, Quat, Vec3, Vec3A};
5use derive_more::derive::From;
6
7#[cfg(all(feature = "bevy_reflect", feature = "serialize"))]
8use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
9
10#[cfg(feature = "bevy-support")]
11use bevy_ecs::component::Component;
12
13#[cfg(feature = "bevy_reflect")]
14use {
15 bevy_ecs::reflect::ReflectComponent,
16 bevy_reflect::{std_traits::ReflectDefault, Reflect},
17};
18
19#[derive(#[automatically_derived]
impl ::core::fmt::Debug for GlobalTransform {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
::core::fmt::Formatter::debug_tuple_field1_finish(f,
"GlobalTransform", &&self.0)
}
}Debug, #[automatically_derived]
impl ::core::cmp::PartialEq for GlobalTransform {
#[inline]
fn eq(&self, other: &GlobalTransform) -> bool { self.0 == other.0 }
}PartialEq, #[automatically_derived]
impl ::core::clone::Clone for GlobalTransform {
#[inline]
fn clone(&self) -> GlobalTransform {
let _: ::core::clone::AssertParamIsClone<Affine3A>;
*self
}
}Clone, #[automatically_derived]
impl ::core::marker::Copy for GlobalTransform { }Copy, #[allow(deprecated)]
#[allow(unreachable_code)]
#[automatically_derived]
impl derive_more::core::convert::From<(Affine3A)> for GlobalTransform {
#[inline]
fn from(value: (Affine3A)) -> Self { GlobalTransform(value) }
}From)]
49#[cfg_attr(feature = "serialize", derive(#[doc(hidden)]
#[allow(non_upper_case_globals, unused_attributes, unused_qualifications,
clippy :: absolute_paths,)]
const _: () =
{
#[allow(unused_extern_crates, clippy :: useless_attribute)]
extern crate serde as _serde;
;
#[automatically_derived]
impl _serde::Serialize for GlobalTransform {
fn serialize<__S>(&self, __serializer: __S)
-> _serde::__private228::Result<__S::Ok, __S::Error> where
__S: _serde::Serializer {
_serde::Serializer::serialize_newtype_struct(__serializer,
"GlobalTransform", &self.0)
}
}
};serde::Serialize, #[doc(hidden)]
#[allow(non_upper_case_globals, unused_attributes, unused_qualifications,
clippy :: absolute_paths,)]
const _: () =
{
#[allow(unused_extern_crates, clippy :: useless_attribute)]
extern crate serde as _serde;
;
#[automatically_derived]
impl<'de> _serde::Deserialize<'de> for GlobalTransform {
fn deserialize<__D>(__deserializer: __D)
-> _serde::__private228::Result<Self, __D::Error> where
__D: _serde::Deserializer<'de> {
#[doc(hidden)]
struct __Visitor<'de> {
marker: _serde::__private228::PhantomData<GlobalTransform>,
lifetime: _serde::__private228::PhantomData<&'de ()>,
}
#[automatically_derived]
impl<'de> _serde::de::Visitor<'de> for __Visitor<'de> {
type Value = GlobalTransform;
fn expecting(&self,
__formatter: &mut _serde::__private228::Formatter)
-> _serde::__private228::fmt::Result {
_serde::__private228::Formatter::write_str(__formatter,
"tuple struct GlobalTransform")
}
#[inline]
fn visit_newtype_struct<__E>(self, __e: __E)
-> _serde::__private228::Result<Self::Value, __E::Error>
where __E: _serde::Deserializer<'de> {
let __field0: Affine3A =
<Affine3A as _serde::Deserialize>::deserialize(__e)?;
_serde::__private228::Ok(GlobalTransform(__field0))
}
#[inline]
fn visit_seq<__A>(self, mut __seq: __A)
-> _serde::__private228::Result<Self::Value, __A::Error>
where __A: _serde::de::SeqAccess<'de> {
let __field0 =
match _serde::de::SeqAccess::next_element::<Affine3A>(&mut __seq)?
{
_serde::__private228::Some(__value) => __value,
_serde::__private228::None =>
return _serde::__private228::Err(_serde::de::Error::invalid_length(0usize,
&"tuple struct GlobalTransform with 1 element")),
};
_serde::__private228::Ok(GlobalTransform(__field0))
}
}
_serde::Deserializer::deserialize_newtype_struct(__deserializer,
"GlobalTransform",
__Visitor {
marker: _serde::__private228::PhantomData::<GlobalTransform>,
lifetime: _serde::__private228::PhantomData,
})
}
}
};serde::Deserialize))]
50#[cfg_attr(feature = "bevy-support", derive(impl bevy_ecs::component::Component for GlobalTransform where
Self: ::core::marker::Send + ::core::marker::Sync + 'static {
const STORAGE_TYPE: bevy_ecs::component::StorageType =
bevy_ecs::component::StorageType::Table;
type Mutability = bevy_ecs::component::Mutable;
fn register_required_components(_requiree:
bevy_ecs::component::ComponentId,
required_components:
&mut bevy_ecs::component::RequiredComponentsRegistrator) {}
fn clone_behavior() -> bevy_ecs::component::ComponentCloneBehavior {
use bevy_ecs::component::{
DefaultCloneBehaviorBase, DefaultCloneBehaviorViaClone,
};
(&&&bevy_ecs::component::DefaultCloneBehaviorSpecialization::<Self>::default()).default_clone_behavior()
}
fn relationship_accessor()
->
::core::option::Option<bevy_ecs::relationship::ComponentRelationshipAccessor<Self>> {
::core::option::Option::None
}
}Component))]
51#[cfg_attr(
52 feature = "bevy_reflect",
53 derive(const _: () =
{
impl bevy_reflect::GetTypeRegistration for GlobalTransform where {
fn get_type_registration() -> bevy_reflect::TypeRegistration {
let mut registration =
bevy_reflect::TypeRegistration::of::<Self>();
registration.insert::<bevy_reflect::ReflectFromPtr>(bevy_reflect::FromType::<Self>::from_type());
registration.insert::<bevy_reflect::ReflectFromReflect>(bevy_reflect::FromType::<Self>::from_type());
registration.register_type_data::<ReflectComponent, Self>();
registration.register_type_data::<ReflectDefault, Self>();
registration.register_type_data::<ReflectSerialize, Self>();
registration.register_type_data::<ReflectDeserialize, Self>();
registration
}
#[inline(never)]
fn register_type_dependencies(registry:
&mut bevy_reflect::TypeRegistry) {
<Affine3A as
bevy_reflect::__macro_exports::RegisterForReflection>::__register(registry);
}
}
impl bevy_reflect::Typed for GlobalTransform where {
#[inline]
fn type_info() -> &'static bevy_reflect::TypeInfo {
static CELL: bevy_reflect::utility::NonGenericTypeInfoCell =
bevy_reflect::utility::NonGenericTypeInfoCell::new();
CELL.get_or_set(||
{
bevy_reflect::TypeInfo::TupleStruct(bevy_reflect::tuple_struct::TupleStructInfo::new::<Self>(&[bevy_reflect::UnnamedField::new::<Affine3A>(0usize)]))
})
}
}
#[allow(deprecated, reason =
"derives on a deprecated type shouldn't be considered a usage")]
impl bevy_reflect::TypePath for GlobalTransform where {
fn type_path() -> &'static str {
"bevy_transform::components::global_transform::GlobalTransform"
}
fn short_type_path() -> &'static str { "GlobalTransform" }
fn type_ident() -> ::core::option::Option<&'static str> {
::core::option::Option::Some("GlobalTransform")
}
fn crate_name() -> ::core::option::Option<&'static str> {
::core::option::Option::Some("bevy_transform::components::global_transform".split(':').next().unwrap())
}
fn module_path() -> ::core::option::Option<&'static str> {
::core::option::Option::Some("bevy_transform::components::global_transform")
}
}
impl bevy_reflect::Reflect for GlobalTransform where {
#[inline]
fn into_any(self:
bevy_reflect::__macro_exports::alloc_utils::Box<Self>)
->
bevy_reflect::__macro_exports::alloc_utils::Box<dyn ::core::any::Any> {
self
}
#[inline]
fn as_any(&self) -> &dyn ::core::any::Any { self }
#[inline]
fn as_any_mut(&mut self) -> &mut dyn ::core::any::Any { self }
#[inline]
fn into_reflect(self:
bevy_reflect::__macro_exports::alloc_utils::Box<Self>)
->
bevy_reflect::__macro_exports::alloc_utils::Box<dyn bevy_reflect::Reflect> {
self
}
#[inline]
fn as_reflect(&self) -> &dyn bevy_reflect::Reflect { self }
#[inline]
fn as_reflect_mut(&mut self) -> &mut dyn bevy_reflect::Reflect {
self
}
#[inline]
fn set(&mut self,
value:
bevy_reflect::__macro_exports::alloc_utils::Box<dyn bevy_reflect::Reflect>)
->
::core::result::Result<(),
bevy_reflect::__macro_exports::alloc_utils::Box<dyn bevy_reflect::Reflect>> {
*self = <dyn bevy_reflect::Reflect>::take(value)?;
::core::result::Result::Ok(())
}
}
impl bevy_reflect::tuple_struct::TupleStruct for GlobalTransform where
{
fn field(&self, index: usize)
-> ::core::option::Option<&dyn bevy_reflect::PartialReflect> {
match index {
0usize => ::core::option::Option::Some(&self.0),
_ => ::core::option::Option::None,
}
}
fn field_mut(&mut self, index: usize)
->
::core::option::Option<&mut dyn bevy_reflect::PartialReflect> {
match index {
0usize => ::core::option::Option::Some(&mut self.0),
_ => ::core::option::Option::None,
}
}
#[inline]
fn field_len(&self) -> usize { 1usize }
#[inline]
fn iter_fields(&self)
-> bevy_reflect::tuple_struct::TupleStructFieldIter {
bevy_reflect::tuple_struct::TupleStructFieldIter::new(self)
}
fn to_dynamic_tuple_struct(&self)
-> bevy_reflect::tuple_struct::DynamicTupleStruct {
let mut dynamic:
bevy_reflect::tuple_struct::DynamicTupleStruct =
::core::default::Default::default();
dynamic.set_represented_type(bevy_reflect::PartialReflect::get_represented_type_info(self));
dynamic.insert_boxed(bevy_reflect::PartialReflect::to_dynamic(&self.0));
dynamic
}
}
impl bevy_reflect::PartialReflect for GlobalTransform where {
#[inline]
fn get_represented_type_info(&self)
-> ::core::option::Option<&'static bevy_reflect::TypeInfo> {
::core::option::Option::Some(<Self as
bevy_reflect::Typed>::type_info())
}
#[inline]
fn try_apply(&mut self, value: &dyn bevy_reflect::PartialReflect)
-> ::core::result::Result<(), bevy_reflect::ApplyError> {
if let bevy_reflect::ReflectRef::TupleStruct(struct_value) =
bevy_reflect::PartialReflect::reflect_ref(value) {
for (i, value) in
::core::iter::Iterator::enumerate(bevy_reflect::tuple_struct::TupleStruct::iter_fields(struct_value))
{
if let ::core::option::Option::Some(v) =
bevy_reflect::tuple_struct::TupleStruct::field_mut(self, i)
{
bevy_reflect::PartialReflect::try_apply(v, value)?;
}
}
} else {
return ::core::result::Result::Err(bevy_reflect::ApplyError::MismatchedKinds {
from_kind: bevy_reflect::PartialReflect::reflect_kind(value),
to_kind: bevy_reflect::ReflectKind::TupleStruct,
});
}
::core::result::Result::Ok(())
}
#[inline]
fn reflect_kind(&self) -> bevy_reflect::ReflectKind {
bevy_reflect::ReflectKind::TupleStruct
}
#[inline]
fn reflect_ref(&self) -> bevy_reflect::ReflectRef {
bevy_reflect::ReflectRef::TupleStruct(self)
}
#[inline]
fn reflect_mut(&mut self) -> bevy_reflect::ReflectMut {
bevy_reflect::ReflectMut::TupleStruct(self)
}
#[inline]
fn reflect_owned(self:
bevy_reflect::__macro_exports::alloc_utils::Box<Self>)
-> bevy_reflect::ReflectOwned {
bevy_reflect::ReflectOwned::TupleStruct(self)
}
#[inline]
fn try_into_reflect(self:
bevy_reflect::__macro_exports::alloc_utils::Box<Self>)
->
::core::result::Result<bevy_reflect::__macro_exports::alloc_utils::Box<dyn bevy_reflect::Reflect>,
bevy_reflect::__macro_exports::alloc_utils::Box<dyn bevy_reflect::PartialReflect>> {
::core::result::Result::Ok(self)
}
#[inline]
fn try_as_reflect(&self)
-> ::core::option::Option<&dyn bevy_reflect::Reflect> {
::core::option::Option::Some(self)
}
#[inline]
fn try_as_reflect_mut(&mut self)
-> ::core::option::Option<&mut dyn bevy_reflect::Reflect> {
::core::option::Option::Some(self)
}
#[inline]
fn into_partial_reflect(self:
bevy_reflect::__macro_exports::alloc_utils::Box<Self>)
->
bevy_reflect::__macro_exports::alloc_utils::Box<dyn bevy_reflect::PartialReflect> {
self
}
#[inline]
fn as_partial_reflect(&self)
-> &dyn bevy_reflect::PartialReflect {
self
}
#[inline]
fn as_partial_reflect_mut(&mut self)
-> &mut dyn bevy_reflect::PartialReflect {
self
}
fn reflect_partial_eq(&self,
value: &dyn bevy_reflect::PartialReflect)
-> ::core::option::Option<bool> {
let value =
<dyn bevy_reflect::PartialReflect>::try_downcast_ref::<Self>(value);
if let ::core::option::Option::Some(value) = value {
::core::option::Option::Some(::core::cmp::PartialEq::eq(self,
value))
} else { ::core::option::Option::Some(false) }
}
fn reflect_partial_cmp(&self,
value: &dyn bevy_reflect::PartialReflect)
-> ::core::option::Option<::core::cmp::Ordering> {
(bevy_reflect::tuple_struct::tuple_struct_partial_cmp)(self,
value)
}
fn debug(&self, f: &mut ::core::fmt::Formatter<'_>)
-> ::core::fmt::Result {
::core::fmt::Debug::fmt(self, f)
}
#[inline]
fn reflect_clone(&self)
->
::core::result::Result<bevy_reflect::__macro_exports::alloc_utils::Box<dyn bevy_reflect::Reflect>,
bevy_reflect::ReflectCloneError> {
::core::result::Result::Ok(bevy_reflect::__macro_exports::alloc_utils::Box::new(::core::clone::Clone::clone(self)))
}
}
impl bevy_reflect::FromReflect for GlobalTransform where {
fn from_reflect(reflect: &dyn bevy_reflect::PartialReflect)
-> ::core::option::Option<Self> {
if let bevy_reflect::ReflectRef::TupleStruct(__ref_struct) =
bevy_reflect::PartialReflect::reflect_ref(reflect) {
let mut __this =
<Self as ::core::default::Default>::default();
if let ::core::option::Option::Some(__field) =
(||
<Affine3A as
bevy_reflect::FromReflect>::from_reflect(bevy_reflect::tuple_struct::TupleStruct::field(__ref_struct,
0)?))() {
__this.0 = __field;
}
::core::option::Option::Some(__this)
} else { ::core::option::Option::None }
}
}
};Reflect),
54 reflect(Component, Default, PartialEq, Debug, Clone)
55)]
56#[cfg_attr(
57 all(feature = "bevy_reflect", feature = "serialize"),
58 reflect(Serialize, Deserialize)
59)]
60pub struct GlobalTransform(Affine3A);
61
62macro_rules! impl_local_axis {
63 ($pos_name: ident, $neg_name: ident, $axis: ident) => {
64 #[doc=core::concat!("Return the local ", core::stringify!($pos_name), " vector (", core::stringify!($axis) ,").")]
65 #[inline]
66 pub fn $pos_name(&self) -> Dir3 {
67 Dir3::new_unchecked((self.0.matrix3 * Vec3::$axis).normalize())
68 }
69
70 #[doc=core::concat!("Return the local ", core::stringify!($neg_name), " vector (-", core::stringify!($axis) ,").")]
71 #[inline]
72 pub fn $neg_name(&self) -> Dir3 {
73 -self.$pos_name()
74 }
75 };
76}
77
78impl GlobalTransform {
79 pub const IDENTITY: Self = Self(Affine3A::IDENTITY);
81
82 #[doc(hidden)]
83 #[inline]
84 pub fn from_xyz(x: f32, y: f32, z: f32) -> Self {
85 Self::from_translation(Vec3::new(x, y, z))
86 }
87
88 #[doc(hidden)]
89 #[inline]
90 pub fn from_translation(translation: Vec3) -> Self {
91 GlobalTransform(Affine3A::from_translation(translation))
92 }
93
94 #[doc(hidden)]
95 #[inline]
96 pub fn from_rotation(rotation: Quat) -> Self {
97 GlobalTransform(Affine3A::from_rotation_translation(rotation, Vec3::ZERO))
98 }
99
100 #[doc(hidden)]
101 #[inline]
102 pub fn from_scale(scale: Vec3) -> Self {
103 GlobalTransform(Affine3A::from_scale(scale))
104 }
105
106 #[doc(hidden)]
107 #[inline]
108 pub fn from_isometry(iso: Isometry3d) -> Self {
109 Self(iso.into())
110 }
111
112 #[inline]
114 pub fn to_matrix(&self) -> Mat4 {
115 Mat4::from(self.0)
116 }
117
118 #[inline]
120 pub fn affine(&self) -> Affine3A {
121 self.0
122 }
123
124 #[inline]
129 pub fn compute_transform(&self) -> Transform {
130 let (scale, rotation, translation) = self.0.to_scale_rotation_translation();
131 Transform {
132 translation,
133 rotation,
134 scale,
135 }
136 }
137
138 #[inline]
147 pub fn to_isometry(&self) -> Isometry3d {
148 let (_, rotation, translation) = self.0.to_scale_rotation_translation();
149 Isometry3d::new(translation, rotation)
150 }
151
152 #[inline]
185 pub fn reparented_to(&self, parent: &GlobalTransform) -> Transform {
186 let relative_affine = parent.affine().inverse() * self.affine();
187 let (scale, rotation, translation) = relative_affine.to_scale_rotation_translation();
188 Transform {
189 translation,
190 rotation,
191 scale,
192 }
193 }
194
195 #[inline]
200 pub fn to_scale_rotation_translation(&self) -> (Vec3, Quat, Vec3) {
201 self.0.to_scale_rotation_translation()
202 }
203
204 #[doc = "Return the local right vector (X)."]
#[inline]
pub fn right(&self) -> Dir3 {
Dir3::new_unchecked((self.0.matrix3 * Vec3::X).normalize())
}
#[doc = "Return the local left vector (-X)."]
#[inline]
pub fn left(&self) -> Dir3 { -self.right() }impl_local_axis!(right, left, X);
205 #[doc = "Return the local up vector (Y)."]
#[inline]
pub fn up(&self) -> Dir3 {
Dir3::new_unchecked((self.0.matrix3 * Vec3::Y).normalize())
}
#[doc = "Return the local down vector (-Y)."]
#[inline]
pub fn down(&self) -> Dir3 { -self.up() }impl_local_axis!(up, down, Y);
206 #[doc = "Return the local back vector (Z)."]
#[inline]
pub fn back(&self) -> Dir3 {
Dir3::new_unchecked((self.0.matrix3 * Vec3::Z).normalize())
}
#[doc = "Return the local forward vector (-Z)."]
#[inline]
pub fn forward(&self) -> Dir3 { -self.back() }impl_local_axis!(back, forward, Z);
207
208 #[inline]
210 pub fn translation(&self) -> Vec3 {
211 self.0.translation.into()
212 }
213
214 #[inline]
216 pub fn translation_vec3a(&self) -> Vec3A {
217 self.0.translation
218 }
219
220 #[inline]
229 pub fn rotation(&self) -> Quat {
230 self.to_scale_rotation_translation().1
231 }
232
233 #[inline]
240 pub fn scale(&self) -> Vec3 {
241 let det = self.0.matrix3.determinant();
243 Vec3::new(
244 self.0.matrix3.x_axis.length() * ops::copysign(1., det),
245 self.0.matrix3.y_axis.length(),
246 self.0.matrix3.z_axis.length(),
247 )
248 }
249
250 #[inline]
252 pub fn radius_vec3a(&self, extents: Vec3A) -> f32 {
253 (self.0.matrix3 * extents).length()
254 }
255
256 #[inline]
290 pub fn transform_point(&self, point: Vec3) -> Vec3 {
291 self.0.transform_point3(point)
292 }
293
294 #[inline]
297 pub fn mul_transform(&self, transform: Transform) -> Self {
298 Self(self.0 * transform.compute_affine())
299 }
300}
301
302impl Default for GlobalTransform {
303 fn default() -> Self {
304 Self::IDENTITY
305 }
306}
307
308impl From<Transform> for GlobalTransform {
309 fn from(transform: Transform) -> Self {
310 Self(transform.compute_affine())
311 }
312}
313
314impl From<Mat4> for GlobalTransform {
315 fn from(world_from_local: Mat4) -> Self {
316 Self(Affine3A::from_mat4(world_from_local))
317 }
318}
319
320impl Mul<GlobalTransform> for GlobalTransform {
321 type Output = GlobalTransform;
322
323 #[inline]
324 fn mul(self, global_transform: GlobalTransform) -> Self::Output {
325 GlobalTransform(self.0 * global_transform.0)
326 }
327}
328
329impl Mul<Transform> for GlobalTransform {
330 type Output = GlobalTransform;
331
332 #[inline]
333 fn mul(self, transform: Transform) -> Self::Output {
334 self.mul_transform(transform)
335 }
336}
337
338impl Mul<Vec3> for GlobalTransform {
339 type Output = Vec3;
340
341 #[inline]
342 fn mul(self, value: Vec3) -> Self::Output {
343 self.transform_point(value)
344 }
345}
346
347#[cfg(test)]
348mod test {
349 use super::*;
350
351 use bevy_math::EulerRot::XYZ;
352
353 fn transform_equal(left: GlobalTransform, right: Transform) -> bool {
354 left.0.abs_diff_eq(right.compute_affine(), 0.01)
355 }
356
357 #[test]
358 fn reparented_to_transform_identity() {
359 fn reparent_to_same(t1: GlobalTransform, t2: GlobalTransform) -> Transform {
360 t2.mul_transform(t1.into()).reparented_to(&t2)
361 }
362 let t1 = GlobalTransform::from(Transform {
363 translation: Vec3::new(1034.0, 34.0, -1324.34),
364 rotation: Quat::from_euler(XYZ, 1.0, 0.9, 2.1),
365 scale: Vec3::new(1.0, 1.0, 1.0),
366 });
367 let t2 = GlobalTransform::from(Transform {
368 translation: Vec3::new(0.0, -54.493, 324.34),
369 rotation: Quat::from_euler(XYZ, 1.9, 0.3, 3.0),
370 scale: Vec3::new(1.345, 1.345, 1.345),
371 });
372 let retransformed = reparent_to_same(t1, t2);
373 assert!(
374 transform_equal(t1, retransformed),
375 "t1:{:#?} retransformed:{:#?}",
376 t1.compute_transform(),
377 retransformed,
378 );
379 }
380 #[test]
381 fn reparented_usecase() {
382 let t1 = GlobalTransform::from(Transform {
383 translation: Vec3::new(1034.0, 34.0, -1324.34),
384 rotation: Quat::from_euler(XYZ, 0.8, 1.9, 2.1),
385 scale: Vec3::new(10.9, 10.9, 10.9),
386 });
387 let t2 = GlobalTransform::from(Transform {
388 translation: Vec3::new(28.0, -54.493, 324.34),
389 rotation: Quat::from_euler(XYZ, 0.0, 3.1, 0.1),
390 scale: Vec3::new(0.9, 0.9, 0.9),
391 });
392 let reparented = t1.reparented_to(&t2);
394 let t1_prime = t2 * reparented;
395 assert!(
396 transform_equal(t1, t1_prime.into()),
397 "t1:{:#?} t1_prime:{:#?}",
398 t1.compute_transform(),
399 t1_prime.compute_transform(),
400 );
401 }
402
403 #[test]
404 fn scale() {
405 let test_values = [-42.42, 0., 42.42];
406 for x in test_values {
407 for y in test_values {
408 for z in test_values {
409 let scale = Vec3::new(x, y, z);
410 let gt = GlobalTransform::from_scale(scale);
411 assert_eq!(gt.scale(), gt.to_scale_rotation_translation().0);
412 }
413 }
414 }
415 }
416}