├── .github └── workflows │ └── ci.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── README.md ├── crates └── vrm-spec │ ├── Cargo.toml │ ├── README.md │ ├── src │ ├── lib.rs │ ├── serde_utils.rs │ ├── vrm_0_0.rs │ ├── vrmc_materials_mtoon_1_0.rs │ ├── vrmc_spring_bone_1_0.rs │ ├── vrmc_vrm_1_0 │ │ ├── expression_preset_name.rs │ │ ├── human_bone_name.rs │ │ └── mod.rs │ └── vrmc_vrm_animation_1_0.rs │ └── tests │ ├── snapshots │ ├── test__vrm0.snap │ ├── test__vrm1-2.snap │ ├── test__vrm1.snap │ └── test__vrm_animation.snap │ └── test.rs ├── deny.toml ├── examples └── vrm-spec-example │ ├── Cargo.toml │ ├── README.md │ └── examples │ └── vrm-spec-example.rs └── fixtures ├── AvatarSample_A.vrm ├── README.md ├── VRM1_Constraint_Twist_Sample.vrm └── test.vrma /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | push: 4 | branches: [main] 5 | pull_request: 6 | 7 | env: 8 | CARGO_TERM_COLOR: always 9 | RUSTFLAGS: "-Dwarnings" 10 | 11 | jobs: 12 | build-and-test: 13 | runs-on: ubuntu-22.04 14 | strategy: 15 | matrix: 16 | toolchain: 17 | - stable 18 | # - beta 19 | # - nightly 20 | steps: 21 | - uses: actions/checkout@v4 22 | - run: rustup update ${{ matrix.toolchain }} && rustup default ${{ matrix.toolchain }} 23 | - run: cargo build --verbose 24 | - run: cargo test --verbose 25 | 26 | lint: 27 | runs-on: ubuntu-latest 28 | steps: 29 | - uses: actions/checkout@v4 30 | - run: cargo fmt --check 31 | - run: cargo clippy --all-targets --all-features 32 | 33 | cargo-deny: 34 | runs-on: ubuntu-22.04 35 | steps: 36 | - uses: actions/checkout@v3 37 | - uses: EmbarkStudios/cargo-deny-action@v1 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /tmp -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "adler" 7 | version = "1.0.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 10 | 11 | [[package]] 12 | name = "autocfg" 13 | version = "1.3.0" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" 16 | 17 | [[package]] 18 | name = "base64" 19 | version = "0.13.1" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" 22 | 23 | [[package]] 24 | name = "bitflags" 25 | version = "1.3.2" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 28 | 29 | [[package]] 30 | name = "bytemuck" 31 | version = "1.16.0" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "78834c15cb5d5efe3452d58b1e8ba890dd62d21907f867f383358198e56ebca5" 34 | 35 | [[package]] 36 | name = "byteorder" 37 | version = "1.5.0" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 40 | 41 | [[package]] 42 | name = "cfg-if" 43 | version = "1.0.0" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 46 | 47 | [[package]] 48 | name = "console" 49 | version = "0.15.8" 50 | source = "registry+https://github.com/rust-lang/crates.io-index" 51 | checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" 52 | dependencies = [ 53 | "encode_unicode", 54 | "lazy_static", 55 | "libc", 56 | "windows-sys", 57 | ] 58 | 59 | [[package]] 60 | name = "crc32fast" 61 | version = "1.4.2" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" 64 | dependencies = [ 65 | "cfg-if", 66 | ] 67 | 68 | [[package]] 69 | name = "encode_unicode" 70 | version = "0.3.6" 71 | source = "registry+https://github.com/rust-lang/crates.io-index" 72 | checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" 73 | 74 | [[package]] 75 | name = "fdeflate" 76 | version = "0.3.4" 77 | source = "registry+https://github.com/rust-lang/crates.io-index" 78 | checksum = "4f9bfee30e4dedf0ab8b422f03af778d9612b63f502710fc500a334ebe2de645" 79 | dependencies = [ 80 | "simd-adler32", 81 | ] 82 | 83 | [[package]] 84 | name = "flate2" 85 | version = "1.0.30" 86 | source = "registry+https://github.com/rust-lang/crates.io-index" 87 | checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" 88 | dependencies = [ 89 | "crc32fast", 90 | "miniz_oxide", 91 | ] 92 | 93 | [[package]] 94 | name = "gltf" 95 | version = "1.4.1" 96 | source = "registry+https://github.com/rust-lang/crates.io-index" 97 | checksum = "e3ce1918195723ce6ac74e80542c5a96a40c2b26162c1957a5cd70799b8cacf7" 98 | dependencies = [ 99 | "base64", 100 | "byteorder", 101 | "gltf-json", 102 | "image", 103 | "lazy_static", 104 | "serde_json", 105 | "urlencoding", 106 | ] 107 | 108 | [[package]] 109 | name = "gltf-derive" 110 | version = "1.4.1" 111 | source = "registry+https://github.com/rust-lang/crates.io-index" 112 | checksum = "14070e711538afba5d6c807edb74bcb84e5dbb9211a3bf5dea0dfab5b24f4c51" 113 | dependencies = [ 114 | "inflections", 115 | "proc-macro2", 116 | "quote", 117 | "syn", 118 | ] 119 | 120 | [[package]] 121 | name = "gltf-json" 122 | version = "1.4.1" 123 | source = "registry+https://github.com/rust-lang/crates.io-index" 124 | checksum = "e6176f9d60a7eab0a877e8e96548605dedbde9190a7ae1e80bbcc1c9af03ab14" 125 | dependencies = [ 126 | "gltf-derive", 127 | "serde", 128 | "serde_derive", 129 | "serde_json", 130 | ] 131 | 132 | [[package]] 133 | name = "image" 134 | version = "0.25.1" 135 | source = "registry+https://github.com/rust-lang/crates.io-index" 136 | checksum = "fd54d660e773627692c524beaad361aca785a4f9f5730ce91f42aabe5bce3d11" 137 | dependencies = [ 138 | "bytemuck", 139 | "byteorder", 140 | "num-traits", 141 | "png", 142 | "zune-core", 143 | "zune-jpeg", 144 | ] 145 | 146 | [[package]] 147 | name = "inflections" 148 | version = "1.1.1" 149 | source = "registry+https://github.com/rust-lang/crates.io-index" 150 | checksum = "a257582fdcde896fd96463bf2d40eefea0580021c0712a0e2b028b60b47a837a" 151 | 152 | [[package]] 153 | name = "insta" 154 | version = "1.39.0" 155 | source = "registry+https://github.com/rust-lang/crates.io-index" 156 | checksum = "810ae6042d48e2c9e9215043563a58a80b877bc863228a74cf10c49d4620a6f5" 157 | dependencies = [ 158 | "console", 159 | "lazy_static", 160 | "linked-hash-map", 161 | "similar", 162 | ] 163 | 164 | [[package]] 165 | name = "itoa" 166 | version = "1.0.11" 167 | source = "registry+https://github.com/rust-lang/crates.io-index" 168 | checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" 169 | 170 | [[package]] 171 | name = "lazy_static" 172 | version = "1.4.0" 173 | source = "registry+https://github.com/rust-lang/crates.io-index" 174 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 175 | 176 | [[package]] 177 | name = "libc" 178 | version = "0.2.155" 179 | source = "registry+https://github.com/rust-lang/crates.io-index" 180 | checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" 181 | 182 | [[package]] 183 | name = "linked-hash-map" 184 | version = "0.5.6" 185 | source = "registry+https://github.com/rust-lang/crates.io-index" 186 | checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" 187 | 188 | [[package]] 189 | name = "miniz_oxide" 190 | version = "0.7.4" 191 | source = "registry+https://github.com/rust-lang/crates.io-index" 192 | checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" 193 | dependencies = [ 194 | "adler", 195 | "simd-adler32", 196 | ] 197 | 198 | [[package]] 199 | name = "num-traits" 200 | version = "0.2.19" 201 | source = "registry+https://github.com/rust-lang/crates.io-index" 202 | checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 203 | dependencies = [ 204 | "autocfg", 205 | ] 206 | 207 | [[package]] 208 | name = "png" 209 | version = "0.17.13" 210 | source = "registry+https://github.com/rust-lang/crates.io-index" 211 | checksum = "06e4b0d3d1312775e782c86c91a111aa1f910cbb65e1337f9975b5f9a554b5e1" 212 | dependencies = [ 213 | "bitflags", 214 | "crc32fast", 215 | "fdeflate", 216 | "flate2", 217 | "miniz_oxide", 218 | ] 219 | 220 | [[package]] 221 | name = "proc-macro2" 222 | version = "1.0.85" 223 | source = "registry+https://github.com/rust-lang/crates.io-index" 224 | checksum = "22244ce15aa966053a896d1accb3a6e68469b97c7f33f284b99f0d576879fc23" 225 | dependencies = [ 226 | "unicode-ident", 227 | ] 228 | 229 | [[package]] 230 | name = "quote" 231 | version = "1.0.36" 232 | source = "registry+https://github.com/rust-lang/crates.io-index" 233 | checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" 234 | dependencies = [ 235 | "proc-macro2", 236 | ] 237 | 238 | [[package]] 239 | name = "rustc-hash" 240 | version = "2.0.0" 241 | source = "registry+https://github.com/rust-lang/crates.io-index" 242 | checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152" 243 | 244 | [[package]] 245 | name = "ryu" 246 | version = "1.0.18" 247 | source = "registry+https://github.com/rust-lang/crates.io-index" 248 | checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" 249 | 250 | [[package]] 251 | name = "serde" 252 | version = "1.0.203" 253 | source = "registry+https://github.com/rust-lang/crates.io-index" 254 | checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" 255 | dependencies = [ 256 | "serde_derive", 257 | ] 258 | 259 | [[package]] 260 | name = "serde_derive" 261 | version = "1.0.203" 262 | source = "registry+https://github.com/rust-lang/crates.io-index" 263 | checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" 264 | dependencies = [ 265 | "proc-macro2", 266 | "quote", 267 | "syn", 268 | ] 269 | 270 | [[package]] 271 | name = "serde_json" 272 | version = "1.0.117" 273 | source = "registry+https://github.com/rust-lang/crates.io-index" 274 | checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" 275 | dependencies = [ 276 | "itoa", 277 | "ryu", 278 | "serde", 279 | ] 280 | 281 | [[package]] 282 | name = "simd-adler32" 283 | version = "0.3.7" 284 | source = "registry+https://github.com/rust-lang/crates.io-index" 285 | checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" 286 | 287 | [[package]] 288 | name = "similar" 289 | version = "2.5.0" 290 | source = "registry+https://github.com/rust-lang/crates.io-index" 291 | checksum = "fa42c91313f1d05da9b26f267f931cf178d4aba455b4c4622dd7355eb80c6640" 292 | 293 | [[package]] 294 | name = "syn" 295 | version = "2.0.66" 296 | source = "registry+https://github.com/rust-lang/crates.io-index" 297 | checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5" 298 | dependencies = [ 299 | "proc-macro2", 300 | "quote", 301 | "unicode-ident", 302 | ] 303 | 304 | [[package]] 305 | name = "unicode-ident" 306 | version = "1.0.12" 307 | source = "registry+https://github.com/rust-lang/crates.io-index" 308 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 309 | 310 | [[package]] 311 | name = "urlencoding" 312 | version = "2.1.3" 313 | source = "registry+https://github.com/rust-lang/crates.io-index" 314 | checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" 315 | 316 | [[package]] 317 | name = "vrm-spec" 318 | version = "0.1.0" 319 | dependencies = [ 320 | "gltf", 321 | "insta", 322 | "rustc-hash", 323 | "serde", 324 | "serde_json", 325 | ] 326 | 327 | [[package]] 328 | name = "vrm-spec-example" 329 | version = "0.0.0" 330 | dependencies = [ 331 | "gltf", 332 | "rustc-hash", 333 | "serde", 334 | "serde_json", 335 | "vrm-spec", 336 | ] 337 | 338 | [[package]] 339 | name = "windows-sys" 340 | version = "0.52.0" 341 | source = "registry+https://github.com/rust-lang/crates.io-index" 342 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 343 | dependencies = [ 344 | "windows-targets", 345 | ] 346 | 347 | [[package]] 348 | name = "windows-targets" 349 | version = "0.52.5" 350 | source = "registry+https://github.com/rust-lang/crates.io-index" 351 | checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" 352 | dependencies = [ 353 | "windows_aarch64_gnullvm", 354 | "windows_aarch64_msvc", 355 | "windows_i686_gnu", 356 | "windows_i686_gnullvm", 357 | "windows_i686_msvc", 358 | "windows_x86_64_gnu", 359 | "windows_x86_64_gnullvm", 360 | "windows_x86_64_msvc", 361 | ] 362 | 363 | [[package]] 364 | name = "windows_aarch64_gnullvm" 365 | version = "0.52.5" 366 | source = "registry+https://github.com/rust-lang/crates.io-index" 367 | checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" 368 | 369 | [[package]] 370 | name = "windows_aarch64_msvc" 371 | version = "0.52.5" 372 | source = "registry+https://github.com/rust-lang/crates.io-index" 373 | checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" 374 | 375 | [[package]] 376 | name = "windows_i686_gnu" 377 | version = "0.52.5" 378 | source = "registry+https://github.com/rust-lang/crates.io-index" 379 | checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" 380 | 381 | [[package]] 382 | name = "windows_i686_gnullvm" 383 | version = "0.52.5" 384 | source = "registry+https://github.com/rust-lang/crates.io-index" 385 | checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" 386 | 387 | [[package]] 388 | name = "windows_i686_msvc" 389 | version = "0.52.5" 390 | source = "registry+https://github.com/rust-lang/crates.io-index" 391 | checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" 392 | 393 | [[package]] 394 | name = "windows_x86_64_gnu" 395 | version = "0.52.5" 396 | source = "registry+https://github.com/rust-lang/crates.io-index" 397 | checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" 398 | 399 | [[package]] 400 | name = "windows_x86_64_gnullvm" 401 | version = "0.52.5" 402 | source = "registry+https://github.com/rust-lang/crates.io-index" 403 | checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" 404 | 405 | [[package]] 406 | name = "windows_x86_64_msvc" 407 | version = "0.52.5" 408 | source = "registry+https://github.com/rust-lang/crates.io-index" 409 | checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" 410 | 411 | [[package]] 412 | name = "zune-core" 413 | version = "0.4.12" 414 | source = "registry+https://github.com/rust-lang/crates.io-index" 415 | checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" 416 | 417 | [[package]] 418 | name = "zune-jpeg" 419 | version = "0.4.11" 420 | source = "registry+https://github.com/rust-lang/crates.io-index" 421 | checksum = "ec866b44a2a1fd6133d363f073ca1b179f438f99e7e5bfb1e33f7181facfe448" 422 | dependencies = [ 423 | "zune-core", 424 | ] 425 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace.package] 2 | authors = ["pixiv"] 3 | edition = "2021" 4 | license = "Apache-2.0" 5 | readme = "README.md" 6 | rust-version = "1.68" 7 | 8 | [workspace] 9 | members = [ 10 | "crates/*", 11 | "examples/*", 12 | ] 13 | # https://github.com/rust-lang/cargo/issues/10112 14 | resolver = "2" 15 | 16 | [workspace.dependencies] 17 | gltf = {version = "1", features = ["utils", "extensions", "extras"]} 18 | insta = "=1.39.0" 19 | rustc-hash = "< 3" 20 | serde = {version = "1.0", features = ["derive"]} 21 | serde_json = "1.0" 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vrm-utils-rs 2 | 3 | Utilities for handling the [VRM](https://vrm.dev) format in rust. 4 | -------------------------------------------------------------------------------- /crates/vrm-spec/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["pixiv"] 3 | description = "VRM data structures" 4 | documentation = "https://docs.rs/vrm-spec" 5 | edition.workspace = true 6 | license = "Apache-2.0" 7 | name = "vrm-spec" 8 | readme = "README.md" 9 | repository = "https://github.com/pixiv/vrm-utils-rs/tree/main/crates/vrm-spec" 10 | rust-version.workspace = true 11 | version = "0.1.0" 12 | 13 | [dependencies] 14 | gltf = {workspace = true, features = ["utils", "extensions"], optional = true} 15 | rustc-hash = {workspace = true, optional = true} 16 | serde = {workspace = true} 17 | serde_json = {workspace = true} 18 | 19 | [dev-dependencies] 20 | insta = {workspace = true} 21 | 22 | [features] 23 | default = ["rustc_hash", "gltf_index"] 24 | gltf_index = ["dep:gltf"] 25 | rustc_hash = ["dep:rustc-hash"] 26 | -------------------------------------------------------------------------------- /crates/vrm-spec/README.md: -------------------------------------------------------------------------------- 1 | # vrm-spec 2 | 3 | Data structures for the [VRM](https://vrm.dev) Format. 4 | 5 | Schema definitions: 6 | 7 | ## Example 8 | 9 | ```rust 10 | use vrm_spec::vrmc_vrm_1_0::{VRMCVrmSchema, VRMC_VRM}; 11 | 12 | let file = include_bytes!("../../../fixtures/VRM1_Constraint_Twist_Sample.vrm"); 13 | let (doc, _, _) = gltf::import_slice(file).expect("ok"); 14 | let value = doc.extension_value(VRMC_VRM).expect("exist"); 15 | let vrmc_vrm: VRMCVrmSchema = serde_json::from_value(value.to_owned()).expect("ok"); 16 | // do something with vrm 17 | ``` 18 | -------------------------------------------------------------------------------- /crates/vrm-spec/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # vrm-spec 2 | //! 3 | //! Data structures for the [VRM](https://vrm.dev) Format. 4 | //! 5 | //! Schema definitions: 6 | //! 7 | //! ## Example 8 | //! 9 | //! ```rust 10 | //! use vrm_spec::vrmc_vrm_1_0::{VRMCVrmSchema, VRMC_VRM}; 11 | //! 12 | //! let file = include_bytes!("../../../fixtures/VRM1_Constraint_Twist_Sample.vrm"); 13 | //! let (doc, _, _) = gltf::import_slice(file).expect("ok"); 14 | //! let value = doc.extension_value(VRMC_VRM).expect("exist"); 15 | //! let vrmc_vrm: VRMCVrmSchema = serde_json::from_value(value.to_owned()).expect("ok"); 16 | //! 17 | //! // do something with vrm 18 | //! ``` 19 | 20 | mod serde_utils; 21 | pub mod vrm_0_0; 22 | pub mod vrmc_materials_mtoon_1_0; 23 | pub mod vrmc_spring_bone_1_0; 24 | pub mod vrmc_vrm_1_0; 25 | pub mod vrmc_vrm_animation_1_0; 26 | -------------------------------------------------------------------------------- /crates/vrm-spec/src/serde_utils.rs: -------------------------------------------------------------------------------- 1 | //! This mod handles common issues when using VRMs. 2 | 3 | #[cfg(feature = "rustc_hash")] 4 | use rustc_hash::FxHashMap as HashMap; 5 | use serde::{Deserialize, Deserializer}; 6 | #[cfg(not(feature = "rustc_hash"))] 7 | use std::collections::HashMap; 8 | 9 | #[cfg(feature = "gltf_index")] 10 | use gltf::json::Index; 11 | 12 | // NOTE: Unity puts -1 in some indexes but those are not valid as glTF index. 13 | // Treat minus values as None 14 | #[cfg(feature = "gltf_index")] 15 | pub(crate) fn deserialize_option_index<'de, D, T>( 16 | deserializer: D, 17 | ) -> Result>, D::Error> 18 | where 19 | D: Deserializer<'de>, 20 | { 21 | let n = i64::deserialize(deserializer)?; 22 | Ok(if n >= 0 { 23 | Some(Index::new(n as u32)) 24 | } else { 25 | None 26 | }) 27 | } 28 | 29 | // NOTE: Unity puts -1 in some indexes but those are not valid as glTF index. 30 | // Treat minus values as None 31 | #[cfg(feature = "gltf_index")] 32 | pub(crate) fn deserialize_option_map_index<'de, D, T>( 33 | deserializer: D, 34 | ) -> Result>>, D::Error> 35 | where 36 | D: Deserializer<'de>, 37 | { 38 | let map = HashMap::::deserialize(deserializer)?; 39 | Ok(Some(HashMap::>::from_iter( 40 | map.into_iter().filter_map(|(k, v)| { 41 | if v >= 0 { 42 | Some((k, Index::::new(v as u32))) 43 | } else { 44 | None 45 | } 46 | }), 47 | ))) 48 | } 49 | 50 | // NOTE: tmp fix for some VRMs that have float_properties value set to null 51 | pub(crate) fn deserialize_option_map_and_skip_nullable<'de, D, K, V>( 52 | deserializer: D, 53 | ) -> Result>, D::Error> 54 | where 55 | K: Eq + std::hash::Hash + Deserialize<'de>, 56 | V: Deserialize<'de>, 57 | D: Deserializer<'de>, 58 | { 59 | let map = HashMap::>::deserialize(deserializer)?; 60 | Ok(Some(HashMap::::from_iter( 61 | map.into_iter().filter_map(|(k, v)| v.map(|v| (k, v))), 62 | ))) 63 | } 64 | -------------------------------------------------------------------------------- /crates/vrm-spec/src/vrm_0_0.rs: -------------------------------------------------------------------------------- 1 | //! Data structures for the [`VRM`](https://github.com/vrm-c/vrm-specification/tree/master/specification/0.0) 0.0 glTF Extension. 2 | 3 | /// VRM extension name 4 | pub const VRM: &str = "VRM"; 5 | 6 | #[cfg(feature = "rustc_hash")] 7 | use rustc_hash::FxHashMap as HashMap; 8 | use serde::{Deserialize, Serialize}; 9 | #[cfg(not(feature = "rustc_hash"))] 10 | use std::collections::HashMap; 11 | 12 | use crate::serde_utils::{ 13 | deserialize_option_index, deserialize_option_map_and_skip_nullable, 14 | deserialize_option_map_index, 15 | }; 16 | 17 | /// VRM extension is for 3d humanoid avatars (and models) in VR applications. 18 | #[derive(Debug, Clone, Serialize, Deserialize)] 19 | #[serde(rename_all = "camelCase")] 20 | pub struct VRM0Schema { 21 | #[serde(skip_serializing_if = "Option::is_none")] 22 | pub blend_shape_master: Option, 23 | 24 | /// Version of exporter that vrm created. UniVRM-0.46 25 | #[serde(skip_serializing_if = "Option::is_none")] 26 | pub exporter_version: Option, 27 | 28 | #[serde(skip_serializing_if = "Option::is_none")] 29 | pub first_person: Option, 30 | 31 | #[serde(skip_serializing_if = "Option::is_none")] 32 | pub humanoid: Option, 33 | 34 | #[serde(skip_serializing_if = "Option::is_none")] 35 | pub material_properties: Option>, 36 | 37 | #[serde(skip_serializing_if = "Option::is_none")] 38 | pub meta: Option, 39 | 40 | #[serde(skip_serializing_if = "Option::is_none")] 41 | pub secondary_animation: Option, 42 | 43 | /// Version of VRM specification. 0.0 44 | #[serde(skip_serializing_if = "Option::is_none")] 45 | pub spec_version: Option, 46 | } 47 | 48 | /// BlendShapeAvatar of UniVRM 49 | #[derive(Debug, Clone, Serialize, Deserialize)] 50 | #[serde(rename_all = "camelCase")] 51 | pub struct VRMBlendShape { 52 | #[serde(skip_serializing_if = "Option::is_none")] 53 | pub blend_shape_groups: Option>, 54 | } 55 | 56 | #[derive(Debug, Clone, Serialize, Deserialize)] 57 | #[serde(rename_all = "camelCase")] 58 | pub struct VRMBlendShapeGroup { 59 | /// Low level blendshape references. 60 | #[serde(skip_serializing_if = "Option::is_none")] 61 | pub binds: Option>, 62 | 63 | /// 0 or 1. Do not allow an intermediate value. Value should rounded 64 | #[serde(skip_serializing_if = "Option::is_none")] 65 | pub is_binary: Option, 66 | 67 | /// Material animation references. 68 | #[serde(skip_serializing_if = "Option::is_none")] 69 | pub material_values: Option>, 70 | 71 | /// Expression name 72 | #[serde(skip_serializing_if = "Option::is_none")] 73 | pub name: Option, 74 | 75 | /// Predefined Expression name 76 | #[serde(skip_serializing_if = "Option::is_none")] 77 | pub preset_name: Option, 78 | } 79 | 80 | #[derive(Debug, Clone, Serialize, Deserialize)] 81 | pub struct VRMBlendShapeBind { 82 | #[serde(skip_serializing_if = "Option::is_none")] 83 | pub index: Option, 84 | 85 | #[serde( 86 | default, 87 | skip_serializing_if = "Option::is_none", 88 | deserialize_with = "deserialize_option_index::<_, gltf::json::Mesh>" 89 | )] 90 | #[cfg(feature = "gltf_index")] 91 | pub mesh: Option>, 92 | #[cfg(not(feature = "gltf_index"))] 93 | pub mesh: Option, 94 | 95 | /// SkinnedMeshRenderer.SetBlendShapeWeight 96 | #[serde(skip_serializing_if = "Option::is_none")] 97 | pub weight: Option, 98 | } 99 | 100 | #[derive(Debug, Clone, Serialize, Deserialize)] 101 | #[serde(rename_all = "camelCase")] 102 | pub struct VRMBlendShapeMaterialBind { 103 | #[serde(skip_serializing_if = "Option::is_none")] 104 | pub material_name: Option, 105 | 106 | #[serde(skip_serializing_if = "Option::is_none")] 107 | pub property_name: Option, 108 | 109 | #[serde(skip_serializing_if = "Option::is_none")] 110 | pub target_value: Option>, 111 | } 112 | 113 | #[derive(Debug, Clone, Serialize, Deserialize)] 114 | #[serde(rename_all = "camelCase")] 115 | pub struct VRMFirstPerson { 116 | /// The bone whose rendering should be turned off in first-person view. Usually Head is 117 | /// specified. 118 | #[serde( 119 | default, 120 | skip_serializing_if = "Option::is_none", 121 | deserialize_with = "deserialize_option_index::<_, gltf::json::Node>" 122 | )] 123 | #[cfg(feature = "gltf_index")] 124 | pub first_person_bone: Option>, 125 | #[cfg(not(feature = "gltf_index"))] 126 | pub first_person_bone: Option, 127 | 128 | /// The target position of the VR headset in first-person view. It is assumed that an offset 129 | /// from the head bone to the VR headset is added. 130 | #[serde(skip_serializing_if = "Option::is_none")] 131 | pub first_person_bone_offset: Option, 132 | 133 | #[serde(skip_serializing_if = "Option::is_none")] 134 | pub look_at_horizontal_inner: Option, 135 | 136 | #[serde(skip_serializing_if = "Option::is_none")] 137 | pub look_at_horizontal_outer: Option, 138 | 139 | /// Eye controller mode. 140 | #[serde(skip_serializing_if = "Option::is_none")] 141 | pub look_at_type_name: Option, 142 | 143 | #[serde(skip_serializing_if = "Option::is_none")] 144 | pub look_at_vertical_down: Option, 145 | 146 | #[serde(skip_serializing_if = "Option::is_none")] 147 | pub look_at_vertical_up: Option, 148 | 149 | /// Switch display / undisplay for each mesh in first-person view or the others. 150 | #[serde(skip_serializing_if = "Option::is_none")] 151 | pub mesh_annotations: Option>, 152 | } 153 | 154 | #[derive(Debug, Clone, Serialize, Deserialize)] 155 | /// Vector3 but has optional x,y,z. 156 | /// 157 | /// normally x,y,z should not be optional but the VRM 0.0 spec allows it 158 | pub struct OptionalVector3 { 159 | #[serde(skip_serializing_if = "Option::is_none")] 160 | pub x: Option, 161 | 162 | #[serde(skip_serializing_if = "Option::is_none")] 163 | pub y: Option, 164 | 165 | #[serde(skip_serializing_if = "Option::is_none")] 166 | pub z: Option, 167 | } 168 | 169 | /// The target position of the VR headset in first-person view. It is assumed that an offset 170 | /// from the head bone to the VR headset is added. 171 | pub type FirstPersonBoneOffset = OptionalVector3; 172 | 173 | /// Eye controller setting. 174 | #[derive(Debug, Clone, Serialize, Deserialize)] 175 | #[serde(rename_all = "camelCase")] 176 | pub struct VRMFirstPersonDegreeMap { 177 | /// None linear mapping params. time, value, inTangent, outTangent 178 | #[serde(skip_serializing_if = "Option::is_none")] 179 | pub curve: Option>, 180 | 181 | /// Look at input clamp range degree. 182 | #[serde(skip_serializing_if = "Option::is_none")] 183 | pub x_range: Option, 184 | 185 | /// Look at map range degree from xRange. 186 | #[serde(skip_serializing_if = "Option::is_none")] 187 | pub y_range: Option, 188 | } 189 | 190 | #[derive(Debug, Clone, Serialize, Deserialize)] 191 | #[serde(rename_all = "camelCase")] 192 | pub struct VRMFirstPersonMeshAnnotation { 193 | #[serde(skip_serializing_if = "Option::is_none")] 194 | pub first_person_flag: Option, 195 | 196 | #[serde( 197 | default, 198 | skip_serializing_if = "Option::is_none", 199 | deserialize_with = "deserialize_option_index::<_, gltf::json::Mesh>" 200 | )] 201 | #[cfg(feature = "gltf_index")] 202 | pub mesh: Option>, 203 | #[cfg(not(feature = "gltf_index"))] 204 | pub mesh: Option, 205 | } 206 | 207 | #[derive(Debug, Clone, Serialize, Deserialize)] 208 | #[serde(rename_all = "camelCase")] 209 | pub struct VRMHumanoid { 210 | /// Unity's HumanDescription.armStretch 211 | #[serde(skip_serializing_if = "Option::is_none")] 212 | pub arm_stretch: Option, 213 | 214 | /// Unity's HumanDescription.feetSpacing 215 | #[serde(skip_serializing_if = "Option::is_none")] 216 | pub feet_spacing: Option, 217 | 218 | /// Unity's HumanDescription.hasTranslationDoF 219 | #[serde(skip_serializing_if = "Option::is_none")] 220 | pub has_translation_do_f: Option, 221 | 222 | #[serde(skip_serializing_if = "Option::is_none")] 223 | pub human_bones: Option>, 224 | 225 | /// Unity's HumanDescription.legStretch 226 | #[serde(skip_serializing_if = "Option::is_none")] 227 | pub leg_stretch: Option, 228 | 229 | /// Unity's HumanDescription.lowerArmTwist 230 | #[serde(skip_serializing_if = "Option::is_none")] 231 | pub lower_arm_twist: Option, 232 | 233 | /// Unity's HumanDescription.lowerLegTwist 234 | #[serde(skip_serializing_if = "Option::is_none")] 235 | pub lower_leg_twist: Option, 236 | 237 | /// Unity's HumanDescription.upperArmTwist 238 | #[serde(skip_serializing_if = "Option::is_none")] 239 | pub upper_arm_twist: Option, 240 | 241 | /// Unity's HumanDescription.upperLegTwist 242 | #[serde(skip_serializing_if = "Option::is_none")] 243 | pub upper_leg_twist: Option, 244 | } 245 | 246 | #[derive(Debug, Clone, Serialize, Deserialize)] 247 | #[serde(rename_all = "camelCase")] 248 | pub struct VRMHumanoidBone { 249 | /// Unity's HumanLimit.axisLength 250 | #[serde(skip_serializing_if = "Option::is_none")] 251 | pub axis_length: Option, 252 | 253 | /// Human bone name. 254 | #[serde(skip_serializing_if = "Option::is_none")] 255 | pub bone: Option, 256 | 257 | /// Unity's HumanLimit.center 258 | #[serde(skip_serializing_if = "Option::is_none")] 259 | pub center: Option
, 260 | 261 | /// Unity's HumanLimit.max 262 | #[serde(skip_serializing_if = "Option::is_none")] 263 | pub max: Option, 264 | 265 | /// Unity's HumanLimit.min 266 | #[serde(skip_serializing_if = "Option::is_none")] 267 | pub min: Option, 268 | 269 | /// Reference node index 270 | #[serde( 271 | default, 272 | skip_serializing_if = "Option::is_none", 273 | deserialize_with = "deserialize_option_index::<_, gltf::json::Node>" 274 | )] 275 | #[cfg(feature = "gltf_index")] 276 | pub node: Option>, 277 | #[cfg(not(feature = "gltf_index"))] 278 | pub node: Option, 279 | 280 | /// Unity's HumanLimit.useDefaultValues 281 | #[serde(skip_serializing_if = "Option::is_none")] 282 | pub use_default_values: Option, 283 | } 284 | 285 | /// Unity's HumanLimit.center 286 | pub type Center = OptionalVector3; 287 | 288 | /// Unity's HumanLimit.max 289 | pub type Max = OptionalVector3; 290 | 291 | /// Unity's HumanLimit.min 292 | pub type Min = OptionalVector3; 293 | 294 | #[derive(Debug, Clone, Serialize, Deserialize)] 295 | #[serde(rename_all = "camelCase")] 296 | pub struct VRMMaterial { 297 | #[serde( 298 | skip_serializing_if = "Option::is_none", 299 | deserialize_with = "deserialize_option_map_and_skip_nullable::<_, String, f64>" 300 | )] 301 | pub float_properties: Option>, 302 | 303 | #[serde(skip_serializing_if = "Option::is_none")] 304 | pub keyword_map: Option>, 305 | 306 | #[serde(skip_serializing_if = "Option::is_none")] 307 | pub name: Option, 308 | 309 | #[serde(skip_serializing_if = "Option::is_none")] 310 | pub render_queue: Option, 311 | 312 | /// This contains shader name. VRM/MToon, VRM/UnlitTransparentZWrite, and VRM_USE_GLTFSHADER 313 | /// (and legacy materials as Standard, UniGLTF/UniUnlit, VRM/UnlitTexture, VRM/UnlitCutout, 314 | /// VRM/UnlitTransparent) . If VRM_USE_GLTFSHADER is specified, use same index of gltf's 315 | /// material settings 316 | #[serde(skip_serializing_if = "Option::is_none")] 317 | pub shader: Option, 318 | 319 | #[serde(skip_serializing_if = "Option::is_none")] 320 | pub tag_map: Option>, 321 | 322 | #[serde( 323 | default, 324 | skip_serializing_if = "Option::is_none", 325 | deserialize_with = "deserialize_option_map_index::<_, gltf::json::Texture>" 326 | )] 327 | #[cfg(feature = "gltf_index")] 328 | pub texture_properties: Option>>, 329 | #[cfg(not(feature = "gltf_index"))] 330 | pub texture_properties: Option>, 331 | 332 | #[serde(skip_serializing_if = "Option::is_none")] 333 | pub vector_properties: Option>>, 334 | } 335 | 336 | #[derive(Debug, Clone, Serialize, Deserialize)] 337 | #[serde(rename_all = "camelCase")] 338 | pub struct VRMMeta { 339 | /// A person who can perform with this avatar 340 | #[serde(skip_serializing_if = "Option::is_none")] 341 | pub allowed_user_name: Option, 342 | 343 | /// Author of VRM model 344 | #[serde(skip_serializing_if = "Option::is_none")] 345 | pub author: Option, 346 | 347 | /// For commercial use 348 | #[serde(skip_serializing_if = "Option::is_none")] 349 | pub commercial_ussage_name: Option, 350 | 351 | /// Contact Information of VRM model author 352 | #[serde(skip_serializing_if = "Option::is_none")] 353 | pub contact_information: Option, 354 | 355 | /// License type 356 | #[serde(skip_serializing_if = "Option::is_none")] 357 | pub license_name: Option, 358 | 359 | /// If “Other” is selected, put the URL link of the license document here. 360 | #[serde(skip_serializing_if = "Option::is_none")] 361 | pub other_license_url: Option, 362 | 363 | /// If there are any conditions not mentioned above, put the URL link of the license document 364 | /// here. 365 | #[serde(skip_serializing_if = "Option::is_none")] 366 | pub other_permission_url: Option, 367 | 368 | /// Reference of VRM model 369 | #[serde(skip_serializing_if = "Option::is_none")] 370 | pub reference: Option, 371 | 372 | /// Permission to perform sexual acts with this avatar 373 | #[serde(skip_serializing_if = "Option::is_none")] 374 | pub sexual_ussage_name: Option, 375 | 376 | /// Thumbnail of VRM model 377 | #[serde( 378 | default, 379 | skip_serializing_if = "Option::is_none", 380 | deserialize_with = "deserialize_option_index::<_, gltf::json::Texture>" 381 | )] 382 | #[cfg(feature = "gltf_index")] 383 | pub texture: Option>, 384 | #[cfg(not(feature = "gltf_index"))] 385 | pub texture: Option, 386 | 387 | /// Title of VRM model 388 | #[serde(skip_serializing_if = "Option::is_none")] 389 | pub title: Option, 390 | 391 | /// Version of VRM model 392 | #[serde(skip_serializing_if = "Option::is_none")] 393 | pub version: Option, 394 | 395 | /// Permission to perform violent acts with this avatar 396 | #[serde(skip_serializing_if = "Option::is_none")] 397 | pub violent_ussage_name: Option, 398 | } 399 | 400 | /// The setting of automatic animation of string-like objects such as tails and hairs. 401 | #[derive(Debug, Clone, Serialize, Deserialize)] 402 | #[serde(rename_all = "camelCase")] 403 | pub struct VRMSecondaryAnimation { 404 | #[serde(skip_serializing_if = "Option::is_none")] 405 | pub bone_groups: Option>, 406 | 407 | #[serde(skip_serializing_if = "Option::is_none")] 408 | pub collider_groups: Option>, 409 | } 410 | 411 | #[derive(Debug, Clone, Serialize, Deserialize)] 412 | #[serde(rename_all = "camelCase")] 413 | pub struct VRMSecondaryAnimationSpring { 414 | /// Specify the node index of the root bone of the swaying object. 415 | #[serde(skip_serializing_if = "Option::is_none")] 416 | #[cfg(feature = "gltf_index")] 417 | pub bones: Option>>, 418 | #[cfg(not(feature = "gltf_index"))] 419 | pub bones: Option>, 420 | 421 | /// The reference point of a swaying object can be set at any location except the origin. 422 | /// When implementing UI moving with warp, the parent node to move with warp can be specified 423 | /// if you don't want to make the object swaying with warp movement. 424 | #[serde( 425 | default, 426 | skip_serializing_if = "Option::is_none", 427 | deserialize_with = "deserialize_option_index::<_, gltf::json::Node>" 428 | )] 429 | #[cfg(feature = "gltf_index")] 430 | pub center: Option>, 431 | #[cfg(not(feature = "gltf_index"))] 432 | pub center: Option, 433 | 434 | /// Specify the index of the collider group for collisions with swaying objects. 435 | #[serde(skip_serializing_if = "Option::is_none")] 436 | pub collider_groups: Option>, 437 | 438 | /// Annotation comment 439 | #[serde(skip_serializing_if = "Option::is_none")] 440 | pub comment: Option, 441 | 442 | /// The resistance (deceleration) of automatic animation. 443 | #[serde(skip_serializing_if = "Option::is_none")] 444 | pub drag_force: Option, 445 | 446 | /// The direction of gravity. Set (0, -1, 0) for simulating the gravity. Set (1, 0, 0) for 447 | /// simulating the wind. 448 | #[serde(skip_serializing_if = "Option::is_none")] 449 | pub gravity_dir: Option, 450 | 451 | /// The strength of gravity. 452 | #[serde(skip_serializing_if = "Option::is_none")] 453 | pub gravity_power: Option, 454 | 455 | /// The radius of the sphere used for the collision detection with colliders. 456 | #[serde(skip_serializing_if = "Option::is_none")] 457 | pub hit_radius: Option, 458 | 459 | /// The resilience of the swaying object (the power of returning to the initial pose). 460 | #[serde(skip_serializing_if = "Option::is_none")] 461 | pub stiffiness: Option, 462 | } 463 | 464 | /// The direction of gravity. Set (0, -1, 0) for simulating the gravity. Set (1, 0, 0) for 465 | /// simulating the wind. 466 | pub type GravityDir = OptionalVector3; 467 | 468 | #[derive(Debug, Clone, Serialize, Deserialize)] 469 | pub struct VRMSecondaryAnimationColliderGroup { 470 | #[serde(skip_serializing_if = "Option::is_none")] 471 | pub colliders: Option>, 472 | 473 | /// The node of the collider group for setting up collision detections. 474 | #[serde( 475 | default, 476 | skip_serializing_if = "Option::is_none", 477 | deserialize_with = "deserialize_option_index::<_, gltf::json::Node>" 478 | )] 479 | pub node: Option>, 480 | #[cfg(not(feature = "gltf_index"))] 481 | pub node: Option, 482 | } 483 | 484 | #[derive(Debug, Clone, Serialize, Deserialize)] 485 | pub struct Collider { 486 | /// The local coordinate from the node of the collider group in *left-handed* Y-up coordinate. 487 | #[serde(skip_serializing_if = "Option::is_none")] 488 | pub offset: Option, 489 | 490 | /// The radius of the collider. 491 | #[serde(skip_serializing_if = "Option::is_none")] 492 | pub radius: Option, 493 | } 494 | 495 | /// The local coordinate from the node of the collider group in *left-handed* Y-up coordinate. 496 | pub type Offset = OptionalVector3; 497 | 498 | /// Predefined Expression name. 499 | #[derive(Debug, Clone, Copy, Hash, Serialize, Deserialize)] 500 | #[serde(rename_all = "snake_case")] 501 | pub enum PresetName { 502 | A, 503 | Angry, 504 | Blink, 505 | BlinkL, 506 | BlinkR, 507 | E, 508 | Fun, 509 | I, 510 | Joy, 511 | Lookdown, 512 | Lookleft, 513 | Lookright, 514 | Lookup, 515 | Neutral, 516 | O, 517 | Sorrow, 518 | U, 519 | Unknown, 520 | } 521 | 522 | /// Eye controller mode. 523 | #[derive(Debug, Clone, Copy, Hash, Serialize, Deserialize)] 524 | pub enum LookAtTypeName { 525 | BlendShape, 526 | Bone, 527 | } 528 | 529 | /// Human bone name. 530 | #[derive(Debug, Clone, Copy, Hash, Serialize, Deserialize)] 531 | #[serde(rename_all = "camelCase")] 532 | pub enum Bone { 533 | Chest, 534 | Head, 535 | Hips, 536 | Jaw, 537 | LeftEye, 538 | LeftFoot, 539 | LeftHand, 540 | LeftIndexDistal, 541 | LeftIndexIntermediate, 542 | LeftIndexProximal, 543 | LeftLittleDistal, 544 | LeftLittleIntermediate, 545 | LeftLittleProximal, 546 | LeftLowerArm, 547 | LeftLowerLeg, 548 | LeftMiddleDistal, 549 | LeftMiddleIntermediate, 550 | LeftMiddleProximal, 551 | LeftRingDistal, 552 | LeftRingIntermediate, 553 | LeftRingProximal, 554 | LeftShoulder, 555 | LeftThumbDistal, 556 | LeftThumbIntermediate, 557 | LeftThumbProximal, 558 | LeftToes, 559 | LeftUpperArm, 560 | LeftUpperLeg, 561 | Neck, 562 | RightEye, 563 | RightFoot, 564 | RightHand, 565 | RightIndexDistal, 566 | RightIndexIntermediate, 567 | RightIndexProximal, 568 | RightLittleDistal, 569 | RightLittleIntermediate, 570 | RightLittleProximal, 571 | RightLowerArm, 572 | RightLowerLeg, 573 | RightMiddleDistal, 574 | RightMiddleIntermediate, 575 | RightMiddleProximal, 576 | RightRingDistal, 577 | RightRingIntermediate, 578 | RightRingProximal, 579 | RightShoulder, 580 | RightThumbDistal, 581 | RightThumbIntermediate, 582 | RightThumbProximal, 583 | RightToes, 584 | RightUpperArm, 585 | RightUpperLeg, 586 | Spine, 587 | UpperChest, 588 | } 589 | 590 | /// A person who can perform with this avatar. 591 | #[derive(Debug, Clone, Copy, Hash, Serialize, Deserialize)] 592 | pub enum AllowedUserName { 593 | Everyone, 594 | ExplicitlyLicensedPerson, 595 | OnlyAuthor, 596 | } 597 | 598 | /// Usage Permission. 599 | /// 600 | /// NOTE: the typo is intended following the spec. 601 | #[derive(Debug, Clone, Copy, Hash, Serialize, Deserialize)] 602 | pub enum UssageName { 603 | Allow, 604 | Disallow, 605 | } 606 | 607 | /// License type. 608 | #[derive(Debug, Clone, Copy, Hash, Serialize, Deserialize)] 609 | pub enum LicenseName { 610 | #[serde(rename = "CC0")] 611 | Cc0, 612 | 613 | #[serde(rename = "CC_BY")] 614 | CcBy, 615 | 616 | #[serde(rename = "CC_BY_NC")] 617 | CcByNc, 618 | 619 | #[serde(rename = "CC_BY_NC_ND")] 620 | CcByNcNd, 621 | 622 | #[serde(rename = "CC_BY_NC_SA")] 623 | CcByNcSa, 624 | 625 | #[serde(rename = "CC_BY_ND")] 626 | CcByNd, 627 | 628 | #[serde(rename = "CC_BY_SA")] 629 | CcBySa, 630 | 631 | #[serde(rename = "Redistribution_Prohibited")] 632 | RedistributionProhibited, 633 | 634 | Other, 635 | } 636 | -------------------------------------------------------------------------------- /crates/vrm-spec/src/vrmc_materials_mtoon_1_0.rs: -------------------------------------------------------------------------------- 1 | //! 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. 2 | 3 | use serde::{Deserialize, Serialize}; 4 | 5 | /// VRMC_materials_mtoon extension name 6 | pub const VRMC_MATERIALS_MTOON: &str = "VRMC_materials_mtoon"; 7 | 8 | #[cfg(feature = "rustc_hash")] 9 | use rustc_hash::FxHashMap as HashMap; 10 | #[cfg(not(feature = "rustc_hash"))] 11 | use std::collections::HashMap; 12 | 13 | #[cfg(feature = "gltf_index")] 14 | use gltf::json::texture::Info as TextureInfo; 15 | 16 | #[derive(Debug, Clone, Serialize, Deserialize)] 17 | #[serde(rename_all = "camelCase")] 18 | pub struct VrmcMaterialsMtoonSchema { 19 | #[serde(skip_serializing_if = "Option::is_none")] 20 | pub extensions: Option>>>, 21 | 22 | #[serde(skip_serializing_if = "Option::is_none")] 23 | pub extras: Option, 24 | 25 | #[serde(skip_serializing_if = "Option::is_none")] 26 | pub gi_equalization_factor: Option, 27 | 28 | #[serde(skip_serializing_if = "Option::is_none")] 29 | pub matcap_factor: Option<[f64; 3]>, 30 | 31 | #[serde(skip_serializing_if = "Option::is_none")] 32 | pub matcap_texture: Option, 33 | 34 | #[serde(skip_serializing_if = "Option::is_none")] 35 | pub outline_color_factor: Option<[f64; 3]>, 36 | 37 | #[serde(skip_serializing_if = "Option::is_none")] 38 | pub outline_lighting_mix_factor: Option, 39 | 40 | #[serde(skip_serializing_if = "Option::is_none")] 41 | pub outline_width_factor: Option, 42 | 43 | #[serde(skip_serializing_if = "Option::is_none")] 44 | pub outline_width_mode: Option, 45 | 46 | #[serde(skip_serializing_if = "Option::is_none")] 47 | pub outline_width_multiply_texture: Option, 48 | 49 | #[serde(skip_serializing_if = "Option::is_none")] 50 | pub parametric_rim_color_factor: Option<[f64; 3]>, 51 | 52 | #[serde(skip_serializing_if = "Option::is_none")] 53 | pub parametric_rim_fresnel_power_factor: Option, 54 | 55 | #[serde(skip_serializing_if = "Option::is_none")] 56 | pub parametric_rim_lift_factor: Option, 57 | 58 | #[serde(skip_serializing_if = "Option::is_none")] 59 | pub render_queue_offset_number: Option, 60 | 61 | #[serde(skip_serializing_if = "Option::is_none")] 62 | pub rim_lighting_mix_factor: Option, 63 | 64 | #[serde(skip_serializing_if = "Option::is_none")] 65 | pub rim_multiply_texture: Option, 66 | 67 | #[serde(skip_serializing_if = "Option::is_none")] 68 | pub shade_color_factor: Option<[f64; 3]>, 69 | 70 | #[serde(skip_serializing_if = "Option::is_none")] 71 | pub shade_multiply_texture: Option, 72 | 73 | #[serde(skip_serializing_if = "Option::is_none")] 74 | pub shading_shift_factor: Option, 75 | 76 | #[serde(skip_serializing_if = "Option::is_none")] 77 | pub shading_shift_texture: Option, 78 | 79 | #[serde(skip_serializing_if = "Option::is_none")] 80 | pub shading_toony_factor: Option, 81 | 82 | /// Specification version of VRMC_materials_mtoon 83 | pub spec_version: String, 84 | 85 | /// enable depth buffer when `alphaMode` is `BLEND` 86 | #[serde(skip_serializing_if = "Option::is_none")] 87 | pub transparent_with_z_write: Option, 88 | 89 | #[serde(skip_serializing_if = "Option::is_none")] 90 | pub uv_animation_mask_texture: Option, 91 | 92 | #[serde(skip_serializing_if = "Option::is_none")] 93 | pub uv_animation_rotation_speed_factor: Option, 94 | 95 | #[serde(skip_serializing_if = "Option::is_none")] 96 | pub uv_animation_scroll_x_speed_factor: Option, 97 | 98 | #[serde(skip_serializing_if = "Option::is_none")] 99 | pub uv_animation_scroll_y_speed_factor: Option, 100 | } 101 | 102 | /// Reference to a texture. 103 | #[cfg(not(feature = "gltf_index"))] 104 | #[derive(Debug, Clone, Serialize, Deserialize)] 105 | #[serde(rename_all = "camelCase")] 106 | pub struct TextureInfo { 107 | #[serde(skip_serializing_if = "Option::is_none")] 108 | pub extensions: Option>>>, 109 | 110 | #[serde(skip_serializing_if = "Option::is_none")] 111 | pub extras: Option, 112 | 113 | pub index: usize, 114 | 115 | /// The set index of texture's TEXCOORD attribute used for texture coordinate mapping. 116 | #[cfg(feature = "gltf_index")] 117 | #[serde(skip_serializing_if = "Option::is_none")] 118 | pub tex_coord: Option>, 119 | #[cfg(not(feature = "gltf_index"))] 120 | #[serde(skip_serializing_if = "Option::is_none")] 121 | pub tex_coord: Option, 122 | } 123 | 124 | /// Reference to a texture. 125 | #[derive(Debug, Clone, Serialize, Deserialize)] 126 | #[serde(rename_all = "camelCase")] 127 | pub struct ShadingShiftTextureInfo { 128 | #[serde(skip_serializing_if = "Option::is_none")] 129 | pub extensions: Option>>>, 130 | 131 | #[serde(skip_serializing_if = "Option::is_none")] 132 | pub extras: Option, 133 | 134 | /// The index of the texture. 135 | #[cfg(feature = "gltf_index")] 136 | pub index: gltf::json::Index, 137 | #[cfg(not(feature = "gltf_index"))] 138 | pub index: usize, 139 | 140 | /// The scalar multiplier applied to the texture. 141 | #[serde(skip_serializing_if = "Option::is_none")] 142 | pub scale: Option, 143 | 144 | /// The set index of texture's TEXCOORD attribute used for texture coordinate mapping. 145 | #[cfg(feature = "gltf_index")] 146 | #[serde(skip_serializing_if = "Option::is_none")] 147 | pub tex_coord: Option>, 148 | #[cfg(not(feature = "gltf_index"))] 149 | #[serde(skip_serializing_if = "Option::is_none")] 150 | pub tex_coord: Option, 151 | } 152 | 153 | /// Outline 154 | #[derive(Debug, Clone, Copy, Hash, Serialize, Deserialize)] 155 | #[serde(rename_all = "camelCase")] 156 | pub enum OutlineWidthMode { 157 | None, 158 | ScreenCoordinates, 159 | WorldCoordinates, 160 | } 161 | -------------------------------------------------------------------------------- /crates/vrm-spec/src/vrmc_spring_bone_1_0.rs: -------------------------------------------------------------------------------- 1 | //! Data structures for the [`VRMC_springBone`](https://github.com/vrm-c/vrm-specification/tree/master/specification/VRMC_springBone-1.0) 1.0 glTF Extension. 2 | 3 | #[cfg(feature = "rustc_hash")] 4 | use rustc_hash::FxHashMap as HashMap; 5 | use serde::{Deserialize, Serialize}; 6 | #[cfg(not(feature = "rustc_hash"))] 7 | use std::collections::HashMap; 8 | 9 | /// VRMC_springBone extension name 10 | pub const VRMC_SPRING_BONE: &str = "VRMC_springBone"; 11 | 12 | /// SpringBone makes objects such as costumes and hair swaying 13 | #[derive(Debug, Clone, Serialize, Deserialize)] 14 | #[serde(rename_all = "camelCase")] 15 | pub struct VrmcSpringBoneSchema { 16 | /// An array of colliderGroups. 17 | #[serde(skip_serializing_if = "Option::is_none")] 18 | pub collider_groups: Option>, 19 | 20 | /// An array of colliders. 21 | #[serde(skip_serializing_if = "Option::is_none")] 22 | pub colliders: Option>, 23 | 24 | #[serde(skip_serializing_if = "Option::is_none")] 25 | pub extensions: Option>>>, 26 | 27 | #[serde(skip_serializing_if = "Option::is_none")] 28 | pub extras: Option, 29 | 30 | /// Specification version of VRMC_springBone 31 | pub spec_version: String, 32 | 33 | /// An array of springs. 34 | #[serde(skip_serializing_if = "Option::is_none")] 35 | pub springs: Option>, 36 | } 37 | 38 | /// collider group definition for SpringBone 39 | #[derive(Debug, Clone, Serialize, Deserialize)] 40 | pub struct ColliderGroup { 41 | /// An array of colliders. 42 | pub colliders: Vec, 43 | 44 | #[serde(skip_serializing_if = "Option::is_none")] 45 | pub extensions: Option>>>, 46 | 47 | #[serde(skip_serializing_if = "Option::is_none")] 48 | pub extras: Option, 49 | 50 | /// Name of the ColliderGroup 51 | #[serde(skip_serializing_if = "Option::is_none")] 52 | pub name: Option, 53 | } 54 | 55 | /// collider definition for SpringBone 56 | #[derive(Debug, Clone, Serialize, Deserialize)] 57 | pub struct Collider { 58 | #[serde(skip_serializing_if = "Option::is_none")] 59 | pub extensions: Option>>>, 60 | 61 | #[serde(skip_serializing_if = "Option::is_none")] 62 | pub extras: Option, 63 | 64 | /// The node index. 65 | #[cfg(feature = "gltf_index")] 66 | pub node: gltf::json::Index, 67 | #[cfg(not(feature = "gltf_index"))] 68 | pub node: usize, 69 | 70 | pub shape: ColliderShape, 71 | } 72 | 73 | /// Shape of collider. Have one of sphere and capsule 74 | #[derive(Debug, Clone, Serialize, Deserialize)] 75 | pub struct ColliderShape { 76 | #[serde(skip_serializing_if = "Option::is_none")] 77 | pub capsule: Option, 78 | 79 | #[serde(skip_serializing_if = "Option::is_none")] 80 | pub extensions: Option>>>, 81 | 82 | #[serde(skip_serializing_if = "Option::is_none")] 83 | pub extras: Option, 84 | 85 | #[serde(skip_serializing_if = "Option::is_none")] 86 | pub sphere: Option, 87 | } 88 | 89 | #[derive(Debug, Clone, Copy, Serialize, Deserialize)] 90 | pub struct ColliderShapeCapsule { 91 | /// The capsule head. vector3 92 | #[serde(skip_serializing_if = "Option::is_none")] 93 | pub offset: Option<[f64; 3]>, 94 | 95 | /// The capsule radius 96 | #[serde(skip_serializing_if = "Option::is_none")] 97 | pub radius: Option, 98 | 99 | /// The capsule tail. vector3 100 | #[serde(skip_serializing_if = "Option::is_none")] 101 | pub tail: Option<[f64; 3]>, 102 | } 103 | 104 | #[derive(Debug, Clone, Copy, Serialize, Deserialize)] 105 | pub struct ColliderShapeSphere { 106 | /// The sphere center. vector3 107 | #[serde(skip_serializing_if = "Option::is_none")] 108 | pub offset: Option<[f64; 3]>, 109 | 110 | /// The sphere radius 111 | #[serde(skip_serializing_if = "Option::is_none")] 112 | pub radius: Option, 113 | } 114 | 115 | /// A bone group of VRMCSpringBone. 116 | #[derive(Debug, Clone, Serialize, Deserialize)] 117 | #[serde(rename_all = "camelCase")] 118 | pub struct Spring { 119 | /// An index of node which is used as a root of center space. 120 | #[cfg(feature = "gltf_index")] 121 | pub center: Option>, 122 | #[cfg(not(feature = "gltf_index"))] 123 | pub center: Option, 124 | 125 | /// Indices of ColliderGroups that detect collision with this spring. 126 | #[serde(skip_serializing_if = "Option::is_none")] 127 | pub collider_groups: Option>, 128 | 129 | #[serde(skip_serializing_if = "Option::is_none")] 130 | pub extensions: Option>>>, 131 | 132 | #[serde(skip_serializing_if = "Option::is_none")] 133 | pub extras: Option, 134 | 135 | /// Joints of the spring. Except for the first element, a previous joint of the array must be 136 | /// an ancestor of the joint. 137 | pub joints: Vec, 138 | 139 | /// Name of the Spring 140 | #[serde(skip_serializing_if = "Option::is_none")] 141 | pub name: Option, 142 | } 143 | 144 | /// A bone joint of VRMCSpringBone. 145 | #[derive(Debug, Clone, Serialize, Deserialize)] 146 | #[serde(rename_all = "camelCase")] 147 | pub struct SpringBoneJoint { 148 | /// Air resistance. Deceleration force. 149 | #[serde(skip_serializing_if = "Option::is_none")] 150 | pub drag_force: Option, 151 | 152 | #[serde(skip_serializing_if = "Option::is_none")] 153 | pub extensions: Option>>>, 154 | 155 | #[serde(skip_serializing_if = "Option::is_none")] 156 | pub extras: Option, 157 | 158 | /// The direction of gravity. A gravity other than downward direction also works. 159 | #[serde(skip_serializing_if = "Option::is_none")] 160 | pub gravity_dir: Option<[f64; 3]>, 161 | 162 | /// Gravitational acceleration. 163 | #[serde(skip_serializing_if = "Option::is_none")] 164 | pub gravity_power: Option, 165 | 166 | /// The radius of spring sphere. 167 | #[serde(skip_serializing_if = "Option::is_none")] 168 | pub hit_radius: Option, 169 | 170 | /// The node index. 171 | #[cfg(feature = "gltf_index")] 172 | pub node: gltf::json::Index, 173 | #[cfg(not(feature = "gltf_index"))] 174 | pub node: usize, 175 | 176 | /// The force to return to the initial pose. 177 | #[serde(skip_serializing_if = "Option::is_none")] 178 | pub stiffness: Option, 179 | } 180 | -------------------------------------------------------------------------------- /crates/vrm-spec/src/vrmc_vrm_1_0/expression_preset_name.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Serialize, Deserialize)] 4 | #[serde(rename_all = "camelCase")] 5 | pub enum ExpressionPresetName { 6 | Aa, 7 | Angry, 8 | Blink, 9 | BlinkLeft, 10 | BlinkRight, 11 | Ee, 12 | Happy, 13 | Ih, 14 | LookDown, 15 | LookLeft, 16 | LookRight, 17 | LookUp, 18 | Neutral, 19 | Oh, 20 | Ou, 21 | Relaxed, 22 | Sad, 23 | Surprised, 24 | } 25 | -------------------------------------------------------------------------------- /crates/vrm-spec/src/vrmc_vrm_1_0/human_bone_name.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Serialize, Deserialize)] 4 | #[serde(rename_all = "camelCase")] 5 | pub enum HumanBoneName { 6 | Chest, 7 | Head, 8 | Hips, 9 | Jaw, 10 | LeftEye, 11 | LeftFoot, 12 | LeftHand, 13 | LeftIndexDistal, 14 | LeftIndexIntermediate, 15 | LeftIndexProximal, 16 | LeftLittleDistal, 17 | LeftLittleIntermediate, 18 | LeftLittleProximal, 19 | LeftLowerArm, 20 | LeftLowerLeg, 21 | LeftMiddleDistal, 22 | LeftMiddleIntermediate, 23 | LeftMiddleProximal, 24 | LeftRingDistal, 25 | LeftRingIntermediate, 26 | LeftRingProximal, 27 | LeftShoulder, 28 | LeftThumbDistal, 29 | LeftThumbMetacarpal, 30 | LeftThumbProximal, 31 | LeftToes, 32 | LeftUpperArm, 33 | LeftUpperLeg, 34 | Neck, 35 | RightEye, 36 | RightFoot, 37 | RightHand, 38 | RightIndexDistal, 39 | RightIndexIntermediate, 40 | RightIndexProximal, 41 | RightLittleDistal, 42 | RightLittleIntermediate, 43 | RightLittleProximal, 44 | RightLowerArm, 45 | RightLowerLeg, 46 | RightMiddleDistal, 47 | RightMiddleIntermediate, 48 | RightMiddleProximal, 49 | RightRingDistal, 50 | RightRingIntermediate, 51 | RightRingProximal, 52 | RightShoulder, 53 | RightThumbDistal, 54 | RightThumbMetacarpal, 55 | RightThumbProximal, 56 | RightToes, 57 | RightUpperArm, 58 | RightUpperLeg, 59 | Spine, 60 | UpperChest, 61 | } 62 | -------------------------------------------------------------------------------- /crates/vrm-spec/src/vrmc_vrm_1_0/mod.rs: -------------------------------------------------------------------------------- 1 | //! Data structures for the [`VRMC_vrm`](https://github.com/vrm-c/vrm-specification/tree/master/specification/VRMC_vrm-1.0) 1.0 glTF Extension. 2 | 3 | pub mod expression_preset_name; 4 | pub mod human_bone_name; 5 | 6 | pub use expression_preset_name::ExpressionPresetName; 7 | pub use human_bone_name::HumanBoneName; 8 | use serde::{Deserialize, Serialize}; 9 | 10 | /// VRMC_VRM extension name 11 | pub const VRMC_VRM: &str = "VRMC_vrm"; 12 | 13 | #[cfg(feature = "rustc_hash")] 14 | use rustc_hash::FxHashMap as HashMap; 15 | #[cfg(not(feature = "rustc_hash"))] 16 | use std::collections::HashMap; 17 | 18 | #[derive(Debug, Clone, Serialize, Deserialize)] 19 | #[serde(rename_all = "camelCase")] 20 | pub struct VRMCVrmSchema { 21 | #[serde(skip_serializing_if = "Option::is_none")] 22 | pub expressions: Option, 23 | 24 | #[serde(skip_serializing_if = "Option::is_none")] 25 | pub extensions: Option>>>, 26 | 27 | #[serde(skip_serializing_if = "Option::is_none")] 28 | pub extras: Option, 29 | 30 | /// First-person perspective settings 31 | #[serde(skip_serializing_if = "Option::is_none")] 32 | pub first_person: Option, 33 | 34 | pub humanoid: Humanoid, 35 | 36 | /// Eye gaze control 37 | #[serde(skip_serializing_if = "Option::is_none")] 38 | pub look_at: Option, 39 | 40 | /// Meta informations of the VRM model 41 | pub meta: Meta, 42 | 43 | /// Specification version of VRMC_vrm 44 | pub spec_version: String, 45 | } 46 | 47 | /// Definition of expressions 48 | #[derive(Debug, Clone, Serialize, Deserialize)] 49 | pub struct Expressions { 50 | /// Custom expressions 51 | #[serde(skip_serializing_if = "Option::is_none")] 52 | pub custom: Option>, 53 | 54 | #[serde(skip_serializing_if = "Option::is_none")] 55 | pub extensions: Option>>>, 56 | 57 | #[serde(skip_serializing_if = "Option::is_none")] 58 | pub extras: Option, 59 | 60 | /// Preset expressions 61 | #[serde(skip_serializing_if = "Option::is_none")] 62 | pub preset: Option, 63 | } 64 | 65 | /// Definition of expression by weighted animation 66 | #[derive(Debug, Clone, Serialize, Deserialize)] 67 | #[serde(rename_all = "camelCase")] 68 | pub struct Expression { 69 | #[serde(skip_serializing_if = "Option::is_none")] 70 | pub extensions: Option>>>, 71 | 72 | #[serde(skip_serializing_if = "Option::is_none")] 73 | pub extras: Option, 74 | 75 | /// A value greater than 0.5 is 1.0, otherwise 0.0 76 | #[serde(skip_serializing_if = "Option::is_none")] 77 | pub is_binary: Option, 78 | 79 | /// Material color animation references 80 | #[serde(skip_serializing_if = "Option::is_none")] 81 | pub material_color_binds: Option>, 82 | 83 | /// Specify a morph target 84 | #[serde(skip_serializing_if = "Option::is_none")] 85 | pub morph_target_binds: Option>, 86 | 87 | /// Override values of Blink expressions when this Expression is enabled 88 | #[serde(skip_serializing_if = "Option::is_none")] 89 | pub override_blink: Option, 90 | 91 | /// Override values of LookAt expressions when this Expression is enabled 92 | #[serde(skip_serializing_if = "Option::is_none")] 93 | pub override_look_at: Option, 94 | 95 | /// Override values of Mouth expressions when this Expression is enabled 96 | #[serde(skip_serializing_if = "Option::is_none")] 97 | pub override_mouth: Option, 98 | 99 | /// Texture transform animation references 100 | #[serde(skip_serializing_if = "Option::is_none")] 101 | pub texture_transform_binds: Option>, 102 | } 103 | 104 | /// Material color value associated with a expression 105 | #[derive(Debug, Clone, Serialize, Deserialize)] 106 | #[serde(rename_all = "camelCase")] 107 | pub struct MaterialColorBind { 108 | #[serde(skip_serializing_if = "Option::is_none")] 109 | pub extensions: Option>>>, 110 | 111 | #[serde(skip_serializing_if = "Option::is_none")] 112 | pub extras: Option, 113 | 114 | /// target material 115 | #[cfg(feature = "gltf_index")] 116 | pub material: gltf::json::Index, 117 | #[cfg(not(feature = "gltf_index"))] 118 | pub material: usize, 119 | 120 | /// target color 121 | pub target_value: Vec, 122 | 123 | pub r#type: MaterialColorType, 124 | } 125 | 126 | /// Morph target value associated with a expression 127 | #[derive(Debug, Clone, Serialize, Deserialize)] 128 | pub struct MorphTargetBind { 129 | #[serde(skip_serializing_if = "Option::is_none")] 130 | pub extensions: Option>>>, 131 | 132 | #[serde(skip_serializing_if = "Option::is_none")] 133 | pub extras: Option, 134 | 135 | /// The index of the morph target in the mesh. 136 | pub index: usize, 137 | 138 | /// The index of the node that attached to target mesh. 139 | #[cfg(feature = "gltf_index")] 140 | pub node: gltf::json::Index, 141 | #[cfg(not(feature = "gltf_index"))] 142 | pub node: usize, 143 | 144 | /// The weight value of target morph target. 145 | pub weight: f64, 146 | } 147 | 148 | /// Texture transform value associated with a expression 149 | #[derive(Debug, Clone, Serialize, Deserialize)] 150 | pub struct TextureTransformBind { 151 | #[serde(skip_serializing_if = "Option::is_none")] 152 | pub extensions: Option>>>, 153 | 154 | #[serde(skip_serializing_if = "Option::is_none")] 155 | pub extras: Option, 156 | 157 | /// target material 158 | #[cfg(feature = "gltf_index")] 159 | pub material: gltf::json::Index, 160 | #[cfg(not(feature = "gltf_index"))] 161 | pub material: usize, 162 | 163 | /// uv offset for TEXCOORD_0 164 | #[serde(skip_serializing_if = "Option::is_none")] 165 | pub offset: Option>, 166 | 167 | /// uv scaling for TEXCOORD_0 168 | #[serde(skip_serializing_if = "Option::is_none")] 169 | pub scale: Option>, 170 | } 171 | /// Preset expressions 172 | #[derive(Debug, Clone, Serialize, Deserialize)] 173 | pub struct Preset(pub HashMap); 174 | 175 | /// First-person perspective settings 176 | #[derive(Debug, Clone, Serialize, Deserialize)] 177 | #[serde(rename_all = "camelCase")] 178 | pub struct FirstPerson { 179 | #[serde(skip_serializing_if = "Option::is_none")] 180 | pub extensions: Option>>>, 181 | 182 | #[serde(skip_serializing_if = "Option::is_none")] 183 | pub extras: Option, 184 | 185 | /// Mesh rendering annotation for cameras. 186 | #[serde(skip_serializing_if = "Option::is_none")] 187 | pub mesh_annotations: Option>, 188 | } 189 | 190 | /// Specify how the mesh should be interpreted by the camera 191 | #[derive(Debug, Clone, Serialize, Deserialize)] 192 | pub struct MeshAnnotation { 193 | #[serde(skip_serializing_if = "Option::is_none")] 194 | pub extensions: Option>>>, 195 | 196 | #[serde(skip_serializing_if = "Option::is_none")] 197 | pub extras: Option, 198 | 199 | /// The index of the node that attached to target mesh. 200 | #[cfg(feature = "gltf_index")] 201 | #[serde(skip_serializing_if = "Option::is_none")] 202 | pub node: Option>, 203 | #[cfg(not(feature = "gltf_index"))] 204 | #[serde(skip_serializing_if = "Option::is_none")] 205 | pub node: Option, 206 | 207 | /// How the camera interprets the mesh. 208 | #[serde(rename = "type")] 209 | pub mesh_annotation_type: FirstPersonType, 210 | } 211 | 212 | /// Correspondence between nodes and human bones 213 | #[derive(Debug, Clone, Serialize, Deserialize)] 214 | #[serde(rename_all = "camelCase")] 215 | pub struct Humanoid { 216 | #[serde(skip_serializing_if = "Option::is_none")] 217 | pub extensions: Option>>>, 218 | 219 | #[serde(skip_serializing_if = "Option::is_none")] 220 | pub extras: Option, 221 | 222 | pub human_bones: HumanBones, 223 | } 224 | 225 | /// Represents a set of humanBones of a humanoid. 226 | // FIXME: all bones are optional by now 227 | #[derive(Debug, Clone, Serialize, Deserialize)] 228 | pub struct HumanBones(pub HashMap>); 229 | 230 | /// Represents a single bone of a Humanoid. 231 | #[derive(Debug, Clone, Serialize, Deserialize)] 232 | pub struct HumanBone { 233 | #[serde(skip_serializing_if = "Option::is_none")] 234 | pub extensions: Option>>>, 235 | 236 | #[serde(skip_serializing_if = "Option::is_none")] 237 | pub extras: Option, 238 | 239 | /// Represents a single glTF node tied to this humanBone. 240 | #[cfg(feature = "gltf_index")] 241 | #[serde(skip_serializing_if = "Option::is_none")] 242 | pub node: Option>, 243 | #[cfg(not(feature = "gltf_index"))] 244 | #[serde(skip_serializing_if = "Option::is_none")] 245 | pub node: Option, 246 | } 247 | 248 | /// Eye gaze control 249 | #[derive(Debug, Clone, Serialize, Deserialize)] 250 | #[serde(rename_all = "camelCase")] 251 | pub struct LookAt { 252 | #[serde(skip_serializing_if = "Option::is_none")] 253 | pub extensions: Option>>>, 254 | 255 | #[serde(skip_serializing_if = "Option::is_none")] 256 | pub extras: Option, 257 | 258 | /// The origin of LookAt. Position offset from the head bone 259 | #[serde(skip_serializing_if = "Option::is_none")] 260 | pub offset_from_head_bone: Option>, 261 | 262 | /// Horizontal inward movement. The left eye moves right. The right eye moves left. 263 | #[serde(skip_serializing_if = "Option::is_none")] 264 | pub range_map_horizontal_inner: Option, 265 | 266 | /// Horizontal outward movement. The left eye moves left. The right eye moves right. 267 | #[serde(skip_serializing_if = "Option::is_none")] 268 | pub range_map_horizontal_outer: Option, 269 | 270 | /// Vertical downward movement. Both eyes move upwards 271 | #[serde(skip_serializing_if = "Option::is_none")] 272 | pub range_map_vertical_down: Option, 273 | 274 | /// Vertical upward movement. Both eyes move downwards 275 | #[serde(skip_serializing_if = "Option::is_none")] 276 | pub range_map_vertical_up: Option, 277 | 278 | #[serde(rename = "type")] 279 | #[serde(skip_serializing_if = "Option::is_none")] 280 | pub look_at_type: Option, 281 | } 282 | 283 | /// LookAt range definition 284 | #[derive(Debug, Clone, Serialize, Deserialize)] 285 | #[serde(rename_all = "camelCase")] 286 | pub struct LookAtRangeMap { 287 | #[serde(skip_serializing_if = "Option::is_none")] 288 | pub extensions: Option>>>, 289 | 290 | #[serde(skip_serializing_if = "Option::is_none")] 291 | pub extras: Option, 292 | 293 | /// Yaw and pitch angles ( degrees ) between the head bone forward vector and the eye gaze 294 | /// LookAt vector 295 | #[serde(skip_serializing_if = "Option::is_none")] 296 | pub input_max_value: Option, 297 | 298 | /// Degree for type.bone, Weight for type.expressions 299 | #[serde(skip_serializing_if = "Option::is_none")] 300 | pub output_scale: Option, 301 | } 302 | 303 | /// Meta information of the VRM model 304 | #[derive(Debug, Clone, Serialize, Deserialize)] 305 | #[serde(rename_all = "camelCase")] 306 | pub struct Meta { 307 | /// A flag that permits to use this model in contents contain anti-social activities or hate 308 | /// speeches 309 | #[serde(skip_serializing_if = "Option::is_none")] 310 | pub allow_antisocial_or_hate_usage: Option, 311 | 312 | /// A flag that permits to use this model in excessively sexual contents 313 | #[serde(skip_serializing_if = "Option::is_none")] 314 | pub allow_excessively_sexual_usage: Option, 315 | 316 | /// A flag that permits to use this model in excessively violent contents 317 | #[serde(skip_serializing_if = "Option::is_none")] 318 | pub allow_excessively_violent_usage: Option, 319 | 320 | /// A flag that permits to use this model in political or religious contents 321 | #[serde(skip_serializing_if = "Option::is_none")] 322 | pub allow_political_or_religious_usage: Option, 323 | 324 | /// A flag that permits to redistribute this model 325 | #[serde(skip_serializing_if = "Option::is_none")] 326 | pub allow_redistribution: Option, 327 | 328 | /// Authors of the model 329 | pub authors: Vec, 330 | 331 | /// A person who can perform as an avatar with this model 332 | #[serde(skip_serializing_if = "Option::is_none")] 333 | pub avatar_permission: Option, 334 | 335 | /// An option that permits to use this model in commercial products 336 | #[serde(skip_serializing_if = "Option::is_none")] 337 | pub commercial_usage: Option, 338 | 339 | /// An information that describes the contact information of the author 340 | #[serde(skip_serializing_if = "Option::is_none")] 341 | pub contact_information: Option, 342 | 343 | /// An information that describes the copyright of the model 344 | #[serde(skip_serializing_if = "Option::is_none")] 345 | pub copyright_information: Option, 346 | 347 | /// An option that forces or abandons to display the credit of this model 348 | #[serde(skip_serializing_if = "Option::is_none")] 349 | pub credit_notation: Option, 350 | 351 | #[serde(skip_serializing_if = "Option::is_none")] 352 | pub extensions: Option>>>, 353 | 354 | #[serde(skip_serializing_if = "Option::is_none")] 355 | pub extras: Option, 356 | 357 | /// A URL towards the license document this model refers to 358 | pub license_url: String, 359 | 360 | /// An option that controls the condition to modify this model 361 | #[serde(skip_serializing_if = "Option::is_none")] 362 | pub modification: Option, 363 | 364 | /// The name of the model 365 | pub name: String, 366 | 367 | /// Describe the URL links of other license 368 | #[serde(skip_serializing_if = "Option::is_none")] 369 | pub other_license_url: Option, 370 | 371 | /// References / original works of the model 372 | #[serde(skip_serializing_if = "Option::is_none")] 373 | pub references: Option>, 374 | 375 | /// Third party licenses of the model, if required. You can use line breaks 376 | #[serde(skip_serializing_if = "Option::is_none")] 377 | pub third_party_licenses: Option, 378 | 379 | /// The index to the thumbnail image of the model in gltf.images 380 | #[cfg(feature = "gltf_index")] 381 | #[serde(skip_serializing_if = "Option::is_none")] 382 | pub thumbnail_image: Option>, 383 | #[cfg(not(feature = "gltf_index"))] 384 | #[serde(skip_serializing_if = "Option::is_none")] 385 | pub thumbnail_image: Option, 386 | 387 | /// The version of the model 388 | #[serde(skip_serializing_if = "Option::is_none")] 389 | pub version: Option, 390 | } 391 | 392 | #[derive(Debug, Clone, Copy, Hash, Serialize, Deserialize)] 393 | #[serde(rename_all = "camelCase")] 394 | pub enum MaterialColorType { 395 | Color, 396 | EmissionColor, 397 | MatcapColor, 398 | OutlineColor, 399 | RimColor, 400 | ShadeColor, 401 | } 402 | 403 | /// Expression override types 404 | #[derive(Debug, Clone, Copy, Hash, Serialize, Deserialize)] 405 | #[serde(rename_all = "camelCase")] 406 | pub enum ExpressionOverrideType { 407 | Blend, 408 | Block, 409 | None, 410 | } 411 | 412 | /// How the camera interprets the mesh. 413 | #[derive(Debug, Clone, Copy, Hash, Serialize, Deserialize)] 414 | #[serde(rename_all = "camelCase")] 415 | pub enum FirstPersonType { 416 | Auto, 417 | Both, 418 | FirstPersonOnly, 419 | ThirdPersonOnly, 420 | } 421 | 422 | #[derive(Debug, Clone, Copy, Hash, Serialize, Deserialize)] 423 | #[serde(rename_all = "camelCase")] 424 | pub enum LookAtType { 425 | Bone, 426 | Expression, 427 | } 428 | 429 | /// A person who can perform as an avatar with this model 430 | #[derive(Debug, Clone, Copy, Hash, Serialize, Deserialize)] 431 | #[serde(rename_all = "camelCase")] 432 | pub enum AvatarPermissionType { 433 | Everyone, 434 | OnlyAuthor, 435 | OnlySeparatelyLicensedPerson, 436 | } 437 | 438 | /// An option that permits to use this model in commercial products 439 | #[derive(Debug, Clone, Copy, Hash, Serialize, Deserialize)] 440 | #[serde(rename_all = "camelCase")] 441 | pub enum CommercialUsageType { 442 | Corporation, 443 | PersonalNonProfit, 444 | PersonalProfit, 445 | } 446 | 447 | /// An option that forces or abandons to display the credit of this model 448 | #[derive(Debug, Clone, Copy, Hash, Serialize, Deserialize)] 449 | #[serde(rename_all = "camelCase")] 450 | pub enum CreditNotationType { 451 | Required, 452 | Unnecessary, 453 | } 454 | 455 | /// An option that controls the condition to modify this model 456 | #[derive(Debug, Clone, Copy, Hash, Serialize, Deserialize)] 457 | #[serde(rename_all = "camelCase")] 458 | pub enum ModificationType { 459 | AllowModification, 460 | AllowModificationRedistribution, 461 | Prohibited, 462 | } 463 | -------------------------------------------------------------------------------- /crates/vrm-spec/src/vrmc_vrm_animation_1_0.rs: -------------------------------------------------------------------------------- 1 | //! Data structures for the [`VRMC_vrm_animation`](https://github.com/vrm-c/vrm-specification/blob/master/specification/VRMC_vrm_animation-1.0/README.md) 1.0 glTF Extension. 2 | 3 | #[cfg(feature = "rustc_hash")] 4 | use rustc_hash::FxHashMap as HashMap; 5 | use serde::{Deserialize, Serialize}; 6 | #[cfg(not(feature = "rustc_hash"))] 7 | use std::collections::HashMap; 8 | 9 | /// VRMC_vrm_animation extension name 10 | pub const VRMC_VRM_ANIMATION: &str = "VRMC_vrm_animation"; 11 | 12 | /// glTF extension that defines humanoid animations. 13 | #[derive(Debug, Clone, Serialize, Deserialize)] 14 | #[serde(rename_all = "camelCase")] 15 | pub struct VRMCVrmAnimationSchema { 16 | /// Specification version of VRMC_vrm_animation 17 | pub spec_version: String, 18 | 19 | /// An object which describes about humanoid bones. 20 | #[serde(skip_serializing_if = "Option::is_none")] 21 | pub humanoid: Option, 22 | 23 | /// An object which maps expressions to nodes. 24 | #[serde(skip_serializing_if = "Option::is_none")] 25 | pub expressions: Option, 26 | 27 | /// An object which maps a eye gaze point to a node. 28 | #[serde(skip_serializing_if = "Option::is_none")] 29 | pub look_at: Option, 30 | 31 | #[serde(skip_serializing_if = "Option::is_none")] 32 | pub extensions: Option>>>, 33 | 34 | #[serde(skip_serializing_if = "Option::is_none")] 35 | pub extras: Option, 36 | } 37 | 38 | /// An object which describes about humanoid bones. 39 | #[derive(Debug, Clone, Serialize, Deserialize)] 40 | #[serde(rename_all = "camelCase")] 41 | pub struct VRMCVrmAnimationHumanoid { 42 | /// An object which maps humanoid bones to nodes. 43 | pub human_bones: VRMCVrmAnimationHumanBones, 44 | 45 | #[serde(skip_serializing_if = "Option::is_none")] 46 | pub extensions: Option>>>, 47 | 48 | #[serde(skip_serializing_if = "Option::is_none")] 49 | pub extras: Option, 50 | } 51 | 52 | /// Represents a set of humanBones of a humanoid. 53 | // FIXME: all bones are optional by now 54 | #[derive(Debug, Clone, Serialize, Deserialize)] 55 | pub struct VRMCVrmAnimationHumanBones( 56 | pub HashMap>, 57 | ); 58 | 59 | /// Represents a single bone of a Humanoid. 60 | #[derive(Debug, Clone, Serialize, Deserialize)] 61 | pub struct VRMCVrmAnimationHumanBone { 62 | /// Represents a single glTF node tied to this humanBone. 63 | #[cfg(feature = "gltf_index")] 64 | #[serde(skip_serializing_if = "Option::is_none")] 65 | pub node: Option>, 66 | #[cfg(not(feature = "gltf_index"))] 67 | #[serde(skip_serializing_if = "Option::is_none")] 68 | pub node: Option, 69 | 70 | #[serde(skip_serializing_if = "Option::is_none")] 71 | pub extensions: Option>>>, 72 | 73 | #[serde(skip_serializing_if = "Option::is_none")] 74 | pub extras: Option, 75 | } 76 | 77 | /// An object which maps expressions to nodes. 78 | #[derive(Debug, Clone, Serialize, Deserialize)] 79 | pub struct VRMCVrmAnimationExpressions { 80 | /// An object that contains definitions of preset expressions. 81 | #[serde(skip_serializing_if = "Option::is_none")] 82 | pub preset: Option, 83 | 84 | /// An object that contains definitions of custom expressions. 85 | #[serde(skip_serializing_if = "Option::is_none")] 86 | pub custom: Option>, 87 | 88 | #[serde(skip_serializing_if = "Option::is_none")] 89 | pub extensions: Option>>>, 90 | 91 | #[serde(skip_serializing_if = "Option::is_none")] 92 | pub extras: Option, 93 | } 94 | 95 | /// An object that contains definitions of preset expressions. 96 | #[derive(Debug, Clone, Serialize, Deserialize)] 97 | pub struct VRMCVrmAnimationExpressionPreset( 98 | pub HashMap, 99 | ); 100 | 101 | /// Represents a single expression. 102 | #[derive(Debug, Clone, Serialize, Deserialize)] 103 | pub struct VRMCVrmAnimationExpression { 104 | /// Represents a single glTF node mapped to this expression. 105 | #[cfg(feature = "gltf_index")] 106 | #[serde(skip_serializing_if = "Option::is_none")] 107 | pub node: Option>, 108 | #[cfg(not(feature = "gltf_index"))] 109 | #[serde(skip_serializing_if = "Option::is_none")] 110 | pub node: Option, 111 | 112 | #[serde(skip_serializing_if = "Option::is_none")] 113 | pub extensions: Option>>>, 114 | 115 | #[serde(skip_serializing_if = "Option::is_none")] 116 | pub extras: Option, 117 | } 118 | 119 | /// An object which maps a eye gaze point to a node. 120 | #[derive(Debug, Clone, Serialize, Deserialize)] 121 | #[serde(rename_all = "camelCase")] 122 | pub struct VRMCVrmAnimationLookAt { 123 | /// Represents a single glTF node represents the eye gaze point. 124 | #[cfg(feature = "gltf_index")] 125 | #[serde(skip_serializing_if = "Option::is_none")] 126 | pub node: Option>, 127 | #[cfg(not(feature = "gltf_index"))] 128 | #[serde(skip_serializing_if = "Option::is_none")] 129 | pub node: Option, 130 | 131 | /// The position offset of the origin of the LookAt from the humanoid head bone 132 | #[serde(skip_serializing_if = "Option::is_none")] 133 | pub offset_from_head_bone: Option<[f64; 3]>, 134 | 135 | #[serde(skip_serializing_if = "Option::is_none")] 136 | pub extensions: Option>>>, 137 | 138 | #[serde(skip_serializing_if = "Option::is_none")] 139 | pub extras: Option, 140 | } 141 | -------------------------------------------------------------------------------- /crates/vrm-spec/tests/snapshots/test__vrm1.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/vrm-spec/tests/test.rs 3 | expression: vrmc_vrm 4 | --- 5 | VRMCVrmSchema { 6 | expressions: Some( 7 | Expressions { 8 | custom: None, 9 | extensions: None, 10 | extras: None, 11 | preset: Some( 12 | Preset( 13 | { 14 | Aa: Expression { 15 | extensions: None, 16 | extras: None, 17 | is_binary: Some( 18 | false, 19 | ), 20 | material_color_binds: None, 21 | morph_target_binds: Some( 22 | [ 23 | MorphTargetBind { 24 | extensions: None, 25 | extras: None, 26 | index: 36, 27 | node: 1, 28 | weight: 1.0, 29 | }, 30 | ], 31 | ), 32 | override_blink: None, 33 | override_look_at: None, 34 | override_mouth: None, 35 | texture_transform_binds: None, 36 | }, 37 | Relaxed: Expression { 38 | extensions: None, 39 | extras: None, 40 | is_binary: Some( 41 | false, 42 | ), 43 | material_color_binds: None, 44 | morph_target_binds: Some( 45 | [ 46 | MorphTargetBind { 47 | extensions: None, 48 | extras: None, 49 | index: 2, 50 | node: 1, 51 | weight: 1.0, 52 | }, 53 | ], 54 | ), 55 | override_blink: None, 56 | override_look_at: None, 57 | override_mouth: None, 58 | texture_transform_binds: None, 59 | }, 60 | LookUp: Expression { 61 | extensions: None, 62 | extras: None, 63 | is_binary: Some( 64 | false, 65 | ), 66 | material_color_binds: None, 67 | morph_target_binds: None, 68 | override_blink: None, 69 | override_look_at: None, 70 | override_mouth: None, 71 | texture_transform_binds: None, 72 | }, 73 | Ih: Expression { 74 | extensions: None, 75 | extras: None, 76 | is_binary: Some( 77 | false, 78 | ), 79 | material_color_binds: None, 80 | morph_target_binds: Some( 81 | [ 82 | MorphTargetBind { 83 | extensions: None, 84 | extras: None, 85 | index: 37, 86 | node: 1, 87 | weight: 1.0, 88 | }, 89 | ], 90 | ), 91 | override_blink: None, 92 | override_look_at: None, 93 | override_mouth: None, 94 | texture_transform_binds: None, 95 | }, 96 | BlinkLeft: Expression { 97 | extensions: None, 98 | extras: None, 99 | is_binary: Some( 100 | false, 101 | ), 102 | material_color_binds: None, 103 | morph_target_binds: Some( 104 | [ 105 | MorphTargetBind { 106 | extensions: None, 107 | extras: None, 108 | index: 13, 109 | node: 1, 110 | weight: 1.0, 111 | }, 112 | ], 113 | ), 114 | override_blink: None, 115 | override_look_at: None, 116 | override_mouth: None, 117 | texture_transform_binds: None, 118 | }, 119 | Ou: Expression { 120 | extensions: None, 121 | extras: None, 122 | is_binary: Some( 123 | false, 124 | ), 125 | material_color_binds: None, 126 | morph_target_binds: Some( 127 | [ 128 | MorphTargetBind { 129 | extensions: None, 130 | extras: None, 131 | index: 38, 132 | node: 1, 133 | weight: 1.0, 134 | }, 135 | ], 136 | ), 137 | override_blink: None, 138 | override_look_at: None, 139 | override_mouth: None, 140 | texture_transform_binds: None, 141 | }, 142 | LookRight: Expression { 143 | extensions: None, 144 | extras: None, 145 | is_binary: Some( 146 | false, 147 | ), 148 | material_color_binds: None, 149 | morph_target_binds: None, 150 | override_blink: None, 151 | override_look_at: None, 152 | override_mouth: None, 153 | texture_transform_binds: None, 154 | }, 155 | Happy: Expression { 156 | extensions: None, 157 | extras: None, 158 | is_binary: Some( 159 | false, 160 | ), 161 | material_color_binds: None, 162 | morph_target_binds: Some( 163 | [ 164 | MorphTargetBind { 165 | extensions: None, 166 | extras: None, 167 | index: 3, 168 | node: 1, 169 | weight: 1.0, 170 | }, 171 | ], 172 | ), 173 | override_blink: Some( 174 | Blend, 175 | ), 176 | override_look_at: None, 177 | override_mouth: Some( 178 | Blend, 179 | ), 180 | texture_transform_binds: None, 181 | }, 182 | Blink: Expression { 183 | extensions: None, 184 | extras: None, 185 | is_binary: Some( 186 | false, 187 | ), 188 | material_color_binds: None, 189 | morph_target_binds: Some( 190 | [ 191 | MorphTargetBind { 192 | extensions: None, 193 | extras: None, 194 | index: 12, 195 | node: 1, 196 | weight: 1.0, 197 | }, 198 | ], 199 | ), 200 | override_blink: None, 201 | override_look_at: None, 202 | override_mouth: None, 203 | texture_transform_binds: None, 204 | }, 205 | Surprised: Expression { 206 | extensions: None, 207 | extras: None, 208 | is_binary: Some( 209 | false, 210 | ), 211 | material_color_binds: None, 212 | morph_target_binds: Some( 213 | [ 214 | MorphTargetBind { 215 | extensions: None, 216 | extras: None, 217 | index: 5, 218 | node: 1, 219 | weight: 1.0, 220 | }, 221 | ], 222 | ), 223 | override_blink: None, 224 | override_look_at: None, 225 | override_mouth: Some( 226 | Blend, 227 | ), 228 | texture_transform_binds: None, 229 | }, 230 | Oh: Expression { 231 | extensions: None, 232 | extras: None, 233 | is_binary: Some( 234 | false, 235 | ), 236 | material_color_binds: None, 237 | morph_target_binds: Some( 238 | [ 239 | MorphTargetBind { 240 | extensions: None, 241 | extras: None, 242 | index: 40, 243 | node: 1, 244 | weight: 1.0, 245 | }, 246 | ], 247 | ), 248 | override_blink: None, 249 | override_look_at: None, 250 | override_mouth: None, 251 | texture_transform_binds: None, 252 | }, 253 | LookLeft: Expression { 254 | extensions: None, 255 | extras: None, 256 | is_binary: Some( 257 | false, 258 | ), 259 | material_color_binds: None, 260 | morph_target_binds: None, 261 | override_blink: None, 262 | override_look_at: None, 263 | override_mouth: None, 264 | texture_transform_binds: None, 265 | }, 266 | Ee: Expression { 267 | extensions: None, 268 | extras: None, 269 | is_binary: Some( 270 | false, 271 | ), 272 | material_color_binds: None, 273 | morph_target_binds: Some( 274 | [ 275 | MorphTargetBind { 276 | extensions: None, 277 | extras: None, 278 | index: 39, 279 | node: 1, 280 | weight: 1.0, 281 | }, 282 | ], 283 | ), 284 | override_blink: None, 285 | override_look_at: None, 286 | override_mouth: None, 287 | texture_transform_binds: None, 288 | }, 289 | Angry: Expression { 290 | extensions: None, 291 | extras: None, 292 | is_binary: Some( 293 | false, 294 | ), 295 | material_color_binds: None, 296 | morph_target_binds: Some( 297 | [ 298 | MorphTargetBind { 299 | extensions: None, 300 | extras: None, 301 | index: 1, 302 | node: 1, 303 | weight: 1.0, 304 | }, 305 | ], 306 | ), 307 | override_blink: None, 308 | override_look_at: None, 309 | override_mouth: None, 310 | texture_transform_binds: None, 311 | }, 312 | Sad: Expression { 313 | extensions: None, 314 | extras: None, 315 | is_binary: Some( 316 | false, 317 | ), 318 | material_color_binds: None, 319 | morph_target_binds: Some( 320 | [ 321 | MorphTargetBind { 322 | extensions: None, 323 | extras: None, 324 | index: 4, 325 | node: 1, 326 | weight: 1.0, 327 | }, 328 | ], 329 | ), 330 | override_blink: None, 331 | override_look_at: None, 332 | override_mouth: Some( 333 | Blend, 334 | ), 335 | texture_transform_binds: None, 336 | }, 337 | Neutral: Expression { 338 | extensions: None, 339 | extras: None, 340 | is_binary: Some( 341 | false, 342 | ), 343 | material_color_binds: None, 344 | morph_target_binds: Some( 345 | [ 346 | MorphTargetBind { 347 | extensions: None, 348 | extras: None, 349 | index: 0, 350 | node: 1, 351 | weight: 1.0, 352 | }, 353 | ], 354 | ), 355 | override_blink: None, 356 | override_look_at: None, 357 | override_mouth: None, 358 | texture_transform_binds: None, 359 | }, 360 | LookDown: Expression { 361 | extensions: None, 362 | extras: None, 363 | is_binary: Some( 364 | false, 365 | ), 366 | material_color_binds: None, 367 | morph_target_binds: None, 368 | override_blink: None, 369 | override_look_at: None, 370 | override_mouth: None, 371 | texture_transform_binds: None, 372 | }, 373 | BlinkRight: Expression { 374 | extensions: None, 375 | extras: None, 376 | is_binary: Some( 377 | false, 378 | ), 379 | material_color_binds: None, 380 | morph_target_binds: Some( 381 | [ 382 | MorphTargetBind { 383 | extensions: None, 384 | extras: None, 385 | index: 14, 386 | node: 1, 387 | weight: 1.0, 388 | }, 389 | ], 390 | ), 391 | override_blink: None, 392 | override_look_at: None, 393 | override_mouth: None, 394 | texture_transform_binds: None, 395 | }, 396 | }, 397 | ), 398 | ), 399 | }, 400 | ), 401 | extensions: None, 402 | extras: None, 403 | first_person: Some( 404 | FirstPerson { 405 | extensions: None, 406 | extras: None, 407 | mesh_annotations: Some( 408 | [ 409 | MeshAnnotation { 410 | extensions: None, 411 | extras: None, 412 | node: Some( 413 | 0, 414 | ), 415 | mesh_annotation_type: Auto, 416 | }, 417 | MeshAnnotation { 418 | extensions: None, 419 | extras: None, 420 | node: Some( 421 | 1, 422 | ), 423 | mesh_annotation_type: Auto, 424 | }, 425 | MeshAnnotation { 426 | extensions: None, 427 | extras: None, 428 | node: Some( 429 | 2, 430 | ), 431 | mesh_annotation_type: Auto, 432 | }, 433 | ], 434 | ), 435 | }, 436 | ), 437 | humanoid: Humanoid { 438 | extensions: None, 439 | extras: None, 440 | human_bones: HumanBones( 441 | { 442 | Chest: Some( 443 | HumanBone { 444 | extensions: None, 445 | extras: None, 446 | node: Some( 447 | 22, 448 | ), 449 | }, 450 | ), 451 | LeftRingIntermediate: Some( 452 | HumanBone { 453 | extensions: None, 454 | extras: None, 455 | node: Some( 456 | 112, 457 | ), 458 | }, 459 | ), 460 | RightShoulder: Some( 461 | HumanBone { 462 | extensions: None, 463 | extras: None, 464 | node: Some( 465 | 122, 466 | ), 467 | }, 468 | ), 469 | RightLowerArm: Some( 470 | HumanBone { 471 | extensions: None, 472 | extras: None, 473 | node: Some( 474 | 131, 475 | ), 476 | }, 477 | ), 478 | LeftLittleIntermediate: Some( 479 | HumanBone { 480 | extensions: None, 481 | extras: None, 482 | node: Some( 483 | 104, 484 | ), 485 | }, 486 | ), 487 | RightFoot: Some( 488 | HumanBone { 489 | extensions: None, 490 | extras: None, 491 | node: Some( 492 | 167, 493 | ), 494 | }, 495 | ), 496 | RightThumbProximal: Some( 497 | HumanBone { 498 | extensions: None, 499 | extras: None, 500 | node: Some( 501 | 150, 502 | ), 503 | }, 504 | ), 505 | LeftThumbDistal: Some( 506 | HumanBone { 507 | extensions: None, 508 | extras: None, 509 | node: Some( 510 | 117, 511 | ), 512 | }, 513 | ), 514 | RightMiddleIntermediate: Some( 515 | HumanBone { 516 | extensions: None, 517 | extras: None, 518 | node: Some( 519 | 142, 520 | ), 521 | }, 522 | ), 523 | LeftLowerLeg: Some( 524 | HumanBone { 525 | extensions: None, 526 | extras: None, 527 | node: Some( 528 | 161, 529 | ), 530 | }, 531 | ), 532 | RightIndexIntermediate: Some( 533 | HumanBone { 534 | extensions: None, 535 | extras: None, 536 | node: Some( 537 | 134, 538 | ), 539 | }, 540 | ), 541 | LeftHand: Some( 542 | HumanBone { 543 | extensions: None, 544 | extras: None, 545 | node: Some( 546 | 98, 547 | ), 548 | }, 549 | ), 550 | RightUpperLeg: Some( 551 | HumanBone { 552 | extensions: None, 553 | extras: None, 554 | node: Some( 555 | 165, 556 | ), 557 | }, 558 | ), 559 | LeftToes: Some( 560 | HumanBone { 561 | extensions: None, 562 | extras: None, 563 | node: Some( 564 | 163, 565 | ), 566 | }, 567 | ), 568 | LeftMiddleProximal: Some( 569 | HumanBone { 570 | extensions: None, 571 | extras: None, 572 | node: Some( 573 | 107, 574 | ), 575 | }, 576 | ), 577 | RightRingIntermediate: Some( 578 | HumanBone { 579 | extensions: None, 580 | extras: None, 581 | node: Some( 582 | 146, 583 | ), 584 | }, 585 | ), 586 | RightLittleIntermediate: Some( 587 | HumanBone { 588 | extensions: None, 589 | extras: None, 590 | node: Some( 591 | 138, 592 | ), 593 | }, 594 | ), 595 | LeftIndexProximal: Some( 596 | HumanBone { 597 | extensions: None, 598 | extras: None, 599 | node: Some( 600 | 99, 601 | ), 602 | }, 603 | ), 604 | Head: Some( 605 | HumanBone { 606 | extensions: None, 607 | extras: None, 608 | node: Some( 609 | 25, 610 | ), 611 | }, 612 | ), 613 | Neck: Some( 614 | HumanBone { 615 | extensions: None, 616 | extras: None, 617 | node: Some( 618 | 24, 619 | ), 620 | }, 621 | ), 622 | LeftRingProximal: Some( 623 | HumanBone { 624 | extensions: None, 625 | extras: None, 626 | node: Some( 627 | 111, 628 | ), 629 | }, 630 | ), 631 | RightThumbDistal: Some( 632 | HumanBone { 633 | extensions: None, 634 | extras: None, 635 | node: Some( 636 | 151, 637 | ), 638 | }, 639 | ), 640 | RightLowerLeg: Some( 641 | HumanBone { 642 | extensions: None, 643 | extras: None, 644 | node: Some( 645 | 166, 646 | ), 647 | }, 648 | ), 649 | LeftLittleProximal: Some( 650 | HumanBone { 651 | extensions: None, 652 | extras: None, 653 | node: Some( 654 | 103, 655 | ), 656 | }, 657 | ), 658 | LeftEye: Some( 659 | HumanBone { 660 | extensions: None, 661 | extras: None, 662 | node: Some( 663 | 26, 664 | ), 665 | }, 666 | ), 667 | RightHand: Some( 668 | HumanBone { 669 | extensions: None, 670 | extras: None, 671 | node: Some( 672 | 132, 673 | ), 674 | }, 675 | ), 676 | LeftThumbMetacarpal: Some( 677 | HumanBone { 678 | extensions: None, 679 | extras: None, 680 | node: Some( 681 | 115, 682 | ), 683 | }, 684 | ), 685 | RightToes: Some( 686 | HumanBone { 687 | extensions: None, 688 | extras: None, 689 | node: Some( 690 | 168, 691 | ), 692 | }, 693 | ), 694 | RightMiddleProximal: Some( 695 | HumanBone { 696 | extensions: None, 697 | extras: None, 698 | node: Some( 699 | 141, 700 | ), 701 | }, 702 | ), 703 | LeftMiddleDistal: Some( 704 | HumanBone { 705 | extensions: None, 706 | extras: None, 707 | node: Some( 708 | 109, 709 | ), 710 | }, 711 | ), 712 | LeftIndexDistal: Some( 713 | HumanBone { 714 | extensions: None, 715 | extras: None, 716 | node: Some( 717 | 101, 718 | ), 719 | }, 720 | ), 721 | RightIndexProximal: Some( 722 | HumanBone { 723 | extensions: None, 724 | extras: None, 725 | node: Some( 726 | 133, 727 | ), 728 | }, 729 | ), 730 | LeftUpperArm: Some( 731 | HumanBone { 732 | extensions: None, 733 | extras: None, 734 | node: Some( 735 | 96, 736 | ), 737 | }, 738 | ), 739 | Spine: Some( 740 | HumanBone { 741 | extensions: None, 742 | extras: None, 743 | node: Some( 744 | 21, 745 | ), 746 | }, 747 | ), 748 | RightRingProximal: Some( 749 | HumanBone { 750 | extensions: None, 751 | extras: None, 752 | node: Some( 753 | 145, 754 | ), 755 | }, 756 | ), 757 | LeftRingDistal: Some( 758 | HumanBone { 759 | extensions: None, 760 | extras: None, 761 | node: Some( 762 | 113, 763 | ), 764 | }, 765 | ), 766 | LeftLittleDistal: Some( 767 | HumanBone { 768 | extensions: None, 769 | extras: None, 770 | node: Some( 771 | 105, 772 | ), 773 | }, 774 | ), 775 | RightLittleProximal: Some( 776 | HumanBone { 777 | extensions: None, 778 | extras: None, 779 | node: Some( 780 | 137, 781 | ), 782 | }, 783 | ), 784 | RightEye: Some( 785 | HumanBone { 786 | extensions: None, 787 | extras: None, 788 | node: Some( 789 | 27, 790 | ), 791 | }, 792 | ), 793 | Hips: Some( 794 | HumanBone { 795 | extensions: None, 796 | extras: None, 797 | node: Some( 798 | 4, 799 | ), 800 | }, 801 | ), 802 | RightThumbMetacarpal: Some( 803 | HumanBone { 804 | extensions: None, 805 | extras: None, 806 | node: Some( 807 | 149, 808 | ), 809 | }, 810 | ), 811 | LeftShoulder: Some( 812 | HumanBone { 813 | extensions: None, 814 | extras: None, 815 | node: Some( 816 | 88, 817 | ), 818 | }, 819 | ), 820 | LeftLowerArm: Some( 821 | HumanBone { 822 | extensions: None, 823 | extras: None, 824 | node: Some( 825 | 97, 826 | ), 827 | }, 828 | ), 829 | RightMiddleDistal: Some( 830 | HumanBone { 831 | extensions: None, 832 | extras: None, 833 | node: Some( 834 | 143, 835 | ), 836 | }, 837 | ), 838 | RightIndexDistal: Some( 839 | HumanBone { 840 | extensions: None, 841 | extras: None, 842 | node: Some( 843 | 135, 844 | ), 845 | }, 846 | ), 847 | LeftFoot: Some( 848 | HumanBone { 849 | extensions: None, 850 | extras: None, 851 | node: Some( 852 | 162, 853 | ), 854 | }, 855 | ), 856 | RightUpperArm: Some( 857 | HumanBone { 858 | extensions: None, 859 | extras: None, 860 | node: Some( 861 | 130, 862 | ), 863 | }, 864 | ), 865 | LeftThumbProximal: Some( 866 | HumanBone { 867 | extensions: None, 868 | extras: None, 869 | node: Some( 870 | 116, 871 | ), 872 | }, 873 | ), 874 | LeftMiddleIntermediate: Some( 875 | HumanBone { 876 | extensions: None, 877 | extras: None, 878 | node: Some( 879 | 108, 880 | ), 881 | }, 882 | ), 883 | RightRingDistal: Some( 884 | HumanBone { 885 | extensions: None, 886 | extras: None, 887 | node: Some( 888 | 147, 889 | ), 890 | }, 891 | ), 892 | RightLittleDistal: Some( 893 | HumanBone { 894 | extensions: None, 895 | extras: None, 896 | node: Some( 897 | 139, 898 | ), 899 | }, 900 | ), 901 | LeftIndexIntermediate: Some( 902 | HumanBone { 903 | extensions: None, 904 | extras: None, 905 | node: Some( 906 | 100, 907 | ), 908 | }, 909 | ), 910 | UpperChest: Some( 911 | HumanBone { 912 | extensions: None, 913 | extras: None, 914 | node: Some( 915 | 23, 916 | ), 917 | }, 918 | ), 919 | LeftUpperLeg: Some( 920 | HumanBone { 921 | extensions: None, 922 | extras: None, 923 | node: Some( 924 | 160, 925 | ), 926 | }, 927 | ), 928 | }, 929 | ), 930 | }, 931 | look_at: Some( 932 | LookAt { 933 | extensions: None, 934 | extras: None, 935 | offset_from_head_bone: Some( 936 | [ 937 | 0.059, 938 | 0.061, 939 | 0.0, 940 | ], 941 | ), 942 | range_map_horizontal_inner: Some( 943 | LookAtRangeMap { 944 | extensions: None, 945 | extras: None, 946 | input_max_value: Some( 947 | 90.0, 948 | ), 949 | output_scale: Some( 950 | 10.0, 951 | ), 952 | }, 953 | ), 954 | range_map_horizontal_outer: Some( 955 | LookAtRangeMap { 956 | extensions: None, 957 | extras: None, 958 | input_max_value: Some( 959 | 90.0, 960 | ), 961 | output_scale: Some( 962 | 10.0, 963 | ), 964 | }, 965 | ), 966 | range_map_vertical_down: Some( 967 | LookAtRangeMap { 968 | extensions: None, 969 | extras: None, 970 | input_max_value: Some( 971 | 90.0, 972 | ), 973 | output_scale: Some( 974 | 10.0, 975 | ), 976 | }, 977 | ), 978 | range_map_vertical_up: Some( 979 | LookAtRangeMap { 980 | extensions: None, 981 | extras: None, 982 | input_max_value: Some( 983 | 90.0, 984 | ), 985 | output_scale: Some( 986 | 10.0, 987 | ), 988 | }, 989 | ), 990 | look_at_type: Some( 991 | Bone, 992 | ), 993 | }, 994 | ), 995 | meta: Meta { 996 | allow_antisocial_or_hate_usage: Some( 997 | false, 998 | ), 999 | allow_excessively_sexual_usage: Some( 1000 | true, 1001 | ), 1002 | allow_excessively_violent_usage: Some( 1003 | true, 1004 | ), 1005 | allow_political_or_religious_usage: Some( 1006 | true, 1007 | ), 1008 | allow_redistribution: Some( 1009 | true, 1010 | ), 1011 | authors: [ 1012 | "pixiv Inc.", 1013 | ], 1014 | avatar_permission: Some( 1015 | Everyone, 1016 | ), 1017 | commercial_usage: Some( 1018 | Corporation, 1019 | ), 1020 | contact_information: None, 1021 | copyright_information: Some( 1022 | "(c) 2022 pixiv Inc.", 1023 | ), 1024 | credit_notation: Some( 1025 | Unnecessary, 1026 | ), 1027 | extensions: None, 1028 | extras: None, 1029 | license_url: "https://vrm.dev/licenses/1.0/", 1030 | modification: Some( 1031 | AllowModificationRedistribution, 1032 | ), 1033 | name: "VRM1_Constraint_Twist_Sample", 1034 | other_license_url: None, 1035 | references: None, 1036 | third_party_licenses: None, 1037 | thumbnail_image: Some( 1038 | 18, 1039 | ), 1040 | version: Some( 1041 | "v1.0.1", 1042 | ), 1043 | }, 1044 | spec_version: "1.0", 1045 | } 1046 | -------------------------------------------------------------------------------- /crates/vrm-spec/tests/snapshots/test__vrm_animation.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/vrm-spec/tests/test.rs 3 | assertion_line: 44 4 | expression: vrmc_vrm_animation 5 | --- 6 | VRMCVrmAnimationSchema { 7 | spec_version: "1.0", 8 | humanoid: Some( 9 | VRMCVrmAnimationHumanoid { 10 | human_bones: VRMCVrmAnimationHumanBones( 11 | { 12 | Chest: Some( 13 | VRMCVrmAnimationHumanBone { 14 | node: Some( 15 | 2, 16 | ), 17 | extensions: None, 18 | extras: None, 19 | }, 20 | ), 21 | LeftRingIntermediate: Some( 22 | VRMCVrmAnimationHumanBone { 23 | node: Some( 24 | 19, 25 | ), 26 | extensions: None, 27 | extras: None, 28 | }, 29 | ), 30 | RightShoulder: Some( 31 | VRMCVrmAnimationHumanBone { 32 | node: Some( 33 | 24, 34 | ), 35 | extensions: None, 36 | extras: None, 37 | }, 38 | ), 39 | RightLowerArm: Some( 40 | VRMCVrmAnimationHumanBone { 41 | node: Some( 42 | 26, 43 | ), 44 | extensions: None, 45 | extras: None, 46 | }, 47 | ), 48 | LeftLittleIntermediate: Some( 49 | VRMCVrmAnimationHumanBone { 50 | node: Some( 51 | 22, 52 | ), 53 | extensions: None, 54 | extras: None, 55 | }, 56 | ), 57 | RightFoot: Some( 58 | VRMCVrmAnimationHumanBone { 59 | node: Some( 60 | 49, 61 | ), 62 | extensions: None, 63 | extras: None, 64 | }, 65 | ), 66 | RightThumbProximal: Some( 67 | VRMCVrmAnimationHumanBone { 68 | node: Some( 69 | 29, 70 | ), 71 | extensions: None, 72 | extras: None, 73 | }, 74 | ), 75 | LeftThumbDistal: Some( 76 | VRMCVrmAnimationHumanBone { 77 | node: Some( 78 | 11, 79 | ), 80 | extensions: None, 81 | extras: None, 82 | }, 83 | ), 84 | RightMiddleIntermediate: Some( 85 | VRMCVrmAnimationHumanBone { 86 | node: Some( 87 | 35, 88 | ), 89 | extensions: None, 90 | extras: None, 91 | }, 92 | ), 93 | LeftLowerLeg: Some( 94 | VRMCVrmAnimationHumanBone { 95 | node: Some( 96 | 44, 97 | ), 98 | extensions: None, 99 | extras: None, 100 | }, 101 | ), 102 | RightIndexIntermediate: Some( 103 | VRMCVrmAnimationHumanBone { 104 | node: Some( 105 | 32, 106 | ), 107 | extensions: None, 108 | extras: None, 109 | }, 110 | ), 111 | LeftHand: Some( 112 | VRMCVrmAnimationHumanBone { 113 | node: Some( 114 | 8, 115 | ), 116 | extensions: None, 117 | extras: None, 118 | }, 119 | ), 120 | RightUpperLeg: Some( 121 | VRMCVrmAnimationHumanBone { 122 | node: Some( 123 | 47, 124 | ), 125 | extensions: None, 126 | extras: None, 127 | }, 128 | ), 129 | LeftToes: Some( 130 | VRMCVrmAnimationHumanBone { 131 | node: Some( 132 | 46, 133 | ), 134 | extensions: None, 135 | extras: None, 136 | }, 137 | ), 138 | LeftMiddleProximal: Some( 139 | VRMCVrmAnimationHumanBone { 140 | node: Some( 141 | 15, 142 | ), 143 | extensions: None, 144 | extras: None, 145 | }, 146 | ), 147 | RightRingIntermediate: Some( 148 | VRMCVrmAnimationHumanBone { 149 | node: Some( 150 | 38, 151 | ), 152 | extensions: None, 153 | extras: None, 154 | }, 155 | ), 156 | RightLittleIntermediate: Some( 157 | VRMCVrmAnimationHumanBone { 158 | node: Some( 159 | 41, 160 | ), 161 | extensions: None, 162 | extras: None, 163 | }, 164 | ), 165 | LeftIndexProximal: Some( 166 | VRMCVrmAnimationHumanBone { 167 | node: Some( 168 | 12, 169 | ), 170 | extensions: None, 171 | extras: None, 172 | }, 173 | ), 174 | Head: Some( 175 | VRMCVrmAnimationHumanBone { 176 | node: Some( 177 | 4, 178 | ), 179 | extensions: None, 180 | extras: None, 181 | }, 182 | ), 183 | Neck: Some( 184 | VRMCVrmAnimationHumanBone { 185 | node: Some( 186 | 3, 187 | ), 188 | extensions: None, 189 | extras: None, 190 | }, 191 | ), 192 | LeftRingProximal: Some( 193 | VRMCVrmAnimationHumanBone { 194 | node: Some( 195 | 18, 196 | ), 197 | extensions: None, 198 | extras: None, 199 | }, 200 | ), 201 | RightThumbDistal: Some( 202 | VRMCVrmAnimationHumanBone { 203 | node: Some( 204 | 30, 205 | ), 206 | extensions: None, 207 | extras: None, 208 | }, 209 | ), 210 | RightLowerLeg: Some( 211 | VRMCVrmAnimationHumanBone { 212 | node: Some( 213 | 48, 214 | ), 215 | extensions: None, 216 | extras: None, 217 | }, 218 | ), 219 | LeftLittleProximal: Some( 220 | VRMCVrmAnimationHumanBone { 221 | node: Some( 222 | 21, 223 | ), 224 | extensions: None, 225 | extras: None, 226 | }, 227 | ), 228 | RightHand: Some( 229 | VRMCVrmAnimationHumanBone { 230 | node: Some( 231 | 27, 232 | ), 233 | extensions: None, 234 | extras: None, 235 | }, 236 | ), 237 | LeftThumbMetacarpal: Some( 238 | VRMCVrmAnimationHumanBone { 239 | node: Some( 240 | 9, 241 | ), 242 | extensions: None, 243 | extras: None, 244 | }, 245 | ), 246 | RightToes: Some( 247 | VRMCVrmAnimationHumanBone { 248 | node: Some( 249 | 50, 250 | ), 251 | extensions: None, 252 | extras: None, 253 | }, 254 | ), 255 | RightMiddleProximal: Some( 256 | VRMCVrmAnimationHumanBone { 257 | node: Some( 258 | 34, 259 | ), 260 | extensions: None, 261 | extras: None, 262 | }, 263 | ), 264 | LeftMiddleDistal: Some( 265 | VRMCVrmAnimationHumanBone { 266 | node: Some( 267 | 17, 268 | ), 269 | extensions: None, 270 | extras: None, 271 | }, 272 | ), 273 | LeftIndexDistal: Some( 274 | VRMCVrmAnimationHumanBone { 275 | node: Some( 276 | 14, 277 | ), 278 | extensions: None, 279 | extras: None, 280 | }, 281 | ), 282 | RightIndexProximal: Some( 283 | VRMCVrmAnimationHumanBone { 284 | node: Some( 285 | 31, 286 | ), 287 | extensions: None, 288 | extras: None, 289 | }, 290 | ), 291 | LeftUpperArm: Some( 292 | VRMCVrmAnimationHumanBone { 293 | node: Some( 294 | 6, 295 | ), 296 | extensions: None, 297 | extras: None, 298 | }, 299 | ), 300 | Spine: Some( 301 | VRMCVrmAnimationHumanBone { 302 | node: Some( 303 | 1, 304 | ), 305 | extensions: None, 306 | extras: None, 307 | }, 308 | ), 309 | RightRingProximal: Some( 310 | VRMCVrmAnimationHumanBone { 311 | node: Some( 312 | 37, 313 | ), 314 | extensions: None, 315 | extras: None, 316 | }, 317 | ), 318 | LeftRingDistal: Some( 319 | VRMCVrmAnimationHumanBone { 320 | node: Some( 321 | 20, 322 | ), 323 | extensions: None, 324 | extras: None, 325 | }, 326 | ), 327 | LeftLittleDistal: Some( 328 | VRMCVrmAnimationHumanBone { 329 | node: Some( 330 | 23, 331 | ), 332 | extensions: None, 333 | extras: None, 334 | }, 335 | ), 336 | RightLittleProximal: Some( 337 | VRMCVrmAnimationHumanBone { 338 | node: Some( 339 | 40, 340 | ), 341 | extensions: None, 342 | extras: None, 343 | }, 344 | ), 345 | Hips: Some( 346 | VRMCVrmAnimationHumanBone { 347 | node: Some( 348 | 0, 349 | ), 350 | extensions: None, 351 | extras: None, 352 | }, 353 | ), 354 | RightThumbMetacarpal: Some( 355 | VRMCVrmAnimationHumanBone { 356 | node: Some( 357 | 28, 358 | ), 359 | extensions: None, 360 | extras: None, 361 | }, 362 | ), 363 | LeftShoulder: Some( 364 | VRMCVrmAnimationHumanBone { 365 | node: Some( 366 | 5, 367 | ), 368 | extensions: None, 369 | extras: None, 370 | }, 371 | ), 372 | LeftLowerArm: Some( 373 | VRMCVrmAnimationHumanBone { 374 | node: Some( 375 | 7, 376 | ), 377 | extensions: None, 378 | extras: None, 379 | }, 380 | ), 381 | RightMiddleDistal: Some( 382 | VRMCVrmAnimationHumanBone { 383 | node: Some( 384 | 36, 385 | ), 386 | extensions: None, 387 | extras: None, 388 | }, 389 | ), 390 | RightIndexDistal: Some( 391 | VRMCVrmAnimationHumanBone { 392 | node: Some( 393 | 33, 394 | ), 395 | extensions: None, 396 | extras: None, 397 | }, 398 | ), 399 | LeftFoot: Some( 400 | VRMCVrmAnimationHumanBone { 401 | node: Some( 402 | 45, 403 | ), 404 | extensions: None, 405 | extras: None, 406 | }, 407 | ), 408 | RightUpperArm: Some( 409 | VRMCVrmAnimationHumanBone { 410 | node: Some( 411 | 25, 412 | ), 413 | extensions: None, 414 | extras: None, 415 | }, 416 | ), 417 | LeftThumbProximal: Some( 418 | VRMCVrmAnimationHumanBone { 419 | node: Some( 420 | 10, 421 | ), 422 | extensions: None, 423 | extras: None, 424 | }, 425 | ), 426 | LeftMiddleIntermediate: Some( 427 | VRMCVrmAnimationHumanBone { 428 | node: Some( 429 | 16, 430 | ), 431 | extensions: None, 432 | extras: None, 433 | }, 434 | ), 435 | RightRingDistal: Some( 436 | VRMCVrmAnimationHumanBone { 437 | node: Some( 438 | 39, 439 | ), 440 | extensions: None, 441 | extras: None, 442 | }, 443 | ), 444 | RightLittleDistal: Some( 445 | VRMCVrmAnimationHumanBone { 446 | node: Some( 447 | 42, 448 | ), 449 | extensions: None, 450 | extras: None, 451 | }, 452 | ), 453 | LeftIndexIntermediate: Some( 454 | VRMCVrmAnimationHumanBone { 455 | node: Some( 456 | 13, 457 | ), 458 | extensions: None, 459 | extras: None, 460 | }, 461 | ), 462 | LeftUpperLeg: Some( 463 | VRMCVrmAnimationHumanBone { 464 | node: Some( 465 | 43, 466 | ), 467 | extensions: None, 468 | extras: None, 469 | }, 470 | ), 471 | }, 472 | ), 473 | extensions: None, 474 | extras: None, 475 | }, 476 | ), 477 | expressions: Some( 478 | VRMCVrmAnimationExpressions { 479 | preset: Some( 480 | VRMCVrmAnimationExpressionPreset( 481 | { 482 | Happy: VRMCVrmAnimationExpression { 483 | node: Some( 484 | 51, 485 | ), 486 | extensions: None, 487 | extras: None, 488 | }, 489 | }, 490 | ), 491 | ), 492 | custom: None, 493 | extensions: None, 494 | extras: None, 495 | }, 496 | ), 497 | look_at: Some( 498 | VRMCVrmAnimationLookAt { 499 | node: Some( 500 | 52, 501 | ), 502 | offset_from_head_bone: None, 503 | extensions: None, 504 | extras: None, 505 | }, 506 | ), 507 | extensions: None, 508 | extras: None, 509 | } 510 | -------------------------------------------------------------------------------- /crates/vrm-spec/tests/test.rs: -------------------------------------------------------------------------------- 1 | use vrm_spec::{vrm_0_0, vrmc_spring_bone_1_0, vrmc_vrm_1_0, vrmc_vrm_animation_1_0}; 2 | 3 | #[test] 4 | fn test_vrm0() { 5 | let file = include_bytes!("../../../fixtures/AvatarSample_A.vrm"); 6 | let (doc, _, _) = gltf::import_slice(file).expect("ok"); 7 | let extensions = doc.extension_value(vrm_0_0::VRM).expect("exist"); 8 | let vrm: vrm_0_0::VRM0Schema = serde_json::from_value(extensions.to_owned()).expect("ok"); 9 | 10 | insta::assert_debug_snapshot!(vrm); 11 | } 12 | 13 | #[test] 14 | fn test_vrm1() { 15 | let file = include_bytes!("../../../fixtures/VRM1_Constraint_Twist_Sample.vrm"); 16 | let (doc, _, _) = gltf::import_slice(file).expect("ok"); 17 | let value = doc.extension_value(vrmc_vrm_1_0::VRMC_VRM).expect("exist"); 18 | let vrmc_vrm: vrmc_vrm_1_0::VRMCVrmSchema = 19 | serde_json::from_value(value.to_owned()).expect("ok"); 20 | 21 | insta::assert_debug_snapshot!(vrmc_vrm); 22 | 23 | let value = doc 24 | .extension_value(vrmc_spring_bone_1_0::VRMC_SPRING_BONE) 25 | .expect("exist"); 26 | 27 | let vrmc_spring_bone: vrmc_spring_bone_1_0::VrmcSpringBoneSchema = 28 | serde_json::from_value(value.to_owned()).expect("ok"); 29 | 30 | insta::assert_debug_snapshot!(vrmc_spring_bone); 31 | } 32 | 33 | #[test] 34 | fn test_vrm_animation() { 35 | let file = include_bytes!("../../../fixtures/test.vrma"); 36 | let (doc, _, _) = gltf::import_slice(file).expect("ok"); 37 | let value = doc 38 | .extension_value(vrmc_vrm_animation_1_0::VRMC_VRM_ANIMATION) 39 | .expect("exist"); 40 | 41 | let vrmc_vrm_animation: vrmc_vrm_animation_1_0::VRMCVrmAnimationSchema = 42 | serde_json::from_value(value.to_owned()).expect("ok"); 43 | 44 | insta::assert_debug_snapshot!(vrmc_vrm_animation); 45 | } 46 | -------------------------------------------------------------------------------- /deny.toml: -------------------------------------------------------------------------------- 1 | # This template contains all of the possible sections and their default values 2 | 3 | # Note that all fields that take a lint level have these possible values: 4 | # * deny - An error will be produced and the check will fail 5 | # * warn - A warning will be produced, but the check will not fail 6 | # * allow - No warning or error will be produced, though in some cases a note 7 | # will be 8 | 9 | # The values provided in this template are the default values that will be used 10 | # when any section or field is not specified in your own configuration 11 | 12 | # Root options 13 | 14 | # The graph table configures how the dependency graph is constructed and thus 15 | # which crates the checks are performed against 16 | [graph] 17 | # If 1 or more target triples (and optionally, target_features) are specified, 18 | # only the specified targets will be checked when running `cargo deny check`. 19 | # This means, if a particular package is only ever used as a target specific 20 | # dependency, such as, for example, the `nix` crate only being used via the 21 | # `target_family = "unix"` configuration, that only having windows targets in 22 | # this list would mean the nix crate, as well as any of its exclusive 23 | # dependencies not shared by any other crates, would be ignored, as the target 24 | # list here is effectively saying which targets you are building for. 25 | targets = [ 26 | # The triple can be any string, but only the target triples built in to 27 | # rustc (as of 1.40) can be checked against actual config expressions 28 | #"x86_64-unknown-linux-musl", 29 | # You can also specify which target_features you promise are enabled for a 30 | # particular target. target_features are currently not validated against 31 | # the actual valid features supported by the target architecture. 32 | #{ triple = "wasm32-unknown-unknown", features = ["atomics"] }, 33 | ] 34 | # When creating the dependency graph used as the source of truth when checks are 35 | # executed, this field can be used to prune crates from the graph, removing them 36 | # from the view of cargo-deny. This is an extremely heavy hammer, as if a crate 37 | # is pruned from the graph, all of its dependencies will also be pruned unless 38 | # they are connected to another crate in the graph that hasn't been pruned, 39 | # so it should be used with care. The identifiers are [Package ID Specifications] 40 | # (https://doc.rust-lang.org/cargo/reference/pkgid-spec.html) 41 | #exclude = [] 42 | # If true, metadata will be collected with `--all-features`. Note that this can't 43 | # be toggled off if true, if you want to conditionally enable `--all-features` it 44 | # is recommended to pass `--all-features` on the cmd line instead 45 | all-features = false 46 | # If true, metadata will be collected with `--no-default-features`. The same 47 | # caveat with `all-features` applies 48 | no-default-features = false 49 | # If set, these feature will be enabled when collecting metadata. If `--features` 50 | # is specified on the cmd line they will take precedence over this option. 51 | #features = [] 52 | 53 | # The output table provides options for how/if diagnostics are outputted 54 | [output] 55 | # When outputting inclusion graphs in diagnostics that include features, this 56 | # option can be used to specify the depth at which feature edges will be added. 57 | # This option is included since the graphs can be quite large and the addition 58 | # of features from the crate(s) to all of the graph roots can be far too verbose. 59 | # This option can be overridden via `--feature-depth` on the cmd line 60 | feature-depth = 1 61 | 62 | # This section is considered when running `cargo deny check advisories` 63 | # More documentation for the advisories section can be found here: 64 | # https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html 65 | [advisories] 66 | # The path where the advisory databases are cloned/fetched into 67 | #db-path = "$CARGO_HOME/advisory-dbs" 68 | # The url(s) of the advisory databases to use 69 | #db-urls = ["https://github.com/rustsec/advisory-db"] 70 | # A list of advisory IDs to ignore. Note that ignored advisories will still 71 | # output a note when they are encountered. 72 | ignore = [ 73 | #"RUSTSEC-0000-0000", 74 | #{ id = "RUSTSEC-0000-0000", reason = "you can specify a reason the advisory is ignored" }, 75 | #"a-crate-that-is-yanked@0.1.1", # you can also ignore yanked crate versions if you wish 76 | #{ crate = "a-crate-that-is-yanked@0.1.1", reason = "you can specify why you are ignoring the yanked crate" }, 77 | ] 78 | # If this is true, then cargo deny will use the git executable to fetch advisory database. 79 | # If this is false, then it uses a built-in git library. 80 | # Setting this to true can be helpful if you have special authentication requirements that cargo-deny does not support. 81 | # See Git Authentication for more information about setting up git authentication. 82 | #git-fetch-with-cli = true 83 | 84 | # This section is considered when running `cargo deny check licenses` 85 | # More documentation for the licenses section can be found here: 86 | # https://embarkstudios.github.io/cargo-deny/checks/licenses/cfg.html 87 | [licenses] 88 | # List of explicitly allowed licenses 89 | # See https://spdx.org/licenses/ for list of possible licenses 90 | # [possible values: any SPDX 3.11 short identifier (+ optional exception)]. 91 | allow = [ 92 | "MIT", 93 | "Apache-2.0", 94 | "Unicode-DFS-2016", #"Apache-2.0 WITH LLVM-exception", 95 | ] 96 | # The confidence threshold for detecting a license from license text. 97 | # The higher the value, the more closely the license text must be to the 98 | # canonical license text of a valid SPDX license file. 99 | # [possible values: any between 0.0 and 1.0]. 100 | confidence-threshold = 0.8 101 | # Allow 1 or more licenses on a per-crate basis, so that particular licenses 102 | # aren't accepted for every possible crate as with the normal allow list 103 | exceptions = [ 104 | # Each entry is the crate and version constraint, and its specific allow 105 | # list 106 | #{ allow = ["Zlib"], crate = "adler32" }, 107 | ] 108 | 109 | # Some crates don't have (easily) machine readable licensing information, 110 | # adding a clarification entry for it allows you to manually specify the 111 | # licensing information 112 | #[[licenses.clarify]] 113 | # The package spec the clarification applies to 114 | #crate = "ring" 115 | # The SPDX expression for the license requirements of the crate 116 | #expression = "MIT AND ISC AND OpenSSL" 117 | # One or more files in the crate's source used as the "source of truth" for 118 | # the license expression. If the contents match, the clarification will be used 119 | # when running the license check, otherwise the clarification will be ignored 120 | # and the crate will be checked normally, which may produce warnings or errors 121 | # depending on the rest of your configuration 122 | #license-files = [ 123 | # Each entry is a crate relative path, and the (opaque) hash of its contents 124 | #{ path = "LICENSE", hash = 0xbd0eed23 } 125 | #] 126 | 127 | [licenses.private] 128 | # If true, ignores workspace crates that aren't published, or are only 129 | # published to private registries. 130 | # To see how to mark a crate as unpublished (to the official registry), 131 | # visit https://doc.rust-lang.org/cargo/reference/manifest.html#the-publish-field. 132 | ignore = false 133 | # One or more private registries that you might publish crates to, if a crate 134 | # is only published to private registries, and ignore is true, the crate will 135 | # not have its license(s) checked 136 | registries = [ 137 | #"https://sekretz.com/registry 138 | ] 139 | 140 | # This section is considered when running `cargo deny check bans`. 141 | # More documentation about the 'bans' section can be found here: 142 | # https://embarkstudios.github.io/cargo-deny/checks/bans/cfg.html 143 | [bans] 144 | # Lint level for when multiple versions of the same crate are detected 145 | multiple-versions = "warn" 146 | # Lint level for when a crate version requirement is `*` 147 | wildcards = "allow" 148 | # The graph highlighting used when creating dotgraphs for crates 149 | # with multiple versions 150 | # * lowest-version - The path to the lowest versioned duplicate is highlighted 151 | # * simplest-path - The path to the version with the fewest edges is highlighted 152 | # * all - Both lowest-version and simplest-path are used 153 | highlight = "all" 154 | # The default lint level for `default` features for crates that are members of 155 | # the workspace that is being checked. This can be overridden by allowing/denying 156 | # `default` on a crate-by-crate basis if desired. 157 | workspace-default-features = "allow" 158 | # The default lint level for `default` features for external crates that are not 159 | # members of the workspace. This can be overridden by allowing/denying `default` 160 | # on a crate-by-crate basis if desired. 161 | external-default-features = "allow" 162 | # List of crates that are allowed. Use with care! 163 | allow = [ 164 | #"ansi_term@0.11.0", 165 | #{ crate = "ansi_term@0.11.0", reason = "you can specify a reason it is allowed" }, 166 | ] 167 | # List of crates to deny 168 | deny = [ 169 | #"ansi_term@0.11.0", 170 | #{ crate = "ansi_term@0.11.0", reason = "you can specify a reason it is banned" }, 171 | # Wrapper crates can optionally be specified to allow the crate when it 172 | # is a direct dependency of the otherwise banned crate 173 | #{ crate = "ansi_term@0.11.0", wrappers = ["this-crate-directly-depends-on-ansi_term"] }, 174 | ] 175 | 176 | # List of features to allow/deny 177 | # Each entry the name of a crate and a version range. If version is 178 | # not specified, all versions will be matched. 179 | #[[bans.features]] 180 | #crate = "reqwest" 181 | # Features to not allow 182 | #deny = ["json"] 183 | # Features to allow 184 | #allow = [ 185 | # "rustls", 186 | # "__rustls", 187 | # "__tls", 188 | # "hyper-rustls", 189 | # "rustls", 190 | # "rustls-pemfile", 191 | # "rustls-tls-webpki-roots", 192 | # "tokio-rustls", 193 | # "webpki-roots", 194 | #] 195 | # If true, the allowed features must exactly match the enabled feature set. If 196 | # this is set there is no point setting `deny` 197 | #exact = true 198 | 199 | # Certain crates/versions that will be skipped when doing duplicate detection. 200 | skip = [ 201 | #"ansi_term@0.11.0", 202 | #{ crate = "ansi_term@0.11.0", reason = "you can specify a reason why it can't be updated/removed" }, 203 | ] 204 | # Similarly to `skip` allows you to skip certain crates during duplicate 205 | # detection. Unlike skip, it also includes the entire tree of transitive 206 | # dependencies starting at the specified crate, up to a certain depth, which is 207 | # by default infinite. 208 | skip-tree = [ 209 | #"ansi_term@0.11.0", # will be skipped along with _all_ of its direct and transitive dependencies 210 | #{ crate = "ansi_term@0.11.0", depth = 20 }, 211 | ] 212 | 213 | # This section is considered when running `cargo deny check sources`. 214 | # More documentation about the 'sources' section can be found here: 215 | # https://embarkstudios.github.io/cargo-deny/checks/sources/cfg.html 216 | [sources] 217 | # Lint level for what to happen when a crate from a crate registry that is not 218 | # in the allow list is encountered 219 | unknown-registry = "warn" 220 | # Lint level for what to happen when a crate from a git repository that is not 221 | # in the allow list is encountered 222 | unknown-git = "warn" 223 | # List of URLs for allowed crate registries. Defaults to the crates.io index 224 | # if not specified. If it is specified but empty, no registries are allowed. 225 | allow-registry = ["https://github.com/rust-lang/crates.io-index"] 226 | # List of URLs for allowed Git repositories 227 | allow-git = [] 228 | 229 | [sources.allow-org] 230 | # 1 or more github.com organizations to allow git sources for 231 | # github = [""] 232 | # 1 or more gitlab.com organizations to allow git sources for 233 | # gitlab = [""] 234 | # 1 or more bitbucket.org organizations to allow git sources for 235 | # bitbucket = [""] 236 | -------------------------------------------------------------------------------- /examples/vrm-spec-example/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | edition.workspace = true 3 | license.workspace = true 4 | name = "vrm-spec-example" 5 | publish = false 6 | rust-version.workspace = true 7 | version = "0.0.0" 8 | 9 | [dependencies] 10 | gltf = {workspace = true, features = ["utils", "extensions"]} 11 | rustc-hash = {workspace = true} 12 | serde = {workspace = true} 13 | serde_json = {workspace = true} 14 | vrm-spec = {path = "../../crates/vrm-spec", features = ["gltf_index", "rustc_hash"]} 15 | -------------------------------------------------------------------------------- /examples/vrm-spec-example/README.md: -------------------------------------------------------------------------------- 1 | ```sh 2 | cargo run --example vrm-spec-example 3 | ``` 4 | -------------------------------------------------------------------------------- /examples/vrm-spec-example/examples/vrm-spec-example.rs: -------------------------------------------------------------------------------- 1 | use vrm_spec::vrmc_vrm_1_0::{Meta, VRMCVrmSchema, VRMC_VRM}; 2 | 3 | fn main() { 4 | let file = include_bytes!("../../../fixtures/VRM1_Constraint_Twist_Sample.vrm"); 5 | let (doc, _, _) = gltf::import_slice(file).expect("ok"); 6 | let extensions = doc.extension_value(VRMC_VRM).expect("exist"); 7 | let vrm: VRMCVrmSchema = serde_json::from_value(extensions.to_owned()).expect("ok"); 8 | 9 | let Meta { 10 | name, 11 | authors, 12 | allow_redistribution, 13 | copyright_information, 14 | commercial_usage, 15 | credit_notation, 16 | avatar_permission, 17 | .. 18 | } = vrm.meta; 19 | 20 | println!("Details of {}", name); 21 | println!("VRM spec version: {}", vrm.spec_version); 22 | println!("Authors"); 23 | for author in authors { 24 | println!("- {}", author); 25 | } 26 | println!("Licence"); 27 | println!( 28 | r#"- allow_redistribution: {} 29 | - copyright_information: {} 30 | - commercial_usage: {:?} 31 | - credit_notation: {:?} 32 | - avatar_permission: {:?}"#, 33 | allow_redistribution.unwrap_or(false), 34 | copyright_information.as_deref().unwrap_or("None"), 35 | commercial_usage, 36 | credit_notation, 37 | avatar_permission 38 | ) 39 | } 40 | -------------------------------------------------------------------------------- /fixtures/AvatarSample_A.vrm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pixiv/vrm-utils-rs/0ac94380a2dd56fb9fc0b1502e64fb2a98067c48/fixtures/AvatarSample_A.vrm -------------------------------------------------------------------------------- /fixtures/README.md: -------------------------------------------------------------------------------- 1 | - `VRM1_Constraint_Twist_Sample.vrm` from https://github.com/vrm-c/vrm-specification/tree/master/samples 2 | - `AvatarSample_A.vrm` from https://hub.vroid.com/en/characters/2843975675147313744/models/5644550979324015604 3 | -------------------------------------------------------------------------------- /fixtures/VRM1_Constraint_Twist_Sample.vrm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pixiv/vrm-utils-rs/0ac94380a2dd56fb9fc0b1502e64fb2a98067c48/fixtures/VRM1_Constraint_Twist_Sample.vrm -------------------------------------------------------------------------------- /fixtures/test.vrma: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pixiv/vrm-utils-rs/0ac94380a2dd56fb9fc0b1502e64fb2a98067c48/fixtures/test.vrma --------------------------------------------------------------------------------