Merge pull request #324 from bdunderscore/enhancement/mesh-settings

Add the Mesh Settings component
This commit is contained in:
bd_ 2023-06-19 20:02:08 +09:00 committed by GitHub
commit cdb5ede7fe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 1202 additions and 230 deletions

View File

@ -0,0 +1,72 @@
using nadena.dev.modular_avatar.core.editor;
using NUnit.Framework;
using UnityEngine;
using VRC.SDK3.Avatars.Components;
namespace modular_avatar_tests
{
internal class MeshSettingsTests : TestBase
{
private static readonly string PREFAB_NAME = "MeshSettingsTests.prefab";
[Test]
public void TestProbeAnchor()
{
var prefab = CreatePrefab(PREFAB_NAME);
var context = new BuildContext(prefab.GetComponent<VRCAvatarDescriptor>());
new MeshSettingsPass(context).OnPreprocessAvatar();
var root = prefab.transform.Find("RendererRoot");
var target = prefab.transform.Find("ProbeTarget");
var obj1 = prefab.transform.Find("ProbeTargetRenderers/SkinnedMeshRenderer").GetComponent<Renderer>();
var obj2 = prefab.transform.Find("ProbeTargetRenderers/MeshRenderer").GetComponent<Renderer>();
var obj3 = prefab.transform.Find("ProbeTargetRenderers/ParticleSystemRenderer").GetComponent<Renderer>();
var obj4 = prefab.transform.Find("ProbeTargetRenderers/TrailRenderer").GetComponent<Renderer>();
Assert.AreEqual(target, obj1.probeAnchor);
Assert.AreEqual(target, obj2.probeAnchor);
Assert.AreEqual(target, obj3.probeAnchor);
Assert.AreEqual(target, obj4.probeAnchor);
}
[Test]
public void TestProbeAnchorOverrides()
{
var prefab = CreatePrefab(PREFAB_NAME);
var context = new BuildContext(prefab.GetComponent<VRCAvatarDescriptor>());
new MeshSettingsPass(context).OnPreprocessAvatar();
var noninherit = prefab.transform.Find("ProbeTargetRenderers/NonInherited").GetComponent<MeshRenderer>();
var overrideset = prefab.transform.Find("ProbeTargetRenderers/OverrideSet").GetComponent<MeshRenderer>();
Assert.AreEqual(noninherit.transform.Find("Target"), noninherit.probeAnchor);
Assert.AreEqual(overrideset.transform.Find("Target"), overrideset.probeAnchor);
}
[Test]
public void TestSetBounds()
{
var prefab = CreatePrefab(PREFAB_NAME);
var context = new BuildContext(prefab.GetComponent<VRCAvatarDescriptor>());
new MeshSettingsPass(context).OnPreprocessAvatar();
var target = prefab.transform.Find("ProbeTarget");
var inherit = prefab.transform.Find("Bounds/Inherit").GetComponent<SkinnedMeshRenderer>();
var overridden = prefab.transform.Find("Bounds/Overridden").GetComponent<SkinnedMeshRenderer>();
var notset = prefab.transform.Find("Bounds/NotSet").GetComponent<SkinnedMeshRenderer>();
Assert.AreEqual(new Vector3(1, 2, 3), inherit.bounds.center);
Assert.AreEqual(new Vector3(4, 5, 6), inherit.bounds.extents);
Assert.AreEqual(target, inherit.rootBone);
Assert.AreEqual(new Vector3(8, 8, 8), overridden.bounds.center);
Assert.AreEqual(new Vector3(9, 9, 9), overridden.bounds.extents);
Assert.AreEqual(overridden.transform.Find("Target"), overridden.rootBone);
Assert.AreEqual(new Vector3(0, 0, 0), notset.bounds.center);
Assert.AreEqual(new Vector3(2, 2, 2), notset.bounds.extents);
Assert.AreEqual(notset.transform.Find("Target"), notset.rootBone);
}
}
}

View File

