Skip to main content

vrm_spec/
vrm_0_0.rs

1//! Data structures for the [`VRM`](https://github.com/vrm-c/vrm-specification/tree/master/specification/0.0) 0.0 glTF Extension.
2
3/// VRM extension name
4pub const VRM: &str = "VRM";
5
6#[cfg(feature = "rustc_hash")]
7use rustc_hash::FxHashMap as HashMap;
8use serde::{Deserialize, Serialize};
9#[cfg(not(feature = "rustc_hash"))]
10use std::collections::HashMap;
11
12use crate::serde_utils::{
13    deserialize_option_index, deserialize_option_map_and_skip_nullable,
14    deserialize_option_map_index,
15};
16
17/// VRM extension is for 3d humanoid avatars (and models) in VR applications.
18#[derive(Debug, Clone, Serialize, Deserialize)]
19#[serde(rename_all = "camelCase")]
20pub struct VRM0Schema {
21    #[serde(skip_serializing_if = "Option::is_none")]
22    pub blend_shape_master: Option<VRMBlendShape>,
23
24    /// Version of exporter that vrm created. UniVRM-0.46
25    #[serde(skip_serializing_if = "Option::is_none")]
26    pub exporter_version: Option<String>,
27
28    #[serde(skip_serializing_if = "Option::is_none")]
29    pub first_person: Option<VRMFirstPerson>,
30
31    #[serde(skip_serializing_if = "Option::is_none")]
32    pub humanoid: Option<VRMHumanoid>,
33
34    #[serde(skip_serializing_if = "Option::is_none")]
35    pub material_properties: Option<Vec<VRMMaterial>>,
36
37    #[serde(skip_serializing_if = "Option::is_none")]
38    pub meta: Option<VRMMeta>,
39
40    #[serde(skip_serializing_if = "Option::is_none")]
41    pub secondary_animation: Option<VRMSecondaryAnimation>,
42
43    /// Version of VRM specification. 0.0
44    #[serde(skip_serializing_if = "Option::is_none")]
45    pub spec_version: Option<String>,
46}
47
48/// BlendShapeAvatar of UniVRM
49#[derive(Debug, Clone, Serialize, Deserialize)]
50#[serde(rename_all = "camelCase")]
51pub struct VRMBlendShape {
52    #[serde(skip_serializing_if = "Option::is_none")]
53    pub blend_shape_groups: Option<Vec<VRMBlendShapeGroup>>,
54}
55
56#[derive(Debug, Clone, Serialize, Deserialize)]
57#[serde(rename_all = "camelCase")]
58pub struct VRMBlendShapeGroup {
59    /// Low level blendshape references.
60    #[serde(skip_serializing_if = "Option::is_none")]
61    pub binds: Option<Vec<VRMBlendShapeBind>>,
62
63    /// 0 or 1. Do not allow an intermediate value. Value should rounded
64    #[serde(skip_serializing_if = "Option::is_none")]
65    pub is_binary: Option<bool>,
66
67    /// Material animation references.
68    #[serde(skip_serializing_if = "Option::is_none")]
69    pub material_values: Option<Vec<VRMBlendShapeMaterialBind>>,
70
71    /// Expression name
72    #[serde(skip_serializing_if = "Option::is_none")]
73    pub name: Option<String>,
74
75    /// Predefined Expression name
76    #[serde(skip_serializing_if = "Option::is_none")]
77    pub preset_name: Option<PresetName>,
78}
79
80#[derive(Debug, Clone, Serialize, Deserialize)]
81pub struct VRMBlendShapeBind {
82    #[serde(skip_serializing_if = "Option::is_none")]
83    pub index: Option<i64>,
84
85    #[serde(
86        default,
87        skip_serializing_if = "Option::is_none",
88        deserialize_with = "deserialize_option_index::<_, gltf::json::Mesh>"
89    )]
90    #[cfg(feature = "gltf_index")]
91    pub mesh: Option<gltf::json::Index<gltf::json::Mesh>>,
92    #[cfg(not(feature = "gltf_index"))]
93    pub mesh: Option<i64>,
94
95    /// SkinnedMeshRenderer.SetBlendShapeWeight
96    #[serde(skip_serializing_if = "Option::is_none")]
97    pub weight: Option<f64>,
98}
99
100#[derive(Debug, Clone, Serialize, Deserialize)]
101#[serde(rename_all = "camelCase")]
102pub struct VRMBlendShapeMaterialBind {
103    #[serde(skip_serializing_if = "Option::is_none")]
104    pub material_name: Option<String>,
105
106    #[serde(skip_serializing_if = "Option::is_none")]
107    pub property_name: Option<String>,
108
109    #[serde(skip_serializing_if = "Option::is_none")]
110    pub target_value: Option<Vec<f64>>,
111}
112
113#[derive(Debug, Clone, Serialize, Deserialize)]
114#[serde(rename_all = "camelCase")]
115pub struct VRMFirstPerson {
116    /// The bone whose rendering should be turned off in first-person view. Usually Head is
117    /// specified.
118    #[serde(
119        default,
120        skip_serializing_if = "Option::is_none",
121        deserialize_with = "deserialize_option_index::<_, gltf::json::Node>"
122    )]
123    #[cfg(feature = "gltf_index")]
124    pub first_person_bone: Option<gltf::json::Index<gltf::json::Node>>,
125    #[cfg(not(feature = "gltf_index"))]
126    pub first_person_bone: Option<i64>,
127
128    /// The target position of the VR headset in first-person view. It is assumed that an offset
129    /// from the head bone to the VR headset is added.
130    #[serde(skip_serializing_if = "Option::is_none")]
131    pub first_person_bone_offset: Option<FirstPersonBoneOffset>,
132
133    #[serde(skip_serializing_if = "Option::is_none")]
134    pub look_at_horizontal_inner: Option<VRMFirstPersonDegreeMap>,
135
136    #[serde(skip_serializing_if = "Option::is_none")]
137    pub look_at_horizontal_outer: Option<VRMFirstPersonDegreeMap>,
138
139    /// Eye controller mode.
140    #[serde(skip_serializing_if = "Option::is_none")]
141    pub look_at_type_name: Option<LookAtTypeName>,
142
143    #[serde(skip_serializing_if = "Option::is_none")]
144    pub look_at_vertical_down: Option<VRMFirstPersonDegreeMap>,
145
146    #[serde(skip_serializing_if = "Option::is_none")]
147    pub look_at_vertical_up: Option<VRMFirstPersonDegreeMap>,
148
149    /// Switch display / undisplay for each mesh in first-person view or the others.
150    #[serde(skip_serializing_if = "Option::is_none")]
151    pub mesh_annotations: Option<Vec<VRMFirstPersonMeshAnnotation>>,
152}
153
154#[derive(Debug, Clone, Serialize, Deserialize)]
155/// Vector3 but has optional x,y,z.
156///
157/// normally x,y,z should not be optional but the VRM 0.0 spec allows it
158pub struct OptionalVector3 {
159    #[serde(skip_serializing_if = "Option::is_none")]
160    pub x: Option<f64>,
161
162    #[serde(skip_serializing_if = "Option::is_none")]
163    pub y: Option<f64>,
164
165    #[serde(skip_serializing_if = "Option::is_none")]
166    pub z: Option<f64>,
167}
168
169/// The target position of the VR headset in first-person view. It is assumed that an offset
170/// from the head bone to the VR headset is added.
171pub type FirstPersonBoneOffset = OptionalVector3;
172
173/// Eye controller setting.
174#[derive(Debug, Clone, Serialize, Deserialize)]
175#[serde(rename_all = "camelCase")]
176pub struct VRMFirstPersonDegreeMap {
177    /// None linear mapping params. time, value, inTangent, outTangent
178    #[serde(skip_serializing_if = "Option::is_none")]
179    pub curve: Option<Vec<f64>>,
180
181    /// Look at input clamp range degree.
182    #[serde(skip_serializing_if = "Option::is_none")]
183    pub x_range: Option<f64>,
184
185    /// Look at map range degree from xRange.
186    #[serde(skip_serializing_if = "Option::is_none")]
187    pub y_range: Option<f64>,
188}
189
190#[derive(Debug, Clone, Serialize, Deserialize)]
191#[serde(rename_all = "camelCase")]
192pub struct VRMFirstPersonMeshAnnotation {
193    #[serde(skip_serializing_if = "Option::is_none")]
194    pub first_person_flag: Option<String>,
195
196    #[serde(
197        default,
198        skip_serializing_if = "Option::is_none",
199        deserialize_with = "deserialize_option_index::<_, gltf::json::Mesh>"
200    )]
201    #[cfg(feature = "gltf_index")]
202    pub mesh: Option<gltf::json::Index<gltf::json::Mesh>>,
203    #[cfg(not(feature = "gltf_index"))]
204    pub mesh: Option<i64>,
205}
206
207#[derive(Debug, Clone, Serialize, Deserialize)]
208#[serde(rename_all = "camelCase")]
209pub struct VRMHumanoid {
210    /// Unity's HumanDescription.armStretch
211    #[serde(skip_serializing_if = "Option::is_none")]
212    pub arm_stretch: Option<f64>,
213
214    /// Unity's HumanDescription.feetSpacing
215    #[serde(skip_serializing_if = "Option::is_none")]
216    pub feet_spacing: Option<f64>,
217
218    /// Unity's HumanDescription.hasTranslationDoF
219    #[serde(skip_serializing_if = "Option::is_none")]
220    pub has_translation_do_f: Option<bool>,
221
222    #[serde(skip_serializing_if = "Option::is_none")]
223    pub human_bones: Option<Vec<VRMHumanoidBone>>,
224
225    /// Unity's HumanDescription.legStretch
226    #[serde(skip_serializing_if = "Option::is_none")]
227    pub leg_stretch: Option<f64>,
228
229    /// Unity's HumanDescription.lowerArmTwist
230    #[serde(skip_serializing_if = "Option::is_none")]
231    pub lower_arm_twist: Option<f64>,
232
233    /// Unity's HumanDescription.lowerLegTwist
234    #[serde(skip_serializing_if = "Option::is_none")]
235    pub lower_leg_twist: Option<f64>,
236
237    /// Unity's HumanDescription.upperArmTwist
238    #[serde(skip_serializing_if = "Option::is_none")]
239    pub upper_arm_twist: Option<f64>,
240
241    /// Unity's HumanDescription.upperLegTwist
242    #[serde(skip_serializing_if = "Option::is_none")]
243    pub upper_leg_twist: Option<f64>,
244}
245
246#[derive(Debug, Clone, Serialize, Deserialize)]
247#[serde(rename_all = "camelCase")]
248pub struct VRMHumanoidBone {
249    /// Unity's HumanLimit.axisLength
250    #[serde(skip_serializing_if = "Option::is_none")]
251    pub axis_length: Option<f64>,
252
253    /// Human bone name.
254    #[serde(skip_serializing_if = "Option::is_none")]
255    pub bone: Option<Bone>,
256
257    /// Unity's HumanLimit.center
258    #[serde(skip_serializing_if = "Option::is_none")]
259    pub center: Option<Center>,
260
261    /// Unity's HumanLimit.max
262    #[serde(skip_serializing_if = "Option::is_none")]
263    pub max: Option<Max>,
264
265    /// Unity's HumanLimit.min
266    #[serde(skip_serializing_if = "Option::is_none")]
267    pub min: Option<Min>,
268
269    /// Reference node index
270    #[serde(
271        default,
272        skip_serializing_if = "Option::is_none",
273        deserialize_with = "deserialize_option_index::<_, gltf::json::Node>"
274    )]
275    #[cfg(feature = "gltf_index")]
276    pub node: Option<gltf::json::Index<gltf::json::Node>>,
277    #[cfg(not(feature = "gltf_index"))]
278    pub node: Option<i64>,
279
280    /// Unity's HumanLimit.useDefaultValues
281    #[serde(skip_serializing_if = "Option::is_none")]
282    pub use_default_values: Option<bool>,
283}
284
285/// Unity's HumanLimit.center
286pub type Center = OptionalVector3;
287
288/// Unity's HumanLimit.max
289pub type Max = OptionalVector3;
290
291/// Unity's HumanLimit.min
292pub type Min = OptionalVector3;
293
294#[derive(Debug, Clone, Serialize, Deserialize)]
295#[serde(rename_all = "camelCase")]
296pub struct VRMMaterial {
297    #[serde(
298        default,
299        skip_serializing_if = "Option::is_none",
300        deserialize_with = "deserialize_option_map_and_skip_nullable::<_, String, f64>"
301    )]
302    pub float_properties: Option<HashMap<String, f64>>,
303
304    #[serde(skip_serializing_if = "Option::is_none")]
305    pub keyword_map: Option<HashMap<String, bool>>,
306
307    #[serde(skip_serializing_if = "Option::is_none")]
308    pub name: Option<String>,
309
310    #[serde(skip_serializing_if = "Option::is_none")]
311    pub render_queue: Option<i64>,
312
313    /// This contains shader name.  VRM/MToon, VRM/UnlitTransparentZWrite, and VRM_USE_GLTFSHADER
314    /// (and legacy materials as Standard, UniGLTF/UniUnlit, VRM/UnlitTexture, VRM/UnlitCutout,
315    /// VRM/UnlitTransparent) . If VRM_USE_GLTFSHADER is specified, use same index of gltf's
316    /// material settings
317    #[serde(skip_serializing_if = "Option::is_none")]
318    pub shader: Option<String>,
319
320    #[serde(skip_serializing_if = "Option::is_none")]
321    pub tag_map: Option<HashMap<String, String>>,
322
323    #[serde(
324        default,
325        skip_serializing_if = "Option::is_none",
326        deserialize_with = "deserialize_option_map_index::<_, gltf::json::Texture>"
327    )]
328    #[cfg(feature = "gltf_index")]
329    pub texture_properties: Option<HashMap<String, gltf::json::Index<gltf::json::Texture>>>,
330    #[cfg(not(feature = "gltf_index"))]
331    pub texture_properties: Option<HashMap<String, usize>>,
332
333    #[serde(skip_serializing_if = "Option::is_none")]
334    pub vector_properties: Option<HashMap<String, Vec<f64>>>,
335}
336
337#[derive(Debug, Clone, Serialize, Deserialize)]
338#[serde(rename_all = "camelCase")]
339pub struct VRMMeta {
340    /// A person who can perform with this avatar
341    #[serde(skip_serializing_if = "Option::is_none")]
342    pub allowed_user_name: Option<AllowedUserName>,
343
344    /// Author of VRM model
345    #[serde(skip_serializing_if = "Option::is_none")]
346    pub author: Option<String>,
347
348    /// For commercial use
349    #[serde(skip_serializing_if = "Option::is_none")]
350    pub commercial_ussage_name: Option<UssageName>,
351
352    /// Contact Information of VRM model author
353    #[serde(skip_serializing_if = "Option::is_none")]
354    pub contact_information: Option<String>,
355
356    /// License type
357    #[serde(skip_serializing_if = "Option::is_none")]
358    pub license_name: Option<LicenseName>,
359
360    /// If “Other” is selected, put the URL link of the license document here.
361    #[serde(skip_serializing_if = "Option::is_none")]
362    pub other_license_url: Option<String>,
363
364    /// If there are any conditions not mentioned above, put the URL link of the license document
365    /// here.
366    #[serde(skip_serializing_if = "Option::is_none")]
367    pub other_permission_url: Option<String>,
368
369    /// Reference of VRM model
370    #[serde(skip_serializing_if = "Option::is_none")]
371    pub reference: Option<String>,
372
373    /// Permission to perform sexual acts with this avatar
374    #[serde(skip_serializing_if = "Option::is_none")]
375    pub sexual_ussage_name: Option<UssageName>,
376
377    /// Thumbnail of VRM model
378    #[serde(
379        default,
380        skip_serializing_if = "Option::is_none",
381        deserialize_with = "deserialize_option_index::<_, gltf::json::Texture>"
382    )]
383    #[cfg(feature = "gltf_index")]
384    pub texture: Option<gltf::json::Index<gltf::json::Texture>>,
385    #[cfg(not(feature = "gltf_index"))]
386    pub texture: Option<i64>,
387
388    /// Title of VRM model
389    #[serde(skip_serializing_if = "Option::is_none")]
390    pub title: Option<String>,
391
392    /// Version of VRM model
393    #[serde(skip_serializing_if = "Option::is_none")]
394    pub version: Option<String>,
395
396    /// Permission to perform violent acts with this avatar
397    #[serde(skip_serializing_if = "Option::is_none")]
398    pub violent_ussage_name: Option<UssageName>,
399}
400
401/// The setting of automatic animation of string-like objects such as tails and hairs.
402#[derive(Debug, Clone, Serialize, Deserialize)]
403#[serde(rename_all = "camelCase")]
404pub struct VRMSecondaryAnimation {
405    #[serde(skip_serializing_if = "Option::is_none")]
406    pub bone_groups: Option<Vec<VRMSecondaryAnimationSpring>>,
407
408    #[serde(skip_serializing_if = "Option::is_none")]
409    pub collider_groups: Option<Vec<VRMSecondaryAnimationColliderGroup>>,
410}
411
412#[derive(Debug, Clone, Serialize, Deserialize)]
413#[serde(rename_all = "camelCase")]
414pub struct VRMSecondaryAnimationSpring {
415    /// Specify the node index of the root bone of the swaying object.
416    #[serde(skip_serializing_if = "Option::is_none")]
417    #[cfg(feature = "gltf_index")]
418    pub bones: Option<Vec<gltf::json::Index<gltf::json::Node>>>,
419    #[cfg(not(feature = "gltf_index"))]
420    pub bones: Option<Vec<i64>>,
421
422    /// The reference point of a swaying object can be set at any location except the origin.
423    /// When implementing UI moving with warp, the parent node to move with warp can be specified
424    /// if you don't want to make the object swaying with warp movement.
425    #[serde(
426        default,
427        skip_serializing_if = "Option::is_none",
428        deserialize_with = "deserialize_option_index::<_, gltf::json::Node>"
429    )]
430    #[cfg(feature = "gltf_index")]
431    pub center: Option<gltf::json::Index<gltf::json::Node>>,
432    #[cfg(not(feature = "gltf_index"))]
433    pub center: Option<i64>,
434
435    /// Specify the index of the collider group for collisions with swaying objects.
436    #[serde(skip_serializing_if = "Option::is_none")]
437    pub collider_groups: Option<Vec<i64>>,
438
439    /// Annotation comment
440    #[serde(skip_serializing_if = "Option::is_none")]
441    pub comment: Option<String>,
442
443    /// The resistance (deceleration) of automatic animation.
444    #[serde(skip_serializing_if = "Option::is_none")]
445    pub drag_force: Option<f64>,
446
447    /// The direction of gravity. Set (0, -1, 0) for simulating the gravity. Set (1, 0, 0) for
448    /// simulating the wind.
449    #[serde(skip_serializing_if = "Option::is_none")]
450    pub gravity_dir: Option<GravityDir>,
451
452    /// The strength of gravity.
453    #[serde(skip_serializing_if = "Option::is_none")]
454    pub gravity_power: Option<f64>,
455
456    /// The radius of the sphere used for the collision detection with colliders.
457    #[serde(skip_serializing_if = "Option::is_none")]
458    pub hit_radius: Option<f64>,
459
460    /// The resilience of the swaying object (the power of returning to the initial pose).
461    #[serde(skip_serializing_if = "Option::is_none")]
462    pub stiffiness: Option<f64>,
463}
464
465/// The direction of gravity. Set (0, -1, 0) for simulating the gravity. Set (1, 0, 0) for
466/// simulating the wind.
467pub type GravityDir = OptionalVector3;
468
469#[derive(Debug, Clone, Serialize, Deserialize)]
470pub struct VRMSecondaryAnimationColliderGroup {
471    #[serde(skip_serializing_if = "Option::is_none")]
472    pub colliders: Option<Vec<Collider>>,
473
474    /// The node of the collider group for setting up collision detections.
475    #[serde(
476        default,
477        skip_serializing_if = "Option::is_none",
478        deserialize_with = "deserialize_option_index::<_, gltf::json::Node>"
479    )]
480    pub node: Option<gltf::json::Index<gltf::json::Node>>,
481    #[cfg(not(feature = "gltf_index"))]
482    pub node: Option<i64>,
483}
484
485#[derive(Debug, Clone, Serialize, Deserialize)]
486pub struct Collider {
487    /// The local coordinate from the node of the collider group in *left-handed* Y-up coordinate.
488    #[serde(skip_serializing_if = "Option::is_none")]
489    pub offset: Option<Offset>,
490
491    /// The radius of the collider.
492    #[serde(skip_serializing_if = "Option::is_none")]
493    pub radius: Option<f64>,
494}
495
496/// The local coordinate from the node of the collider group in *left-handed* Y-up coordinate.
497pub type Offset = OptionalVector3;
498
499/// Predefined Expression name.
500#[derive(Debug, Clone, Copy, Hash, Serialize, Deserialize)]
501#[serde(rename_all = "snake_case")]
502pub enum PresetName {
503    A,
504    Angry,
505    Blink,
506    BlinkL,
507    BlinkR,
508    E,
509    Fun,
510    I,
511    Joy,
512    Lookdown,
513    Lookleft,
514    Lookright,
515    Lookup,
516    Neutral,
517    O,
518    Sorrow,
519    U,
520    Unknown,
521}
522
523/// Eye controller mode.
524#[derive(Debug, Clone, Copy, Hash, Serialize, Deserialize)]
525pub enum LookAtTypeName {
526    BlendShape,
527    Bone,
528}
529
530/// Human bone name.
531#[derive(Debug, Clone, Copy, Hash, Serialize, Deserialize)]
532#[serde(rename_all = "camelCase")]
533pub enum Bone {
534    Chest,
535    Head,
536    Hips,
537    Jaw,
538    LeftEye,
539    LeftFoot,
540    LeftHand,
541    LeftIndexDistal,
542    LeftIndexIntermediate,
543    LeftIndexProximal,
544    LeftLittleDistal,
545    LeftLittleIntermediate,
546    LeftLittleProximal,
547    LeftLowerArm,
548    LeftLowerLeg,
549    LeftMiddleDistal,
550    LeftMiddleIntermediate,
551    LeftMiddleProximal,
552    LeftRingDistal,
553    LeftRingIntermediate,
554    LeftRingProximal,
555    LeftShoulder,
556    LeftThumbDistal,
557    LeftThumbIntermediate,
558    LeftThumbProximal,
559    LeftToes,
560    LeftUpperArm,
561    LeftUpperLeg,
562    Neck,
563    RightEye,
564    RightFoot,
565    RightHand,
566    RightIndexDistal,
567    RightIndexIntermediate,
568    RightIndexProximal,
569    RightLittleDistal,
570    RightLittleIntermediate,
571    RightLittleProximal,
572    RightLowerArm,
573    RightLowerLeg,
574    RightMiddleDistal,
575    RightMiddleIntermediate,
576    RightMiddleProximal,
577    RightRingDistal,
578    RightRingIntermediate,
579    RightRingProximal,
580    RightShoulder,
581    RightThumbDistal,
582    RightThumbIntermediate,
583    RightThumbProximal,
584    RightToes,
585    RightUpperArm,
586    RightUpperLeg,
587    Spine,
588    UpperChest,
589}
590
591/// A person who can perform with this avatar.
592#[derive(Debug, Clone, Copy, Hash, Serialize, Deserialize)]
593pub enum AllowedUserName {
594    Everyone,
595    ExplicitlyLicensedPerson,
596    OnlyAuthor,
597}
598
599/// Usage Permission.
600///
601/// NOTE: the typo is intended following the spec.
602#[derive(Debug, Clone, Copy, Hash, Serialize, Deserialize)]
603pub enum UssageName {
604    Allow,
605    Disallow,
606}
607
608/// License type.
609#[derive(Debug, Clone, Copy, Hash, Serialize, Deserialize)]
610pub enum LicenseName {
611    #[serde(rename = "CC0")]
612    Cc0,
613
614    #[serde(rename = "CC_BY")]
615    CcBy,
616
617    #[serde(rename = "CC_BY_NC")]
618    CcByNc,
619
620    #[serde(rename = "CC_BY_NC_ND")]
621    CcByNcNd,
622
623    #[serde(rename = "CC_BY_NC_SA")]
624    CcByNcSa,
625
626    #[serde(rename = "CC_BY_ND")]
627    CcByNd,
628
629    #[serde(rename = "CC_BY_SA")]
630    CcBySa,
631
632    #[serde(rename = "Redistribution_Prohibited")]
633    RedistributionProhibited,
634
635    Other,
636}