/****************************************************************************
*
*
* Copyright (c) 2010, 2010 All Rights Reserved, http://klakos.com
*
* LICENCE : GPLv2 [http://www.gnu.org/licenses/gpl-2.0.html]
*
* This source code is intended only as a supplement to Unity
* Development Tools and/or on-line documentation. See these other
* materials for detailed information regarding Unity code samples.
*
* THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
* KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
* PARTICULAR PURPOSE.
*
*
*
* Gauthier BOAGLIO
* gauthier __at__ klakos.com
* 2011-05-10
*
*
* Basic tool (Unity [Copyright (c)] 3.3.0f3 addon) for easy setup of
* Double sided / Dual Materials Shading Technics - MORE INFORMATIONS
* HERE : http://klakos.com/?p=2020, [ARTICLE : Advanced Waving Flag
* Shader for Unity (Double sided, Alpha shadow support)]
*
*
* *************************************************************************/
/****************************************************************************
*
*
* Note : Regular => Backward only...
* ----
*
* Install :
* -------
* Just drop the “BackwardMaterialBuilder.cs” (ScriptableWizard) script into
* the “Assets/Editor” directory of the Unity project.
*
* Prerequisites :
* -------------
*
* 1- Naming conventions for shaders :
* - Front/Forward shaders Name has to end with “Regular” suffix
* - Back/Backward shaders Name has to end with “Backward” suffix
*
* Those conventions deal with the Shaders name (not the Materials name)
*
* [CODE]
*
* Shader "Selfmade/for-2sided/FlagWave Advanced Regular"
* {
*
* Properties
* {
* // Usual stuffs
* _Color ("Main Color", Color) = (1,1,1,1)
* _SpecColor ("Specular Color", Color) = (0.5, 0.5, 0.5, 0)
* .
* .
* .
* [/CODE]
*
* 2- Material assets need to be made by hand and this tool will only
* help to set them up correctly.
*
* 3- The different shaders may have been previously created.
*
* Usage :
* -----
* To access the script, select an object (in the Hierarchy or Project pane).
* Then right-click on the context menu icon of the
* Renderer (either Mesh or SkinnedMesh Renderer), and choose “Set 2-sided Materials” :
* The related window displays a selectable list of all the “Regular” shaded
* materials from the Renderer materials list :
* Then select one of those Materials and press “Proceed”.
*
* If the corresponding “Backward” Shader exists in you Project, 3 different
* things may happen :
*
* - Synchronize props form “Regular” to “Backward”, if Mats are existing
* - Create the “Backward” one if not existing (not setting the Shader)
* - Erase or not the actual Shader of the current “Target Backup Material”
*
* Then drop a hand made target Material of your choice from your “Assets” folder
* into the “Target Backup Material” field.
*
* Option “Force Shader”, will set the right existing “Backward” Shader to the
* targeted Material.
* If unchecked, the Shader of the original dropped Material will remain the same.
* In many cases, this option has to be checked (but you'll lose all the current
* settings of the target Material).
*
*
*
* *************************************************************************/
using UnityEngine;
using UnityEditor;
using System.Collections;
using System.Collections.Generic;
using System.IO;
public class BackwardMaterialBuilder : ScriptableWizard
{
# region Main Window Handler
private static BackwardMaterialBuilder m_Win;
# endregion
# region Operating Stuffs
public static bool doCreateBWMaterial = false;
public static Renderer targetRenderer;
private Material regularMaterial;
private Material backwardMaterial;
List materials = new List();
static string FRONT_FACE_SUFFIX = "Regular";
static string BACK_FACE_SUFFIX = "Backward";
// Target material
private Material matToSaveTo;
private bool showMaterialCreatorWindow = false;
private bool forceShader = false;
# endregion
# region GUI stuffs
Rect windowRect = new Rect(100, 100, 500, 200);
Vector2 scrollPos = Vector2.zero;
int intSelectedMat = 0;
Dictionary matsMap = new Dictionary();
List matNames = new List();
# endregion
# region Context Menu Settings
// Context menu:
[MenuItem("CONTEXT/MeshRenderer/Set 2-sided Materials")]
static void UpdateBackwardMaterialForMeshRenderer(MenuCommand cmd)
{
RunWizard(cmd);
}
// Validator:
[MenuItem("CONTEXT/MeshRenderer/Set 2-sided Materials", true)]
static bool ValidateBackwardMaterialForMeshRendererContext(MenuCommand cmd)
{
return cmd.context is MeshRenderer;
}
// Context menu:
[MenuItem("CONTEXT/SkinnedMeshRenderer/Set 2-sided Materials")]
static void UpdateBackwardMaterialForSkinnedMeshRenderer(MenuCommand cmd)
{
RunWizard(cmd);
}
// Validator:
[MenuItem("CONTEXT/SkinnedMeshRenderer/Set 2-sided Materials", true)]
static bool ValidateBackwardMaterialForSkinnedMeshRendererContext(MenuCommand cmd)
{
return cmd.context is SkinnedMeshRenderer;
}
static void RunWizard(MenuCommand cmd)
{
BackwardMaterialBuilder.doCreateBWMaterial = (BackwardMaterialBuilder.targetRenderer == null);
BackwardMaterialBuilder.targetRenderer = (Renderer)cmd.context;
m_Win = (BackwardMaterialBuilder)ScriptableWizard.DisplayWizard("Create & Update Backwards Materials", typeof(BackwardMaterialBuilder));
}
# endregion
# region Let's Go
void OnGUI()
{
if (m_Win != null)
{
//GUILayout.BeginScrollView(scrollPos);
// Listing "Regular" (Front faces dedicated) Materials
// Cleanup
List to_remove = new List();
foreach (KeyValuePair kv in matsMap)
{
if (!MaterialAlreadyExists(kv.Value)) to_remove.Add(kv.Key);
}
foreach (string mat_name in to_remove)
{
matsMap.Remove(mat_name);
matNames.Remove(mat_name);
}
// Rebuild
foreach (Material mat in targetRenderer.sharedMaterials)
{
if (!(matsMap.ContainsKey(mat.shader.name)) && IsRegularMaterial(mat))
{
matsMap.Add(mat.shader.name, mat);
matNames.Add(mat.shader.name);
}
}
// Display
if (matNames.Count > 0)
intSelectedMat = GUILayout.SelectionGrid(intSelectedMat, matNames.ToArray(), 1);
else
{
GUILayout.Label("Nothing to operate on ! \nThe Renderer of the object \"" + targetRenderer.gameObject.name +
"\" must contain at least one Material with a \"Regular\" shader");
if (GUILayout.Button("Abort"))
{
m_Win.Close();
}
return;
}
GUILayout.Space(50);
regularMaterial = matsMap[matNames[intSelectedMat]];
if (GUILayout.Button("Proceed"))
{
Proceed();
}
else if (showMaterialCreatorWindow && m_Win != null)
{
BeginWindows();
windowRect = GUILayout.Window(1, windowRect, DoMaterialCreatorWindow, "Regular => Backward");
EndWindows();
}
//GUILayout.EndScrollView();
}
}
// Processing
void Proceed()
{
// Synchronization for existing "Regular & Backward" Materials
// foreach (Material mat1 in targetrenderer.sharedMaterials)
Material mat1 = regularMaterial;
if (mat1 != null)
{
if (mat1.shader.name.EndsWith(FRONT_FACE_SUFFIX))
{
Debug.Log("Regular Mat found : " + mat1.shader.name);
// Try finding the 'Backward' one
Material mat22 = null;
foreach (Material mat2 in targetRenderer.sharedMaterials)
{
string bw_shader_name = mat1.shader.name.Replace(FRONT_FACE_SUFFIX, BACK_FACE_SUFFIX);
Shader bw_shader = Shader.Find(bw_shader_name);
if (mat2 != mat1 && mat2.shader.name == bw_shader_name)
{
Debug.Log("Backward Mat found : " + mat2.shader.name);
// Synchronize & Break
if (bw_shader != null)
{
mat2.shader = bw_shader;
mat2.CopyPropertiesFromMaterial(mat1);
mat22 = mat2;
}
break;
}
}
// Do synchronization
if (mat22 != null)
{
EditorUtility.DisplayDialog("Info !", "The Regular material [SHADER : \"" + mat1.shader.name + "\"] \n\nAND \n\nthe Backward " +
"material [SHADER : \"" + mat22.shader.name + "\"] \nhave been successfuly synchronized ...\n\n" +
"Please give a click in the \"Inspector Tab\" to show the changes ;-)",
"Ok");
Debug.Log("Synch. done !");
}
// Try to setup the "Backward" Material from existing shader
else
{
bool doCreate = EditorUtility.DisplayDialog("Warning !", "The Regular material \"" + mat1.shader.name + "\" was found," +
"\nbut no related Backward one.\n" +
"Try to create the Backward material ?",
"Ok", "Cancel");
if (doCreate)
{
// Try finding the right shader
string shader_to_find = mat1.shader.name.Replace(FRONT_FACE_SUFFIX, BACK_FACE_SUFFIX);
Shader shader1 = Shader.Find(shader_to_find);
// Shader not found
if (shader1 == null)
{
bool read = EditorUtility.DisplayDialog("Warning !", "The Backward shader \"" + shader_to_find + "\" could not be found !\n" +
"RTFM : To create a \"Backward\" shader from any other regular one, just add a \"CULL Front\" " +
"instruction at the beginning of your original shader.\n" +
"Then, invert the back faces normals by adding a \"-\"n in the normals unpacking instruction. Ex : " +
"\"o.Normal = -UnpackNormal(tex2D(_BumpMap, IN.uv_BumpMap));\" ...",
"Ok");
if (read) m_Win.Close();
}
// Shader found : continue with Material setup
else
{
regularMaterial = mat1;
Debug.Log("Creating material : \"" + shader1.name + "\" ...");
showMaterialCreatorWindow = true;
}
}
}
}
else
{
bool try_again = EditorUtility.DisplayDialog("Warning !", "The material named \"" + mat1.shader.name + "\" should be a regular one ...", "Try Again", "Cancel");
if (!try_again) m_Win.Close();
}
}
else
{
EditorUtility.DisplayDialog("Warning !", "The material named \"" + mat1.shader.name + "\" should be != null ...", "Ok");
}
}
// SubWindow : Material creation from existing "Backward" shader
public void DoMaterialCreatorWindow(int id)
{
matToSaveTo = (Material)EditorGUILayout.ObjectField("Target Backup Material : ", matToSaveTo, typeof(Material));
forceShader = GUILayout.Toggle(forceShader, "Force Shader");
if (GUILayout.Button("Proceed"))
{
if (matToSaveTo != null)
{
bool setMatOk;
if (forceShader)
setMatOk = SetMaterialProperties(regularMaterial, true);
else
setMatOk = SetMaterialProperties(regularMaterial, false);
if (!setMatOk)
{
EditorUtility.DisplayDialog("Error !", "Copy failed because of uncompatible shaders. \nPlease retry with \"Force Shader\" checked ...",
"Ok");
showMaterialCreatorWindow = false;
return;
}
backwardMaterial = matToSaveTo;
// Update Mats list in Renderer
foreach (Material mat in targetRenderer.sharedMaterials)
{
materials.Add(mat);
if (mat == regularMaterial && !MaterialAlreadyExists(backwardMaterial)) materials.Add(backwardMaterial);
}
targetRenderer.sharedMaterials = materials.ToArray();
showMaterialCreatorWindow = false;
m_Win.Close();
}
else
{
string err = "";
if (matToSaveTo == null)
err = "The selected material is Null !";
else
err = "The material \"" + matToSaveTo.shader.name + "\" is Null or not a \"Backward\" one !";
EditorUtility.DisplayDialog("Warning !", err, "Ok");
}
}
Repaint();
GUI.DragWindow();
}
// Try setting Material properties if Source and Target Materials are Copy-Compatible
public bool SetMaterialProperties(Material mat, bool forceShader)
{
if (forceShader)
{
string sh_name = mat.shader.name.Replace(FRONT_FACE_SUFFIX, BACK_FACE_SUFFIX);
Shader sh = Shader.Find(sh_name);
if (sh == null)
{
EditorUtility.DisplayDialog("Warning !", "The shader [" + sh_name +
"] couln't be found \nand will be skipped from material copy !",
"Ok");
}
else
matToSaveTo.shader = sh;
}
if (!IsSameBasedMaterials(mat, matToSaveTo)) return false;
matToSaveTo.CopyPropertiesFromMaterial(mat);
return true;
}
// if mat is tagged as a "Regular" one
public bool IsRegularMaterial(Material mat)
{
return (mat.shader.name.EndsWith(FRONT_FACE_SUFFIX));
}
// if mat is tagged as a "Backward" one
public bool IsBackwardMaterial(Material mat)
{
return (mat.shader.name.EndsWith(BACK_FACE_SUFFIX));
}
// If mat1 and mat2 are design to be of the same type (means : with the same inner properties)
public bool IsSameBasedMaterials(Material mat1, Material mat2)
{
string mat1name = mat1.shader.name;
string mat2name = mat2.shader.name;
return (mat1name.Substring(0, mat1name.LastIndexOf(' ')) == mat2name.Substring(0, mat2name.LastIndexOf(' ')));
}
// If mat is already present in the Renderer materials list
public bool MaterialAlreadyExists(Material mat)
{
bool found = false;
foreach (Material m in targetRenderer.sharedMaterials)
{
if (m == mat)
{
found = true;
break;
}
}
return found;
}
# endregion
}