diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..eb68687 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +/tmp \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..ed2c805 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,424 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "autocfg" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bytemuck" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78834c15cb5d5efe3452d58b1e8ba890dd62d21907f867f383358198e56ebca5" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "console" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" +dependencies = [ + "encode_unicode", + "lazy_static", + "libc", + "windows-sys", +] + +[[package]] +name = "crc32fast" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "encode_unicode" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" + +[[package]] +name = "fdeflate" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f9bfee30e4dedf0ab8b422f03af778d9612b63f502710fc500a334ebe2de645" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "flate2" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "gltf" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ce1918195723ce6ac74e80542c5a96a40c2b26162c1957a5cd70799b8cacf7" +dependencies = [ + "base64", + "byteorder", + "gltf-json", + "image", + "lazy_static", + "serde_json", + "urlencoding", +] + +[[package]] +name = "gltf-derive" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14070e711538afba5d6c807edb74bcb84e5dbb9211a3bf5dea0dfab5b24f4c51" +dependencies = [ + "inflections", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "gltf-json" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6176f9d60a7eab0a877e8e96548605dedbde9190a7ae1e80bbcc1c9af03ab14" +dependencies = [ + "gltf-derive", + "serde", + "serde_derive", + "serde_json", +] + +[[package]] +name = "image" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd54d660e773627692c524beaad361aca785a4f9f5730ce91f42aabe5bce3d11" +dependencies = [ + "bytemuck", + "byteorder", + "num-traits", + "png", + "zune-core", + "zune-jpeg", +] + +[[package]] +name = "inflections" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a257582fdcde896fd96463bf2d40eefea0580021c0712a0e2b028b60b47a837a" + +[[package]] +name = "insta" +version = "1.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "810ae6042d48e2c9e9215043563a58a80b877bc863228a74cf10c49d4620a6f5" +dependencies = [ + "console", + "lazy_static", + "linked-hash-map", + "similar", +] + +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.155" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" + +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + +[[package]] +name = "miniz_oxide" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" +dependencies = [ + "adler", + "simd-adler32", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "png" +version = "0.17.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06e4b0d3d1312775e782c86c91a111aa1f910cbb65e1337f9975b5f9a554b5e1" +dependencies = [ + "bitflags", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + +[[package]] +name = "proc-macro2" +version = "1.0.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22244ce15aa966053a896d1accb3a6e68469b97c7f33f284b99f0d576879fc23" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rustc-hash" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152" + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "serde" +version = "1.0.203" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.203" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + +[[package]] +name = "similar" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa42c91313f1d05da9b26f267f931cf178d4aba455b4c4622dd7355eb80c6640" + +[[package]] +name = "syn" +version = "2.0.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + +[[package]] +name = "vrm-spec" +version = "0.0.1" +dependencies = [ + "gltf", + "insta", + "rustc-hash", + "serde", + "serde_json", +] + +[[package]] +name = "vrm-spec-example" +version = "0.0.0" +dependencies = [ + "gltf", + "rustc-hash", + "serde", + "serde_json", + "vrm-spec", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" + +[[package]] +name = "zune-core" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" + +[[package]] +name = "zune-jpeg" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec866b44a2a1fd6133d363f073ca1b179f438f99e7e5bfb1e33f7181facfe448" +dependencies = [ + "zune-core", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..6b7dfc2 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,21 @@ +[workspace.package] +authors = ["pixiv"] +edition = "2021" +license = "Apache-2.0" +readme = "README.md" +rust-version = "1.68" + +[workspace] +members = [ + "crates/*", + "examples/*", +] +# https://github.com/rust-lang/cargo/issues/10112 +resolver = "2" + +[workspace.dependencies] +gltf = {version = "1", features = ["utils", "extensions", "extras"]} +insta = "=1.39.0" +rustc-hash = "< 3" +serde = {version = "1.0", features = ["derive"]} +serde_json = "1.0" diff --git a/README.md b/README.md index 40ca506..74b4ce5 100644 --- a/README.md +++ b/README.md @@ -1 +1,3 @@ -# rm-utils-rs \ No newline at end of file +# vrm-utils-rs + +Utilities for handling the [VRM](https://vrm.dev) format in rust. diff --git a/crates/vrm-spec/Cargo.toml b/crates/vrm-spec/Cargo.toml new file mode 100644 index 0000000..06baf9b --- /dev/null +++ b/crates/vrm-spec/Cargo.toml @@ -0,0 +1,25 @@ +[package] +authors = ["pixiv"] +description = "VRM data structures" +documentation = "https://docs.rs/vrm-spec" +edition.workspace = true +license = "Apache-2.0" +name = "vrm-spec" +readme = "README.md" +repository = "https://github.com/pixiv/vrm-spec" +rust-version.workspace = true +version = "0.0.1" + +[dependencies] +gltf = {workspace = true, features = ["utils", "extensions"], optional = true} +rustc-hash = {workspace = true, optional = true} +serde = {workspace = true} +serde_json = {workspace = true} + +[dev-dependencies] +insta = {workspace = true} + +[features] +default = ["rustc_hash", "gltf_index"] +gltf_index = ["dep:gltf"] +rustc_hash = ["dep:rustc-hash"] diff --git a/crates/vrm-spec/README.md b/crates/vrm-spec/README.md new file mode 100644 index 0000000..ae63d71 --- /dev/null +++ b/crates/vrm-spec/README.md @@ -0,0 +1,17 @@ +# vrm-spec + +Data structures for the [VRM](https://vrm.dev) Format. + +Schema definitions: + +## Example + +```rust +use vrm_spec::vrmc_vrm_1_0::{VRMCVrmSchema, VRMC_VRM}; + +let file = include_bytes!("../../../fixtures/VRM1_Constraint_Twist_Sample.vrm"); +let (doc, _, _) = gltf::import_slice(file).expect("ok"); +let value = doc.extension_value(VRMC_VRM).expect("exist"); +let vrmc_vrm: VRMCVrmSchema = serde_json::from_value(value.to_owned()).expect("ok"); +// do something with vrm +``` diff --git a/crates/vrm-spec/src/lib.rs b/crates/vrm-spec/src/lib.rs new file mode 100644 index 0000000..42b6ac6 --- /dev/null +++ b/crates/vrm-spec/src/lib.rs @@ -0,0 +1,24 @@ +//! # vrm-spec +//! +//! Data structures for the [VRM](https://vrm.dev) Format. +//! +//! Schema definitions: +//! +//! ## Example +//! +//! ```rust +//! use vrm_spec::vrmc_vrm_1_0::{VRMCVrmSchema, VRMC_VRM}; +//! +//! let file = include_bytes!("../../../fixtures/VRM1_Constraint_Twist_Sample.vrm"); +//! let (doc, _, _) = gltf::import_slice(file).expect("ok"); +//! let value = doc.extension_value(VRMC_VRM).expect("exist"); +//! let vrmc_vrm: VRMCVrmSchema = serde_json::from_value(value.to_owned()).expect("ok"); +//! +//! // do something with vrm +//! ``` + +mod serde_utils; +pub mod vrm_0_0; +pub mod vrmc_materials_mtoon_1_0; +pub mod vrmc_spring_bone_1_0; +pub mod vrmc_vrm_1_0; diff --git a/crates/vrm-spec/src/serde_utils.rs b/crates/vrm-spec/src/serde_utils.rs new file mode 100644 index 0000000..fc55cc0 --- /dev/null +++ b/crates/vrm-spec/src/serde_utils.rs @@ -0,0 +1,48 @@ +/// This mod handles common issues when using VRMs. + +#[cfg(feature = "rustc_hash")] +use rustc_hash::FxHashMap as HashMap; +use serde::{Deserialize, Deserializer}; +#[cfg(not(feature = "rustc_hash"))] +use std::collections::HashMap; + +#[cfg(feature = "gltf_index")] +use gltf::json::Index; + +// NOTE: Unity puts -1 in some indexes but those are not valid as glTF index. +// Treat minus values as None +#[cfg(feature = "gltf_index")] +pub(crate) fn deserialize_option_index<'de, D, T>( + deserializer: D, +) -> Result>, D::Error> +where + D: Deserializer<'de>, +{ + let n = i64::deserialize(deserializer)?; + Ok(if n >= 0 { + Some(Index::new(n as u32)) + } else { + None + }) +} + +// NOTE: Unity puts -1 in some indexes but those are not valid as glTF index. +// Treat minus values as None +#[cfg(feature = "gltf_index")] +pub(crate) fn deserialize_option_map_index<'de, D, T>( + deserializer: D, +) -> Result>>, D::Error> +where + D: Deserializer<'de>, +{ + let map = HashMap::::deserialize(deserializer)?; + Ok(Some(HashMap::>::from_iter( + map.into_iter().filter_map(|(k, v)| { + if v >= 0 { + Some((k, Index::::new(v as u32))) + } else { + None + } + }), + ))) +} diff --git a/crates/vrm-spec/src/vrm_0_0.rs b/crates/vrm-spec/src/vrm_0_0.rs new file mode 100644 index 0000000..1b2b1dc --- /dev/null +++ b/crates/vrm-spec/src/vrm_0_0.rs @@ -0,0 +1,629 @@ +//! Data structures for the [`VRM`](https://github.com/vrm-c/vrm-specification/tree/master/specification/0.0) 0.0 glTF Extension. + +/// VRM extension name +pub const VRM: &str = "VRM"; + +#[cfg(feature = "rustc_hash")] +use rustc_hash::FxHashMap as HashMap; +use serde::{Deserialize, Serialize}; +#[cfg(not(feature = "rustc_hash"))] +use std::collections::HashMap; + +use crate::serde_utils::{deserialize_option_index, deserialize_option_map_index}; + +/// VRM extension is for 3d humanoid avatars (and models) in VR applications. +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct VRM0Schema { + #[serde(skip_serializing_if = "Option::is_none")] + pub blend_shape_master: Option, + + /// Version of exporter that vrm created. UniVRM-0.46 + #[serde(skip_serializing_if = "Option::is_none")] + pub exporter_version: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub first_person: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub humanoid: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub material_properties: Option>, + + #[serde(skip_serializing_if = "Option::is_none")] + pub meta: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub secondary_animation: Option, + + /// Version of VRM specification. 0.0 + #[serde(skip_serializing_if = "Option::is_none")] + pub spec_version: Option, +} + +/// BlendShapeAvatar of UniVRM +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct VRMBlendShape { + #[serde(skip_serializing_if = "Option::is_none")] + pub blend_shape_groups: Option>, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct VRMBlendShapeGroup { + /// Low level blendshape references. + #[serde(skip_serializing_if = "Option::is_none")] + pub binds: Option>, + + /// 0 or 1. Do not allow an intermediate value. Value should rounded + #[serde(skip_serializing_if = "Option::is_none")] + pub is_binary: Option, + + /// Material animation references. + #[serde(skip_serializing_if = "Option::is_none")] + pub material_values: Option>, + + /// Expression name + #[serde(skip_serializing_if = "Option::is_none")] + pub name: Option, + + /// Predefined Expression name + #[serde(skip_serializing_if = "Option::is_none")] + pub preset_name: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct VRMBlendShapeBind { + #[serde(skip_serializing_if = "Option::is_none")] + pub index: Option, + + #[serde( + default, + skip_serializing_if = "Option::is_none", + deserialize_with = "deserialize_option_index::<_, gltf::json::Mesh>" + )] + #[cfg(feature = "gltf_index")] + pub mesh: Option>, + #[cfg(not(feature = "gltf_index"))] + pub mesh: Option, + + /// SkinnedMeshRenderer.SetBlendShapeWeight + #[serde(skip_serializing_if = "Option::is_none")] + pub weight: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct VRMBlendShapeMaterialBind { + #[serde(skip_serializing_if = "Option::is_none")] + pub material_name: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub property_name: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub target_value: Option>, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct VRMFirstPerson { + /// The bone whose rendering should be turned off in first-person view. Usually Head is + /// specified. + #[serde( + default, + skip_serializing_if = "Option::is_none", + deserialize_with = "deserialize_option_index::<_, gltf::json::Node>" + )] + #[cfg(feature = "gltf_index")] + pub first_person_bone: Option>, + #[cfg(not(feature = "gltf_index"))] + pub first_person_bone: Option, + + /// The target position of the VR headset in first-person view. It is assumed that an offset + /// from the head bone to the VR headset is added. + #[serde(skip_serializing_if = "Option::is_none")] + pub first_person_bone_offset: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub look_at_horizontal_inner: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub look_at_horizontal_outer: Option, + + /// Eye controller mode. + #[serde(skip_serializing_if = "Option::is_none")] + pub look_at_type_name: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub look_at_vertical_down: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub look_at_vertical_up: Option, + + /// Switch display / undisplay for each mesh in first-person view or the others. + #[serde(skip_serializing_if = "Option::is_none")] + pub mesh_annotations: Option>, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +/// Vector3 but has optional x,y,z. +/// +/// normally x,y,z should not be optional but the VRM 0.0 spec allows it +pub struct OptionalVector3 { + #[serde(skip_serializing_if = "Option::is_none")] + pub x: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub y: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub z: Option, +} + +/// The target position of the VR headset in first-person view. It is assumed that an offset +/// from the head bone to the VR headset is added. +pub type FirstPersonBoneOffset = OptionalVector3; + +/// Eye controller setting. +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct VRMFirstPersonDegreeMap { + /// None linear mapping params. time, value, inTangent, outTangent + #[serde(skip_serializing_if = "Option::is_none")] + pub curve: Option>, + + /// Look at input clamp range degree. + #[serde(skip_serializing_if = "Option::is_none")] + pub x_range: Option, + + /// Look at map range degree from xRange. + #[serde(skip_serializing_if = "Option::is_none")] + pub y_range: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct VRMFirstPersonMeshAnnotation { + #[serde(skip_serializing_if = "Option::is_none")] + pub first_person_flag: Option, + + #[serde( + default, + skip_serializing_if = "Option::is_none", + deserialize_with = "deserialize_option_index::<_, gltf::json::Mesh>" + )] + #[cfg(feature = "gltf_index")] + pub mesh: Option>, + #[cfg(not(feature = "gltf_index"))] + pub mesh: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct VRMHumanoid { + /// Unity's HumanDescription.armStretch + #[serde(skip_serializing_if = "Option::is_none")] + pub arm_stretch: Option, + + /// Unity's HumanDescription.feetSpacing + #[serde(skip_serializing_if = "Option::is_none")] + pub feet_spacing: Option, + + /// Unity's HumanDescription.hasTranslationDoF + #[serde(skip_serializing_if = "Option::is_none")] + pub has_translation_do_f: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub human_bones: Option>, + + /// Unity's HumanDescription.legStretch + #[serde(skip_serializing_if = "Option::is_none")] + pub leg_stretch: Option, + + /// Unity's HumanDescription.lowerArmTwist + #[serde(skip_serializing_if = "Option::is_none")] + pub lower_arm_twist: Option, + + /// Unity's HumanDescription.lowerLegTwist + #[serde(skip_serializing_if = "Option::is_none")] + pub lower_leg_twist: Option, + + /// Unity's HumanDescription.upperArmTwist + #[serde(skip_serializing_if = "Option::is_none")] + pub upper_arm_twist: Option, + + /// Unity's HumanDescription.upperLegTwist + #[serde(skip_serializing_if = "Option::is_none")] + pub upper_leg_twist: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct VRMHumanoidBone { + /// Unity's HumanLimit.axisLength + #[serde(skip_serializing_if = "Option::is_none")] + pub axis_length: Option, + + /// Human bone name. + #[serde(skip_serializing_if = "Option::is_none")] + pub bone: Option, + + /// Unity's HumanLimit.center + #[serde(skip_serializing_if = "Option::is_none")] + pub center: Option
, + + /// Unity's HumanLimit.max + #[serde(skip_serializing_if = "Option::is_none")] + pub max: Option, + + /// Unity's HumanLimit.min + #[serde(skip_serializing_if = "Option::is_none")] + pub min: Option, + + /// Reference node index + #[serde( + default, + skip_serializing_if = "Option::is_none", + deserialize_with = "deserialize_option_index::<_, gltf::json::Node>" + )] + #[cfg(feature = "gltf_index")] + pub node: Option>, + #[cfg(not(feature = "gltf_index"))] + pub node: Option, + + /// Unity's HumanLimit.useDefaultValues + #[serde(skip_serializing_if = "Option::is_none")] + pub use_default_values: Option, +} + +/// Unity's HumanLimit.center +pub type Center = OptionalVector3; + +/// Unity's HumanLimit.max +pub type Max = OptionalVector3; + +/// Unity's HumanLimit.min +pub type Min = OptionalVector3; + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct VRMMaterial { + #[serde(skip_serializing_if = "Option::is_none")] + pub float_properties: Option>, + + #[serde(skip_serializing_if = "Option::is_none")] + pub keyword_map: Option>, + + #[serde(skip_serializing_if = "Option::is_none")] + pub name: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub render_queue: Option, + + /// This contains shader name. VRM/MToon, VRM/UnlitTransparentZWrite, and VRM_USE_GLTFSHADER + /// (and legacy materials as Standard, UniGLTF/UniUnlit, VRM/UnlitTexture, VRM/UnlitCutout, + /// VRM/UnlitTransparent) . If VRM_USE_GLTFSHADER is specified, use same index of gltf's + /// material settings + #[serde(skip_serializing_if = "Option::is_none")] + pub shader: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub tag_map: Option>, + + #[serde( + default, + skip_serializing_if = "Option::is_none", + deserialize_with = "deserialize_option_map_index::<_, gltf::json::Texture>" + )] + #[cfg(feature = "gltf_index")] + pub texture_properties: Option>>, + #[cfg(not(feature = "gltf_index"))] + pub texture_properties: Option>, + + #[serde(skip_serializing_if = "Option::is_none")] + pub vector_properties: Option>>, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct VRMMeta { + /// A person who can perform with this avatar + #[serde(skip_serializing_if = "Option::is_none")] + pub allowed_user_name: Option, + + /// Author of VRM model + #[serde(skip_serializing_if = "Option::is_none")] + pub author: Option, + + /// For commercial use + #[serde(skip_serializing_if = "Option::is_none")] + pub commercial_ussage_name: Option, + + /// Contact Information of VRM model author + #[serde(skip_serializing_if = "Option::is_none")] + pub contact_information: Option, + + /// License type + #[serde(skip_serializing_if = "Option::is_none")] + pub license_name: Option, + + /// If “Other” is selected, put the URL link of the license document here. + #[serde(skip_serializing_if = "Option::is_none")] + pub other_license_url: Option, + + /// If there are any conditions not mentioned above, put the URL link of the license document + /// here. + #[serde(skip_serializing_if = "Option::is_none")] + pub other_permission_url: Option, + + /// Reference of VRM model + #[serde(skip_serializing_if = "Option::is_none")] + pub reference: Option, + + /// Permission to perform sexual acts with this avatar + #[serde(skip_serializing_if = "Option::is_none")] + pub sexual_ussage_name: Option, + + /// Thumbnail of VRM model + #[serde( + default, + skip_serializing_if = "Option::is_none", + deserialize_with = "deserialize_option_index::<_, gltf::json::Texture>" + )] + #[cfg(feature = "gltf_index")] + pub texture: Option>, + #[cfg(not(feature = "gltf_index"))] + pub texture: Option, + + /// Title of VRM model + #[serde(skip_serializing_if = "Option::is_none")] + pub title: Option, + + /// Version of VRM model + #[serde(skip_serializing_if = "Option::is_none")] + pub version: Option, + + /// Permission to perform violent acts with this avatar + #[serde(skip_serializing_if = "Option::is_none")] + pub violent_ussage_name: Option, +} + +/// The setting of automatic animation of string-like objects such as tails and hairs. +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct VRMSecondaryAnimation { + #[serde(skip_serializing_if = "Option::is_none")] + pub bone_groups: Option>, + + #[serde(skip_serializing_if = "Option::is_none")] + pub collider_groups: Option>, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct VRMSecondaryAnimationSpring { + /// Specify the node index of the root bone of the swaying object. + #[serde(skip_serializing_if = "Option::is_none")] + #[cfg(feature = "gltf_index")] + pub bones: Option>>, + #[cfg(not(feature = "gltf_index"))] + pub bones: Option>, + + /// The reference point of a swaying object can be set at any location except the origin. + /// When implementing UI moving with warp, the parent node to move with warp can be specified + /// if you don't want to make the object swaying with warp movement. + #[serde( + default, + skip_serializing_if = "Option::is_none", + deserialize_with = "deserialize_option_index::<_, gltf::json::Node>" + )] + #[cfg(feature = "gltf_index")] + pub center: Option>, + #[cfg(not(feature = "gltf_index"))] + pub center: Option, + + /// Specify the index of the collider group for collisions with swaying objects. + #[serde(skip_serializing_if = "Option::is_none")] + pub collider_groups: Option>, + + /// Annotation comment + #[serde(skip_serializing_if = "Option::is_none")] + pub comment: Option, + + /// The resistance (deceleration) of automatic animation. + #[serde(skip_serializing_if = "Option::is_none")] + pub drag_force: Option, + + /// The direction of gravity. Set (0, -1, 0) for simulating the gravity. Set (1, 0, 0) for + /// simulating the wind. + #[serde(skip_serializing_if = "Option::is_none")] + pub gravity_dir: Option, + + /// The strength of gravity. + #[serde(skip_serializing_if = "Option::is_none")] + pub gravity_power: Option, + + /// The radius of the sphere used for the collision detection with colliders. + #[serde(skip_serializing_if = "Option::is_none")] + pub hit_radius: Option, + + /// The resilience of the swaying object (the power of returning to the initial pose). + #[serde(skip_serializing_if = "Option::is_none")] + pub stiffiness: Option, +} + +/// The direction of gravity. Set (0, -1, 0) for simulating the gravity. Set (1, 0, 0) for +/// simulating the wind. +pub type GravityDir = OptionalVector3; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct VRMSecondaryAnimationColliderGroup { + #[serde(skip_serializing_if = "Option::is_none")] + pub colliders: Option>, + + /// The node of the collider group for setting up collision detections. + #[serde( + default, + skip_serializing_if = "Option::is_none", + deserialize_with = "deserialize_option_index::<_, gltf::json::Node>" + )] + pub node: Option>, + #[cfg(not(feature = "gltf_index"))] + pub node: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Collider { + /// The local coordinate from the node of the collider group in *left-handed* Y-up coordinate. + #[serde(skip_serializing_if = "Option::is_none")] + pub offset: Option, + + /// The radius of the collider. + #[serde(skip_serializing_if = "Option::is_none")] + pub radius: Option, +} + +/// The local coordinate from the node of the collider group in *left-handed* Y-up coordinate. +pub type Offset = OptionalVector3; + +/// Predefined Expression name. +#[derive(Debug, Clone, Copy, Hash, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum PresetName { + A, + Angry, + Blink, + BlinkL, + BlinkR, + E, + Fun, + I, + Joy, + Lookdown, + Lookleft, + Lookright, + Lookup, + Neutral, + O, + Sorrow, + U, + Unknown, +} + +/// Eye controller mode. +#[derive(Debug, Clone, Copy, Hash, Serialize, Deserialize)] +pub enum LookAtTypeName { + BlendShape, + Bone, +} + +/// Human bone name. +#[derive(Debug, Clone, Copy, Hash, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub enum Bone { + Chest, + Head, + Hips, + Jaw, + LeftEye, + LeftFoot, + LeftHand, + LeftIndexDistal, + LeftIndexIntermediate, + LeftIndexProximal, + LeftLittleDistal, + LeftLittleIntermediate, + LeftLittleProximal, + LeftLowerArm, + LeftLowerLeg, + LeftMiddleDistal, + LeftMiddleIntermediate, + LeftMiddleProximal, + LeftRingDistal, + LeftRingIntermediate, + LeftRingProximal, + LeftShoulder, + LeftThumbDistal, + LeftThumbIntermediate, + LeftThumbProximal, + LeftToes, + LeftUpperArm, + LeftUpperLeg, + Neck, + RightEye, + RightFoot, + RightHand, + RightIndexDistal, + RightIndexIntermediate, + RightIndexProximal, + RightLittleDistal, + RightLittleIntermediate, + RightLittleProximal, + RightLowerArm, + RightLowerLeg, + RightMiddleDistal, + RightMiddleIntermediate, + RightMiddleProximal, + RightRingDistal, + RightRingIntermediate, + RightRingProximal, + RightShoulder, + RightThumbDistal, + RightThumbIntermediate, + RightThumbProximal, + RightToes, + RightUpperArm, + RightUpperLeg, + Spine, + UpperChest, +} + +/// A person who can perform with this avatar. +#[derive(Debug, Clone, Copy, Hash, Serialize, Deserialize)] +pub enum AllowedUserName { + Everyone, + ExplicitlyLicensedPerson, + OnlyAuthor, +} + +/// Usage Permission. +/// +/// NOTE: the typo is intended following the spec. +#[derive(Debug, Clone, Copy, Hash, Serialize, Deserialize)] +pub enum UssageName { + Allow, + Disallow, +} + +/// License type. +#[derive(Debug, Clone, Copy, Hash, Serialize, Deserialize)] +pub enum LicenseName { + #[serde(rename = "CC0")] + Cc0, + + #[serde(rename = "CC_BY")] + CcBy, + + #[serde(rename = "CC_BY_NC")] + CcByNc, + + #[serde(rename = "CC_BY_NC_ND")] + CcByNcNd, + + #[serde(rename = "CC_BY_NC_SA")] + CcByNcSa, + + #[serde(rename = "CC_BY_ND")] + CcByNd, + + #[serde(rename = "CC_BY_SA")] + CcBySa, + + #[serde(rename = "Redistribution_Prohibited")] + RedistributionProhibited, + + Other, +} diff --git a/crates/vrm-spec/src/vrmc_materials_mtoon_1_0.rs b/crates/vrm-spec/src/vrmc_materials_mtoon_1_0.rs new file mode 100644 index 0000000..d216a6a --- /dev/null +++ b/crates/vrm-spec/src/vrmc_materials_mtoon_1_0.rs @@ -0,0 +1,160 @@ +//! Data structures for the [`VRMC_materials_mtoon`](https://github.com/vrm-c/vrm-specification/tree/master/specification/VRMC_materials_mtoon-1.0) 1.0 glTF Extension. + +use serde::{Deserialize, Serialize}; + +/// VRMC_materials_mtoon extension name +pub const VRMC_MATERIALS_MTOON: &str = "VRMC_materials_mtoon"; + +#[cfg(feature = "rustc_hash")] +use rustc_hash::FxHashMap as HashMap; +#[cfg(not(feature = "rustc_hash"))] +use std::collections::HashMap; + +#[cfg(feature = "gltf_index")] +use gltf::json::texture::Info as TextureInfo; + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct VrmcMaterialsMtoonSchema { + #[serde(skip_serializing_if = "Option::is_none")] + pub extensions: Option>>>, + + #[serde(skip_serializing_if = "Option::is_none")] + pub extras: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub gi_equalization_factor: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub matcap_factor: Option<[f64; 3]>, + + #[serde(skip_serializing_if = "Option::is_none")] + pub matcap_texture: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub outline_color_factor: Option<[f64; 3]>, + + #[serde(skip_serializing_if = "Option::is_none")] + pub outline_lighting_mix_factor: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub outline_width_factor: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub outline_width_mode: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub outline_width_multiply_texture: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub parametric_rim_color_factor: Option<[f64; 3]>, + + #[serde(skip_serializing_if = "Option::is_none")] + pub parametric_rim_fresnel_power_factor: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub parametric_rim_lift_factor: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub render_queue_offset_number: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub rim_lighting_mix_factor: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub rim_multiply_texture: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub shade_color_factor: Option<[f64; 3]>, + + #[serde(skip_serializing_if = "Option::is_none")] + pub shade_multiply_texture: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub shading_shift_factor: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub shading_shift_texture: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub shading_toony_factor: Option, + + /// Specification version of VRMC_materials_mtoon + pub spec_version: String, + + /// enable depth buffer when `alphaMode` is `BLEND` + #[serde(skip_serializing_if = "Option::is_none")] + pub transparent_with_z_write: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub uv_animation_mask_texture: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub uv_animation_rotation_speed_factor: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub uv_animation_scroll_x_speed_factor: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub uv_animation_scroll_y_speed_factor: Option, +} + +/// Reference to a texture. +#[cfg(not(feature = "gltf_index"))] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct TextureInfo { + #[serde(skip_serializing_if = "Option::is_none")] + pub extensions: Option>>>, + + #[serde(skip_serializing_if = "Option::is_none")] + pub extras: Option, + + pub index: usize, + + /// The set index of texture's TEXCOORD attribute used for texture coordinate mapping. + #[cfg(feature = "gltf_index")] + #[serde(skip_serializing_if = "Option::is_none")] + pub tex_coord: Option>, + #[cfg(not(feature = "gltf_index"))] + #[serde(skip_serializing_if = "Option::is_none")] + pub tex_coord: Option, +} + +/// Reference to a texture. +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ShadingShiftTextureInfo { + #[serde(skip_serializing_if = "Option::is_none")] + pub extensions: Option>>>, + + #[serde(skip_serializing_if = "Option::is_none")] + pub extras: Option, + + /// The index of the texture. + #[cfg(feature = "gltf_index")] + pub index: gltf::json::Index, + #[cfg(not(feature = "gltf_index"))] + pub index: usize, + + /// The scalar multiplier applied to the texture. + #[serde(skip_serializing_if = "Option::is_none")] + pub scale: Option, + + /// The set index of texture's TEXCOORD attribute used for texture coordinate mapping. + #[cfg(feature = "gltf_index")] + #[serde(skip_serializing_if = "Option::is_none")] + pub tex_coord: Option>, + #[cfg(not(feature = "gltf_index"))] + #[serde(skip_serializing_if = "Option::is_none")] + pub tex_coord: Option, +} + +/// Outline +#[derive(Debug, Clone, Copy, Hash, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub enum OutlineWidthMode { + None, + ScreenCoordinates, + WorldCoordinates, +} diff --git a/crates/vrm-spec/src/vrmc_spring_bone_1_0.rs b/crates/vrm-spec/src/vrmc_spring_bone_1_0.rs new file mode 100644 index 0000000..250a0a2 --- /dev/null +++ b/crates/vrm-spec/src/vrmc_spring_bone_1_0.rs @@ -0,0 +1,179 @@ +//! Data structures for the [`VRMC_springBone`](https://github.com/vrm-c/vrm-specification/tree/master/specification/VRMC_springBone-1.0) 1.0 glTF Extension. + +use serde::{Deserialize, Serialize}; +#[cfg(feature = "rustc_hash")] +use rustc_hash::FxHashMap as HashMap; +#[cfg(not(feature = "rustc_hash"))] +use std::collections::HashMap; + +/// VRMC_springBone extension name +pub const VRMC_SPRING_BONE: &str = "VRMC_springBone"; + +/// SpringBone makes objects such as costumes and hair swaying +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct VrmcSpringBoneSchema { + /// An array of colliderGroups. + #[serde(skip_serializing_if = "Option::is_none")] + pub collider_groups: Option>, + + /// An array of colliders. + #[serde(skip_serializing_if = "Option::is_none")] + pub colliders: Option>, + + #[serde(skip_serializing_if = "Option::is_none")] + pub extensions: Option>>>, + + #[serde(skip_serializing_if = "Option::is_none")] + pub extras: Option, + + /// Specification version of VRMC_springBone + pub spec_version: String, + + /// An array of springs. + #[serde(skip_serializing_if = "Option::is_none")] + pub springs: Option>, +} + +/// collider group definition for SpringBone +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ColliderGroup { + /// An array of colliders. + pub colliders: Vec, + + #[serde(skip_serializing_if = "Option::is_none")] + pub extensions: Option>>>, + + #[serde(skip_serializing_if = "Option::is_none")] + pub extras: Option, + + /// Name of the ColliderGroup + #[serde(skip_serializing_if = "Option::is_none")] + pub name: Option, +} + +/// collider definition for SpringBone +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Collider { + #[serde(skip_serializing_if = "Option::is_none")] + pub extensions: Option>>>, + + #[serde(skip_serializing_if = "Option::is_none")] + pub extras: Option, + + /// The node index. + #[cfg(feature = "gltf_index")] + pub node: gltf::json::Index, + #[cfg(not(feature = "gltf_index"))] + pub node: usize, + + pub shape: ColliderShape, +} + +/// Shape of collider. Have one of sphere and capsule +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ColliderShape { + #[serde(skip_serializing_if = "Option::is_none")] + pub capsule: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub extensions: Option>>>, + + #[serde(skip_serializing_if = "Option::is_none")] + pub extras: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub sphere: Option, +} + +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +pub struct ColliderShapeCapsule { + /// The capsule head. vector3 + #[serde(skip_serializing_if = "Option::is_none")] + pub offset: Option<[f64; 3]>, + + /// The capsule radius + #[serde(skip_serializing_if = "Option::is_none")] + pub radius: Option, + + /// The capsule tail. vector3 + #[serde(skip_serializing_if = "Option::is_none")] + pub tail: Option<[f64; 3]>, +} + +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +pub struct ColliderShapeSphere { + /// The sphere center. vector3 + #[serde(skip_serializing_if = "Option::is_none")] + pub offset: Option<[f64; 3]>, + + /// The sphere radius + #[serde(skip_serializing_if = "Option::is_none")] + pub radius: Option, +} + +/// A bone group of VRMCSpringBone. +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Spring { + /// An index of node which is used as a root of center space. + #[cfg(feature = "gltf_index")] + pub center: Option>, + #[cfg(not(feature = "gltf_index"))] + pub center: Option, + + /// Indices of ColliderGroups that detect collision with this spring. + #[serde(skip_serializing_if = "Option::is_none")] + pub collider_groups: Option>, + + #[serde(skip_serializing_if = "Option::is_none")] + pub extensions: Option>>>, + + #[serde(skip_serializing_if = "Option::is_none")] + pub extras: Option, + + /// Joints of the spring. Except for the first element, a previous joint of the array must be + /// an ancestor of the joint. + pub joints: Vec, + + /// Name of the Spring + #[serde(skip_serializing_if = "Option::is_none")] + pub name: Option, +} + +/// A bone joint of VRMCSpringBone. +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SpringBoneJoint { + /// Air resistance. Deceleration force. + #[serde(skip_serializing_if = "Option::is_none")] + pub drag_force: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub extensions: Option>>>, + + #[serde(skip_serializing_if = "Option::is_none")] + pub extras: Option, + + /// The direction of gravity. A gravity other than downward direction also works. + #[serde(skip_serializing_if = "Option::is_none")] + pub gravity_dir: Option<[f64; 3]>, + + /// Gravitational acceleration. + #[serde(skip_serializing_if = "Option::is_none")] + pub gravity_power: Option, + + /// The radius of spring sphere. + #[serde(skip_serializing_if = "Option::is_none")] + pub hit_radius: Option, + + /// The node index. + #[cfg(feature = "gltf_index")] + pub node: gltf::json::Index, + #[cfg(not(feature = "gltf_index"))] + pub node: usize, + + /// The force to return to the initial pose. + #[serde(skip_serializing_if = "Option::is_none")] + pub stiffness: Option, +} diff --git a/crates/vrm-spec/src/vrmc_vrm_1_0/expression_preset_name.rs b/crates/vrm-spec/src/vrmc_vrm_1_0/expression_preset_name.rs new file mode 100644 index 0000000..7f8d41b --- /dev/null +++ b/crates/vrm-spec/src/vrmc_vrm_1_0/expression_preset_name.rs @@ -0,0 +1,24 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub enum ExpressionPresetName { + Aa, + Angry, + Blink, + BlinkLeft, + BlinkRight, + Ee, + Happy, + Ih, + LookDown, + LookLeft, + LookRight, + LookUp, + Neutral, + Oh, + Ou, + Relaxed, + Sad, + Surprised, +} diff --git a/crates/vrm-spec/src/vrmc_vrm_1_0/human_bone_name.rs b/crates/vrm-spec/src/vrmc_vrm_1_0/human_bone_name.rs new file mode 100644 index 0000000..b9bdfd5 --- /dev/null +++ b/crates/vrm-spec/src/vrmc_vrm_1_0/human_bone_name.rs @@ -0,0 +1,61 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub enum HumanBoneName { + Chest, + Head, + Hips, + Jaw, + LeftEye, + LeftFoot, + LeftHand, + LeftIndexDistal, + LeftIndexIntermediate, + LeftIndexProximal, + LeftLittleDistal, + LeftLittleIntermediate, + LeftLittleProximal, + LeftLowerArm, + LeftLowerLeg, + LeftMiddleDistal, + LeftMiddleIntermediate, + LeftMiddleProximal, + LeftRingDistal, + LeftRingIntermediate, + LeftRingProximal, + LeftShoulder, + LeftThumbDistal, + LeftThumbMetacarpal, + LeftThumbProximal, + LeftToes, + LeftUpperArm, + LeftUpperLeg, + Neck, + RightEye, + RightFoot, + RightHand, + RightIndexDistal, + RightIndexIntermediate, + RightIndexProximal, + RightLittleDistal, + RightLittleIntermediate, + RightLittleProximal, + RightLowerArm, + RightLowerLeg, + RightMiddleDistal, + RightMiddleIntermediate, + RightMiddleProximal, + RightRingDistal, + RightRingIntermediate, + RightRingProximal, + RightShoulder, + RightThumbDistal, + RightThumbMetacarpal, + RightThumbProximal, + RightToes, + RightUpperArm, + RightUpperLeg, + Spine, + UpperChest, +} diff --git a/crates/vrm-spec/src/vrmc_vrm_1_0/mod.rs b/crates/vrm-spec/src/vrmc_vrm_1_0/mod.rs new file mode 100644 index 0000000..81ab4c6 --- /dev/null +++ b/crates/vrm-spec/src/vrmc_vrm_1_0/mod.rs @@ -0,0 +1,462 @@ +//! Data structures for the [`VRMC_vrm`](https://github.com/vrm-c/vrm-specification/tree/master/specification/VRMC_vrm-1.0) 1.0 glTF Extension. + +pub mod expression_preset_name; +pub mod human_bone_name; + +pub use expression_preset_name::ExpressionPresetName; +pub use human_bone_name::HumanBoneName; +use serde::{Deserialize, Serialize}; + +/// VRMC_VRM extension name +pub const VRMC_VRM: &str = "VRMC_vrm"; + +#[cfg(feature = "rustc_hash")] +use rustc_hash::FxHashMap as HashMap; +#[cfg(not(feature = "rustc_hash"))] +use std::collections::HashMap; + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct VRMCVrmSchema { + #[serde(skip_serializing_if = "Option::is_none")] + pub expressions: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub extensions: Option>>>, + + #[serde(skip_serializing_if = "Option::is_none")] + pub extras: Option, + + /// First-person perspective settings + #[serde(skip_serializing_if = "Option::is_none")] + pub first_person: Option, + + pub humanoid: Humanoid, + + /// Eye gaze control + #[serde(skip_serializing_if = "Option::is_none")] + pub look_at: Option, + + /// Meta informations of the VRM model + pub meta: Meta, + + /// Specification version of VRMC_vrm + pub spec_version: String, +} + +/// Definition of expressions +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Expressions { + /// Custom expressions + #[serde(skip_serializing_if = "Option::is_none")] + pub custom: Option>, + + #[serde(skip_serializing_if = "Option::is_none")] + pub extensions: Option>>>, + + #[serde(skip_serializing_if = "Option::is_none")] + pub extras: Option, + + /// Preset expressions + #[serde(skip_serializing_if = "Option::is_none")] + pub preset: Option, +} + +/// Definition of expression by weighted animation +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Expression { + #[serde(skip_serializing_if = "Option::is_none")] + pub extensions: Option>>>, + + #[serde(skip_serializing_if = "Option::is_none")] + pub extras: Option, + + /// A value greater than 0.5 is 1.0, otherwise 0.0 + #[serde(skip_serializing_if = "Option::is_none")] + pub is_binary: Option, + + /// Material color animation references + #[serde(skip_serializing_if = "Option::is_none")] + pub material_color_binds: Option>, + + /// Specify a morph target + #[serde(skip_serializing_if = "Option::is_none")] + pub morph_target_binds: Option>, + + /// Override values of Blink expressions when this Expression is enabled + #[serde(skip_serializing_if = "Option::is_none")] + pub override_blink: Option, + + /// Override values of LookAt expressions when this Expression is enabled + #[serde(skip_serializing_if = "Option::is_none")] + pub override_look_at: Option, + + /// Override values of Mouth expressions when this Expression is enabled + #[serde(skip_serializing_if = "Option::is_none")] + pub override_mouth: Option, + + /// Texture transform animation references + #[serde(skip_serializing_if = "Option::is_none")] + pub texture_transform_binds: Option>, +} + +/// Material color value associated with a expression +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct MaterialColorBind { + #[serde(skip_serializing_if = "Option::is_none")] + pub extensions: Option>>>, + + #[serde(skip_serializing_if = "Option::is_none")] + pub extras: Option, + + /// target material + #[cfg(feature = "gltf_index")] + pub material: gltf::json::Index, + #[cfg(not(feature = "gltf_index"))] + pub material: usize, + + /// target color + pub target_value: Vec, + + pub material_color_bind_type: MaterialColorType, +} + +/// Morph target value associated with a expression +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct MorphTargetBind { + #[serde(skip_serializing_if = "Option::is_none")] + pub extensions: Option>>>, + + #[serde(skip_serializing_if = "Option::is_none")] + pub extras: Option, + + /// The index of the morph target in the mesh. + pub index: usize, + + /// The index of the node that attached to target mesh. + #[cfg(feature = "gltf_index")] + pub node: gltf::json::Index, + #[cfg(not(feature = "gltf_index"))] + pub node: usize, + + /// The weight value of target morph target. + pub weight: f64, +} + +/// Texture transform value associated with a expression +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct TextureTransformBind { + #[serde(skip_serializing_if = "Option::is_none")] + pub extensions: Option>>>, + + #[serde(skip_serializing_if = "Option::is_none")] + pub extras: Option, + + /// target material + #[cfg(feature = "gltf_index")] + pub material: gltf::json::Index, + #[cfg(not(feature = "gltf_index"))] + pub material: usize, + + /// uv offset for TEXCOORD_0 + #[serde(skip_serializing_if = "Option::is_none")] + pub offset: Option>, + + /// uv scaling for TEXCOORD_0 + #[serde(skip_serializing_if = "Option::is_none")] + pub scale: Option>, +} +/// Preset expressions +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Preset(pub HashMap); + +/// First-person perspective settings +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct FirstPerson { + #[serde(skip_serializing_if = "Option::is_none")] + pub extensions: Option>>>, + + #[serde(skip_serializing_if = "Option::is_none")] + pub extras: Option, + + /// Mesh rendering annotation for cameras. + #[serde(skip_serializing_if = "Option::is_none")] + pub mesh_annotations: Option>, +} + +/// Specify how the mesh should be interpreted by the camera +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct MeshAnnotation { + #[serde(skip_serializing_if = "Option::is_none")] + pub extensions: Option>>>, + + #[serde(skip_serializing_if = "Option::is_none")] + pub extras: Option, + + /// The index of the node that attached to target mesh. + #[cfg(feature = "gltf_index")] + #[serde(skip_serializing_if = "Option::is_none")] + pub node: Option>, + #[cfg(not(feature = "gltf_index"))] + #[serde(skip_serializing_if = "Option::is_none")] + pub node: Option, + + /// How the camera interprets the mesh. + #[serde(rename = "type")] + pub mesh_annotation_type: FirstPersonType, +} + +/// Correspondence between nodes and human bones +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Humanoid { + #[serde(skip_serializing_if = "Option::is_none")] + pub extensions: Option>>>, + + #[serde(skip_serializing_if = "Option::is_none")] + pub extras: Option, + + pub human_bones: HumanBones, +} + +/// Represents a set of humanBones of a humanoid. +// FIXME: all bones are optional by now +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct HumanBones(pub HashMap>); + +/// Represents a single bone of a Humanoid. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct HumanBone { + #[serde(skip_serializing_if = "Option::is_none")] + pub extensions: Option>>>, + + #[serde(skip_serializing_if = "Option::is_none")] + pub extras: Option, + + /// Represents a single glTF node tied to this humanBone. + #[cfg(feature = "gltf_index")] + #[serde(skip_serializing_if = "Option::is_none")] + pub node: Option>, + #[cfg(not(feature = "gltf_index"))] + #[serde(skip_serializing_if = "Option::is_none")] + pub node: Option, +} + +/// Eye gaze control +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct LookAt { + #[serde(skip_serializing_if = "Option::is_none")] + pub extensions: Option>>>, + + #[serde(skip_serializing_if = "Option::is_none")] + pub extras: Option, + + /// The origin of LookAt. Position offset from the head bone + #[serde(skip_serializing_if = "Option::is_none")] + pub offset_from_head_bone: Option>, + + /// Horizontal inward movement. The left eye moves right. The right eye moves left. + #[serde(skip_serializing_if = "Option::is_none")] + pub range_map_horizontal_inner: Option, + + /// Horizontal outward movement. The left eye moves left. The right eye moves right. + #[serde(skip_serializing_if = "Option::is_none")] + pub range_map_horizontal_outer: Option, + + /// Vertical downward movement. Both eyes move upwards + #[serde(skip_serializing_if = "Option::is_none")] + pub range_map_vertical_down: Option, + + /// Vertical upward movement. Both eyes move downwards + #[serde(skip_serializing_if = "Option::is_none")] + pub range_map_vertical_up: Option, + + #[serde(rename = "type")] + #[serde(skip_serializing_if = "Option::is_none")] + pub look_at_type: Option, +} + +/// LookAt range definition +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct LookAtRangeMap { + #[serde(skip_serializing_if = "Option::is_none")] + pub extensions: Option>>>, + + #[serde(skip_serializing_if = "Option::is_none")] + pub extras: Option, + + /// Yaw and pitch angles ( degrees ) between the head bone forward vector and the eye gaze + /// LookAt vector + #[serde(skip_serializing_if = "Option::is_none")] + pub input_max_value: Option, + + /// Degree for type.bone, Weight for type.expressions + #[serde(skip_serializing_if = "Option::is_none")] + pub output_scale: Option, +} + +/// Meta information of the VRM model +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Meta { + /// A flag that permits to use this model in contents contain anti-social activities or hate + /// speeches + #[serde(skip_serializing_if = "Option::is_none")] + pub allow_antisocial_or_hate_usage: Option, + + /// A flag that permits to use this model in excessively sexual contents + #[serde(skip_serializing_if = "Option::is_none")] + pub allow_excessively_sexual_usage: Option, + + /// A flag that permits to use this model in excessively violent contents + #[serde(skip_serializing_if = "Option::is_none")] + pub allow_excessively_violent_usage: Option, + + /// A flag that permits to use this model in political or religious contents + #[serde(skip_serializing_if = "Option::is_none")] + pub allow_political_or_religious_usage: Option, + + /// A flag that permits to redistribute this model + #[serde(skip_serializing_if = "Option::is_none")] + pub allow_redistribution: Option, + + /// Authors of the model + pub authors: Vec, + + /// A person who can perform as an avatar with this model + #[serde(skip_serializing_if = "Option::is_none")] + pub avatar_permission: Option, + + /// An option that permits to use this model in commercial products + #[serde(skip_serializing_if = "Option::is_none")] + pub commercial_usage: Option, + + /// An information that describes the contact information of the author + #[serde(skip_serializing_if = "Option::is_none")] + pub contact_information: Option, + + /// An information that describes the copyright of the model + #[serde(skip_serializing_if = "Option::is_none")] + pub copyright_information: Option, + + /// An option that forces or abandons to display the credit of this model + #[serde(skip_serializing_if = "Option::is_none")] + pub credit_notation: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub extensions: Option>>>, + + #[serde(skip_serializing_if = "Option::is_none")] + pub extras: Option, + + /// A URL towards the license document this model refers to + pub license_url: String, + + /// An option that controls the condition to modify this model + #[serde(skip_serializing_if = "Option::is_none")] + pub modification: Option, + + /// The name of the model + pub name: String, + + /// Describe the URL links of other license + #[serde(skip_serializing_if = "Option::is_none")] + pub other_license_url: Option, + + /// References / original works of the model + #[serde(skip_serializing_if = "Option::is_none")] + pub references: Option>, + + /// Third party licenses of the model, if required. You can use line breaks + #[serde(skip_serializing_if = "Option::is_none")] + pub third_party_licenses: Option, + + /// The index to the thumbnail image of the model in gltf.images + #[cfg(feature = "gltf_index")] + #[serde(skip_serializing_if = "Option::is_none")] + pub thumbnail_image: Option>, + #[cfg(not(feature = "gltf_index"))] + #[serde(skip_serializing_if = "Option::is_none")] + pub thumbnail_image: Option, + + /// The version of the model + #[serde(skip_serializing_if = "Option::is_none")] + pub version: Option, +} + +#[derive(Debug, Clone, Copy, Hash, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub enum MaterialColorType { + Color, + EmissionColor, + MatcapColor, + OutlineColor, + RimColor, + ShadeColor, +} + +/// Expression override types +#[derive(Debug, Clone, Copy, Hash, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub enum ExpressionOverrideType { + Blend, + Block, + None, +} + +/// How the camera interprets the mesh. +#[derive(Debug, Clone, Copy, Hash, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub enum FirstPersonType { + Auto, + Both, + FirstPersonOnly, + ThirdPersonOnly, +} + +#[derive(Debug, Clone, Copy, Hash, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub enum LookAtType { + Bone, + Expression, +} + +/// A person who can perform as an avatar with this model +#[derive(Debug, Clone, Copy, Hash, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub enum AvatarPermissionType { + Everyone, + OnlyAuthor, + OnlySeparatelyLicensedPerson, +} + +/// An option that permits to use this model in commercial products +#[derive(Debug, Clone, Copy, Hash, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub enum CommercialUsageType { + Corporation, + PersonalNonProfit, + PersonalProfit, +} + +/// An option that forces or abandons to display the credit of this model +#[derive(Debug, Clone, Copy, Hash, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub enum CreditNotationType { + Required, + Unnecessary, +} + +/// An option that controls the condition to modify this model +#[derive(Debug, Clone, Copy, Hash, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub enum ModificationType { + AllowModification, + AllowModificationRedistribution, + Prohibited, +} diff --git a/crates/vrm-spec/tests/snapshots/test__vrm0.snap b/crates/vrm-spec/tests/snapshots/test__vrm0.snap new file mode 100644 index 0000000..6e5fcad --- /dev/null +++ b/crates/vrm-spec/tests/snapshots/test__vrm0.snap @@ -0,0 +1,3523 @@ +--- +source: crates/vrm-spec/tests/test.rs +expression: vrm +--- +VRM0Schema { + blend_shape_master: Some( + VRMBlendShape { + blend_shape_groups: Some( + [ + VRMBlendShapeGroup { + binds: Some( + [ + VRMBlendShapeBind { + index: Some( + 0, + ), + mesh: Some( + 0, + ), + weight: Some( + 100.0, + ), + }, + ], + ), + is_binary: Some( + false, + ), + material_values: Some( + [], + ), + name: Some( + "Neutral", + ), + preset_name: Some( + Neutral, + ), + }, + VRMBlendShapeGroup { + binds: Some( + [ + VRMBlendShapeBind { + index: Some( + 39, + ), + mesh: Some( + 0, + ), + weight: Some( + 100.0, + ), + }, + ], + ), + is_binary: Some( + false, + ), + material_values: Some( + [], + ), + name: Some( + "A", + ), + preset_name: Some( + A, + ), + }, + VRMBlendShapeGroup { + binds: Some( + [ + VRMBlendShapeBind { + index: Some( + 40, + ), + mesh: Some( + 0, + ), + weight: Some( + 100.0, + ), + }, + ], + ), + is_binary: Some( + false, + ), + material_values: Some( + [], + ), + name: Some( + "I", + ), + preset_name: Some( + I, + ), + }, + VRMBlendShapeGroup { + binds: Some( + [ + VRMBlendShapeBind { + index: Some( + 41, + ), + mesh: Some( + 0, + ), + weight: Some( + 100.0, + ), + }, + ], + ), + is_binary: Some( + false, + ), + material_values: Some( + [], + ), + name: Some( + "U", + ), + preset_name: Some( + U, + ), + }, + VRMBlendShapeGroup { + binds: Some( + [ + VRMBlendShapeBind { + index: Some( + 42, + ), + mesh: Some( + 0, + ), + weight: Some( + 100.0, + ), + }, + ], + ), + is_binary: Some( + false, + ), + material_values: Some( + [], + ), + name: Some( + "E", + ), + preset_name: Some( + E, + ), + }, + VRMBlendShapeGroup { + binds: Some( + [ + VRMBlendShapeBind { + index: Some( + 43, + ), + mesh: Some( + 0, + ), + weight: Some( + 100.0, + ), + }, + ], + ), + is_binary: Some( + false, + ), + material_values: Some( + [], + ), + name: Some( + "O", + ), + preset_name: Some( + O, + ), + }, + VRMBlendShapeGroup { + binds: Some( + [ + VRMBlendShapeBind { + index: Some( + 13, + ), + mesh: Some( + 0, + ), + weight: Some( + 100.0, + ), + }, + ], + ), + is_binary: Some( + false, + ), + material_values: Some( + [], + ), + name: Some( + "Blink", + ), + preset_name: Some( + Blink, + ), + }, + VRMBlendShapeGroup { + binds: Some( + [ + VRMBlendShapeBind { + index: Some( + 15, + ), + mesh: Some( + 0, + ), + weight: Some( + 100.0, + ), + }, + ], + ), + is_binary: Some( + false, + ), + material_values: Some( + [], + ), + name: Some( + "Blink_L", + ), + preset_name: Some( + BlinkL, + ), + }, + VRMBlendShapeGroup { + binds: Some( + [ + VRMBlendShapeBind { + index: Some( + 14, + ), + mesh: Some( + 0, + ), + weight: Some( + 100.0, + ), + }, + ], + ), + is_binary: Some( + false, + ), + material_values: Some( + [], + ), + name: Some( + "Blink_R", + ), + preset_name: Some( + BlinkR, + ), + }, + VRMBlendShapeGroup { + binds: Some( + [ + VRMBlendShapeBind { + index: Some( + 1, + ), + mesh: Some( + 0, + ), + weight: Some( + 100.0, + ), + }, + ], + ), + is_binary: Some( + false, + ), + material_values: Some( + [], + ), + name: Some( + "Angry", + ), + preset_name: Some( + Angry, + ), + }, + VRMBlendShapeGroup { + binds: Some( + [ + VRMBlendShapeBind { + index: Some( + 2, + ), + mesh: Some( + 0, + ), + weight: Some( + 100.0, + ), + }, + ], + ), + is_binary: Some( + false, + ), + material_values: Some( + [], + ), + name: Some( + "Fun", + ), + preset_name: Some( + Fun, + ), + }, + VRMBlendShapeGroup { + binds: Some( + [ + VRMBlendShapeBind { + index: Some( + 3, + ), + mesh: Some( + 0, + ), + weight: Some( + 100.0, + ), + }, + ], + ), + is_binary: Some( + false, + ), + material_values: Some( + [], + ), + name: Some( + "Joy", + ), + preset_name: Some( + Joy, + ), + }, + VRMBlendShapeGroup { + binds: Some( + [ + VRMBlendShapeBind { + index: Some( + 4, + ), + mesh: Some( + 0, + ), + weight: Some( + 100.0, + ), + }, + ], + ), + is_binary: Some( + false, + ), + material_values: Some( + [], + ), + name: Some( + "Sorrow", + ), + preset_name: Some( + Sorrow, + ), + }, + VRMBlendShapeGroup { + binds: Some( + [ + VRMBlendShapeBind { + index: Some( + 5, + ), + mesh: Some( + 0, + ), + weight: Some( + 100.0, + ), + }, + ], + ), + is_binary: Some( + false, + ), + material_values: Some( + [], + ), + name: Some( + "Surprised", + ), + preset_name: Some( + Unknown, + ), + }, + ], + ), + }, + ), + exporter_version: Some( + "VRoid Studio-1.26.0", + ), + first_person: Some( + VRMFirstPerson { + first_person_bone: Some( + 18, + ), + first_person_bone_offset: Some( + OptionalVector3 { + x: Some( + 0.0, + ), + y: Some( + 0.06, + ), + z: Some( + 0.0, + ), + }, + ), + look_at_horizontal_inner: Some( + VRMFirstPersonDegreeMap { + curve: Some( + [ + 0.0, + 0.0, + 0.0, + 1.0, + 1.0, + 1.0, + 1.0, + 0.0, + ], + ), + x_range: Some( + 90.0, + ), + y_range: Some( + 8.0, + ), + }, + ), + look_at_horizontal_outer: Some( + VRMFirstPersonDegreeMap { + curve: Some( + [ + 0.0, + 0.0, + 0.0, + 1.0, + 1.0, + 1.0, + 1.0, + 0.0, + ], + ), + x_range: Some( + 90.0, + ), + y_range: Some( + 12.0, + ), + }, + ), + look_at_type_name: Some( + Bone, + ), + look_at_vertical_down: Some( + VRMFirstPersonDegreeMap { + curve: Some( + [ + 0.0, + 0.0, + 0.0, + 1.0, + 1.0, + 1.0, + 1.0, + 0.0, + ], + ), + x_range: Some( + 90.0, + ), + y_range: Some( + 10.0, + ), + }, + ), + look_at_vertical_up: Some( + VRMFirstPersonDegreeMap { + curve: Some( + [ + 0.0, + 0.0, + 0.0, + 1.0, + 1.0, + 1.0, + 1.0, + 0.0, + ], + ), + x_range: Some( + 90.0, + ), + y_range: Some( + 10.0, + ), + }, + ), + mesh_annotations: Some( + [], + ), + }, + ), + humanoid: Some( + VRMHumanoid { + arm_stretch: Some( + 0.05, + ), + feet_spacing: Some( + 0.0, + ), + has_translation_do_f: Some( + false, + ), + human_bones: Some( + [ + VRMHumanoidBone { + axis_length: None, + bone: Some( + Hips, + ), + center: None, + max: None, + min: None, + node: Some( + 1, + ), + use_default_values: Some( + true, + ), + }, + VRMHumanoidBone { + axis_length: None, + bone: Some( + LeftUpperLeg, + ), + center: None, + max: None, + min: None, + node: Some( + 83, + ), + use_default_values: Some( + true, + ), + }, + VRMHumanoidBone { + axis_length: None, + bone: Some( + RightUpperLeg, + ), + center: None, + max: None, + min: None, + node: Some( + 87, + ), + use_default_values: Some( + true, + ), + }, + VRMHumanoidBone { + axis_length: None, + bone: Some( + LeftLowerLeg, + ), + center: None, + max: None, + min: None, + node: Some( + 84, + ), + use_default_values: Some( + true, + ), + }, + VRMHumanoidBone { + axis_length: None, + bone: Some( + RightLowerLeg, + ), + center: None, + max: None, + min: None, + node: Some( + 88, + ), + use_default_values: Some( + true, + ), + }, + VRMHumanoidBone { + axis_length: None, + bone: Some( + LeftFoot, + ), + center: None, + max: None, + min: None, + node: Some( + 85, + ), + use_default_values: Some( + true, + ), + }, + VRMHumanoidBone { + axis_length: None, + bone: Some( + RightFoot, + ), + center: None, + max: None, + min: None, + node: Some( + 89, + ), + use_default_values: Some( + true, + ), + }, + VRMHumanoidBone { + axis_length: None, + bone: Some( + Spine, + ), + center: None, + max: None, + min: None, + node: Some( + 2, + ), + use_default_values: Some( + true, + ), + }, + VRMHumanoidBone { + axis_length: None, + bone: Some( + Chest, + ), + center: None, + max: None, + min: None, + node: Some( + 3, + ), + use_default_values: Some( + true, + ), + }, + VRMHumanoidBone { + axis_length: None, + bone: Some( + Neck, + ), + center: None, + max: None, + min: None, + node: Some( + 17, + ), + use_default_values: Some( + true, + ), + }, + VRMHumanoidBone { + axis_length: None, + bone: Some( + Head, + ), + center: None, + max: None, + min: None, + node: Some( + 18, + ), + use_default_values: Some( + true, + ), + }, + VRMHumanoidBone { + axis_length: None, + bone: Some( + LeftShoulder, + ), + center: None, + max: None, + min: None, + node: Some( + 45, + ), + use_default_values: Some( + true, + ), + }, + VRMHumanoidBone { + axis_length: None, + bone: Some( + RightShoulder, + ), + center: None, + max: None, + min: None, + node: Some( + 64, + ), + use_default_values: Some( + true, + ), + }, + VRMHumanoidBone { + axis_length: None, + bone: Some( + LeftUpperArm, + ), + center: None, + max: None, + min: None, + node: Some( + 46, + ), + use_default_values: Some( + true, + ), + }, + VRMHumanoidBone { + axis_length: None, + bone: Some( + RightUpperArm, + ), + center: None, + max: None, + min: None, + node: Some( + 65, + ), + use_default_values: Some( + true, + ), + }, + VRMHumanoidBone { + axis_length: None, + bone: Some( + LeftLowerArm, + ), + center: None, + max: None, + min: None, + node: Some( + 47, + ), + use_default_values: Some( + true, + ), + }, + VRMHumanoidBone { + axis_length: None, + bone: Some( + RightLowerArm, + ), + center: None, + max: None, + min: None, + node: Some( + 66, + ), + use_default_values: Some( + true, + ), + }, + VRMHumanoidBone { + axis_length: None, + bone: Some( + LeftHand, + ), + center: None, + max: None, + min: None, + node: Some( + 48, + ), + use_default_values: Some( + true, + ), + }, + VRMHumanoidBone { + axis_length: None, + bone: Some( + RightHand, + ), + center: None, + max: None, + min: None, + node: Some( + 67, + ), + use_default_values: Some( + true, + ), + }, + VRMHumanoidBone { + axis_length: None, + bone: Some( + LeftToes, + ), + center: None, + max: None, + min: None, + node: Some( + 86, + ), + use_default_values: Some( + true, + ), + }, + VRMHumanoidBone { + axis_length: None, + bone: Some( + RightToes, + ), + center: None, + max: None, + min: None, + node: Some( + 90, + ), + use_default_values: Some( + true, + ), + }, + VRMHumanoidBone { + axis_length: None, + bone: Some( + LeftEye, + ), + center: None, + max: None, + min: None, + node: Some( + 19, + ), + use_default_values: Some( + true, + ), + }, + VRMHumanoidBone { + axis_length: None, + bone: Some( + RightEye, + ), + center: None, + max: None, + min: None, + node: Some( + 20, + ), + use_default_values: Some( + true, + ), + }, + VRMHumanoidBone { + axis_length: None, + bone: Some( + LeftThumbProximal, + ), + center: None, + max: None, + min: None, + node: Some( + 61, + ), + use_default_values: Some( + true, + ), + }, + VRMHumanoidBone { + axis_length: None, + bone: Some( + LeftThumbIntermediate, + ), + center: None, + max: None, + min: None, + node: Some( + 62, + ), + use_default_values: Some( + true, + ), + }, + VRMHumanoidBone { + axis_length: None, + bone: Some( + LeftThumbDistal, + ), + center: None, + max: None, + min: None, + node: Some( + 63, + ), + use_default_values: Some( + true, + ), + }, + VRMHumanoidBone { + axis_length: None, + bone: Some( + LeftIndexProximal, + ), + center: None, + max: None, + min: None, + node: Some( + 49, + ), + use_default_values: Some( + true, + ), + }, + VRMHumanoidBone { + axis_length: None, + bone: Some( + LeftIndexIntermediate, + ), + center: None, + max: None, + min: None, + node: Some( + 50, + ), + use_default_values: Some( + true, + ), + }, + VRMHumanoidBone { + axis_length: None, + bone: Some( + LeftIndexDistal, + ), + center: None, + max: None, + min: None, + node: Some( + 51, + ), + use_default_values: Some( + true, + ), + }, + VRMHumanoidBone { + axis_length: None, + bone: Some( + LeftMiddleProximal, + ), + center: None, + max: None, + min: None, + node: Some( + 55, + ), + use_default_values: Some( + true, + ), + }, + VRMHumanoidBone { + axis_length: None, + bone: Some( + LeftMiddleIntermediate, + ), + center: None, + max: None, + min: None, + node: Some( + 56, + ), + use_default_values: Some( + true, + ), + }, + VRMHumanoidBone { + axis_length: None, + bone: Some( + LeftMiddleDistal, + ), + center: None, + max: None, + min: None, + node: Some( + 57, + ), + use_default_values: Some( + true, + ), + }, + VRMHumanoidBone { + axis_length: None, + bone: Some( + LeftRingProximal, + ), + center: None, + max: None, + min: None, + node: Some( + 58, + ), + use_default_values: Some( + true, + ), + }, + VRMHumanoidBone { + axis_length: None, + bone: Some( + LeftRingIntermediate, + ), + center: None, + max: None, + min: None, + node: Some( + 59, + ), + use_default_values: Some( + true, + ), + }, + VRMHumanoidBone { + axis_length: None, + bone: Some( + LeftRingDistal, + ), + center: None, + max: None, + min: None, + node: Some( + 60, + ), + use_default_values: Some( + true, + ), + }, + VRMHumanoidBone { + axis_length: None, + bone: Some( + LeftLittleProximal, + ), + center: None, + max: None, + min: None, + node: Some( + 52, + ), + use_default_values: Some( + true, + ), + }, + VRMHumanoidBone { + axis_length: None, + bone: Some( + LeftLittleIntermediate, + ), + center: None, + max: None, + min: None, + node: Some( + 53, + ), + use_default_values: Some( + true, + ), + }, + VRMHumanoidBone { + axis_length: None, + bone: Some( + LeftLittleDistal, + ), + center: None, + max: None, + min: None, + node: Some( + 54, + ), + use_default_values: Some( + true, + ), + }, + VRMHumanoidBone { + axis_length: None, + bone: Some( + RightThumbProximal, + ), + center: None, + max: None, + min: None, + node: Some( + 80, + ), + use_default_values: Some( + true, + ), + }, + VRMHumanoidBone { + axis_length: None, + bone: Some( + RightThumbIntermediate, + ), + center: None, + max: None, + min: None, + node: Some( + 81, + ), + use_default_values: Some( + true, + ), + }, + VRMHumanoidBone { + axis_length: None, + bone: Some( + RightThumbDistal, + ), + center: None, + max: None, + min: None, + node: Some( + 82, + ), + use_default_values: Some( + true, + ), + }, + VRMHumanoidBone { + axis_length: None, + bone: Some( + RightIndexProximal, + ), + center: None, + max: None, + min: None, + node: Some( + 68, + ), + use_default_values: Some( + true, + ), + }, + VRMHumanoidBone { + axis_length: None, + bone: Some( + RightIndexIntermediate, + ), + center: None, + max: None, + min: None, + node: Some( + 69, + ), + use_default_values: Some( + true, + ), + }, + VRMHumanoidBone { + axis_length: None, + bone: Some( + RightIndexDistal, + ), + center: None, + max: None, + min: None, + node: Some( + 70, + ), + use_default_values: Some( + true, + ), + }, + VRMHumanoidBone { + axis_length: None, + bone: Some( + RightMiddleProximal, + ), + center: None, + max: None, + min: None, + node: Some( + 74, + ), + use_default_values: Some( + true, + ), + }, + VRMHumanoidBone { + axis_length: None, + bone: Some( + RightMiddleIntermediate, + ), + center: None, + max: None, + min: None, + node: Some( + 75, + ), + use_default_values: Some( + true, + ), + }, + VRMHumanoidBone { + axis_length: None, + bone: Some( + RightMiddleDistal, + ), + center: None, + max: None, + min: None, + node: Some( + 76, + ), + use_default_values: Some( + true, + ), + }, + VRMHumanoidBone { + axis_length: None, + bone: Some( + RightRingProximal, + ), + center: None, + max: None, + min: None, + node: Some( + 77, + ), + use_default_values: Some( + true, + ), + }, + VRMHumanoidBone { + axis_length: None, + bone: Some( + RightRingIntermediate, + ), + center: None, + max: None, + min: None, + node: Some( + 78, + ), + use_default_values: Some( + true, + ), + }, + VRMHumanoidBone { + axis_length: None, + bone: Some( + RightRingDistal, + ), + center: None, + max: None, + min: None, + node: Some( + 79, + ), + use_default_values: Some( + true, + ), + }, + VRMHumanoidBone { + axis_length: None, + bone: Some( + RightLittleProximal, + ), + center: None, + max: None, + min: None, + node: Some( + 71, + ), + use_default_values: Some( + true, + ), + }, + VRMHumanoidBone { + axis_length: None, + bone: Some( + RightLittleIntermediate, + ), + center: None, + max: None, + min: None, + node: Some( + 72, + ), + use_default_values: Some( + true, + ), + }, + VRMHumanoidBone { + axis_length: None, + bone: Some( + RightLittleDistal, + ), + center: None, + max: None, + min: None, + node: Some( + 73, + ), + use_default_values: Some( + true, + ), + }, + VRMHumanoidBone { + axis_length: None, + bone: Some( + UpperChest, + ), + center: None, + max: None, + min: None, + node: Some( + 4, + ), + use_default_values: Some( + true, + ), + }, + ], + ), + leg_stretch: Some( + 0.05, + ), + lower_arm_twist: Some( + 0.5, + ), + lower_leg_twist: Some( + 0.5, + ), + upper_arm_twist: Some( + 0.5, + ), + upper_leg_twist: Some( + 0.5, + ), + }, + ), + material_properties: Some( + [ + VRMMaterial { + float_properties: Some( + { + "_OutlineLightingMix": 1.0, + "_OutlineScaledMaxDistance": 1.0, + "_ShadingGradeRate": 1.0, + "_BumpScale": 1.0, + "_OutlineWidth": 0.075, + "_SrcBlend": 1.0, + "_CullMode": 2.0, + "_IndirectLightIntensity": 0.1, + "_ShadeShift": -0.9, + "_UvAnimRotation": 0.0, + "_BlendMode": 1.0, + "_RimFresnelPower": 7.999984, + "_ReceiveShadowRate": 1.0, + "_UvAnimScrollX": 0.0, + "_OutlineWidthMode": 1.0, + "_UvAnimScrollY": 0.0, + "_MToonVersion": 34.0, + "_DstBlend": 0.0, + "_RimLift": 0.1, + "_ZWrite": 1.0, + "_ShadeToony": 0.0, + "_Cutoff": 0.5, + "_DebugMode": 0.0, + "_OutlineCullMode": 1.0, + "_OutlineColorMode": 0.0, + "_RimLightingMix": 0.0, + "_LightColorAttenuation": 0.0, + }, + ), + keyword_map: Some( + { + "MTOON_OUTLINE_WIDTH_WORLD": true, + "_ALPHATEST_ON": true, + "MTOON_OUTLINE_COLOR_FIXED": true, + "_NORMALMAP": true, + }, + ), + name: Some( + "N00_000_00_FaceMouth_00_FACE (Instance)", + ), + render_queue: Some( + 2450, + ), + shader: Some( + "VRM/MToon", + ), + tag_map: Some( + { + "RenderType": "TransparentCutout", + }, + ), + texture_properties: Some( + { + "_BumpMap": 2, + "_OutlineWidthTexture": 9, + "_EmissionMap": 1, + "_MainTex": 0, + "_ShadeTexture": 7, + "_SphereAdd": 8, + }, + ), + vector_properties: Some( + { + "_ShadeTexture": [ + 0.0, + 0.0, + 1.0, + 1.0, + ], + "_RimTexture": [ + 0.0, + 0.0, + 1.0, + 1.0, + ], + "_EmissionColor": [ + 1.0, + 1.0, + 1.0, + 1.0, + ], + "_OutlineColor": [ + 0.2745098, + 0.0901960656, + 0.125490159, + 1.0, + ], + "_SphereAdd": [ + 0.0, + 0.0, + 1.0, + 1.0, + ], + "_MainTex": [ + 0.0, + 0.0, + 1.0, + 1.0, + ], + "_Color": [ + 1.0, + 1.0, + 1.0, + 1.0, + ], + "_BumpMap": [ + 0.0, + 0.0, + 1.0, + 1.0, + ], + "_OutlineWidthTexture": [ + 0.0, + 0.0, + 1.0, + 1.0, + ], + "_EmissionMap": [ + 0.0, + 0.0, + 1.0, + 1.0, + ], + "_ReceiveShadowTexture": [ + 0.0, + 0.0, + 1.0, + 1.0, + ], + "_ShadeColor": [ + 1.0, + 1.0, + 1.0, + 1.0, + ], + "_UvAnimMaskTexture": [ + 0.0, + 0.0, + 1.0, + 1.0, + ], + "_ShadingGradeTexture": [ + 0.0, + 0.0, + 1.0, + 1.0, + ], + "_RimColor": [ + 0.0, + 0.0, + 0.0, + 1.0, + ], + }, + ), + }, + VRMMaterial { + float_properties: Some( + { + "_OutlineLightingMix": 1.0, + "_OutlineScaledMaxDistance": 1.0, + "_ShadingGradeRate": 1.0, + "_BumpScale": 1.0, + "_OutlineWidth": 0.0, + "_SrcBlend": 5.0, + "_CullMode": 2.0, + "_IndirectLightIntensity": 0.1, + "_ShadeShift": -0.9, + "_UvAnimRotation": 0.0, + "_BlendMode": 2.0, + "_RimFresnelPower": 7.999984, + "_ReceiveShadowRate": 1.0, + "_UvAnimScrollX": 0.0, + "_OutlineWidthMode": 0.0, + "_UvAnimScrollY": 0.0, + "_MToonVersion": 34.0, + "_DstBlend": 10.0, + "_RimLift": 0.1, + "_ZWrite": 0.0, + "_ShadeToony": 0.0, + "_Cutoff": 0.5, + "_DebugMode": 0.0, + "_OutlineCullMode": 1.0, + "_OutlineColorMode": 0.0, + "_RimLightingMix": 0.0, + "_LightColorAttenuation": 0.0, + }, + ), + keyword_map: Some( + { + "_NORMALMAP": true, + "_ALPHABLEND_ON": true, + }, + ), + name: Some( + "N00_000_00_EyeIris_00_EYE (Instance)", + ), + render_queue: Some( + 3000, + ), + shader: Some( + "VRM/MToon", + ), + tag_map: Some( + { + "RenderType": "Transparent", + }, + ), + texture_properties: Some( + { + "_BumpMap": 2, + "_OutlineWidthTexture": 9, + "_EmissionMap": 1, + "_MainTex": 0, + "_ShadeTexture": 7, + "_SphereAdd": 8, + }, + ), + vector_properties: Some( + { + "_ShadeTexture": [ + 0.0, + 0.0, + 1.0, + 1.0, + ], + "_RimTexture": [ + 0.0, + 0.0, + 1.0, + 1.0, + ], + "_EmissionColor": [ + 1.0, + 1.0, + 1.0, + 1.0, + ], + "_OutlineColor": [ + 0.0, + 0.0, + 0.0, + 0.0, + ], + "_SphereAdd": [ + 0.0, + 0.0, + 1.0, + 1.0, + ], + "_MainTex": [ + 0.0, + 0.0, + 1.0, + 1.0, + ], + "_Color": [ + 1.0, + 1.0, + 1.0, + 1.0, + ], + "_BumpMap": [ + 0.0, + 0.0, + 1.0, + 1.0, + ], + "_OutlineWidthTexture": [ + 0.0, + 0.0, + 1.0, + 1.0, + ], + "_EmissionMap": [ + 0.0, + 0.0, + 1.0, + 1.0, + ], + "_ReceiveShadowTexture": [ + 0.0, + 0.0, + 1.0, + 1.0, + ], + "_ShadeColor": [ + 1.0, + 1.0, + 1.0, + 1.0, + ], + "_UvAnimMaskTexture": [ + 0.0, + 0.0, + 1.0, + 1.0, + ], + "_ShadingGradeTexture": [ + 0.0, + 0.0, + 1.0, + 1.0, + ], + "_RimColor": [ + 0.0, + 0.0, + 0.0, + 1.0, + ], + }, + ), + }, + VRMMaterial { + float_properties: Some( + { + "_OutlineLightingMix": 1.0, + "_OutlineScaledMaxDistance": 1.0, + "_ShadingGradeRate": 1.0, + "_BumpScale": 1.0, + "_OutlineWidth": 0.0, + "_SrcBlend": 5.0, + "_CullMode": 0.0, + "_IndirectLightIntensity": 0.1, + "_ShadeShift": -0.9, + "_UvAnimRotation": 0.0, + "_BlendMode": 2.0, + "_RimFresnelPower": 7.999984, + "_ReceiveShadowRate": 1.0, + "_UvAnimScrollX": 0.0, + "_OutlineWidthMode": 0.0, + "_UvAnimScrollY": 0.0, + "_MToonVersion": 34.0, + "_DstBlend": 10.0, + "_RimLift": 0.1, + "_ZWrite": 0.0, + "_ShadeToony": 0.0, + "_Cutoff": 0.5, + "_DebugMode": 0.0, + "_OutlineCullMode": 1.0, + "_OutlineColorMode": 0.0, + "_RimLightingMix": 0.0, + "_LightColorAttenuation": 0.0, + }, + ), + keyword_map: Some( + { + "_NORMALMAP": true, + "_ALPHABLEND_ON": true, + }, + ), + name: Some( + "N00_000_00_EyeHighlight_00_EYE (Instance)", + ), + render_queue: Some( + 3500, + ), + shader: Some( + "VRM/MToon", + ), + tag_map: Some( + { + "RenderType": "Transparent", + }, + ), + texture_properties: Some( + { + "_BumpMap": 2, + "_OutlineWidthTexture": 9, + "_EmissionMap": 1, + "_MainTex": 0, + "_ShadeTexture": 7, + "_SphereAdd": 8, + }, + ), + vector_properties: Some( + { + "_ShadeTexture": [ + 0.0, + 0.0, + 1.0, + 1.0, + ], + "_RimTexture": [ + 0.0, + 0.0, + 1.0, + 1.0, + ], + "_EmissionColor": [ + 1.0, + 1.0, + 1.0, + 1.0, + ], + "_OutlineColor": [ + 0.0, + 0.0, + 0.0, + 0.0, + ], + "_SphereAdd": [ + 0.0, + 0.0, + 1.0, + 1.0, + ], + "_MainTex": [ + 0.0, + 0.0, + 1.0, + 1.0, + ], + "_Color": [ + 1.0, + 1.0, + 1.0, + 1.0, + ], + "_BumpMap": [ + 0.0, + 0.0, + 1.0, + 1.0, + ], + "_OutlineWidthTexture": [ + 0.0, + 0.0, + 1.0, + 1.0, + ], + "_EmissionMap": [ + 0.0, + 0.0, + 1.0, + 1.0, + ], + "_ReceiveShadowTexture": [ + 0.0, + 0.0, + 1.0, + 1.0, + ], + "_ShadeColor": [ + 1.0, + 1.0, + 1.0, + 1.0, + ], + "_UvAnimMaskTexture": [ + 0.0, + 0.0, + 1.0, + 1.0, + ], + "_ShadingGradeTexture": [ + 0.0, + 0.0, + 1.0, + 1.0, + ], + "_RimColor": [ + 0.0, + 0.0, + 0.0, + 1.0, + ], + }, + ), + }, + VRMMaterial { + float_properties: Some( + { + "_OutlineLightingMix": 1.0, + "_OutlineScaledMaxDistance": 1.0, + "_ShadingGradeRate": 1.0, + "_BumpScale": 1.0, + "_OutlineWidth": 0.075, + "_SrcBlend": 1.0, + "_CullMode": 0.0, + "_IndirectLightIntensity": 0.1, + "_ShadeShift": -0.9, + "_UvAnimRotation": 0.0, + "_BlendMode": 1.0, + "_RimFresnelPower": 7.999984, + "_ReceiveShadowRate": 1.0, + "_UvAnimScrollX": 0.0, + "_OutlineWidthMode": 1.0, + "_UvAnimScrollY": 0.0, + "_MToonVersion": 34.0, + "_DstBlend": 0.0, + "_RimLift": 0.1, + "_ZWrite": 1.0, + "_ShadeToony": 0.0, + "_Cutoff": 0.5, + "_DebugMode": 0.0, + "_OutlineCullMode": 1.0, + "_OutlineColorMode": 0.0, + "_RimLightingMix": 0.0, + "_LightColorAttenuation": 0.0, + }, + ), + keyword_map: Some( + { + "MTOON_OUTLINE_WIDTH_WORLD": true, + "_ALPHATEST_ON": true, + "MTOON_OUTLINE_COLOR_FIXED": true, + "_NORMALMAP": true, + }, + ), + name: Some( + "N00_000_00_Face_00_SKIN (Instance)", + ), + render_queue: Some( + 2450, + ), + shader: Some( + "VRM/MToon", + ), + tag_map: Some( + { + "RenderType": "TransparentCutout", + }, + ), + texture_properties: Some( + { + "_BumpMap": 2, + "_OutlineWidthTexture": 9, + "_EmissionMap": 1, + "_MainTex": 0, + "_ShadeTexture": 7, + "_SphereAdd": 10, + }, + ), + vector_properties: Some( + { + "_ShadeTexture": [ + 0.0, + 0.0, + 1.0, + 1.0, + ], + "_RimTexture": [ + 0.0, + 0.0, + 1.0, + 1.0, + ], + "_EmissionColor": [ + 1.0, + 1.0, + 1.0, + 1.0, + ], + "_OutlineColor": [ + 0.2745098, + 0.0901960656, + 0.125490159, + 1.0, + ], + "_SphereAdd": [ + 0.0, + 0.0, + 1.0, + 1.0, + ], + "_MainTex": [ + 0.0, + 0.0, + 1.0, + 1.0, + ], + "_Color": [ + 1.0, + 1.0, + 1.0, + 1.0, + ], + "_BumpMap": [ + 0.0, + 0.0, + 1.0, + 1.0, + ], + "_OutlineWidthTexture": [ + 0.0, + 0.0, + 1.0, + 1.0, + ], + "_EmissionMap": [ + 0.0, + 0.0, + 1.0, + 1.0, + ], + "_ReceiveShadowTexture": [ + 0.0, + 0.0, + 1.0, + 1.0, + ], + "_ShadeColor": [ + 1.0, + 1.0, + 1.0, + 1.0, + ], + "_UvAnimMaskTexture": [ + 0.0, + 0.0, + 1.0, + 1.0, + ], + "_ShadingGradeTexture": [ + 0.0, + 0.0, + 1.0, + 1.0, + ], + "_RimColor": [ + 0.06814244, + 0.06814244, + 0.06814244, + 1.0, + ], + }, + ), + }, + VRMMaterial { + float_properties: Some( + { + "_OutlineLightingMix": 1.0, + "_OutlineScaledMaxDistance": 1.0, + "_ShadingGradeRate": 1.0, + "_BumpScale": 1.0, + "_OutlineWidth": 0.075, + "_SrcBlend": 1.0, + "_CullMode": 2.0, + "_IndirectLightIntensity": 0.1, + "_ShadeShift": -1.0, + "_UvAnimRotation": 0.0, + "_BlendMode": 1.0, + "_RimFresnelPower": 7.999984, + "_ReceiveShadowRate": 1.0, + "_UvAnimScrollX": 0.0, + "_OutlineWidthMode": 1.0, + "_UvAnimScrollY": 0.0, + "_MToonVersion": 34.0, + "_DstBlend": 0.0, + "_RimLift": 0.1, + "_ZWrite": 1.0, + "_ShadeToony": 1.0, + "_Cutoff": 0.5, + "_DebugMode": 0.0, + "_OutlineCullMode": 1.0, + "_OutlineColorMode": 0.0, + "_RimLightingMix": 0.0, + "_LightColorAttenuation": 0.0, + }, + ), + keyword_map: Some( + { + "MTOON_OUTLINE_WIDTH_WORLD": true, + "_ALPHATEST_ON": true, + "MTOON_OUTLINE_COLOR_FIXED": true, + "_NORMALMAP": true, + }, + ), + name: Some( + "N00_000_00_Body_00_SKIN (Instance)", + ), + render_queue: Some( + 2450, + ), + shader: Some( + "VRM/MToon", + ), + tag_map: Some( + { + "RenderType": "TransparentCutout", + }, + ), + texture_properties: Some( + { + "_BumpMap": 5, + "_OutlineWidthTexture": 12, + "_EmissionMap": 4, + "_MainTex": 3, + "_ShadeTexture": 11, + "_SphereAdd": 10, + }, + ), + vector_properties: Some( + { + "_ShadeTexture": [ + 0.0, + 0.0, + 1.0, + 1.0, + ], + "_RimTexture": [ + 0.0, + 0.0, + 1.0, + 1.0, + ], + "_EmissionColor": [ + 1.0, + 1.0, + 1.0, + 1.0, + ], + "_OutlineColor": [ + 0.2745098, + 0.0901960656, + 0.125490159, + 1.0, + ], + "_SphereAdd": [ + 0.0, + 0.0, + 1.0, + 1.0, + ], + "_MainTex": [ + 0.0, + 0.0, + 1.0, + 1.0, + ], + "_Color": [ + 1.0, + 1.0, + 1.0, + 1.0, + ], + "_BumpMap": [ + 0.0, + 0.0, + 1.0, + 1.0, + ], + "_OutlineWidthTexture": [ + 0.0, + 0.0, + 1.0, + 1.0, + ], + "_EmissionMap": [ + 0.0, + 0.0, + 1.0, + 1.0, + ], + "_ReceiveShadowTexture": [ + 0.0, + 0.0, + 1.0, + 1.0, + ], + "_ShadeColor": [ + 1.0, + 1.0, + 1.0, + 1.0, + ], + "_UvAnimMaskTexture": [ + 0.0, + 0.0, + 1.0, + 1.0, + ], + "_ShadingGradeTexture": [ + 0.0, + 0.0, + 1.0, + 1.0, + ], + "_RimColor": [ + 0.06814244, + 0.06814244, + 0.06814244, + 1.0, + ], + }, + ), + }, + VRMMaterial { + float_properties: Some( + { + "_OutlineLightingMix": 1.0, + "_OutlineScaledMaxDistance": 1.0, + "_ShadingGradeRate": 1.0, + "_BumpScale": 1.0, + "_OutlineWidth": 0.075, + "_SrcBlend": 1.0, + "_CullMode": 0.0, + "_IndirectLightIntensity": 0.1, + "_ShadeShift": -1.0, + "_UvAnimRotation": 0.0, + "_BlendMode": 1.0, + "_RimFresnelPower": 7.999984, + "_ReceiveShadowRate": 1.0, + "_UvAnimScrollX": 0.0, + "_OutlineWidthMode": 1.0, + "_UvAnimScrollY": 0.0, + "_MToonVersion": 34.0, + "_DstBlend": 0.0, + "_RimLift": 0.1, + "_ZWrite": 1.0, + "_ShadeToony": 1.0, + "_Cutoff": 0.5, + "_DebugMode": 0.0, + "_OutlineCullMode": 1.0, + "_OutlineColorMode": 0.0, + "_RimLightingMix": 0.0, + "_LightColorAttenuation": 0.0, + }, + ), + keyword_map: Some( + { + "MTOON_OUTLINE_WIDTH_WORLD": true, + "_ALPHATEST_ON": true, + "MTOON_OUTLINE_COLOR_FIXED": true, + "_NORMALMAP": true, + }, + ), + name: Some( + "N00_001_02_Bottoms_01_CLOTH (Instance)", + ), + render_queue: Some( + 2450, + ), + shader: Some( + "VRM/MToon", + ), + tag_map: Some( + { + "RenderType": "TransparentCutout", + }, + ), + texture_properties: Some( + { + "_BumpMap": 5, + "_OutlineWidthTexture": 12, + "_EmissionMap": 4, + "_MainTex": 3, + "_ShadeTexture": 11, + "_SphereAdd": 10, + }, + ), + vector_properties: Some( + { + "_ShadeTexture": [ + 0.0, + 0.0, + 1.0, + 1.0, + ], + "_RimTexture": [ + 0.0, + 0.0, + 1.0, + 1.0, + ], + "_EmissionColor": [ + 1.0, + 1.0, + 1.0, + 1.0, + ], + "_OutlineColor": [ + 0.2745098, + 0.0901960656, + 0.125490159, + 1.0, + ], + "_SphereAdd": [ + 0.0, + 0.0, + 1.0, + 1.0, + ], + "_MainTex": [ + 0.0, + 0.0, + 1.0, + 1.0, + ], + "_Color": [ + 1.0, + 1.0, + 1.0, + 1.0, + ], + "_BumpMap": [ + 0.0, + 0.0, + 1.0, + 1.0, + ], + "_OutlineWidthTexture": [ + 0.0, + 0.0, + 1.0, + 1.0, + ], + "_EmissionMap": [ + 0.0, + 0.0, + 1.0, + 1.0, + ], + "_ReceiveShadowTexture": [ + 0.0, + 0.0, + 1.0, + 1.0, + ], + "_ShadeColor": [ + 1.0, + 1.0, + 1.0, + 1.0, + ], + "_UvAnimMaskTexture": [ + 0.0, + 0.0, + 1.0, + 1.0, + ], + "_ShadingGradeTexture": [ + 0.0, + 0.0, + 1.0, + 1.0, + ], + "_RimColor": [ + 0.06814244, + 0.06814244, + 0.06814244, + 1.0, + ], + }, + ), + }, + VRMMaterial { + float_properties: Some( + { + "_OutlineLightingMix": 1.0, + "_OutlineScaledMaxDistance": 1.0, + "_ShadingGradeRate": 1.0, + "_BumpScale": 1.0, + "_OutlineWidth": 0.075, + "_SrcBlend": 1.0, + "_CullMode": 0.0, + "_IndirectLightIntensity": 0.1, + "_ShadeShift": -0.3, + "_UvAnimRotation": 0.0, + "_BlendMode": 1.0, + "_RimFresnelPower": 8.61537552, + "_ReceiveShadowRate": 1.0, + "_UvAnimScrollX": 0.0, + "_OutlineWidthMode": 1.0, + "_UvAnimScrollY": 0.0, + "_MToonVersion": 34.0, + "_DstBlend": 0.0, + "_RimLift": 0.1, + "_ZWrite": 1.0, + "_ShadeToony": 0.0, + "_Cutoff": 0.5, + "_DebugMode": 0.0, + "_OutlineCullMode": 1.0, + "_OutlineColorMode": 0.0, + "_RimLightingMix": 0.0, + "_LightColorAttenuation": 0.0, + }, + ), + keyword_map: Some( + { + "MTOON_OUTLINE_WIDTH_WORLD": true, + "_ALPHATEST_ON": true, + "MTOON_OUTLINE_COLOR_FIXED": true, + "_NORMALMAP": true, + }, + ), + name: Some( + "N00_000_Hair_00_HAIR_01 (Instance)", + ), + render_queue: Some( + 2450, + ), + shader: Some( + "VRM/MToon", + ), + tag_map: Some( + { + "RenderType": "TransparentCutout", + }, + ), + texture_properties: Some( + { + "_BumpMap": 5, + "_OutlineWidthTexture": 12, + "_EmissionMap": 4, + "_MainTex": 3, + "_ShadeTexture": 11, + "_SphereAdd": 10, + }, + ), + vector_properties: Some( + { + "_ShadeTexture": [ + 0.0, + 0.0, + 1.0, + 1.0, + ], + "_RimTexture": [ + 0.0, + 0.0, + 1.0, + 1.0, + ], + "_EmissionColor": [ + 1.0, + 1.0, + 1.0, + 1.0, + ], + "_OutlineColor": [ + 0.0, + 0.0, + 0.0, + 1.0, + ], + "_SphereAdd": [ + 0.0, + 0.0, + 1.0, + 1.0, + ], + "_MainTex": [ + 0.0, + 0.0, + 1.0, + 1.0, + ], + "_Color": [ + 1.0, + 1.0, + 1.0, + 1.0, + ], + "_BumpMap": [ + 0.0, + 0.0, + 1.0, + 1.0, + ], + "_OutlineWidthTexture": [ + 0.0, + 0.0, + 1.0, + 1.0, + ], + "_EmissionMap": [ + 0.0, + 0.0, + 1.0, + 1.0, + ], + "_ReceiveShadowTexture": [ + 0.0, + 0.0, + 1.0, + 1.0, + ], + "_ShadeColor": [ + 1.0, + 1.0, + 1.0, + 1.0, + ], + "_UvAnimMaskTexture": [ + 0.0, + 0.0, + 1.0, + 1.0, + ], + "_ShadingGradeTexture": [ + 0.0, + 0.0, + 1.0, + 1.0, + ], + "_RimColor": [ + 0.04832035, + 0.04832035, + 0.04832035, + 1.0, + ], + }, + ), + }, + ], + ), + meta: Some( + VRMMeta { + allowed_user_name: Some( + Everyone, + ), + author: Some( + "VRoid Project", + ), + commercial_ussage_name: Some( + Allow, + ), + contact_information: Some( + "https://www.pixiv.net/support.php?mode=select_vroid", + ), + license_name: Some( + Other, + ), + other_license_url: Some( + "https://hub.vroid.com/license?allowed_to_use_user=everyone&characterization_allowed_user=everyone&corporate_commercial_use=allow&credit=unnecessary&modification=allow&personal_commercial_use=profit&redistribution=allow&sexual_expression=allow&version=1&violent_expression=allow", + ), + other_permission_url: Some( + "https://hub.vroid.com/license?allowed_to_use_user=everyone&characterization_allowed_user=everyone&corporate_commercial_use=allow&credit=unnecessary&modification=allow&personal_commercial_use=profit&redistribution=allow&sexual_expression=allow&version=1&violent_expression=allow", + ), + reference: None, + sexual_ussage_name: Some( + Allow, + ), + texture: Some( + 6, + ), + title: Some( + "AvatarSample_A", + ), + version: Some( + "1.0", + ), + violent_ussage_name: Some( + Allow, + ), + }, + ), + secondary_animation: Some( + VRMSecondaryAnimation { + bone_groups: Some( + [ + VRMSecondaryAnimationSpring { + bones: Some( + [ + 5, + 7, + ], + ), + center: Some( + 0, + ), + collider_groups: Some( + [], + ), + comment: Some( + "Bust", + ), + drag_force: Some( + 0.05, + ), + gravity_dir: Some( + OptionalVector3 { + x: Some( + 0.0, + ), + y: Some( + -1.0, + ), + z: Some( + 0.0, + ), + }, + ), + gravity_power: Some( + 0.0, + ), + hit_radius: Some( + 0.00772699434, + ), + stiffiness: Some( + 0.75, + ), + }, + VRMSecondaryAnimationSpring { + bones: Some( + [ + 9, + ], + ), + center: Some( + 0, + ), + collider_groups: Some( + [], + ), + comment: Some( + "Hood", + ), + drag_force: Some( + 0.05, + ), + gravity_dir: Some( + OptionalVector3 { + x: Some( + 0.0, + ), + y: Some( + -1.0, + ), + z: Some( + 0.0, + ), + }, + ), + gravity_power: Some( + 0.0, + ), + hit_radius: Some( + 0.00773664657, + ), + stiffiness: Some( + 0.75, + ), + }, + VRMSecondaryAnimationSpring { + bones: Some( + [ + 12, + 15, + ], + ), + center: Some( + 0, + ), + collider_groups: Some( + [], + ), + comment: Some( + "HoodString", + ), + drag_force: Some( + 0.0, + ), + gravity_dir: Some( + OptionalVector3 { + x: Some( + 0.0, + ), + y: Some( + -1.0, + ), + z: Some( + 0.0, + ), + }, + ), + gravity_power: Some( + 0.25, + ), + hit_radius: Some( + 0.007736551, + ), + stiffiness: Some( + 0.26, + ), + }, + VRMSecondaryAnimationSpring { + bones: Some( + [ + 21, + ], + ), + center: Some( + 0, + ), + collider_groups: Some( + [ + 3, + 4, + 7, + 5, + 8, + 6, + 9, + 1, + 0, + 2, + ], + ), + comment: Some( + "Hair", + ), + drag_force: Some( + 0.4, + ), + gravity_dir: Some( + OptionalVector3 { + x: Some( + 0.0, + ), + y: Some( + -1.0, + ), + z: Some( + 0.0, + ), + }, + ), + gravity_power: Some( + 0.0, + ), + hit_radius: Some( + 0.0107275, + ), + stiffiness: Some( + 0.8475211, + ), + }, + VRMSecondaryAnimationSpring { + bones: Some( + [ + 23, + ], + ), + center: Some( + 0, + ), + collider_groups: Some( + [ + 3, + 4, + 7, + 5, + 8, + 6, + 9, + 1, + 0, + 2, + ], + ), + comment: Some( + "Hair", + ), + drag_force: Some( + 0.4, + ), + gravity_dir: Some( + OptionalVector3 { + x: Some( + 0.0, + ), + y: Some( + -1.0, + ), + z: Some( + 0.0, + ), + }, + ), + gravity_power: Some( + 0.0, + ), + hit_radius: Some( + 0.0107275, + ), + stiffiness: Some( + 0.848229468, + ), + }, + VRMSecondaryAnimationSpring { + bones: Some( + [ + 26, + ], + ), + center: Some( + 0, + ), + collider_groups: Some( + [ + 3, + 4, + 7, + 5, + 8, + 6, + 9, + 1, + 0, + 2, + ], + ), + comment: Some( + "Hair", + ), + drag_force: Some( + 0.4, + ), + gravity_dir: Some( + OptionalVector3 { + x: Some( + 0.0, + ), + y: Some( + -1.0, + ), + z: Some( + 0.0, + ), + }, + ), + gravity_power: Some( + 0.0, + ), + hit_radius: Some( + 0.0107275015, + ), + stiffiness: Some( + 0.8020291, + ), + }, + VRMSecondaryAnimationSpring { + bones: Some( + [ + 30, + ], + ), + center: Some( + 0, + ), + collider_groups: Some( + [ + 3, + 4, + 7, + 5, + 8, + 6, + 9, + 1, + 0, + 2, + ], + ), + comment: Some( + "Hair", + ), + drag_force: Some( + 0.4, + ), + gravity_dir: Some( + OptionalVector3 { + x: Some( + 0.0, + ), + y: Some( + -1.0, + ), + z: Some( + 0.0, + ), + }, + ), + gravity_power: Some( + 0.0, + ), + hit_radius: Some( + 0.0107275154, + ), + stiffiness: Some( + 0.8012918, + ), + }, + VRMSecondaryAnimationSpring { + bones: Some( + [ + 34, + ], + ), + center: Some( + 0, + ), + collider_groups: Some( + [ + 3, + 4, + 7, + 5, + 8, + 6, + 9, + 1, + 0, + 2, + ], + ), + comment: Some( + "Hair", + ), + drag_force: Some( + 0.4, + ), + gravity_dir: Some( + OptionalVector3 { + x: Some( + 0.0, + ), + y: Some( + -1.0, + ), + z: Some( + 0.0, + ), + }, + ), + gravity_power: Some( + 0.0, + ), + hit_radius: Some( + 0.0107275052, + ), + stiffiness: Some( + 0.7997162, + ), + }, + VRMSecondaryAnimationSpring { + bones: Some( + [ + 38, + ], + ), + center: Some( + 0, + ), + collider_groups: Some( + [ + 3, + 4, + 7, + 5, + 8, + 6, + 9, + 1, + 0, + 2, + ], + ), + comment: Some( + "Hair", + ), + drag_force: Some( + 0.4, + ), + gravity_dir: Some( + OptionalVector3 { + x: Some( + 0.0, + ), + y: Some( + -1.0, + ), + z: Some( + 0.0, + ), + }, + ), + gravity_power: Some( + 0.0, + ), + hit_radius: Some( + 0.0107275071, + ), + stiffiness: Some( + 0.854821265, + ), + }, + VRMSecondaryAnimationSpring { + bones: Some( + [ + 41, + ], + ), + center: Some( + 0, + ), + collider_groups: Some( + [ + 3, + 4, + 7, + 5, + 8, + 6, + 9, + 1, + 0, + 2, + ], + ), + comment: Some( + "Hair", + ), + drag_force: Some( + 0.4, + ), + gravity_dir: Some( + OptionalVector3 { + x: Some( + 0.0, + ), + y: Some( + -1.0, + ), + z: Some( + 0.0, + ), + }, + ), + gravity_power: Some( + 0.0, + ), + hit_radius: Some( + 0.0107275164, + ), + stiffiness: Some( + 0.798603058, + ), + }, + ], + ), + collider_groups: Some( + [ + VRMSecondaryAnimationColliderGroup { + colliders: Some( + [ + Collider { + offset: Some( + OptionalVector3 { + x: Some( + 0.0, + ), + y: Some( + 0.0, + ), + z: Some( + 0.0, + ), + }, + ), + radius: Some( + 0.103557207, + ), + }, + ], + ), + node: Some( + 2, + ), + }, + VRMSecondaryAnimationColliderGroup { + colliders: Some( + [ + Collider { + offset: Some( + OptionalVector3 { + x: Some( + 0.0, + ), + y: Some( + 0.00286316872, + ), + z: Some( + 0.00814094953, + ), + }, + ), + radius: Some( + 0.08629768, + ), + }, + Collider { + offset: Some( + OptionalVector3 { + x: Some( + -0.04314884, + ), + y: Some( + 0.050053, + ), + z: Some( + -0.0267516635, + ), + }, + ), + radius: Some( + 0.0604083762, + ), + }, + Collider { + offset: Some( + OptionalVector3 { + x: Some( + 0.04314884, + ), + y: Some( + 0.050053, + ), + z: Some( + -0.0267516635, + ), + }, + ), + radius: Some( + 0.0604083762, + ), + }, + ], + ), + node: Some( + 4, + ), + }, + VRMSecondaryAnimationColliderGroup { + colliders: Some( + [ + Collider { + offset: Some( + OptionalVector3 { + x: Some( + 0.0, + ), + y: Some( + 0.0243667364, + ), + z: Some( + 0.0142260259, + ), + }, + ), + radius: Some( + 0.04314884, + ), + }, + ], + ), + node: Some( + 17, + ), + }, + VRMSecondaryAnimationColliderGroup { + colliders: Some( + [ + Collider { + offset: Some( + OptionalVector3 { + x: Some( + 1.19215393e-8, + ), + y: Some( + 0.107435942, + ), + z: Some( + -0.0142858028, + ), + }, + ), + radius: Some( + 0.107275121, + ), + }, + ], + ), + node: Some( + 18, + ), + }, + VRMSecondaryAnimationColliderGroup { + colliders: Some( + [ + Collider { + offset: Some( + OptionalVector3 { + x: Some( + 0.0, + ), + y: Some( + -0.008629799, + ), + z: Some( + -3.7252903e-9, + ), + }, + ), + radius: Some( + 0.0431488454, + ), + }, + Collider { + offset: Some( + OptionalVector3 { + x: Some( + -0.06472327, + ), + y: Some( + -0.008629799, + ), + z: Some( + -3.7252903e-9, + ), + }, + ), + radius: Some( + 0.0431488454, + ), + }, + Collider { + offset: Some( + OptionalVector3 { + x: Some( + -0.129446536, + ), + y: Some( + -0.008629799, + ), + z: Some( + -1.86264515e-9, + ), + }, + ), + radius: Some( + 0.0431488454, + ), + }, + ], + ), + node: Some( + 46, + ), + }, + VRMSecondaryAnimationColliderGroup { + colliders: Some( + [ + Collider { + offset: Some( + OptionalVector3 { + x: Some( + 2.98023224e-8, + ), + y: Some( + 0.0, + ), + z: Some( + -3.7252903e-9, + ), + }, + ), + radius: Some( + 0.0258893073, + ), + }, + Collider { + offset: Some( + OptionalVector3 { + x: Some( + -0.043148756, + ), + y: Some( + 0.0, + ), + z: Some( + 7.530488e-5, + ), + }, + ), + radius: Some( + 0.0302041918, + ), + }, + Collider { + offset: Some( + OptionalVector3 { + x: Some( + -0.08629751, + ), + y: Some( + 0.0, + ), + z: Some( + 0.000150615349, + ), + }, + ), + radius: Some( + 0.0258893073, + ), + }, + Collider { + offset: Some( + OptionalVector3 { + x: Some( + -0.129446328, + ), + y: Some( + 0.0, + ), + z: Some( + 0.000225923955, + ), + }, + ), + radius: Some( + 0.0258893073, + ), + }, + ], + ), + node: Some( + 47, + ), + }, + VRMSecondaryAnimationColliderGroup { + colliders: Some( + [ + Collider { + offset: Some( + OptionalVector3 { + x: Some( + -0.0159171224, + ), + y: Some( + 0.0, + ), + z: Some( + 7.450581e-9, + ), + }, + ), + radius: Some( + 0.0238757227, + ), + }, + ], + ), + node: Some( + 48, + ), + }, + VRMSecondaryAnimationColliderGroup { + colliders: Some( + [ + Collider { + offset: Some( + OptionalVector3 { + x: Some( + 0.0, + ), + y: Some( + -0.008629799, + ), + z: Some( + -3.7252903e-9, + ), + }, + ), + radius: Some( + 0.0431488454, + ), + }, + Collider { + offset: Some( + OptionalVector3 { + x: Some( + 0.06472327, + ), + y: Some( + -0.008629799, + ), + z: Some( + -3.7252903e-9, + ), + }, + ), + radius: Some( + 0.0431488454, + ), + }, + Collider { + offset: Some( + OptionalVector3 { + x: Some( + 0.129446536, + ), + y: Some( + -0.008629799, + ), + z: Some( + -1.86264515e-9, + ), + }, + ), + radius: Some( + 0.0431488454, + ), + }, + ], + ), + node: Some( + 65, + ), + }, + VRMSecondaryAnimationColliderGroup { + colliders: Some( + [ + Collider { + offset: Some( + OptionalVector3 { + x: Some( + -2.98023224e-8, + ), + y: Some( + 0.0, + ), + z: Some( + -3.7252903e-9, + ), + }, + ), + radius: Some( + 0.0258893073, + ), + }, + Collider { + offset: Some( + OptionalVector3 { + x: Some( + 0.043148756, + ), + y: Some( + 0.0, + ), + z: Some( + 7.530488e-5, + ), + }, + ), + radius: Some( + 0.0302041918, + ), + }, + Collider { + offset: Some( + OptionalVector3 { + x: Some( + 0.08629751, + ), + y: Some( + 0.0, + ), + z: Some( + 0.000150615349, + ), + }, + ), + radius: Some( + 0.0258893073, + ), + }, + Collider { + offset: Some( + OptionalVector3 { + x: Some( + 0.129446328, + ), + y: Some( + 0.0, + ), + z: Some( + 0.000225923955, + ), + }, + ), + radius: Some( + 0.0258893073, + ), + }, + ], + ), + node: Some( + 66, + ), + }, + VRMSecondaryAnimationColliderGroup { + colliders: Some( + [ + Collider { + offset: Some( + OptionalVector3 { + x: Some( + 0.0159171224, + ), + y: Some( + 0.0, + ), + z: Some( + 7.450581e-9, + ), + }, + ), + radius: Some( + 0.0238757227, + ), + }, + ], + ), + node: Some( + 67, + ), + }, + ], + ), + }, + ), + spec_version: Some( + "0.0", + ), +} diff --git a/crates/vrm-spec/tests/snapshots/test__vrm1-2.snap b/crates/vrm-spec/tests/snapshots/test__vrm1-2.snap new file mode 100644 index 0000000..f5c875a --- /dev/null +++ b/crates/vrm-spec/tests/snapshots/test__vrm1-2.snap @@ -0,0 +1,2257 @@ +--- +source: crates/vrm-spec/tests/test.rs +expression: vrmc_spring_bone +--- +VrmcSpringBoneSchema { + collider_groups: Some( + [ + ColliderGroup { + colliders: [ + 0, + ], + extensions: None, + extras: None, + name: Some( + "Spine", + ), + }, + ColliderGroup { + colliders: [ + 1, + 2, + ], + extensions: None, + extras: None, + name: Some( + "UpperChest", + ), + }, + ColliderGroup { + colliders: [ + 3, + ], + extensions: None, + extras: None, + name: Some( + "Neck", + ), + }, + ColliderGroup { + colliders: [ + 4, + ], + extensions: None, + extras: None, + name: Some( + "Head", + ), + }, + ColliderGroup { + colliders: [ + 5, + ], + extensions: None, + extras: None, + name: Some( + "LeftUpperArm", + ), + }, + ColliderGroup { + colliders: [ + 6, + ], + extensions: None, + extras: None, + name: Some( + "leftLowerArm", + ), + }, + ColliderGroup { + colliders: [ + 7, + ], + extensions: None, + extras: None, + name: Some( + "LeftHand", + ), + }, + ColliderGroup { + colliders: [ + 8, + ], + extensions: None, + extras: None, + name: Some( + "RightUpperArm", + ), + }, + ColliderGroup { + colliders: [ + 9, + ], + extensions: None, + extras: None, + name: Some( + "RightLowerArm", + ), + }, + ColliderGroup { + colliders: [ + 10, + ], + extensions: None, + extras: None, + name: Some( + "RightHand", + ), + }, + ColliderGroup { + colliders: [ + 11, + ], + extensions: None, + extras: None, + name: Some( + "LeftUpperLeg", + ), + }, + ColliderGroup { + colliders: [ + 12, + ], + extensions: None, + extras: None, + name: Some( + "RightUpperLeg", + ), + }, + ], + ), + colliders: Some( + [ + Collider { + extensions: None, + extras: None, + node: 21, + shape: ColliderShape { + capsule: None, + extensions: None, + extras: None, + sphere: Some( + ColliderShapeSphere { + offset: Some( + [ + 0.0, + 0.0, + 0.0, + ], + ), + radius: Some( + 0.114335559, + ), + }, + ), + }, + }, + Collider { + extensions: None, + extras: None, + node: 23, + shape: ColliderShape { + capsule: None, + extensions: None, + extras: None, + sphere: Some( + ColliderShapeSphere { + offset: Some( + [ + 0.0, + 0.003161192, + -0.008988261, + ], + ), + radius: Some( + 0.095279634, + ), + }, + ), + }, + }, + Collider { + extensions: None, + extras: None, + node: 23, + shape: ColliderShape { + capsule: Some( + ColliderShapeCapsule { + offset: Some( + [ + 0.047639817, + 0.0552625656, + 0.0295360126, + ], + ), + radius: Some( + 0.06669574, + ), + tail: Some( + [ + -0.047639817, + 0.0552625656, + 0.0295360126, + ], + ), + }, + ), + extensions: None, + extras: None, + sphere: None, + }, + }, + Collider { + extensions: None, + extras: None, + node: 24, + shape: ColliderShape { + capsule: None, + extensions: None, + extras: None, + sphere: Some( + ColliderShapeSphere { + offset: Some( + [ + 0.0, + 0.0269027948, + -0.0157066863, + ], + ), + radius: Some( + 0.047639817, + ), + }, + ), + }, + }, + Collider { + extensions: None, + extras: None, + node: 25, + shape: ColliderShape { + capsule: None, + extensions: None, + extras: None, + sphere: Some( + ColliderShapeSphere { + offset: Some( + [ + 0.100349069, + -0.0133434664, + 0.0, + ], + ), + radius: Some( + 0.100198768, + ), + }, + ), + }, + }, + Collider { + extensions: None, + extras: None, + node: 96, + shape: ColliderShape { + capsule: Some( + ColliderShapeCapsule { + offset: Some( + [ + 0.0, + -0.009527922, + 0.0, + ], + ), + radius: Some( + 0.04763983, + ), + tail: Some( + [ + 0.142919481, + -0.009527922, + 0.0, + ], + ), + }, + ), + extensions: None, + extras: None, + sphere: None, + }, + }, + Collider { + extensions: None, + extras: None, + node: 97, + shape: ColliderShape { + capsule: Some( + ColliderShapeCapsule { + offset: Some( + [ + 0.0, + 0.0, + 0.0, + ], + ), + radius: Some( + 0.0285838936, + ), + tail: Some( + [ + 0.142919272, + 0.0, + -0.000249436125, + ], + ), + }, + ), + extensions: None, + extras: None, + sphere: None, + }, + }, + Collider { + extensions: None, + extras: None, + node: 98, + shape: ColliderShape { + capsule: None, + extensions: None, + extras: None, + sphere: Some( + ColliderShapeSphere { + offset: Some( + [ + -0.019055903, + 0.0, + 0.0, + ], + ), + radius: Some( + 0.0285838936, + ), + }, + ), + }, + }, + Collider { + extensions: None, + extras: None, + node: 130, + shape: ColliderShape { + capsule: Some( + ColliderShapeCapsule { + offset: Some( + [ + 0.0, + -0.009527922, + 0.0, + ], + ), + radius: Some( + 0.04763983, + ), + tail: Some( + [ + -0.142919481, + -0.009527922, + 0.0, + ], + ), + }, + ), + extensions: None, + extras: None, + sphere: None, + }, + }, + Collider { + extensions: None, + extras: None, + node: 131, + shape: ColliderShape { + capsule: Some( + ColliderShapeCapsule { + offset: Some( + [ + 0.0, + 0.0, + 0.0, + ], + ), + radius: Some( + 0.0285838936, + ), + tail: Some( + [ + -0.142919272, + 0.0, + -0.000249436125, + ], + ), + }, + ), + extensions: None, + extras: None, + sphere: None, + }, + }, + Collider { + extensions: None, + extras: None, + node: 132, + shape: ColliderShape { + capsule: None, + extensions: None, + extras: None, + sphere: Some( + ColliderShapeSphere { + offset: Some( + [ + 0.019055903, + 0.0, + 0.0, + ], + ), + radius: Some( + 0.0285838936, + ), + }, + ), + }, + }, + Collider { + extensions: None, + extras: None, + node: 160, + shape: ColliderShape { + capsule: Some( + ColliderShapeCapsule { + offset: Some( + [ + 0.0, + 0.0, + 0.0, + ], + ), + radius: Some( + 0.0771765038, + ), + tail: Some( + [ + 0.0, + -0.209569335, + 0.004385664, + ], + ), + }, + ), + extensions: None, + extras: None, + sphere: None, + }, + }, + Collider { + extensions: None, + extras: None, + node: 165, + shape: ColliderShape { + capsule: Some( + ColliderShapeCapsule { + offset: Some( + [ + 0.0, + 0.0, + 0.0, + ], + ), + radius: Some( + 0.0771765038, + ), + tail: Some( + [ + 0.0, + -0.209569335, + 0.004385664, + ], + ), + }, + ), + extensions: None, + extras: None, + sphere: None, + }, + }, + ], + ), + extensions: None, + extras: None, + spec_version: "1.0", + springs: Some( + [ + Spring { + center: None, + collider_groups: Some( + [ + 10, + 11, + ], + ), + extensions: None, + extras: None, + joints: [ + SpringBoneJoint { + drag_force: Some( + 0.05, + ), + extensions: None, + extras: None, + gravity_dir: None, + gravity_power: None, + hit_radius: Some( + 0.014418928, + ), + node: 7, + stiffness: Some( + 0.5, + ), + }, + SpringBoneJoint { + drag_force: Some( + 0.5, + ), + extensions: None, + extras: None, + gravity_dir: None, + gravity_power: None, + hit_radius: Some( + 0.0, + ), + node: 8, + stiffness: Some( + 1.0, + ), + }, + ], + name: Some( + "TopsUpperLeg", + ), + }, + Spring { + center: None, + collider_groups: Some( + [ + 10, + 11, + ], + ), + extensions: None, + extras: None, + joints: [ + SpringBoneJoint { + drag_force: Some( + 0.05, + ), + extensions: None, + extras: None, + gravity_dir: None, + gravity_power: None, + hit_radius: Some( + 0.014418928, + ), + node: 9, + stiffness: Some( + 0.5, + ), + }, + SpringBoneJoint { + drag_force: Some( + 0.5, + ), + extensions: None, + extras: None, + gravity_dir: None, + gravity_power: None, + hit_radius: Some( + 0.0, + ), + node: 10, + stiffness: Some( + 1.0, + ), + }, + ], + name: Some( + "TopsUpperLeg", + ), + }, + Spring { + center: None, + collider_groups: Some( + [ + 10, + 11, + ], + ), + extensions: None, + extras: None, + joints: [ + SpringBoneJoint { + drag_force: Some( + 0.05, + ), + extensions: None, + extras: None, + gravity_dir: None, + gravity_power: None, + hit_radius: Some( + 0.014418928, + ), + node: 11, + stiffness: Some( + 0.5, + ), + }, + SpringBoneJoint { + drag_force: Some( + 0.5, + ), + extensions: None, + extras: None, + gravity_dir: None, + gravity_power: None, + hit_radius: Some( + 0.0, + ), + node: 12, + stiffness: Some( + 1.0, + ), + }, + ], + name: Some( + "TopsUpperLeg", + ), + }, + Spring { + center: None, + collider_groups: Some( + [ + 10, + 11, + ], + ), + extensions: None, + extras: None, + joints: [ + SpringBoneJoint { + drag_force: Some( + 0.05, + ), + extensions: None, + extras: None, + gravity_dir: None, + gravity_power: None, + hit_radius: Some( + 0.014418928, + ), + node: 15, + stiffness: Some( + 0.5, + ), + }, + SpringBoneJoint { + drag_force: Some( + 0.5, + ), + extensions: None, + extras: None, + gravity_dir: None, + gravity_power: None, + hit_radius: Some( + 0.0, + ), + node: 16, + stiffness: Some( + 1.0, + ), + }, + ], + name: Some( + "TopsUpperLeg", + ), + }, + Spring { + center: None, + collider_groups: Some( + [ + 10, + 11, + ], + ), + extensions: None, + extras: None, + joints: [ + SpringBoneJoint { + drag_force: Some( + 0.05, + ), + extensions: None, + extras: None, + gravity_dir: None, + gravity_power: None, + hit_radius: Some( + 0.014418928, + ), + node: 17, + stiffness: Some( + 0.5, + ), + }, + SpringBoneJoint { + drag_force: Some( + 0.5, + ), + extensions: None, + extras: None, + gravity_dir: None, + gravity_power: None, + hit_radius: Some( + 0.0, + ), + node: 18, + stiffness: Some( + 1.0, + ), + }, + ], + name: Some( + "TopsUpperLeg", + ), + }, + Spring { + center: None, + collider_groups: Some( + [ + 10, + 11, + ], + ), + extensions: None, + extras: None, + joints: [ + SpringBoneJoint { + drag_force: Some( + 0.05, + ), + extensions: None, + extras: None, + gravity_dir: None, + gravity_power: None, + hit_radius: Some( + 0.014418928, + ), + node: 19, + stiffness: Some( + 0.5, + ), + }, + SpringBoneJoint { + drag_force: Some( + 0.5, + ), + extensions: None, + extras: None, + gravity_dir: None, + gravity_power: None, + hit_radius: Some( + 0.0, + ), + node: 20, + stiffness: Some( + 1.0, + ), + }, + ], + name: Some( + "TopsUpperLeg", + ), + }, + Spring { + center: None, + collider_groups: None, + extensions: None, + extras: None, + joints: [ + SpringBoneJoint { + drag_force: Some( + 0.05, + ), + extensions: None, + extras: None, + gravity_dir: None, + gravity_power: None, + hit_radius: Some( + 0.0159853827, + ), + node: 92, + stiffness: Some( + 0.75, + ), + }, + SpringBoneJoint { + drag_force: Some( + 0.5, + ), + extensions: None, + extras: None, + gravity_dir: None, + gravity_power: None, + hit_radius: Some( + 0.0, + ), + node: 93, + stiffness: Some( + 1.0, + ), + }, + ], + name: Some( + "TopsUpperArm", + ), + }, + Spring { + center: None, + collider_groups: None, + extensions: None, + extras: None, + joints: [ + SpringBoneJoint { + drag_force: Some( + 0.05, + ), + extensions: None, + extras: None, + gravity_dir: None, + gravity_power: None, + hit_radius: Some( + 0.0159853827, + ), + node: 94, + stiffness: Some( + 0.75, + ), + }, + SpringBoneJoint { + drag_force: Some( + 0.5, + ), + extensions: None, + extras: None, + gravity_dir: None, + gravity_power: None, + hit_radius: Some( + 0.0, + ), + node: 95, + stiffness: Some( + 1.0, + ), + }, + ], + name: Some( + "TopsUpperArm", + ), + }, + Spring { + center: None, + collider_groups: None, + extensions: None, + extras: None, + joints: [ + SpringBoneJoint { + drag_force: Some( + 0.05, + ), + extensions: None, + extras: None, + gravity_dir: None, + gravity_power: None, + hit_radius: Some( + 0.0159853827, + ), + node: 126, + stiffness: Some( + 0.75, + ), + }, + SpringBoneJoint { + drag_force: Some( + 0.5, + ), + extensions: None, + extras: None, + gravity_dir: None, + gravity_power: None, + hit_radius: Some( + 0.0, + ), + node: 127, + stiffness: Some( + 1.0, + ), + }, + ], + name: Some( + "TopsUpperArm", + ), + }, + Spring { + center: None, + collider_groups: None, + extensions: None, + extras: None, + joints: [ + SpringBoneJoint { + drag_force: Some( + 0.05, + ), + extensions: None, + extras: None, + gravity_dir: None, + gravity_power: None, + hit_radius: Some( + 0.0159853827, + ), + node: 128, + stiffness: Some( + 0.75, + ), + }, + SpringBoneJoint { + drag_force: Some( + 0.5, + ), + extensions: None, + extras: None, + gravity_dir: None, + gravity_power: None, + hit_radius: Some( + 0.0, + ), + node: 129, + stiffness: Some( + 1.0, + ), + }, + ], + name: Some( + "TopsUpperArm", + ), + }, + Spring { + center: None, + collider_groups: Some( + [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + ], + ), + extensions: None, + extras: None, + joints: [ + SpringBoneJoint { + drag_force: Some( + 0.4, + ), + extensions: None, + extras: None, + gravity_dir: None, + gravity_power: None, + hit_radius: Some( + 0.0100198835, + ), + node: 29, + stiffness: Some( + 0.6041667, + ), + }, + SpringBoneJoint { + drag_force: Some( + 0.4, + ), + extensions: None, + extras: None, + gravity_dir: None, + gravity_power: None, + hit_radius: Some( + 0.0100198835, + ), + node: 30, + stiffness: Some( + 0.6041667, + ), + }, + SpringBoneJoint { + drag_force: Some( + 0.5, + ), + extensions: None, + extras: None, + gravity_dir: None, + gravity_power: None, + hit_radius: Some( + 0.0, + ), + node: 31, + stiffness: Some( + 1.0, + ), + }, + ], + name: Some( + "Hair", + ), + }, + Spring { + center: None, + collider_groups: Some( + [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + ], + ), + extensions: None, + extras: None, + joints: [ + SpringBoneJoint { + drag_force: Some( + 0.4, + ), + extensions: None, + extras: None, + gravity_dir: None, + gravity_power: None, + hit_radius: Some( + 0.0100198826, + ), + node: 32, + stiffness: Some( + 0.7041662, + ), + }, + SpringBoneJoint { + drag_force: Some( + 0.4, + ), + extensions: None, + extras: None, + gravity_dir: None, + gravity_power: None, + hit_radius: Some( + 0.0100198826, + ), + node: 33, + stiffness: Some( + 0.7041662, + ), + }, + SpringBoneJoint { + drag_force: Some( + 0.5, + ), + extensions: None, + extras: None, + gravity_dir: None, + gravity_power: None, + hit_radius: Some( + 0.0, + ), + node: 34, + stiffness: Some( + 1.0, + ), + }, + ], + name: Some( + "Hair", + ), + }, + Spring { + center: None, + collider_groups: Some( + [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + ], + ), + extensions: None, + extras: None, + joints: [ + SpringBoneJoint { + drag_force: Some( + 0.4, + ), + extensions: None, + extras: None, + gravity_dir: None, + gravity_power: None, + hit_radius: Some( + 0.0100198826, + ), + node: 35, + stiffness: Some( + 0.704166651, + ), + }, + SpringBoneJoint { + drag_force: Some( + 0.4, + ), + extensions: None, + extras: None, + gravity_dir: None, + gravity_power: None, + hit_radius: Some( + 0.0100198826, + ), + node: 36, + stiffness: Some( + 0.704166651, + ), + }, + SpringBoneJoint { + drag_force: Some( + 0.5, + ), + extensions: None, + extras: None, + gravity_dir: None, + gravity_power: None, + hit_radius: Some( + 0.0, + ), + node: 37, + stiffness: Some( + 1.0, + ), + }, + ], + name: Some( + "Hair", + ), + }, + Spring { + center: None, + collider_groups: Some( + [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + ], + ), + extensions: None, + extras: None, + joints: [ + SpringBoneJoint { + drag_force: Some( + 0.4, + ), + extensions: None, + extras: None, + gravity_dir: None, + gravity_power: None, + hit_radius: Some( + 0.0100198826, + ), + node: 38, + stiffness: Some( + 0.5, + ), + }, + SpringBoneJoint { + drag_force: Some( + 0.4, + ), + extensions: None, + extras: None, + gravity_dir: None, + gravity_power: None, + hit_radius: Some( + 0.0100198826, + ), + node: 39, + stiffness: Some( + 0.5, + ), + }, + SpringBoneJoint { + drag_force: Some( + 0.5, + ), + extensions: None, + extras: None, + gravity_dir: None, + gravity_power: None, + hit_radius: Some( + 0.0, + ), + node: 40, + stiffness: Some( + 1.0, + ), + }, + ], + name: Some( + "Hair", + ), + }, + Spring { + center: None, + collider_groups: Some( + [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + ], + ), + extensions: None, + extras: None, + joints: [ + SpringBoneJoint { + drag_force: Some( + 0.4, + ), + extensions: None, + extras: None, + gravity_dir: None, + gravity_power: Some( + 0.09539474, + ), + hit_radius: Some( + 0.03164175, + ), + node: 41, + stiffness: Some( + 0.498684227, + ), + }, + SpringBoneJoint { + drag_force: Some( + 0.4, + ), + extensions: None, + extras: None, + gravity_dir: None, + gravity_power: Some( + 0.09539474, + ), + hit_radius: Some( + 0.03164175, + ), + node: 42, + stiffness: Some( + 0.498684227, + ), + }, + SpringBoneJoint { + drag_force: Some( + 0.4, + ), + extensions: None, + extras: None, + gravity_dir: None, + gravity_power: Some( + 0.09539474, + ), + hit_radius: Some( + 0.03164175, + ), + node: 43, + stiffness: Some( + 0.498684227, + ), + }, + SpringBoneJoint { + drag_force: Some( + 0.4, + ), + extensions: None, + extras: None, + gravity_dir: None, + gravity_power: Some( + 0.09539474, + ), + hit_radius: Some( + 0.03164175, + ), + node: 44, + stiffness: Some( + 0.498684227, + ), + }, + SpringBoneJoint { + drag_force: Some( + 0.4, + ), + extensions: None, + extras: None, + gravity_dir: None, + gravity_power: Some( + 0.09539474, + ), + hit_radius: Some( + 0.03164175, + ), + node: 45, + stiffness: Some( + 0.498684227, + ), + }, + SpringBoneJoint { + drag_force: Some( + 0.4, + ), + extensions: None, + extras: None, + gravity_dir: None, + gravity_power: Some( + 0.09539474, + ), + hit_radius: Some( + 0.03164175, + ), + node: 46, + stiffness: Some( + 0.498684227, + ), + }, + SpringBoneJoint { + drag_force: Some( + 0.5, + ), + extensions: None, + extras: None, + gravity_dir: None, + gravity_power: None, + hit_radius: Some( + 0.0, + ), + node: 47, + stiffness: Some( + 1.0, + ), + }, + ], + name: Some( + "Hair", + ), + }, + Spring { + center: None, + collider_groups: Some( + [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + ], + ), + extensions: None, + extras: None, + joints: [ + SpringBoneJoint { + drag_force: Some( + 0.4, + ), + extensions: None, + extras: None, + gravity_dir: None, + gravity_power: Some( + 0.1, + ), + hit_radius: Some( + 0.03206363, + ), + node: 48, + stiffness: Some( + 0.5, + ), + }, + SpringBoneJoint { + drag_force: Some( + 0.4, + ), + extensions: None, + extras: None, + gravity_dir: None, + gravity_power: Some( + 0.1, + ), + hit_radius: Some( + 0.03206363, + ), + node: 49, + stiffness: Some( + 0.5, + ), + }, + SpringBoneJoint { + drag_force: Some( + 0.4, + ), + extensions: None, + extras: None, + gravity_dir: None, + gravity_power: Some( + 0.1, + ), + hit_radius: Some( + 0.03206363, + ), + node: 50, + stiffness: Some( + 0.5, + ), + }, + SpringBoneJoint { + drag_force: Some( + 0.4, + ), + extensions: None, + extras: None, + gravity_dir: None, + gravity_power: Some( + 0.1, + ), + hit_radius: Some( + 0.03206363, + ), + node: 51, + stiffness: Some( + 0.5, + ), + }, + SpringBoneJoint { + drag_force: Some( + 0.4, + ), + extensions: None, + extras: None, + gravity_dir: None, + gravity_power: Some( + 0.1, + ), + hit_radius: Some( + 0.03206363, + ), + node: 52, + stiffness: Some( + 0.5, + ), + }, + SpringBoneJoint { + drag_force: Some( + 0.5, + ), + extensions: None, + extras: None, + gravity_dir: None, + gravity_power: None, + hit_radius: Some( + 0.0, + ), + node: 53, + stiffness: Some( + 1.0, + ), + }, + ], + name: Some( + "Hair", + ), + }, + Spring { + center: None, + collider_groups: Some( + [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + ], + ), + extensions: None, + extras: None, + joints: [ + SpringBoneJoint { + drag_force: Some( + 0.4, + ), + extensions: None, + extras: None, + gravity_dir: None, + gravity_power: Some( + 0.1, + ), + hit_radius: Some( + 0.032063622, + ), + node: 54, + stiffness: Some( + 0.5, + ), + }, + SpringBoneJoint { + drag_force: Some( + 0.4, + ), + extensions: None, + extras: None, + gravity_dir: None, + gravity_power: Some( + 0.1, + ), + hit_radius: Some( + 0.032063622, + ), + node: 55, + stiffness: Some( + 0.5, + ), + }, + SpringBoneJoint { + drag_force: Some( + 0.4, + ), + extensions: None, + extras: None, + gravity_dir: None, + gravity_power: Some( + 0.1, + ), + hit_radius: Some( + 0.032063622, + ), + node: 56, + stiffness: Some( + 0.5, + ), + }, + SpringBoneJoint { + drag_force: Some( + 0.4, + ), + extensions: None, + extras: None, + gravity_dir: None, + gravity_power: Some( + 0.1, + ), + hit_radius: Some( + 0.032063622, + ), + node: 57, + stiffness: Some( + 0.5, + ), + }, + SpringBoneJoint { + drag_force: Some( + 0.4, + ), + extensions: None, + extras: None, + gravity_dir: None, + gravity_power: Some( + 0.1, + ), + hit_radius: Some( + 0.032063622, + ), + node: 58, + stiffness: Some( + 0.5, + ), + }, + SpringBoneJoint { + drag_force: Some( + 0.5, + ), + extensions: None, + extras: None, + gravity_dir: None, + gravity_power: None, + hit_radius: Some( + 0.0, + ), + node: 59, + stiffness: Some( + 1.0, + ), + }, + ], + name: Some( + "Hair", + ), + }, + Spring { + center: None, + collider_groups: Some( + [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + ], + ), + extensions: None, + extras: None, + joints: [ + SpringBoneJoint { + drag_force: Some( + 0.4, + ), + extensions: None, + extras: None, + gravity_dir: None, + gravity_power: Some( + 0.1, + ), + hit_radius: Some( + 0.0320636332, + ), + node: 60, + stiffness: Some( + 0.5, + ), + }, + SpringBoneJoint { + drag_force: Some( + 0.4, + ), + extensions: None, + extras: None, + gravity_dir: None, + gravity_power: Some( + 0.1, + ), + hit_radius: Some( + 0.0320636332, + ), + node: 61, + stiffness: Some( + 0.5, + ), + }, + SpringBoneJoint { + drag_force: Some( + 0.4, + ), + extensions: None, + extras: None, + gravity_dir: None, + gravity_power: Some( + 0.1, + ), + hit_radius: Some( + 0.0320636332, + ), + node: 62, + stiffness: Some( + 0.5, + ), + }, + SpringBoneJoint { + drag_force: Some( + 0.4, + ), + extensions: None, + extras: None, + gravity_dir: None, + gravity_power: Some( + 0.1, + ), + hit_radius: Some( + 0.0320636332, + ), + node: 63, + stiffness: Some( + 0.5, + ), + }, + SpringBoneJoint { + drag_force: Some( + 0.4, + ), + extensions: None, + extras: None, + gravity_dir: None, + gravity_power: Some( + 0.1, + ), + hit_radius: Some( + 0.0320636332, + ), + node: 64, + stiffness: Some( + 0.5, + ), + }, + SpringBoneJoint { + drag_force: Some( + 0.5, + ), + extensions: None, + extras: None, + gravity_dir: None, + gravity_power: None, + hit_radius: Some( + 0.0, + ), + node: 65, + stiffness: Some( + 1.0, + ), + }, + ], + name: Some( + "Hair", + ), + }, + Spring { + center: None, + collider_groups: Some( + [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + ], + ), + extensions: None, + extras: None, + joints: [ + SpringBoneJoint { + drag_force: Some( + 0.4, + ), + extensions: None, + extras: None, + gravity_dir: None, + gravity_power: Some( + 0.1, + ), + hit_radius: Some( + 0.03206362, + ), + node: 66, + stiffness: Some( + 0.5, + ), + }, + SpringBoneJoint { + drag_force: Some( + 0.4, + ), + extensions: None, + extras: None, + gravity_dir: None, + gravity_power: Some( + 0.1, + ), + hit_radius: Some( + 0.03206362, + ), + node: 67, + stiffness: Some( + 0.5, + ), + }, + SpringBoneJoint { + drag_force: Some( + 0.4, + ), + extensions: None, + extras: None, + gravity_dir: None, + gravity_power: Some( + 0.1, + ), + hit_radius: Some( + 0.03206362, + ), + node: 68, + stiffness: Some( + 0.5, + ), + }, + SpringBoneJoint { + drag_force: Some( + 0.4, + ), + extensions: None, + extras: None, + gravity_dir: None, + gravity_power: Some( + 0.1, + ), + hit_radius: Some( + 0.03206362, + ), + node: 69, + stiffness: Some( + 0.5, + ), + }, + SpringBoneJoint { + drag_force: Some( + 0.4, + ), + extensions: None, + extras: None, + gravity_dir: None, + gravity_power: Some( + 0.1, + ), + hit_radius: Some( + 0.03206362, + ), + node: 70, + stiffness: Some( + 0.5, + ), + }, + SpringBoneJoint { + drag_force: Some( + 0.5, + ), + extensions: None, + extras: None, + gravity_dir: None, + gravity_power: None, + hit_radius: Some( + 0.0, + ), + node: 71, + stiffness: Some( + 1.0, + ), + }, + ], + name: Some( + "Hair", + ), + }, + Spring { + center: None, + collider_groups: Some( + [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + ], + ), + extensions: None, + extras: None, + joints: [ + SpringBoneJoint { + drag_force: Some( + 0.4, + ), + extensions: None, + extras: None, + gravity_dir: None, + gravity_power: Some( + 0.1, + ), + hit_radius: Some( + 0.03206363, + ), + node: 72, + stiffness: Some( + 0.5, + ), + }, + SpringBoneJoint { + drag_force: Some( + 0.4, + ), + extensions: None, + extras: None, + gravity_dir: None, + gravity_power: Some( + 0.1, + ), + hit_radius: Some( + 0.03206363, + ), + node: 73, + stiffness: Some( + 0.5, + ), + }, + SpringBoneJoint { + drag_force: Some( + 0.4, + ), + extensions: None, + extras: None, + gravity_dir: None, + gravity_power: Some( + 0.1, + ), + hit_radius: Some( + 0.03206363, + ), + node: 74, + stiffness: Some( + 0.5, + ), + }, + SpringBoneJoint { + drag_force: Some( + 0.4, + ), + extensions: None, + extras: None, + gravity_dir: None, + gravity_power: Some( + 0.1, + ), + hit_radius: Some( + 0.03206363, + ), + node: 75, + stiffness: Some( + 0.5, + ), + }, + SpringBoneJoint { + drag_force: Some( + 0.4, + ), + extensions: None, + extras: None, + gravity_dir: None, + gravity_power: Some( + 0.1, + ), + hit_radius: Some( + 0.03206363, + ), + node: 76, + stiffness: Some( + 0.5, + ), + }, + SpringBoneJoint { + drag_force: Some( + 0.5, + ), + extensions: None, + extras: None, + gravity_dir: None, + gravity_power: None, + hit_radius: Some( + 0.0, + ), + node: 77, + stiffness: Some( + 1.0, + ), + }, + ], + name: Some( + "Hair", + ), + }, + Spring { + center: None, + collider_groups: Some( + [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + ], + ), + extensions: None, + extras: None, + joints: [ + SpringBoneJoint { + drag_force: Some( + 0.4, + ), + extensions: None, + extras: None, + gravity_dir: None, + gravity_power: None, + hit_radius: Some( + 0.0100198835, + ), + node: 78, + stiffness: Some( + 0.4, + ), + }, + SpringBoneJoint { + drag_force: Some( + 0.4, + ), + extensions: None, + extras: None, + gravity_dir: None, + gravity_power: None, + hit_radius: Some( + 0.0100198835, + ), + node: 79, + stiffness: Some( + 0.4, + ), + }, + SpringBoneJoint { + drag_force: Some( + 0.4, + ), + extensions: None, + extras: None, + gravity_dir: None, + gravity_power: None, + hit_radius: Some( + 0.0100198835, + ), + node: 80, + stiffness: Some( + 0.4, + ), + }, + SpringBoneJoint { + drag_force: Some( + 0.4, + ), + extensions: None, + extras: None, + gravity_dir: None, + gravity_power: None, + hit_radius: Some( + 0.0100198835, + ), + node: 81, + stiffness: Some( + 0.4, + ), + }, + SpringBoneJoint { + drag_force: Some( + 0.5, + ), + extensions: None, + extras: None, + gravity_dir: None, + gravity_power: None, + hit_radius: Some( + 0.0, + ), + node: 82, + stiffness: Some( + 1.0, + ), + }, + ], + name: Some( + "Hair", + ), + }, + Spring { + center: None, + collider_groups: Some( + [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + ], + ), + extensions: None, + extras: None, + joints: [ + SpringBoneJoint { + drag_force: Some( + 0.4, + ), + extensions: None, + extras: None, + gravity_dir: None, + gravity_power: None, + hit_radius: Some( + 0.0100198826, + ), + node: 83, + stiffness: Some( + 0.4, + ), + }, + SpringBoneJoint { + drag_force: Some( + 0.4, + ), + extensions: None, + extras: None, + gravity_dir: None, + gravity_power: None, + hit_radius: Some( + 0.0100198826, + ), + node: 84, + stiffness: Some( + 0.4, + ), + }, + SpringBoneJoint { + drag_force: Some( + 0.4, + ), + extensions: None, + extras: None, + gravity_dir: None, + gravity_power: None, + hit_radius: Some( + 0.0100198826, + ), + node: 85, + stiffness: Some( + 0.4, + ), + }, + SpringBoneJoint { + drag_force: Some( + 0.4, + ), + extensions: None, + extras: None, + gravity_dir: None, + gravity_power: None, + hit_radius: Some( + 0.0100198826, + ), + node: 86, + stiffness: Some( + 0.4, + ), + }, + SpringBoneJoint { + drag_force: Some( + 0.5, + ), + extensions: None, + extras: None, + gravity_dir: None, + gravity_power: None, + hit_radius: Some( + 0.0, + ), + node: 87, + stiffness: Some( + 1.0, + ), + }, + ], + name: Some( + "Hair", + ), + }, + ], + ), +} diff --git a/crates/vrm-spec/tests/snapshots/test__vrm1.snap b/crates/vrm-spec/tests/snapshots/test__vrm1.snap new file mode 100644 index 0000000..25caf67 --- /dev/null +++ b/crates/vrm-spec/tests/snapshots/test__vrm1.snap @@ -0,0 +1,1045 @@ +--- +source: crates/vrm-spec/tests/test.rs +expression: vrmc_vrm +--- +VRMCVrmSchema { + expressions: Some( + Expressions { + custom: None, + extensions: None, + extras: None, + preset: Some( + Preset( + { + Aa: Expression { + extensions: None, + extras: None, + is_binary: Some( + false, + ), + material_color_binds: None, + morph_target_binds: Some( + [ + MorphTargetBind { + extensions: None, + extras: None, + index: 36, + node: 1, + weight: 1.0, + }, + ], + ), + override_blink: None, + override_look_at: None, + override_mouth: None, + texture_transform_binds: None, + }, + Relaxed: Expression { + extensions: None, + extras: None, + is_binary: Some( + false, + ), + material_color_binds: None, + morph_target_binds: Some( + [ + MorphTargetBind { + extensions: None, + extras: None, + index: 2, + node: 1, + weight: 1.0, + }, + ], + ), + override_blink: None, + override_look_at: None, + override_mouth: None, + texture_transform_binds: None, + }, + LookUp: Expression { + extensions: None, + extras: None, + is_binary: Some( + false, + ), + material_color_binds: None, + morph_target_binds: None, + override_blink: None, + override_look_at: None, + override_mouth: None, + texture_transform_binds: None, + }, + Ih: Expression { + extensions: None, + extras: None, + is_binary: Some( + false, + ), + material_color_binds: None, + morph_target_binds: Some( + [ + MorphTargetBind { + extensions: None, + extras: None, + index: 37, + node: 1, + weight: 1.0, + }, + ], + ), + override_blink: None, + override_look_at: None, + override_mouth: None, + texture_transform_binds: None, + }, + BlinkLeft: Expression { + extensions: None, + extras: None, + is_binary: Some( + false, + ), + material_color_binds: None, + morph_target_binds: Some( + [ + MorphTargetBind { + extensions: None, + extras: None, + index: 13, + node: 1, + weight: 1.0, + }, + ], + ), + override_blink: None, + override_look_at: None, + override_mouth: None, + texture_transform_binds: None, + }, + Ou: Expression { + extensions: None, + extras: None, + is_binary: Some( + false, + ), + material_color_binds: None, + morph_target_binds: Some( + [ + MorphTargetBind { + extensions: None, + extras: None, + index: 38, + node: 1, + weight: 1.0, + }, + ], + ), + override_blink: None, + override_look_at: None, + override_mouth: None, + texture_transform_binds: None, + }, + LookRight: Expression { + extensions: None, + extras: None, + is_binary: Some( + false, + ), + material_color_binds: None, + morph_target_binds: None, + override_blink: None, + override_look_at: None, + override_mouth: None, + texture_transform_binds: None, + }, + Happy: Expression { + extensions: None, + extras: None, + is_binary: Some( + false, + ), + material_color_binds: None, + morph_target_binds: Some( + [ + MorphTargetBind { + extensions: None, + extras: None, + index: 3, + node: 1, + weight: 1.0, + }, + ], + ), + override_blink: Some( + Blend, + ), + override_look_at: None, + override_mouth: Some( + Blend, + ), + texture_transform_binds: None, + }, + Blink: Expression { + extensions: None, + extras: None, + is_binary: Some( + false, + ), + material_color_binds: None, + morph_target_binds: Some( + [ + MorphTargetBind { + extensions: None, + extras: None, + index: 12, + node: 1, + weight: 1.0, + }, + ], + ), + override_blink: None, + override_look_at: None, + override_mouth: None, + texture_transform_binds: None, + }, + Surprised: Expression { + extensions: None, + extras: None, + is_binary: Some( + false, + ), + material_color_binds: None, + morph_target_binds: Some( + [ + MorphTargetBind { + extensions: None, + extras: None, + index: 5, + node: 1, + weight: 1.0, + }, + ], + ), + override_blink: None, + override_look_at: None, + override_mouth: Some( + Blend, + ), + texture_transform_binds: None, + }, + Oh: Expression { + extensions: None, + extras: None, + is_binary: Some( + false, + ), + material_color_binds: None, + morph_target_binds: Some( + [ + MorphTargetBind { + extensions: None, + extras: None, + index: 40, + node: 1, + weight: 1.0, + }, + ], + ), + override_blink: None, + override_look_at: None, + override_mouth: None, + texture_transform_binds: None, + }, + LookLeft: Expression { + extensions: None, + extras: None, + is_binary: Some( + false, + ), + material_color_binds: None, + morph_target_binds: None, + override_blink: None, + override_look_at: None, + override_mouth: None, + texture_transform_binds: None, + }, + Ee: Expression { + extensions: None, + extras: None, + is_binary: Some( + false, + ), + material_color_binds: None, + morph_target_binds: Some( + [ + MorphTargetBind { + extensions: None, + extras: None, + index: 39, + node: 1, + weight: 1.0, + }, + ], + ), + override_blink: None, + override_look_at: None, + override_mouth: None, + texture_transform_binds: None, + }, + Angry: Expression { + extensions: None, + extras: None, + is_binary: Some( + false, + ), + material_color_binds: None, + morph_target_binds: Some( + [ + MorphTargetBind { + extensions: None, + extras: None, + index: 1, + node: 1, + weight: 1.0, + }, + ], + ), + override_blink: None, + override_look_at: None, + override_mouth: None, + texture_transform_binds: None, + }, + Sad: Expression { + extensions: None, + extras: None, + is_binary: Some( + false, + ), + material_color_binds: None, + morph_target_binds: Some( + [ + MorphTargetBind { + extensions: None, + extras: None, + index: 4, + node: 1, + weight: 1.0, + }, + ], + ), + override_blink: None, + override_look_at: None, + override_mouth: Some( + Blend, + ), + texture_transform_binds: None, + }, + Neutral: Expression { + extensions: None, + extras: None, + is_binary: Some( + false, + ), + material_color_binds: None, + morph_target_binds: Some( + [ + MorphTargetBind { + extensions: None, + extras: None, + index: 0, + node: 1, + weight: 1.0, + }, + ], + ), + override_blink: None, + override_look_at: None, + override_mouth: None, + texture_transform_binds: None, + }, + LookDown: Expression { + extensions: None, + extras: None, + is_binary: Some( + false, + ), + material_color_binds: None, + morph_target_binds: None, + override_blink: None, + override_look_at: None, + override_mouth: None, + texture_transform_binds: None, + }, + BlinkRight: Expression { + extensions: None, + extras: None, + is_binary: Some( + false, + ), + material_color_binds: None, + morph_target_binds: Some( + [ + MorphTargetBind { + extensions: None, + extras: None, + index: 14, + node: 1, + weight: 1.0, + }, + ], + ), + override_blink: None, + override_look_at: None, + override_mouth: None, + texture_transform_binds: None, + }, + }, + ), + ), + }, + ), + extensions: None, + extras: None, + first_person: Some( + FirstPerson { + extensions: None, + extras: None, + mesh_annotations: Some( + [ + MeshAnnotation { + extensions: None, + extras: None, + node: Some( + 0, + ), + mesh_annotation_type: Auto, + }, + MeshAnnotation { + extensions: None, + extras: None, + node: Some( + 1, + ), + mesh_annotation_type: Auto, + }, + MeshAnnotation { + extensions: None, + extras: None, + node: Some( + 2, + ), + mesh_annotation_type: Auto, + }, + ], + ), + }, + ), + humanoid: Humanoid { + extensions: None, + extras: None, + human_bones: HumanBones( + { + Chest: Some( + HumanBone { + extensions: None, + extras: None, + node: Some( + 22, + ), + }, + ), + LeftRingIntermediate: Some( + HumanBone { + extensions: None, + extras: None, + node: Some( + 112, + ), + }, + ), + RightShoulder: Some( + HumanBone { + extensions: None, + extras: None, + node: Some( + 122, + ), + }, + ), + RightLowerArm: Some( + HumanBone { + extensions: None, + extras: None, + node: Some( + 131, + ), + }, + ), + LeftLittleIntermediate: Some( + HumanBone { + extensions: None, + extras: None, + node: Some( + 104, + ), + }, + ), + RightFoot: Some( + HumanBone { + extensions: None, + extras: None, + node: Some( + 167, + ), + }, + ), + RightThumbProximal: Some( + HumanBone { + extensions: None, + extras: None, + node: Some( + 150, + ), + }, + ), + LeftThumbDistal: Some( + HumanBone { + extensions: None, + extras: None, + node: Some( + 117, + ), + }, + ), + RightMiddleIntermediate: Some( + HumanBone { + extensions: None, + extras: None, + node: Some( + 142, + ), + }, + ), + LeftLowerLeg: Some( + HumanBone { + extensions: None, + extras: None, + node: Some( + 161, + ), + }, + ), + RightIndexIntermediate: Some( + HumanBone { + extensions: None, + extras: None, + node: Some( + 134, + ), + }, + ), + LeftHand: Some( + HumanBone { + extensions: None, + extras: None, + node: Some( + 98, + ), + }, + ), + RightUpperLeg: Some( + HumanBone { + extensions: None, + extras: None, + node: Some( + 165, + ), + }, + ), + LeftToes: Some( + HumanBone { + extensions: None, + extras: None, + node: Some( + 163, + ), + }, + ), + LeftMiddleProximal: Some( + HumanBone { + extensions: None, + extras: None, + node: Some( + 107, + ), + }, + ), + RightRingIntermediate: Some( + HumanBone { + extensions: None, + extras: None, + node: Some( + 146, + ), + }, + ), + RightLittleIntermediate: Some( + HumanBone { + extensions: None, + extras: None, + node: Some( + 138, + ), + }, + ), + LeftIndexProximal: Some( + HumanBone { + extensions: None, + extras: None, + node: Some( + 99, + ), + }, + ), + Head: Some( + HumanBone { + extensions: None, + extras: None, + node: Some( + 25, + ), + }, + ), + Neck: Some( + HumanBone { + extensions: None, + extras: None, + node: Some( + 24, + ), + }, + ), + LeftRingProximal: Some( + HumanBone { + extensions: None, + extras: None, + node: Some( + 111, + ), + }, + ), + RightThumbDistal: Some( + HumanBone { + extensions: None, + extras: None, + node: Some( + 151, + ), + }, + ), + RightLowerLeg: Some( + HumanBone { + extensions: None, + extras: None, + node: Some( + 166, + ), + }, + ), + LeftLittleProximal: Some( + HumanBone { + extensions: None, + extras: None, + node: Some( + 103, + ), + }, + ), + LeftEye: Some( + HumanBone { + extensions: None, + extras: None, + node: Some( + 26, + ), + }, + ), + RightHand: Some( + HumanBone { + extensions: None, + extras: None, + node: Some( + 132, + ), + }, + ), + LeftThumbMetacarpal: Some( + HumanBone { + extensions: None, + extras: None, + node: Some( + 115, + ), + }, + ), + RightToes: Some( + HumanBone { + extensions: None, + extras: None, + node: Some( + 168, + ), + }, + ), + RightMiddleProximal: Some( + HumanBone { + extensions: None, + extras: None, + node: Some( + 141, + ), + }, + ), + LeftMiddleDistal: Some( + HumanBone { + extensions: None, + extras: None, + node: Some( + 109, + ), + }, + ), + LeftIndexDistal: Some( + HumanBone { + extensions: None, + extras: None, + node: Some( + 101, + ), + }, + ), + RightIndexProximal: Some( + HumanBone { + extensions: None, + extras: None, + node: Some( + 133, + ), + }, + ), + LeftUpperArm: Some( + HumanBone { + extensions: None, + extras: None, + node: Some( + 96, + ), + }, + ), + Spine: Some( + HumanBone { + extensions: None, + extras: None, + node: Some( + 21, + ), + }, + ), + RightRingProximal: Some( + HumanBone { + extensions: None, + extras: None, + node: Some( + 145, + ), + }, + ), + LeftRingDistal: Some( + HumanBone { + extensions: None, + extras: None, + node: Some( + 113, + ), + }, + ), + LeftLittleDistal: Some( + HumanBone { + extensions: None, + extras: None, + node: Some( + 105, + ), + }, + ), + RightLittleProximal: Some( + HumanBone { + extensions: None, + extras: None, + node: Some( + 137, + ), + }, + ), + RightEye: Some( + HumanBone { + extensions: None, + extras: None, + node: Some( + 27, + ), + }, + ), + Hips: Some( + HumanBone { + extensions: None, + extras: None, + node: Some( + 4, + ), + }, + ), + RightThumbMetacarpal: Some( + HumanBone { + extensions: None, + extras: None, + node: Some( + 149, + ), + }, + ), + LeftShoulder: Some( + HumanBone { + extensions: None, + extras: None, + node: Some( + 88, + ), + }, + ), + LeftLowerArm: Some( + HumanBone { + extensions: None, + extras: None, + node: Some( + 97, + ), + }, + ), + RightMiddleDistal: Some( + HumanBone { + extensions: None, + extras: None, + node: Some( + 143, + ), + }, + ), + RightIndexDistal: Some( + HumanBone { + extensions: None, + extras: None, + node: Some( + 135, + ), + }, + ), + LeftFoot: Some( + HumanBone { + extensions: None, + extras: None, + node: Some( + 162, + ), + }, + ), + RightUpperArm: Some( + HumanBone { + extensions: None, + extras: None, + node: Some( + 130, + ), + }, + ), + LeftThumbProximal: Some( + HumanBone { + extensions: None, + extras: None, + node: Some( + 116, + ), + }, + ), + LeftMiddleIntermediate: Some( + HumanBone { + extensions: None, + extras: None, + node: Some( + 108, + ), + }, + ), + RightRingDistal: Some( + HumanBone { + extensions: None, + extras: None, + node: Some( + 147, + ), + }, + ), + RightLittleDistal: Some( + HumanBone { + extensions: None, + extras: None, + node: Some( + 139, + ), + }, + ), + LeftIndexIntermediate: Some( + HumanBone { + extensions: None, + extras: None, + node: Some( + 100, + ), + }, + ), + UpperChest: Some( + HumanBone { + extensions: None, + extras: None, + node: Some( + 23, + ), + }, + ), + LeftUpperLeg: Some( + HumanBone { + extensions: None, + extras: None, + node: Some( + 160, + ), + }, + ), + }, + ), + }, + look_at: Some( + LookAt { + extensions: None, + extras: None, + offset_from_head_bone: Some( + [ + 0.059, + 0.061, + 0.0, + ], + ), + range_map_horizontal_inner: Some( + LookAtRangeMap { + extensions: None, + extras: None, + input_max_value: Some( + 90.0, + ), + output_scale: Some( + 10.0, + ), + }, + ), + range_map_horizontal_outer: Some( + LookAtRangeMap { + extensions: None, + extras: None, + input_max_value: Some( + 90.0, + ), + output_scale: Some( + 10.0, + ), + }, + ), + range_map_vertical_down: Some( + LookAtRangeMap { + extensions: None, + extras: None, + input_max_value: Some( + 90.0, + ), + output_scale: Some( + 10.0, + ), + }, + ), + range_map_vertical_up: Some( + LookAtRangeMap { + extensions: None, + extras: None, + input_max_value: Some( + 90.0, + ), + output_scale: Some( + 10.0, + ), + }, + ), + look_at_type: Some( + Bone, + ), + }, + ), + meta: Meta { + allow_antisocial_or_hate_usage: Some( + false, + ), + allow_excessively_sexual_usage: Some( + true, + ), + allow_excessively_violent_usage: Some( + true, + ), + allow_political_or_religious_usage: Some( + true, + ), + allow_redistribution: Some( + true, + ), + authors: [ + "pixiv Inc.", + ], + avatar_permission: Some( + Everyone, + ), + commercial_usage: Some( + Corporation, + ), + contact_information: None, + copyright_information: Some( + "(c) 2022 pixiv Inc.", + ), + credit_notation: Some( + Unnecessary, + ), + extensions: None, + extras: None, + license_url: "https://vrm.dev/licenses/1.0/", + modification: Some( + AllowModificationRedistribution, + ), + name: "VRM1_Constraint_Twist_Sample", + other_license_url: None, + references: None, + third_party_licenses: None, + thumbnail_image: Some( + 18, + ), + version: Some( + "v1.0.1", + ), + }, + spec_version: "1.0", +} diff --git a/crates/vrm-spec/tests/test.rs b/crates/vrm-spec/tests/test.rs new file mode 100644 index 0000000..101edae --- /dev/null +++ b/crates/vrm-spec/tests/test.rs @@ -0,0 +1,31 @@ +use vrm_spec::{vrm_0_0, vrmc_spring_bone_1_0, vrmc_vrm_1_0}; + +#[test] +fn test_vrm0() { + let file = include_bytes!("../../../fixtures/AvatarSample_A.vrm"); + let (doc, _, _) = gltf::import_slice(file).expect("ok"); + let extensions = doc.extension_value(vrm_0_0::VRM).expect("exist"); + let vrm: vrm_0_0::VRM0Schema = serde_json::from_value(extensions.to_owned()).expect("ok"); + + insta::assert_debug_snapshot!(vrm); +} + +#[test] +fn test_vrm1() { + let file = include_bytes!("../../../fixtures/VRM1_Constraint_Twist_Sample.vrm"); + let (doc, _, _) = gltf::import_slice(file).expect("ok"); + let value = doc.extension_value(vrmc_vrm_1_0::VRMC_VRM).expect("exist"); + let vrmc_vrm: vrmc_vrm_1_0::VRMCVrmSchema = + serde_json::from_value(value.to_owned()).expect("ok"); + + insta::assert_debug_snapshot!(vrmc_vrm); + + let value = doc + .extension_value(vrmc_spring_bone_1_0::VRMC_SPRING_BONE) + .expect("exist"); + + let vrmc_spring_bone: vrmc_spring_bone_1_0::VrmcSpringBoneSchema = + serde_json::from_value(value.to_owned()).expect("ok"); + + insta::assert_debug_snapshot!(vrmc_spring_bone); +} diff --git a/deny.toml b/deny.toml new file mode 100644 index 0000000..f3b1bfc --- /dev/null +++ b/deny.toml @@ -0,0 +1,235 @@ +# This template contains all of the possible sections and their default values + +# Note that all fields that take a lint level have these possible values: +# * deny - An error will be produced and the check will fail +# * warn - A warning will be produced, but the check will not fail +# * allow - No warning or error will be produced, though in some cases a note +# will be + +# The values provided in this template are the default values that will be used +# when any section or field is not specified in your own configuration + +# Root options + +# The graph table configures how the dependency graph is constructed and thus +# which crates the checks are performed against +[graph] +# If 1 or more target triples (and optionally, target_features) are specified, +# only the specified targets will be checked when running `cargo deny check`. +# This means, if a particular package is only ever used as a target specific +# dependency, such as, for example, the `nix` crate only being used via the +# `target_family = "unix"` configuration, that only having windows targets in +# this list would mean the nix crate, as well as any of its exclusive +# dependencies not shared by any other crates, would be ignored, as the target +# list here is effectively saying which targets you are building for. +targets = [ + # The triple can be any string, but only the target triples built in to + # rustc (as of 1.40) can be checked against actual config expressions + #"x86_64-unknown-linux-musl", + # You can also specify which target_features you promise are enabled for a + # particular target. target_features are currently not validated against + # the actual valid features supported by the target architecture. + #{ triple = "wasm32-unknown-unknown", features = ["atomics"] }, +] +# When creating the dependency graph used as the source of truth when checks are +# executed, this field can be used to prune crates from the graph, removing them +# from the view of cargo-deny. This is an extremely heavy hammer, as if a crate +# is pruned from the graph, all of its dependencies will also be pruned unless +# they are connected to another crate in the graph that hasn't been pruned, +# so it should be used with care. The identifiers are [Package ID Specifications] +# (https://doc.rust-lang.org/cargo/reference/pkgid-spec.html) +#exclude = [] +# If true, metadata will be collected with `--all-features`. Note that this can't +# be toggled off if true, if you want to conditionally enable `--all-features` it +# is recommended to pass `--all-features` on the cmd line instead +all-features = false +# If true, metadata will be collected with `--no-default-features`. The same +# caveat with `all-features` applies +no-default-features = false +# If set, these feature will be enabled when collecting metadata. If `--features` +# is specified on the cmd line they will take precedence over this option. +#features = [] + +# The output table provides options for how/if diagnostics are outputted +[output] +# When outputting inclusion graphs in diagnostics that include features, this +# option can be used to specify the depth at which feature edges will be added. +# This option is included since the graphs can be quite large and the addition +# of features from the crate(s) to all of the graph roots can be far too verbose. +# This option can be overridden via `--feature-depth` on the cmd line +feature-depth = 1 + +# This section is considered when running `cargo deny check advisories` +# More documentation for the advisories section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html +[advisories] +# The path where the advisory databases are cloned/fetched into +#db-path = "$CARGO_HOME/advisory-dbs" +# The url(s) of the advisory databases to use +#db-urls = ["https://github.com/rustsec/advisory-db"] +# A list of advisory IDs to ignore. Note that ignored advisories will still +# output a note when they are encountered. +ignore = [ + #"RUSTSEC-0000-0000", + #{ id = "RUSTSEC-0000-0000", reason = "you can specify a reason the advisory is ignored" }, + #"a-crate-that-is-yanked@0.1.1", # you can also ignore yanked crate versions if you wish + #{ crate = "a-crate-that-is-yanked@0.1.1", reason = "you can specify why you are ignoring the yanked crate" }, +] +# If this is true, then cargo deny will use the git executable to fetch advisory database. +# If this is false, then it uses a built-in git library. +# Setting this to true can be helpful if you have special authentication requirements that cargo-deny does not support. +# See Git Authentication for more information about setting up git authentication. +#git-fetch-with-cli = true + +# This section is considered when running `cargo deny check licenses` +# More documentation for the licenses section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/licenses/cfg.html +[licenses] +# List of explicitly allowed licenses +# See https://spdx.org/licenses/ for list of possible licenses +# [possible values: any SPDX 3.11 short identifier (+ optional exception)]. +allow = [ + "MIT", + "Apache-2.0", + "Unicode-DFS-2016", #"Apache-2.0 WITH LLVM-exception", +] +# The confidence threshold for detecting a license from license text. +# The higher the value, the more closely the license text must be to the +# canonical license text of a valid SPDX license file. +# [possible values: any between 0.0 and 1.0]. +confidence-threshold = 0.8 +# Allow 1 or more licenses on a per-crate basis, so that particular licenses +# aren't accepted for every possible crate as with the normal allow list +exceptions = [ + # Each entry is the crate and version constraint, and its specific allow + # list + #{ allow = ["Zlib"], crate = "adler32" }, +] + +# Some crates don't have (easily) machine readable licensing information, +# adding a clarification entry for it allows you to manually specify the +# licensing information +#[[licenses.clarify]] +# The package spec the clarification applies to +#crate = "ring" +# The SPDX expression for the license requirements of the crate +#expression = "MIT AND ISC AND OpenSSL" +# One or more files in the crate's source used as the "source of truth" for +# the license expression. If the contents match, the clarification will be used +# when running the license check, otherwise the clarification will be ignored +# and the crate will be checked normally, which may produce warnings or errors +# depending on the rest of your configuration +#license-files = [ +# Each entry is a crate relative path, and the (opaque) hash of its contents +#{ path = "LICENSE", hash = 0xbd0eed23 } +#] + +[licenses.private] +# If true, ignores workspace crates that aren't published, or are only +# published to private registries. +# To see how to mark a crate as unpublished (to the official registry), +# visit https://doc.rust-lang.org/cargo/reference/manifest.html#the-publish-field. +ignore = false +# One or more private registries that you might publish crates to, if a crate +# is only published to private registries, and ignore is true, the crate will +# not have its license(s) checked +registries = [ + #"https://sekretz.com/registry +] + +# This section is considered when running `cargo deny check bans`. +# More documentation about the 'bans' section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/bans/cfg.html +[bans] +# Lint level for when multiple versions of the same crate are detected +multiple-versions = "warn" +# Lint level for when a crate version requirement is `*` +wildcards = "allow" +# The graph highlighting used when creating dotgraphs for crates +# with multiple versions +# * lowest-version - The path to the lowest versioned duplicate is highlighted +# * simplest-path - The path to the version with the fewest edges is highlighted +# * all - Both lowest-version and simplest-path are used +highlight = "all" +# The default lint level for `default` features for crates that are members of +# the workspace that is being checked. This can be overridden by allowing/denying +# `default` on a crate-by-crate basis if desired. +workspace-default-features = "allow" +# The default lint level for `default` features for external crates that are not +# members of the workspace. This can be overridden by allowing/denying `default` +# on a crate-by-crate basis if desired. +external-default-features = "allow" +# List of crates that are allowed. Use with care! +allow = [ + #"ansi_term@0.11.0", + #{ crate = "ansi_term@0.11.0", reason = "you can specify a reason it is allowed" }, +] +# List of crates to deny +deny = [ + #"ansi_term@0.11.0", + #{ crate = "ansi_term@0.11.0", reason = "you can specify a reason it is banned" }, + # Wrapper crates can optionally be specified to allow the crate when it + # is a direct dependency of the otherwise banned crate + #{ crate = "ansi_term@0.11.0", wrappers = ["this-crate-directly-depends-on-ansi_term"] }, +] + +# List of features to allow/deny +# Each entry the name of a crate and a version range. If version is +# not specified, all versions will be matched. +#[[bans.features]] +#crate = "reqwest" +# Features to not allow +#deny = ["json"] +# Features to allow +#allow = [ +# "rustls", +# "__rustls", +# "__tls", +# "hyper-rustls", +# "rustls", +# "rustls-pemfile", +# "rustls-tls-webpki-roots", +# "tokio-rustls", +# "webpki-roots", +#] +# If true, the allowed features must exactly match the enabled feature set. If +# this is set there is no point setting `deny` +#exact = true + +# Certain crates/versions that will be skipped when doing duplicate detection. +skip = [ + #"ansi_term@0.11.0", + #{ crate = "ansi_term@0.11.0", reason = "you can specify a reason why it can't be updated/removed" }, +] +# Similarly to `skip` allows you to skip certain crates during duplicate +# detection. Unlike skip, it also includes the entire tree of transitive +# dependencies starting at the specified crate, up to a certain depth, which is +# by default infinite. +skip-tree = [ + #"ansi_term@0.11.0", # will be skipped along with _all_ of its direct and transitive dependencies + #{ crate = "ansi_term@0.11.0", depth = 20 }, +] + +# This section is considered when running `cargo deny check sources`. +# More documentation about the 'sources' section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/sources/cfg.html +[sources] +# Lint level for what to happen when a crate from a crate registry that is not +# in the allow list is encountered +unknown-registry = "warn" +# Lint level for what to happen when a crate from a git repository that is not +# in the allow list is encountered +unknown-git = "warn" +# List of URLs for allowed crate registries. Defaults to the crates.io index +# if not specified. If it is specified but empty, no registries are allowed. +allow-registry = ["https://github.com/rust-lang/crates.io-index"] +# List of URLs for allowed Git repositories +allow-git = [] + +[sources.allow-org] +# 1 or more github.com organizations to allow git sources for +# github = [""] +# 1 or more gitlab.com organizations to allow git sources for +# gitlab = [""] +# 1 or more bitbucket.org organizations to allow git sources for +# bitbucket = [""] diff --git a/examples/vrm-spec-example/Cargo.toml b/examples/vrm-spec-example/Cargo.toml new file mode 100644 index 0000000..fa09e52 --- /dev/null +++ b/examples/vrm-spec-example/Cargo.toml @@ -0,0 +1,14 @@ +[package] +edition.workspace = true +license.workspace = true +name = "vrm-spec-example" +publish = false +rust-version.workspace = true +version = "0.0.0" + +[dependencies] +gltf = {workspace = true, features = ["utils", "extensions"]} +rustc-hash = {workspace = true} +serde = {workspace = true} +serde_json = {workspace = true} +vrm-spec = {path = "../../crates/vrm-spec", features = ["gltf_index", "rustc_hash"]} diff --git a/examples/vrm-spec-example/README.md b/examples/vrm-spec-example/README.md new file mode 100644 index 0000000..9e8cbc3 --- /dev/null +++ b/examples/vrm-spec-example/README.md @@ -0,0 +1,3 @@ +```sh +cargo run --example vrm-spec-example +``` diff --git a/examples/vrm-spec-example/examples/vrm-spec-example.rs b/examples/vrm-spec-example/examples/vrm-spec-example.rs new file mode 100644 index 0000000..cbadf1a --- /dev/null +++ b/examples/vrm-spec-example/examples/vrm-spec-example.rs @@ -0,0 +1,39 @@ +use vrm_spec::vrmc_vrm_1_0::{Meta, VRMCVrmSchema, VRMC_VRM}; + +fn main() { + let file = include_bytes!("../../../fixtures/VRM1_Constraint_Twist_Sample.vrm"); + let (doc, _, _) = gltf::import_slice(file).expect("ok"); + let extensions = doc.extension_value(VRMC_VRM).expect("exist"); + let vrm: VRMCVrmSchema = serde_json::from_value(extensions.to_owned()).expect("ok"); + + let Meta { + name, + authors, + allow_redistribution, + copyright_information, + commercial_usage, + credit_notation, + avatar_permission, + .. + } = vrm.meta; + + println!("Details of {}", name); + println!("VRM spec version: {}", vrm.spec_version); + println!("Authors"); + for author in authors { + println!("- {}", author); + } + println!("Licence"); + println!( + r#"- allow_redistribution: {} +- copyright_information: {} +- commercial_usage: {:?} +- credit_notation: {:?} +- avatar_permission: {:?}"#, + allow_redistribution.unwrap_or(false), + copyright_information.as_deref().unwrap_or("None"), + commercial_usage, + credit_notation, + avatar_permission + ) +} diff --git a/fixtures/AvatarSample_A.vrm b/fixtures/AvatarSample_A.vrm new file mode 100644 index 0000000..2ab43ee Binary files /dev/null and b/fixtures/AvatarSample_A.vrm differ diff --git a/fixtures/README.md b/fixtures/README.md new file mode 100644 index 0000000..8184448 --- /dev/null +++ b/fixtures/README.md @@ -0,0 +1,2 @@ +- `VRM1_Constraint_Twist_Sample.vrm` from https://github.com/vrm-c/vrm-specification/tree/master/samples +- `AvatarSample_A.vrm` from https://hub.vroid.com/en/characters/2843975675147313744/models/5644550979324015604 diff --git a/fixtures/VRM1_Constraint_Twist_Sample.vrm b/fixtures/VRM1_Constraint_Twist_Sample.vrm new file mode 100644 index 0000000..8ee8717 Binary files /dev/null and b/fixtures/VRM1_Constraint_Twist_Sample.vrm differ