mirror of
https://github.com/bdunderscore/modular-avatar.git
synced 2025-04-24 05:19:00 +08:00
feat: change shape changer to support multiple target renderers (#1011)
* feat: add target renderer to ChangedShape * chore: add test for ShapeChanger target renderer * feat: add override target to MaterialSetter * fix: resolve added AvatarObjectReference * fix: record prefab instance property modifications * refactor: remove unused setter for AvatarObjectReference * refactor: change ChangedShape and MaterialSwitchObject from struct to class * feat: remove override target from ShapeChanger and MaterialSetter * refactor: align flow and code style of ShapeChanger and MaterialSetter * feat: ShapeChanger target migration * fix: add null check * chore: added some comments and nullchecks --------- Co-authored-by: bd_ <bd_@nadena.dev>
This commit is contained in:
parent
3b9f0d1838
commit
8418f8e047
@ -28,44 +28,14 @@ namespace nadena.dev.modular_avatar.core.editor.ShapeChanger
|
|||||||
|
|
||||||
var f_material_index = uxml.Q<DropdownField>("f-material-index");
|
var f_material_index = uxml.Q<DropdownField>("f-material-index");
|
||||||
|
|
||||||
var f_object = uxml.Q<ObjectField>("f-object");
|
var f_object = uxml.Q<PropertyField>("f-object");
|
||||||
f_object.objectType = typeof(Renderer);
|
|
||||||
f_object.allowSceneObjects = true;
|
|
||||||
|
|
||||||
var f_target_object = uxml.Q<ObjectField>("f-obj-target-object");
|
f_object.RegisterValueChangeCallback(evt =>
|
||||||
var f_reference_path = uxml.Q<TextField>("f-obj-ref-path");
|
|
||||||
|
|
||||||
f_object.RegisterValueChangedCallback(evt =>
|
|
||||||
{
|
{
|
||||||
var gameObj = (evt.newValue as Renderer)?.gameObject;
|
|
||||||
|
|
||||||
if (gameObj == null)
|
|
||||||
{
|
|
||||||
f_target_object.value = null;
|
|
||||||
f_reference_path.value = "";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var path = RuntimeUtil.AvatarRootPath(gameObj);
|
|
||||||
|
|
||||||
f_reference_path.value = path;
|
|
||||||
if (path == "")
|
|
||||||
{
|
|
||||||
f_target_object.value = null;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
f_target_object.value = gameObj;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
EditorApplication.delayCall += UpdateMaterialDropdown;
|
EditorApplication.delayCall += UpdateMaterialDropdown;
|
||||||
});
|
});
|
||||||
UpdateMaterialDropdown();
|
UpdateMaterialDropdown();
|
||||||
|
|
||||||
f_target_object.RegisterValueChangedCallback(_ => UpdateVisualTarget());
|
|
||||||
f_reference_path.RegisterValueChangedCallback(_ => UpdateVisualTarget());
|
|
||||||
|
|
||||||
// Link dropdown to material index field
|
// Link dropdown to material index field
|
||||||
var f_material_index_int = uxml.Q<IntegerField>("f-material-index-int");
|
var f_material_index_int = uxml.Q<IntegerField>("f-material-index-int");
|
||||||
f_material_index_int.RegisterValueChangedCallback(evt =>
|
f_material_index_int.RegisterValueChangedCallback(evt =>
|
||||||
@ -83,30 +53,13 @@ namespace nadena.dev.modular_avatar.core.editor.ShapeChanger
|
|||||||
|
|
||||||
return uxml;
|
return uxml;
|
||||||
|
|
||||||
void UpdateVisualTarget()
|
|
||||||
{
|
|
||||||
var targetObject = AvatarObjectReference.Get(property.FindPropertyRelative("Object"));
|
|
||||||
Renderer targetRenderer;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
targetRenderer = targetObject?.GetComponent<Renderer>();
|
|
||||||
}
|
|
||||||
catch (MissingComponentException e)
|
|
||||||
{
|
|
||||||
targetRenderer = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
f_object.SetValueWithoutNotify(targetRenderer);
|
|
||||||
}
|
|
||||||
|
|
||||||
void UpdateMaterialDropdown()
|
void UpdateMaterialDropdown()
|
||||||
{
|
{
|
||||||
var toggledObject = AvatarObjectReference.Get(property.FindPropertyRelative("Object"));
|
var targetObject = AvatarObjectReference.Get(property.FindPropertyRelative("Object"));
|
||||||
Material[] sharedMaterials;
|
Material[] sharedMaterials;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
sharedMaterials = toggledObject?.GetComponent<Renderer>()?.sharedMaterials;
|
sharedMaterials = targetObject?.GetComponent<Renderer>()?.sharedMaterials;
|
||||||
}
|
}
|
||||||
catch (MissingComponentException e)
|
catch (MissingComponentException e)
|
||||||
{
|
{
|
||||||
|
@ -1,13 +1,9 @@
|
|||||||
<UXML xmlns:ui="UnityEngine.UIElements" xmlns:ed="UnityEditor.UIElements">
|
<UXML xmlns:ui="UnityEngine.UIElements" xmlns:ed="UnityEditor.UIElements">
|
||||||
<ui:VisualElement class="toggled-object-editor">
|
<ui:VisualElement class="toggled-object-editor">
|
||||||
<ui:VisualElement class="horizontal">
|
<ui:VisualElement class="horizontal">
|
||||||
<!--<ed:PropertyField binding-path="Object" label="" name="f-object" class="f-object"/>-->
|
<ed:PropertyField binding-path="Object" label="" name="f-object" class="f-object"/>
|
||||||
<ed:ObjectField label="" name="f-object" class="f-object"/>
|
|
||||||
<ui:DropdownField name="f-material-index" binding-path="MaterialIndex"/>
|
<ui:DropdownField name="f-material-index" binding-path="MaterialIndex"/>
|
||||||
|
|
||||||
<ui:VisualElement style="display:none">
|
<ui:VisualElement style="display:none">
|
||||||
<ui:TextField binding-path="Object.referencePath" label="" name="f-obj-ref-path"/>
|
|
||||||
<ed:ObjectField name="f-obj-target-object" binding-path="Object.targetObject"/>
|
|
||||||
<ed:IntegerField binding-path="MaterialIndex" name="f-material-index-int"/>
|
<ed:IntegerField binding-path="MaterialIndex" name="f-material-index-int"/>
|
||||||
</ui:VisualElement>
|
</ui:VisualElement>
|
||||||
</ui:VisualElement>
|
</ui:VisualElement>
|
||||||
|
@ -1,99 +0,0 @@
|
|||||||
#region
|
|
||||||
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using UnityEditor;
|
|
||||||
using UnityEngine;
|
|
||||||
using UnityEngine.UIElements;
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
namespace nadena.dev.modular_avatar.core.editor.ShapeChanger
|
|
||||||
{
|
|
||||||
public class AddShapePopup : PopupWindowContent
|
|
||||||
{
|
|
||||||
private const string Root = "Packages/nadena.dev.modular-avatar/Editor/Inspector/ShapeChanger/";
|
|
||||||
const string UxmlPath = Root + "AddShapePopup.uxml";
|
|
||||||
const string UssPath = Root + "ShapeChangerStyles.uss";
|
|
||||||
|
|
||||||
private VisualElement _elem;
|
|
||||||
private ScrollView _scrollView;
|
|
||||||
|
|
||||||
public AddShapePopup(ModularAvatarShapeChanger changer)
|
|
||||||
{
|
|
||||||
if (changer == null) return;
|
|
||||||
var target = changer.targetRenderer.Get(changer)?.GetComponent<SkinnedMeshRenderer>();
|
|
||||||
if (target == null || target.sharedMesh == null) return;
|
|
||||||
|
|
||||||
var alreadyRegistered = changer.Shapes.Select(c => c.ShapeName).ToHashSet();
|
|
||||||
|
|
||||||
var keys = new List<string>();
|
|
||||||
for (int i = 0; i < target.sharedMesh.blendShapeCount; i++)
|
|
||||||
{
|
|
||||||
var name = target.sharedMesh.GetBlendShapeName(i);
|
|
||||||
if (alreadyRegistered.Contains(name)) continue;
|
|
||||||
|
|
||||||
keys.Add(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
var uss = AssetDatabase.LoadAssetAtPath<StyleSheet>(UssPath);
|
|
||||||
var uxml = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>(UxmlPath);
|
|
||||||
|
|
||||||
_elem = uxml.CloneTree();
|
|
||||||
_elem.styleSheets.Add(uss);
|
|
||||||
Localization.UI.Localize(_elem);
|
|
||||||
|
|
||||||
_scrollView = _elem.Q<ScrollView>("scroll-view");
|
|
||||||
|
|
||||||
if (keys.Count > 0)
|
|
||||||
{
|
|
||||||
_scrollView.contentContainer.Clear();
|
|
||||||
|
|
||||||
foreach (var key in keys)
|
|
||||||
{
|
|
||||||
var container = new VisualElement();
|
|
||||||
container.AddToClassList("add-shape-row");
|
|
||||||
|
|
||||||
Button btn = default;
|
|
||||||
btn = new Button(() =>
|
|
||||||
{
|
|
||||||
AddShape(changer, key);
|
|
||||||
container.RemoveFromHierarchy();
|
|
||||||
});
|
|
||||||
btn.text = "+";
|
|
||||||
container.Add(btn);
|
|
||||||
|
|
||||||
var label = new Label(key);
|
|
||||||
container.Add(label);
|
|
||||||
|
|
||||||
_scrollView.contentContainer.Add(container);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void AddShape(ModularAvatarShapeChanger changer, string key)
|
|
||||||
{
|
|
||||||
Undo.RecordObject(changer, "Add Shape");
|
|
||||||
|
|
||||||
changer.Shapes.Add(new ChangedShape()
|
|
||||||
{
|
|
||||||
ShapeName = key,
|
|
||||||
ChangeType = ShapeChangeType.Delete,
|
|
||||||
Value = 100
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void OnGUI(Rect rect)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void OnOpen()
|
|
||||||
{
|
|
||||||
editorWindow.rootVisualElement.Clear();
|
|
||||||
editorWindow.rootVisualElement.Add(_elem);
|
|
||||||
//editorWindow.rootVisualElement.Clear();
|
|
||||||
|
|
||||||
//editorWindow.rootVisualElement.Add(new Label("Hello, World!"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,3 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: 1a8351fafb3740918363f60365adfeda
|
|
||||||
timeCreated: 1717205112
|
|
@ -1,11 +0,0 @@
|
|||||||
<UXML xmlns:ui="UnityEngine.UIElements">
|
|
||||||
<ui:VisualElement class="add-shape-popup">
|
|
||||||
<ui:VisualElement class="add-shape-popup">
|
|
||||||
<ui:Label text="Select Blendshape"/>
|
|
||||||
<ui:VisualElement class="vline"/>
|
|
||||||
<ui:ScrollView show-horizontal-scroller="false" name="scroll-view">
|
|
||||||
<ui:Label text="<none remaining>" class="placeholder"/>
|
|
||||||
</ui:ScrollView>
|
|
||||||
</ui:VisualElement>
|
|
||||||
</ui:VisualElement>
|
|
||||||
</UXML>
|
|
@ -1,3 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: 6753a7b3eae1416cb04786cf53778c33
|
|
||||||
timeCreated: 1717205258
|
|
@ -1,7 +1,10 @@
|
|||||||
#region
|
#region
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using UnityEditor;
|
using UnityEditor;
|
||||||
using UnityEditor.UIElements;
|
using UnityEditor.UIElements;
|
||||||
|
using UnityEngine;
|
||||||
using UnityEngine.UIElements;
|
using UnityEngine.UIElements;
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
@ -24,6 +27,16 @@ namespace nadena.dev.modular_avatar.core.editor.ShapeChanger
|
|||||||
uxml.styleSheets.Add(uss);
|
uxml.styleSheets.Add(uss);
|
||||||
uxml.BindProperty(property);
|
uxml.BindProperty(property);
|
||||||
|
|
||||||
|
var f_shape_name = uxml.Q<DropdownField>("f-shape-name");
|
||||||
|
|
||||||
|
var f_object = uxml.Q<PropertyField>("f-object");
|
||||||
|
|
||||||
|
f_object.RegisterValueChangeCallback(evt =>
|
||||||
|
{
|
||||||
|
EditorApplication.delayCall += UpdateShapeDropdown;
|
||||||
|
});
|
||||||
|
UpdateShapeDropdown();
|
||||||
|
|
||||||
uxml.Q<PropertyField>("f-change-type").RegisterCallback<ChangeEvent<string>>(
|
uxml.Q<PropertyField>("f-change-type").RegisterCallback<ChangeEvent<string>>(
|
||||||
e =>
|
e =>
|
||||||
{
|
{
|
||||||
@ -39,6 +52,29 @@ namespace nadena.dev.modular_avatar.core.editor.ShapeChanger
|
|||||||
);
|
);
|
||||||
|
|
||||||
return uxml;
|
return uxml;
|
||||||
|
|
||||||
|
void UpdateShapeDropdown()
|
||||||
|
{
|
||||||
|
var targetObject = AvatarObjectReference.Get(property.FindPropertyRelative("Object"));
|
||||||
|
List<string> shapeNames;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var mesh = targetObject?.GetComponent<SkinnedMeshRenderer>()?.sharedMesh;
|
||||||
|
shapeNames = mesh == null ? null : Enumerable.Range(0, mesh.blendShapeCount)
|
||||||
|
.Select(x => mesh.GetBlendShapeName(x))
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
catch (MissingComponentException e)
|
||||||
|
{
|
||||||
|
shapeNames = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
f_shape_name.SetEnabled(shapeNames != null);
|
||||||
|
f_shape_name.choices = shapeNames ?? new();
|
||||||
|
|
||||||
|
f_shape_name.formatListItemCallback = name => shapeNames != null ? name : "<Missing SkinnedMeshRenderer>";
|
||||||
|
f_shape_name.formatSelectedValueCallback = f_shape_name.formatListItemCallback;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,8 +1,13 @@
|
|||||||
<UXML xmlns:ui="UnityEngine.UIElements" xmlns:ed="UnityEditor.UIElements">
|
<UXML xmlns:ui="UnityEngine.UIElements" xmlns:ed="UnityEditor.UIElements">
|
||||||
<ui:VisualElement class="changed-shape-editor">
|
<ui:VisualElement class="changed-shape-editor">
|
||||||
<ui:Label text="<shape name>" binding-path="ShapeName" name="f-name"/>
|
<ui:VisualElement class="horizontal">
|
||||||
<ed:PropertyField binding-path="ChangeType" label="" name="f-change-type"/>
|
<ed:PropertyField binding-path="Object" label="" name="f-object" class="f-object"/>
|
||||||
<ed:PropertyField binding-path="Value" label="" name="f-value" class="f-value"/>
|
<ed:PropertyField binding-path="ChangeType" label="" name="f-change-type"/>
|
||||||
<ui:VisualElement name="f-value-delete" class="f-value"/>
|
</ui:VisualElement>
|
||||||
|
<ui:VisualElement class="horizontal">
|
||||||
|
<ui:DropdownField name="f-shape-name" binding-path="ShapeName"/>
|
||||||
|
<ed:PropertyField binding-path="Value" label="" name="f-value" class="f-value"/>
|
||||||
|
<ui:VisualElement name="f-value-delete" class="f-value"/>
|
||||||
|
</ui:VisualElement>
|
||||||
</ui:VisualElement>
|
</ui:VisualElement>
|
||||||
</UXML>
|
</UXML>
|
@ -2,7 +2,6 @@
|
|||||||
xmlns:ma="nadena.dev.modular_avatar.core.editor">
|
xmlns:ma="nadena.dev.modular_avatar.core.editor">
|
||||||
<ui:VisualElement name="root-box">
|
<ui:VisualElement name="root-box">
|
||||||
<ui:VisualElement name="group-box">
|
<ui:VisualElement name="group-box">
|
||||||
<ed:PropertyField binding-path="m_targetRenderer" label="reactive_object.shape-changer.target-renderer" class="ndmf-tr"/>
|
|
||||||
<ed:PropertyField binding-path="m_inverted" label="reactive_object.inverse" class="ndmf-tr"/>
|
<ed:PropertyField binding-path="m_inverted" label="reactive_object.inverse" class="ndmf-tr"/>
|
||||||
|
|
||||||
<ui:VisualElement name="ListViewContainer">
|
<ui:VisualElement name="ListViewContainer">
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
#region
|
#region
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Linq;
|
||||||
using UnityEditor;
|
using UnityEditor;
|
||||||
using UnityEditor.UIElements;
|
using UnityEditor.UIElements;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
@ -18,6 +19,7 @@ namespace nadena.dev.modular_avatar.core.editor.ShapeChanger
|
|||||||
[SerializeField] private StyleSheet uss;
|
[SerializeField] private StyleSheet uss;
|
||||||
[SerializeField] private VisualTreeAsset uxml;
|
[SerializeField] private VisualTreeAsset uxml;
|
||||||
|
|
||||||
|
private BlendshapeSelectWindow _window;
|
||||||
|
|
||||||
protected override void OnInnerInspectorGUI()
|
protected override void OnInnerInspectorGUI()
|
||||||
{
|
{
|
||||||
@ -41,12 +43,50 @@ namespace nadena.dev.modular_avatar.core.editor.ShapeChanger
|
|||||||
var field_addButton = typeof(BaseListView).GetField("m_AddButton", NonPublic | Instance);
|
var field_addButton = typeof(BaseListView).GetField("m_AddButton", NonPublic | Instance);
|
||||||
var addButton = (Button)field_addButton.GetValue(listView);
|
var addButton = (Button)field_addButton.GetValue(listView);
|
||||||
|
|
||||||
addButton.clickable = new Clickable(() =>
|
addButton.clickable = new Clickable(OpenAddWindow);
|
||||||
{
|
|
||||||
PopupWindow.Show(addButton.worldBound, new AddShapePopup(target as ModularAvatarShapeChanger));
|
|
||||||
});
|
|
||||||
|
|
||||||
return root;
|
return root;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnDisable()
|
||||||
|
{
|
||||||
|
if (_window != null) DestroyImmediate(_window);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnDestroy()
|
||||||
|
{
|
||||||
|
base.OnDestroy();
|
||||||
|
if (_window != null) DestroyImmediate(_window);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OpenAddWindow()
|
||||||
|
{
|
||||||
|
if (_window != null) DestroyImmediate(_window);
|
||||||
|
_window = CreateInstance<BlendshapeSelectWindow>();
|
||||||
|
_window.AvatarRoot = RuntimeUtil.FindAvatarTransformInParents((target as ModularAvatarShapeChanger).transform).gameObject;
|
||||||
|
_window.OfferBinding += OfferBinding;
|
||||||
|
_window.Show();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OfferBinding(BlendshapeBinding binding)
|
||||||
|
{
|
||||||
|
var changer = target as ModularAvatarShapeChanger;
|
||||||
|
if (changer.Shapes.Any(x => x.Object.Equals(binding.ReferenceMesh) && x.ShapeName == binding.Blendshape))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Undo.RecordObject(changer, "Add Shape");
|
||||||
|
|
||||||
|
changer.Shapes.Add(new ChangedShape()
|
||||||
|
{
|
||||||
|
Object = binding.ReferenceMesh,
|
||||||
|
ShapeName = binding.Blendshape,
|
||||||
|
ChangeType = ShapeChangeType.Delete,
|
||||||
|
Value = 100
|
||||||
|
});
|
||||||
|
|
||||||
|
PrefabUtility.RecordPrefabInstancePropertyModifications(changer);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -39,11 +39,15 @@
|
|||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
}
|
}
|
||||||
|
|
||||||
.changed-shape-editor {
|
.changed-shape-editor .horizontal {
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
}
|
}
|
||||||
|
|
||||||
.changed-shape-editor #f-name {
|
.changed-shape-editor #f-object {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.changed-shape-editor #f-shape-name {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,7 +68,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.f-value {
|
.f-value {
|
||||||
width: 40px;
|
width: 75px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#f-value-delete {
|
#f-value-delete {
|
||||||
@ -77,6 +81,7 @@
|
|||||||
|
|
||||||
.change-type-delete #f-value-delete {
|
.change-type-delete #f-value-delete {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
height: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Add shape window */
|
/* Add shape window */
|
||||||
|
@ -251,6 +251,5 @@
|
|||||||
"ma_info.param_usage_ui.no_data": "[ NO DATA ]",
|
"ma_info.param_usage_ui.no_data": "[ NO DATA ]",
|
||||||
"reactive_object.inverse": "Inverse Condition",
|
"reactive_object.inverse": "Inverse Condition",
|
||||||
"reactive_object.material-setter.set-to": "Set material to: ",
|
"reactive_object.material-setter.set-to": "Set material to: ",
|
||||||
"reactive_object.shape-changer.target-renderer": "Target Renderer",
|
|
||||||
"menuitem.misc.add_toggle": "Add toggle"
|
"menuitem.misc.add_toggle": "Add toggle"
|
||||||
}
|
}
|
@ -247,6 +247,5 @@
|
|||||||
"ma_info.param_usage_ui.no_data": "[ NO DATA ]",
|
"ma_info.param_usage_ui.no_data": "[ NO DATA ]",
|
||||||
"reactive_object.inverse": "条件を反転",
|
"reactive_object.inverse": "条件を反転",
|
||||||
"reactive_object.material-setter.set-to": "変更先のマテリアル ",
|
"reactive_object.material-setter.set-to": "変更先のマテリアル ",
|
||||||
"reactive_object.shape-changer.target-renderer": "操作するレンダラー",
|
|
||||||
"menuitem.misc.add_toggle": "トグルを追加"
|
"menuitem.misc.add_toggle": "トグルを追加"
|
||||||
}
|
}
|
@ -250,6 +250,5 @@
|
|||||||
"ma_info.param_usage_ui.no_data": "【無資訊】",
|
"ma_info.param_usage_ui.no_data": "【無資訊】",
|
||||||
"reactive_object.inverse": "反轉條件",
|
"reactive_object.inverse": "反轉條件",
|
||||||
"reactive_object.material-setter.set-to": "將材質設定為:",
|
"reactive_object.material-setter.set-to": "將材質設定為:",
|
||||||
"reactive_object.shape-changer.target-renderer": "目標 Renderer",
|
|
||||||
"menuitem.misc.add_toggle": "新增開關"
|
"menuitem.misc.add_toggle": "新增開關"
|
||||||
}
|
}
|
@ -15,15 +15,16 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
|
|
||||||
foreach (var changer in changers)
|
foreach (var changer in changers)
|
||||||
{
|
{
|
||||||
var renderer = changer.targetRenderer.Get(changer)?.GetComponent<SkinnedMeshRenderer>();
|
if (changer.Shapes == null) continue;
|
||||||
if (renderer == null) continue;
|
|
||||||
|
|
||||||
var mesh = renderer.sharedMesh;
|
|
||||||
|
|
||||||
if (mesh == null) continue;
|
|
||||||
|
|
||||||
foreach (var shape in changer.Shapes)
|
foreach (var shape in changer.Shapes)
|
||||||
{
|
{
|
||||||
|
var renderer = shape.Object.Get(changer)?.GetComponent<SkinnedMeshRenderer>();
|
||||||
|
if (renderer == null) continue;
|
||||||
|
|
||||||
|
var mesh = renderer.sharedMesh;
|
||||||
|
if (mesh == null) continue;
|
||||||
|
|
||||||
var shapeId = mesh.GetBlendShapeIndex(shape.ShapeName);
|
var shapeId = mesh.GetBlendShapeIndex(shape.ShapeName);
|
||||||
if (shapeId < 0) continue;
|
if (shapeId < 0) continue;
|
||||||
|
|
||||||
@ -92,9 +93,7 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
|
|
||||||
foreach (var obj in setter.Objects)
|
foreach (var obj in setter.Objects)
|
||||||
{
|
{
|
||||||
var target = obj.Object.Get(setter);
|
var renderer = obj.Object.Get(setter)?.GetComponent<Renderer>();
|
||||||
if (target == null) continue;
|
|
||||||
var renderer = target.GetComponent<Renderer>();
|
|
||||||
if (renderer == null || renderer.sharedMaterials.Length < obj.MaterialIndex) continue;
|
if (renderer == null || renderer.sharedMaterials.Length < obj.MaterialIndex) continue;
|
||||||
|
|
||||||
var key = new TargetProp
|
var key = new TargetProp
|
||||||
|
@ -31,40 +31,41 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
var menuItemPreview = new MenuItemPreviewCondition(context);
|
var menuItemPreview = new MenuItemPreviewCondition(context);
|
||||||
var setters = context.GetComponentsByType<ModularAvatarMaterialSetter>();
|
var setters = context.GetComponentsByType<ModularAvatarMaterialSetter>();
|
||||||
|
|
||||||
var groups = new Dictionary<Renderer, ImmutableList<ModularAvatarMaterialSetter>>();
|
var builders =
|
||||||
|
new Dictionary<Renderer, ImmutableList<ModularAvatarMaterialSetter>.Builder>(
|
||||||
|
new ObjectIdentityComparer<Renderer>());
|
||||||
|
|
||||||
foreach (var setter in setters)
|
foreach (var setter in setters)
|
||||||
{
|
{
|
||||||
|
if (setter == null) continue;
|
||||||
|
|
||||||
var mami = context.GetComponent<ModularAvatarMenuItem>(setter.gameObject);
|
var mami = context.GetComponent<ModularAvatarMenuItem>(setter.gameObject);
|
||||||
bool active = context.ActiveAndEnabled(setter) && (mami == null || menuItemPreview.IsEnabledForPreview(mami));
|
bool active = context.ActiveAndEnabled(setter) && (mami == null || menuItemPreview.IsEnabledForPreview(mami));
|
||||||
if (active == context.Observe(setter, t => t.Inverted)) continue;
|
if (active == context.Observe(setter, s => s.Inverted)) continue;
|
||||||
|
|
||||||
var objs = context.Observe(setter, s => s.Objects.Select(o => (o.Object.Get(s), o.Material, o.MaterialIndex)).ToList(), (x, y) => x.SequenceEqual(y));
|
var objects = context.Observe(setter, s => s.Objects.Select(o => (o.Object.Get(s), o.Material, o.MaterialIndex)).ToList(), Enumerable.SequenceEqual);
|
||||||
|
|
||||||
if (setter.Objects == null) continue;
|
foreach (var (target, mat, index) in objects)
|
||||||
|
|
||||||
foreach (var (obj, mat, index) in objs)
|
|
||||||
{
|
{
|
||||||
if (obj == null) continue;
|
var renderer = context.GetComponent<Renderer>(target);
|
||||||
var renderer = context.GetComponent<Renderer>(obj);
|
|
||||||
if (renderer == null) continue;
|
if (renderer == null) continue;
|
||||||
|
|
||||||
var matCount = context.Observe(renderer, r => r.sharedMaterials.Length);
|
var matCount = context.Observe(renderer, r => r.sharedMaterials.Length);
|
||||||
|
|
||||||
if (matCount <= index) continue;
|
if (matCount <= index) continue;
|
||||||
|
|
||||||
if (!groups.TryGetValue(renderer, out var list))
|
if (!builders.TryGetValue(renderer, out var builder))
|
||||||
{
|
{
|
||||||
list = ImmutableList.Create<ModularAvatarMaterialSetter>();
|
builder = ImmutableList.CreateBuilder<ModularAvatarMaterialSetter>();
|
||||||
groups.Add(renderer, list);
|
builders[renderer] = builder;
|
||||||
}
|
}
|
||||||
|
|
||||||
groups[renderer] = list.Add(setter);
|
builder.Add(setter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var finalGroups = groups.Select(g => RenderGroup.For(g.Key).WithData(g.Value)).ToImmutableList();
|
return builders.Select(g => RenderGroup.For(g.Key).WithData(g.Value.ToImmutable()))
|
||||||
return finalGroups;
|
.ToImmutableList();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<IRenderFilterNode> Instantiate(RenderGroup group, IEnumerable<(Renderer, Renderer)> proxyPairs, ComputeContext context)
|
public Task<IRenderFilterNode> Instantiate(RenderGroup group, IEnumerable<(Renderer, Renderer)> proxyPairs, ComputeContext context)
|
||||||
@ -80,48 +81,33 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
private readonly ImmutableList<ModularAvatarMaterialSetter> _setters;
|
private readonly ImmutableList<ModularAvatarMaterialSetter> _setters;
|
||||||
private ImmutableList<(int, Material)> _materials;
|
private ImmutableList<(int, Material)> _materials;
|
||||||
|
|
||||||
public RenderAspects WhatChanged {get; private set; }
|
public RenderAspects WhatChanged => RenderAspects.Material;
|
||||||
|
|
||||||
public void OnFrame(Renderer original, Renderer proxy)
|
|
||||||
{
|
|
||||||
var mats = proxy.sharedMaterials;
|
|
||||||
|
|
||||||
foreach (var mat in _materials)
|
|
||||||
{
|
|
||||||
if (mat.Item1 <= mats.Length)
|
|
||||||
{
|
|
||||||
mats[mat.Item1] = mat.Item2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
proxy.sharedMaterials = mats;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Node(ImmutableList<ModularAvatarMaterialSetter> setters)
|
public Node(ImmutableList<ModularAvatarMaterialSetter> setters)
|
||||||
{
|
{
|
||||||
_setters = setters;
|
_setters = setters;
|
||||||
_materials = ImmutableList<(int, Material)>.Empty;
|
_materials = ImmutableList<(int, Material)>.Empty;
|
||||||
WhatChanged = RenderAspects.Material;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<IRenderFilterNode> Refresh(IEnumerable<(Renderer, Renderer)> proxyPairs, ComputeContext context, RenderAspects updatedAspects)
|
public Task<IRenderFilterNode> Refresh(IEnumerable<(Renderer, Renderer)> proxyPairs, ComputeContext context, RenderAspects updatedAspects)
|
||||||
{
|
{
|
||||||
var proxyPair = proxyPairs.First();
|
var (original, proxy) = proxyPairs.First();
|
||||||
var original = proxyPair.Item1;
|
|
||||||
var proxy = proxyPair.Item2;
|
if (original == null || proxy == null) return null;
|
||||||
|
|
||||||
var mats = new Material[proxy.sharedMaterials.Length];
|
var mats = new Material[proxy.sharedMaterials.Length];
|
||||||
|
|
||||||
foreach (var setter in _setters)
|
foreach (var setter in _setters)
|
||||||
{
|
{
|
||||||
var objects = context.Observe(setter, s => s.Objects
|
if (setter == null) continue;
|
||||||
.Where(obj => obj.Object.Get(s) == original.gameObject)
|
|
||||||
.Select(obj => (obj.Material, obj.MaterialIndex)),
|
var objects = context.Observe(setter, s => s.Objects.Select(o => (o.Object.Get(s), o.Material, o.MaterialIndex)).ToList(), Enumerable.SequenceEqual);
|
||||||
(x, y) => x.SequenceEqual(y)
|
|
||||||
);
|
|
||||||
|
|
||||||
foreach (var (mat, index) in objects)
|
foreach (var (target, mat, index) in objects)
|
||||||
{
|
{
|
||||||
|
var renderer = context.GetComponent<Renderer>(target);
|
||||||
|
if (renderer != original) continue;
|
||||||
|
|
||||||
if (index <= mats.Length)
|
if (index <= mats.Length)
|
||||||
{
|
{
|
||||||
mats[index] = mat;
|
mats[index] = mat;
|
||||||
@ -131,14 +117,32 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
|
|
||||||
var materials = mats.Select((m, i) => (i, m)).Where(kvp => kvp.m != null).ToImmutableList();
|
var materials = mats.Select((m, i) => (i, m)).Where(kvp => kvp.m != null).ToImmutableList();
|
||||||
|
|
||||||
if (materials.SequenceEqual(_materials))
|
if (!materials.SequenceEqual(_materials))
|
||||||
{
|
{
|
||||||
return Task.FromResult<IRenderFilterNode>(this);
|
return Task.FromResult<IRenderFilterNode>(new Node(_setters)
|
||||||
|
{
|
||||||
|
_materials = materials,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
return Task.FromResult<IRenderFilterNode>(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnFrame(Renderer original, Renderer proxy)
|
||||||
|
{
|
||||||
|
if (original == null || proxy == null) return;
|
||||||
|
|
||||||
|
var mats = proxy.sharedMaterials;
|
||||||
|
|
||||||
|
foreach (var mat in _materials)
|
||||||
{
|
{
|
||||||
return Task.FromResult<IRenderFilterNode>(new Node(_setters) { _materials = materials });
|
if (mat.Item1 <= mats.Length)
|
||||||
|
{
|
||||||
|
mats[mat.Item1] = mat.Item2;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
proxy.sharedMaterials = mats;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,79 +31,107 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
return context.Observe(EnableNode.IsEnabled);
|
return context.Observe(EnableNode.IsEnabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ImmutableList<RenderGroup> GetTargetGroups(ComputeContext ctx)
|
public ImmutableList<RenderGroup> GetTargetGroups(ComputeContext context)
|
||||||
{
|
{
|
||||||
var menuItemPreview = new MenuItemPreviewCondition(ctx);
|
var menuItemPreview = new MenuItemPreviewCondition(context);
|
||||||
|
var changers = context.GetComponentsByType<ModularAvatarShapeChanger>();
|
||||||
var allChangers = ctx.GetComponentsByType<ModularAvatarShapeChanger>();
|
|
||||||
|
|
||||||
var groups =
|
var builders =
|
||||||
new Dictionary<Renderer, ImmutableList<ModularAvatarShapeChanger>.Builder>(
|
new Dictionary<Renderer, ImmutableList<ModularAvatarShapeChanger>.Builder>(
|
||||||
new ObjectIdentityComparer<Renderer>());
|
new ObjectIdentityComparer<Renderer>());
|
||||||
|
|
||||||
foreach (var changer in allChangers)
|
foreach (var changer in changers)
|
||||||
{
|
{
|
||||||
if (changer == null) continue;
|
if (changer == null) continue;
|
||||||
|
|
||||||
var mami = ctx.GetComponent<ModularAvatarMenuItem>(changer.gameObject);
|
var mami = context.GetComponent<ModularAvatarMenuItem>(changer.gameObject);
|
||||||
bool active = ctx.ActiveAndEnabled(changer) && (mami == null || menuItemPreview.IsEnabledForPreview(mami));
|
bool active = context.ActiveAndEnabled(changer) && (mami == null || menuItemPreview.IsEnabledForPreview(mami));
|
||||||
if (active == ctx.Observe(changer, t => t.Inverted)) continue;
|
if (active == context.Observe(changer, c => c.Inverted)) continue;
|
||||||
|
|
||||||
var target = ctx.Observe(changer, _ => changer.targetRenderer.Get(changer));
|
var shapes = context.Observe(changer, c => c.Shapes.Select(s => (s.Object.Get(c), s.ShapeName, s.ChangeType, s.Value)).ToList(), Enumerable.SequenceEqual);
|
||||||
var renderer = ctx.GetComponent<SkinnedMeshRenderer>(target);
|
|
||||||
|
|
||||||
if (renderer == null) continue;
|
foreach (var (target, name, type, value) in shapes)
|
||||||
|
|
||||||
if (!groups.TryGetValue(renderer, out var group))
|
|
||||||
{
|
{
|
||||||
group = ImmutableList.CreateBuilder<ModularAvatarShapeChanger>();
|
var renderer = context.GetComponent<SkinnedMeshRenderer>(target);
|
||||||
groups[renderer] = group;
|
if (renderer == null) continue;
|
||||||
}
|
|
||||||
|
|
||||||
group.Add(changer);
|
if (!builders.TryGetValue(renderer, out var builder))
|
||||||
|
{
|
||||||
|
builder = ImmutableList.CreateBuilder<ModularAvatarShapeChanger>();
|
||||||
|
builders[renderer] = builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.Add(changer);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return groups.Select(g => RenderGroup.For(g.Key).WithData(g.Value.ToImmutable()))
|
return builders.Select(g => RenderGroup.For(g.Key).WithData(g.Value.ToImmutable()))
|
||||||
.ToImmutableList();
|
.ToImmutableList();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IRenderFilterNode> Instantiate(
|
public Task<IRenderFilterNode> Instantiate(RenderGroup group, IEnumerable<(Renderer, Renderer)> proxyPairs, ComputeContext context)
|
||||||
RenderGroup group,
|
|
||||||
IEnumerable<(Renderer, Renderer)> proxyPairs,
|
|
||||||
ComputeContext context)
|
|
||||||
{
|
{
|
||||||
var node = new Node(group);
|
var changers = group.GetData<ImmutableList<ModularAvatarShapeChanger>>();
|
||||||
|
var node = new Node(changers);
|
||||||
|
|
||||||
try
|
return node.Refresh(proxyPairs, context, 0);
|
||||||
{
|
|
||||||
await node.Init(proxyPairs, context);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
// dispose
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
|
|
||||||
return node;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private class Node : IRenderFilterNode
|
private class Node : IRenderFilterNode
|
||||||
{
|
{
|
||||||
private readonly RenderGroup _group;
|
|
||||||
private readonly ImmutableList<ModularAvatarShapeChanger> _changers;
|
private readonly ImmutableList<ModularAvatarShapeChanger> _changers;
|
||||||
|
private ImmutableHashSet<(int, float)> _shapes;
|
||||||
|
private ImmutableHashSet<int> _toDelete;
|
||||||
private Mesh _generatedMesh = null;
|
private Mesh _generatedMesh = null;
|
||||||
private HashSet<int> _toDelete;
|
|
||||||
|
|
||||||
internal Node(RenderGroup group)
|
public RenderAspects WhatChanged => RenderAspects.Shapes | RenderAspects.Mesh;
|
||||||
|
|
||||||
|
internal Node(ImmutableList<ModularAvatarShapeChanger> changers)
|
||||||
{
|
{
|
||||||
_group = group;
|
_changers = changers;
|
||||||
_changers = _group.GetData<ImmutableList<ModularAvatarShapeChanger>>();
|
_shapes = ImmutableHashSet<(int, float)>.Empty;
|
||||||
|
_toDelete = ImmutableHashSet<int>.Empty;
|
||||||
|
_generatedMesh = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private HashSet<int> GetToDeleteSet(SkinnedMeshRenderer proxy, ComputeContext context)
|
public Task<IRenderFilterNode> Refresh(IEnumerable<(Renderer, Renderer)> proxyPairs, ComputeContext context, RenderAspects updatedAspects)
|
||||||
{
|
{
|
||||||
var toDelete = new HashSet<int>();
|
var (original, proxy) = proxyPairs.First();
|
||||||
|
|
||||||
|
if (original == null || proxy == null) return null;
|
||||||
|
if (original is not SkinnedMeshRenderer originalSmr || proxy is not SkinnedMeshRenderer proxySmr) return null;
|
||||||
|
|
||||||
|
var shapes = GetShapesSet(originalSmr, proxySmr, context);
|
||||||
|
var toDelete = GetToDeleteSet(originalSmr, proxySmr, context);
|
||||||
|
|
||||||
|
if (!toDelete.SequenceEqual(_toDelete))
|
||||||
|
{
|
||||||
|
return Task.FromResult<IRenderFilterNode>(new Node(_changers)
|
||||||
|
{
|
||||||
|
_shapes = shapes,
|
||||||
|
_toDelete = toDelete,
|
||||||
|
_generatedMesh = GetGeneratedMesh(proxySmr, toDelete),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!shapes.SequenceEqual(_shapes))
|
||||||
|
{
|
||||||
|
var reusableMesh = _generatedMesh;
|
||||||
|
_generatedMesh = null;
|
||||||
|
return Task.FromResult<IRenderFilterNode>(new Node(_changers)
|
||||||
|
{
|
||||||
|
_shapes = shapes,
|
||||||
|
_toDelete = toDelete,
|
||||||
|
_generatedMesh = reusableMesh,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.FromResult<IRenderFilterNode>(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ImmutableHashSet<(int, float)> GetShapesSet(SkinnedMeshRenderer original, SkinnedMeshRenderer proxy, ComputeContext context)
|
||||||
|
{
|
||||||
|
var builder = ImmutableHashSet.CreateBuilder<(int, float)>();
|
||||||
var mesh = context.Observe(proxy, p => p.sharedMesh, (a, b) =>
|
var mesh = context.Observe(proxy, p => p.sharedMesh, (a, b) =>
|
||||||
{
|
{
|
||||||
if (a != b)
|
if (a != b)
|
||||||
@ -117,67 +145,60 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
|
|
||||||
foreach (var changer in _changers)
|
foreach (var changer in _changers)
|
||||||
{
|
{
|
||||||
var shapes = context.Observe(changer,
|
if (changer == null) continue;
|
||||||
c => c.Shapes
|
|
||||||
.Where(s => s.ChangeType == ShapeChangeType.Delete)
|
|
||||||
.Select(s => s.ShapeName)
|
|
||||||
.ToImmutableList(),
|
|
||||||
Enumerable.SequenceEqual
|
|
||||||
);
|
|
||||||
|
|
||||||
foreach (var shape in shapes)
|
var shapes = context.Observe(changer, c => c.Shapes.Select(s => (s.Object.Get(c), s.ShapeName, s.ChangeType, s.Value)).ToList(), Enumerable.SequenceEqual);
|
||||||
|
|
||||||
|
foreach (var (target, name, type, value) in shapes)
|
||||||
{
|
{
|
||||||
var index = mesh.GetBlendShapeIndex(shape);
|
var renderer = context.GetComponent<SkinnedMeshRenderer>(target);
|
||||||
|
if (renderer != original) continue;
|
||||||
|
|
||||||
|
var index = mesh.GetBlendShapeIndex(name);
|
||||||
if (index < 0) continue;
|
if (index < 0) continue;
|
||||||
toDelete.Add(index);
|
builder.Add((index, type == ShapeChangeType.Delete ? 100 : value));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return toDelete;
|
return builder.ToImmutable();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Init(
|
private ImmutableHashSet<int> GetToDeleteSet(SkinnedMeshRenderer original, SkinnedMeshRenderer proxy, ComputeContext context)
|
||||||
IEnumerable<(Renderer, Renderer)> renderers,
|
|
||||||
ComputeContext context
|
|
||||||
)
|
|
||||||
{
|
{
|
||||||
var (original, proxy) = renderers.First();
|
var builder = ImmutableHashSet.CreateBuilder<int>();
|
||||||
|
var mesh = context.Observe(proxy, p => p.sharedMesh, (a, b) =>
|
||||||
if (original == null || proxy == null) return;
|
|
||||||
if (!(proxy is SkinnedMeshRenderer smr)) return;
|
|
||||||
|
|
||||||
await Init(smr, context, GetToDeleteSet(smr, context));
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<IRenderFilterNode> Refresh(IEnumerable<(Renderer, Renderer)> proxyPairs,
|
|
||||||
ComputeContext context, RenderAspects updatedAspects)
|
|
||||||
{
|
|
||||||
if ((updatedAspects & RenderAspects.Mesh) != 0) return null;
|
|
||||||
|
|
||||||
var (original, proxy) = proxyPairs.First();
|
|
||||||
|
|
||||||
if (original == null || proxy == null) return null;
|
|
||||||
if (!(proxy is SkinnedMeshRenderer smr)) return null;
|
|
||||||
|
|
||||||
var toDelete = GetToDeleteSet(smr, context);
|
|
||||||
if (toDelete.Count == _toDelete.Count && toDelete.All(_toDelete.Contains))
|
|
||||||
{
|
{
|
||||||
//System.Diagnostics.Debug.WriteLine("[ShapeChangerPreview] No changes detected. Retaining node.");
|
if (a != b)
|
||||||
return this;
|
{
|
||||||
|
Debug.Log($"mesh changed {a.GetInstanceID()} -> {b.GetInstanceID()}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
foreach (var changer in _changers)
|
||||||
|
{
|
||||||
|
var shapes = context.Observe(changer, c => c.Shapes.Select(s => (s.Object.Get(c), s.ShapeName, s.ChangeType, s.Value)).ToList(), Enumerable.SequenceEqual);
|
||||||
|
|
||||||
|
foreach (var (target, name, type, value) in shapes)
|
||||||
|
{
|
||||||
|
if (type != ShapeChangeType.Delete) continue;
|
||||||
|
|
||||||
|
var renderer = context.GetComponent<SkinnedMeshRenderer>(target);
|
||||||
|
if (renderer != original) continue;
|
||||||
|
|
||||||
|
var index = mesh.GetBlendShapeIndex(name);
|
||||||
|
if (index < 0) continue;
|
||||||
|
builder.Add(index);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var node = new Node(_group);
|
return builder.ToImmutable();
|
||||||
await node.Init(smr, context, toDelete);
|
|
||||||
return node;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Init(
|
public Mesh GetGeneratedMesh(SkinnedMeshRenderer proxy, ImmutableHashSet<int> toDelete)
|
||||||
SkinnedMeshRenderer proxy,
|
|
||||||
ComputeContext context,
|
|
||||||
HashSet<int> toDelete
|
|
||||||
)
|
|
||||||
{
|
{
|
||||||
_toDelete = toDelete;
|
|
||||||
var mesh = proxy.sharedMesh;
|
var mesh = proxy.sharedMesh;
|
||||||
|
|
||||||
if (toDelete.Count > 0)
|
if (toDelete.Count > 0)
|
||||||
@ -223,61 +244,33 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
|
|
||||||
mesh.SetTriangles(tris, subMesh, false, baseVertex: baseVertex);
|
mesh.SetTriangles(tris, subMesh, false, baseVertex: baseVertex);
|
||||||
}
|
}
|
||||||
|
|
||||||
_generatedMesh = mesh;
|
return mesh;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void OnFrame(Renderer original, Renderer proxy)
|
||||||
|
{
|
||||||
|
if (original == null || proxy == null) return;
|
||||||
|
if (original is not SkinnedMeshRenderer originalSmr || proxy is not SkinnedMeshRenderer proxySmr) return;
|
||||||
|
|
||||||
public RenderAspects Reads => RenderAspects.Shapes | RenderAspects.Mesh;
|
if (_generatedMesh != null)
|
||||||
public RenderAspects WhatChanged => RenderAspects.Shapes | RenderAspects.Mesh;
|
{
|
||||||
|
proxySmr.sharedMesh = _generatedMesh;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var shape in _shapes)
|
||||||
|
{
|
||||||
|
proxySmr.SetBlendShapeWeight(shape.Item1, shape.Item2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
if (_generatedMesh != null) Object.DestroyImmediate(_generatedMesh);
|
if (_generatedMesh != null) Object.DestroyImmediate(_generatedMesh);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnFrame(Renderer original, Renderer proxy)
|
|
||||||
{
|
|
||||||
if (_changers == null) return; // can happen transiently as we disable the last component
|
|
||||||
if (!(proxy is SkinnedMeshRenderer smr)) return;
|
|
||||||
|
|
||||||
Mesh mesh;
|
|
||||||
if (_generatedMesh != null)
|
|
||||||
{
|
|
||||||
smr.sharedMesh = _generatedMesh;
|
|
||||||
mesh = _generatedMesh;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
mesh = smr.sharedMesh;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mesh == null) return;
|
|
||||||
|
|
||||||
foreach (var changer in _changers)
|
|
||||||
{
|
|
||||||
foreach (var shape in changer.Shapes)
|
|
||||||
{
|
|
||||||
var index = mesh.GetBlendShapeIndex(shape.ShapeName);
|
|
||||||
if (index < 0) continue;
|
|
||||||
|
|
||||||
float setToValue = -1;
|
|
||||||
|
|
||||||
switch (shape.ChangeType)
|
|
||||||
{
|
|
||||||
case ShapeChangeType.Delete:
|
|
||||||
setToValue = 100;
|
|
||||||
break;
|
|
||||||
case ShapeChangeType.Set:
|
|
||||||
setToValue = shape.Value;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
smr.SetBlendShapeWeight(index, setToValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -5,7 +5,7 @@ using UnityEngine;
|
|||||||
namespace nadena.dev.modular_avatar.core
|
namespace nadena.dev.modular_avatar.core
|
||||||
{
|
{
|
||||||
[Serializable]
|
[Serializable]
|
||||||
public struct MaterialSwitchObject
|
public class MaterialSwitchObject
|
||||||
{
|
{
|
||||||
public AvatarObjectReference Object;
|
public AvatarObjectReference Object;
|
||||||
public Material Material;
|
public Material Material;
|
||||||
|
@ -17,15 +17,16 @@ namespace nadena.dev.modular_avatar.core
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Serializable]
|
[Serializable]
|
||||||
public struct ChangedShape
|
public class ChangedShape
|
||||||
{
|
{
|
||||||
|
public AvatarObjectReference Object;
|
||||||
public string ShapeName;
|
public string ShapeName;
|
||||||
public ShapeChangeType ChangeType;
|
public ShapeChangeType ChangeType;
|
||||||
public float Value;
|
public float Value;
|
||||||
|
|
||||||
public bool Equals(ChangedShape other)
|
public bool Equals(ChangedShape other)
|
||||||
{
|
{
|
||||||
return ShapeName == other.ShapeName && ChangeType == other.ChangeType && Value.Equals(other.Value);
|
return Equals(Object, other.Object) && ShapeName == other.ShapeName && ChangeType == other.ChangeType && Value.Equals(other.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool Equals(object obj)
|
public override bool Equals(object obj)
|
||||||
@ -35,12 +36,12 @@ namespace nadena.dev.modular_avatar.core
|
|||||||
|
|
||||||
public override int GetHashCode()
|
public override int GetHashCode()
|
||||||
{
|
{
|
||||||
return HashCode.Combine(ShapeName, (int)ChangeType, Value);
|
return HashCode.Combine(Object, ShapeName, (int)ChangeType, Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
return $"{ShapeName} {ChangeType} {Value}";
|
return $"{Object.referencePath} {ShapeName} {ChangeType} {Value}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,14 +49,10 @@ namespace nadena.dev.modular_avatar.core
|
|||||||
[HelpURL("https://modular-avatar.nadena.dev/docs/reference/shape-changer?lang=auto")]
|
[HelpURL("https://modular-avatar.nadena.dev/docs/reference/shape-changer?lang=auto")]
|
||||||
public class ModularAvatarShapeChanger : ReactiveComponent
|
public class ModularAvatarShapeChanger : ReactiveComponent
|
||||||
{
|
{
|
||||||
[SerializeField] [FormerlySerializedAs("targetRenderer")]
|
// Migration field to help with 1.10-beta series avatar data. Since this was never in a released version of MA,
|
||||||
private AvatarObjectReference m_targetRenderer;
|
// this migration support will be removed in 1.10.0.
|
||||||
|
[SerializeField] [FormerlySerializedAs("targetRenderer")] [HideInInspector]
|
||||||
public AvatarObjectReference targetRenderer
|
private AvatarObjectReference m_targetRenderer = new();
|
||||||
{
|
|
||||||
get => m_targetRenderer;
|
|
||||||
set => m_targetRenderer = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
[SerializeField] [FormerlySerializedAs("Shapes")]
|
[SerializeField] [FormerlySerializedAs("Shapes")]
|
||||||
private List<ChangedShape> m_shapes = new();
|
private List<ChangedShape> m_shapes = new();
|
||||||
@ -68,7 +65,44 @@ namespace nadena.dev.modular_avatar.core
|
|||||||
|
|
||||||
public override void ResolveReferences()
|
public override void ResolveReferences()
|
||||||
{
|
{
|
||||||
m_targetRenderer?.Get(this);
|
foreach (var shape in m_shapes)
|
||||||
|
{
|
||||||
|
shape.Object?.Get(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnEnable()
|
||||||
|
{
|
||||||
|
MigrateTargetRenderer();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnValidate()
|
||||||
|
{
|
||||||
|
base.OnValidate();
|
||||||
|
MigrateTargetRenderer();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Migrate early versions of MASC (from Modular Avatar 1.10.0-beta.4 or earlier) to the new format, where the
|
||||||
|
// target renderer is stored separately for each shape.
|
||||||
|
// This logic will be removed in 1.10.0.
|
||||||
|
private void MigrateTargetRenderer()
|
||||||
|
{
|
||||||
|
// Note: This method runs in the context of OnValidate, and therefore cannot touch any other unity objects.
|
||||||
|
if (!string.IsNullOrEmpty(m_targetRenderer.referencePath) || m_targetRenderer.targetObject != null)
|
||||||
|
{
|
||||||
|
foreach (var shape in m_shapes)
|
||||||
|
{
|
||||||
|
if (shape.Object == null) shape.Object = new AvatarObjectReference();
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(shape.Object.referencePath) && shape.Object.targetObject == null)
|
||||||
|
{
|
||||||
|
shape.Object.referencePath = m_targetRenderer.referencePath;
|
||||||
|
shape.Object.targetObject = m_targetRenderer.targetObject;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m_targetRenderer.referencePath = null;
|
||||||
|
m_targetRenderer.targetObject = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user