@ -9,9 +9,9 @@ GameObject:
serializedVersion: 6
m_Component:
- component: {fileID: 5397716290800633055}
- component: {fileID: 2330010457497982229}
- component: {fileID: 335967967441451257}
m_Layer: 0
m_Name: RendererRoot
m_Name: ProbeTargetRenderers
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
@ -32,10 +32,12 @@ Transform:
- {fileID: 7708823067105146344}
- {fileID: 6857113755099998103}
- {fileID: 3582219504831888926}
- {fileID: 8922954295583054356}
- {fileID: 4234831726961402887}
m_Father: {fileID: 2813872215014323517}
m_RootOrder: 1
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!114 &2330010457497982229
--- !u!114 &335967967441451257
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
@ -44,11 +46,78 @@ MonoBehaviour:
m_GameObject: {fileID: 680825651427616331}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: b18784296c2581e498e5127a23e6f019, type: 3}
m_Script: {fileID: 11500000, guid: 560fdafd46c74b2db6422fdf0e7f2363, type: 3}
m_Name:
m_EditorClassIdentifier:
probeTarget:
InheritProbeAnchor: 1
ProbeAnchor:
referencePath: ProbeTarget
InheritBounds: 0
RootBone:
referencePath:
Bounds:
m_Center: {x: 0, y: 0, z: 0}
m_Extent: {x: 1, y: 1, z: 1}
--- !u!1 &935411094479274245
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 7820260671288448613}
m_Layer: 0
m_Name: Target
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &7820260671288448613
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 935411094479274245}
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_Children: []
m_Father: {fileID: 8922954295583054356}
m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1 &1110437597349189514
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 5019135646683737746}
m_Layer: 0
m_Name: Target
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &5019135646683737746
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1110437597349189514}
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_Children: []
m_Father: {fileID: 2306861396738510558}
m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1 &2395813488222310007
GameObject:
m_ObjectHideFlags: 0
@ -119,6 +188,111 @@ MeshRenderer:
m_SortingLayerID: 0
m_SortingLayer: 0
m_SortingOrder: 0
--- !u!1 &2871342873229566282
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 2671010323382710830}
- component: {fileID: 2589302178758902106}
- component: {fileID: 5369087837389602381}
m_Layer: 0
m_Name: Overridden
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &2671010323382710830
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2871342873229566282}
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_Children:
- {fileID: 8008288380682626085}
m_Father: {fileID: 9198237797126898215}
m_RootOrder: 1
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!114 &2589302178758902106
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2871342873229566282}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 560fdafd46c74b2db6422fdf0e7f2363, type: 3}
m_Name:
m_EditorClassIdentifier:
InheritProbeAnchor: 0
ProbeAnchor:
referencePath:
InheritBounds: 1
RootBone:
referencePath: ProbeTarget
Bounds:
m_Center: {x: 8, y: 8, z: 8}
m_Extent: {x: 9, y: 9, z: 9}
--- !u!137 &5369087837389602381
SkinnedMeshRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2871342873229566282}
m_Enabled: 1
m_CastShadows: 1
m_ReceiveShadows: 1
m_DynamicOccludee: 1
m_MotionVectors: 1
m_LightProbeUsage: 1
m_ReflectionProbeUsage: 1
m_RayTracingMode: 0
m_RenderingLayerMask: 1
m_RendererPriority: 0
m_Materials:
- {fileID: 0}
m_StaticBatchInfo:
firstSubMesh: 0
subMeshCount: 0
m_StaticBatchRoot: {fileID: 0}
m_ProbeAnchor: {fileID: 0}
m_LightProbeVolumeOverride: {fileID: 0}
m_ScaleInLightmap: 1
m_ReceiveGI: 1
m_PreserveUVs: 0
m_IgnoreNormalsForChartDetection: 0
m_ImportantGI: 0
m_StitchLightmapSeams: 1
m_SelectedEditorRenderState: 3
m_MinimumChartSize: 4
m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0}
m_SortingLayerID: 0
m_SortingLayer: 0
m_SortingOrder: 0
serializedVersion: 2
m_Quality: 0
m_UpdateWhenOffscreen: 0
m_SkinnedMotionVectors: 1
m_Mesh: {fileID: 0}
m_Bones: []
m_BlendShapeWeights: []
m_RootBone: {fileID: 0}
m_AABB:
m_Center: {x: 0, y: 0, z: 0}
m_Extent: {x: 0, y: 0, z: 0}
m_DirtyAABB: 1
--- !u!1 &3657214856810207961
GameObject:
m_ObjectHideFlags: 0
@ -132,7 +306,7 @@ GameObject:
- component: {fileID: 265261251370394767}
- component: {fileID: 7103399505028166247}
m_Layer: 0
m_Name: ProbeAnchorTests
m_Name: MeshSettingsTests
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
@ -151,6 +325,7 @@ Transform:
m_Children:
- {fileID: 1865266364885290703}
- {fileID: 5397716290800633055}
- {fileID: 9198237797126898215}
m_Father: {fileID: 0}
m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
@ -440,6 +615,111 @@ Animator:
m_HasTransformHierarchy: 1
m_AllowConstantClipSamplingOptimization: 1
m_KeepAnimatorControllerStateOnDisable: 0
--- !u!1 &3968597137123995335
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 2306861396738510558}
- component: {fileID: 9171463375283835425}
- component: {fileID: 6233715779515687049}
m_Layer: 0
m_Name: NotSet
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &2306861396738510558
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3968597137123995335}
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_Children:
- {fileID: 5019135646683737746}
m_Father: {fileID: 9198237797126898215}
m_RootOrder: 2
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!114 &9171463375283835425
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3968597137123995335}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 560fdafd46c74b2db6422fdf0e7f2363, type: 3}
m_Name:
m_EditorClassIdentifier:
InheritProbeAnchor: 0
ProbeAnchor:
referencePath:
InheritBounds: 2
RootBone:
referencePath: ProbeTarget
Bounds:
m_Center: {x: 0, y: 0, z: 0}
m_Extent: {x: 1, y: 1, z: 1}
--- !u!137 &6233715779515687049
SkinnedMeshRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3968597137123995335}
m_Enabled: 1
m_CastShadows: 1
m_ReceiveShadows: 1
m_DynamicOccludee: 1
m_MotionVectors: 1
m_LightProbeUsage: 1
m_ReflectionProbeUsage: 1
m_RayTracingMode: 0
m_RenderingLayerMask: 1
m_RendererPriority: 0
m_Materials:
- {fileID: 0}
m_StaticBatchInfo:
firstSubMesh: 0
subMeshCount: 0
m_StaticBatchRoot: {fileID: 0}
m_ProbeAnchor: {fileID: 0}
m_LightProbeVolumeOverride: {fileID: 0}
m_ScaleInLightmap: 1
m_ReceiveGI: 1
m_PreserveUVs: 0
m_IgnoreNormalsForChartDetection: 0
m_ImportantGI: 0
m_StitchLightmapSeams: 1
m_SelectedEditorRenderState: 3
m_MinimumChartSize: 4
m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0}
m_SortingLayerID: 0
m_SortingLayer: 0
m_SortingOrder: 0
serializedVersion: 2
m_Quality: 0
m_UpdateWhenOffscreen: 0
m_SkinnedMotionVectors: 1
m_Mesh: {fileID: 0}
m_Bones: []
m_BlendShapeWeights: []
m_RootBone: {fileID: 5019135646683737746}
m_AABB:
m_Center: {x: 0, y: 0, z: 0}
m_Extent: {x: 2, y: 2, z: 2}
m_DirtyAABB: 0
--- !u!1 &6236283415756824863
GameObject:
m_ObjectHideFlags: 0
@ -470,6 +750,91 @@ Transform:
m_Father: {fileID: 2813872215014323517}
m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1 &6389017230970607628
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 8008288380682626085}
m_Layer: 0
m_Name: Target
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &8008288380682626085
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6389017230970607628}
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_Children: []
m_Father: {fileID: 2671010323382710830}
m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1 &7015511926769845160
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 9198237797126898215}
- component: {fileID: 5213165685920700274}
m_Layer: 0
m_Name: Bounds
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &9198237797126898215
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7015511926769845160}
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_Children:
- {fileID: 4332104211298917797}
- {fileID: 2671010323382710830}
- {fileID: 2306861396738510558}
m_Father: {fileID: 2813872215014323517}
m_RootOrder: 2
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!114 &5213165685920700274
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7015511926769845160}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 560fdafd46c74b2db6422fdf0e7f2363, type: 3}
m_Name:
m_EditorClassIdentifier:
InheritProbeAnchor: 0
ProbeAnchor:
referencePath:
InheritBounds: 1
RootBone:
referencePath: ProbeTarget
Bounds:
m_Center: {x: 1, y: 2, z: 3}
m_Extent: {x: 4, y: 5, z: 6}
--- !u!1 &7136371616812247070
GameObject:
m_ObjectHideFlags: 0
@ -598,6 +963,181 @@ TrailRenderer:
m_MinVertexDistance: 0.1
m_Autodestruct: 0
m_Emitting: 1
--- !u!1 &7694387925439316296
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 4332104211298917797}
- component: {fileID: 3551356292515820315}
m_Layer: 0
m_Name: Inherit
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &4332104211298917797
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7694387925439316296}
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_Children: []
m_Father: {fileID: 9198237797126898215}
m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!137 &3551356292515820315
SkinnedMeshRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7694387925439316296}
m_Enabled: 1
m_CastShadows: 1
m_ReceiveShadows: 1
m_DynamicOccludee: 1
m_MotionVectors: 1
m_LightProbeUsage: 1
m_ReflectionProbeUsage: 1
m_RayTracingMode: 0
m_RenderingLayerMask: 1
m_RendererPriority: 0
m_Materials:
- {fileID: 0}
m_StaticBatchInfo:
firstSubMesh: 0
subMeshCount: 0
m_StaticBatchRoot: {fileID: 0}
m_ProbeAnchor: {fileID: 0}
m_LightProbeVolumeOverride: {fileID: 0}
m_ScaleInLightmap: 1
m_ReceiveGI: 1
m_PreserveUVs: 0
m_IgnoreNormalsForChartDetection: 0
m_ImportantGI: 0
m_StitchLightmapSeams: 1
m_SelectedEditorRenderState: 3
m_MinimumChartSize: 4
m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0}
m_SortingLayerID: 0
m_SortingLayer: 0
m_SortingOrder: 0
serializedVersion: 2
m_Quality: 0
m_UpdateWhenOffscreen: 0
m_SkinnedMotionVectors: 1
m_Mesh: {fileID: 0}
m_Bones: []
m_BlendShapeWeights: []
m_RootBone: {fileID: 0}
m_AABB:
m_Center: {x: 0, y: 0, z: 0}
m_Extent: {x: 0, y: 0, z: 0}
m_DirtyAABB: 1
--- !u!1 &8118624344453541351
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 8922954295583054356}
- component: {fileID: 5109966626089099389}
- component: {fileID: 4914466322604787005}
m_Layer: 0
m_Name: NonInherited
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &8922954295583054356
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 8118624344453541351}
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_Children:
- {fileID: 7820260671288448613}
m_Father: {fileID: 5397716290800633055}
m_RootOrder: 4
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!23 &5109966626089099389
MeshRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 8118624344453541351}
m_Enabled: 1
m_CastShadows: 1
m_ReceiveShadows: 1
m_DynamicOccludee: 1
m_MotionVectors: 1
m_LightProbeUsage: 1
m_ReflectionProbeUsage: 1
m_RayTracingMode: 2
m_RenderingLayerMask: 1
m_RendererPriority: 0
m_Materials:
- {fileID: 0}
m_StaticBatchInfo:
firstSubMesh: 0
subMeshCount: 0
m_StaticBatchRoot: {fileID: 0}
m_ProbeAnchor: {fileID: 7820260671288448613}
m_LightProbeVolumeOverride: {fileID: 0}
m_ScaleInLightmap: 1
m_ReceiveGI: 1
m_PreserveUVs: 0
m_IgnoreNormalsForChartDetection: 0
m_ImportantGI: 0
m_StitchLightmapSeams: 1
m_SelectedEditorRenderState: 3
m_MinimumChartSize: 4
m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0}
m_SortingLayerID: 0
m_SortingLayer: 0
m_SortingOrder: 0
--- !u!114 &4914466322604787005
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 8118624344453541351}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 560fdafd46c74b2db6422fdf0e7f2363, type: 3}
m_Name:
m_EditorClassIdentifier:
InheritProbeAnchor: 2
ProbeAnchor:
referencePath:
InheritBounds: 0
RootBone:
referencePath:
Bounds:
m_Center: {x: 0, y: 0, z: 0}
m_Extent: {x: 1, y: 1, z: 1}
--- !u!1 &8298941597190323301
GameObject:
m_ObjectHideFlags: 0
@ -680,6 +1220,129 @@ SkinnedMeshRenderer:
m_Center: {x: 0, y: 0, z: 0}
m_Extent: {x: 0, y: 0, z: 0}
m_DirtyAABB: 1
--- !u!1 &8312581682139214105
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1691276452827846806}
m_Layer: 0
m_Name: Target
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &1691276452827846806
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 8312581682139214105}
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_Children: []
m_Father: {fileID: 4234831726961402887}
m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1 &8524827212543336267
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 4234831726961402887}
- component: {fileID: 5041197310081425676}
- component: {fileID: 7885810423339069945}
m_Layer: 0
m_Name: OverrideSet
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &4234831726961402887
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 8524827212543336267}
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_Children:
- {fileID: 1691276452827846806}
m_Father: {fileID: 5397716290800633055}
m_RootOrder: 5
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!23 &5041197310081425676
MeshRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 8524827212543336267}
m_Enabled: 1
m_CastShadows: 1
m_ReceiveShadows: 1
m_DynamicOccludee: 1
m_MotionVectors: 1
m_LightProbeUsage: 1
m_ReflectionProbeUsage: 1
m_RayTracingMode: 2
m_RenderingLayerMask: 1
m_RendererPriority: 0
m_Materials:
- {fileID: 0}
m_StaticBatchInfo:
firstSubMesh: 0
subMeshCount: 0
m_StaticBatchRoot: {fileID: 0}
m_ProbeAnchor: {fileID: 0}
m_LightProbeVolumeOverride: {fileID: 0}
m_ScaleInLightmap: 1
m_ReceiveGI: 1
m_PreserveUVs: 0
m_IgnoreNormalsForChartDetection: 0
m_ImportantGI: 0
m_StitchLightmapSeams: 1
m_SelectedEditorRenderState: 3
m_MinimumChartSize: 4
m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0}
m_SortingLayerID: 0
m_SortingLayer: 0
m_SortingOrder: 0
--- !u!114 &7885810423339069945
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 8524827212543336267}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 560fdafd46c74b2db6422fdf0e7f2363, type: 3}
m_Name:
m_EditorClassIdentifier:
InheritProbeAnchor: 1
ProbeAnchor:
referencePath: ProbeTargetRenderers/OverrideSet/Target
InheritBounds: 0
RootBone:
referencePath:
Bounds:
m_Center: {x: 0, y: 0, z: 0}
m_Extent: {x: 1, y: 1, z: 1}
--- !u!1 &9152779281672452689
GameObject:
m_ObjectHideFlags: 0

