diff --git a/src/MaterialEditor.Base/CopyContainer.cs b/src/MaterialEditor.Base/CopyContainer.cs index 691dfac7..6477133d 100644 --- a/src/MaterialEditor.Base/CopyContainer.cs +++ b/src/MaterialEditor.Base/CopyContainer.cs @@ -28,6 +28,10 @@ public class CopyContainer /// List of shader edits /// public List MaterialShaderList = new List(); + /// + /// List of projector edits + /// + public List ProjectorPropertyList = new List(); /// /// Whether there are any copied edits @@ -36,7 +40,7 @@ public bool IsEmpty { get { - if (MaterialFloatPropertyList.Count == 0 && MaterialKeywordPropertyList.Count == 0 && MaterialColorPropertyList.Count == 0 && MaterialTexturePropertyList.Count == 0 && MaterialShaderList.Count == 0) + if (MaterialFloatPropertyList.Count == 0 && MaterialKeywordPropertyList.Count == 0 && MaterialColorPropertyList.Count == 0 && MaterialTexturePropertyList.Count == 0 && MaterialShaderList.Count == 0 && ProjectorPropertyList.Count == 0) return true; return false; } @@ -52,6 +56,7 @@ public void ClearAll() MaterialColorPropertyList = new List(); MaterialTexturePropertyList = new List(); MaterialShaderList = new List(); + ProjectorPropertyList = new List(); } /// @@ -217,5 +222,31 @@ public MaterialShader(int? renderQueue) /// public bool NullCheck() => ShaderName.IsNullOrEmpty() && RenderQueue == null; } + + /// + /// Data storage class for projector properties + /// + public class ProjectorProperty + { + /// + /// Name of the property + /// + public MaterialAPI.ProjectorProperties Property; + /// + /// Value + /// + public float Value; + + /// + /// Data storage class for projector properties + /// + /// Name of the property + /// Value + public ProjectorProperty(MaterialAPI.ProjectorProperties property, float value) + { + Property = property; + Value = value; + } + } } } diff --git a/src/MaterialEditor.Base/Extensions.cs b/src/MaterialEditor.Base/Extensions.cs index e073ed45..8132c315 100644 --- a/src/MaterialEditor.Base/Extensions.cs +++ b/src/MaterialEditor.Base/Extensions.cs @@ -23,6 +23,7 @@ public static void Randomize(this IList list) public static string NameFormatted(this GameObject go) => go == null ? "" : go.name.Replace("(Instance)", "").Replace(" Instance", "").Trim(); public static string NameFormatted(this Material go) => go == null ? "" : go.name.Replace("(Instance)", "").Replace(" Instance", "").Trim(); public static string NameFormatted(this Renderer go) => go == null ? "" : go.name.Replace("(Instance)", "").Replace(" Instance", "").Trim(); + public static string NameFormatted(this Projector go) => go == null ? "" : go.name.Replace("(Instance)", "").Replace(" Instance", "").Trim(); public static string NameFormatted(this Shader go) => go == null ? "" : go.name.Replace("(Instance)", "").Replace(" Instance", "").Trim(); public static string NameFormatted(this Mesh go) => go == null ? "" : go.name.Replace("(Instance)", "").Replace(" Instance", "").Trim(); /// diff --git a/src/MaterialEditor.Base/MaterialAPI.cs b/src/MaterialEditor.Base/MaterialAPI.cs index 07446bce..0c106518 100644 --- a/src/MaterialEditor.Base/MaterialAPI.cs +++ b/src/MaterialEditor.Base/MaterialAPI.cs @@ -1,6 +1,5 @@ using System.Collections.Generic; using System.Linq; -using System.Reflection; using UnityEngine; namespace MaterialEditorAPI @@ -10,6 +9,12 @@ namespace MaterialEditorAPI /// public static class MaterialAPI { + /// + /// Projector materials share an instance by default, unlike renderers which get a unique one by default. + /// This list keeps track of every projector and it's new unique material instance + /// + internal static readonly Dictionary ProjectorMaterialInstances = new Dictionary(); + /// /// Postfix added to the name of a material when copied /// @@ -26,6 +31,30 @@ public static IEnumerable GetRendererList(GameObject gameObject) return gameObject.GetComponentsInChildren(true); } + /// + /// Get a list of all the projectors of a GameObject + /// + public static IEnumerable GetProjectorList(GameObject gameObject, bool inChildren = false) + { + if (gameObject == null) + return new List(); + + IEnumerable projectors; + if(inChildren) + projectors = gameObject.GetComponentsInChildren(true); + else + projectors = gameObject.GetComponents(); + + //Assign a unique copy of the material if the projector didn't have one already + foreach (var projector in projectors) + if (!ProjectorMaterialInstances.ContainsKey(projector)) + { + projector.material = new Material(projector.material); + ProjectorMaterialInstances[projector] = projector.material; + } + return projectors; + } + /// /// Get a list of materials for the renderer /// @@ -58,6 +87,9 @@ private static List GetObjectMaterials(GameObject gameObject, string m if (material.NameFormatted() == materialName) materials.Add(material); + foreach (var projector in GetProjectorList(gameObject)) + materials.Add(projector.material); + return materials; } @@ -396,6 +428,204 @@ public static bool SetRendererRecalculateNormals(GameObject gameObject, string r return didSet; } + /// + /// Set the value of the specified projector property + /// + /// GameObject to search for the projector + /// Name of the projector being modified + /// Property of the projector being modified + /// Value to be set + /// True if the value was set, false if it could not be set + public static bool SetProjectorProperty(GameObject gameObject, string projectorName, ProjectorProperties propertyName, float value) + { + if (propertyName == ProjectorProperties.Enabled) + return SetProjectorEnabled(gameObject, projectorName, System.Convert.ToBoolean(value)); + else if (propertyName == ProjectorProperties.FarClipPlane) + return SetProjectorFarClipPlane(gameObject, projectorName, value); + else if (propertyName == ProjectorProperties.NearClipPlane) + return SetProjectorNearClipPlane(gameObject, projectorName, value); + else if (propertyName == ProjectorProperties.FieldOfView) + return SetProjectorFieldOfView(gameObject, projectorName, value); + else if (propertyName == ProjectorProperties.AspectRatio) + return SetProjectorAspectRatio(gameObject, projectorName, value); + else if (propertyName == ProjectorProperties.Orthographic) + return SetProjectorOrthographic(gameObject, projectorName, System.Convert.ToBoolean(value)); + else if (propertyName == ProjectorProperties.OrthographicSize) + return SetProjectorOrthographicSize(gameObject, projectorName, value); + else if (propertyName == ProjectorProperties.IgnoreCharaLayer) + return SetProjectorIgnoreLayers(gameObject, projectorName, System.Convert.ToBoolean(value), 10); + else if (propertyName == ProjectorProperties.IgnoreMapLayer) + return SetProjectorIgnoreLayers(gameObject, projectorName, System.Convert.ToBoolean(value), 11); + return false; + } + + /// + /// Set the enabled property of a projector + /// + /// GameObject to search for the renderer + /// Name of the renderer being modified + /// Value to be set + /// True if the value was set, false if it could not be set + public static bool SetProjectorEnabled(GameObject gameObject, string projectorName, bool value) + { + foreach (var projector in GetProjectorList(gameObject)) + { + if (projector.NameFormatted() == projectorName) + { + projector.enabled = value; + return true; + } + } + return false; + } + + /// + /// Set the farclipPlane property of a projector + /// + /// GameObject to search for the projector + /// Name of the projector being modified + /// Value to be set + /// True if the value was set, false if it could not be set + public static bool SetProjectorFarClipPlane(GameObject gameObject, string projectorName, float value) + { + foreach(var projector in GetProjectorList(gameObject)) + { + if(projector.NameFormatted() == projectorName) + { + projector.farClipPlane = value; + return true; + } + } + return false; + } + + /// + /// Set the nearClipPlane property of a projector + /// + /// GameObject to search for the projector + /// Name of the projector being modified + /// Value to be set + /// True if the value was set, false if it could not be set + public static bool SetProjectorNearClipPlane(GameObject gameObject, string projectorName, float value) + { + foreach (var projector in GetProjectorList(gameObject)) + { + if (projector.NameFormatted() == projectorName) + { + projector.nearClipPlane = value; + return true; + } + } + return false; + } + + /// + /// Set the fieldOfView property of a projector + /// + /// GameObject to search for the projector + /// Name of the projector being modified + /// Value to be set + /// True if the value was set, false if it could not be set + public static bool SetProjectorFieldOfView(GameObject gameObject, string projectorName, float value) + { + foreach (var projector in GetProjectorList(gameObject)) + { + if (projector.NameFormatted() == projectorName) + { + projector.fieldOfView = value; + return true; + } + } + return false; + } + + /// + /// Set the aspectRatio property of a projector + /// + /// GameObject to search for the projector + /// Name of the projector being modified + /// Value to be set + /// True if the value was set, false if it could not be set + public static bool SetProjectorAspectRatio(GameObject gameObject, string projectorName, float value) + { + foreach (var projector in GetProjectorList(gameObject)) + { + if (projector.NameFormatted() == projectorName) + { + projector.aspectRatio = value; + return true; + } + } + return false; + } + + /// + /// Set the orthographic property of a projector + /// + /// GameObject to search for the projector + /// Name of the projector being modified + /// Value to be set + /// True if the value was set, false if it could not be set + public static bool SetProjectorOrthographic(GameObject gameObject, string projectorName, bool value) + { + foreach (var projector in GetProjectorList(gameObject)) + { + if (projector.NameFormatted() == projectorName) + { + projector.orthographic = value; + return true; + } + } + return false; + } + + /// + /// Set the orthoGraphicSize property of a projector + /// + /// GameObject to search for the projector + /// Name of the projector being modified + /// Value to be set + /// True if the value was set, false if it could not be set + public static bool SetProjectorOrthographicSize(GameObject gameObject, string projectorName, float value) + { + foreach (var projector in GetProjectorList(gameObject)) + { + if (projector.NameFormatted() == projectorName) + { + projector.orthographicSize = value; + return true; + } + } + return false; + } + + /// + /// Add or remove the given layer to/from the ignored layers property. + /// Ensures it's not removed/added that was already the case + /// + /// GameObject to search for the projector + /// Name of the projector being modified + /// If the given layer should be ignored or not + /// The index of the layer to be added/removed + /// True if the value was set, false if it could not be set + public static bool SetProjectorIgnoreLayers(GameObject gameObject, string projectorName, bool ignore, int layer) + { + foreach (var projector in GetProjectorList(gameObject)) + { + if (projector.NameFormatted() == projectorName) + { + bool inMask = projector.ignoreLayers == (projector.ignoreLayers | (1 << layer)); + //Remove layer from mask + if (inMask && !ignore) + projector.ignoreLayers &= ~(1 << layer); + //Add layer to mask + else if(!inMask && ignore) + projector.ignoreLayers |= (1 << layer); + } + } + return false; + } + /// /// Set the texture property of a material /// @@ -625,5 +855,47 @@ public enum RendererProperties /// UpdateWhenOffscreen } + /// + /// Properties of a projector that can be set + /// + public enum ProjectorProperties + { + /// + /// Whether the projector is enabled + /// + Enabled, + /// + /// Near clip plane to start projecting + /// + NearClipPlane, + /// + /// Far clip plane to stop projecting + /// + FarClipPlane, + /// + /// Field of View of projection + /// + FieldOfView, + /// + /// Aspect ratio of the projection + /// + AspectRatio, + /// + /// Whether the projector should project in orthographic mode + /// + Orthographic, + /// + /// The size of the orthographic projection + /// + OrthographicSize, + /// + /// If the projector should ignore objects on the chara layer + /// + IgnoreCharaLayer, + /// + /// If the projector should ignore objects on the map layer + /// + IgnoreMapLayer + } } } diff --git a/src/MaterialEditor.Base/PluginBase.cs b/src/MaterialEditor.Base/PluginBase.cs index d89780e3..d28a582d 100644 --- a/src/MaterialEditor.Base/PluginBase.cs +++ b/src/MaterialEditor.Base/PluginBase.cs @@ -50,6 +50,11 @@ public partial class MaterialEditorPluginBase : BaseUnityPlugin internal static ConfigEntry ConfigExportPath { get; private set; } public static ConfigEntry PersistFilter { get; set; } public static ConfigEntry ShowTimelineButtons { get; set; } + public static ConfigEntry ProjectorNearClipPlaneMax { get; set; } + public static ConfigEntry ProjectorFarClipPlaneMax { get; set; } + public static ConfigEntry ProjectorFieldOfViewMax { get; set; } + public static ConfigEntry ProjectorAspectRatioMax { get; set; } + public static ConfigEntry ProjectorOrthographicSizeMax { get; set; } public virtual void Awake() { @@ -69,6 +74,19 @@ public virtual void Awake() PersistFilter = Config.Bind("Config", "Persist Filter", false, "Persist search filter across editor windows"); ShowTimelineButtons = Config.Bind("Config", "Show Timeline Buttons", false, "Show buttons in the UI (in studio only) to add interpolables to the timeline. Requires game restart to take effect"); + //Everything in these games is 10x the size of KK/KKS +#if AI || HS2 || PH + ProjectorNearClipPlaneMax = Config.Bind("Projector", "Max Near Clip Plane", 100f, new ConfigDescription("Controls the max value of the slider for this projector property", new AcceptableValueRange(0.01f, 1000f), new ConfigurationManagerAttributes { Order = 5 })); + ProjectorFarClipPlaneMax = Config.Bind("Projector", "Max Far Clip Plane", 1000f, new ConfigDescription("Controls the max value of the slider for this projector property", new AcceptableValueRange(0.01f, 1000f), new ConfigurationManagerAttributes { Order = 4 })); + ProjectorOrthographicSizeMax = Config.Bind("Projector", "Max Orthographic Size", 20f, new ConfigDescription("Controls the max value of the slider for this projector property", new AcceptableValueRange(0.01f, 1000f), new ConfigurationManagerAttributes { Order = 1 })); +#else + ProjectorNearClipPlaneMax = Config.Bind("Projector", "Max Near Clip Plane", 10f, new ConfigDescription("Controls the max value of the slider for this projector property", new AcceptableValueRange(0.01f, 100f), new ConfigurationManagerAttributes { Order = 5 })); + ProjectorFarClipPlaneMax = Config.Bind("Projector", "Max Far Clip Plane", 100f, new ConfigDescription("Controls the max value of the slider for this projector property", new AcceptableValueRange(0.01f, 100f), new ConfigurationManagerAttributes { Order = 4 })); + ProjectorOrthographicSizeMax = Config.Bind("Projector", "Max Orthographic Size", 2f, new ConfigDescription("Controls the max value of the slider for this projector property", new AcceptableValueRange(0.01f, 100f), new ConfigurationManagerAttributes { Order = 1 })); +#endif + ProjectorFieldOfViewMax = Config.Bind("Projector", "Max Field Of View", 180f, new ConfigDescription("Controls the max value of the slider for this projector property", new AcceptableValueRange(0.01f, 180f), new ConfigurationManagerAttributes { Order = 3 })); + ProjectorAspectRatioMax = Config.Bind("Projector", "Max Aspect Ratio", 2f, new ConfigDescription("Controls the max value of the slider for this projector property", new AcceptableValueRange(0.01f, 100f), new ConfigurationManagerAttributes { Order = 2 })); + UIScale.SettingChanged += MaterialEditorUI.UISettingChanged; UIWidth.SettingChanged += MaterialEditorUI.UISettingChanged; UIHeight.SettingChanged += MaterialEditorUI.UISettingChanged; diff --git a/src/MaterialEditor.Base/UI/UI.ListEntry.cs b/src/MaterialEditor.Base/UI/UI.ListEntry.cs index 76aed0ee..c67cfbaf 100644 --- a/src/MaterialEditor.Base/UI/UI.ListEntry.cs +++ b/src/MaterialEditor.Base/UI/UI.ListEntry.cs @@ -259,8 +259,13 @@ public void SetItem(ItemInfo item, bool force) Text text = MaterialCopyRemove.GetComponentInChildren(); text.text = "Copy Material"; } - MaterialCopyRemove.onClick.RemoveAllListeners(); - MaterialCopyRemove.onClick.AddListener(delegate { item.MaterialOnCopyRemove.Invoke(); }); + if (item.MaterialOnCopyRemove != null) + { + MaterialCopyRemove.onClick.RemoveAllListeners(); + MaterialCopyRemove.onClick.AddListener(delegate { item.MaterialOnCopyRemove.Invoke(); }); + } + else + MaterialCopyRemove.gameObject.SetActive(false); break; case ItemInfo.RowItemType.Shader: diff --git a/src/MaterialEditor.Base/UI/UI.cs b/src/MaterialEditor.Base/UI/UI.cs index 98fdcd7b..8926d652 100644 --- a/src/MaterialEditor.Base/UI/UI.cs +++ b/src/MaterialEditor.Base/UI/UI.cs @@ -74,6 +74,7 @@ public abstract class MaterialEditorUI : BaseUnityPlugin private Renderer ObjRenderer; internal static SelectedInterpolable selectedInterpolable; + internal static SelectedProjectorInterpolable selectedProjectorInterpolable; /// /// Initialize the MaterialEditor UI @@ -304,6 +305,8 @@ protected void PopulateList(GameObject go, object data, string filter = null) List rendList = new List(); IEnumerable rendListFull = GetRendererList(go); + List projectorList = new List(); + IEnumerable projectorListFull = GetProjectorList(data, go); List filterList = new List(); List filterListProperties = new List(); List items = new List(); @@ -331,6 +334,7 @@ protected void PopulateList(GameObject go, object data, string filter = null) else if (filterList.Count == 0) rendList = rendListFull.ToList(); else + { foreach (var rend in rendListFull) { foreach (string filterWord in filterList) @@ -341,7 +345,13 @@ protected void PopulateList(GameObject go, object data, string filter = null) foreach (string filterWord in filterList) if (WildCardSearch(mat.NameFormatted(), filterWord.Trim())) matList[mat.NameFormatted()] = mat; + } + foreach (var projector in projectorListFull) + foreach (string filterWord in filterList) + if (WildCardSearch(projector.NameFormatted(), filterWord.Trim())) + projectorList.Add(projector); + } for (var i = 0; i < rendList.Count; i++) { @@ -443,6 +453,14 @@ protected void PopulateList(GameObject go, object data, string filter = null) } foreach (var mat in matList.Values) + PopulateListMaterial(mat); + + foreach (var projector in filterList.Count == 0 ? projectorListFull : projectorList) + PopulateListMaterial(projector.material, projector); + + VirtualList.SetList(items); + + void PopulateListMaterial(Material mat, Projector projector = null) { string materialName = mat.NameFormatted(); string shaderName = mat.shader.NameFormatted(); @@ -457,14 +475,19 @@ protected void PopulateList(GameObject go, object data, string filter = null) PopulateList(go, data, filter); } }; - materialItem.MaterialOnCopyRemove = () => - { - MaterialCopyRemove(data, mat, go); - PopulateList(go, data, filter); - PopulateMaterialList(go, data, rendListFull); - }; + //Projectors only support 1 material. Copy button is hidden if the function is null + if (projector == null) + materialItem.MaterialOnCopyRemove = () => + { + MaterialCopyRemove(data, mat, go); + PopulateList(go, data, filter); + PopulateMaterialList(go, data, rendListFull); + }; items.Add(materialItem); + if (projector != null) + PopulateProjectorSettings(projector); + //Shader string shaderNameOriginal = shaderName; var temp = GetMaterialShaderNameOriginal(data, mat, go); @@ -505,8 +528,8 @@ protected void PopulateList(GameObject go, object data, string filter = null) { string propertyName = property.Key; if (Instance.CheckBlacklist(materialName, propertyName)) continue; - - bool showProperty = filterListProperties.Count == 0 || + + bool showProperty = filterListProperties.Count == 0 || filterListProperties.Any(filterWord => WildCardSearch(propertyName, filterWord)); if (!showProperty) continue; @@ -612,24 +635,22 @@ void OnFileAccept(string[] strings) { if (mat.HasProperty($"_{propertyName}")) { - float valueFloat = mat.GetFloat($"_{propertyName}"); - float valueFloatOriginal = valueFloat; + float valueFloatOriginal = mat.GetFloat($"_{propertyName}"); float? valueFloatOriginalTemp = GetMaterialFloatPropertyValueOriginal(data, mat, propertyName, go); if (valueFloatOriginalTemp != null) valueFloatOriginal = (float)valueFloatOriginalTemp; - var contentItem = new ItemInfo(ItemInfo.RowItemType.FloatProperty, propertyName) - { - FloatValue = valueFloat, - FloatValueOriginal = valueFloatOriginal, - SelectInterpolableButtonFloatOnClick = () => SelectInterpolableButtonOnClick(go, ItemInfo.RowItemType.FloatProperty, materialName, propertyName) - }; - if (property.Value.MinValue != null) - contentItem.FloatValueSliderMin = (float)property.Value.MinValue; - if (property.Value.MaxValue != null) - contentItem.FloatValueSliderMax = (float)property.Value.MaxValue; - contentItem.FloatValueOnChange = value => SetMaterialFloatProperty(data, mat, propertyName, value, go); - contentItem.FloatValueOnReset = () => RemoveMaterialFloatProperty(data, mat, propertyName, go); - items.Add(contentItem); + + AddFloatslider + ( + valueFloat: mat.GetFloat($"_{propertyName}"), + propertyName: propertyName, + onInteroperableClick: () => SelectInterpolableButtonOnClick(go, ItemInfo.RowItemType.FloatProperty, materialName, propertyName), + changeValue: value => SetMaterialFloatProperty(data, mat, propertyName, value, go), + resetValue: () => RemoveMaterialFloatProperty(data, mat, propertyName, go), + valueFloatOriginal: valueFloatOriginal, + minValue: property.Value.MinValue, + maxValue: property.Value.MaxValue + ); } } else if (property.Value.Type == ShaderPropertyType.Keyword) @@ -654,7 +675,95 @@ void OnFileAccept(string[] strings) } } - VirtualList.SetList(items); + void PopulateProjectorSettings(Projector projector) + { + foreach (var property in Enum.GetValues(typeof(ProjectorProperties)).Cast()) + { + float maxValue = 100f; + string name = ""; + float valueFloat = 0f; + float? originalValueTemp = GetProjectorPropertyValueOriginal(data, projector, property, go); + + switch (property) + { + case ProjectorProperties.Enabled: + name = "Enabled"; + valueFloat = Convert.ToSingle(projector.enabled); + maxValue = 1f; + break; + case ProjectorProperties.NearClipPlane: + name = "Near Clip Plane"; + valueFloat = projector.nearClipPlane; + maxValue = ProjectorNearClipPlaneMax.Value; + break; + case ProjectorProperties.FarClipPlane: + name = "Far Clip Plane"; + valueFloat = projector.farClipPlane; + maxValue = ProjectorFarClipPlaneMax.Value; + break; + case ProjectorProperties.FieldOfView: + name = "Field Of View"; + valueFloat = projector.fieldOfView; + maxValue = ProjectorFieldOfViewMax.Value; + break; + case ProjectorProperties.AspectRatio: + name = "Aspect Ratio"; + valueFloat = projector.aspectRatio; + maxValue = ProjectorAspectRatioMax.Value; + break; + case ProjectorProperties.Orthographic: + name = "Orthographic"; + valueFloat = Convert.ToSingle(projector.orthographic); + maxValue = 1f; + break; + case ProjectorProperties.OrthographicSize: + name = "Orthographic Size"; + valueFloat = projector.orthographicSize; + maxValue = ProjectorOrthographicSizeMax.Value; + break; + case ProjectorProperties.IgnoreMapLayer: + name = "Ignore Map layer"; + valueFloat = Convert.ToSingle(projector.ignoreLayers == (projector.ignoreLayers | (1 << 11))); + maxValue = 1f; + break; + case ProjectorProperties.IgnoreCharaLayer: + name = "Ignore Chara Layer"; + valueFloat = Convert.ToSingle(projector.ignoreLayers == (projector.ignoreLayers | (1 << 10))); + maxValue = 1f; + break; + } + + if (filterListProperties.Count == 0 || filterListProperties.Any(filterWord => WildCardSearch(name, filterWord))) + AddFloatslider + ( + valueFloat: valueFloat, + propertyName: name, + onInteroperableClick: () => SelectProjectorInterpolableButtonOnClick(go, property, projector.NameFormatted()), + changeValue: value => SetProjectorProperty(data, projector, property, value, projector.gameObject), + resetValue: () => RemoveProjectorProperty(data, projector, property, projector.gameObject), + valueFloatOriginal: originalValueTemp != null ? (float)originalValueTemp : valueFloat, + minValue: 0f, + maxValue: maxValue + ); + } + } + + void AddFloatslider(float valueFloat, string propertyName, Action onInteroperableClick, Action changeValue, Action resetValue, float valueFloatOriginal, float? minValue = null, float? maxValue = null) + { + var contentItem = new ItemInfo(ItemInfo.RowItemType.FloatProperty, propertyName) + { + FloatValue = valueFloat, + FloatValueOriginal = valueFloatOriginal, + SelectInterpolableButtonFloatOnClick = () => onInteroperableClick() + }; + if (minValue != null) + contentItem.FloatValueSliderMin = (float)minValue; + if (maxValue != null) + contentItem.FloatValueSliderMax = (float)maxValue; + contentItem.FloatValueOnChange = value => changeValue(value); + contentItem.FloatValueOnReset = () => resetValue(); + items.Add(contentItem); + } } /// @@ -706,6 +815,12 @@ private static void ExportTexture(Material mat, string property) public abstract void SetRendererProperty(object data, Renderer renderer, RendererProperties property, string value, GameObject gameObject); public abstract void RemoveRendererProperty(object data, Renderer renderer, RendererProperties property, GameObject gameObject); + public abstract float? GetProjectorPropertyValueOriginal(object data, Projector renderer, ProjectorProperties property, GameObject gameObject); + public abstract float? GetProjectorPropertyValue(object data, Projector renderer, ProjectorProperties property, GameObject gameObject); + public abstract void SetProjectorProperty(object data, Projector projector, ProjectorProperties property, float value, GameObject gameObject); + public abstract void RemoveProjectorProperty(object data, Projector projector, ProjectorProperties property, GameObject gameObject); + public abstract IEnumerable GetProjectorList(object data, GameObject gameObject); + public abstract void MaterialCopyEdits(object data, Material material, GameObject gameObject); public abstract void MaterialPasteEdits(object data, Material material, GameObject gameObject); public abstract void MaterialCopyRemove(object data, Material material, GameObject gameObject); @@ -786,6 +901,15 @@ private void SelectInterpolableButtonOnClick(GameObject go, ItemInfo.RowItemType #endif } + private void SelectProjectorInterpolableButtonOnClick(GameObject go, ProjectorProperties property, string projectorName) + { + selectedProjectorInterpolable = new SelectedProjectorInterpolable(go, property, projectorName); + MaterialEditorPluginBase.Logger.LogMessage($"Activated interpolable(s), {selectedProjectorInterpolable}"); +#if !API && !EC + TimelineCompatibilityHelper.RefreshInterpolablesList(); +#endif + } + internal class SelectedInterpolable { public string MaterialName; @@ -808,5 +932,24 @@ public override string ToString() return $"{RowType}: {string.Join(" - ", new string[] { PropertyName, MaterialName, RendererName, }.Where(x => !x.IsNullOrEmpty()).ToArray())}"; } } + + internal class SelectedProjectorInterpolable + { + public string ProjectorName; + public ProjectorProperties Property; + public GameObject GameObject; + + public SelectedProjectorInterpolable(GameObject go, ProjectorProperties property, string projectorName) + { + GameObject = go; + Property = property; + ProjectorName = projectorName; + } + + public override string ToString() + { + return $"Projector: {string.Join(" - ", new string[] { Property.ToString(), ProjectorName, }.Where(x => !x.IsNullOrEmpty()).ToArray())}"; + } + } } } diff --git a/src/MaterialEditor.Core.Maker/Core.MaterialEditor.Maker.cs b/src/MaterialEditor.Core.Maker/Core.MaterialEditor.Maker.cs index cf781d41..504ce001 100644 --- a/src/MaterialEditor.Core.Maker/Core.MaterialEditor.Maker.cs +++ b/src/MaterialEditor.Core.Maker/Core.MaterialEditor.Maker.cs @@ -3,9 +3,10 @@ using KKAPI; using KKAPI.Maker; using KKAPI.Maker.UI; -using MaterialEditorAPI; +using MaterialEditorAPI; using System.Collections; -using UnityEngine; +using System.Collections.Generic; +using UnityEngine; using static MaterialEditorAPI.MaterialAPI; #if AI || HS2 using AIChara; @@ -317,6 +318,35 @@ public override void RemoveRendererProperty(object data, Renderer renderer, Rend { ObjectData objectData = (ObjectData)data; MaterialEditorPlugin.GetCharaController(MakerAPI.GetCharacterControl()).RemoveRendererProperty(objectData.Slot, objectData.ObjectType, renderer, property, go); + } + + public override float? GetProjectorPropertyValueOriginal(object data, Projector projector, ProjectorProperties property, GameObject gameObject) + { + ObjectData objectData = (ObjectData)data; + return MaterialEditorPlugin.GetCharaController(MakerAPI.GetCharacterControl()).GetProjectorPropertyValueOriginal(objectData.Slot, objectData.ObjectType, projector, property, gameObject); + } + + public override float? GetProjectorPropertyValue(object data, Projector projector, ProjectorProperties property, GameObject gameObject) + { + ObjectData objectData = (ObjectData)data; + return MaterialEditorPlugin.GetCharaController(MakerAPI.GetCharacterControl()).GetProjectorPropertyValue(objectData.Slot, objectData.ObjectType, projector, property, gameObject); + } + + public override void SetProjectorProperty(object data, Projector projector, ProjectorProperties property, float value, GameObject gameObject) + { + ObjectData objectData = (ObjectData)data; + MaterialEditorPlugin.GetCharaController(MakerAPI.GetCharacterControl()).SetProjectorProperty(objectData.Slot, objectData.ObjectType, projector, property, value, gameObject); + } + + public override void RemoveProjectorProperty(object data, Projector projector, ProjectorProperties property, GameObject gameObject) + { + ObjectData objectData = (ObjectData)data; + MaterialEditorPlugin.GetCharaController(MakerAPI.GetCharacterControl()).RemoveProjectorProperty(objectData.Slot, objectData.ObjectType, projector, property, gameObject); + } + public override IEnumerable GetProjectorList(object data, GameObject gameObject) + { + ObjectData objectData = (ObjectData)data; + return MaterialEditorPlugin.GetCharaController(MakerAPI.GetCharacterControl()).GetProjectorList(objectData.ObjectType, gameObject); } public override void MaterialCopyEdits(object data, Material material, GameObject go) diff --git a/src/MaterialEditor.Core.Studio/Core.MaterialEditor.SceneController.cs b/src/MaterialEditor.Core.Studio/Core.MaterialEditor.SceneController.cs index 9ba34599..ab642f7b 100644 --- a/src/MaterialEditor.Core.Studio/Core.MaterialEditor.SceneController.cs +++ b/src/MaterialEditor.Core.Studio/Core.MaterialEditor.SceneController.cs @@ -23,6 +23,7 @@ namespace KK_Plugins.MaterialEditor public class SceneController : SceneCustomFunctionController { private readonly List RendererPropertyList = new List(); + private readonly List ProjectorPropertyList = new List(); private readonly List MaterialFloatPropertyList = new List(); private readonly List MaterialKeywordPropertyList = new List(); private readonly List MaterialColorPropertyList = new List(); @@ -57,6 +58,11 @@ protected override void OnSceneSave() else data.data.Add(nameof(RendererPropertyList), null); + if (ProjectorPropertyList.Count > 0) + data.data.Add(nameof(ProjectorPropertyList), MessagePackSerializer.Serialize(ProjectorPropertyList)); + else + data.data.Add(nameof(ProjectorPropertyList), null); + if (MaterialFloatPropertyList.Count > 0) data.data.Add(nameof(MaterialFloatPropertyList), MessagePackSerializer.Serialize(MaterialFloatPropertyList)); else @@ -198,6 +204,18 @@ protected override void OnSceneLoad(SceneOperationKind operation, ReadOnlyDictio } } + if (data.data.TryGetValue(nameof(ProjectorPropertyList), out var projectorProperties) && projectorProperties != null) + { + var properties = MessagePackSerializer.Deserialize>((byte[])projectorProperties); + for (var i = 0; i < properties.Count; i++) + { + var loadedProperty = properties[i]; + if (loadedItems.TryGetValue(loadedProperty.ID, out ObjectCtrlInfo objectCtrlInfo) && objectCtrlInfo is OCIItem ociItem) + if (MaterialAPI.SetProjectorProperty(ociItem.objectItem, loadedProperty.ProjectorName, loadedProperty.Property, float.Parse(loadedProperty.Value))) + ProjectorPropertyList.Add(new ProjectorProperty(MEStudio.GetObjectID(objectCtrlInfo), loadedProperty.ProjectorName, loadedProperty.Property, loadedProperty.Value, loadedProperty.ValueOriginal)); + } + } + if (data.data.TryGetValue(nameof(MaterialFloatPropertyList), out var materialFloatProperties) && materialFloatProperties != null) { var properties = MessagePackSerializer.Deserialize>((byte[])materialFloatProperties); @@ -274,6 +292,7 @@ protected override void OnSceneLoad(SceneOperationKind operation, ReadOnlyDictio protected override void OnObjectsCopied(ReadOnlyDictionary copiedItems) { List rendererPropertyListNew = new List(); + List projectorPropertyListNew = new List(); List materialFloatPropertyListNew = new List(); List materialKeywordPropertyListNew = new List(); List materialColorPropertyListNew = new List(); @@ -315,6 +334,14 @@ protected override void OnObjectsCopied(ReadOnlyDictionary rendererPropertyListNew.Add(new RendererProperty(copiedItem.Value.GetSceneId(), loadedProperty.RendererName, loadedProperty.Property, loadedProperty.Value, loadedProperty.ValueOriginal)); } + for (var i = 0; i < ProjectorPropertyList.Count; i++) + { + var loadedProperty = ProjectorPropertyList[i]; + if (loadedProperty.ID == copiedItem.Key) + if (MaterialAPI.SetProjectorProperty(ociItem.objectItem, loadedProperty.ProjectorName, loadedProperty.Property, float.Parse(loadedProperty.Value))) + projectorPropertyListNew.Add(new ProjectorProperty(copiedItem.Value.GetSceneId(), loadedProperty.ProjectorName, loadedProperty.Property, loadedProperty.Value, loadedProperty.ValueOriginal)); + } + for (var i = 0; i < MaterialFloatPropertyList.Count; i++) { var loadedProperty = MaterialFloatPropertyList[i]; @@ -359,6 +386,7 @@ protected override void OnObjectsCopied(ReadOnlyDictionary } RendererPropertyList.AddRange(rendererPropertyListNew); + ProjectorPropertyList.AddRange(projectorPropertyListNew); MaterialFloatPropertyList.AddRange(materialFloatPropertyListNew); MaterialKeywordPropertyList.AddRange(materialKeywordPropertyListNew); MaterialColorPropertyList.AddRange(materialColorPropertyListNew); @@ -563,6 +591,8 @@ private void PasteEditsRecursive(TreeNodeObject node, ref int count) MaterialPasteEdits(ociItem.objectInfo.dicKey, mat); } } + foreach(var projector in GetProjectorList(ociItem.objectItem)) + MaterialPasteEdits(ociItem.objectInfo.dicKey, projector.material); } foreach (var child in node.child) PasteEditsRecursive(child, ref count); @@ -574,6 +604,7 @@ protected override void OnObjectDeleted(ObjectCtrlInfo objectCtrlInfo) { var id = item.GetSceneId(); RendererPropertyList.RemoveAll(x => x.ID == id); + ProjectorPropertyList.RemoveAll(x => x.ID == id); MaterialFloatPropertyList.RemoveAll(x => x.ID == id); MaterialKeywordPropertyList.RemoveAll(x => x.ID == id); MaterialColorPropertyList.RemoveAll(x => x.ID == id); @@ -649,6 +680,7 @@ internal static void AddExternallyReferencedTextureID(int id) /// /// Item ID as found in studio's dicObjectCtrl /// Material being modified. Also modifies all other materials of the same name. + /// Projector being modified public void MaterialCopyEdits(int id, Material material) { CopyData.ClearAll(); @@ -688,6 +720,14 @@ public void MaterialCopyEdits(int id, Material material) CopyData.MaterialTexturePropertyList.Add(new CopyContainer.MaterialTextureProperty(materialTextureProperty.Property, null, materialTextureProperty.Offset, materialTextureProperty.Scale)); } } + + if (GetProjectorList(GetObjectByID(id)).FirstOrDefault(x => x.material == material) != null) + for (var i = 0; i < ProjectorPropertyList.Count; i++) + { + var projectorProperty = ProjectorPropertyList[i]; + if (projectorProperty.ID == id) + CopyData.ProjectorPropertyList.Add(new CopyContainer.ProjectorProperty(projectorProperty.Property, float.Parse(projectorProperty.Value))); + } } /// /// Paste any edits for the specified object @@ -695,6 +735,7 @@ public void MaterialCopyEdits(int id, Material material) /// Item ID as found in studio's dicObjectCtrl /// Material being modified. Also modifies all other materials of the same name. /// Whether to also apply the value to the materials + /// Projector being modified public void MaterialPasteEdits(int id, Material material, bool setProperty = true) { for (var i = 0; i < CopyData.MaterialShaderList.Count; i++) @@ -732,6 +773,14 @@ public void MaterialPasteEdits(int id, Material material, bool setProperty = tru if (materialTextureProperty.Scale != null) SetMaterialTextureScale(id, material, materialTextureProperty.Property, (Vector2)materialTextureProperty.Scale, setProperty); } + + var projector = GetProjectorList(GetObjectByID(id)).FirstOrDefault(x => x.material == material); + if (projector != null) + for (var i = 0; i < CopyData.ProjectorPropertyList.Count; i++) + { + var projectorProperty = CopyData.ProjectorPropertyList[i]; + SetProjectorProperty(id, projector, projectorProperty.Property, projectorProperty.Value, setProperty); + } } public void MaterialCopyRemove(int id, Material material, GameObject go, bool setProperty = true) { @@ -776,6 +825,104 @@ public void MaterialCopyRemove(int id, Material material, GameObject go, bool se } } + /// + /// Get the saved renderer property's original value or null if none is saved + /// + /// Item ID as found in studio's dicObjectCtrl + /// Renderer being modified + /// Property of the renderer + /// Saved renderer property's original value + public float? GetProjectorPropertyValueOriginal(int id, Projector projector, ProjectorProperties property) + { + var valueOriginal = ProjectorPropertyList.FirstOrDefault(x => x.ID == id && x.Property == property && x.ProjectorName == projector.NameFormatted())?.ValueOriginal; + if (valueOriginal.IsNullOrEmpty()) + return null; + return float.Parse(valueOriginal); + } + + /// + /// Get the saved projector property value or null if none is saved + /// + /// Item ID as found in studio's dicObjectCtrl + /// Projector being modified + /// Property of the projector + /// Saved projector property value + public float? GetProjectorPropertyValue(int id, Projector projector, ProjectorProperties property) + { + var valueOriginal = ProjectorPropertyList.FirstOrDefault(x => x.ID == id && x.Property == property && x.ProjectorName == projector.NameFormatted())?.Value; + if (valueOriginal.IsNullOrEmpty()) + return null; + return float.Parse(valueOriginal); + } + + /// + /// Remove the saved projector property value if one is saved and optionally also update the projector + /// + /// Item ID as found in studio's dicObjectCtrl + /// projector being modified + /// Property of the projector + /// Whether to also apply the value to the projector + public void RemoveProjectorProperty(int id, Projector projector, ProjectorProperties property, bool setProperty = true) + { + GameObject go = GetObjectByID(id); + if (setProperty) + { + var original = GetProjectorPropertyValueOriginal(id, projector, property); + if (original != null) + { + MaterialAPI.SetProjectorProperty(go, projector.NameFormatted(), property, (float)original); + } + } + + ProjectorPropertyList.RemoveAll(x => x.ID == id && x.Property == property && x.ProjectorName == projector.NameFormatted()); + } + + public void SetProjectorProperty(int id, Projector projector, ProjectorProperties property, float value, bool setProperty = true) + { + GameObject go = GetObjectByID(id); + var projectorProperty = ProjectorPropertyList.FirstOrDefault(x => x.ID == id && x.Property == property && x.ProjectorName == projector.NameFormatted()); + if (projectorProperty == null) + { + string valueOriginal = ""; + if (property == ProjectorProperties.FarClipPlane) + valueOriginal = projector.farClipPlane.ToString(CultureInfo.InvariantCulture); + else if (property == ProjectorProperties.NearClipPlane) + valueOriginal = projector.nearClipPlane.ToString(CultureInfo.InvariantCulture); + else if (property == ProjectorProperties.FieldOfView) + valueOriginal = projector.fieldOfView.ToString(CultureInfo.InvariantCulture); + else if (property == ProjectorProperties.AspectRatio) + valueOriginal = projector.aspectRatio.ToString(CultureInfo.InvariantCulture); + else if (property == ProjectorProperties.Orthographic) + valueOriginal = Convert.ToSingle(projector.orthographic).ToString(CultureInfo.InvariantCulture); + else if (property == ProjectorProperties.OrthographicSize) + valueOriginal = projector.orthographicSize.ToString(CultureInfo.InvariantCulture); + else if (property == ProjectorProperties.IgnoreCharaLayer) + valueOriginal = Convert.ToSingle(projector.ignoreLayers == (projector.ignoreLayers | (1 << 10))).ToString(CultureInfo.InvariantCulture); + else if (property == ProjectorProperties.IgnoreMapLayer) + valueOriginal = Convert.ToSingle(projector.ignoreLayers == (projector.ignoreLayers | (1 << 11))).ToString(CultureInfo.InvariantCulture); + + if (valueOriginal != "") + ProjectorPropertyList.Add(new ProjectorProperty(id, projector.NameFormatted(), property, value.ToString(CultureInfo.InvariantCulture), valueOriginal)); + } + else + { + if (value.ToString(CultureInfo.InvariantCulture) == projectorProperty.ValueOriginal) + RemoveProjectorProperty(id, projector, property, false); + else + projectorProperty.Value = value.ToString(CultureInfo.InvariantCulture); + } + + if (setProperty) + MaterialAPI.SetProjectorProperty(go, projector.NameFormatted(), property, value); + } + + public IEnumerable GetProjectorList(GameObject gameObject) + { + //Assume the projector component will always be attached to the root object + //Otherwise no distinction can be made between projectors and editing them will not work properly + return MaterialAPI.GetProjectorList(gameObject, false); + } + #region Set, Get, Remove methods /// /// Add a renderer property to be saved and loaded with the scene and optionally also update the renderer. @@ -1895,5 +2042,56 @@ public MaterialCopy(int id, string materialName, string materialCopyName) MaterialCopyName = materialCopyName; } } + + /// + /// Data storage class for projector properties + /// + [Serializable] + [MessagePackObject] + public class ProjectorProperty + { + /// + /// ID of the item + /// + [Key("ID")] + public int ID; + /// + /// Name of the projector + /// + [Key("ProjectorName")] + public string ProjectorName; + /// + /// Property type + /// + [Key("Property")] + public ProjectorProperties Property; + /// + /// Value + /// + [Key("Value")] + public string Value; + /// + /// Original value + /// + [Key("ValueOriginal")] + public string ValueOriginal; + + /// + /// Data storage class for renderer properties + /// + /// ID of the item + /// Name of the renderer + /// Property type + /// Value + /// Original + public ProjectorProperty(int id, string projectorName, ProjectorProperties property, string value, string valueOriginal) + { + ID = id; + ProjectorName = projectorName.Replace("(Instance)", "").Trim(); + Property = property; + Value = value; + ValueOriginal = valueOriginal; + } + } } } \ No newline at end of file diff --git a/src/MaterialEditor.Core.Studio/Core.MaterialEditor.Studio.cs b/src/MaterialEditor.Core.Studio/Core.MaterialEditor.Studio.cs index 911bec4e..8915b997 100644 --- a/src/MaterialEditor.Core.Studio/Core.MaterialEditor.Studio.cs +++ b/src/MaterialEditor.Core.Studio/Core.MaterialEditor.Studio.cs @@ -7,14 +7,13 @@ using MaterialEditorAPI; using Studio; using System; +using System.Collections.Generic; using System.IO; -using System.Linq; using System.Reflection; using UILib; -using UniRx; using UnityEngine; using UnityEngine.SceneManagement; -using UnityEngine.UI; +using UnityEngine.UI; using static MaterialEditorAPI.MaterialAPI; #if AI || HS2 using AIChara; @@ -431,6 +430,56 @@ public override void RemoveRendererProperty(object data, Renderer renderer, Rend } else GetSceneController().RemoveRendererProperty((int)data, renderer, property); + } + public override float? GetProjectorPropertyValueOriginal(object data, Projector projector, ProjectorProperties property, GameObject gameObject) + { + if (data is ObjectData objectData) + { + var chaControl = gameObject.GetComponentInParent(); + return MaterialEditorPlugin.GetCharaController(chaControl).GetProjectorPropertyValueOriginal(objectData.Slot, objectData.ObjectType, projector, property, gameObject); + } + else + return GetSceneController().GetProjectorPropertyValueOriginal((int)data, projector, property); + } + public override float? GetProjectorPropertyValue(object data, Projector projector, ProjectorProperties property, GameObject gameObject) + { + if (data is ObjectData objectData) + { + var chaControl = gameObject.GetComponentInParent(); + return MaterialEditorPlugin.GetCharaController(chaControl).GetProjectorPropertyValue(objectData.Slot, objectData.ObjectType, projector, property, gameObject); + } + else + return GetSceneController().GetProjectorPropertyValue((int)data, projector, property); + } + public override void SetProjectorProperty(object data, Projector projector, ProjectorProperties property, float value, GameObject go) + { + if (data is ObjectData objectData) + { + var chaControl = go.GetComponentInParent(); + MaterialEditorPlugin.GetCharaController(chaControl).SetProjectorProperty(objectData.Slot, objectData.ObjectType, projector, property, value, gameObject); + } + else + GetSceneController().SetProjectorProperty((int)data, projector, property, value); + } + public override void RemoveProjectorProperty(object data, Projector projector, ProjectorProperties property, GameObject gameObject) + { + if (data is ObjectData objectData) + { + var chaControl = gameObject.GetComponentInParent(); + MaterialEditorPlugin.GetCharaController(chaControl).RemoveProjectorProperty(objectData.Slot, objectData.ObjectType, projector, property, gameObject); + } + else + GetSceneController().RemoveProjectorProperty((int)data, projector, property); + } + public override IEnumerable GetProjectorList(object data, GameObject gameObject) + { + if (data is ObjectData objectData) + { + var chaControl = gameObject.GetComponentInParent(); + return MaterialEditorPlugin.GetCharaController(chaControl).GetProjectorList(objectData.ObjectType, gameObject); + } + else + return GetSceneController().GetProjectorList(gameObject); } public override void MaterialCopyEdits(object data, Material material, GameObject go) diff --git a/src/MaterialEditor.Core.Studio/Core.MaterialEditor.TimelineCompatibilityHelper.cs b/src/MaterialEditor.Core.Studio/Core.MaterialEditor.TimelineCompatibilityHelper.cs index 79737160..1c823846 100644 --- a/src/MaterialEditor.Core.Studio/Core.MaterialEditor.TimelineCompatibilityHelper.cs +++ b/src/MaterialEditor.Core.Studio/Core.MaterialEditor.TimelineCompatibilityHelper.cs @@ -9,6 +9,7 @@ using static MaterialEditorAPI.MaterialEditorUI; using static KK_Plugins.MaterialEditor.SceneController; using System.Text.RegularExpressions; +using System; namespace MaterialEditorAPI { @@ -247,6 +248,24 @@ internal static void PopulateTimeline() getFinalName: (currentName, oci, parameter) => $"{parameter.propertyName}: {parameter.materialName}", isCompatibleWithTarget: (oci) => IsCompatibleWithTarget(ItemInfo.RowItemType.FloatProperty) ); + + //Projector property + TimelineCompatibility.AddInterpolableModelDynamic( + owner: "MaterialEditor", + id: "projectorFloatProperty", + name: "Projector Property", + interpolateBefore: (oci, parameter, leftValue, rightValue, factor) => SetProjectorProperty(parameter.GetGameObject(oci), parameter.projectorName, parameter.property, Mathf.LerpUnclamped(leftValue, rightValue, factor)), + interpolateAfter: null, + getValue: (oci, parameter) => parameter.GetValue(oci), + readValueFromXml: (parameter, node) => XmlConvert.ToSingle(node.Attributes["value"].Value), + writeValueToXml: (parameter, writer, value) => writer.WriteAttributeString("value", value.ToString()), + getParameter: GetProjectorInfoParameter, + readParameterFromXml: ReadProjectorInfoXml, + writeParameterToXml: WriteProjectorInfoXml, + checkIntegrity: (oci, parameter, leftValue, rightValue) => true, + getFinalName: (currentName, oci, parameter) => $"{parameter.projectorName}: {parameter.property}", + isCompatibleWithTarget: (oci) => IsCompatibleWithProjectorTarget() + ); } private static MaterialInfo GetMaterialInfoParameter(ObjectCtrlInfo oci) @@ -266,6 +285,22 @@ private static MaterialInfo ReadMaterialInfoXml(ObjectCtrlInfo oci, XmlNode node return new MaterialInfo(node.Attributes["gameObjectPath"].Value, node.Attributes["materialName"].Value, node.Attributes["propertyName"].Value, node.Attributes["rendererName"].Value); } + private static ProjectorInfo GetProjectorInfoParameter(ObjectCtrlInfo oci) + { + return new ProjectorInfo(selectedProjectorInterpolable.GameObject.GetFullPath(), selectedProjectorInterpolable.ProjectorName, selectedProjectorInterpolable.Property); + } + + private static void WriteProjectorInfoXml(ObjectCtrlInfo oci, XmlTextWriter writer, ProjectorInfo parameter) + { + writer.WriteAttributeString("gameObjectPath", parameter.gameObjectPath); + writer.WriteAttributeString("projectorName", parameter.projectorName); + writer.WriteAttributeString("property", parameter.property.ToString()); + } + private static ProjectorInfo ReadProjectorInfoXml(ObjectCtrlInfo oci, XmlNode node) + { + return new ProjectorInfo(node.Attributes["gameObjectPath"].Value, node.Attributes["projectorName"].Value, (ProjectorProperties)Enum.Parse(typeof(ProjectorProperties), node.Attributes["property"].Value)); + } + private static bool CheckIntegrity(ObjectCtrlInfo oci, MaterialInfo parameter, object leftValue, object rightValue, ItemInfo.RowItemType rowType) { if (oci != null && parameter != null) @@ -291,6 +326,13 @@ private static bool IsCompatibleWithTarget(ItemInfo.RowItemType rowtype) return false; } + private static bool IsCompatibleWithProjectorTarget() + { + if (selectedProjectorInterpolable != null && !selectedProjectorInterpolable.ProjectorName.IsNullOrEmpty()) + return true; + return false; + } + private class MaterialInfo { public string gameObjectPath; @@ -343,6 +385,9 @@ public Material GetMaterial(ObjectCtrlInfo oci) foreach (var mat in GetMaterials(go, rend)) if (mat.NameFormatted() == materialName) return mat; + foreach (var projector in GetProjectorList(go)) + if (projector.material.NameFormatted() == materialName) + return projector.material; return null; } @@ -367,12 +412,81 @@ public bool CheckIntegrity(ItemInfo.RowItemType rowType) return true; } + public override int GetHashCode() + { + return this._hashCode; + } + } + + private class ProjectorInfo + { + public string gameObjectPath; + public string projectorName; + public ProjectorProperties property; + private readonly int _hashCode; + + public ProjectorInfo(string gameObjectPath, string projectorName, ProjectorProperties property) + { + this.gameObjectPath = gameObjectPath; + this.projectorName = projectorName; + this.property = property; + + unchecked + { + int hash = 17; + hash = hash * 31 + this.projectorName.GetHashCode(); + hash = hash * 31 + this.property.GetHashCode(); + this._hashCode = hash * 31 + this.gameObjectPath.GetHashCode(); + } + } + + public GameObject GetGameObject(ObjectCtrlInfo oci) + { + if (oci is OCIItem ociItem) + return ociItem.objectItem; + else if (oci is OCIChar oCIChar) + //Characters can reference multiple game objects depending on if the edits is on the body, clothes, accessories, etc. + //This makes sure we get the right one. This works because characters are always stored under a unique name within the scene, unlike objects + return oCIChar.charInfo.transform.Find(gameObjectPath).gameObject; + return null; + } + + public float GetValue(ObjectCtrlInfo oci) + { + Projector projector = null; + var go = GetGameObject(oci); + foreach (var _projector in GetProjectorList(go)) + if (_projector.NameFormatted() == projectorName) + { + projector = _projector; + break; + } + + if (projector == null) return 0; + + if (property == ProjectorProperties.FarClipPlane) + return projector.farClipPlane; + else if (property == ProjectorProperties.NearClipPlane) + return projector.nearClipPlane; + else if (property == ProjectorProperties.FieldOfView) + return projector.fieldOfView; + else if (property == ProjectorProperties.AspectRatio) + return projector.aspectRatio; + else if (property == ProjectorProperties.Orthographic) + return System.Convert.ToSingle(projector.orthographic); + else if (property == ProjectorProperties.OrthographicSize) + return projector.orthographicSize; + + return 0; + } + public override int GetHashCode() { return this._hashCode; } #endif } + private static System.Type GetKkapiType() { #if KK diff --git a/src/MaterialEditor.Core/Core.MaterialEditor.CharaController.cs b/src/MaterialEditor.Core/Core.MaterialEditor.CharaController.cs index 03f9b419..de29ae8b 100644 --- a/src/MaterialEditor.Core/Core.MaterialEditor.CharaController.cs +++ b/src/MaterialEditor.Core/Core.MaterialEditor.CharaController.cs @@ -30,6 +30,7 @@ namespace KK_Plugins.MaterialEditor public class MaterialEditorCharaController : CharaCustomFunctionController { private readonly List RendererPropertyList = new List(); + private readonly List ProjectorPropertyList = new List(); private readonly List MaterialFloatPropertyList = new List(); private readonly List MaterialColorPropertyList = new List(); private readonly List MaterialKeywordPropertyList = new List(); @@ -80,6 +81,11 @@ protected override void OnCardBeingSaved(GameMode currentGameMode) else data.data.Add(nameof(RendererPropertyList), null); + if (ProjectorPropertyList.Count > 0) + data.data.Add(nameof(ProjectorPropertyList), MessagePackSerializer.Serialize(ProjectorPropertyList)); + else + data.data.Add(nameof(ProjectorPropertyList), null); + if (MaterialFloatPropertyList.Count > 0) data.data.Add(nameof(MaterialFloatPropertyList), MessagePackSerializer.Serialize(MaterialFloatPropertyList)); else @@ -164,6 +170,7 @@ protected override void OnCoordinateBeingSaved(ChaFileCoordinate coordinate) { var coordinateRendererPropertyList = RendererPropertyList.Where(x => x.CoordinateIndex == CurrentCoordinateIndex && x.ObjectType != ObjectType.Hair && x.ObjectType != ObjectType.Character).ToList(); + var coordinateProjectorPropertyList = ProjectorPropertyList.Where(x => x.CoordinateIndex == CurrentCoordinateIndex && x.ObjectType != ObjectType.Hair && x.ObjectType != ObjectType.Character).ToList(); var coordinateMaterialFloatPropertyList = MaterialFloatPropertyList.Where(x => x.CoordinateIndex == CurrentCoordinateIndex && x.ObjectType != ObjectType.Hair && x.ObjectType != ObjectType.Character).ToList(); var coordinateMaterialKeywordPropertyList = MaterialKeywordPropertyList.Where(x => x.CoordinateIndex == CurrentCoordinateIndex && x.ObjectType != ObjectType.Hair && x.ObjectType != ObjectType.Character).ToList(); @@ -194,6 +201,11 @@ protected override void OnCoordinateBeingSaved(ChaFileCoordinate coordinate) else data.data.Add(nameof(RendererPropertyList), null); + if (coordinateProjectorPropertyList.Count > 0) + data.data.Add(nameof(ProjectorPropertyList), MessagePackSerializer.Serialize(coordinateProjectorPropertyList)); + else + data.data.Add(nameof(ProjectorPropertyList), null); + if (coordinateMaterialFloatPropertyList.Count > 0) data.data.Add(nameof(MaterialFloatPropertyList), MessagePackSerializer.Serialize(coordinateMaterialFloatPropertyList)); else @@ -258,6 +270,7 @@ private void LoadCharacterExtSaveData() if (loadFlags == null) { RendererPropertyList.Clear(); + ProjectorPropertyList.Clear(); MaterialFloatPropertyList.Clear(); MaterialKeywordPropertyList.Clear(); MaterialColorPropertyList.Clear(); @@ -275,6 +288,7 @@ private void LoadCharacterExtSaveData() if (loadFlags.Face || loadFlags.Body) { RendererPropertyList.RemoveAll(x => x.ObjectType == ObjectType.Character); + ProjectorPropertyList.RemoveAll(x => x.ObjectType == ObjectType.Character); MaterialFloatPropertyList.RemoveAll(x => x.ObjectType == ObjectType.Character); MaterialKeywordPropertyList.RemoveAll(x => x.ObjectType == ObjectType.Character); MaterialColorPropertyList.RemoveAll(x => x.ObjectType == ObjectType.Character); @@ -287,6 +301,7 @@ private void LoadCharacterExtSaveData() if (loadFlags.Clothes) { RendererPropertyList.RemoveAll(x => x.ObjectType == ObjectType.Clothing); + ProjectorPropertyList.RemoveAll(x => x.ObjectType == ObjectType.Clothing); MaterialFloatPropertyList.RemoveAll(x => x.ObjectType == ObjectType.Clothing); MaterialKeywordPropertyList.RemoveAll(x => x.ObjectType == ObjectType.Clothing); MaterialColorPropertyList.RemoveAll(x => x.ObjectType == ObjectType.Clothing); @@ -296,6 +311,7 @@ private void LoadCharacterExtSaveData() objectTypesToLoad.Add(ObjectType.Clothing); RendererPropertyList.RemoveAll(x => x.ObjectType == ObjectType.Accessory); + ProjectorPropertyList.RemoveAll(x => x.ObjectType == ObjectType.Accessory); MaterialFloatPropertyList.RemoveAll(x => x.ObjectType == ObjectType.Accessory); MaterialKeywordPropertyList.RemoveAll(x => x.ObjectType == ObjectType.Accessory); MaterialColorPropertyList.RemoveAll(x => x.ObjectType == ObjectType.Accessory); @@ -307,6 +323,7 @@ private void LoadCharacterExtSaveData() if (loadFlags.Hair) { RendererPropertyList.RemoveAll(x => x.ObjectType == ObjectType.Hair); + ProjectorPropertyList.RemoveAll(x => x.ObjectType == ObjectType.Hair); MaterialFloatPropertyList.RemoveAll(x => x.ObjectType == ObjectType.Hair); MaterialKeywordPropertyList.RemoveAll(x => x.ObjectType == ObjectType.Hair); MaterialColorPropertyList.RemoveAll(x => x.ObjectType == ObjectType.Hair); @@ -368,6 +385,18 @@ private void LoadCharacterExtSaveData() } } + if (data.data.TryGetValue(nameof(ProjectorPropertyList), out var projectorProperties) && projectorProperties != null) + { + var properties = MessagePackSerializer.Deserialize>((byte[])projectorProperties); + for (var i = 0; i < properties.Count; i++) + { + var loadedProperty = properties[i]; + int coordinateIndex = loadedProperty.ObjectType == ObjectType.Character ? 0 : loadedProperty.CoordinateIndex; + if (objectTypesToLoad.Contains(loadedProperty.ObjectType)) + ProjectorPropertyList.Add(new ProjectorProperty(loadedProperty.ObjectType, coordinateIndex, loadedProperty.Slot, loadedProperty.ProjectorName, loadedProperty.Property, loadedProperty.Value, loadedProperty.ValueOriginal)); + } + } + if (data.data.TryGetValue(nameof(MaterialFloatPropertyList), out var materialFloatProperties) && materialFloatProperties != null) { var properties = MessagePackSerializer.Deserialize>((byte[])materialFloatProperties); @@ -692,6 +721,16 @@ public IEnumerator LoadData(bool clothes, bool accessories, bool hair) SetTextureOffset(go, property.MaterialName, property.Property, property.Offset); SetTextureScale(go, property.MaterialName, property.Property, property.Scale); } + for (var i = 0; i < ProjectorPropertyList.Count; i++) + { + var property = ProjectorPropertyList[i]; + if (property.ObjectType == ObjectType.Clothing && !clothes) continue; + if (property.ObjectType == ObjectType.Accessory && !accessories) continue; + if (property.ObjectType == ObjectType.Hair && !hair) continue; + if ((property.ObjectType == ObjectType.Clothing || property.ObjectType == ObjectType.Accessory) && property.CoordinateIndex != CurrentCoordinateIndex) continue; + + MaterialAPI.SetProjectorProperty(FindGameObject(property.ObjectType, property.Slot), property.ProjectorName, property.Property, float.Parse(property.Value)); + } #if KK || EC || KKS @@ -1192,6 +1231,10 @@ public void MaterialCopyEdits(int slot, ObjectType objectType, Material material else CopyData.MaterialTexturePropertyList.Add(new CopyContainer.MaterialTextureProperty(materialTextureProperty.Property, null, materialTextureProperty.Offset, materialTextureProperty.Scale)); } + if(GetProjectorList(objectType, go).FirstOrDefault(x => x.material == material) != null) + foreach (var projectorProperty in ProjectorPropertyList.Where(x => x.ObjectType == objectType && x.CoordinateIndex == GetCoordinateIndex(objectType) && x.Slot == slot && x.ProjectorName == material.NameFormatted())) + CopyData.ProjectorPropertyList.Add(new CopyContainer.ProjectorProperty(projectorProperty.Property, float.Parse(projectorProperty.Value))); + } /// /// Paste any edits for the specified object @@ -1237,6 +1280,14 @@ public void MaterialPasteEdits(int slot, ObjectType objectType, Material materia if (materialTextureProperty.Scale != null) SetMaterialTextureScale(slot, objectType, material, materialTextureProperty.Property, (Vector2)materialTextureProperty.Scale, go, setProperty); } + + var projector = GetProjectorList(objectType, go).FirstOrDefault(x => x.material == material); + if (projector != null) + for (var i = 0; i < CopyData.MaterialTexturePropertyList.Count; i++) + { + var projectorProperty = CopyData.ProjectorPropertyList[i]; + SetProjectorProperty(slot, objectType, projector, projectorProperty.Property, projectorProperty.Value, go, setProperty); + } } public void MaterialCopyRemove(int slot, ObjectType objectType, Material material, GameObject go) @@ -1283,6 +1334,109 @@ public void MaterialCopyRemove(int slot, ObjectType objectType, Material materia } #region Set, Get, Remove methods + /// + /// Get the original value of the saved projector property value or null if none is saved + /// + /// Slot of the clothing (0=tops, 1=bottoms, etc.), the hair (0=back, 1=front, etc.), or of the accessory. Ignored for other object types. + /// Projector being modified + /// Property of the projector + /// GameObject the projector belongs to + /// Saved projector property value + public float? GetProjectorPropertyValueOriginal(int slot, ObjectType objectType, Projector projector, ProjectorProperties property, GameObject go) + { + var valueOriginal = ProjectorPropertyList.FirstOrDefault(x => x.ObjectType == objectType && x.CoordinateIndex == GetCoordinateIndex(objectType) && x.Slot == slot && x.Property == property && x.ProjectorName == projector.NameFormatted())?.ValueOriginal; + if (valueOriginal.IsNullOrEmpty()) + return null; + return float.Parse(valueOriginal); + } + /// + /// Get the value of the saved projector property value or null if none is saved + /// + /// Slot of the clothing (0=tops, 1=bottoms, etc.), the hair (0=back, 1=front, etc.), or of the accessory. Ignored for other object types. + /// Projector being modified + /// Property of the projector + /// GameObject the projector belongs to + /// Saved projector property value + public float? GetProjectorPropertyValue(int slot, ObjectType objectType, Projector projector, ProjectorProperties property, GameObject go) + { + var valueOriginal = ProjectorPropertyList.FirstOrDefault(x => x.ObjectType == objectType && x.CoordinateIndex == GetCoordinateIndex(objectType) && x.Slot == slot && x.Property == property && x.ProjectorName == projector.NameFormatted())?.Value; + if (valueOriginal.IsNullOrEmpty()) + return null; + return float.Parse(valueOriginal); + } + /// + /// Remove the saved projector property value if one is saved and optionally also update the projector + /// + /// Slot of the clothing (0=tops, 1=bottoms, etc.), the hair (0=back, 1=front, etc.), or of the accessory. Ignored for other object types. + /// Projector being modified + /// Property of the projector + /// GameObject the projector belongs to + /// Whether to also apply the value to the projector + public void RemoveProjectorProperty(int slot, ObjectType objectType, Projector projector, ProjectorProperties property, GameObject go, bool setProperty = true) + { + if (setProperty) + { + var original = GetProjectorPropertyValueOriginal(slot, objectType, projector, property, go); + if (original != null) + MaterialAPI.SetProjectorProperty(go, projector.NameFormatted(), property, (float)original); + } + ProjectorPropertyList.RemoveAll(x => x.ObjectType == objectType && x.CoordinateIndex == GetCoordinateIndex(objectType) && x.Slot == slot && x.Property == property && x.ProjectorName == projector.NameFormatted()); + } + /// + /// Add a renderer property to be saved and loaded with the card and optionally also update the renderer. + /// + /// Slot of the clothing (0=tops, 1=bottoms, etc.), the hair (0=back, 1=front, etc.), or of the accessory. Ignored for other object types. + /// Renderer being modified + /// Property of the renderer + /// Value + /// GameObject the renderer belongs to + /// Whether to also apply the value to the renderer + public void SetProjectorProperty(int slot, ObjectType objectType, Projector projector, ProjectorProperties property, float value, GameObject go, bool setProperty = true) + { + ProjectorProperty projectorProperty = ProjectorPropertyList.FirstOrDefault(x => x.ObjectType == objectType && x.CoordinateIndex == GetCoordinateIndex(objectType) && x.Slot == slot && x.Property == property && x.ProjectorName == projector.NameFormatted()); + if (projectorProperty == null) + { + string valueOriginal = ""; + if (property == ProjectorProperties.FarClipPlane) + valueOriginal = projector.farClipPlane.ToString(CultureInfo.InvariantCulture); + else if (property == ProjectorProperties.NearClipPlane) + valueOriginal = projector.nearClipPlane.ToString(CultureInfo.InvariantCulture); + else if (property == ProjectorProperties.FieldOfView) + valueOriginal = projector.fieldOfView.ToString(CultureInfo.InvariantCulture); + else if (property == ProjectorProperties.AspectRatio) + valueOriginal = projector.aspectRatio.ToString(CultureInfo.InvariantCulture); + else if (property == ProjectorProperties.Orthographic) + valueOriginal = Convert.ToSingle(projector.orthographic).ToString(CultureInfo.InvariantCulture); + else if (property == ProjectorProperties.OrthographicSize) + valueOriginal = projector.orthographicSize.ToString(CultureInfo.InvariantCulture); + else if (property == ProjectorProperties.IgnoreCharaLayer) + valueOriginal = Convert.ToSingle(projector.ignoreLayers == (projector.ignoreLayers | (1 << 10))).ToString(CultureInfo.InvariantCulture); + else if (property == ProjectorProperties.IgnoreMapLayer) + valueOriginal = Convert.ToSingle(projector.ignoreLayers == (projector.ignoreLayers | (1 << 11))).ToString(CultureInfo.InvariantCulture); + + if (valueOriginal != "") + ProjectorPropertyList.Add(new ProjectorProperty(objectType, GetCoordinateIndex(objectType), slot, projector.NameFormatted(), property, value.ToString(CultureInfo.InvariantCulture), valueOriginal)); + } + else + { + if (value.ToString(CultureInfo.InvariantCulture) == projectorProperty.ValueOriginal) + RemoveProjectorProperty(slot, objectType, projector, property, go, false); + else + projectorProperty.Value = value.ToString(CultureInfo.InvariantCulture); + } + + if (setProperty) + MaterialAPI.SetProjectorProperty(go, projector.NameFormatted(), property, value); + } + public IEnumerable GetProjectorList(ObjectType objectType, GameObject gameObject) + { + //The body will never have a projector component attached + //And returning all components from children will return every projector on a character + if (objectType == ObjectType.Character) + return new List(); + return MaterialAPI.GetProjectorList(gameObject, true); + } + /// /// Add a renderer property to be saved and loaded with the card and optionally also update the renderer. /// @@ -2758,5 +2912,68 @@ public MaterialCopy(ObjectType objectType, int coordinateIndex, int slot, string MaterialCopyName = materialCopyName; } } + + /// + /// Data storage class for projector properties + /// + [Serializable] + [MessagePackObject] + public class ProjectorProperty + { + /// + /// Type of the object + /// + [Key("ObjectType")] + public ObjectType ObjectType; + /// + /// Coordinate index, always 0 except in Koikatsu + /// + [Key("CoordinateIndex")] + public int CoordinateIndex; + /// + /// Slot of the accessory, hair, or clothing + /// + [Key("Slot")] + public int Slot; + /// + /// Name of the projector + /// + [Key("ProjectorName")] + public string ProjectorName; + /// + /// Property type + /// + [Key("Property")] + public ProjectorProperties Property; + /// + /// Value + /// + [Key("Value")] + public string Value; + /// + /// Original value + /// + [Key("ValueOriginal")] + public string ValueOriginal; + + /// + /// Data storage class for projector properties + /// + /// ID of the item + /// Name of the projector + /// Property type + /// Value + /// Original + public ProjectorProperty(ObjectType objectType, int coordinateIndex, int slot, string projectorName, ProjectorProperties property, string value, string valueOriginal) + { + ObjectType = objectType; + CoordinateIndex = coordinateIndex; + Slot = slot; + ProjectorName = projectorName.Replace("(Instance)", "").Trim(); + Property = property; + Value = value; + ValueOriginal = valueOriginal; + } + } } } diff --git a/src/MaterialEditor.Core/Core.MaterialEditor.cs b/src/MaterialEditor.Core/Core.MaterialEditor.cs index 06640bd0..a850e92d 100644 --- a/src/MaterialEditor.Core/Core.MaterialEditor.cs +++ b/src/MaterialEditor.Core/Core.MaterialEditor.cs @@ -912,6 +912,9 @@ protected override void AssetLoadedHook(AssetLoadedContext context) ConvertNormalMaps(material); } } + var projectors = go.GetComponentsInChildren(); + foreach (var projector in projectors) + ReplaceShaders(projector.material); } else if (context.Asset is Material material) { diff --git a/src/Shared/Shared.Extensions.cs b/src/Shared/Shared.Extensions.cs index b054ee4b..514f42a6 100644 --- a/src/Shared/Shared.Extensions.cs +++ b/src/Shared/Shared.Extensions.cs @@ -24,6 +24,7 @@ public static void Randomize(this IList list) public static string NameFormatted(this GameObject go) => go == null ? "" : go.name.Replace("(Instance)", "").Replace(" Instance", "").Trim(); public static string NameFormatted(this Material go) => go == null ? "" : go.name.Replace("(Instance)", "").Replace(" Instance", "").Trim(); public static string NameFormatted(this Renderer go) => go == null ? "" : go.name.Replace("(Instance)", "").Replace(" Instance", "").Trim(); + public static string NameFormatted(this Projector go) => go == null ? "" : go.name.Replace("(Instance)", "").Replace(" Instance", "").Trim(); public static string NameFormatted(this Shader go) => go == null ? "" : go.name.Replace("(Instance)", "").Replace(" Instance", "").Trim(); public static string NameFormatted(this Mesh go) => go == null ? "" : go.name.Replace("(Instance)", "").Replace(" Instance", "").Trim(); ///