metaverse_messages 0.3.0

packet definitions for the open metaverse
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
use crate::{
    errors::ParseError,
    utils::{
        item_metadata::{Permissions, SaleInfo, SaleType},
        material::MaterialType,
        object_types::ObjectType,
        path::Path,
        sound::AttachedSound,
        texture_entry::TextureEntry,
    },
};
use glam::{bool, Quat, Vec3, Vec4};
use quick_xml::{
    escape::unescape,
    events::{BytesText, Event},
    Reader,
};
use rgb::Rgba;
use serde::{Deserialize, Serialize};
use std::{
    str::{from_utf8, FromStr},
    time::SystemTime,
};
use uuid::Uuid;

#[derive(Debug, Default, Clone, Serialize, Deserialize)]
/// The group of scenes received from the server, used for rendering meshes.
///
/// Scenes contain attributes for the meshes contained within them. They describe which meshes are
/// present, how they are textured, and how they are placed and parented in relation to each other.
/// This is the main structure for how multi-mesh objects are created in OpenSimulator. Upon upload
/// to the server, each multipart mesh is broken up into its component pieces, and given a unique
/// UUID. SceneGroups describe how those individual pieces should be reconstituted back into a
/// single mesh.
///
/// An example SceneGroup can be found here: https://opensimulator.org/wiki/Object_Formats
pub struct SceneGroup {
    /// The parts of the root object. These contain different meshes that make up the whole object.
    /// The root object is always at position 0 of the vec.
    pub parts: Vec<SceneObject>,
}
impl SceneGroup {
    /// Receive bytes from the server, and parse them as XML.
    ///
    /// Despite the majority of the project using things like LLSD, or LLSD-XML, this uses raw 2001 standard
    /// xml. XML is not a very good option, because of how hard it can be to iterate over data that
    /// can contain a variable number of entries, while having to read line by line.
    /// If this were JSON, life would be much better.
    pub fn from_xml(bytes: &[u8]) -> Result<Self, ParseError> {
        let mut root_object = SceneObject::default();
        let mut children = Vec::new();
        let mut reader = Reader::from_reader(bytes);
        let mut buf = Vec::new();
        let mut path: Vec<String> = Vec::new();
        loop {
            match reader.read_event_into(&mut buf)? {
                Event::Start(ref e) => {
                    path.push(from_utf8(e.name().as_ref())?.to_string());
                    // If we are at the part of the XML describing the other parts, create a new
                    // scene object that will be mutated by the read_until_scene_object_end
                    // function.
                    if path.ends_with(&[
                        "SceneObjectGroup".to_string(),
                        "OtherParts".to_string(),
                        "Part".to_string(),
                        "SceneObjectPart".to_string(),
                    ]) {
                        let mut obj = SceneObject::default();
                        SceneObject::read_scene_object(&mut reader, &mut obj, path.clone())?;
                        children.push(obj);
                        path.pop(); // pop "SceneObjectPart"
                    }
                }
                Event::End(_) => {
                    path.pop();
                }
                Event::Text(e) => {
                    let path_refs: Vec<&str> = path.iter().map(String::as_str).collect();
                    // If we are at the part of the XML describing the RootPart, mutate the
                    // root_object SceneOBject field by field.
                    if path_refs.starts_with(&["SceneObjectGroup", "RootPart", "SceneObjectPart"]) {
                        SceneObject::from_path_str(path_refs.clone(), e, &mut root_object, 3)?;
                    }
                }
                Event::Eof => break,
                _ => {}
            }

            buf.clear();
        }
        // set the root object to the first child.
        children.insert(0, root_object);
        Ok(SceneGroup { parts: children })
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
/// metadata for Scene objects. Includes things like ID, name, description, etc.
pub struct SceneMetadata {
    /// the name of the scene
    pub name: String,
    /// information about the item's sale properties, like price and cost to own
    pub sale_info: SaleInfo,
    /// Information about the item's permissions
    pub permissions: Permissions,
    /// the ID of the scene
    pub id: Uuid,
    /// The description of the scene. Could be used for building MUDs
    pub description: String,
    /// the time the scene was created
    pub created_at: std::time::SystemTime,
    /// TODO: unknown
    pub flags: i32,
}
impl Default for SceneMetadata {
    fn default() -> Self {
        Self {
            name: Default::default(),
            sale_info: Default::default(),
            permissions: Default::default(),
            id: Default::default(),
            description: Default::default(),
            flags: Default::default(),
            created_at: SystemTime::UNIX_EPOCH,
        }
    }
}

#[derive(Default, Debug, Clone, Serialize, Deserialize)]
/// Shape information. This includes data for building meshes, stretching objects, creating sculpt
/// texture objects, light and physics.
pub struct Shape {
    /// Path information, used for drawing paths
    pub path: Path,
    /// TODO: Unknown
    pub extra_params: Vec<u8>,
    /// The type of the object
    pub pcode: ObjectType,
    /// The state of the object
    pub state: i32,
    /// The last point the object was attached to. Helps reattach items to the same place when
    /// logging in or putting on again.
    pub last_attach_point: i32,
    /// Sculpt information. Contains data for creating a Scullpted prim (now legacy), and
    /// information about the mesh
    pub sculpt: Sculpt,
    /// Information about the flex attributes
    /// Contains information about how the object bends, sways and moves using gravity, tension and
    /// wind.
    pub flex: Flex,
    /// Information about the light attributes.
    /// Contains information about how the object emits lights.
    pub light: Light,
    /// Informaiton about the shape's texture
    pub texture: TextureEntry,
    /// TODO: Not handled yet
    pub texture_animation: Vec<u8>,
    /// TODO: not handled yet
    pub particle_system: Vec<u8>,
}

#[derive(Debug, Default, Clone, Serialize, Deserialize)]
/// Sculpt information
/// <https://wiki.secondlife.com/wiki/Sculpted_Prims:_FAQ>
///
/// Sculpts were used before meshes became widespread. They were created using SculptTextures,
/// which was a standard RGB texture where the R,G and B values were mapped onto values in x,y,z
/// space. This allowed low-bandwith distribution for meshes, and easy animation. Flash animations
/// could easily generate animations, by playing the sculpt textures like a traditional animation,
/// and compression and decompression were very straightforward.
///
/// This has been almost completely replaced by modern meshes, but the name "Sculpt" remains the
/// standard.
pub struct Sculpt {
    /// a boolean to signify that the scene has sculpt data
    pub entry: bool,
    /// The UUID of the "sculpt texture".
    /// This used to contain the UUID of the SculptTexture image, but now contains the UUID of the
    /// mesh object.
    pub texture: Uuid,
    /// Tells the engine what base to use when deforming based on the SculptTexture.
    /// For example, the SculptTexture applied to a sphere would deform that sphere, or a
    /// SculptTexture applied to a plane would deform differently.
    /// Mostly legacy now. Always set to 5 when displaying meshes.
    pub sculpt_type: SculptType,
}

#[derive(Debug, Default, Clone, Serialize, Deserialize)]
/// Information about an object's motion within the scene
pub struct SceneMotionData {
    /// the velocity of the prim in the scene
    pub velocity: Vec3,
    /// the angular velocity of the prim in the scene
    pub angular_velocity: Vec3,
    /// the acceleration of the prim in the scene
    pub acceleration: Vec3,
    /// determines the effect of gravity on the object.
    /// 0 = normal gravity
    /// 1 = floating
    /// \>1 = rising
    pub buoyancy: i32,
    /// Constant force applied by physics each frame. Used for things like hovering platforms.
    pub force: Vec3,
    /// Constant torque applied by physics each frame. Used for things like spinners and wheels.
    pub torque: Vec3,
}

#[derive(Debug, Default, Clone, Serialize, Deserialize)]
/// information about an object's sound
pub struct SceneSound {
    /// The sound the object makes on collision
    pub collision_sound: Uuid,
    /// The volume of the collision sound
    pub collision_sound_volume: i32,
    /// Flag for preventing sounds from overlapping by queuing them instead. Used to prevent audio
    /// spam.
    pub sound_queueing: bool,
    /// data to determine which sound to play
    pub attached: AttachedSound,
}

#[derive(Debug, Default, Clone, Serialize, Deserialize)]
/// An object within a scene
///
/// This contains information about the object, and how it relates to other objects within the
/// scene.
pub struct SceneObject {
    /// If the object is allowed to be dropped or not
    pub allowed_drop: bool,
    /// ID of the person who created the object
    pub creator_id: Uuid,
    /// The folder the object is located in
    pub folder_id: Uuid,
    /// A serial number that gets incrimented upon every change of the object.
    /// This is used to ensure the inventory is up to date.
    pub inventory_serial: i32,
    /// Type of material the object is made from
    pub material: MaterialType,
    /// A flag that determines if a prim passes on click events to prims behind it.
    /// This is useful for things like HUDs where things are visually in front, but should not
    /// block input to things behind.
    pub pass_touches: bool,
    /// A flag that determines if a prim passes collision events to the prim behind it.
    /// useful for things like dresses where you don't want to set the collision for the dress
    /// itself, but the body wearing it.
    pub pass_collisions: bool,
    /// World position of the root. The true location of the object.
    pub group_position: Vec3,
    /// offset of the object. If it is the root, this is the offset from the group position. if it
    /// is a child, it's the offset from the root.
    pub offset_position: Vec3,
    /// The rotation compared to the rotation of the root prim. This is in quaternion format where the components are <X>, <Y>, <Z> and <W>.
    pub rotation_offset: Quat,
    /// The color of the prim.
    /// This is expressed as a 0-255 value of red <R>, green <G>, blue <B> and alpha <A>, where alpha = 0 is fully transparent.
    pub color: Rgba<i32>,
    /// text that hovers over the object in-world
    pub text: String,
    /// Label returned when the object is clicked
    pub touch_name: String,
    /// The number of this prim in the SceneObjectGroup.
    /// The root prim will always have LinkNum = 0. Subsequent prims will have consecutive numbers, 1, 2, 3
    pub link_num: i32,
    /// TODO: unknown
    pub click_action: u8,
    /// XYZ scale of the object
    pub scale: Vec3,
    /// Deprecated commerce type
    pub legacy_category: String,
    /// UUID of the thing that most recently rezzed te object.
    pub rezzer_id: Uuid,
    /// Used by vendors to populate pricetags
    pub pay_price: [i32; 5],
    /// Offset of the attachment relative to the avatar bone when worn
    /// allows repositioning of attachments without editing the root.
    pub attached_pos: Vec3,
    /// Flag for determining if the object is a non-solid "Sensor volume" that triggers on
    /// collisions. Used for triggers without physical shape.
    pub volume_detect_active: bool,
    /// Controls collision hull optimization.
    pub physics_shape_type: PhysicsShapeType,
    /// Offset applied to the avatar's camera when seated on this prim. For things like changing
    /// camera angle when driving a car. Matches edit camera settings.
    pub camera_eye_offset: Vec3,
    /// "Look at" focus offset for the camera eye offset. For things like changing camera angle
    /// when driving a car.
    pub camera_at_offset: Vec3,

    /// metadata about the scene, such as SaleInfo, Permissions, etc
    pub metadata: SceneMetadata,
    /// shape information. Contains information about paths, sculpted prims, and meshes.
    pub shape: Shape,
    /// data about the sound the object makes
    pub sound: SceneSound,
    /// Information about how the object is moving in the scene
    pub motion_data: SceneMotionData,
    /// Information about the Sculpt.
    /// Contains information about how the viewer will render the geometry of the object.
    pub sculpt: Sculpt,
    /// Information about sitting
    /// contains information about how the position and orientation of agent sitting.
    pub sit_data: SitData,

    /// temporary ID to a prim within a scene.
    /// legacy
    pub legacy_local_id: u32,
    /// region that the part was in when it was last serialized.
    /// legacy
    pub legacy_region_handle: u32,
    /// X location of the scene in the world. Parsed from RegionHandle.
    pub legacy_region_x: u32,
    /// Y location of the scene of the world. Parsed from RegionHandle
    pub legacy_region_y: u32,
    /// A pin to prevent unauthorized script injections. Kind of like a password.
    /// Insecure, legacy code
    pub legacy_script_access_pin: u64,
    /// temporary local ID of the parent prim
    /// legacy
    pub legacy_parent_id: u32,
}

impl SceneObject {
    /// This is used for reading the bytes after the root object has been parsed.
    /// This is for handling the OtherParts section of the xml.
    fn read_scene_object<R: std::io::BufRead>(
        reader: &mut Reader<R>,
        scene_object: &mut SceneObject,
        path_prefix: Vec<String>,
    ) -> Result<(), ParseError> {
        let mut buf = Vec::new();
        let mut path = path_prefix;

        loop {
            match reader.read_event_into(&mut buf)? {
                Event::Start(ref e) => {
                    path.push(from_utf8(e.name().as_ref())?.to_string());
                }
                Event::End(ref e) => {
                    let tag_bytes = e.name(); // get the raw name
                    let tag = from_utf8(tag_bytes.as_ref())?; // convert to &str
                    if let Some(last) = path.last()
                        && last == tag
                    {
                        path.pop();
                    }
                    // Exit if we've closed the original SceneObjectPart
                    if tag == "SceneObjectPart" {
                        break;
                    }

                    // Exit if we've closed the original SceneObjectPart
                    if tag == "SceneObjectPart"
                        && !path.ends_with(&[
                            "Part".to_string(),
                            "OtherParts".to_string(),
                            "SceneObjectGroup".to_string(),
                        ])
                    {
                        break;
                    }
                }
                Event::Text(e) => {
                    let path_refs: Vec<&str> = path.iter().map(String::as_str).collect();
                    SceneObject::from_path_str(path_refs, e, scene_object, 4)?;
                }
                Event::Eof => break,
                _ => {}
            }

            buf.clear();
        }
        Ok(())
    }
    /// from_path_str takes one path string, and parses the scene object value out of it.
    /// This takes a mutable scene object, and changes its default value to the value defined by
    /// the xml.
    pub fn from_path_str(
        path_str: Vec<&str>,
        e: BytesText<'_>,
        scene_object: &mut SceneObject,
        offset: usize,
    ) -> Result<(), ParseError> {
        let text = str::from_utf8(e.as_ref())?;
        let val = unescape(text)?.into_owned();
        match &path_str[offset..] {
            ["CreatorID", "UUID"] => {
                scene_object.creator_id = Uuid::parse_str(&val)?;
            }
            ["FolderId", "UUID"] => {
                scene_object.folder_id = Uuid::parse_str(&val)?;
            }
            ["InventorySerial"] => {
                scene_object.inventory_serial = val.parse::<i32>()?;
            }
            ["UUID", "UUID"] => {
                scene_object.metadata.id = Uuid::parse_str(&val)?;
            }
            ["LocalId"] => {
                scene_object.legacy_local_id = val.parse()?;
            }
            ["Name"] => {
                scene_object.metadata.name = val;
            }
            ["Material"] => {
                scene_object.material = MaterialType::from_bytes(&val.parse::<u8>()?);
            }
            ["PassTouches"] => {
                scene_object.pass_touches = val.parse::<bool>()?;
            }
            ["PassCollisions"] => {
                scene_object.pass_collisions = val.parse::<bool>()?;
            }
            ["RegionHandle"] => {
                let region_handle = val.parse::<u64>()?;
                scene_object.legacy_region_x = (region_handle >> 32) as u32;
                scene_object.legacy_region_y = (region_handle & 0xFFFF_FFFF) as u32;
            }
            ["ScriptAccessPin"] => {
                scene_object.legacy_script_access_pin = val.parse::<u64>()?;
            }

            ["GroupPosition", rest @ ..] => match rest {
                ["X"] => scene_object.group_position[0] = val.parse()?,
                ["Y"] => scene_object.group_position[1] = val.parse()?,
                ["Z"] => scene_object.group_position[2] = val.parse()?,
                _ => {}
            },
            ["OffsetPosition", rest @ ..] => match rest {
                ["X"] => scene_object.offset_position[0] = val.parse()?,
                ["Y"] => scene_object.offset_position[1] = val.parse()?,
                ["Z"] => scene_object.offset_position[2] = val.parse()?,
                _ => {}
            },
            ["RotationOffset", rest @ ..] => match rest {
                ["X"] => scene_object.rotation_offset.x = val.parse()?,
                ["Y"] => scene_object.rotation_offset.y = val.parse()?,
                ["Z"] => scene_object.rotation_offset.z = val.parse()?,
                ["W"] => scene_object.rotation_offset.w = val.parse()?,
                _ => {}
            },
            ["Velocity", rest @ ..] => match rest {
                ["X"] => scene_object.motion_data.velocity[0] = val.parse()?,
                ["Y"] => scene_object.motion_data.velocity[1] = val.parse()?,
                ["Z"] => scene_object.motion_data.velocity[2] = val.parse()?,
                _ => {}
            },
            ["AngularVelocity", rest @ ..] => match rest {
                ["X"] => scene_object.motion_data.angular_velocity[0] = val.parse()?,
                ["Y"] => scene_object.motion_data.angular_velocity[1] = val.parse()?,
                ["Z"] => scene_object.motion_data.angular_velocity[2] = val.parse()?,
                _ => {}
            },
            ["Acceleration", rest @ ..] => match rest {
                ["X"] => scene_object.motion_data.acceleration[0] = val.parse()?,
                ["Y"] => scene_object.motion_data.acceleration[1] = val.parse()?,
                ["Z"] => scene_object.motion_data.acceleration[2] = val.parse()?,
                _ => {}
            },
            ["Description"] => {
                scene_object.metadata.description = val;
            }
            ["Color", rest @ ..] => match rest {
                ["R"] => {
                    scene_object.color.r = val.parse::<i32>()?;
                }
                ["G"] => {
                    scene_object.color.g = val.parse::<i32>()?;
                }
                ["B"] => {
                    scene_object.color.b = val.parse::<i32>()?;
                }
                ["A"] => {
                    scene_object.color.a = val.parse::<i32>()?;
                }
                _ => {}
            },
            ["Text"] => {
                scene_object.text = val;
            }
            ["SitName"] => {
                scene_object.sit_data.sit_name = val;
            }
            ["TouchName"] => {
                scene_object.touch_name = val;
            }
            ["LinkNum"] => {
                scene_object.link_num = val.parse::<i32>()?;
            }
            ["ClickAction"] => {
                scene_object.click_action = val.parse()?;
            }
            ["Shape", rest @ ..] => match rest {
                ["ProfileCurve"] => scene_object.shape.path.profile_curve = val.parse::<u8>()?,
                ["TextureEntry"] => {
                    scene_object.shape.texture = TextureEntry::from_b64(val.as_bytes())?
                }
                ["ExtraParams"] => scene_object.shape.extra_params = val.as_bytes().to_vec(),
                ["PathBegin"] => scene_object.shape.path.begin = val.parse::<u16>()?,
                ["PathCurve"] => scene_object.shape.path.curve = val.parse::<u8>()?,
                ["PathEnd"] => scene_object.shape.path.end = val.parse::<u16>()?,
                ["PathRadiusOffset"] => {
                    scene_object.shape.path.radius_offset = val.parse::<i8>()?
                }
                ["PathRevolutions"] => scene_object.shape.path.revolutions = val.parse::<u8>()?,
                ["PathScaleX"] => scene_object.shape.path.scale_x = val.parse::<u8>()?,
                ["PathScaleY"] => scene_object.shape.path.scale_y = val.parse::<u8>()?,
                ["PathShearX"] => scene_object.shape.path.shear_x = val.parse::<u8>()?,
                ["PathShearY"] => scene_object.shape.path.shear_y = val.parse::<u8>()?,
                ["PathSkew"] => scene_object.shape.path.skew = val.parse::<i8>()?,
                ["PathTaperX"] => scene_object.shape.path.taper_x = val.parse::<i8>()?,
                ["PathTaperY"] => scene_object.shape.path.taper_y = val.parse::<i8>()?,
                ["PathTwist"] => scene_object.shape.path.twist_end = val.parse::<i8>()?,
                ["PathTwistBegin"] => scene_object.shape.path.twist_begin = val.parse::<i8>()?,
                ["PCode"] => {
                    scene_object.shape.pcode = ObjectType::from_bytes(&val.parse::<u8>()?);
                }
                ["ProfileBegin"] => scene_object.shape.path.profile_begin = val.parse::<u16>()?,
                ["ProfileEnd"] => scene_object.shape.path.profile_end = val.parse::<u16>()?,
                ["ProfileHollow"] => scene_object.shape.path.profile_hollow = val.parse::<f32>()?,
                ["ProfileShape"] => scene_object.shape.path.profile_shape = Some(val),
                ["HollowShape"] => scene_object.shape.path.hollow_shape = Some(val),

                ["State"] => scene_object.shape.state = val.parse::<i32>()?,
                ["LastAttachPoint"] => scene_object.shape.last_attach_point = val.parse::<i32>()?,
                ["SculptTexture", "UUID"] => scene_object.sculpt.texture = Uuid::parse_str(&val)?,
                ["SculptType"] => {
                    scene_object.sculpt.sculpt_type = SculptType::from_i32(val.parse::<i32>()?)
                }
                ["SculptEntry"] => scene_object.sculpt.entry = val.parse::<bool>()?,
                ["FlexiSoftness"] => scene_object.shape.flex.softness = val.parse::<i32>()?,
                ["FlexiTension"] => scene_object.shape.flex.tension = val.parse::<i32>()?,
                ["FlexiDrag"] => scene_object.shape.flex.drag = val.parse::<i32>()?,
                ["FlexiGravity"] => scene_object.shape.flex.gravity = val.parse::<i32>()?,
                ["FlexiWind"] => scene_object.shape.flex.wind = val.parse::<i32>()?,

                ["FlexiForceX"] => scene_object.shape.flex.force[0] = val.parse::<f32>()?,
                ["FlexiForceY"] => scene_object.shape.flex.force[1] = val.parse::<f32>()?,
                ["FlexiForceZ"] => scene_object.shape.flex.force[2] = val.parse::<f32>()?,
                ["LightColorR"] => scene_object.shape.light.color.r = val.parse::<i32>()?,
                ["LightColorG"] => scene_object.shape.light.color.g = val.parse::<i32>()?,
                ["LightColorB"] => scene_object.shape.light.color.b = val.parse::<i32>()?,
                ["LightColorA"] => scene_object.shape.light.color.a = val.parse::<i32>()?,
                ["LightRadius"] => scene_object.shape.light.radius = val.parse::<i32>()?,
                ["LightCutoff"] => scene_object.shape.light.cutoff = val.parse::<i32>()?,
                ["LightFalloff"] => scene_object.shape.light.falloff = val.parse::<i32>()?,
                ["LightIntensity"] => scene_object.shape.light.intensity = val.parse::<i32>()?,
                ["FlexiEntry"] => scene_object.shape.flex.entry = val.parse::<bool>()?,
                ["LightEntry"] => scene_object.shape.light.entry = val.parse::<bool>()?,
                _ => {}
            },

            ["Scale", rest @ ..] => match rest {
                ["X"] => scene_object.scale[0] = val.parse()?,
                ["Y"] => scene_object.scale[1] = val.parse()?,
                ["Z"] => scene_object.scale[2] = val.parse()?,
                _ => {}
            },
            ["SitTargetOrientation", rest @ ..] => match rest {
                ["X"] => scene_object.sit_data.orientation[0] = val.parse()?,
                ["Y"] => scene_object.sit_data.orientation[1] = val.parse()?,
                ["Z"] => scene_object.sit_data.orientation[2] = val.parse()?,
                _ => {}
            },
            ["SitTargetOrientationLL", rest @ ..] => match rest {
                ["X"] => scene_object.sit_data.legacy_orientation_ll[0] = val.parse()?,
                ["Y"] => scene_object.sit_data.legacy_orientation_ll[1] = val.parse()?,
                ["Z"] => scene_object.sit_data.legacy_orientation_ll[2] = val.parse()?,
                _ => {}
            },
            ["SitTargetPosition", rest @ ..] => match rest {
                ["X"] => scene_object.sit_data.position[0] = val.parse()?,
                ["Y"] => scene_object.sit_data.position[1] = val.parse()?,
                ["Z"] => scene_object.sit_data.position[2] = val.parse()?,
                _ => {}
            },
            ["SitTargetPositionLL", rest @ ..] => match rest {
                ["X"] => scene_object.sit_data.legacy_position_ll[0] = val.parse()?,
                ["Y"] => scene_object.sit_data.legacy_position_ll[1] = val.parse()?,
                ["Z"] => scene_object.sit_data.legacy_position_ll[2] = val.parse()?,
                _ => {}
            },
            ["ParentID"] => {
                scene_object.legacy_parent_id = if val == "0" { 0 } else { u32::from_str(&val)? };
            }
            ["CreationDate"] => {
                scene_object.metadata.created_at =
                    SystemTime::UNIX_EPOCH + std::time::Duration::from_secs(val.parse::<u64>()?)
            }
            ["Category"] => scene_object.legacy_category = val,
            ["SalePrice"] => scene_object.metadata.sale_info.price = val.parse::<i32>()?,
            ["ObjectSaleType"] => {
                scene_object.metadata.sale_info.sale_type = SaleType::from_string(&val)
            }

            ["OwnershipCost"] => {
                scene_object.metadata.sale_info.ownership_cost = Some(val.parse::<i32>()?)
            }
            ["GroupID", "UUID"] => {
                scene_object.metadata.permissions.group_id = Uuid::from_str(&val)?
            }
            ["OwnerID", "UUID"] => {
                scene_object.metadata.permissions.owner_id = Uuid::from_str(&val)?
            }
            ["LastOwnerID", "UUID"] => {
                scene_object.metadata.permissions.last_owner_id = Some(Uuid::from_str(&val)?)
            }
            ["RezzerID", "UUID"] => scene_object.rezzer_id = Uuid::from_str(&val)?,
            ["BaseMask"] => scene_object.metadata.permissions.base_mask = val.parse::<i32>()?,
            ["OwnerMask"] => scene_object.metadata.permissions.owner_mask = val.parse::<i32>()?,
            ["Groupmask"] => scene_object.metadata.permissions.group_mask = val.parse::<i32>()?,
            ["EveryoneMask"] => {
                scene_object.metadata.permissions.everyone_mask = val.parse::<i32>()?
            }
            ["NextOwnerMask"] => {
                scene_object.metadata.permissions.next_owner_mask = val.parse::<i32>()?
            }
            ["Flags"] => {
                if val == "None" {
                    scene_object.metadata.flags = 0
                } else {
                    scene_object.metadata.flags = val.parse::<i32>()?
                }
            }
            ["CollisionSound", "UUID"] => {
                scene_object.sound.collision_sound = Uuid::from_str(&val)?
            }
            ["CollisionSoundVolume"] => {
                scene_object.sound.collision_sound_volume = val.parse::<i32>()?
            }
            ["AttachedPos", rest @ ..] => match rest {
                ["X"] => scene_object.attached_pos[0] = val.parse()?,
                ["Y"] => scene_object.attached_pos[1] = val.parse()?,
                ["Z"] => scene_object.attached_pos[2] = val.parse()?,
                _ => {}
            },
            ["Textureanimation"] => scene_object.shape.texture_animation = val.into_bytes(),
            ["ParticleSystem"] => scene_object.shape.particle_system = val.into_bytes(),
            ["PayPrice0"] => scene_object.pay_price[0] = val.parse::<i32>()?,
            ["PayPrice1"] => scene_object.pay_price[1] = val.parse::<i32>()?,
            ["PayPrice2"] => scene_object.pay_price[2] = val.parse::<i32>()?,
            ["PayPrice3"] => scene_object.pay_price[3] = val.parse::<i32>()?,
            ["PayPrice4"] => scene_object.pay_price[4] = val.parse::<i32>()?,
            ["Buoyancy"] => scene_object.motion_data.buoyancy = val.parse::<i32>()?,
            ["Force", rest @ ..] => match rest {
                ["X"] => scene_object.motion_data.force[0] = val.parse()?,
                ["Y"] => scene_object.motion_data.force[1] = val.parse()?,
                ["Z"] => scene_object.motion_data.force[2] = val.parse()?,
                _ => {}
            },
            ["Torque", rest @ ..] => match rest {
                ["x"] => scene_object.motion_data.torque[0] = val.parse()?,
                ["y"] => scene_object.motion_data.torque[1] = val.parse()?,
                ["z"] => scene_object.motion_data.torque[2] = val.parse()?,
                _ => {}
            },
            ["VolumeDetectActive"] => scene_object.volume_detect_active = val.parse::<bool>()?,
            ["PhysicsShapetype"] => {
                scene_object.physics_shape_type = PhysicsShapeType::from_bytes(val.parse::<i32>()?)
            }
            ["CameraEyeOffset", rest @ ..] => match rest {
                ["x"] => scene_object.camera_eye_offset[0] = val.parse()?,
                ["y"] => scene_object.camera_eye_offset[1] = val.parse()?,
                ["z"] => scene_object.camera_eye_offset[2] = val.parse()?,
                _ => {}
            },
            ["CameraAtOffset", rest @ ..] => match rest {
                ["x"] => scene_object.camera_at_offset[0] = val.parse()?,
                ["y"] => scene_object.camera_at_offset[1] = val.parse()?,
                ["z"] => scene_object.camera_at_offset[2] = val.parse()?,
                _ => {}
            },
            ["SoundID", "UUID"] => scene_object.sound.attached.sound_id = Uuid::from_str(&val)?,
            ["SoundGain"] => scene_object.sound.attached.gain = val.parse::<f32>()?,
            ["SoundFlags"] => scene_object.sound.attached.flags = val.parse::<u8>()?,
            ["SoundRadius"] => scene_object.sound.attached.radius = val.parse::<f32>()?,
            ["SoundQueueing"] => scene_object.sound.sound_queueing = val.parse::<bool>()?,
            _ => {}
        }

        Ok(())
    }
}

#[derive(Debug, Default, Clone, Serialize, Deserialize)]
/// Flex information
///
/// Used to describe how physics forces act on a non-rigid object.
pub struct Flex {
    /// Enables flexible behavior. If false, the object is rigid.
    pub entry: bool,
    /// Controls the number of segements in the flexible object. Higher = more bendy.
    pub softness: i32,
    /// A spring-like force that pulls the prim back to its original position.
    /// higher = tighter
    pub tension: i32,
    /// Damping effect. High values reduce fast motion.
    pub drag: i32,
    /// Gravity's downward force on the object
    pub gravity: i32,
    /// the effect wind has on the object
    pub wind: i32,
    /// A custom force applied to the object
    pub force: Vec4,
}

#[derive(Debug, Default, Clone, Serialize, Deserialize)]
/// Information about how the object can be sat on.
pub struct SitData {
    /// Custom name that is displayed when the user right-clicks to sit.
    /// Could be things like "sit", "pose", "lounge" etc
    pub sit_name: String,
    /// How to rotate the avatar that sits down
    pub orientation: Vec3,
    /// Undocumented. Maybe legacy rotation vector
    pub legacy_orientation_ll: Vec3,
    /// XYZ position the avatar should be placed when sitting.
    pub position: Vec3,
    /// Undocumented. Maybe legacy position vector.
    pub legacy_position_ll: Vec3,
}

#[derive(Debug, Default, Clone, Serialize, Deserialize)]
/// Information about how the object emits light
pub struct Light {
    /// Enables glow. If false, the object doesn't glow.
    pub entry: bool,
    /// The color of the glow
    pub color: Rgba<i32>,
    /// how far the light reaches from the source
    pub radius: i32,
    /// Cone cutoff angle for spotlights. 0 = point light, 180 is a full sphere.
    pub cutoff: i32,
    /// Brightness of the light
    pub intensity: i32,
    /// How quickly the light intensity decreases with distance
    pub falloff: i32,
}

#[derive(Debug, Default, Clone, Serialize, Deserialize)]
/// The types used for physics collision
pub enum PhysicsShapeType {
    /// Primitive collision
    Prim,
    /// The smallest convex shape that contains all the points of the 3d mesh.
    /// Kind of like shrinkwrapping to find the collision points of the mesh. Does not handle
    /// collision for inclusions.
    ConvexHull,
    /// No collision
    None,

    #[default]
    /// Unknown
    Unknown,
}
impl PhysicsShapeType {
    fn from_bytes(bytes: i32) -> Self {
        match bytes {
            0 => Self::Prim,
            1 => Self::ConvexHull,
            2 => Self::None,
            _ => Self::Unknown,
        }
    }
}

/// Used for legacy compatability with SculptTextures.
/// describes the different basic shapes the sculpttexture can deform.
#[derive(Debug, Copy, Default, Clone, Serialize, Deserialize)]
pub enum SculptType {
    /// The sculpt texture deforms a sphere
    Sphere,
    /// The sculpt texture deforms a torus
    Torus,
    /// The sculpt texture deforms a plane
    Plane,
    /// The sculpt texture deforms a cylinder
    Cylinder,
    /// This describes a mesh. which is the primary supported type.
    Mesh,

    #[default]
    /// Unknown
    Unknown,
}

impl SculptType {
    /// Convert a sculpt type from an i32 to a SculptType enum
    pub fn from_i32(bytes: i32) -> Self {
        match bytes {
            1 => Self::Sphere,
            2 => Self::Torus,
            3 => Self::Plane,
            4 => Self::Cylinder,
            5 => Self::Mesh,
            _ => Self::Unknown,
        }
    }
    /// Convert a sculpt type from a u8 byte to a SculptType enum
    pub fn from_bytes(bytes: &u8) -> Self {
        match bytes {
            1 => Self::Sphere,
            2 => Self::Torus,
            3 => Self::Plane,
            4 => Self::Cylinder,
            5 => Self::Mesh,
            _ => Self::Unknown,
        }
    }
}