View File

@ -1,29 +0,0 @@
using nadena.dev.modular_avatar.core.editor;
using NUnit.Framework;
using UnityEditor;
using UnityEngine;
namespace modular_avatar_tests
{
internal class ProbeAnchorTests : TestBase
{
[Test]
public void TestProbeAnchor()
{
var prefab = CreatePrefab("ProbeAnchorTests.prefab");
AvatarProcessor.ProcessAvatar(prefab);
var root = prefab.transform.Find("RendererRoot");
var target = prefab.transform.Find("ProbeTarget");
var obj1 = prefab.transform.Find("RendererRoot/SkinnedMeshRenderer").GetComponent<Renderer>();
var obj2 = prefab.transform.Find("RendererRoot/MeshRenderer").GetComponent<Renderer>();
var obj3 = prefab.transform.Find("RendererRoot/ParticleSystemRenderer").GetComponent<Renderer>();
var obj4 = prefab.transform.Find("RendererRoot/TrailRenderer").GetComponent<Renderer>();
Assert.AreEqual(target, obj1.probeAnchor);
Assert.AreEqual(target, obj2.probeAnchor);
Assert.AreEqual(target, obj3.probeAnchor);
Assert.AreEqual(target, obj4.probeAnchor);
}
}
}

View File

@ -198,8 +198,8 @@ namespace nadena.dev.modular_avatar.core.editor
new MenuInstallHook().OnPreprocessAvatar(avatarGameObject, context);
new MergeArmatureHook().OnPreprocessAvatar(context, avatarGameObject);
new BoneProxyProcessor().OnPreprocessAvatar(avatarGameObject);
new ProbeAnchorProcessor().OnPreprocessAvatar(avatarGameObject);
new VisibleHeadAccessoryProcessor(vrcAvatarDescriptor).Process(context);
new MeshSettingsPass(context).OnPreprocessAvatar();
new RemapAnimationPass(vrcAvatarDescriptor).Process(context.AnimationDatabase);
new BlendshapeSyncAnimationProcessor().OnPreprocessAvatar(avatarGameObject, context);
PhysboneBlockerPass.Process(avatarGameObject);

View File

@ -17,6 +17,7 @@ namespace nadena.dev.modular_avatar.core.editor
out var avatarRoot, out var avatarHips, out var outfitHips)
) return;
var outfitRoot = cmd.context as GameObject;
var avatarArmature = avatarHips.transform.parent;
var outfitArmature = outfitHips.transform.parent;
@ -28,6 +29,95 @@ namespace nadena.dev.modular_avatar.core.editor
merge.InferPrefixSuffix();
HeuristicBoneMapper.RenameBonesByHeuristic(merge);
}
if (outfitRoot != null
&& outfitRoot.GetComponent<ModularAvatarMeshSettings>() == null
&& outfitRoot.GetComponentInParent<ModularAvatarMeshSettings>() == null)
{
var meshSettings = Undo.AddComponent<ModularAvatarMeshSettings>(outfitRoot.gameObject);
Transform rootBone = null, probeAnchor = null;
Bounds bounds = ModularAvatarMeshSettings.DEFAULT_BOUNDS;
FindConsistentSettings(avatarRoot, ref probeAnchor, ref rootBone, ref bounds);
if (probeAnchor == null)
{
probeAnchor = avatarHips.transform;
}
if (rootBone == null)
{
rootBone = avatarRoot.transform;
}
meshSettings.InheritProbeAnchor = ModularAvatarMeshSettings.InheritMode.Set;
meshSettings.InheritBounds = ModularAvatarMeshSettings.InheritMode.Set;
meshSettings.ProbeAnchor = new AvatarObjectReference();
meshSettings.ProbeAnchor.referencePath = RuntimeUtil.RelativePath(avatarRoot, probeAnchor.gameObject);
meshSettings.RootBone = new AvatarObjectReference();
meshSettings.RootBone.referencePath = RuntimeUtil.RelativePath(avatarRoot, rootBone.gameObject);
meshSettings.Bounds = bounds;
}
}
private static void FindConsistentSettings(
GameObject avatarRoot,
ref Transform probeAnchor,
ref Transform rootBone,
ref Bounds bounds
)
{
// We assume the renderers directly under the avatar root came from the original avatar and are _probably_
// set consistently. If so, we use this as a basis for the new outfit's settings.
bool firstRenderer = true;
bool firstSkinnedMeshRenderer = true;
foreach (Transform directChild in avatarRoot.transform)
{
var renderer = directChild.GetComponent<Renderer>();
if (renderer == null) continue;
if (firstRenderer)
{
probeAnchor = renderer.probeAnchor;
}
else
{
if (renderer.probeAnchor != probeAnchor)
{
probeAnchor = null; // inconsistent configuration
}
}
firstRenderer = false;
var skinnedMeshRenderer = renderer as SkinnedMeshRenderer;
if (skinnedMeshRenderer == null) continue;
if (firstSkinnedMeshRenderer)
{
rootBone = skinnedMeshRenderer.rootBone;
bounds = skinnedMeshRenderer.localBounds;
}
else
{
if (rootBone != skinnedMeshRenderer.rootBone)
{
rootBone = null; // inconsistent configuration
bounds = ModularAvatarMeshSettings.DEFAULT_BOUNDS;
}
else if (Vector3.Distance(bounds.center, skinnedMeshRenderer.bounds.center) > 0.01f
|| Vector3.Distance(bounds.extents, skinnedMeshRenderer.bounds.extents) > 0.01f)
{
bounds = ModularAvatarMeshSettings.DEFAULT_BOUNDS;
}
}
firstSkinnedMeshRenderer = false;
}
}
[MenuItem("GameObject/ModularAvatar/Setup Outfit", true, PRIORITY)]

View File

