summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authoryum <yum.food.vr@gmail.com>2025-02-19 03:26:58 -0800
committeryum <yum.food.vr@gmail.com>2025-02-19 03:27:16 -0800
commit3c7d7df216fe1537362b19520b304cc6e5d856a6 (patch)
tree25520012d433f6e188bfef08a80329ef7c0db284
parent0675266fac2d1b9b1af9b89b5830bc7efedfed21 (diff)
organize, update readme
-rw-r--r--.gitignore2
-rw-r--r--Examples/example0.json26
-rw-r--r--Examples/example1.json80
-rw-r--r--README.md169
-rw-r--r--Scripts/YOTSCore.cs (renamed from YOTSCore.cs)0
-rw-r--r--Scripts/YOTSNDMFConfig.cs (renamed from YOTSNDMFConfig.cs)0
-rw-r--r--Scripts/YOTSNDMFGenerator.cs (renamed from YOTSNDMFGenerator.cs)0
-rw-r--r--YOTS.prefab47
8 files changed, 239 insertions, 85 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..fda7039
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+**/.*.sw[po]
+**/*.meta
diff --git a/Examples/example0.json b/Examples/example0.json
new file mode 100644
index 0000000..d77e3e7
--- /dev/null
+++ b/Examples/example0.json
@@ -0,0 +1,26 @@
+{
+ "api_version": "1.0",
+ "toggles": [
+ {
+ "name": "Shirt",
+ "meshToggles": ["Shirt"],
+ "blendShapes": [
+ {
+ "path": "Body",
+ "blendShape": "Chest_Hide"
+ }
+ ]
+ },
+ {
+ "name": "Jacket",
+ "dependencies": ["Shirt"],
+ "meshToggles": ["Jacket"],
+ "blendShapes": [
+ {
+ "path": "Body",
+ "blendShape": "Chest_Hide"
+ }
+ ]
+ }
+ ]
+}
diff --git a/Examples/example1.json b/Examples/example1.json
new file mode 100644
index 0000000..df27537
--- /dev/null
+++ b/Examples/example1.json
@@ -0,0 +1,80 @@
+{
+ "api_version": "1.0",
+ "toggles": [
+ {
+ "name": "Shirt",
+ "menuPath": "/Clothes",
+ "dependencies": ["Bra"],
+ "meshToggles": ["Shirt"],
+ "defaultValue": 0.0,
+ "blendShapes": [
+ {
+ "path": "Body",
+ "blendShape": "Bra on"
+ },
+ {
+ "path": "Body",
+ "blendShape": "Smush nips"
+ },
+ {
+ "path": "Skirt",
+ "blendShape": "Shirt off",
+ "offValue": 100.0,
+ "onValue": 0.0
+ }
+ ]
+ },
+ {
+ "name": "Skirt",
+ "menuPath": "/Clothes",
+ "meshToggles": ["Skirt"],
+ "blendShapes": []
+ },
+ {
+ "name": "Socks",
+ "menuPath": "/Clothes",
+ "meshToggles": ["Socks"],
+ "blendShapes": [
+ {
+ "path": "Body",
+ "blendShape": "Thigh squish"
+ }
+ ]
+ },
+ {
+ "name": "Shoes",
+ "menuPath": "/Clothes",
+ "meshToggles": ["Shoes"],
+ "blendShapes": []
+ },
+
+ {
+ "name": "Bra",
+ "menuPath": "/NSFW",
+ "meshToggles": ["Bra"],
+ "blendShapes": [
+ {
+ "path": "Body",
+ "blendShape": "Bra on"
+ },
+ {
+ "path": "Body",
+ "blendShape": "Smush nips"
+ }
+ ]
+ },
+ {
+ "name": "Panties",
+ "menuPath": "/NSFW",
+ "meshToggles": ["Panties"],
+ "blendShapes": []
+ },
+
+ {
+ "name": "Hair",
+ "menuPath": "/Misc",
+ "meshToggles": ["Hair"],
+ "blendShapes": []
+ }
+ ]
+}
diff --git a/README.md b/README.md
index a6b7537..94cf27f 100644
--- a/README.md
+++ b/README.md
@@ -1,22 +1,23 @@
# YOTS: yum's optimized toggle system
-YOTS is a text-based system for creating and managing VRChat toggles. It solves
-two core problems:
+YOTS is a text-based system for managing VRChat toggles and sliders. It
+translates a single config file into VRChat menus, parameters, animations, and
+animator layers. They are added to your avatar non-destructively.
-1. Toggles must be flattened into as few layers as possible using direct blend
- trees (DBTs).
-2. Toggles may have dependencies which prevent them from being combined.
-
-The core idea is to use declared dependencies between toggles to perform a
-topological sort, then flatten each layer of the sort into a single DBT.
+YOTS is efficient, maintainable, non-destructive, and compatible with any
+existing animator.
yum!
-## Real world example
+## Installation
+
+First, install [Modular Avatar](https://modular-avatar.nadena.dev/).
+
+Then grab the latest release from [the releases page](https://github.com/yum-food/YOTS/releases/latest).
+
+## Creating your first toggle
-Here's a fairly simple animator I made for an avatar I'm working on. You would
-write and maintain this config file. YOTS uses it to generate the menus,
-parameters, and animator. You hook those into your avatar and upload.
+Open your text editor of choice and paste this in:
```json
{
@@ -24,83 +25,80 @@ parameters, and animator. You hook those into your avatar and upload.
"toggles": [
{
"name": "Shirt",
- "menuPath": "/Clothes",
- "dependencies": ["Bra"],
"meshToggles": ["Shirt"],
- "blendShapes": [
- {
- "path": "Body",
- "blendShape": "Bra on"
- },
- {
- "path": "Body",
- "blendShape": "Smush nips"
- },
- {
- "path": "Skirt",
- "blendShape": "Shirt off",
- "offValue": 100.0,
- "onValue": 0.0
- }
- ]
- },
- {
- "name": "Skirt",
- "menuPath": "/Clothes",
- "meshToggles": ["Skirt"],
- "blendShapes": []
- },
- {
- "name": "Socks",
- "menuPath": "/Clothes",
- "meshToggles": ["Socks"],
- "blendShapes": [
- {
- "path": "Body",
- "blendShape": "Thigh squish"
- }
- ]
- },
- {
- "name": "Shoes",
- "menuPath": "/Clothes",
- "meshToggles": ["Shoes"],
- "blendShapes": []
- },
-
- {
- "name": "Bra",
- "menuPath": "/NSFW",
- "meshToggles": ["Bra"],
- "blendShapes": [
- {
- "path": "Body",
- "blendShape": "Bra on"
- },
- {
- "path": "Body",
- "blendShape": "Smush nips"
- }
- ]
- },
- {
- "name": "Panties",
- "menuPath": "/NSFW",
- "meshToggles": ["Panties"],
- "blendShapes": []
- },
-
- {
- "name": "Hair",
- "menuPath": "/Misc",
- "meshToggles": ["Hair"],
"blendShapes": []
}
]
}
```
-## Design overview by example
+Feel free to replace "Shirt" with the name of some other mesh on your avatar.
+
+Save it to Assets/animator.json.
+
+Drag Assets/yum\_food/YOTS.prefab anywhere on your avatar. Select it in the
+hierarchy, and drag Assets/animator.json onto the "JSON config" field.
+
+Enter play mode. Enable an emulator (I use Lyuma's av3emulator). Open your
+menu. You should see a YOTS submenu. Click it, then click Shirt. Your shirt
+should toggle off.
+
+Congratulations!
+
+A logical sequence of things to try:
+
+1. Add more toggles.
+2. Add blendshape toggles in addition to mesh toggles.
+3. Declare a dependency on another toggle with `"dependencies": ["someOtherToggle"]`.
+4. Install a toggle at a custom path with `"menuPath": "/my/custom/path"`.
+5. Add a radial puppet with `"type": "radial"`.
+
+Toggle options are documented in two places:
+
+1. Read the ToggleSpec definition at the top of
+ [YOTSCore.cs](./Scripts/YOTSCore.cs). This is the definitive source of
+ truth.
+2. Skim the examples under Examples/
+
+## Motivation
+
+Animators have a two fundamental problems:
+
+1. Layers are extremely slow. Their runtime scales with O(n^2).
+2. Animations which affect the same thing cannot be in the same layer.
+
+Any efficient animator must minimize the number of layers. Any real-world
+animator requires multiple layers because some toggles will need to override
+other toggles. Thus the goal is to provide this overriding capability while
+still minimizing the number of layers.
+
+In addition, whenever you add a toggle to a VRChat avatar, you need to edit at
+least 4 files:
+
+1. An animation file for the toggle (usually two)
+2. The avatar menu
+3. The avatar parameters
+4. The animator
+
+With YOTS, you only have to edit one file.
+
+Finally, there are many unduly tedious tasks which you wind up performing over
+the lifetime of an avatar:
+
+1. Adding new articles of clothing. You now have to edit all your existing
+ avatar-wide animations to include them.
+2. Adding new kinds of toggles or sliders. You may want them to affect a large
+ set of items. For example: you added a minimum brightness slider, and now
+ have to animate 20 different articles of clothing.
+3. Removing articles of clothing. You should remove them from your avatar-wide
+ animations. (No one does this because it's a pain in the ass!)
+4. Removing toggles or sliders. It's easy to accidentally orphan an animator
+ layer, or a parameter somewhere.
+
+These are all vastly easier to perform through a textual configuration system
+than through VRChat's native GUI approach.
+
+## Design derivation
Consider this basic example: you have a shirt and a jacket. The shirt hides the
chest to avoid clipping. The jacket hides the shirt sleeves to hide clipping:
@@ -217,7 +215,7 @@ independent part may be comprised of the empty set, in which case it is
discarded. If it's not empty, it's added to the first layer's DBT. The
dependent part is guaranteed to be non-empty, and is simply added to a new DBT.
-## Specification language
+### Specification language
We use JSON to represent the specification. The example above is expressed as
follows:
@@ -465,13 +463,14 @@ Extension 2.
Our animations are complete. Our animator can trivially use the names of each
ToggleSpec as its parameters. To actually generate the first layer, we'll use
-Hai's Animator As Code.
+Unity's built in APIs.
-// TODO document this part. For now just look at AnimatorGenerator.cs.
+// TODO document this part. For now just look at YOTSCore.cs.
We have to generate animations, configs for debug purposes, and finally an
animator file.
+// TODO document this. Again just look at YOTSCore.cs.
## Extensions
diff --git a/YOTSCore.cs b/Scripts/YOTSCore.cs
index a7e275a..a7e275a 100644
--- a/YOTSCore.cs
+++ b/Scripts/YOTSCore.cs
diff --git a/YOTSNDMFConfig.cs b/Scripts/YOTSNDMFConfig.cs
index 0609d18..0609d18 100644
--- a/YOTSNDMFConfig.cs
+++ b/Scripts/YOTSNDMFConfig.cs
diff --git a/YOTSNDMFGenerator.cs b/Scripts/YOTSNDMFGenerator.cs
index e6e0585..e6e0585 100644
--- a/YOTSNDMFGenerator.cs
+++ b/Scripts/YOTSNDMFGenerator.cs
diff --git a/YOTS.prefab b/YOTS.prefab
new file mode 100644
index 0000000..7e296dc
--- /dev/null
+++ b/YOTS.prefab
@@ -0,0 +1,47 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!1 &2194070440052669926
+GameObject:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ serializedVersion: 6
+ m_Component:
+ - component: {fileID: 371625702662608969}
+ - component: {fileID: 386227433558459277}
+ m_Layer: 0
+ m_Name: YOTS
+ m_TagString: EditorOnly
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!4 &371625702662608969
+Transform:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 2194070440052669926}
+ serializedVersion: 2
+ m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
+ m_LocalPosition: {x: 0, y: 0, z: 0}
+ m_LocalScale: {x: 1, y: 1, z: 1}
+ m_ConstrainProportionsScale: 1
+ m_Children: []
+ m_Father: {fileID: 0}
+ m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+--- !u!114 &386227433558459277
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 2194070440052669926}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: fc2ca698e5be6e446861920d8c4e4462, type: 3}
+ m_Name:
+ m_EditorClassIdentifier:
+ jsonConfig: {fileID: 4900000, guid: 7bfaea61dd9889641977c91a93757fce, type: 3}