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        skip_serializing_if = "Option::is_none",
299        deserialize_with = "deserialize_option_map_and_skip_nullable::<_, String, f64>"
300    )]
301    pub float_properties: Option<HashMap<String, f64>>,
302
303    #[serde(skip_serializing_if = "Option::is_none")]
304    pub keyword_map: Option<HashMap<String, bool>>,
305
306    #[serde(skip_serializing_if = "Option::is_none")]
307    pub name: Option<String>,
308
309    #[serde(skip_serializing_if = "Option::is_none")]
310    pub render_queue: Option<i64>,
311
312    /// This contains shader name.  VRM/MToon, VRM/UnlitTransparentZWrite, and VRM_USE_GLTFSHADER
313    /// (and legacy materials as Standard, UniGLTF/UniUnlit, VRM/UnlitTexture, VRM/UnlitCutout,
314    /// VRM/UnlitTransparent) . If VRM_USE_GLTFSHADER is specified, use same index of gltf's
315    /// material settings
316    #[serde(skip_serializing_if = "Option::is_none")]
317    pub shader: Option<String>,
318
319    #[serde(skip_serializing_if = "Option::is_none")]
320    pub tag_map: Option<HashMap<String, String>>,
321
322    #[serde(
323        default,
324        skip_serializing_if = "Option::is_none",
325        deserialize_with = "deserialize_option_map_index::<_, gltf::json::Texture>"
326    )]
327    #[cfg(feature = "gltf_index")]
328    pub texture_properties: Option<HashMap<String, gltf::json::Index<gltf::json::Texture>>>,
329    #[cfg(not(feature = "gltf_index"))]
330    pub texture_properties: Option<HashMap<String, usize>>,
331
332    #[serde(skip_serializing_if = "Option::is_none")]
333    pub vector_properties: Option<HashMap<String, Vec<f64>>>,
334}
335
336#[derive(Debug, Clone, Serialize, Deserialize)]
337#[serde(rename_all = "camelCase")]
338pub struct VRMMeta {
339    /// A person who can perform with this avatar
340    #[serde(skip_serializing_if = "Option::is_none")]
341    pub allowed_user_name: Option<AllowedUserName>,
342
343    /// Author of VRM model
344    #[serde(skip_serializing_if = "Option::is_none")]
345    pub author: Option<String>,
346
347    /// For commercial use
348    #[serde(skip_serializing_if = "Option::is_none")]
349    pub commercial_ussage_name: Option<UssageName>,
350
351    /// Contact Information of VRM model author
352    #[serde(skip_serializing_if = "Option::is_none")]
353    pub contact_information: Option<String>,
354
355    /// License type
356    #[serde(skip_serializing_if = "Option::is_none")]
357    pub license_name: Option<LicenseName>,
358
359    /// If “Other” is selected, put the URL link of the license document here.
360    #[serde(skip_serializing_if = "Option::is_none")]
361    pub other_license_url: Option<String>,
362
363    /// If there are any conditions not mentioned above, put the URL link of the license document
364    /// here.
365    #[serde(skip_serializing_if = "Option::is_none")]
366    pub other_permission_url: Option<String>,
367
368    /// Reference of VRM model
369    #[serde(skip_serializing_if = "Option::is_none")]
370    pub reference: Option<String>,
371
372    /// Permission to perform sexual acts with this avatar
373    #[serde(skip_serializing_if = "Option::is_none")]
374    pub sexual_ussage_name: Option<UssageName>,
375
376    /// Thumbnail of VRM model
377    #[serde(
378        default,
379        skip_serializing_if = "Option::is_none",
380        deserialize_with = "deserialize_option_index::<_, gltf::json::Texture>"
381    )]
382    #[cfg(feature = "gltf_index")]
383    pub texture: Option<gltf::json::Index<gltf::json::Texture>>,
384    #[cfg(not(feature = "gltf_index"))]
385    pub texture: Option<i64>,
386
387    /// Title of VRM model
388    #[serde(skip_serializing_if = "Option::is_none")]
389    pub title: Option<String>,
390
391    /// Version of VRM model
392    #[serde(skip_serializing_if = "Option::is_none")]
393    pub version: Option<String>,
394
395    /// Permission to perform violent acts with this avatar
396    #[serde(skip_serializing_if = "Option::is_none")]
397    pub violent_ussage_name: Option<UssageName>,
398}
399
400/// The setting of automatic animation of string-like objects such as tails and hairs.
401#[derive(Debug, Clone, Serialize, Deserialize)]
402#[serde(rename_all = "camelCase")]
403pub struct VRMSecondaryAnimation {
404    #[serde(skip_serializing_if = "Option::is_none")]
405    pub bone_groups: Option<Vec<VRMSecondaryAnimationSpring>>,
406
407    #[serde(skip_serializing_if = "Option::is_none")]
408    pub collider_groups: Option<Vec<VRMSecondaryAnimationColliderGroup>>,
409}
410
411#[derive(Debug, Clone, Serialize, Deserialize)]
412#[serde(rename_all = "camelCase")]
413pub struct VRMSecondaryAnimationSpring {
414    /// Specify the node index of the root bone of the swaying object.
415    #[serde(skip_serializing_if = "Option::is_none")]
416    #[cfg(feature = "gltf_index")]
417    pub bones: Option<Vec<gltf::json::Index<gltf::json::Node>>>,
418    #[cfg(not(feature = "gltf_index"))]
419    pub bones: Option<Vec<i64>>,
420
421    /// The reference point of a swaying object can be set at any location except the origin.
422    /// When implementing UI moving with warp, the parent node to move with warp can be specified
423    /// if you don't want to make the object swaying with warp movement.
424    #[serde(
425        default,
426        skip_serializing_if = "Option::is_none",
427        deserialize_with = "deserialize_option_index::<_, gltf::json::Node>"
428    )]
429    #[cfg(feature = "gltf_index")]
430    pub center: Option<gltf::json::Index<gltf::json::Node>>,
431    #[cfg(not(feature = "gltf_index"))]
432    pub center: Option<i64>,
433
434    /// Specify the index of the collider group for collisions with swaying objects.
435    #[serde(skip_serializing_if = "Option::is_none")]
436    pub collider_groups: Option<Vec<i64>>,
437
438    /// Annotation comment
439    #[serde(skip_serializing_if = "Option::is_none")]
440    pub comment: Option<String>,
441
442    /// The resistance (deceleration) of automatic animation.
443    #[serde(skip_serializing_if = "Option::is_none")]
444    pub drag_force: Option<f64>,
445
446    /// The direction of gravity. Set (0, -1, 0) for simulating the gravity. Set (1, 0, 0) for
447    /// simulating the wind.
448    #[serde(skip_serializing_if = "Option::is_none")]
449    pub gravity_dir: Option<GravityDir>,
450
451    /// The strength of gravity.
452    #[serde(skip_serializing_if = "Option::is_none")]
453    pub gravity_power: Option<f64>,
454
455    /// The radius of the sphere used for the collision detection with colliders.
456    #[serde(skip_serializing_if = "Option::is_none")]
457    pub hit_radius: Option<f64>,
458
459    /// The resilience of the swaying object (the power of returning to the initial pose).
460    #[serde(skip_serializing_if = "Option::is_none")]
461    pub stiffiness: Option<f64>,
462}
463
464/// The direction of gravity. Set (0, -1, 0) for simulating the gravity. Set (1, 0, 0) for
465/// simulating the wind.
466pub type GravityDir = OptionalVector3;
467
468#[derive(Debug, Clone, Serialize, Deserialize)]
469pub struct VRMSecondaryAnimationColliderGroup {
470    #[serde(skip_serializing_if = "Option::is_none")]
471    pub colliders: Option<Vec<Collider>>,
472
473    /// The node of the collider group for setting up collision detections.
474    #[serde(
475        default,
476        skip_serializing_if = "Option::is_none",
477        deserialize_with = "deserialize_option_index::<_, gltf::json::Node>"
478    )]
479    pub node: Option<gltf::json::Index<gltf::json::Node>>,
480    #[cfg(not(feature = "gltf_index"))]
481    pub node: Option<i64>,
482}
483
484#[derive(Debug, Clone, Serialize, Deserialize)]
485pub struct Collider {
486    /// The local coordinate from the node of the collider group in *left-handed* Y-up coordinate.
487    #[serde(skip_serializing_if = "Option::is_none")]
488    pub offset: Option<Offset>,
489
490    /// The radius of the collider.
491    #[serde(skip_serializing_if = "Option::is_none")]
492    pub radius: Option<f64>,
493}
494
495/// The local coordinate from the node of the collider group in *left-handed* Y-up coordinate.
496pub type Offset = OptionalVector3;
497
498/// Predefined Expression name.
499#[derive(Debug, Clone, Copy, Hash, Serialize, Deserialize)]
500#[serde(rename_all = "snake_case")]
501pub enum PresetName {
502    A,
503    Angry,
504    Blink,
505    BlinkL,
506    BlinkR,
507    E,
508    Fun,
509    I,
510    Joy,
511    Lookdown,
512    Lookleft,
513    Lookright,
514    Lookup,
515    Neutral,
516    O,
517    Sorrow,
518    U,
519    Unknown,
520}
521
522/// Eye controller mode.
523#[derive(Debug, Clone, Copy, Hash, Serialize, Deserialize)]
524pub enum LookAtTypeName {
525    BlendShape,
526    Bone,
527}
528
529/// Human bone name.
530#[derive(Debug, Clone, Copy, Hash, Serialize, Deserialize)]
531#[serde(rename_all = "camelCase")]
532pub enum Bone {
533    Chest,
534    Head,
535    Hips,
536    Jaw,
537    LeftEye,
538    LeftFoot,
539    LeftHand,
540    LeftIndexDistal,
541    LeftIndexIntermediate,
542    LeftIndexProximal,
543    LeftLittleDistal,
544    LeftLittleIntermediate,
545    LeftLittleProximal,
546    LeftLowerArm,
547    LeftLowerLeg,
548    LeftMiddleDistal,
549    LeftMiddleIntermediate,
550    LeftMiddleProximal,
551    LeftRingDistal,
552    LeftRingIntermediate,
553    LeftRingProximal,
554    LeftShoulder,
555    LeftThumbDistal,
556    LeftThumbIntermediate,
557    LeftThumbProximal,
558    LeftToes,
559    LeftUpperArm,
560    LeftUpperLeg,
561    Neck,
562    RightEye,
563    RightFoot,
564    RightHand,
565    RightIndexDistal,
566    RightIndexIntermediate,
567    RightIndexProximal,
568    RightLittleDistal,
569    RightLittleIntermediate,
570    RightLittleProximal,
571    RightLowerArm,
572    RightLowerLeg,
573    RightMiddleDistal,
574    RightMiddleIntermediate,
575    RightMiddleProximal,
576    RightRingDistal,
577    RightRingIntermediate,
578    RightRingProximal,
579    RightShoulder,
580    RightThumbDistal,
581    RightThumbIntermediate,
582    RightThumbProximal,
583    RightToes,
584    RightUpperArm,
585    RightUpperLeg,
586    Spine,
587    UpperChest,
588}
589
590/// A person who can perform with this avatar.
591#[derive(Debug, Clone, Copy, Hash, Serialize, Deserialize)]
592pub enum AllowedUserName {
593    Everyone,
594    ExplicitlyLicensedPerson,
595    OnlyAuthor,
596}
597
598/// Usage Permission.
599///
600/// NOTE: the typo is intended following the spec.
601#[derive(Debug, Clone, Copy, Hash, Serialize, Deserialize)]
602pub enum UssageName {
603    Allow,
604    Disallow,
605}
606
607/// License type.
608#[derive(Debug, Clone, Copy, Hash, Serialize, Deserialize)]
609pub enum LicenseName {
610    #[serde(rename = "CC0")]
611    Cc0,
612
613    #[serde(rename = "CC_BY")]
614    CcBy,
615
616    #[serde(rename = "CC_BY_NC")]
617    CcByNc,
618
619    #[serde(rename = "CC_BY_NC_ND")]
620    CcByNcNd,
621
622    #[serde(rename = "CC_BY_NC_SA")]
623    CcByNcSa,
624
625    #[serde(rename = "CC_BY_ND")]
626    CcByNd,
627
628    #[serde(rename = "CC_BY_SA")]
629    CcBySa,
630
631    #[serde(rename = "Redistribution_Prohibited")]
632    RedistributionProhibited,
633
634    Other,
635}