@ -1,6 +1,7 @@
using System.Collections.Generic;
using nadena.dev.modular_avatar.core;
using nadena.dev.modular_avatar.core.menu;
using NUnit.Framework;
using UnityEngine;
using VRC.SDK3.Avatars.Components;
@ -21,8 +22,6 @@ namespace nadena.dev.modular_avatar.editor.ErrorReporting
return CheckInternal(bs);
case ModularAvatarBoneProxy bp:
return CheckInternal(bp);
case ModularAvatarProbeAnchor pa:
return CheckInternal(pa);
case ModularAvatarMenuInstaller mi:
return CheckInternal(mi);
case ModularAvatarMergeAnimator obj:
@ -137,19 +136,6 @@ namespace nadena.dev.modular_avatar.editor.ErrorReporting
return null;
}
private static List<ErrorLog> CheckInternal(ModularAvatarProbeAnchor pa)
{
if (pa.probeTarget == null)
{
return new List<ErrorLog>()
{
new ErrorLog(ReportLevel.Validation, "validation.probe_anchor.no_target", pa)
};
}
return null;
}
private static List<ErrorLog> CheckInternal(ModularAvatarMenuInstaller mi)
{
// TODO - check that target menu is in the avatar

View File

@ -0,0 +1,122 @@
using System;
using UnityEditor;
using UnityEngine;
using static nadena.dev.modular_avatar.core.editor.Localization;
namespace nadena.dev.modular_avatar.core.editor
{
[CustomPropertyDrawer(typeof(ModularAvatarMeshSettings.InheritMode))]
class MeshSettingsInheritMode : EnumDrawer<ModularAvatarMeshSettings.InheritMode>
{
protected override string localizationPrefix => "mesh_settings.inherit_mode";
}
[CustomEditor(typeof(ModularAvatarMeshSettings))]
[CanEditMultipleObjects]
internal class MeshSettingsEditor : MAEditorBase
{
private SerializedProperty _prop_inherit_probe_anchor;
private SerializedProperty _prop_probe_anchor;
private SerializedProperty _prop_inherit_bounds;
private SerializedProperty _prop_root_bone;
private SerializedProperty _prop_bounds;
private void OnEnable()
{
_prop_inherit_probe_anchor =
serializedObject.FindProperty(nameof(ModularAvatarMeshSettings.InheritProbeAnchor));
_prop_probe_anchor = serializedObject.FindProperty(nameof(ModularAvatarMeshSettings.ProbeAnchor));
_prop_inherit_bounds = serializedObject.FindProperty(nameof(ModularAvatarMeshSettings.InheritBounds));
_prop_root_bone = serializedObject.FindProperty(nameof(ModularAvatarMeshSettings.RootBone));
_prop_bounds = serializedObject.FindProperty(nameof(ModularAvatarMeshSettings.Bounds));
}
protected override void OnInnerInspectorGUI()
{
MeshSettingsPass.MergedSettings merged = new MeshSettingsPass.MergedSettings();
bool haveMerged = false;
ModularAvatarMeshSettings settings = null;
if (targets.Length == 1)
{
settings = (ModularAvatarMeshSettings) target;
var avatar = RuntimeUtil.FindAvatarInParents(settings.transform);
if (avatar != null)
{
Component mesh = (Component) target;
merged = MeshSettingsPass.MergeSettings(avatar.transform, mesh.transform);
haveMerged = true;
}
}
serializedObject.Update();
EditorGUILayout.LabelField(G("mesh_settings.header_probe_anchor"), EditorStyles.boldLabel);
EditorGUILayout.PropertyField(_prop_inherit_probe_anchor, G("mesh_settings.inherit_probe_anchor"));
if (_prop_inherit_probe_anchor.enumValueIndex == (int) ModularAvatarMeshSettings.InheritMode.Set)
{
EditorGUILayout.PropertyField(_prop_probe_anchor, G("mesh_settings.probe_anchor"));
}
else if (_prop_inherit_probe_anchor.enumValueIndex == (int) ModularAvatarMeshSettings.InheritMode.Inherit &&
haveMerged)
{
using (new EditorGUI.DisabledScope(true))
{
EditorGUILayout.ObjectField(G("mesh_settings.probe_anchor"), merged.ProbeAnchor, typeof(Transform),
true);
}
}
EditorGUILayout.Separator();
EditorGUILayout.LabelField(G("mesh_settings.header_bounds"), EditorStyles.boldLabel);
EditorGUILayout.PropertyField(_prop_inherit_bounds, G("mesh_settings.inherit_bounds"));
if (_prop_inherit_bounds.enumValueIndex == (int) ModularAvatarMeshSettings.InheritMode.Set)
{
EditorGUILayout.PropertyField(_prop_root_bone, G("mesh_settings.root_bone"));
EditorGUILayout.PropertyField(_prop_bounds, G("mesh_settings.bounds"));
}
else if (_prop_inherit_bounds.enumValueIndex == (int) ModularAvatarMeshSettings.InheritMode.Inherit &&
haveMerged)
{
using (new EditorGUI.DisabledScope(true))
{
EditorGUILayout.ObjectField(G("mesh_settings.root_bone"), merged.RootBone, typeof(Transform), true);
EditorGUILayout.PropertyField(_prop_bounds, G("mesh_settings.bounds"));
}
}
serializedObject.ApplyModifiedProperties();
ShowLanguageUI();
}
[DrawGizmo(GizmoType.Selected)]
private static void DrawGizmo(ModularAvatarMeshSettings component, GizmoType gizmoType)
{
if (component.InheritBounds != ModularAvatarMeshSettings.InheritMode.Set) return;
Matrix4x4 oldMatrix = Gizmos.matrix;
Vector3 center = component.Bounds.center;
Vector3 size = component.Bounds.size;
Transform rootBone = component.RootBone.Get(component)?.transform;
try
{
if (rootBone != null)
{
Gizmos.matrix *= rootBone.localToWorldMatrix;
}
Gizmos.DrawWireCube(center, size);
}
finally
{
Gizmos.matrix = oldMatrix;
}
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 441e32d18e0f4971be8f1853a7ceacae
timeCreated: 1685960604

View File

@ -1,39 +0,0 @@
using UnityEditor;
using static nadena.dev.modular_avatar.core.editor.Localization;
namespace nadena.dev.modular_avatar.core.editor
{
[CustomEditor(typeof(ModularAvatarProbeAnchor))]
[CanEditMultipleObjects]
internal class ProbeAnchorEditor : MAEditorBase
{
private SerializedProperty prop_probeTarget;
private void OnEnable()
{
prop_probeTarget = serializedObject.FindProperty(nameof(ModularAvatarProbeAnchor.probeTarget));
}
private void ShowParametersUI()
{
serializedObject.Update();
EditorGUILayout.PropertyField(prop_probeTarget, G("probeanchor.target"));
serializedObject.ApplyModifiedProperties();
}
protected override void OnInnerInspectorGUI()
{
EditorGUILayout.HelpBox(S("probe_anchor.help"), MessageType.Info);
EditorGUI.BeginChangeCheck();
ShowParametersUI();
if (EditorGUI.EndChangeCheck())
{
serializedObject.ApplyModifiedProperties();
}
Localization.ShowLanguageUI();
}
}
}

View File

@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 1ac49767157d7e142b4b1f277baf00ed
timeCreated: 1664757842

View File

@ -3,7 +3,6 @@
"test0.test_b": "test_b",
"boneproxy.foldout.advanced": "Advanced",
"boneproxy.target": "Target",
"probeanchor.target": "Target",
"menuinstall.help.hint_set_menu": "This prefab will be installed to the root menu of your avatar by default. Select a different menu or uncheck the component's enabled checkbox to prevent this.",
"menuinstall.help.hint_bad_menu": "Selected menu asset is not part of your avatar.",
"menuinstall.installto": "Install To",
@ -60,14 +59,25 @@
"hint.not_in_avatar": "This component needs to be placed inside your avatar to work properly.",
"boneproxy.err.MovingTarget": "You cannot specify a target object that will be moved by other Modular Avatar components",
"boneproxy.err.NotInAvatar": "You must specify an object that is in the avatar",
"probeanchor.err.NotInAvatar": "You must specify an object that is in the avatar",
"boneproxy.attachment": "Attachment mode",
"boneproxy.attachment.AsChildAtRoot": "As child; at root",
"boneproxy.attachment.AsChildKeepWorldPose": "As child; keep position and rotation",
"boneproxy.attachment.AsChildKeepPosition": "As child; keep position",
"boneproxy.attachment.AsChildKeepRotation": "As child; keep rotation",
"mesh_settings.header_probe_anchor": "Anchor Override Configuration",
"mesh_settings.inherit_probe_anchor": "Anchor Override Mode",
"mesh_settings.probe_anchor": "Anchor Override",
"mesh_settings.probe_anchor.tooltip": "Sets the anchor override used for lighting calculations for renderers inside this object",
"mesh_settings.header_bounds": "Bounds Override Configuration",
"mesh_settings.inherit_bounds": "Bounds Override Mode",
"mesh_settings.root_bone": "Root Bone",
"mesh_settings.root_bone.tooltip": "The root bone of the mesh. This is used as a reference point from which to calculate the bounds of the mesh.",
"mesh_settings.bounds": "Bounds",
"mesh_settings.bounds.tooltip": "The bounds of the mesh. This is used to determine when rendering can be skipped for an offscreen mesh.",
"mesh_settings.inherit_mode.Inherit": "Inherit",
"mesh_settings.inherit_mode.Set": "Set",
"mesh_settings.inherit_mode.DontSet": "Don't Set (use mesh as-is)",
"pb_blocker.help": "This object will not be affected by PhysBones attached to parents.",
"probe_anchor.help": "Sets the Anchor Override for the inner renderer object.",
"hint.bad_vrcsdk": "Incompatible version of VRCSDK detected.\n\nPlease try upgrading your VRCSDK; if this does not work, check for a newer version of Modular Avatar as well.",
"error.stack_trace": "Stack trace (provide this when reporting bugs!)",
"error.merge_armature.merge_into_self": "Your Merge Armature component is referencing itself, or a child of itself, as the merge target. You should reference the avatar's armature instead. Do not put Merge Armature on the avatar's main armature.",
@ -83,7 +93,6 @@
"validation.blendshape_sync.missing_target_renderer": "No renderer found on the target object",
"validation.blendshape_sync.missing_target_mesh": "No mesh found on the renderer on the target object",
"validation.bone_proxy.no_target": "No target object specified (or target object not found)",
"validation.probe_anchor.no_target": "No target object specified (or target object not found)",
"validation.menu_installer.no_menu": "No menu to install specified",
"validation.merge_animator.no_animator": "No animator to merge specified",
"validation.merge_armature.no_target": "No merge target specified",

View File

@ -58,14 +58,25 @@
"hint.not_in_avatar": "このコンポーネントが正しく動作するには、アバター内に配置する必要があります。",
"boneproxy.err.MovingTarget": "他のモジュラーアバターコンポーネントで移動されるオブジェクトを指定できません。",
"boneproxy.err.NotInAvatar": "アバター内のオブジェクトを指定してください。",
"probeanchor.err.NotInAvatar": "アバター内のオブジェクトを指定してください。",
"boneproxy.attachment": "配置モード",
"boneproxy.attachment.AsChildAtRoot": "子として・ルートに配置",
"boneproxy.attachment.AsChildKeepWorldPose": "子として・ワールド位置と向きを維持",
"boneproxy.attachment.AsChildKeepPosition": "子として・ワールド位置を維持",
"boneproxy.attachment.AsChildKeepRotation": "子として・ワールド向きを維持",
"mesh_settings.header_probe_anchor": "Anchor Override 設定",
"mesh_settings.inherit_probe_anchor": "設定モード",
"mesh_settings.probe_anchor": "Anchor Override",
"mesh_settings.probe_anchor.tooltip": "このオブジェクトとその子のレンダラーのAnchorOverrideを設定します。",
"mesh_settings.header_bounds": "Bounds 設定",
"mesh_settings.inherit_bounds": "設定モード",
"mesh_settings.root_bone": "Root Bone",
"mesh_settings.root_bone.tooltip": "このオブジェクトとその子のメッシュで設定されるルートボーン。メッシュのバウンズを計算するための参照点として使用されます。",
"mesh_settings.bounds": "Bounds",
"mesh_settings.bounds.tooltip": "このオブジェクトとその子のメッシュで設定されるバウンズ。画面外のメッシュのレンダリングを省略するかどうかを決定するために使用されます。",
"mesh_settings.inherit_mode.Inherit": "継承",
"mesh_settings.inherit_mode.Set": "設定",
"mesh_settings.inherit_mode.DontSet": "設定しない(メッシュ本体の設定のまま)",
"pb_blocker.help": "このオブジェクトは親のPhysBoneから影響を受けなくなります。",
"probe_anchor.help": "このオブジェクトに含まれるレンダラーのAnchorOverrideを設定します。",
"hint.bad_vrcsdk": "使用中のVRCSDKのバージョンとは互換性がありません。\n\nVRCSDKを更新してみてください。それでもだめでしたら、Modular Avatarにも最新版が出てないかチェックしてください。",
"error.stack_trace": "スタックトレース(バグを報告する時は必ず添付してください!)",
"error.merge_armature.merge_into_self": "Merge Armatureに自分自身のオブジェクト、もしくは自分の子をターゲットにしてしています。かわりにアバターのメインArmatureを指定してください。アバター自体のArmatureに追加しないでください。",
@ -81,7 +92,6 @@
"validation.blendshape_sync.missing_target_renderer": "同期元のオブジェクトにはSkinnedMeshRendererがありません。",
"validation.blendshape_sync.missing_target_mesh": "同期元のオブジェクトにはSkinnedMeshRendererがありますが、メッシュがありません。",
"validation.bone_proxy.no_target": "ターゲットオブジェクトが未設定、もしくは存在しません。",
"validation.probe_anchor.no_target": "ターゲットオブジェクトが未設定、もしくは存在しません。",
"validation.menu_installer.no_menu": "インストールするメニューがありません。",
"validation.merge_animator.no_animator": "Animator Controllerがありません。",
"validation.merge_armature.no_target": "ターゲットオブジェクトが未設定、もしくは存在しません。",

View File

@ -1,7 +1,6 @@
{
"boneproxy.foldout.advanced": "高级设置",
"boneproxy.target": "目标",
"probeanchor.target": "目标",
"menuinstall.help.hint_set_menu": "此预制体的菜单默认会安装Avatar的顶部菜单中. 如果不需要, 可以选择其他菜单或取消勾选启用复选框.",
"menuinstall.help.hint_bad_menu": "选择的菜单不属于当前Avatar.",
"menuinstall.installto": "安装到",
@ -53,11 +52,9 @@
"hint.not_in_avatar": "此组件需要放置于你的Avatar中才能工作",
"boneproxy.err.MovingTarget": "您不能指定将由其他 Modular Avatar 组件移动的目标对象",
"boneproxy.err.NotInAvatar": "你必须指定一个在Avatar中的对象",
"probeanchor.err.NotInAvatar": "你必须指定一个在Avatar中的对象",
"boneproxy.attachment": "附加模式",
"boneproxy.attachment.AsChildAtRoot": "作为子对象, 放置于Root",
"boneproxy.attachment.AsChildKeepWorldPosition": "作为子对象, 保持原有位置",
"pb_blocker.help": "当前对象不会受到附加的父对象的PhysBones的影响.",
"probe_anchor.help": "设置此对象所包含的渲染器的AnchorOverride",
"hint.bad_vrcsdk": "检测到不兼容的VRCSDK版本.\n\n请尝试升级VRCSDK; 如果这不起作用, 请尝试新版本的Modular Avatar."
}

View File

@ -0,0 +1,110 @@
using UnityEngine;
namespace nadena.dev.modular_avatar.core.editor
{
internal static class InheritModeExtension
{
internal static bool NotFinal(this ModularAvatarMeshSettings.InheritMode mode)
{
return mode == ModularAvatarMeshSettings.InheritMode.Inherit;
}
}
internal class MeshSettingsPass
{
private readonly BuildContext context;
public MeshSettingsPass(BuildContext context)
{
this.context = context;
}
public void OnPreprocessAvatar()
{
foreach (var mesh in context.AvatarDescriptor.GetComponentsInChildren<Renderer>(true))
{
ProcessMesh(mesh);
}
}
internal struct MergedSettings
{
public bool SetAnchor, SetBounds;
public Transform ProbeAnchor;
public Transform RootBone;
public Bounds Bounds;
}
private static bool Inherit(ref ModularAvatarMeshSettings.InheritMode mode,
ModularAvatarMeshSettings.InheritMode srcmode)
{
if (mode != ModularAvatarMeshSettings.InheritMode.Inherit ||
srcmode == ModularAvatarMeshSettings.InheritMode.Inherit)
return false;
mode = srcmode;
return true;
}
internal static MergedSettings MergeSettings(Transform avatarRoot, Transform referenceObject)
{
MergedSettings merged = new MergedSettings();
Transform current = referenceObject;
ModularAvatarMeshSettings.InheritMode inheritProbeAnchor = ModularAvatarMeshSettings.InheritMode.Inherit;
ModularAvatarMeshSettings.InheritMode inheritBounds = ModularAvatarMeshSettings.InheritMode.Inherit;
do
{
var settings = current.GetComponent<ModularAvatarMeshSettings>();
if (current == avatarRoot)
{
current = null;
}
else
{
current = current.transform.parent;
}
if (settings == null)
{
continue;
}
if (Inherit(ref inheritProbeAnchor, settings.InheritProbeAnchor))
{
merged.ProbeAnchor = settings.ProbeAnchor.Get(settings)?.transform;
}
if (Inherit(ref inheritBounds, settings.InheritBounds))
{
merged.RootBone = settings.RootBone.Get(settings)?.transform;
merged.Bounds = settings.Bounds;
}
} while (current != null && (inheritProbeAnchor.NotFinal() || inheritBounds.NotFinal()));
merged.SetAnchor = inheritProbeAnchor == ModularAvatarMeshSettings.InheritMode.Set;
merged.SetBounds = inheritBounds == ModularAvatarMeshSettings.InheritMode.Set;
return merged;
}
private void ProcessMesh(Renderer mesh)
{
MergedSettings settings = MergeSettings(context.AvatarDescriptor.transform, mesh.transform);
if (settings.SetAnchor)
{
mesh.probeAnchor = settings.ProbeAnchor;
}
if (settings.SetBounds && mesh is SkinnedMeshRenderer smr)
{
smr.rootBone = settings.RootBone;
smr.localBounds = settings.Bounds;
}
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 06b7e7286f434327b1d0ef4b60496055
timeCreated: 1685960001

View File

@ -1,75 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2022 bd_
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
using nadena.dev.modular_avatar.editor.ErrorReporting;
using UnityEngine;
namespace nadena.dev.modular_avatar.core.editor
{
internal class ProbeAnchorProcessor
{
internal enum ValidationResult
{
OK,
NotInAvatar
}
internal void OnPreprocessAvatar(GameObject avatarGameObject)
{
var boneProxies = avatarGameObject.GetComponentsInChildren<ModularAvatarProbeAnchor>(true);
foreach (var proxy in boneProxies)
{
BuildReport.ReportingObject(proxy, () => ProcessAnchor(avatarGameObject, proxy));
}
}
private void ProcessAnchor(GameObject avatarGameObject, ModularAvatarProbeAnchor proxy)
{
if (proxy.probeTarget.Get(proxy) != null && ValidateTarget(avatarGameObject, proxy.probeTarget.Get(proxy).transform) == ValidationResult.OK)
{
foreach (Renderer r in proxy.GetComponentsInChildren<Renderer>(true))
{
r.probeAnchor = proxy.probeTarget.Get(proxy).transform;
}
}
Object.DestroyImmediate(proxy);
}
internal static ValidationResult ValidateTarget(GameObject avatarGameObject, Transform proxyTarget)
{
var avatar = avatarGameObject.transform;
var node = proxyTarget;
while (node != null && node != avatar)
{
node = node.parent;
}
if (node == null) return ValidationResult.NotInAvatar;
else return ValidationResult.OK;
}
}
}

View File

@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: cdb66cfc3a147a849810baa9315df90f
timeCreated: 1661649405

View File

@ -125,15 +125,16 @@ namespace nadena.dev.modular_avatar.core.editor
return;
}
SetGizmoIconEnabled(typeof(ModularAvatarBoneProxy), false);
SetGizmoIconEnabled(typeof(ModularAvatarProbeAnchor), false);
SetGizmoIconEnabled(typeof(ModularAvatarBlendshapeSync), false);
SetGizmoIconEnabled(typeof(ModularAvatarMenuInstaller), false);
SetGizmoIconEnabled(typeof(ModularAvatarMergeAnimator), false);
SetGizmoIconEnabled(typeof(ModularAvatarMergeArmature), false);
SetGizmoIconEnabled(typeof(ModularAvatarParameters), false);
SetGizmoIconEnabled(typeof(ModularAvatarPBBlocker), false);
SetGizmoIconEnabled(typeof(ModularAvatarVisibleHeadAccessory), false);
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
{
foreach (var ty in assembly.GetTypes())
{
if (typeof(AvatarTagComponent).IsAssignableFrom(ty) && !ty.IsAbstract)
{
SetGizmoIconEnabled(ty, false);
}
}
}
EditorApplication.update -= DisableMAGizmoIcons;
SessionState.GetBool("MAIconsDisabled", true);

View File

@ -15,7 +15,7 @@ namespace nadena.dev.modular_avatar.core
public GameObject Get(Component container)
{
if (_cacheValid && _cachedPath == referencePath) return _cachedReference;
if (_cacheValid && _cachedPath == referencePath && _cachedReference != null) return _cachedReference;
_cacheValid = true;
_cachedPath = referencePath;

View File

@ -0,0 +1,30 @@
using System;
using UnityEngine;
using UnityEngine.Serialization;
namespace nadena.dev.modular_avatar.core
{
[AddComponentMenu("Modular Avatar/MA Mesh Settings")]
[DisallowMultipleComponent]
public class ModularAvatarMeshSettings : AvatarTagComponent
{
internal static readonly Bounds DEFAULT_BOUNDS = new Bounds(Vector3.zero, Vector3.one * 2);
[Serializable]
public enum InheritMode
{
Inherit,
Set,
DontSet
}
//[Header("Probe anchor configuration")]
public InheritMode InheritProbeAnchor = InheritMode.Inherit;
public AvatarObjectReference ProbeAnchor;
//[Header("Bounds configuration")]
public InheritMode InheritBounds = InheritMode.Inherit;
public AvatarObjectReference RootBone;
public Bounds Bounds = DEFAULT_BOUNDS;
}
}

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: b18784296c2581e498e5127a23e6f019
guid: 560fdafd46c74b2db6422fdf0e7f2363
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@ -1,38 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2022 bd_
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
using System;
using UnityEditor;
using UnityEngine;
namespace nadena.dev.modular_avatar.core
{
[ExecuteInEditMode]
[DisallowMultipleComponent]
[AddComponentMenu("Modular Avatar/MA Probe Anchor")]
public class ModularAvatarProbeAnchor : AvatarTagComponent
{
public AvatarObjectReference probeTarget;
}
}

View File

@ -0,0 +1,33 @@
# Mesh Settings
![Mesh Settings](mesh-settings.png)
The Mesh Settings component lets you set certain mesh settings (anchor override and bounds) for all meshes
under a particular game object.
## When should I use it?
You can place this component at the top level of your avatar to ensure that bounds and light probe anchors
are consistent for all meshes in your avatar.
The "Setup Outfit" feature will also automatically configure a Mesh Settings component on newly added outfits.
Finally, Mesh Settings can be used to _exclude_ meshes from the influence of Mesh Settings higher up on the
hierarchy.
## When shouldn't I use it?
Setting bounds or light probes on assets for distribution requires some care, as these configurations
might be inconsistent with the avatar they are applied to. Generally, these should only be set on assets
designed for a specific avatar.
## Manually configuring Mesh Settings
When you add Mesh Settings to a game object, initially it is configured to do nothing. By setting either
"Anchor Override Mode" or "Bounds Override Mode" to "Set", you can configure the anchor override or bounds
for all meshes under that game object. Alternately, by setting the mode to "Don't set", you can exclude
these meshes from the influence of Mesh Settings higher up on the hierarchy.
When configuring bounds, the bounding box will be determined relative to the transform you specify as the
"Root Bone". Note that bounds only affects Skinned Mesh Renderers, but Anchor Override also impacts other
types of renderers like Mesh Renderers or Line Renderers.

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

View File

@ -0,0 +1,30 @@
# Mesh Settings
![Mesh Settings](mesh-settings.png)
Mesh Settingsコンポーネントを使用すると、特定のゲームオブジェクトの配下にあるすべてのメッシュに対して、特定のメッシュ設定
(アンカーオーバーライドとバウンズ)を設定できます。
## いつ使うべきか
このコンポーネントをアバターの最上位レベルに配置すると、アバター内のすべてのメッシュに対してバウンズとライトプローブアンカーが
一貫して設定されるようになります。
「Setup Outfit」機能は、新しく追加されたアウトフィットに自動的にMesh Settingsコンポーネントを自動的に設定します。
最後に、Mesh Settingsは、階層の上位にあるMesh Settingsの影響を受けないように、メッシュを除外するためにも使用できます。
## いつ使うべきでないか
配布用のアセットにバウンズやライトプローブを設定する時は注意が必要です。なぜなら、これらの設定がアバターと一致しない可能性が
あります。一般的に、特定のアバター用に設計されたアセットの場合のみ設定を推奨します。
## Mesh Settingsの手動設定
ゲームオブジェクトにMesh Settingsを追加すると、最初は何も効果がありません。Anchor Overrideの設定モードまたはBounds Override
の設定モードを「設定」に設定することで、そのゲームオブジェクトの下にあるすべてのメッシュのアンカーオーバーライドまたはバウンズ
を設定できます。また、モードを「設定しない」に設定することで、これらのメッシュを階層の上位にあるMesh Settingsの影響から除外できます。
バウンズを設定する場合、バウンディングボックスは「Root Bone」として指定したトランスフォームに対して相対的に決定されます。
また、バウンズはSkinned Mesh Rendererのみに影響しますが、Anchor OverrideはMesh RendererやLine Rendererなどの他のタイプの
Rendererにも設定されます。

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB