├── .editorconfig ├── LICENSE ├── README.md ├── blender-plugin └── export-shapekeys.py ├── example.toml └── sk2aac ├── .gitignore ├── Cargo.lock ├── Cargo.toml └── src ├── codegen ├── aac.rs ├── mod.rs └── writer.rs ├── descriptor ├── mod.rs ├── raw.rs └── validation.rs └── main.rs /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: https://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | [*] 7 | indent_style = space 8 | indent_size = 4 9 | end_of_line = lf 10 | charset = utf-8 11 | trim_trailing_whitespace = true 12 | insert_final_newline = true 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SK2AAC ("Shape Keys" to "Animator As Code") 2 | -------------------------------------------------------------------------------- /blender-plugin/export-shapekeys.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | import bpy 3 | import json 4 | 5 | bl_info = { 6 | "name": "Shape Key Exporter", 7 | "author": "kb10uy", 8 | "version": (2, 0), 9 | "blender": (3, 2, 0), 10 | "location": "", 11 | "description": "", 12 | "category": "Object", 13 | } 14 | 15 | 16 | class ShapeKeyExporter(bpy.types.Operator): 17 | bl_idname = "kb10uy.shapekeyexporter_export" 18 | bl_label = "Export Shape Keys" 19 | bl_description = "Export shape keys as TOML" 20 | bl_options = {"REGISTER"} 21 | 22 | def execute(self, context: Any) -> set[str]: 23 | toml_string = "" 24 | 25 | toml_string += "name = \"AvatarName\"\n" 26 | toml_string += "\n" 27 | 28 | for selected_object in bpy.context.selected_objects: 29 | if selected_object.type != "MESH": 30 | self.report( 31 | {"WARNING"}, 32 | f"{selected_object.name} is not a Mesh Object, skipping..." 33 | ) 34 | continue 35 | mesh_object: bpy.types.Mesh = selected_object.data 36 | shape_key_blocks = mesh_object.shape_keys.key_blocks 37 | 38 | toml_string += "[[mesh_groups]]\n" 39 | toml_string += "mesh = \"Ungrouped\"\n" 40 | toml_string += f"mesh = \"{selected_object.name}\"\n" 41 | 42 | toml_string += "options = [\n" 43 | basis_skipped = False 44 | for name, shape_key in shape_key_blocks.items(): 45 | if not basis_skipped: 46 | basis_skipped = True 47 | continue 48 | if len(shape_key.data) == 0: 49 | self.report( 50 | {"DEBUG"}, 51 | f"{name} moves no vertex, skipping..." 52 | ) 53 | continue 54 | toml_string += f" {{ label = \"{name}\", shapes = [\"{name}\"] }},\n" 55 | 56 | toml_string += "]\n" 57 | toml_string += "\n" 58 | 59 | self.report({"INFO"}, toml_string) 60 | return {"FINISHED"} 61 | 62 | 63 | def register_menu(self: Any, context: Any): 64 | self.layout.operator(ShapeKeyExporter.bl_idname) 65 | 66 | 67 | def register(): 68 | bpy.utils.register_class(ShapeKeyExporter) 69 | bpy.types.VIEW3D_MT_object_context_menu.append(register_menu) 70 | 71 | 72 | def unregister(): 73 | bpy.utils.unregister_class(ShapeKeyExporter) 74 | bpy.types.VIEW3D_MT_object_context_menu.remove(register_menu) 75 | 76 | 77 | if __name__ == "__main__": 78 | register() 79 | -------------------------------------------------------------------------------- /example.toml: -------------------------------------------------------------------------------- 1 | # アバター名。クラス名などで使用される。空白不可。 2 | name = "AvatarName" 3 | 4 | # ----------------------------------------------------------------------------- 5 | 6 | # Int Parameter で駆動される、択一式のアニメーション。 7 | [[shape_groups]] 8 | # パラメーター名。レイヤー名にも使用される。 9 | name = "Eyelids" 10 | 11 | # 対象の SkinnedMeshRenderer の GameObject 名。 12 | mesh = "Face" 13 | 14 | # このグループのいずれかのオプションが選択されている場合、 15 | # VRC_AnimatorTrackingControl で目のトラッキングを停止する。 16 | prevent_eyelids = true 17 | 18 | # このグループのいずれかのオプションが選択されている場合、 19 | # VRC_AnimatorTrackingControl で目のトラッキングを停止する。 20 | # prevent_mouth = false 21 | 22 | # どのオプションも選択されていない場合のデフォルト値。 23 | # options で指定されていないものは無視される(書き込まれない)。 24 | # defaults = [{ shape = "eyelid_jito", value = 0.4 }] 25 | 26 | # 選択可能なオプションのリスト。文字列形式とテーブル形式で指定が可能。 27 | options = [ 28 | # ラベルと BlendShape の名前が一致していて value が 1.0 の場合。 29 | "eyelids_smile", 30 | 31 | # ラベルと BlendShape の名前が一致していて value を任意に指定する場合。 32 | { label = "eyelids_close", value = 0.5 }, 33 | 34 | # ラベルと異なる名前の BlendShape を駆動する場合。 35 | { label = "eyelids_close_1", shapes = [ 36 | { shape = "eyelids_close", value = 0.5 }, 37 | ] }, 38 | { label = "eyelids_close_2", shapes = [ 39 | { shape = "eyelids_close", value = 1.0 }, 40 | ] }, 41 | ] 42 | 43 | # ----------------------------------------------------------------------------- 44 | 45 | # Bool Parameter で駆動される、有効・無効を切り替えられるアニメーション。 46 | [[shape_switches]] 47 | name = "Cheek" 48 | mesh = "Face" 49 | # prevent_eyelids = false 50 | # prevent_mouth = false 51 | 52 | # 駆動する BlendShape 名。 53 | shape = "face_cheek" 54 | 55 | # 有効なときの値。 56 | # enabled_value = 0.6 57 | 58 | # 無効なときの値。 59 | # disabled_value = 0.3 60 | 61 | # ----------------------------------------------------------------------------- 62 | 63 | # ParameterDriver で別の Group などを駆動するレイヤー。 64 | [[drivers]] 65 | # レイヤー名。 66 | name = "FacialExpression" 67 | 68 | # 各オプションの情報。 69 | [[drivers.options]] 70 | 71 | # 表示されるステート名。 72 | label = "Smile" 73 | 74 | # 駆動対象。 75 | drives = [ 76 | { name = "Eyelids", label = "eyelids_smile" }, 77 | { name = "Cheek", enabled = true }, 78 | ] 79 | -------------------------------------------------------------------------------- /sk2aac/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /sk2aac/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 = "anyhow" 7 | version = "1.0.61" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "508b352bb5c066aac251f6daf6b36eccd03e8a88e8081cd44959ea277a3af9a8" 10 | 11 | [[package]] 12 | name = "proc-macro2" 13 | version = "1.0.43" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab" 16 | dependencies = [ 17 | "unicode-ident", 18 | ] 19 | 20 | [[package]] 21 | name = "quote" 22 | version = "1.0.21" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" 25 | dependencies = [ 26 | "proc-macro2", 27 | ] 28 | 29 | [[package]] 30 | name = "serde" 31 | version = "1.0.143" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "53e8e5d5b70924f74ff5c6d64d9a5acd91422117c60f48c4e07855238a254553" 34 | dependencies = [ 35 | "serde_derive", 36 | ] 37 | 38 | [[package]] 39 | name = "serde_derive" 40 | version = "1.0.143" 41 | source = "registry+https://github.com/rust-lang/crates.io-index" 42 | checksum = "d3d8e8de557aee63c26b85b947f5e59b690d0454c753f3adeb5cd7835ab88391" 43 | dependencies = [ 44 | "proc-macro2", 45 | "quote", 46 | "syn", 47 | ] 48 | 49 | [[package]] 50 | name = "sk2aac" 51 | version = "2.2.0" 52 | dependencies = [ 53 | "anyhow", 54 | "serde", 55 | "thiserror", 56 | "toml", 57 | ] 58 | 59 | [[package]] 60 | name = "syn" 61 | version = "1.0.99" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | checksum = "58dbef6ec655055e20b86b15a8cc6d439cca19b667537ac6a1369572d151ab13" 64 | dependencies = [ 65 | "proc-macro2", 66 | "quote", 67 | "unicode-ident", 68 | ] 69 | 70 | [[package]] 71 | name = "thiserror" 72 | version = "1.0.32" 73 | source = "registry+https://github.com/rust-lang/crates.io-index" 74 | checksum = "f5f6586b7f764adc0231f4c79be7b920e766bb2f3e51b3661cdb263828f19994" 75 | dependencies = [ 76 | "thiserror-impl", 77 | ] 78 | 79 | [[package]] 80 | name = "thiserror-impl" 81 | version = "1.0.32" 82 | source = "registry+https://github.com/rust-lang/crates.io-index" 83 | checksum = "12bafc5b54507e0149cdf1b145a5d80ab80a90bcd9275df43d4fff68460f6c21" 84 | dependencies = [ 85 | "proc-macro2", 86 | "quote", 87 | "syn", 88 | ] 89 | 90 | [[package]] 91 | name = "toml" 92 | version = "0.5.9" 93 | source = "registry+https://github.com/rust-lang/crates.io-index" 94 | checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" 95 | dependencies = [ 96 | "serde", 97 | ] 98 | 99 | [[package]] 100 | name = "unicode-ident" 101 | version = "1.0.3" 102 | source = "registry+https://github.com/rust-lang/crates.io-index" 103 | checksum = "c4f5b37a154999a8f3f98cc23a628d850e154479cd94decf3414696e12e31aaf" 104 | -------------------------------------------------------------------------------- /sk2aac/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sk2aac" 3 | version = "2.2.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | anyhow = "1.0.61" 8 | serde = { version = "1.0.140", features = ["derive"] } 9 | thiserror = "1.0.32" 10 | toml = "0.5.9" 11 | -------------------------------------------------------------------------------- /sk2aac/src/codegen/aac.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | codegen::CodeWriter, 3 | descriptor::{ 4 | Descriptor, Driver, ResolvedDrive, ResolvedDriver, ShapeKeyGroup, ShapeKeySwitch, 5 | }, 6 | }; 7 | 8 | use std::{ 9 | collections::HashMap, 10 | io::{prelude::*, Result as IoResult}, 11 | iter::{once, repeat, zip}, 12 | }; 13 | 14 | const ALIGN_UNIT: usize = 8; 15 | 16 | /// Reads the descriptor and generates AAC code. 17 | pub fn write_descriptor_code(writer: &mut W, descriptor: Descriptor) -> IoResult { 18 | let mut writer = CodeWriter::new(writer, 4); 19 | let class_name = format!("SK2AACGenerator_{}", descriptor.name); 20 | 21 | Preamble.write_into(&mut writer)?; 22 | writer.write_empty()?; 23 | CustomEditorClass::new(class_name.clone()).write_into(&mut writer)?; 24 | writer.write_empty()?; 25 | BehaviourClass::new(class_name.clone(), descriptor).write_into(&mut writer)?; 26 | 27 | Ok(class_name) 28 | } 29 | 30 | /// Emits piece of AAC code. 31 | trait AacObject { 32 | fn write_into(self, w: &mut CodeWriter) -> IoResult<()>; 33 | } 34 | 35 | /// Tracking layer. 36 | #[derive(Debug, Clone, Copy)] 37 | enum AnimationTarget { 38 | Eyelids, 39 | JawAndMouth, 40 | } 41 | 42 | impl AnimationTarget { 43 | fn tracking_element(&self) -> &str { 44 | match self { 45 | AnimationTarget::Eyelids => "TrackingElement.Eyes", 46 | AnimationTarget::JawAndMouth => "TrackingElement.Mouth", 47 | } 48 | } 49 | 50 | fn displayed_name(&self) -> &str { 51 | match self { 52 | AnimationTarget::Eyelids => "Eyes", 53 | AnimationTarget::JawAndMouth => "Mouth", 54 | } 55 | } 56 | } 57 | 58 | #[derive(Debug, Clone)] 59 | struct Preamble; 60 | 61 | impl AacObject for Preamble { 62 | fn write_into(self, w: &mut CodeWriter) -> IoResult<()> { 63 | w.write(r#"// This file is generated by sk2aac"#)?; 64 | w.write(r#"using UnityEngine;"#)?; 65 | w.with_ifdef("UNITY_EDITOR", |mut cw| { 66 | cw.write(r#"using UnityEditor;"#)?; 67 | cw.write(r#"using UnityEditor.Animations;"#)?; 68 | cw.write(r#"using VRC.SDK3.Avatars.Components;"#)?; 69 | cw.write(r#"using static AnimatorAsCode.V0.AacFlState;"#)?; 70 | cw.write(r#"using AnimatorAsCodeFramework.Examples;"#) 71 | }) 72 | } 73 | } 74 | 75 | /// `public class _Editor...` 76 | #[derive(Debug, Clone)] 77 | struct CustomEditorClass(String); 78 | 79 | impl CustomEditorClass { 80 | fn new(class_name: impl Into) -> Self { 81 | CustomEditorClass(class_name.into()) 82 | } 83 | } 84 | 85 | impl AacObject for CustomEditorClass { 86 | fn write_into(self, w: &mut CodeWriter) -> IoResult<()> { 87 | let class_name = self.0; 88 | 89 | w.with_ifdef("UNITY_EDITOR", |mut ce| { 90 | ce.write(format_args!(r#"[CustomEditor(typeof({class_name}))]"#))?; 91 | ce.write(format_args!(r#"public class {class_name}_Editor : Editor"#))?; 92 | ce.with_block(|mut ce| { 93 | ce.write(r#"public override void OnInspectorGUI()"#)?; 94 | ce.with_block(|mut ce| { 95 | ce.write(r#"base.OnInspectorGUI();"#)?; 96 | ce.write(format_args!(r#"var executor = target as {class_name};"#))?; 97 | ce.write(r#"if (GUILayout.Button("Generate"))"#)?; 98 | ce.write(r#"{"#)?; 99 | ce.write(r#" executor.GenerateAnimator();"#)?; 100 | ce.write(r#"}"#) 101 | }) 102 | }) 103 | }) 104 | } 105 | } 106 | 107 | /// `public class ` 108 | #[derive(Debug, Clone)] 109 | struct BehaviourClass { 110 | class_name: String, 111 | descriptor: Descriptor, 112 | } 113 | 114 | impl BehaviourClass { 115 | fn new(class_name: impl Into, descriptor: Descriptor) -> Self { 116 | BehaviourClass { 117 | class_name: class_name.into(), 118 | descriptor, 119 | } 120 | } 121 | } 122 | 123 | impl AacObject for BehaviourClass { 124 | fn write_into(self, w: &mut CodeWriter) -> IoResult<()> { 125 | let BehaviourClass { 126 | class_name, 127 | descriptor, 128 | } = self; 129 | 130 | let resolved_drivers: Vec<_> = descriptor 131 | .drivers 132 | .iter() 133 | .map(|d| ResolvedDriver::resolve(&descriptor, d)) 134 | .collect(); 135 | 136 | w.write(format_args!(r#"public class {class_name} : MonoBehaviour"#))?; 137 | w.with_block(|mut cw| { 138 | cw.write(r#"public AnimatorController TargetContainer;"#)?; 139 | cw.write(r#"public string AssetKey = "SK2AAC";"#)?; 140 | cw.write_empty()?; 141 | cw.write(r#"public void GenerateAnimator()"#)?; 142 | cw.with_block(|mut cw| { 143 | cw.write(r#"var avatarDescriptor = GetComponent();"#)?; 144 | cw.write(r#"var aac = AacExample.AnimatorAsCode("SK2AAC", avatarDescriptor, TargetContainer, AssetKey, AacExample.Options().WriteDefaultsOff());"#)?; 145 | cw.write(r#"// var fxDefault = aac.CreateMainFxLayer();"#)?; 146 | 147 | let eyelids_preventions = descriptor 148 | .shape_groups 149 | .iter() 150 | .filter_map(|g| { 151 | if g.common.prevent_eyelids { 152 | Some(ParameterType::Integer(g.common.name.clone())) 153 | } else { 154 | None 155 | } 156 | }) 157 | .chain(descriptor.shape_switches.iter().filter_map(|g| { 158 | if g.common.prevent_eyelids { 159 | Some(ParameterType::Bool(g.common.name.clone())) 160 | } else { 161 | None 162 | } 163 | })); 164 | cw.write_empty()?; 165 | PreventionLayer::new(AnimationTarget::Eyelids, eyelids_preventions) 166 | .write_into(&mut cw)?; 167 | 168 | let mouth_preventions = descriptor 169 | .shape_groups 170 | .iter() 171 | .filter_map(|g| { 172 | if g.common.prevent_mouth { 173 | Some(ParameterType::Integer(g.common.name.clone())) 174 | } else { 175 | None 176 | } 177 | }) 178 | .chain(descriptor.shape_switches.iter().filter_map(|g| { 179 | if g.common.prevent_mouth { 180 | Some(ParameterType::Bool(g.common.name.clone())) 181 | } else { 182 | None 183 | } 184 | })); 185 | cw.write_empty()?; 186 | PreventionLayer::new(AnimationTarget::JawAndMouth, mouth_preventions) 187 | .write_into(&mut cw)?; 188 | 189 | for switch in descriptor.shape_switches { 190 | cw.write_empty()?; 191 | ShapeKeySwitchLayer::new(switch).write_into(&mut cw)?; 192 | } 193 | for group in descriptor.shape_groups { 194 | cw.write_empty()?; 195 | ShapeKeyGroupLayer::new(group).write_into(&mut cw)?; 196 | } 197 | for driver in resolved_drivers { 198 | cw.write_empty()?; 199 | DriverLayer::new(driver).write_into(&mut cw)?; 200 | } 201 | Ok(()) 202 | }) 203 | }) 204 | } 205 | } 206 | 207 | /// `Blocks default animation...` 208 | #[derive(Debug, Clone)] 209 | struct PreventionLayer { 210 | target: AnimationTarget, 211 | params: Vec, 212 | } 213 | 214 | impl PreventionLayer { 215 | fn new( 216 | target: AnimationTarget, 217 | params: impl IntoIterator, 218 | ) -> PreventionLayer { 219 | PreventionLayer { 220 | target, 221 | params: params.into_iter().collect(), 222 | } 223 | } 224 | } 225 | 226 | impl AacObject for PreventionLayer { 227 | fn write_into(self, w: &mut CodeWriter) -> IoResult<()> { 228 | if self.params.is_empty() { 229 | return Ok(()); 230 | } 231 | 232 | let animated_condition = Cond::Or( 233 | self.params 234 | .iter() 235 | .filter_map(|p| match p { 236 | ParameterType::Bool(p) => Some(Cond::Term(Expr::IsTrue(format!("param{p}")))), 237 | ParameterType::Integer(p) => { 238 | Some(Cond::Term(Expr::IntNotEqual(format!("param{p}"), 0))) 239 | } 240 | _ => None, 241 | }) 242 | .collect(), 243 | ); 244 | let tracking_condition = Cond::And( 245 | self.params 246 | .iter() 247 | .filter_map(|p| match p { 248 | ParameterType::Bool(p) => Some(Cond::Term(Expr::IsFalse(format!("param{p}")))), 249 | ParameterType::Integer(p) => { 250 | Some(Cond::Term(Expr::IntEqual(format!("param{p}"), 0))) 251 | } 252 | _ => None, 253 | }) 254 | .collect(), 255 | ); 256 | 257 | w.write(format_args!(r#"// Prevents Animation"#))?; 258 | w.with_block(|mut b| { 259 | LayerDefinition::new(format!("{}_TrackingControl", self.target.displayed_name())) 260 | .write_into(&mut b)?; 261 | 262 | for param in self.params { 263 | let var_name = match ¶m { 264 | ParameterType::Bool(p) => format!("param{p}"), 265 | ParameterType::Integer(p) => format!("param{p}"), 266 | _ => continue, 267 | }; 268 | ParameterDefinition::new(param) 269 | .var_name(var_name) 270 | .write_into(&mut b)?; 271 | } 272 | b.write_empty()?; 273 | 274 | // States 275 | StateDefinition::new("tracking", "Tracking").write_into(&mut b)?; 276 | StateOptions::new("tracking") 277 | .tracks(self.target) 278 | .write_into(&mut b)?; 279 | StateDefinition::new("animated", "Animated").write_into(&mut b)?; 280 | StateOptions::new("animated") 281 | .animates(self.target) 282 | .write_into(&mut b)?; 283 | b.write_empty()?; 284 | 285 | // Transitions 286 | Transition::new("tracking", "animated") 287 | .cond(animated_condition) 288 | .write_into(&mut b)?; 289 | Transition::new("animated", "tracking") 290 | .cond(tracking_condition) 291 | .write_into(&mut b) 292 | }) 293 | } 294 | } 295 | 296 | /// `// Shape Key Switch ...` 297 | #[derive(Debug, Clone)] 298 | struct ShapeKeySwitchLayer(ShapeKeySwitch); 299 | 300 | impl ShapeKeySwitchLayer { 301 | fn new(switch: ShapeKeySwitch) -> Self { 302 | ShapeKeySwitchLayer(switch) 303 | } 304 | } 305 | 306 | impl AacObject for ShapeKeySwitchLayer { 307 | fn write_into(self, w: &mut CodeWriter) -> IoResult<()> { 308 | let switch = self.0; 309 | 310 | w.write(format_args!( 311 | r#"// Shape Key Switch "{}""#, 312 | switch.common.name 313 | ))?; 314 | w.with_block(|mut b| { 315 | LayerDefinition::new(format!("{}", switch.common.name)).write_into(&mut b)?; 316 | RendererFetch::new(switch.common.mesh).write_into(&mut b)?; 317 | ParameterDefinition::bool(switch.common.name).write_into(&mut b)?; 318 | b.write_empty()?; 319 | 320 | // States 321 | StateDefinition::new("disabled", "false: Disabled") 322 | .blend_shapes([(switch.shape.clone(), switch.disabled_value.get())]) 323 | .write_into(&mut b)?; 324 | StateDefinition::new("enabled", "true: Enabled") 325 | .blend_shapes([(switch.shape.clone(), switch.enabled_value.get())]) 326 | .write_into(&mut b)?; 327 | b.write_empty()?; 328 | 329 | // Transitions 330 | Transition::new("disabled", "enabled") 331 | .cond(Cond::Term(Expr::IsTrue( 332 | ParameterDefinition::DEFAULT_VARNAME.into(), 333 | ))) 334 | .write_into(&mut b)?; 335 | Transition::new("enabled", "disabled") 336 | .cond(Cond::Term(Expr::IsFalse( 337 | ParameterDefinition::DEFAULT_VARNAME.into(), 338 | ))) 339 | .write_into(&mut b) 340 | }) 341 | } 342 | } 343 | 344 | /// `// Shape Key Group ...` 345 | #[derive(Debug, Clone)] 346 | struct ShapeKeyGroupLayer(ShapeKeyGroup); 347 | 348 | impl ShapeKeyGroupLayer { 349 | fn new(group: ShapeKeyGroup) -> Self { 350 | ShapeKeyGroupLayer(group) 351 | } 352 | } 353 | 354 | impl AacObject for ShapeKeyGroupLayer { 355 | fn write_into(self, w: &mut CodeWriter) -> IoResult<()> { 356 | let group = self.0; 357 | 358 | let default_values: HashMap<_, _> = group 359 | .defaults 360 | .into_iter() 361 | .map(|d| (d.shape, d.value.get())) 362 | .collect(); 363 | let mut drive_names: Vec<_> = group 364 | .options 365 | .iter() 366 | .map(|o| o.shapes.iter()) 367 | .flatten() 368 | .map(|d| d.shape.clone()) 369 | .collect(); 370 | drive_names.sort(); 371 | drive_names.dedup(); 372 | let default_drives = drive_names.into_iter().map(|n| { 373 | let value = default_values.get(&n).copied().unwrap_or(0.0); 374 | (n, value) 375 | }); 376 | 377 | w.write(format_args!( 378 | r#"// Shape Key Switch "{}""#, 379 | group.common.name 380 | ))?; 381 | w.with_block(|mut b| { 382 | LayerDefinition::new(format!("{}", group.common.name)).write_into(&mut b)?; 383 | RendererFetch::new(group.common.mesh).write_into(&mut b)?; 384 | ParameterDefinition::integer(group.common.name).write_into(&mut b)?; 385 | b.write_empty()?; 386 | 387 | StateDefinition::new("disabled", "0: Disabled") 388 | .blend_shapes(default_drives) 389 | .indented() 390 | .write_into(&mut b)?; 391 | 392 | // TODO: Check id duplicate 393 | let mut right_of = "disabled".to_string(); 394 | for (i, option) in group.options.into_iter().enumerate() { 395 | let index = option.index.map(|i| i.get()).unwrap_or(i + 1); 396 | 397 | let state_name = format!("enabled{index}"); 398 | let state_label = format!("{index}: {}", option.label); 399 | let blend_shapes = option.shapes.into_iter().map(|d| (d.shape, d.value.get())); 400 | 401 | b.write_empty()?; 402 | 403 | // State 404 | let mut statedef = StateDefinition::new(state_name.clone(), state_label) 405 | .blend_shapes(blend_shapes); 406 | if i % ALIGN_UNIT == 0 { 407 | statedef = statedef.right_of(right_of); 408 | right_of = state_name.clone(); 409 | } 410 | statedef.write_into(&mut b)?; 411 | 412 | // Transitions 413 | Transition::new("disabled", state_name.clone()) 414 | .cond(Cond::Term(Expr::IntEqual( 415 | ParameterDefinition::DEFAULT_VARNAME.into(), 416 | index, 417 | ))) 418 | .write_into(&mut b)?; 419 | Transition::exits(state_name.clone()) 420 | .cond(Cond::Term(Expr::IntNotEqual( 421 | ParameterDefinition::DEFAULT_VARNAME.into(), 422 | index, 423 | ))) 424 | .write_into(&mut b)?; 425 | } 426 | Ok(()) 427 | }) 428 | } 429 | } 430 | 431 | /// `var layer = ...` 432 | #[derive(Debug, Clone)] 433 | struct LayerDefinition(String); 434 | 435 | impl LayerDefinition { 436 | fn new(name: impl Into) -> Self { 437 | LayerDefinition(name.into()) 438 | } 439 | } 440 | 441 | impl AacObject for LayerDefinition { 442 | fn write_into(self, w: &mut CodeWriter) -> IoResult<()> { 443 | let layer_name = self.0; 444 | 445 | w.write_yield(|w| { 446 | write!( 447 | w, 448 | r#"var layer = aac.CreateSupportingFxLayer("{layer_name}");"# 449 | ) 450 | }) 451 | } 452 | } 453 | 454 | /// `var renderer = ...` 455 | #[derive(Debug, Clone)] 456 | struct RendererFetch(String); 457 | 458 | impl RendererFetch { 459 | fn new(name: impl Into) -> Self { 460 | RendererFetch(name.into()) 461 | } 462 | } 463 | 464 | impl AacObject for RendererFetch { 465 | fn write_into(self, w: &mut CodeWriter) -> IoResult<()> { 466 | let object_name = self.0; 467 | 468 | w.write_yield(|w| { 469 | write!( 470 | w, 471 | r#"var renderer = (SkinnedMeshRenderer) gameObject.transform.Find("{object_name}").GetComponent();"# 472 | ) 473 | }) 474 | } 475 | } 476 | 477 | /// `var parameter = ...` 478 | #[derive(Debug, Clone)] 479 | struct ParameterDefinition { 480 | var_name: String, 481 | param_type: ParameterType, 482 | } 483 | 484 | impl ParameterDefinition { 485 | const DEFAULT_VARNAME: &'static str = "parameter"; 486 | 487 | fn new(param_type: ParameterType) -> ParameterDefinition { 488 | ParameterDefinition { 489 | var_name: Self::DEFAULT_VARNAME.into(), 490 | param_type, 491 | } 492 | } 493 | 494 | fn var_name(mut self, var_name: impl Into) -> ParameterDefinition { 495 | self.var_name = var_name.into(); 496 | self 497 | } 498 | 499 | fn integer(name: impl Into) -> ParameterDefinition { 500 | ParameterDefinition { 501 | var_name: Self::DEFAULT_VARNAME.into(), 502 | param_type: ParameterType::Integer(name.into()), 503 | } 504 | } 505 | 506 | fn bool(name: impl Into) -> ParameterDefinition { 507 | ParameterDefinition { 508 | var_name: Self::DEFAULT_VARNAME.into(), 509 | param_type: ParameterType::Bool(name.into()), 510 | } 511 | } 512 | 513 | fn integer_group(names: impl IntoIterator) -> ParameterDefinition { 514 | ParameterDefinition { 515 | var_name: Self::DEFAULT_VARNAME.into(), 516 | param_type: ParameterType::IntegerGroup(names.into_iter().collect()), 517 | } 518 | } 519 | 520 | fn bool_group(names: impl IntoIterator) -> ParameterDefinition { 521 | ParameterDefinition { 522 | var_name: Self::DEFAULT_VARNAME.into(), 523 | param_type: ParameterType::BoolGroup(names.into_iter().collect()), 524 | } 525 | } 526 | } 527 | 528 | impl AacObject for ParameterDefinition { 529 | fn write_into(self, w: &mut CodeWriter) -> IoResult<()> { 530 | let ParameterDefinition { 531 | var_name, 532 | param_type, 533 | } = self; 534 | 535 | match param_type { 536 | ParameterType::Bool(p) => w.write(format_args!( 537 | r#"var {var_name} = layer.BoolParameter("{p}");"# 538 | )), 539 | ParameterType::Integer(p) => w.write(format_args!( 540 | r#"var {var_name} = layer.IntParameter("{p}");"# 541 | )), 542 | ParameterType::BoolGroup(ps) => { 543 | let joined = ps.join(r#"", ""#); 544 | w.write(format_args!( 545 | r#"var {var_name} = layer.BoolParameters("{joined}");"# 546 | )) 547 | } 548 | ParameterType::IntegerGroup(ps) => { 549 | let joined = ps.join(r#"", ""#); 550 | w.write(format_args!( 551 | r#"var {var_name} = layer.IntParameters("{joined}");"# 552 | )) 553 | } 554 | } 555 | } 556 | } 557 | 558 | #[derive(Debug, Clone)] 559 | enum ParameterType { 560 | Bool(String), 561 | Integer(String), 562 | BoolGroup(Vec), 563 | IntegerGroup(Vec), 564 | } 565 | 566 | /// `var state = ...` 567 | #[derive(Debug, Clone)] 568 | struct StateDefinition { 569 | state_var: String, 570 | state_name: String, 571 | blend_shapes: Option>, 572 | renderer: String, 573 | right_of: Option, 574 | indented: bool, 575 | } 576 | 577 | impl StateDefinition { 578 | fn new(state_var: impl Into, state_name: impl Into) -> Self { 579 | StateDefinition { 580 | state_var: state_var.into(), 581 | state_name: state_name.into(), 582 | blend_shapes: None, 583 | renderer: "renderer".into(), 584 | right_of: None, 585 | indented: false, 586 | } 587 | } 588 | 589 | fn right_of(mut self, state_name: impl Into) -> Self { 590 | self.right_of = Some(state_name.into()); 591 | self 592 | } 593 | 594 | fn indented(mut self) -> Self { 595 | self.indented = true; 596 | self 597 | } 598 | 599 | fn blend_shapes(mut self, items: impl IntoIterator) -> Self { 600 | self.blend_shapes = Some(items.into_iter().collect()); 601 | self 602 | } 603 | } 604 | 605 | impl AacObject for StateDefinition { 606 | fn write_into(self, w: &mut CodeWriter) -> IoResult<()> { 607 | let StateDefinition { 608 | state_var, 609 | state_name, 610 | renderer, 611 | .. 612 | } = self; 613 | 614 | if self.indented { 615 | w.write_yield(|w| { 616 | write!(w, r#"var {state_var} = layer.NewState("{state_name}")"#)?; 617 | if let Some(ro) = self.right_of { 618 | write!(w, r#".RightOf({ro})"#)?; 619 | } 620 | write!(w, r#".WithAnimation("#) 621 | })?; 622 | 623 | w.with_indent(|mut b| { 624 | b.write(r#"aac.NewClip()"#)?; 625 | if let Some(blend_shapes) = self.blend_shapes { 626 | b.with_indent(|mut b| { 627 | for (name, value) in blend_shapes { 628 | let value = value * 100.0; 629 | b.write(format_args!( 630 | r#".BlendShape({renderer}, "{name}", {value:.1}f)"# 631 | ))?; 632 | } 633 | Ok(()) 634 | })?; 635 | } 636 | Ok(()) 637 | })?; 638 | 639 | w.write(r#");"#) 640 | } else { 641 | w.write_yield(|w| { 642 | write!(w, r#"var {state_var} = layer.NewState("{state_name}")"#)?; 643 | if let Some(ro) = self.right_of { 644 | write!(w, r#".RightOf({ro})"#)?; 645 | } 646 | write!(w, r#".WithAnimation(aac.NewClip()"#)?; 647 | if let Some(blend_shapes) = self.blend_shapes { 648 | for (name, value) in blend_shapes { 649 | let value = value * 100.0; 650 | write!(w, r#".BlendShape({renderer}, "{name}", {value:.1}f)"#)?; 651 | } 652 | } 653 | write!(w, r#");"#) 654 | }) 655 | } 656 | } 657 | } 658 | 659 | /// `state.Tracks/Animates()...` 660 | #[derive(Debug, Clone)] 661 | struct StateOptions { 662 | state_var: String, 663 | options: Vec, 664 | driving: bool, 665 | } 666 | 667 | impl StateOptions { 668 | fn new(state_var: impl Into) -> StateOptions { 669 | StateOptions { 670 | state_var: state_var.into(), 671 | options: vec![], 672 | driving: false, 673 | } 674 | } 675 | 676 | fn tracks(mut self, target: AnimationTarget) -> Self { 677 | self.options.push(StateOption::Tracks(target)); 678 | self 679 | } 680 | 681 | fn animates(mut self, target: AnimationTarget) -> Self { 682 | self.options.push(StateOption::Animates(target)); 683 | self 684 | } 685 | 686 | fn drives(mut self, drive: ResolvedDrive) -> Self { 687 | self.driving = true; 688 | self.options.push(StateOption::DrivesParameter(drive)); 689 | self 690 | } 691 | } 692 | 693 | impl AacObject for StateOptions { 694 | fn write_into(self, w: &mut CodeWriter) -> IoResult<()> { 695 | if self.options.is_empty() { 696 | return Ok(()); 697 | } 698 | 699 | let StateOptions { 700 | state_var, 701 | options, 702 | driving, 703 | } = self; 704 | w.write_yield(|w| { 705 | write!(w, r#"{state_var}"#)?; 706 | if driving { 707 | write!(w, r#".DrivingLocally()"#)?; 708 | } 709 | 710 | for option in options { 711 | match option { 712 | StateOption::Tracks(at) => { 713 | write!(w, r#".TrackingTracks({})"#, at.tracking_element())? 714 | } 715 | StateOption::Animates(at) => { 716 | write!(w, r#".TrackingAnimates({})"#, at.tracking_element())? 717 | } 718 | StateOption::DrivesParameter(drive) => { 719 | write!(w, r#".Drives("#)?; 720 | match drive { 721 | ResolvedDrive::Integer { name, index } => { 722 | write!(w, r#"layer.IntParameter("{name}"), {index}"#,)?; 723 | } 724 | ResolvedDrive::Bool { name, enabled } => { 725 | write!(w, r#"layer.BoolParameter("{name}"), {enabled}"#,)?; 726 | } 727 | } 728 | write!(w, r#")"#)? 729 | } 730 | } 731 | } 732 | write!(w, r#";"#) 733 | }) 734 | } 735 | } 736 | 737 | #[derive(Debug, Clone)] 738 | enum StateOption { 739 | Tracks(AnimationTarget), 740 | Animates(AnimationTarget), 741 | DrivesParameter(ResolvedDrive), 742 | } 743 | 744 | /// `state.TransitionTo()...` 745 | #[derive(Debug, Clone)] 746 | struct Transition { 747 | from: Option, 748 | to: Option, 749 | condition: Option, 750 | } 751 | 752 | impl Transition { 753 | fn new(from: impl Into, to: impl Into) -> Self { 754 | Transition { 755 | from: Some(from.into()), 756 | to: Some(to.into()), 757 | condition: None, 758 | } 759 | } 760 | 761 | fn exits(from: impl Into) -> Self { 762 | Transition { 763 | from: Some(from.into()), 764 | to: None, 765 | condition: None, 766 | } 767 | } 768 | 769 | fn cond(mut self, condition: Cond) -> Self { 770 | if condition.is_valid() { 771 | self.condition = Some(condition); 772 | } 773 | self 774 | } 775 | } 776 | 777 | impl AacObject for Transition { 778 | fn write_into(self, w: &mut CodeWriter) -> IoResult<()> { 779 | let condition = match self.condition { 780 | Some(c) => c, 781 | None => return Ok(()), 782 | }; 783 | 784 | w.write_yield(|w| { 785 | match (self.from, self.to) { 786 | (Some(f), Some(t)) => write!(w, r#"{f}.TransitionsTo({t})"#)?, 787 | (Some(f), None) => write!(w, r#"{f}.Exits()"#)?, 788 | _ => unreachable!("Invalid transition"), 789 | } 790 | condition.write(w)?; 791 | write!(w, r#";"#) 792 | }) 793 | } 794 | } 795 | 796 | #[derive(Debug, Clone)] 797 | enum Expr { 798 | IntEqual(String, usize), 799 | IntNotEqual(String, usize), 800 | IsTrue(String), 801 | IsFalse(String), 802 | } 803 | 804 | impl Expr { 805 | fn write(&self, w: &mut W) -> IoResult<()> { 806 | match self { 807 | Expr::IntEqual(p, v) => write!(w, r#"{p}.IsEqualTo({v})"#), 808 | Expr::IntNotEqual(p, v) => write!(w, r#"{p}.IsNotEqualTo({v})"#), 809 | Expr::IsTrue(p) => write!(w, r#"{p}.IsTrue()"#), 810 | Expr::IsFalse(p) => write!(w, r#"{p}.IsFalse()"#), 811 | } 812 | } 813 | } 814 | 815 | #[derive(Debug, Clone)] 816 | enum Cond { 817 | Or(Vec), 818 | And(Vec), 819 | Term(Expr), 820 | } 821 | 822 | impl Cond { 823 | fn is_valid(&self) -> bool { 824 | match self { 825 | Cond::Or(_) => self.is_valid_or(), 826 | Cond::And(_) => self.is_valid_and(), 827 | Cond::Term(_) => true, 828 | } 829 | } 830 | 831 | fn is_valid_and(&self) -> bool { 832 | match self { 833 | Cond::And(terms) => terms.iter().all(|t| matches!(t, Cond::Term(_))), 834 | Cond::Term(_) => true, 835 | _ => false, 836 | } 837 | } 838 | 839 | fn is_valid_or(&self) -> bool { 840 | match self { 841 | Cond::Or(terms) => terms.iter().all(|t| t.is_valid_and()), 842 | _ => false, 843 | } 844 | } 845 | 846 | fn write(&self, w: &mut W) -> IoResult<()> { 847 | match self { 848 | Cond::Or(and_clauses) => { 849 | let or_splits = once("").chain(repeat(".Or()")); 850 | for (and_clause, or) in zip(and_clauses, or_splits) { 851 | write!(w, r#"{or}"#)?; 852 | and_clause.write(w)?; 853 | } 854 | } 855 | Cond::And(terms) => { 856 | let method_names = once("When").chain(repeat("And")); 857 | for (term, method) in zip(terms, method_names) { 858 | let term = match term { 859 | Cond::Term(t) => t, 860 | _ => unreachable!("Should be validated"), 861 | }; 862 | write!(w, r#".{method}("#)?; 863 | term.write(w)?; 864 | write!(w, r#")"#)?; 865 | } 866 | } 867 | Cond::Term(t) => { 868 | write!(w, r#".When("#)?; 869 | t.write(w)?; 870 | write!(w, r#")"#)?; 871 | } 872 | } 873 | Ok(()) 874 | } 875 | } 876 | 877 | /// `// Driver ...` 878 | struct DriverLayer(ResolvedDriver); 879 | 880 | impl DriverLayer { 881 | fn new(driver: ResolvedDriver) -> DriverLayer { 882 | DriverLayer(driver) 883 | } 884 | } 885 | 886 | impl AacObject for DriverLayer { 887 | fn write_into(self, w: &mut CodeWriter) -> IoResult<()> { 888 | let driver = self.0; 889 | 890 | w.write(format_args!(r#"// Driver "{}""#, driver.name))?; 891 | w.with_block(|mut b| { 892 | LayerDefinition::new(format!("{}", driver.name)).write_into(&mut b)?; 893 | ParameterDefinition::integer(driver.name).write_into(&mut b)?; 894 | StateDefinition::new("waiting", "0: Waiting").write_into(&mut b)?; 895 | 896 | let mut right_of = "waiting".to_string(); 897 | for (i, option) in driver.options.into_iter().enumerate() { 898 | let index = i + 1; 899 | 900 | let state_name = format!("option{index}"); 901 | let state_label = format!("{index}: {}", option.label); 902 | 903 | // State 904 | b.write_empty()?; 905 | let mut statedef = StateDefinition::new(state_name.clone(), state_label); 906 | if i % ALIGN_UNIT == 0 { 907 | statedef = statedef.right_of(right_of); 908 | right_of = state_name.clone(); 909 | } 910 | statedef.write_into(&mut b)?; 911 | let mut state_options = StateOptions::new(state_name.clone()); 912 | for drive in option.drives { 913 | state_options = state_options.drives(drive); 914 | } 915 | state_options.write_into(&mut b)?; 916 | 917 | // Transitions 918 | Transition::new("waiting", state_name.clone()) 919 | .cond(Cond::Term(Expr::IntEqual( 920 | ParameterDefinition::DEFAULT_VARNAME.into(), 921 | index, 922 | ))) 923 | .write_into(&mut b)?; 924 | Transition::exits(state_name.clone()) 925 | .cond(Cond::Term(Expr::IntNotEqual( 926 | ParameterDefinition::DEFAULT_VARNAME.into(), 927 | index, 928 | ))) 929 | .write_into(&mut b)?; 930 | } 931 | Ok(()) 932 | }) 933 | } 934 | } 935 | -------------------------------------------------------------------------------- /sk2aac/src/codegen/mod.rs: -------------------------------------------------------------------------------- 1 | mod aac; 2 | mod writer; 3 | 4 | pub use self::{aac::write_descriptor_code, writer::CodeWriter}; 5 | -------------------------------------------------------------------------------- /sk2aac/src/codegen/writer.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | fmt::Display, 3 | io::{Error as IoError, Write}, 4 | }; 5 | 6 | /// Helps write out indented codes. 7 | pub struct CodeWriter<'a, W> 8 | where 9 | W: Write, 10 | { 11 | writer: &'a mut W, 12 | indent_width: usize, 13 | indent_spaces: String, 14 | termination: Option<(&'a str, bool)>, 15 | } 16 | 17 | impl<'a, W: Write> CodeWriter<'a, W> { 18 | /// Wraps writer for code generation. 19 | pub fn new(writer: &mut W, indent_width: usize) -> CodeWriter { 20 | CodeWriter { 21 | writer, 22 | indent_width, 23 | indent_spaces: String::new(), 24 | termination: None, 25 | } 26 | } 27 | 28 | /// Executes function with indented. 29 | pub fn with_indent<'f, T, F>(&'f mut self, f: F) -> Result 30 | where 31 | F: FnOnce(CodeWriter<'f, W>) -> Result, 32 | { 33 | let inner = CodeWriter { 34 | writer: self.writer, 35 | indent_width: self.indent_width, 36 | indent_spaces: format!("{}{}", self.indent_spaces, " ".repeat(self.indent_width)), 37 | termination: None, 38 | }; 39 | let returned = f(inner)?; 40 | Ok(returned) 41 | } 42 | 43 | /// Executes function with indented block. 44 | pub fn with_block<'f, T, F>(&'f mut self, f: F) -> Result 45 | where 46 | F: FnOnce(CodeWriter<'f, W>) -> Result, 47 | { 48 | self.write("{")?; 49 | 50 | let inner = CodeWriter { 51 | writer: self.writer, 52 | indent_width: self.indent_width, 53 | indent_spaces: format!("{}{}", self.indent_spaces, " ".repeat(self.indent_width)), 54 | termination: Some(("}", true)), 55 | }; 56 | let returned = f(inner)?; 57 | Ok(returned) 58 | } 59 | 60 | /// Executes function with ifdef. 61 | pub fn with_ifdef<'f, T, F>(&'f mut self, identifier: &str, f: F) -> Result 62 | where 63 | F: FnOnce(CodeWriter<'f, W>) -> Result, 64 | { 65 | write!(self.writer, "#if {identifier}")?; 66 | writeln!(self.writer)?; 67 | 68 | let inner = CodeWriter { 69 | writer: self.writer, 70 | indent_width: self.indent_width, 71 | indent_spaces: self.indent_spaces.clone(), 72 | termination: Some(("#endif", false)), 73 | }; 74 | let returned = f(inner)?; 75 | Ok(returned) 76 | } 77 | 78 | /// Writes a line. 79 | pub fn write(&mut self, line: D) -> Result<(), IoError> { 80 | write!(self.writer, "{}{line}", self.indent_spaces)?; 81 | writeln!(self.writer)?; 82 | Ok(()) 83 | } 84 | 85 | /// Writes a blank line. 86 | pub fn write_empty(&mut self) -> Result<(), IoError> { 87 | writeln!(self.writer)?; 88 | Ok(()) 89 | } 90 | 91 | /// Yields write process to given function. 92 | pub fn write_yield(&mut self, f: F) -> Result<(), IoError> 93 | where 94 | F: FnOnce(&mut W) -> Result<(), IoError>, 95 | { 96 | write!(self.writer, "{}", self.indent_spaces)?; 97 | { 98 | let yielded = &mut self.writer; 99 | f(yielded)?; 100 | } 101 | writeln!(self.writer)?; 102 | Ok(()) 103 | } 104 | 105 | /// Flushes current content. 106 | pub fn flush(&mut self) -> Result<(), IoError> { 107 | if let Some((text, with_indent)) = self.termination { 108 | if with_indent { 109 | let prev_indent = self.indent_spaces.len() - self.indent_width; 110 | let indent_space = &self.indent_spaces[..prev_indent]; 111 | write!(self.writer, "{indent_space}")?; 112 | } 113 | write!(self.writer, "{text}")?; 114 | writeln!(self.writer)?; 115 | self.writer.flush()?; 116 | self.termination = None; 117 | } 118 | Ok(()) 119 | } 120 | } 121 | 122 | impl<'a, W: Write> Drop for CodeWriter<'a, W> { 123 | fn drop(&mut self) { 124 | self.flush().expect("Flush failed"); 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /sk2aac/src/descriptor/mod.rs: -------------------------------------------------------------------------------- 1 | mod raw; 2 | mod validation; 3 | 4 | pub use self::validation::{validate_descriptor, ValidationError, ValidationResult}; 5 | 6 | use crate::descriptor::raw::{ 7 | RawDescriptor, RawDrive, RawDriver, RawDriverOption, RawShapeKeyCommon, RawShapeKeyDrive, 8 | RawShapeKeyGroup, RawShapeKeyOption, RawShapeKeySwitch, 9 | }; 10 | 11 | use std::num::NonZeroUsize; 12 | 13 | use serde::{de::Error, Deserialize, Deserializer, Serialize, Serializer}; 14 | 15 | /// Normalized float value in [0, 1]. 16 | #[derive(Debug, Clone, Copy, PartialEq, PartialOrd)] 17 | pub struct NormalizedF64(pub f64); 18 | 19 | impl NormalizedF64 { 20 | pub fn new(v: f64) -> Option { 21 | if v >= 0.0 && v <= 1.0 { 22 | Some(NormalizedF64(v as f64)) 23 | } else { 24 | None 25 | } 26 | } 27 | 28 | pub const fn get(&self) -> f64 { 29 | self.0 30 | } 31 | } 32 | 33 | impl Serialize for NormalizedF64 { 34 | fn serialize(&self, serializer: S) -> Result 35 | where 36 | S: Serializer, 37 | { 38 | serializer.serialize_f64(self.0) 39 | } 40 | } 41 | 42 | /// Represents a whole descriptor object. 43 | #[derive(Debug, Clone, Serialize)] 44 | pub struct Descriptor { 45 | /// Avatar name. 46 | pub name: String, 47 | 48 | /// Shape key switces. 49 | pub shape_switches: Vec, 50 | 51 | /// Shape key groups. 52 | pub shape_groups: Vec, 53 | 54 | /// Parameter driver layers. 55 | pub drivers: Vec, 56 | } 57 | 58 | impl Descriptor { 59 | fn from_raw<'de, D>(raw: RawDescriptor) -> Result 60 | where 61 | D: Deserializer<'de>, 62 | { 63 | let shape_switches = raw 64 | .shape_switches 65 | .into_iter() 66 | .flatten() 67 | .map(|s| ShapeKeySwitch::from_raw::<'de, D>(s)) 68 | .collect::>()?; 69 | let shape_groups = raw 70 | .shape_groups 71 | .into_iter() 72 | .flatten() 73 | .map(|s| ShapeKeyGroup::from_raw::<'de, D>(s)) 74 | .collect::>()?; 75 | let drivers = raw 76 | .drivers 77 | .into_iter() 78 | .flatten() 79 | .map(|s| Driver::from_raw::<'de, D>(s)) 80 | .collect::>()?; 81 | 82 | Ok(Descriptor { 83 | name: raw.name, 84 | shape_switches, 85 | shape_groups, 86 | drivers, 87 | }) 88 | } 89 | } 90 | 91 | impl<'de> Deserialize<'de> for Descriptor { 92 | fn deserialize(deserializer: D) -> Result 93 | where 94 | D: Deserializer<'de>, 95 | { 96 | let raw = RawDescriptor::deserialize(deserializer)?; 97 | let descriptor = Descriptor::from_raw::<'de, D>(raw)?; 98 | Ok(descriptor) 99 | } 100 | } 101 | 102 | /// Represents common part of shape key layers. 103 | #[derive(Debug, Clone, Serialize)] 104 | pub struct ShapeKeyCommon { 105 | /// Name used for both the layer and its Expression Parameter. 106 | pub name: String, 107 | 108 | /// Referencing SkinnedMeshRenderer name. 109 | pub mesh: String, 110 | 111 | /// Decides whether this layer prevents the eyelids animation. 112 | pub prevent_eyelids: bool, 113 | 114 | /// Decides whether this layer prevents the mouth animation. 115 | pub prevent_mouth: bool, 116 | } 117 | 118 | impl ShapeKeyCommon { 119 | fn from_raw<'de, D>(raw: RawShapeKeyCommon) -> Result 120 | where 121 | D: Deserializer<'de>, 122 | { 123 | Ok(ShapeKeyCommon { 124 | name: raw.name, 125 | mesh: raw.mesh, 126 | prevent_eyelids: raw.prevent_eyelids.unwrap_or(false), 127 | prevent_mouth: raw.prevent_mouth.unwrap_or(false), 128 | }) 129 | } 130 | } 131 | 132 | impl<'de> Deserialize<'de> for ShapeKeyCommon { 133 | fn deserialize(deserializer: D) -> Result 134 | where 135 | D: Deserializer<'de>, 136 | { 137 | let raw = RawShapeKeyCommon::deserialize(deserializer)?; 138 | let skc = ShapeKeyCommon::from_raw::<'de, D>(raw)?; 139 | Ok(skc) 140 | } 141 | } 142 | 143 | /// Represents a shape key switch layer. 144 | #[derive(Debug, Clone, Serialize)] 145 | pub struct ShapeKeySwitch { 146 | /// Common part. 147 | #[serde(flatten)] 148 | pub common: ShapeKeyCommon, 149 | 150 | /// Target shape key. 151 | pub shape: String, 152 | 153 | /// The value on enabled. 154 | pub enabled_value: NormalizedF64, 155 | 156 | /// The value on disabled. 157 | pub disabled_value: NormalizedF64, 158 | } 159 | 160 | impl ShapeKeySwitch { 161 | fn from_raw<'de, D>(raw: RawShapeKeySwitch) -> Result 162 | where 163 | D: Deserializer<'de>, 164 | { 165 | let common = ShapeKeyCommon { 166 | name: raw.common.name, 167 | mesh: raw.common.mesh, 168 | prevent_eyelids: raw.common.prevent_eyelids.unwrap_or(false), 169 | prevent_mouth: raw.common.prevent_mouth.unwrap_or(false), 170 | }; 171 | let enabled_value = match NormalizedF64::new(raw.enabled_value.unwrap_or(1.0)) { 172 | Some(v) => v, 173 | None => return Err(D::Error::custom("enabled_value out of range")), 174 | }; 175 | let disabled_value = match NormalizedF64::new(raw.disabled_value.unwrap_or(0.0)) { 176 | Some(v) => v, 177 | None => return Err(D::Error::custom("disabled_value out of range")), 178 | }; 179 | 180 | Ok(ShapeKeySwitch { 181 | common, 182 | shape: raw.shape, 183 | enabled_value, 184 | disabled_value, 185 | }) 186 | } 187 | } 188 | 189 | impl<'de> Deserialize<'de> for ShapeKeySwitch { 190 | fn deserialize(deserializer: D) -> Result 191 | where 192 | D: Deserializer<'de>, 193 | { 194 | let raw = RawShapeKeySwitch::deserialize(deserializer)?; 195 | let sks = ShapeKeySwitch::from_raw::<'de, D>(raw)?; 196 | Ok(sks) 197 | } 198 | } 199 | 200 | /// Represents a group of shape key animations. 201 | #[derive(Debug, Clone, Serialize)] 202 | pub struct ShapeKeyGroup { 203 | /// Common part. 204 | #[serde(flatten)] 205 | pub common: ShapeKeyCommon, 206 | 207 | /// Default shape key values. 208 | pub defaults: Vec, 209 | 210 | /// Group options. 211 | pub options: Vec, 212 | } 213 | 214 | impl ShapeKeyGroup { 215 | fn from_raw<'de, D>(raw: RawShapeKeyGroup) -> Result 216 | where 217 | D: Deserializer<'de>, 218 | { 219 | let common = ShapeKeyCommon { 220 | name: raw.common.name, 221 | mesh: raw.common.mesh, 222 | prevent_eyelids: raw.common.prevent_eyelids.unwrap_or(false), 223 | prevent_mouth: raw.common.prevent_mouth.unwrap_or(false), 224 | }; 225 | let defaults = raw 226 | .defaults 227 | .into_iter() 228 | .flatten() 229 | .map(|d| ShapeKeyDrive::from_raw::<'de, D>(d, 1.0)) 230 | .collect::>()?; 231 | let options = raw 232 | .options 233 | .into_iter() 234 | .flatten() 235 | .map(|o| ShapeKeyOption::from_raw::<'de, D>(o)) 236 | .collect::>()?; 237 | Ok(ShapeKeyGroup { 238 | common, 239 | defaults, 240 | options, 241 | }) 242 | } 243 | } 244 | 245 | impl<'de> Deserialize<'de> for ShapeKeyGroup { 246 | fn deserialize(deserializer: D) -> Result 247 | where 248 | D: Deserializer<'de>, 249 | { 250 | let raw = RawShapeKeyGroup::deserialize(deserializer)?; 251 | let skg = ShapeKeyGroup::from_raw::<'de, D>(raw)?; 252 | Ok(skg) 253 | } 254 | } 255 | 256 | /// A option in `ShapeKeyGroup`. 257 | #[derive(Debug, Clone, Serialize)] 258 | pub struct ShapeKeyOption { 259 | /// Option label. 260 | pub label: String, 261 | 262 | /// Index value for Unity AnimatorController State. 263 | pub index: Option, 264 | 265 | /// Shape keys to move. 266 | pub shapes: Vec, 267 | } 268 | 269 | impl ShapeKeyOption { 270 | fn from_raw<'de, D>(raw: RawShapeKeyOption) -> Result 271 | where 272 | D: Deserializer<'de>, 273 | { 274 | let sko = match raw { 275 | RawShapeKeyOption::Simple(label) => { 276 | let shapes = vec![ShapeKeyDrive::new(&label)]; 277 | ShapeKeyOption { 278 | label, 279 | index: None, 280 | shapes, 281 | } 282 | } 283 | RawShapeKeyOption::Complex { 284 | label, 285 | value, 286 | index, 287 | shapes, 288 | } => { 289 | let default_value = value.unwrap_or(1.0); 290 | let index = match index { 291 | Some(i) => { 292 | let inner = NonZeroUsize::new(i) 293 | .ok_or(D::Error::custom("Index must be non-zero"))?; 294 | Some(inner) 295 | } 296 | None => None, 297 | }; 298 | let shapes = match shapes { 299 | Some(sv) => sv 300 | .into_iter() 301 | .map(|s| ShapeKeyDrive::from_raw::<'de, D>(s, default_value)) 302 | .collect::>()?, 303 | None => vec![ShapeKeyDrive::new(&label)], 304 | }; 305 | 306 | ShapeKeyOption { 307 | label, 308 | index, 309 | shapes, 310 | } 311 | } 312 | }; 313 | Ok(sko) 314 | } 315 | } 316 | 317 | impl<'de> Deserialize<'de> for ShapeKeyOption { 318 | fn deserialize(deserializer: D) -> Result 319 | where 320 | D: Deserializer<'de>, 321 | { 322 | let raw = RawShapeKeyOption::deserialize(deserializer)?; 323 | let option = ShapeKeyOption::from_raw::<'de, D>(raw)?; 324 | Ok(option) 325 | } 326 | } 327 | 328 | /// Drive information of a shape key. 329 | #[derive(Debug, Clone, Serialize)] 330 | pub struct ShapeKeyDrive { 331 | /// Shape key name. 332 | pub shape: String, 333 | 334 | /// Shape key value. 335 | pub value: NormalizedF64, 336 | } 337 | 338 | impl ShapeKeyDrive { 339 | /// Creates new instance with default options. 340 | fn new(label: &str) -> ShapeKeyDrive { 341 | ShapeKeyDrive { 342 | shape: label.to_string(), 343 | value: NormalizedF64::new(1.0).expect("Should be valid"), 344 | } 345 | } 346 | 347 | fn with_default_value<'de, D>( 348 | shape: String, 349 | value: Option, 350 | default_value: f64, 351 | ) -> Result 352 | where 353 | D: Deserializer<'de>, 354 | { 355 | let value = NormalizedF64::new(value.unwrap_or(default_value)) 356 | .ok_or(D::Error::custom("Drive value out of range"))?; 357 | Ok(ShapeKeyDrive { 358 | shape: shape.to_string(), 359 | value, 360 | }) 361 | } 362 | 363 | fn from_raw<'de, D>( 364 | raw: RawShapeKeyDrive, 365 | default_value: f64, 366 | ) -> Result 367 | where 368 | D: Deserializer<'de>, 369 | { 370 | match raw { 371 | RawShapeKeyDrive::Simple(shape) => { 372 | let skd = ShapeKeyDrive::new(&shape); 373 | Ok(skd) 374 | } 375 | RawShapeKeyDrive::Complex { shape, value } => { 376 | let skd = ShapeKeyDrive::with_default_value::<'de, D>(shape, value, default_value)?; 377 | Ok(skd) 378 | } 379 | } 380 | } 381 | } 382 | 383 | impl<'de> Deserialize<'de> for ShapeKeyDrive { 384 | fn deserialize(deserializer: D) -> Result 385 | where 386 | D: Deserializer<'de>, 387 | { 388 | let raw = RawShapeKeyDrive::deserialize(deserializer)?; 389 | let drive = ShapeKeyDrive::from_raw::<'de, D>(raw, 1.0)?; 390 | Ok(drive) 391 | } 392 | } 393 | 394 | /// Represents a parameter driver layer. 395 | #[derive(Debug, Clone, Serialize)] 396 | pub struct Driver { 397 | /// Layer name. 398 | pub name: String, 399 | 400 | /// Driver options. 401 | pub options: Vec, 402 | } 403 | 404 | impl Driver { 405 | fn from_raw<'de, D>(raw: RawDriver) -> Result 406 | where 407 | D: Deserializer<'de>, 408 | { 409 | let options = raw 410 | .options 411 | .into_iter() 412 | .map(|o| DriverOption::from_raw::<'de, D>(o)) 413 | .collect::>()?; 414 | Ok(Driver { 415 | name: raw.name, 416 | options, 417 | }) 418 | } 419 | } 420 | 421 | impl<'de> Deserialize<'de> for Driver { 422 | fn deserialize(deserializer: D) -> Result 423 | where 424 | D: Deserializer<'de>, 425 | { 426 | let raw = RawDriver::deserialize(deserializer)?; 427 | Driver::from_raw::<'de, D>(raw) 428 | } 429 | } 430 | 431 | /// An option of Driver. 432 | #[derive(Debug, Clone, Serialize)] 433 | pub struct DriverOption { 434 | /// Option label. 435 | pub label: String, 436 | pub drives: Vec, 437 | } 438 | 439 | impl DriverOption { 440 | fn from_raw<'de, D>(raw: RawDriverOption) -> Result 441 | where 442 | D: Deserializer<'de>, 443 | { 444 | let drives = raw 445 | .drives 446 | .into_iter() 447 | .map(|o| Drive::from_raw::<'de, D>(o)) 448 | .collect::>()?; 449 | let option = DriverOption { 450 | label: raw.label, 451 | drives, 452 | }; 453 | Ok(option) 454 | } 455 | } 456 | 457 | impl<'de> Deserialize<'de> for DriverOption { 458 | fn deserialize(deserializer: D) -> Result 459 | where 460 | D: Deserializer<'de>, 461 | { 462 | let raw = RawDriverOption::deserialize(deserializer)?; 463 | DriverOption::from_raw::<'de, D>(raw) 464 | } 465 | } 466 | 467 | /// A single drive. 468 | #[derive(Debug, Clone, Serialize)] 469 | #[serde(untagged)] 470 | pub enum Drive { 471 | /// Switch drive. 472 | Switch { name: String, enabled: bool }, 473 | 474 | /// Group drive. 475 | Group { name: String, label: String }, 476 | } 477 | 478 | impl Drive { 479 | fn from_raw<'de, D>(raw: RawDrive) -> Result 480 | where 481 | D: Deserializer<'de>, 482 | { 483 | let option = match raw { 484 | RawDrive::Switch { name, enabled } => Drive::Switch { name, enabled }, 485 | RawDrive::Group { name, label } => Drive::Group { name, label }, 486 | }; 487 | Ok(option) 488 | } 489 | } 490 | 491 | impl<'de> Deserialize<'de> for Drive { 492 | fn deserialize(deserializer: D) -> Result 493 | where 494 | D: Deserializer<'de>, 495 | { 496 | let raw = RawDrive::deserialize(deserializer)?; 497 | Drive::from_raw::<'de, D>(raw) 498 | } 499 | } 500 | 501 | /// Resolved driver layer. 502 | #[derive(Debug, Clone)] 503 | pub struct ResolvedDriver { 504 | pub name: String, 505 | pub options: Vec, 506 | } 507 | 508 | impl ResolvedDriver { 509 | /// Resolves the layer. 510 | pub fn resolve(descriptor: &Descriptor, driver: &Driver) -> ResolvedDriver { 511 | let options = driver 512 | .options 513 | .iter() 514 | .map(|o| ResolvedDriverOption::resolve(descriptor, o)) 515 | .collect(); 516 | ResolvedDriver { 517 | name: driver.name.clone(), 518 | options, 519 | } 520 | } 521 | } 522 | 523 | #[derive(Debug, Clone)] 524 | pub struct ResolvedDriverOption { 525 | pub label: String, 526 | pub drives: Vec, 527 | } 528 | 529 | impl ResolvedDriverOption { 530 | fn resolve(descriptor: &Descriptor, option: &DriverOption) -> ResolvedDriverOption { 531 | let drives = option 532 | .drives 533 | .iter() 534 | .map(|d| ResolvedDrive::resolve(descriptor, d)) 535 | .collect(); 536 | ResolvedDriverOption { 537 | label: option.label.clone(), 538 | drives, 539 | } 540 | } 541 | } 542 | 543 | #[derive(Debug, Clone)] 544 | pub enum ResolvedDrive { 545 | Integer { name: String, index: usize }, 546 | Bool { name: String, enabled: bool }, 547 | } 548 | 549 | impl ResolvedDrive { 550 | fn resolve(descriptor: &Descriptor, drive: &Drive) -> ResolvedDrive { 551 | match drive { 552 | Drive::Switch { name, enabled } => { 553 | // Group name is already validated. 554 | ResolvedDrive::Bool { 555 | name: name.clone(), 556 | enabled: *enabled, 557 | } 558 | } 559 | Drive::Group { name, label } => { 560 | let group = descriptor 561 | .shape_groups 562 | .iter() 563 | .find(|g| &g.common.name == name) 564 | .expect("Parameter name not found"); 565 | 566 | let resolved_index = group 567 | .options 568 | .iter() 569 | .enumerate() 570 | .find_map(|(i, o)| { 571 | if &o.label == label { 572 | Some(o.index.map(|x| x.get()).unwrap_or(i + 1)) 573 | } else { 574 | None 575 | } 576 | }) 577 | .expect("Label not found"); 578 | ResolvedDrive::Integer { 579 | name: name.clone(), 580 | index: resolved_index, 581 | } 582 | } 583 | } 584 | } 585 | } 586 | -------------------------------------------------------------------------------- /sk2aac/src/descriptor/raw.rs: -------------------------------------------------------------------------------- 1 | use serde::Deserialize; 2 | 3 | #[derive(Debug, Deserialize)] 4 | pub struct RawDescriptor { 5 | pub name: String, 6 | pub shape_switches: Option>, 7 | pub shape_groups: Option>, 8 | pub drivers: Option>, 9 | } 10 | 11 | #[derive(Debug, Deserialize)] 12 | pub struct RawShapeKeyCommon { 13 | pub name: String, 14 | pub mesh: String, 15 | pub prevent_eyelids: Option, 16 | pub prevent_mouth: Option, 17 | } 18 | 19 | #[derive(Debug, Deserialize)] 20 | pub struct RawShapeKeySwitch { 21 | #[serde(flatten)] 22 | pub common: RawShapeKeyCommon, 23 | 24 | pub shape: String, 25 | pub enabled_value: Option, 26 | pub disabled_value: Option, 27 | } 28 | 29 | #[derive(Debug, Deserialize)] 30 | pub struct RawShapeKeyGroup { 31 | #[serde(flatten)] 32 | pub common: RawShapeKeyCommon, 33 | 34 | pub defaults: Option>, 35 | pub options: Option>, 36 | } 37 | 38 | #[derive(Debug, Deserialize)] 39 | #[serde(untagged)] 40 | pub enum RawShapeKeyOption { 41 | Simple(String), 42 | Complex { 43 | label: String, 44 | value: Option, 45 | index: Option, 46 | shapes: Option>, 47 | }, 48 | } 49 | 50 | #[derive(Debug, Deserialize)] 51 | #[serde(untagged)] 52 | pub enum RawShapeKeyDrive { 53 | Simple(String), 54 | Complex { shape: String, value: Option }, 55 | } 56 | 57 | #[derive(Debug, Deserialize)] 58 | pub struct RawDriver { 59 | pub name: String, 60 | pub options: Vec, 61 | } 62 | 63 | #[derive(Debug, Deserialize)] 64 | pub struct RawDriverOption { 65 | pub label: String, 66 | pub drives: Vec, 67 | } 68 | 69 | #[derive(Debug, Deserialize)] 70 | #[serde(untagged)] 71 | pub enum RawDrive { 72 | Switch { name: String, enabled: bool }, 73 | Group { name: String, label: String }, 74 | } 75 | -------------------------------------------------------------------------------- /sk2aac/src/descriptor/validation.rs: -------------------------------------------------------------------------------- 1 | use crate::descriptor::{Descriptor, Drive, Driver, ShapeKeyCommon, ShapeKeyGroup, ShapeKeySwitch}; 2 | 3 | use thiserror::Error as ThisError; 4 | 5 | #[non_exhaustive] 6 | #[derive(Debug, Clone, ThisError)] 7 | pub enum ValidationError { 8 | /// Name is invalid for identifier or Expression Parameter name. 9 | #[error("invalid name for an identifier: \"{0}\"")] 10 | InvalidName(String), 11 | 12 | /// Name not found. 13 | #[error("No group or switch found: \"{0}\"")] 14 | NameNotExist(String), 15 | } 16 | 17 | /// Shorthand for `Result<(), ValidationError>`. 18 | pub type ValidationResult = Result<(), ValidationError>; 19 | 20 | pub fn validate_descriptor(descriptor: &Descriptor) -> ValidationResult { 21 | if descriptor.name.chars().any(|c| !c.is_ascii_alphanumeric()) { 22 | return Err(ValidationError::InvalidName(descriptor.name.clone())); 23 | } 24 | for switch in &descriptor.shape_switches { 25 | validate_shape_key_switch(switch)?; 26 | } 27 | for group in &descriptor.shape_groups { 28 | validate_shape_key_group(group)?; 29 | } 30 | for driver in &descriptor.drivers { 31 | validate_driver(driver, descriptor)?; 32 | } 33 | 34 | Ok(()) 35 | } 36 | 37 | fn validate_shape_key_switch(switch: &ShapeKeySwitch) -> ValidationResult { 38 | validate_shape_key_common(&switch.common)?; 39 | 40 | Ok(()) 41 | } 42 | 43 | fn validate_shape_key_group(group: &ShapeKeyGroup) -> ValidationResult { 44 | validate_shape_key_common(&group.common)?; 45 | 46 | Ok(()) 47 | } 48 | 49 | fn validate_shape_key_common(common: &ShapeKeyCommon) -> ValidationResult { 50 | if common.name.chars().any(|c| !c.is_ascii_alphanumeric()) { 51 | return Err(ValidationError::InvalidName(common.name.clone())); 52 | } 53 | 54 | Ok(()) 55 | } 56 | 57 | fn validate_driver(driver: &Driver, descriptor: &Descriptor) -> ValidationResult { 58 | if driver.name.chars().any(|c| !c.is_ascii_alphanumeric()) { 59 | return Err(ValidationError::InvalidName(driver.name.clone())); 60 | } 61 | for option in &driver.options { 62 | for drive in &option.drives { 63 | match drive { 64 | Drive::Switch { name, .. } => { 65 | let exists_shape_switch = descriptor 66 | .shape_switches 67 | .iter() 68 | .any(|s| name == &s.common.name); 69 | if !exists_shape_switch { 70 | return Err(ValidationError::NameNotExist(name.clone())); 71 | } 72 | } 73 | Drive::Group { name, label } => { 74 | let exists_shape_group = descriptor.shape_groups.iter().any(|g| { 75 | name == &g.common.name && g.options.iter().any(|o| &o.label == label) 76 | }); 77 | if !exists_shape_group { 78 | return Err(ValidationError::NameNotExist(name.clone())); 79 | } 80 | } 81 | } 82 | } 83 | } 84 | 85 | Ok(()) 86 | } 87 | -------------------------------------------------------------------------------- /sk2aac/src/main.rs: -------------------------------------------------------------------------------- 1 | mod codegen; 2 | mod descriptor; 3 | 4 | use crate::{ 5 | codegen::write_descriptor_code, 6 | descriptor::{validate_descriptor, Descriptor}, 7 | }; 8 | 9 | use std::{ 10 | env::args, 11 | fs::{read_to_string, File}, 12 | io::BufWriter, 13 | }; 14 | 15 | use anyhow::{bail, Result}; 16 | use toml::from_str as toml_from_str; 17 | 18 | fn main() -> Result<()> { 19 | let args: Vec = args().collect(); 20 | if args.len() <= 2 { 21 | bail!("Usage: sk2aac "); 22 | } 23 | 24 | let descriptor: Descriptor = toml_from_str(&read_to_string(&args[1])?)?; 25 | validate_descriptor(&descriptor)?; 26 | 27 | let mut output_file = BufWriter::new(File::create(&args[2])?); 28 | let class_name = write_descriptor_code(&mut output_file, descriptor)?; 29 | println!("You should rename the file to {class_name}.cs"); 30 | 31 | Ok(()) 32 | } 33 | --------------------------------------------------------------------------------