summaryrefslogtreecommitdiff
path: root/VRCSDK3Worlds/Assets/_PoiyomiShaders/Scripts/poi-tools
diff options
context:
space:
mode:
Diffstat (limited to 'VRCSDK3Worlds/Assets/_PoiyomiShaders/Scripts/poi-tools')
-rw-r--r--VRCSDK3Worlds/Assets/_PoiyomiShaders/Scripts/poi-tools/Editor.meta8
-rw-r--r--VRCSDK3Worlds/Assets/_PoiyomiShaders/Scripts/poi-tools/Editor/BakeToVertexColorsEditor.cs360
-rw-r--r--VRCSDK3Worlds/Assets/_PoiyomiShaders/Scripts/poi-tools/Editor/BakeToVertexColorsEditor.cs.meta11
-rw-r--r--VRCSDK3Worlds/Assets/_PoiyomiShaders/Scripts/poi-tools/Editor/PoiData.cs50
-rw-r--r--VRCSDK3Worlds/Assets/_PoiyomiShaders/Scripts/poi-tools/Editor/PoiData.cs.meta11
-rw-r--r--VRCSDK3Worlds/Assets/_PoiyomiShaders/Scripts/poi-tools/Editor/PoiHelpers.cs394
-rw-r--r--VRCSDK3Worlds/Assets/_PoiyomiShaders/Scripts/poi-tools/Editor/PoiHelpers.cs.meta11
-rw-r--r--VRCSDK3Worlds/Assets/_PoiyomiShaders/Scripts/poi-tools/Editor/TextureChannelPackerEditor.cs373
-rw-r--r--VRCSDK3Worlds/Assets/_PoiyomiShaders/Scripts/poi-tools/Editor/TextureChannelPackerEditor.cs.meta11
-rw-r--r--VRCSDK3Worlds/Assets/_PoiyomiShaders/Scripts/poi-tools/Resources.meta8
-rw-r--r--VRCSDK3Worlds/Assets/_PoiyomiShaders/Scripts/poi-tools/Resources/PoiTexturePacker.shader169
-rw-r--r--VRCSDK3Worlds/Assets/_PoiyomiShaders/Scripts/poi-tools/Resources/PoiTexturePacker.shader.meta9
-rw-r--r--VRCSDK3Worlds/Assets/_PoiyomiShaders/Scripts/poi-tools/Resources/PoiTextureUnpacker.shader156
-rw-r--r--VRCSDK3Worlds/Assets/_PoiyomiShaders/Scripts/poi-tools/Resources/PoiTextureUnpacker.shader.meta9
14 files changed, 1580 insertions, 0 deletions
diff --git a/VRCSDK3Worlds/Assets/_PoiyomiShaders/Scripts/poi-tools/Editor.meta b/VRCSDK3Worlds/Assets/_PoiyomiShaders/Scripts/poi-tools/Editor.meta
new file mode 100644
index 00000000..9ff74d2b
--- /dev/null
+++ b/VRCSDK3Worlds/Assets/_PoiyomiShaders/Scripts/poi-tools/Editor.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 748053328138b574fb97df5308de5e24
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/VRCSDK3Worlds/Assets/_PoiyomiShaders/Scripts/poi-tools/Editor/BakeToVertexColorsEditor.cs b/VRCSDK3Worlds/Assets/_PoiyomiShaders/Scripts/poi-tools/Editor/BakeToVertexColorsEditor.cs
new file mode 100644
index 00000000..cea014c1
--- /dev/null
+++ b/VRCSDK3Worlds/Assets/_PoiyomiShaders/Scripts/poi-tools/Editor/BakeToVertexColorsEditor.cs
@@ -0,0 +1,360 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using UnityEditor;
+using UnityEngine;
+
+
+namespace Poi
+{
+ public class BakeToVertexColorsEditor : EditorWindow
+ {
+ //Window
+ static readonly Vector2 MIN_WINDOW_SIZE = new Vector2(316, 210);
+
+ // Version
+ Version version = new Version(1, 2);
+ string SubTitle
+ {
+ get
+ {
+ if(string.IsNullOrWhiteSpace(_subTitle))
+ _subTitle = "by Pumkin - v" + version.ToString();
+ return _subTitle;
+ }
+ }
+
+
+ //Strings
+ const string log_prefix = "<color=blue>Poi:</color> "; //color is hex or name
+
+ const string bakedSuffix_normals = "baked_normals";
+ const string bakedSuffix_position = "baked_position";
+
+ const string bakesFolderName = "Baked";
+ const string defaultUnityAssetBakesFolder = "Default Unity Resources";
+
+ const string hint_bakeAverageNormals = "Use this if you want seamless outlines";
+ const string hint_bakeVertexPositions = "Use this if you want scrolling emission";
+
+ const string button_bakeAverageNormals = "Bake Averaged Normals";
+ const string button_bakeVertexPositions = "Bake Vertex Positions";
+
+ const string warning_noMeshesDetected =
+ "No meshes detected in selection. Make sure your object has a Skinned Mesh Renderer or a Mesh Renderer with a valid Mesh assigned";
+
+ //Properties
+ static GameObject Selection
+ {
+ get => _selection;
+ set => _selection = value;
+ }
+
+ [MenuItem("Poi/Tools/Vertex Color Baker", priority = 11)]
+ public static void ShowWindow()
+ {
+ //Show existing window instance. If one doesn't exist, make one.
+ EditorWindow editorWindow = GetWindow(typeof(BakeToVertexColorsEditor));
+ editorWindow.autoRepaintOnSceneChange = true;
+ editorWindow.minSize = MIN_WINDOW_SIZE;
+
+ editorWindow.Show();
+ editorWindow.titleContent = new GUIContent("Bake Colors");
+ }
+
+ void OnGUI()
+ {
+ EditorGUILayout.LabelField("Poi Vertex Color Baker", PoiStyles.TitleLabel);
+ EditorGUILayout.LabelField(SubTitle);
+
+ PoiHelpers.DrawLine();
+
+ EditorGUI.BeginChangeCheck();
+ GameObject obj = EditorGUILayout.ObjectField("Avatar", Selection, typeof(GameObject), true) as GameObject;
+ if(EditorGUI.EndChangeCheck())
+ Selection = obj;
+
+ PoiHelpers.DrawLine();
+
+ EditorGUI.BeginDisabledGroup(!Selection);
+ {
+ EditorGUILayout.HelpBox(hint_bakeAverageNormals, MessageType.Info);
+ if(GUILayout.Button(button_bakeAverageNormals))
+ {
+ var meshes = GetAllMeshInfos(Selection);
+ if(meshes == null || meshes.Length == 0)
+ Debug.LogWarning(log_prefix + warning_noMeshesDetected);
+ else
+ BakeAveragedNormalsToColors(meshes);
+ }
+
+ PoiHelpers.DrawLine(true, false);
+ EditorGUILayout.HelpBox(hint_bakeVertexPositions, MessageType.Info);
+ if(GUILayout.Button(button_bakeVertexPositions))
+ {
+ var meshes = GetAllMeshInfos(Selection);
+ if(meshes == null || meshes.Length == 0)
+ Debug.LogWarning(log_prefix + warning_noMeshesDetected);
+ else
+ BakePositionsToColors(meshes);
+ }
+ }
+ EditorGUI.EndDisabledGroup();
+ }
+
+ /// <summary>
+ /// Saves a mesh in the same folder as the original asset
+ /// </summary>
+ /// <param name="mesh"></param>
+ /// <param name="newName">The new name of the mesh</param>
+ /// <returns>Returns the newly created mesh asset</returns>
+ static Mesh SaveMeshAsset(Mesh mesh, string newName)
+ {
+ string assetPath = AssetDatabase.GetAssetPath(mesh);
+
+ if(string.IsNullOrWhiteSpace(assetPath))
+ {
+ Debug.LogWarning(log_prefix + "Invalid asset path for " + mesh.name);
+ return null;
+ }
+
+ //Figure out folder name
+ string bakesDir = $"{Path.GetDirectoryName(assetPath)}";
+
+ //Handle default assets
+ if(bakesDir.StartsWith("Library"))
+ bakesDir = $"Assets\\{defaultUnityAssetBakesFolder}";
+
+ if(!bakesDir.EndsWith(bakesFolderName))
+ bakesDir += $"\\{bakesFolderName}";
+
+ if(!assetPath.Contains('.'))
+ assetPath += '\\';
+
+ PoiHelpers.EnsurePathExistsInAssets(bakesDir);
+
+ //Generate path
+ string pathNoExt = Path.Combine(bakesDir, newName);
+ string newPath = AssetDatabase.GenerateUniqueAssetPath($"{pathNoExt}.mesh");
+
+ //Save mesh, load it back, assign to renderer
+ Mesh newMesh = Instantiate(mesh);
+ AssetDatabase.CreateAsset(newMesh, newPath);
+
+ newMesh = AssetDatabase.LoadAssetAtPath<Mesh>(newPath);
+
+ if(newMesh == null)
+ {
+ Debug.Log(log_prefix + "Failed to load saved mesh");
+ return null;
+ }
+
+ EditorGUIUtility.PingObject(newMesh);
+ return newMesh;
+ }
+
+ /// <summary>
+ /// Sets the sharedMesh of a Skinned Mesh Renderer or Mesh Filter attached to a Mesh Renderer
+ /// </summary>
+ /// <param name="render"></param>
+ /// <param name="mesh"></param>
+ /// <returns></returns>
+ static bool SetRendererSharedMesh(Renderer render, Mesh mesh)
+ {
+ if(render is SkinnedMeshRenderer smr)
+ smr.sharedMesh = mesh;
+ else if(render is MeshRenderer mr)
+ {
+ var filter = mr.gameObject.GetComponent<MeshFilter>();
+ filter.sharedMesh = mesh;
+ }
+ else
+ return false;
+ return true;
+ }
+
+ static MeshInfo[] GetAllMeshInfos(GameObject obj)
+ {
+ return GetAllMeshInfos(obj?.GetComponentsInChildren<Renderer>(true));
+ }
+
+ static MeshInfo[] GetAllMeshInfos(params Renderer[] renderers)
+ {
+ var infos = renderers?.Select(ren =>
+ {
+ MeshInfo info = new MeshInfo();
+ if(ren is SkinnedMeshRenderer smr)
+ {
+ Mesh bakedMesh = new Mesh();
+ Transform tr = smr.gameObject.transform;
+ Quaternion origRot = tr.localRotation;
+ Vector3 origScale = tr.localScale;
+
+ tr.localRotation = Quaternion.identity;
+ tr.localScale = Vector3.one;
+
+ smr.BakeMesh(bakedMesh);
+
+ tr.localRotation = origRot;
+ tr.localScale = origScale;
+
+ info.sharedMesh = smr.sharedMesh;
+ info.bakedVertices = bakedMesh?.vertices;
+ info.bakedNormals = bakedMesh?.normals;
+ info.ownerRenderer = smr;
+ if(!info.sharedMesh)
+ Debug.LogWarning(log_prefix + $"Skinned Mesh Renderer at <b>{info.ownerRenderer.gameObject.name}</b> doesn't have a valid mesh");
+ }
+ else if(ren is MeshRenderer mr)
+ {
+ info.sharedMesh = mr.GetComponent<MeshFilter>()?.sharedMesh;
+ info.bakedVertices = info.sharedMesh?.vertices;
+ info.bakedNormals = info.sharedMesh?.normals;
+ info.ownerRenderer = mr;
+ if(!info.sharedMesh)
+ Debug.LogWarning(log_prefix + $"Mesh renderer at <b>{info.ownerRenderer.gameObject.name}</b> doesn't have a mesh filter with a valid mesh");
+ }
+ return info;
+ }).ToArray();
+
+ return infos;
+ }
+
+ static void BakePositionsToColors(MeshInfo[] meshInfos)
+ {
+ var queue = new Dictionary<MeshInfo, Mesh>();
+ try
+ {
+ AssetDatabase.StartAssetEditing();
+ foreach(var meshInfo in meshInfos)
+ {
+ if(!meshInfo.sharedMesh)
+ continue;
+
+ Vector3[] verts = meshInfo.bakedVertices; //accessing mesh.vertices on every iteration is very slow
+ Color[] colors = new Color[verts.Length];
+ for(int i = 0; i < verts.Length; i++)
+ colors[i] = new Color(verts[i].x, verts[i].y, verts[i].z);
+ meshInfo.sharedMesh.colors = colors;
+
+ //Create new mesh asset and add it to queue
+ string name = PoiHelpers.AddSuffix(meshInfo.ownerRenderer.gameObject.name, bakedSuffix_position);
+ Mesh newMesh = SaveMeshAsset(meshInfo.sharedMesh, name);
+ if(newMesh)
+ queue.Add(meshInfo, newMesh);
+ }
+ }
+ catch(Exception ex)
+ {
+ Debug.LogException(ex);
+ }
+ finally
+ {
+ AssetDatabase.StopAssetEditing();
+ }
+
+ //After all meshes are imported assign the meshes
+ foreach(var kv in queue)
+ {
+ SetRendererSharedMesh(kv.Key.ownerRenderer, kv.Value);
+ }
+ }
+
+ static void BakeAveragedNormalsToColors(params MeshInfo[] infos)
+ {
+ var queue = new Dictionary<MeshInfo, Mesh>();
+ try
+ {
+ AssetDatabase.StartAssetEditing();
+ foreach(var meshInfo in infos)
+ {
+ if(!meshInfo.sharedMesh)
+ continue;
+
+ Vector3[] verts = meshInfo.bakedVertices;
+ Vector3[] normals = meshInfo.bakedNormals;
+ VertexInfo[] vertInfo = new VertexInfo[verts.Length];
+ for(int i = 0; i < verts.Length; i++)
+ {
+ vertInfo[i] = new VertexInfo()
+ {
+ vertex = verts[i],
+ originalIndex = i,
+ normal = normals[i]
+ };
+ }
+ var groups = vertInfo.GroupBy(x => x.vertex);
+ VertexInfo[] processedVertInfo = new VertexInfo[vertInfo.Length];
+ int index = 0;
+ foreach(IGrouping<Vector3, VertexInfo> group in groups)
+ {
+ Vector3 avgNormal = Vector3.zero;
+ foreach(VertexInfo item in group)
+ avgNormal += item.normal;
+
+ avgNormal /= group.Count();
+ foreach(VertexInfo item in group)
+ {
+ processedVertInfo[index] = new VertexInfo()
+ {
+ vertex = item.vertex,
+ originalIndex = item.originalIndex,
+ normal = item.normal,
+ averagedNormal = avgNormal
+ };
+ index++;
+ }
+ }
+ Color[] colors = new Color[verts.Length];
+ for(int i = 0; i < processedVertInfo.Length; i++)
+ {
+ VertexInfo info = processedVertInfo[i];
+
+ int origIndex = info.originalIndex;
+ Vector3 normal = info.averagedNormal;
+ Color normColor = new Color(normal.x, normal.y, normal.z, 1);
+ colors[origIndex] = normColor;
+ }
+ meshInfo.sharedMesh.colors = colors;
+
+ string name = PoiHelpers.AddSuffix(meshInfo.ownerRenderer.gameObject.name, bakedSuffix_normals);
+ Mesh newMesh = SaveMeshAsset(meshInfo.sharedMesh, name);
+ if(newMesh)
+ queue.Add(meshInfo, newMesh);
+ }
+ }
+ catch(Exception ex)
+ {
+ Debug.LogException(ex);
+ }
+ finally
+ {
+ AssetDatabase.StopAssetEditing();
+ }
+
+ //Assign all new meshes to their renderers
+ foreach(var kv in queue)
+ SetRendererSharedMesh(kv.Key.ownerRenderer, kv.Value);
+ }
+
+ struct MeshInfo
+ {
+ public Renderer ownerRenderer;
+ public Mesh sharedMesh;
+ public Vector3[] bakedVertices;
+ public Vector3[] bakedNormals;
+ }
+
+ struct VertexInfo
+ {
+ public Vector3 vertex;
+ public int originalIndex;
+ public Vector3 normal;
+ public Vector3 averagedNormal;
+ }
+
+ static GameObject _selection;
+ private string _subTitle;
+ }
+} \ No newline at end of file
diff --git a/VRCSDK3Worlds/Assets/_PoiyomiShaders/Scripts/poi-tools/Editor/BakeToVertexColorsEditor.cs.meta b/VRCSDK3Worlds/Assets/_PoiyomiShaders/Scripts/poi-tools/Editor/BakeToVertexColorsEditor.cs.meta
new file mode 100644
index 00000000..d603b15a
--- /dev/null
+++ b/VRCSDK3Worlds/Assets/_PoiyomiShaders/Scripts/poi-tools/Editor/BakeToVertexColorsEditor.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 3f398d68f8c01b54485d2a04a13c958b
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/VRCSDK3Worlds/Assets/_PoiyomiShaders/Scripts/poi-tools/Editor/PoiData.cs b/VRCSDK3Worlds/Assets/_PoiyomiShaders/Scripts/poi-tools/Editor/PoiData.cs
new file mode 100644
index 00000000..6c81040c
--- /dev/null
+++ b/VRCSDK3Worlds/Assets/_PoiyomiShaders/Scripts/poi-tools/Editor/PoiData.cs
@@ -0,0 +1,50 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using UnityEditor;
+using UnityEngine;
+
+namespace Poi
+{
+ internal static class PoiPaths
+ {
+ public const string defaultResourcesPath = "Library/unity default resources/";
+ }
+
+ internal static class PoiStyles
+ {
+ public static GUIStyle BigButton
+ {
+ get
+ {
+ if(_bigButton == null)
+ _bigButton= new GUIStyle("button")
+ {
+ fixedHeight = 18 * EditorGUIUtility.pixelsPerPoint
+ };
+ return _bigButton;
+ }
+ }
+
+ public static GUIStyle TitleLabel
+ {
+ get
+ {
+ if(_titleLabel == null)
+ _titleLabel = new GUIStyle(EditorStyles.label)
+ {
+ fontSize = 15,
+ stretchHeight = true,
+ clipping = TextClipping.Overflow
+ };
+
+ return _titleLabel;
+ }
+ }
+
+ static GUIStyle _bigButton;
+ static GUIStyle _titleLabel;
+ }
+}
diff --git a/VRCSDK3Worlds/Assets/_PoiyomiShaders/Scripts/poi-tools/Editor/PoiData.cs.meta b/VRCSDK3Worlds/Assets/_PoiyomiShaders/Scripts/poi-tools/Editor/PoiData.cs.meta
new file mode 100644
index 00000000..2fae90dc
--- /dev/null
+++ b/VRCSDK3Worlds/Assets/_PoiyomiShaders/Scripts/poi-tools/Editor/PoiData.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 8f58036675b906e4797a5c394781b2a0
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/VRCSDK3Worlds/Assets/_PoiyomiShaders/Scripts/poi-tools/Editor/PoiHelpers.cs b/VRCSDK3Worlds/Assets/_PoiyomiShaders/Scripts/poi-tools/Editor/PoiHelpers.cs
new file mode 100644
index 00000000..787abb8b
--- /dev/null
+++ b/VRCSDK3Worlds/Assets/_PoiyomiShaders/Scripts/poi-tools/Editor/PoiHelpers.cs
@@ -0,0 +1,394 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using UnityEditor;
+using UnityEngine;
+
+namespace Poi
+{
+ static class PoiHelpers
+ {
+ static readonly string suffixSeparator = "_";
+
+ /// <summary>
+ /// Changes a path in Assets to an absolute windows path
+ /// </summary>
+ /// <param name="localPath"></param>
+ /// <returns></returns>
+ public static string LocalAssetsPathToAbsolutePath(string localPath)
+ {
+ localPath = NormalizePathSlashes(localPath);
+ const string assets = "Assets/";
+ if(localPath.StartsWith(assets))
+ {
+ localPath = localPath.Remove(0, assets.Length);
+ localPath = $"{Application.dataPath}/{localPath}";
+ }
+ return localPath;
+ }
+
+ /// <summary>
+ /// Replaces all forward slashes \ with back slashes /
+ /// </summary>
+ /// <param name="path"></param>
+ /// <returns></returns>
+ public static string NormalizePathSlashes(string path)
+ {
+ if(!string.IsNullOrEmpty(path))
+ path = path.Replace('\\', '/');
+ return path;
+ }
+
+ /// <summary>
+ /// Ensures directory exists inside the assets folder
+ /// </summary>
+ /// <param name="assetPath"></param>
+ public static void EnsurePathExistsInAssets(string assetPath)
+ {
+ Directory.CreateDirectory(LocalAssetsPathToAbsolutePath(assetPath));
+ }
+
+ /// <summary>
+ /// Adds a suffix to the end of the string then returns it
+ /// </summary>
+ /// <param name="str"></param>
+ /// <param name="suffixes"></param>
+ /// <returns></returns>
+ public static string AddSuffix(string str, params string[] suffixes)
+ {
+ bool ignoreSeparatorOnce = string.IsNullOrWhiteSpace(str);
+ StringBuilder sb = new StringBuilder(str);
+ foreach(var suff in suffixes)
+ {
+ if(ignoreSeparatorOnce)
+ {
+ sb.Append(suff);
+ ignoreSeparatorOnce = false;
+ continue;
+ }
+ sb.Append(suffixSeparator + suff);
+ }
+ return sb.ToString();
+ }
+
+ /// <summary>
+ /// Removes suffix from the end of string then returns it
+ /// </summary>
+ /// <param name="str"></param>
+ /// <param name="suffixes">Each to be removed in order</param>
+ /// <returns></returns>
+ public static string RemoveSuffix(string str, string[] suffixes)
+ {
+ var suffixList = suffixes.ToList();
+ suffixList.Remove(str);
+
+ while(suffixList.Any(str.EndsWith))
+ foreach(string sfx in suffixList)
+ {
+ string s = suffixSeparator + sfx;
+ if(!str.EndsWith(sfx))
+ continue;
+
+ int idx = str.LastIndexOf(s, StringComparison.Ordinal);
+ if(idx != -1)
+ str = str.Remove(idx, s.Length);
+ }
+ return str;
+ }
+
+ /// <summary>
+ /// Draws a GUI ilne
+ /// </summary>
+ /// <param name="spaceBefore"></param>
+ /// <param name="spaceAfter"></param>
+ internal static void DrawLine(bool spaceBefore = true, bool spaceAfter = true)
+ {
+ float spaceHeight = 3f;
+ if(spaceBefore)
+ GUILayout.Space(spaceHeight);
+
+ Rect rect = EditorGUILayout.GetControlRect(false, 1);
+ rect.height = 1;
+ EditorGUI.DrawRect(rect, new Color(0.5f, 0.5f, 0.5f, 1));
+
+ if(spaceAfter)
+ GUILayout.Space(spaceHeight);
+ }
+
+ /// <summary>
+ /// Destroys an object with DestroyImmediate in object mode and Destroy in play mode
+ /// </summary>
+ /// <param name="obj"></param>
+ internal static void DestroyAppropriate(UnityEngine.Object obj)
+ {
+ if(EditorApplication.isPlaying)
+ UnityEngine.Object.Destroy(obj);
+ else
+ UnityEngine.Object.DestroyImmediate(obj);
+ }
+
+ /// <summary>
+ /// Changes path from full windows path to a local path in the Assets folder
+ /// </summary>
+ /// <param name="path"></param>
+ /// <returns>Path starting with Assets</returns>
+ internal static string AbsolutePathToLocalAssetsPath(string path)
+ {
+ if(path.StartsWith(Application.dataPath))
+ path = "Assets" + path.Substring(Application.dataPath.Length);
+ return path;
+ }
+
+ /// <summary>
+ /// Selects and highlights the asset in your unity Project tab
+ /// </summary>
+ /// <param name="path"></param>
+ internal static void PingAssetAtPath(string path)
+ {
+ var inst = AssetDatabase.LoadAssetAtPath<UnityEngine.Object>(path).GetInstanceID();
+ EditorGUIUtility.PingObject(inst);
+ }
+
+ internal static Vector2Int DrawResolutionPicker(Vector2Int size, ref bool linked, ref bool autoDetect, int[] presets = null, string[] presetNames = null)
+ {
+ EditorGUI.BeginDisabledGroup(autoDetect);
+ EditorGUILayout.BeginHorizontal();
+ {
+ EditorGUILayout.PrefixLabel("Size");
+
+ EditorGUI.BeginChangeCheck();
+ size.x = EditorGUILayout.IntField(size.x);
+ if(linked && EditorGUI.EndChangeCheck())
+ size.y = size.x;
+
+ EditorGUILayout.LabelField("x", GUILayout.MaxWidth(12));
+
+ EditorGUI.BeginChangeCheck();
+ size.y = EditorGUILayout.IntField(size.y);
+ if(linked && EditorGUI.EndChangeCheck())
+ size.x = size.y;
+
+ if(presets != null && presetNames != null)
+ {
+ EditorGUI.BeginChangeCheck();
+ int selectedPresetIndex = EditorGUILayout.Popup(GUIContent.none, -1, presetNames, GUILayout.MaxWidth(16));
+ if(EditorGUI.EndChangeCheck() && selectedPresetIndex != -1)
+ size = new Vector2Int(presets[selectedPresetIndex], presets[selectedPresetIndex]);
+ }
+
+ linked = GUILayout.Toggle(linked, "L", EditorStyles.miniButton, GUILayout.MaxWidth(16));
+ }
+ EditorGUILayout.EndHorizontal();
+
+ EditorGUI.EndDisabledGroup();
+
+ autoDetect = EditorGUILayout.Toggle("Auto detect", autoDetect);
+
+ return size;
+ }
+
+ /// <summary>
+ /// Gets the combined maximum width and height of the passed in textures
+ /// </summary>
+ /// <param name="textures"></param>
+ /// <returns></returns>
+ internal static Vector2Int GetMaxSizeFromTextures(params Texture2D[] textures)
+ {
+ var sizes = textures.Where(tex => tex).Select(tex => new Vector2Int(tex.width, tex.height)).ToArray();
+ if(sizes.Length == 0)
+ return default;
+
+ int maxW = sizes.Max(wh => wh.x);
+ int maxH = sizes.Max(wh => wh.y);
+ return new Vector2Int(maxW, maxH);
+ }
+
+ internal static Texture2D PackTextures(Vector2Int resolution, Texture2D red, Texture2D green, Texture2D blue, Texture2D alpha, bool invertRed, bool invertGreen, bool invertBlue, bool invertAlpha)
+ {
+ // Setup Material
+ var mat = new Material(PoiExtensions.PackerShader);
+
+ mat.SetTexture("_Red", red);
+ mat.SetTexture("_Green", green);
+ mat.SetTexture("_Blue", blue);
+ mat.SetTexture("_Alpha", alpha);
+
+ mat.SetInt("_Invert_Red", Convert.ToInt32(invertRed));
+ mat.SetInt("_Invert_Green", Convert.ToInt32(invertGreen));
+ mat.SetInt("_Invert_Blue", Convert.ToInt32(invertBlue));
+ mat.SetInt("_Invert_Alpha", Convert.ToInt32(invertAlpha));
+
+ // Create texture and render to it
+ var tex = new Texture2D(resolution.x, resolution.y);
+ tex.BakeMaterialToTexture(mat);
+
+ // Cleanup
+ DestroyAppropriate(mat);
+
+ return tex;
+ }
+
+ internal static Dictionary<string, Texture2D> UnpackTextureToChannels(Texture2D packedTexture, bool invert, Vector2Int resolution)
+ {
+ var channels = new Dictionary<string, Texture2D>
+ {
+ {PoiExtensions.PoiTextureChannel.Red.ToString().ToLower(),
+ new Texture2D(resolution.x, resolution.y, TextureFormat.RGB24, true)},
+ {PoiExtensions.PoiTextureChannel.Green.ToString().ToLower(),
+ new Texture2D(resolution.x, resolution.y, TextureFormat.RGB24, true)},
+ {PoiExtensions.PoiTextureChannel.Blue.ToString().ToLower(),
+ new Texture2D(resolution.x, resolution.y, TextureFormat.RGB24, true)},
+ {PoiExtensions.PoiTextureChannel.Alpha.ToString().ToLower(),
+ new Texture2D(resolution.x, resolution.y, TextureFormat.RGB24, true)}
+ };
+
+ var mat = new Material(PoiExtensions.UnpackerShader);
+ mat.SetTexture("_MainTex", packedTexture);
+ mat.SetInt("_Invert", Convert.ToInt32(invert));
+
+ for(int i = 0; i < 4; i++)
+ {
+ mat.SetFloat("_Mode", i);
+ channels.ElementAt(i).Value.BakeMaterialToTexture(mat);
+ }
+
+ return channels;
+ }
+
+ internal static void DrawWithLabelWidth(float width, Action action)
+ {
+ if(action == null)
+ return;
+ float old = EditorGUIUtility.labelWidth;
+ action.Invoke();
+ EditorGUIUtility.labelWidth = old;
+ }
+
+ internal static PoiExtensions.PoiTextureChannel DrawChannelSelector(PoiExtensions.PoiTextureChannel currentSelection, params string[] labels)
+ {
+ if(labels == null)
+ return PoiExtensions.PoiTextureChannel.RGBA;
+ return (PoiExtensions.PoiTextureChannel)GUILayout.SelectionGrid((int)currentSelection, labels, labels.Length);
+ }
+ }
+
+ internal static class PoiExtensions
+ {
+ public enum PoiTextureChannel { RGBA, Red, Green, Blue, Alpha }
+ public static Shader PackerShader
+ {
+ get
+ {
+ return Shader.Find("Hidden/Poi/TexturePacker");
+ }
+ }
+ public static Shader UnpackerShader
+ {
+ get
+ {
+ return Shader.Find("Hidden/Poi/TextureUnpacker");
+ }
+ }
+
+ internal static Texture2D GetChannelAsTexture(this Texture2D tex, PoiTextureChannel chan, bool invert = false, Vector2Int sizeOverride = default)
+ {
+ if(chan == PoiTextureChannel.RGBA)
+ return tex;
+
+ if(sizeOverride == default)
+ sizeOverride = new Vector2Int(tex.width, tex.height);
+
+ Material mat = new Material(UnpackerShader);
+ mat.SetFloat("_Mode", (int)chan - 1);
+ mat.SetInt("_Invert", Convert.ToInt32(invert));
+ mat.SetTexture("_MainTex", tex);
+
+ var newTex = new Texture2D(sizeOverride.x, sizeOverride.y, TextureFormat.RGB24, true);
+ newTex.name = chan.ToString();
+ newTex.BakeMaterialToTexture(mat);
+ newTex.Apply(false, false);
+
+ return newTex;
+ }
+
+ /// <summary>
+ /// Extension method that bakes a material to <paramref name="tex"/>
+ /// </summary>
+ /// <param name="tex">Texture to bake <paramref name="materialToBake"/> to</param>
+ /// <param name="materialToBake">Material to bake to <paramref name="tex"/></param>
+ internal static void BakeMaterialToTexture(this Texture2D tex, Material materialToBake)
+ {
+ var res = new Vector2Int(tex.width, tex.height);
+
+ RenderTexture renderTexture = RenderTexture.GetTemporary(res.x, res.y);
+ Graphics.Blit(null, renderTexture, materialToBake);
+
+ //transfer image from rendertexture to texture
+ RenderTexture.active = renderTexture;
+ tex.ReadPixels(new Rect(Vector2.zero, res), 0, 0);
+ tex.Apply(false, false);
+
+ //clean up variables
+ RenderTexture.active = null;
+ RenderTexture.ReleaseTemporary(renderTexture);
+ }
+
+ internal static void SaveTextureAsset(this Texture2D tex, string assetPath, bool overwrite)
+ {
+ var bytes = tex.EncodeToPNG();
+
+
+ // Ensure directory exists then convert path to local asset path
+ if(!assetPath.StartsWith("Assets", StringComparison.OrdinalIgnoreCase))
+ {
+ Directory.CreateDirectory(Path.GetDirectoryName(assetPath));
+ assetPath = PoiHelpers.AbsolutePathToLocalAssetsPath(assetPath);
+ }
+ else
+ {
+ string absolutePath = PoiHelpers.LocalAssetsPathToAbsolutePath(assetPath);
+ Directory.CreateDirectory(Path.GetDirectoryName(absolutePath));
+ }
+
+ if(AssetDatabase.LoadAssetAtPath<UnityEngine.Object>(assetPath) && !overwrite)
+ assetPath = AssetDatabase.GenerateUniqueAssetPath(assetPath);
+
+ File.WriteAllBytes(assetPath, bytes);
+ AssetDatabase.Refresh();
+ }
+
+ internal static Texture2D GetReadableTextureCopy(this Texture2D tex)
+ {
+ byte[] pix = tex.GetRawTextureData();
+ Texture2D finalTex = new Texture2D(tex.width, tex.height, tex.format, false);
+ finalTex.LoadRawTextureData(pix);
+ finalTex.Apply();
+ return finalTex;
+ }
+
+ /// <summary>
+ /// Rounds vector to closest power of two. Optionally, if above ceiling, square root down by one power of two
+ /// </summary>
+ /// <param name="vec"></param>
+ /// <param name="ceiling">Power of two ceiling. Will be rounded to power of two if not power of two already</param>
+ /// <returns></returns>
+ internal static Vector2Int ClosestPowerOfTwo(this Vector2Int vec, int? ceiling = null)
+ {
+ int x = Mathf.ClosestPowerOfTwo(vec.x);
+ int y = Mathf.ClosestPowerOfTwo(vec.y);
+
+ if(ceiling != null)
+ {
+ int ceil = Mathf.ClosestPowerOfTwo((int) ceiling);
+
+ x = Mathf.Clamp(x, x, ceil);
+ y = Mathf.Clamp(y, y, ceil);
+ }
+
+ return new Vector2Int(x, y);
+ }
+ }
+} \ No newline at end of file
diff --git a/VRCSDK3Worlds/Assets/_PoiyomiShaders/Scripts/poi-tools/Editor/PoiHelpers.cs.meta b/VRCSDK3Worlds/Assets/_PoiyomiShaders/Scripts/poi-tools/Editor/PoiHelpers.cs.meta
new file mode 100644
index 00000000..5326296d
--- /dev/null
+++ b/VRCSDK3Worlds/Assets/_PoiyomiShaders/Scripts/poi-tools/Editor/PoiHelpers.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 32406f186e960c04ab7448ec0b4ca0e0
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/VRCSDK3Worlds/Assets/_PoiyomiShaders/Scripts/poi-tools/Editor/TextureChannelPackerEditor.cs b/VRCSDK3Worlds/Assets/_PoiyomiShaders/Scripts/poi-tools/Editor/TextureChannelPackerEditor.cs
new file mode 100644
index 00000000..dbc9517c
--- /dev/null
+++ b/VRCSDK3Worlds/Assets/_PoiyomiShaders/Scripts/poi-tools/Editor/TextureChannelPackerEditor.cs
@@ -0,0 +1,373 @@
+using System;
+using System.Collections.Generic;
+using System.Drawing;
+using System.Linq;
+using UnityEditor;
+using UnityEngine;
+using TextureChannel = Poi.PoiExtensions.PoiTextureChannel;
+
+namespace Poi
+{
+ public class TextureChannelPackerEditor : EditorWindow
+ {
+ const string LOG_PREFIX = "<color=blue>Poi:</color> "; //color is hex or name
+ static readonly Vector2 MIN_WINDOW_SIZE = new Vector2(350, 500);
+ const int AUTO_SELECT_CEILING = 2048;
+
+ const string INVERT_LABEL = "Invert";
+ const string PACKED_TEXTURE_LABEL = "Texture";
+ const string RED_TEXTURE_LABEL = "Red";
+ const string GREEN_TEXTURE_LABEL = "Green";
+ const string BLUE_TEXTURE_LABEL = "Blue";
+ const string ALPHA_TEXTURE_LABEL = "Alpha";
+
+ float InvertToggleWidth
+ {
+ get
+ {
+ if(_invertLabelWidth == default)
+ _invertLabelWidth = EditorStyles.toggle.CalcSize(new GUIContent(INVERT_LABEL)).x;
+ return _invertLabelWidth;
+ }
+ }
+
+ // Default values
+ string savePath = "Assets/_ChannelPacker";
+ string packedName = "packed";
+ string unpackedName = "unpacked";
+
+ // Version
+ Version version = new Version(1, 2);
+ string SubTitle
+ {
+ get
+ {
+ if(string.IsNullOrWhiteSpace(_subTitle))
+ _subTitle = "by Pumkin - v" + version.ToString();
+ return _subTitle;
+ }
+ }
+
+ static EditorWindow Window
+ {
+ get
+ {
+ if(!_window)
+ _window = GetWindow<TextureChannelPackerEditor>();
+ return _window;
+ }
+ }
+
+ // Texture stuff
+ static int[] SizePresets { get; } = { 128, 256, 512, 1024, 2048, 4096 };
+ string[] SizePresetNames
+ {
+ get
+ {
+ if(_sizeNames == null)
+ _sizeNames = SizePresets.Select(i => i + " x " + i).ToArray();
+ return _sizeNames;
+ }
+ }
+
+ Vector2Int PackSize { get; set; } = new Vector2Int(1024, 1024);
+ Vector2Int UnpackSize { get; set; } = new Vector2Int(1024, 1024);
+
+ bool packSizeIsLinked = true;
+ bool unpackSizeIsLinked = true;
+
+ bool packSizeAutoSelect = true;
+ bool unpackSizeAutoSelect = true;
+
+ bool showChannelPicker = false;
+
+ TextureChannel redTexChan, blueTexChan, greenTexChan, alphaTexChan, unpackChan;
+
+ Texture2D packRed, packGreen, packBlue, packAlpha, unpackSource;
+
+ bool redInvert, greenInvert, blueInvert, alphaInvert, unpackInvert;
+
+ string[] ChannelLabels { get; } = { "All", "Red", "Green", "Blue", "Alpha" };
+
+ bool PackerShadersExist
+ {
+ get
+ {
+ bool everythingIsAlwaysFine = true;
+
+ if(!PoiExtensions.UnpackerShader)
+ {
+ Debug.LogWarning(LOG_PREFIX + "Unpacker shader is missing or invalid. Can't unpack textures.");
+ everythingIsAlwaysFine = false;
+ }
+
+ if(!PoiExtensions.PackerShader)
+ {
+ Debug.LogWarning(LOG_PREFIX + "Packer shader is missing or invalid. Can't pack textures.");
+ everythingIsAlwaysFine = false;
+ }
+
+ return everythingIsAlwaysFine;
+ }
+ }
+
+ // UI
+ enum Tab { Pack, Unpack }
+ int selectedTab = 0;
+ string[] TabNames
+ {
+ get
+ {
+ if(_tabNames == null)
+ _tabNames = Enum.GetNames(typeof(Tab));
+ return _tabNames;
+ }
+ }
+
+
+ [MenuItem("Poi/Tools/Texture Packer", priority = 0)]
+ public static void ShowWindow()
+ {
+ //Show existing window instance. If one doesn't exist, make one.
+ Window.autoRepaintOnSceneChange = true;
+ Window.minSize = MIN_WINDOW_SIZE;
+
+ Window.Show();
+ Window.titleContent = new GUIContent("Texture Packer");
+ }
+
+ #region Drawing GUI
+
+ void OnGUI()
+ {
+ EditorGUILayout.LabelField("Poi Texture Packer", PoiStyles.TitleLabel);
+ EditorGUILayout.LabelField(SubTitle);
+
+ PoiHelpers.DrawLine();
+
+ selectedTab = GUILayout.Toolbar(selectedTab, TabNames);
+
+ if(selectedTab == (int)Tab.Pack)
+ DrawPackUI();
+ else
+ DrawUnpackUI();
+ }
+
+ void DrawPackUI()
+ {
+ _scroll = EditorGUILayout.BeginScrollView(_scroll);
+ {
+ EditorGUI.BeginChangeCheck();
+ {
+ DrawTextureSelector(RED_TEXTURE_LABEL, ref packRed, ref redTexChan, ref redInvert);
+ DrawTextureSelector(GREEN_TEXTURE_LABEL, ref packGreen, ref greenTexChan, ref greenInvert);
+ DrawTextureSelector(BLUE_TEXTURE_LABEL, ref packBlue, ref blueTexChan, ref blueInvert);
+ DrawTextureSelector(ALPHA_TEXTURE_LABEL, ref packAlpha, ref alphaTexChan, ref alphaInvert);
+ }
+ if(EditorGUI.EndChangeCheck() && packSizeAutoSelect)
+ {
+ // Get biggest texture size from selections and make a selection in our sizes list
+ var tempSize = PoiHelpers.GetMaxSizeFromTextures(packRed, packGreen, packBlue, packAlpha);
+ if(tempSize != default)
+ PackSize = tempSize.ClosestPowerOfTwo(AUTO_SELECT_CEILING);
+ }
+ }
+ EditorGUILayout.EndScrollView();
+
+ DrawShowChannelPicker(ref showChannelPicker);
+
+ bool disabled = new bool[] { packRed, packGreen, packBlue, packAlpha }.Count(b => b) < 2;
+ EditorGUI.BeginDisabledGroup(disabled);
+ {
+ PackSize = DrawTextureSizeSettings(PackSize, ref packedName, ref packSizeIsLinked, ref packSizeAutoSelect);
+
+ if(GUILayout.Button("Pack", PoiStyles.BigButton))
+ DoPack();
+
+ EditorGUILayout.Space();
+ }
+ EditorGUI.EndDisabledGroup();
+ }
+
+ private void DrawShowChannelPicker(ref bool pickerValue)
+ {
+ EditorGUILayout.BeginHorizontal(EditorStyles.helpBox);
+ pickerValue = EditorGUILayout.ToggleLeft("Pick source channel", pickerValue);
+ EditorGUILayout.EndHorizontal();
+ }
+
+ void DrawUnpackUI()
+ {
+ _scroll = EditorGUILayout.BeginScrollView(_scroll);
+ {
+ EditorGUI.BeginChangeCheck();
+ {
+ DrawTextureSelector(PACKED_TEXTURE_LABEL, ref unpackSource, ref unpackChan, ref unpackInvert);
+ }
+ if(EditorGUI.EndChangeCheck() && unpackSizeAutoSelect)
+ {
+ // Get biggest texture size from selections and make a selection in our sizes list
+ var tempSize = PoiHelpers.GetMaxSizeFromTextures(unpackSource);
+ if(tempSize != default)
+ UnpackSize = tempSize.ClosestPowerOfTwo(AUTO_SELECT_CEILING);
+ }
+
+ DrawShowChannelPicker(ref showChannelPicker);
+ }
+ EditorGUILayout.EndScrollView();
+
+ EditorGUI.BeginDisabledGroup(!unpackSource);
+ {
+ UnpackSize = DrawTextureSizeSettings(UnpackSize, ref unpackedName, ref unpackSizeIsLinked, ref unpackSizeAutoSelect);
+
+ if(GUILayout.Button("Unpack", PoiStyles.BigButton))
+ DoUnpack(unpackChan);
+ }
+ EditorGUI.EndDisabledGroup();
+
+ EditorGUILayout.Space();
+ }
+
+ #endregion
+
+ #region Packing and Unpacking
+
+ void DoPack()
+ {
+ if(!PackerShadersExist)
+ return;
+
+ Texture2D red = packRed;
+ Texture2D green = packGreen;
+ Texture2D blue = packBlue;
+ Texture2D alpha = packAlpha;
+
+ if(showChannelPicker)
+ {
+ red = packRed.GetChannelAsTexture(redTexChan, unpackInvert);
+ green = packGreen.GetChannelAsTexture(greenTexChan, unpackInvert);
+ blue = packBlue.GetChannelAsTexture(blueTexChan, unpackInvert);
+ alpha = packAlpha.GetChannelAsTexture(alphaTexChan, unpackInvert);
+ }
+
+ Texture2D packResult = PoiHelpers.PackTextures(PackSize, red, green, blue, alpha, redInvert, greenInvert, blueInvert, alphaInvert);
+ if(packResult)
+ {
+ string path = $"{savePath}/Packed/{packedName}.png";
+ packResult.SaveTextureAsset(path, true);
+ Debug.Log(LOG_PREFIX + "Finished packing texture at " + path);
+ PoiHelpers.PingAssetAtPath(path);
+ }
+
+ }
+
+ void DoUnpack(TextureChannel singleChannel = TextureChannel.RGBA)
+ {
+ if(!PackerShadersExist)
+ return;
+
+ var channelTextures = new Dictionary<string, Texture2D>();
+ if(singleChannel == TextureChannel.RGBA)
+ channelTextures = PoiHelpers.UnpackTextureToChannels(unpackSource, unpackInvert, UnpackSize);
+ else
+ channelTextures[singleChannel.ToString().ToLower()] = unpackSource.GetChannelAsTexture(singleChannel, unpackInvert, UnpackSize);
+
+
+ string pingPath = null;
+ pingPath = SaveTextures(channelTextures, pingPath);
+
+ Debug.Log(LOG_PREFIX + "Finished unpacking texture at " + pingPath);
+ PoiHelpers.PingAssetAtPath(pingPath);
+ }
+
+ #endregion
+
+ #region Helpers
+
+ string SaveTextures(Dictionary<string, Texture2D> output, string pingPath)
+ {
+ try
+ {
+ AssetDatabase.StartAssetEditing();
+ foreach(var kv in output)
+ {
+ if(string.IsNullOrWhiteSpace(pingPath))
+ pingPath = $"{savePath}/Unpacked/{unpackedName}_{kv.Key}.png";
+ kv.Value?.SaveTextureAsset($"{savePath}/Unpacked/{unpackedName}_{kv.Key}.png", true);
+ }
+ }
+ catch { }
+ finally
+ {
+ AssetDatabase.StopAssetEditing();
+ }
+
+ return pingPath;
+ }
+
+ void DrawTextureSelector(string label, ref Texture2D tex)
+ {
+ EditorGUILayout.BeginVertical(EditorStyles.helpBox);
+ {
+ tex = EditorGUILayout.ObjectField(label, tex, typeof(Texture2D), true, GUILayout.ExpandHeight(true)) as Texture2D;
+ }
+ EditorGUILayout.EndHorizontal();
+ }
+
+ void DrawTextureSelector(string label, ref Texture2D tex, ref TextureChannel selectedChannel, ref bool invert)
+ {
+ EditorGUILayout.BeginVertical(EditorStyles.helpBox);
+ {
+ EditorGUILayout.BeginHorizontal();
+ {
+ EditorGUILayout.BeginVertical();
+ {
+ var labelContent = new GUIContent(label);
+ var size = EditorStyles.boldLabel.CalcSize(labelContent);
+
+ EditorGUILayout.LabelField(labelContent, EditorStyles.boldLabel, GUILayout.MaxWidth(size.x));
+
+ GUILayout.Space(15 * EditorGUIUtility.pixelsPerPoint);
+
+ GUILayout.FlexibleSpace();
+
+ invert = EditorGUILayout.ToggleLeft(INVERT_LABEL, invert, GUILayout.MaxWidth(InvertToggleWidth));
+ }
+ EditorGUILayout.EndVertical();
+
+ tex = EditorGUILayout.ObjectField(GUIContent.none, tex, typeof(Texture2D), true, GUILayout.ExpandHeight(true)) as Texture2D;
+ }
+ EditorGUILayout.EndHorizontal();
+
+ if(showChannelPicker)
+ {
+ EditorGUI.BeginDisabledGroup(!tex);
+ selectedChannel = PoiHelpers.DrawChannelSelector(selectedChannel, ChannelLabels);
+ EditorGUI.EndDisabledGroup();
+ }
+ }
+ EditorGUILayout.EndVertical();
+ }
+
+ Vector2Int DrawTextureSizeSettings(Vector2Int size, ref string fileName, ref bool sizeIsLinked, ref bool sizeAutoSelect)
+ {
+ EditorGUILayout.BeginVertical(EditorStyles.helpBox);
+ {
+ fileName = EditorGUILayout.TextField("File name", fileName);
+ EditorGUILayout.Space();
+ size = PoiHelpers.DrawResolutionPicker(size, ref sizeIsLinked, ref sizeAutoSelect, SizePresets, SizePresetNames);
+ }
+ EditorGUILayout.EndVertical();
+ return size;
+ }
+
+ #endregion
+
+ string[] _tabNames;
+ string[] _sizeNames;
+ static EditorWindow _window;
+ string _subTitle;
+ Vector2 _scroll;
+ float _invertLabelWidth;
+ }
+} \ No newline at end of file
diff --git a/VRCSDK3Worlds/Assets/_PoiyomiShaders/Scripts/poi-tools/Editor/TextureChannelPackerEditor.cs.meta b/VRCSDK3Worlds/Assets/_PoiyomiShaders/Scripts/poi-tools/Editor/TextureChannelPackerEditor.cs.meta
new file mode 100644
index 00000000..49e700e9
--- /dev/null
+++ b/VRCSDK3Worlds/Assets/_PoiyomiShaders/Scripts/poi-tools/Editor/TextureChannelPackerEditor.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 1e29ffa815f2cd648839d9b094a4631f
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/VRCSDK3Worlds/Assets/_PoiyomiShaders/Scripts/poi-tools/Resources.meta b/VRCSDK3Worlds/Assets/_PoiyomiShaders/Scripts/poi-tools/Resources.meta
new file mode 100644
index 00000000..604e4ea7
--- /dev/null
+++ b/VRCSDK3Worlds/Assets/_PoiyomiShaders/Scripts/poi-tools/Resources.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 362826441ef464c458314d76942a2c67
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/VRCSDK3Worlds/Assets/_PoiyomiShaders/Scripts/poi-tools/Resources/PoiTexturePacker.shader b/VRCSDK3Worlds/Assets/_PoiyomiShaders/Scripts/poi-tools/Resources/PoiTexturePacker.shader
new file mode 100644
index 00000000..2f366a9e
--- /dev/null
+++ b/VRCSDK3Worlds/Assets/_PoiyomiShaders/Scripts/poi-tools/Resources/PoiTexturePacker.shader
@@ -0,0 +1,169 @@
+// Made with Amplify Shader Editor
+// Available at the Unity Asset Store - http://u3d.as/y3X
+Shader "Hidden/Poi/TexturePacker"
+{
+ Properties
+ {
+ _Invert_Red("Invert_Red", Float) = 0
+ _Invert_Green("Invert_Green", Float) = 0
+ _Invert_Blue("Invert_Blue", Float) = 0
+ _Invert_Alpha("Invert_Alpha", Float) = 0
+ _Red("Red", 2D) = "white" {}
+ _Green("Green", 2D) = "white" {}
+ _Blue("Blue", 2D) = "white" {}
+ _Alpha("Alpha", 2D) = "white" {}
+ [HideInInspector] _texcoord( "", 2D ) = "white" {}
+ }
+
+ SubShader
+ {
+ Tags { "RenderType"="Opaque" }
+ LOD 100
+ CGINCLUDE
+ #pragma target 3.0
+ ENDCG
+ Blend Off
+ Cull Back
+ ColorMask RGBA
+ ZWrite On
+ ZTest LEqual
+ Offset 0 , 0
+
+
+
+ Pass
+ {
+ Name "Unlit"
+ CGPROGRAM
+ #pragma vertex vert
+ #pragma fragment frag
+ #pragma multi_compile_instancing
+ #include "UnityCG.cginc"
+
+
+ struct appdata
+ {
+ float4 vertex : POSITION;
+ UNITY_VERTEX_INPUT_INSTANCE_ID
+ float4 ase_texcoord : TEXCOORD0;
+ };
+
+ struct v2f
+ {
+ float4 vertex : SV_POSITION;
+ float4 ase_texcoord : TEXCOORD0;
+ UNITY_VERTEX_OUTPUT_STEREO
+ UNITY_VERTEX_INPUT_INSTANCE_ID
+ };
+
+ uniform sampler2D _Red;
+ uniform float4 _Red_ST;
+ uniform float _Invert_Red;
+ uniform sampler2D _Green;
+ uniform float4 _Green_ST;
+ uniform float _Invert_Green;
+ uniform sampler2D _Blue;
+ uniform float4 _Blue_ST;
+ uniform float _Invert_Blue;
+ uniform sampler2D _Alpha;
+ uniform float4 _Alpha_ST;
+ uniform float _Invert_Alpha;
+
+ v2f vert ( appdata v )
+ {
+ v2f o;
+ UNITY_SETUP_INSTANCE_ID(v);
+ UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);
+ UNITY_TRANSFER_INSTANCE_ID(v, o);
+
+ o.ase_texcoord.xy = v.ase_texcoord.xy;
+
+ //setting value to unused interpolator channels and avoid initialization warnings
+ o.ase_texcoord.zw = 0;
+
+ v.vertex.xyz += float3(0,0,0) ;
+ o.vertex = UnityObjectToClipPos(v.vertex);
+ return o;
+ }
+
+ fixed4 frag (v2f i ) : SV_Target
+ {
+ UNITY_SETUP_INSTANCE_ID(i);
+ fixed4 finalColor;
+ float2 uv_Red = i.ase_texcoord.xy * _Red_ST.xy + _Red_ST.zw;
+ float4 tex2DNode28 = tex2D( _Red, uv_Red );
+ float4 temp_cast_0 = (_Invert_Red).xxxx;
+ float4 lerpResult27 = lerp( tex2DNode28 , ( temp_cast_0 - tex2DNode28 ) , _Invert_Red);
+ float2 uv_Green = i.ase_texcoord.xy * _Green_ST.xy + _Green_ST.zw;
+ float4 tex2DNode12 = tex2D( _Green, uv_Green );
+ float4 temp_cast_2 = (_Invert_Green).xxxx;
+ float4 lerpResult20 = lerp( tex2DNode12 , ( temp_cast_2 - tex2DNode12 ) , _Invert_Green);
+ float2 uv_Blue = i.ase_texcoord.xy * _Blue_ST.xy + _Blue_ST.zw;
+ float4 tex2DNode14 = tex2D( _Blue, uv_Blue );
+ float4 temp_cast_4 = (_Invert_Blue).xxxx;
+ float4 lerpResult21 = lerp( tex2DNode14 , ( temp_cast_4 - tex2DNode14 ) , _Invert_Blue);
+ float2 uv_Alpha = i.ase_texcoord.xy * _Alpha_ST.xy + _Alpha_ST.zw;
+ float4 tex2DNode13 = tex2D( _Alpha, uv_Alpha );
+ float4 temp_cast_6 = (_Invert_Alpha).xxxx;
+ float4 lerpResult19 = lerp( tex2DNode13 , ( temp_cast_6 - tex2DNode13 ) , _Invert_Alpha);
+ float4 appendResult30 = (float4(lerpResult27.r , lerpResult20.r , lerpResult21.r , lerpResult19.r));
+
+
+ finalColor = appendResult30;
+ return finalColor;
+ }
+ ENDCG
+ }
+ }
+ CustomEditor "ASEMaterialInspector"
+
+
+}
+/*ASEBEGIN
+Version=15902
+0;0;1368;850;1368.399;595.2781;1;True;False
+Node;AmplifyShaderEditor.SamplerNode;14;-1193.289,314.7757;Float;True;Property;_Blue;Blue;6;0;Create;True;0;0;False;0;None;None;True;0;False;white;Auto;False;Object;-1;Auto;Texture2D;6;0;SAMPLER2D;;False;1;FLOAT2;0,0;False;2;FLOAT;0;False;3;FLOAT2;0,0;False;4;FLOAT2;0,0;False;5;FLOAT;1;False;5;COLOR;0;FLOAT;1;FLOAT;2;FLOAT;3;FLOAT;4
+Node;AmplifyShaderEditor.RangedFloatNode;25;-815.7044,759.9294;Float;False;Property;_Invert_Alpha;Invert_Alpha;3;0;Create;True;0;0;False;0;0;0;0;0;0;1;FLOAT;0
+Node;AmplifyShaderEditor.RangedFloatNode;15;-819.5868,472.4816;Float;False;Property;_Invert_Blue;Invert_Blue;2;0;Create;True;0;0;False;0;0;0;0;0;0;1;FLOAT;0
+Node;AmplifyShaderEditor.RangedFloatNode;31;-803.4256,177.2413;Float;False;Property;_Invert_Green;Invert_Green;1;0;Create;True;0;0;False;0;0;0;0;0;0;1;FLOAT;0
+Node;AmplifyShaderEditor.RangedFloatNode;29;-795.8423,-109.6157;Float;False;Property;_Invert_Red;Invert_Red;0;0;Create;True;0;0;False;0;0;0;0;0;0;1;FLOAT;0
+Node;AmplifyShaderEditor.SamplerNode;28;-1189.017,-285.634;Float;True;Property;_Red;Red;4;0;Create;True;0;0;False;0;None;None;True;0;False;white;Auto;False;Object;-1;Auto;Texture2D;6;0;SAMPLER2D;;False;1;FLOAT2;0,0;False;2;FLOAT;0;False;3;FLOAT2;0,0;False;4;FLOAT2;0,0;False;5;FLOAT;1;False;5;COLOR;0;FLOAT;1;FLOAT;2;FLOAT;3;FLOAT;4
+Node;AmplifyShaderEditor.SamplerNode;12;-1199.358,5.317238;Float;True;Property;_Green;Green;5;0;Create;True;0;0;False;0;None;None;True;0;False;white;Auto;False;Object;-1;Auto;Texture2D;6;0;SAMPLER2D;;False;1;FLOAT2;0,0;False;2;FLOAT;0;False;3;FLOAT2;0,0;False;4;FLOAT2;0,0;False;5;FLOAT;1;False;5;COLOR;0;FLOAT;1;FLOAT;2;FLOAT;3;FLOAT;4
+Node;AmplifyShaderEditor.SamplerNode;13;-1182.523,665.4475;Float;True;Property;_Alpha;Alpha;7;0;Create;True;0;0;False;0;None;None;True;0;False;white;Auto;False;Object;-1;Auto;Texture2D;6;0;SAMPLER2D;;False;1;FLOAT2;0,0;False;2;FLOAT;0;False;3;FLOAT2;0,0;False;4;FLOAT2;0,0;False;5;FLOAT;1;False;5;COLOR;0;FLOAT;1;FLOAT;2;FLOAT;3;FLOAT;4
+Node;AmplifyShaderEditor.SimpleSubtractOpNode;16;-610.2974,-218.5994;Float;False;2;0;FLOAT;0;False;1;COLOR;0,0,0,0;False;1;COLOR;0
+Node;AmplifyShaderEditor.SimpleSubtractOpNode;26;-570.7031,710.9296;Float;False;2;0;FLOAT;0;False;1;COLOR;0,0,0,0;False;1;COLOR;0
+Node;AmplifyShaderEditor.SimpleSubtractOpNode;17;-612.9231,67.14128;Float;False;2;0;FLOAT;0;False;1;COLOR;0,0,0,0;False;1;COLOR;0
+Node;AmplifyShaderEditor.SimpleSubtractOpNode;18;-589.0041,392.5837;Float;False;2;0;FLOAT;0;False;1;COLOR;0,0,0,0;False;1;COLOR;0
+Node;AmplifyShaderEditor.LerpOp;19;-279.5903,619.9736;Float;False;3;0;COLOR;0,0,0,0;False;1;COLOR;0,0,0,0;False;2;FLOAT;0;False;1;COLOR;0
+Node;AmplifyShaderEditor.LerpOp;27;-318.2486,-275.2707;Float;False;3;0;COLOR;0,0,0,0;False;1;COLOR;0,0,0,0;False;2;FLOAT;0;False;1;COLOR;0
+Node;AmplifyShaderEditor.LerpOp;20;-299.71,16.80488;Float;False;3;0;COLOR;0,0,0,0;False;1;COLOR;0,0,0,0;False;2;FLOAT;0;False;1;COLOR;0
+Node;AmplifyShaderEditor.LerpOp;21;-296.069,300.6409;Float;False;3;0;COLOR;0,0,0,0;False;1;COLOR;0,0,0,0;False;2;FLOAT;0;False;1;COLOR;0
+Node;AmplifyShaderEditor.DynamicAppendNode;30;98.28339,102.1202;Float;False;FLOAT4;4;0;FLOAT;0;False;1;FLOAT;0;False;2;FLOAT;0;False;3;FLOAT;0;False;1;FLOAT4;0
+Node;AmplifyShaderEditor.TemplateMultiPassMasterNode;0;369.802,98.57185;Float;False;True;2;Float;ASEMaterialInspector;0;1;Hidden/Poi/TexturePacker;0770190933193b94aaa3065e307002fa;0;0;Unlit;2;True;0;1;False;-1;0;False;-1;0;1;False;-1;0;False;-1;True;0;False;-1;0;False;-1;True;0;False;-1;True;True;True;True;True;0;False;-1;True;False;255;False;-1;255;False;-1;255;False;-1;7;False;-1;1;False;-1;1;False;-1;1;False;-1;7;False;-1;1;False;-1;1;False;-1;1;False;-1;True;1;False;-1;True;3;False;-1;True;True;0;False;-1;0;False;-1;True;1;RenderType=Opaque=RenderType;True;2;0;False;False;False;False;False;False;False;False;False;False;0;;0;0;Standard;0;2;0;FLOAT4;0,0,0,0;False;1;FLOAT3;0,0,0;False;0
+WireConnection;16;0;29;0
+WireConnection;16;1;28;0
+WireConnection;26;0;25;0
+WireConnection;26;1;13;0
+WireConnection;17;0;31;0
+WireConnection;17;1;12;0
+WireConnection;18;0;15;0
+WireConnection;18;1;14;0
+WireConnection;19;0;13;0
+WireConnection;19;1;26;0
+WireConnection;19;2;25;0
+WireConnection;27;0;28;0
+WireConnection;27;1;16;0
+WireConnection;27;2;29;0
+WireConnection;20;0;12;0
+WireConnection;20;1;17;0
+WireConnection;20;2;31;0
+WireConnection;21;0;14;0
+WireConnection;21;1;18;0
+WireConnection;21;2;15;0
+WireConnection;30;0;27;0
+WireConnection;30;1;20;0
+WireConnection;30;2;21;0
+WireConnection;30;3;19;0
+WireConnection;0;0;30;0
+ASEEND*/
+//CHKSM=2C30DB01285F07958B9316BD81CB0A64AD7E3B0E \ No newline at end of file
diff --git a/VRCSDK3Worlds/Assets/_PoiyomiShaders/Scripts/poi-tools/Resources/PoiTexturePacker.shader.meta b/VRCSDK3Worlds/Assets/_PoiyomiShaders/Scripts/poi-tools/Resources/PoiTexturePacker.shader.meta
new file mode 100644
index 00000000..1658d888
--- /dev/null
+++ b/VRCSDK3Worlds/Assets/_PoiyomiShaders/Scripts/poi-tools/Resources/PoiTexturePacker.shader.meta
@@ -0,0 +1,9 @@
+fileFormatVersion: 2
+guid: 71129bd3774e04d48827a25fc98d45a7
+ShaderImporter:
+ externalObjects: {}
+ defaultTextures: []
+ nonModifiableTextures: []
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/VRCSDK3Worlds/Assets/_PoiyomiShaders/Scripts/poi-tools/Resources/PoiTextureUnpacker.shader b/VRCSDK3Worlds/Assets/_PoiyomiShaders/Scripts/poi-tools/Resources/PoiTextureUnpacker.shader
new file mode 100644
index 00000000..736465be
--- /dev/null
+++ b/VRCSDK3Worlds/Assets/_PoiyomiShaders/Scripts/poi-tools/Resources/PoiTextureUnpacker.shader
@@ -0,0 +1,156 @@
+// Made with Amplify Shader Editor
+// Available at the Unity Asset Store - http://u3d.as/y3X
+Shader "Hidden/Poi/TextureUnpacker"
+{
+ Properties
+ {
+ _MainTex("MainTex", 2D) = "white" {}
+ _Mode("Mode", Range( 0 , 3)) = 3
+ _Invert("Invert", Float) = 0
+ [HideInInspector] _texcoord( "", 2D ) = "white" {}
+ }
+
+ SubShader
+ {
+ Tags { "RenderType"="Opaque" }
+ LOD 100
+ CGINCLUDE
+ #pragma target 3.0
+ ENDCG
+ Blend Off
+ Cull Back
+ ColorMask RGBA
+ ZWrite On
+ ZTest LEqual
+ Offset 0 , 0
+
+
+
+ Pass
+ {
+ Name "Unlit"
+ CGPROGRAM
+ #pragma vertex vert
+ #pragma fragment frag
+ #pragma multi_compile_instancing
+ #include "UnityCG.cginc"
+
+
+ struct appdata
+ {
+ float4 vertex : POSITION;
+ UNITY_VERTEX_INPUT_INSTANCE_ID
+ float4 ase_texcoord : TEXCOORD0;
+ };
+
+ struct v2f
+ {
+ float4 vertex : SV_POSITION;
+ float4 ase_texcoord : TEXCOORD0;
+ UNITY_VERTEX_OUTPUT_STEREO
+ UNITY_VERTEX_INPUT_INSTANCE_ID
+ };
+
+ uniform float _Mode;
+ uniform sampler2D _MainTex;
+ uniform float4 _MainTex_ST;
+ uniform float _Invert;
+
+ v2f vert ( appdata v )
+ {
+ v2f o;
+ UNITY_SETUP_INSTANCE_ID(v);
+ UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);
+ UNITY_TRANSFER_INSTANCE_ID(v, o);
+
+ o.ase_texcoord.xy = v.ase_texcoord.xy;
+
+ //setting value to unused interpolator channels and avoid initialization warnings
+ o.ase_texcoord.zw = 0;
+
+ v.vertex.xyz += float3(0,0,0) ;
+ o.vertex = UnityObjectToClipPos(v.vertex);
+ return o;
+ }
+
+ fixed4 frag (v2f i ) : SV_Target
+ {
+ UNITY_SETUP_INSTANCE_ID(i);
+ fixed4 finalColor;
+ float2 uv_MainTex = i.ase_texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
+ float4 tex2DNode32 = tex2D( _MainTex, uv_MainTex );
+ float ifLocalVar34 = 0;
+ if( _Mode == 0.0 )
+ ifLocalVar34 = tex2DNode32.r;
+ float ifLocalVar35 = 0;
+ if( _Mode == 1.0 )
+ ifLocalVar35 = tex2DNode32.g;
+ float ifLocalVar36 = 0;
+ if( _Mode == 2.0 )
+ ifLocalVar36 = tex2DNode32.b;
+ float ifLocalVar37 = 0;
+ if( _Mode == 3.0 )
+ ifLocalVar37 = tex2DNode32.a;
+ float4 ifLocalVar42 = 0;
+ if( _Mode < 0.0 )
+ ifLocalVar42 = tex2DNode32;
+ float4 ifLocalVar43 = 0;
+ if( _Mode > 3.0 )
+ ifLocalVar43 = tex2DNode32;
+ float4 temp_output_40_0 = ( ifLocalVar34 + ifLocalVar35 + ifLocalVar36 + ifLocalVar37 + ifLocalVar42 + ifLocalVar43 );
+ float4 temp_cast_0 = (_Invert).xxxx;
+ float4 lerpResult46 = lerp( temp_output_40_0 , ( temp_cast_0 - temp_output_40_0 ) , _Invert);
+
+
+ finalColor = lerpResult46;
+ return finalColor;
+ }
+ ENDCG
+ }
+ }
+ CustomEditor "ASEMaterialInspector"
+
+
+}
+/*ASEBEGIN
+Version=15902
+0;0;1368;850;930.0129;673.0209;1.753676;True;False
+Node;AmplifyShaderEditor.SamplerNode;32;-446.011,1.547681;Float;True;Property;_MainTex;MainTex;0;0;Create;True;0;0;False;0;None;None;True;0;False;white;Auto;False;Object;-1;Auto;Texture2D;6;0;SAMPLER2D;;False;1;FLOAT2;0,0;False;2;FLOAT;0;False;3;FLOAT2;0,0;False;4;FLOAT2;0,0;False;5;FLOAT;1;False;5;COLOR;0;FLOAT;1;FLOAT;2;FLOAT;3;FLOAT;4
+Node;AmplifyShaderEditor.RangedFloatNode;33;-414.2798,-86.22936;Float;False;Property;_Mode;Mode;1;0;Create;True;0;0;False;0;3;0;0;3;0;1;FLOAT;0
+Node;AmplifyShaderEditor.ConditionalIfNode;35;17.04439,123.3313;Float;False;False;5;0;FLOAT;0;False;1;FLOAT;1;False;2;FLOAT;0;False;3;FLOAT;0;False;4;FLOAT;0;False;1;FLOAT;0
+Node;AmplifyShaderEditor.ConditionalIfNode;36;17.16646,287.5046;Float;False;False;5;0;FLOAT;0;False;1;FLOAT;2;False;2;FLOAT;0;False;3;FLOAT;0;False;4;FLOAT;0;False;1;FLOAT;0
+Node;AmplifyShaderEditor.ConditionalIfNode;37;15.75801,456.534;Float;False;False;5;0;FLOAT;0;False;1;FLOAT;3;False;2;FLOAT;0;False;3;FLOAT;0;False;4;FLOAT;0;False;1;FLOAT;0
+Node;AmplifyShaderEditor.ConditionalIfNode;34;17.15299,-44.90865;Float;False;False;5;0;FLOAT;0;False;1;FLOAT;0;False;2;FLOAT;0;False;3;FLOAT;0;False;4;FLOAT;0;False;1;FLOAT;0
+Node;AmplifyShaderEditor.ConditionalIfNode;42;19.07808,-276.4948;Float;False;False;5;0;FLOAT;0;False;1;FLOAT;0;False;2;FLOAT;0;False;3;FLOAT;0;False;4;COLOR;0,0,0,0;False;1;COLOR;0
+Node;AmplifyShaderEditor.ConditionalIfNode;43;19.07608,697.2275;Float;False;False;5;0;FLOAT;0;False;1;FLOAT;3;False;2;COLOR;0,0,0,0;False;3;FLOAT;0;False;4;COLOR;0,0,0,0;False;1;COLOR;0
+Node;AmplifyShaderEditor.SimpleAddOpNode;40;370.6085,1.924235;Float;True;6;6;0;FLOAT;0;False;1;FLOAT;0;False;2;FLOAT;0;False;3;FLOAT;0;False;4;COLOR;0,0,0,0;False;5;COLOR;0,0,0,0;False;1;COLOR;0
+Node;AmplifyShaderEditor.RangedFloatNode;44;440.5474,232.555;Float;False;Property;_Invert;Invert;2;0;Create;True;0;0;False;0;0;0;0;0;0;1;FLOAT;0
+Node;AmplifyShaderEditor.SimpleSubtractOpNode;45;609.8499,63.25506;Float;False;2;0;FLOAT;0;False;1;COLOR;0,0,0,0;False;1;COLOR;0
+Node;AmplifyShaderEditor.LerpOp;46;780.463,-0.6814048;Float;False;3;0;COLOR;0,0,0,0;False;1;COLOR;0,0,0,0;False;2;FLOAT;0;False;1;COLOR;0
+Node;AmplifyShaderEditor.TemplateMultiPassMasterNode;0;975.2405,-1.718735;Float;False;True;2;Float;ASEMaterialInspector;0;1;Hidden/Poi/TextureUnpacker;0770190933193b94aaa3065e307002fa;0;0;Unlit;2;True;0;1;False;-1;0;False;-1;0;1;False;-1;0;False;-1;True;0;False;-1;0;False;-1;True;0;False;-1;True;True;True;True;True;0;False;-1;True;False;255;False;-1;255;False;-1;255;False;-1;7;False;-1;1;False;-1;1;False;-1;1;False;-1;7;False;-1;1;False;-1;1;False;-1;1;False;-1;True;1;False;-1;True;3;False;-1;True;True;0;False;-1;0;False;-1;True;1;RenderType=Opaque=RenderType;True;2;0;False;False;False;False;False;False;False;False;False;False;0;;0;0;Standard;0;2;0;FLOAT4;0,0,0,0;False;1;FLOAT3;0,0,0;False;0
+WireConnection;35;0;33;0
+WireConnection;35;3;32;2
+WireConnection;36;0;33;0
+WireConnection;36;3;32;3
+WireConnection;37;0;33;0
+WireConnection;37;3;32;4
+WireConnection;34;0;33;0
+WireConnection;34;3;32;1
+WireConnection;42;0;33;0
+WireConnection;42;4;32;0
+WireConnection;43;0;33;0
+WireConnection;43;2;32;0
+WireConnection;40;0;34;0
+WireConnection;40;1;35;0
+WireConnection;40;2;36;0
+WireConnection;40;3;37;0
+WireConnection;40;4;42;0
+WireConnection;40;5;43;0
+WireConnection;45;0;44;0
+WireConnection;45;1;40;0
+WireConnection;46;0;40;0
+WireConnection;46;1;45;0
+WireConnection;46;2;44;0
+WireConnection;0;0;46;0
+ASEEND*/
+//CHKSM=FB476DC839C9D986CDFBE64BF68940FC3E2666AE \ No newline at end of file
diff --git a/VRCSDK3Worlds/Assets/_PoiyomiShaders/Scripts/poi-tools/Resources/PoiTextureUnpacker.shader.meta b/VRCSDK3Worlds/Assets/_PoiyomiShaders/Scripts/poi-tools/Resources/PoiTextureUnpacker.shader.meta
new file mode 100644
index 00000000..d7c2c86d
--- /dev/null
+++ b/VRCSDK3Worlds/Assets/_PoiyomiShaders/Scripts/poi-tools/Resources/PoiTextureUnpacker.shader.meta
@@ -0,0 +1,9 @@
+fileFormatVersion: 2
+guid: 245e67c21ccaa9a43ad7e84d1c7bb5fc
+ShaderImporter:
+ externalObjects: {}
+ defaultTextures: []
+ nonModifiableTextures: []
+ userData:
+ assetBundleName:
+ assetBundleVariant: