//This is UnityEditor only for the time being... #if UNITY_EDITOR using UnityEngine; using UnityEditor; using System; using System.Collections; using System.Collections.Generic; using System.IO; using UMA; using UMA.CharacterSystem; namespace UMA.CharacterSystem.Examples { //UPDATED For CharacterSystem. //Takes photos of the character based on the Wardrobe slots. //HUGE MemoryLeak or infinite loop in this somewhere... public class PhotoBooth : MonoBehaviour { public RenderTexture bodyRenderTexture; public RenderTexture outfitRenderTexture; public RenderTexture headRenderTexture; public RenderTexture chestRenderTexture; public RenderTexture handsRenderTexture; public RenderTexture legsRenderTexture; public RenderTexture feetRenderTexture; public DynamicCharacterAvatar avatarToPhoto; public enum renderTextureOpts { BodyRenderTexture, OutfitRenderTexture, HeadRenderTexture, ChestRenderTexture, HandsRenderTexture, LegsRenderTexture, FeetRenderTexture }; public string photoName; public bool freezeAnimation; public float animationFreezeFrame = 1.8f; public string destinationFolder;//non-serialized? [Tooltip("If true will automatically take all possible wardrobe photos for the current character. Otherwise photographs character in its current state.")] public bool autoPhotosEnabled = true; [Tooltip("In manual mode use this to select the RenderTexture you wish to Photo")] public renderTextureOpts textureToPhoto = renderTextureOpts.BodyRenderTexture; [Tooltip("If true will dim everything but the target wardrobe recipe (AutoPhotosEnabled only)")] public bool dimAllButTarget = false; public Color dimToColor = new Color(0, 0, 0, 1); public Color dimToMetallic = new Color(0, 0, 0, 1); [Tooltip("If true will set the colors for the target wardrobe recipe to the neuttal color (AutoPhotosEnabled only)")] public bool neutralizeTargetColors = false; public Color neutralizeToColor = new Color(1, 1, 1, 1); public Color neutralizeToMetallic = new Color(0, 0, 0, 1); [Tooltip("If true will attempt to find an underwear wardrobe slot to apply when taking the base race photo (AutoPhotosEnabled only)")] public bool addUnderwearToBasePhoto = true; public bool overwriteExistingPhotos = true; public bool doingTakePhoto = false; bool canTakePhoto = true; RenderTexture renderTextureToUse; List wardrobeRecipeToPhoto = new List(); Dictionary> originalColors = new Dictionary>(); bool basePhotoTaken; void Start() { destinationFolder = ""; } public void TakePhotos() { if (doingTakePhoto == false) { doingTakePhoto = true; if (avatarToPhoto == null) { if (Debug.isDebugBuild) Debug.Log("You need to set the Avatar to photo in the inspector!"); doingTakePhoto = false; return; } if (destinationFolder == "") { if (Debug.isDebugBuild) Debug.Log("You need to set a DestinationFolder in the inspector!"); doingTakePhoto = false; return; } if (!autoPhotosEnabled) { bool canPhoto = SetBestRenderTexture(); if (canPhoto) { photoName = photoName == "" ? avatarToPhoto.activeRace.name + Path.GetRandomFileName().Replace(".", "") : photoName; StartCoroutine(TakePhotoCoroutine()); } else { if (Debug.isDebugBuild) Debug.Log("Unknown RenderTexture Error..."); doingTakePhoto = false; return; } } else { Dictionary> recipesToPhoto = UMAContext.Instance.GetRecipes(avatarToPhoto.activeRace.name); basePhotoTaken = false; StartCoroutine(TakePhotosCoroutine(recipesToPhoto)); } } } IEnumerator TakePhotosCoroutine(Dictionary> recipesToPhoto) { yield return null; wardrobeRecipeToPhoto.Clear(); if (!basePhotoTaken) { if (Debug.isDebugBuild) Debug.Log("Gonna take base Photo..."); avatarToPhoto.ClearSlots(); if (addUnderwearToBasePhoto) { foreach (KeyValuePair> kp in recipesToPhoto) { if (kp.Key.IndexOf("Underwear") > -1 || kp.Key.IndexOf("underwear") > -1) { avatarToPhoto.SetSlot(kp.Value[0]); } } } bool renderTextureFound = SetBestRenderTexture("Body"); if (!renderTextureFound) { if (Debug.isDebugBuild) Debug.Log("No suitable RenderTexture found for Base Photo.."); doingTakePhoto = false; yield break; } photoName = avatarToPhoto.activeRace.name; canTakePhoto = false; avatarToPhoto.CharacterUpdated.AddListener(SetCharacterReady); avatarToPhoto.BuildCharacter(true); while (!canTakePhoto) { yield return new WaitForSeconds(1f); } yield return StartCoroutine("TakePhotoCoroutine"); if (Debug.isDebugBuild) Debug.Log("Took base Photo..."); StopCoroutine("TakePhotoCoroutine"); //really we need photos for each area of the body (i.e. from each renderTexture/Cam) with no clothes on so we can have a 'None' image //so we'll need head, chest, hands, legs, feet, full outfit... if (Debug.isDebugBuild) Debug.Log("Now taking the Base Photos for body parts..."); List emptySlotsToPhoto = new List { "Head", "Chest", "Hands", "Legs", "Feet", "Outfit" }; //if dimming and neutralizing is on do it if (dimAllButTarget && neutralizeTargetColors) { canTakePhoto = false; avatarToPhoto.CharacterUpdated.AddListener(SetCharacterReadyAfterColorChange); DoDimmingAndNeutralizing(); while (!canTakePhoto) { yield return new WaitForSeconds(1f); } } foreach (string emptySlotToPhoto in emptySlotsToPhoto) { photoName = avatarToPhoto.activeRace.name + emptySlotToPhoto + "None"; renderTextureFound = SetBestRenderTexture(emptySlotToPhoto); if (!renderTextureFound) { if (Debug.isDebugBuild) Debug.Log("No suitable RenderTexture found for " + emptySlotToPhoto + " Photo.."); continue; } yield return StartCoroutine("TakePhotoCoroutine"); if (Debug.isDebugBuild) Debug.Log("Took base " + emptySlotToPhoto + " Photo..."); StopCoroutine("TakePhotoCoroutine"); } basePhotoTaken = true; if (Debug.isDebugBuild) Debug.Log("Now taking the rest..."); StartCoroutine(TakePhotosCoroutine(recipesToPhoto)); yield break; } else { if (Debug.isDebugBuild) Debug.Log("Gonna take other wardrobe photos..."); if (originalColors.Count > 0) { avatarToPhoto.CharacterUpdated.RemoveListener(SetCharacterReadyAfterColorChange); UndoDimmingAnNeutralizing(); } var numKeys = recipesToPhoto.Count; int slotsDone = 0; foreach (string wardrobeSlot in recipesToPhoto.Keys) { if (Debug.isDebugBuild) Debug.Log("Gonna take photos for " + wardrobeSlot); bool renderTextureFound = SetBestRenderTexture(wardrobeSlot); if (!renderTextureFound) { if (Debug.isDebugBuild) Debug.Log("No suitable RenderTexture found for " + wardrobeSlot + " Photo.."); doingTakePhoto = false; yield break; } foreach (UMATextRecipe wardrobeRecipe in recipesToPhoto[wardrobeSlot]) { if (Debug.isDebugBuild) Debug.Log("Gonna take photos for " + wardrobeRecipe.name + " in " + wardrobeSlot); photoName = wardrobeRecipe.name; var path = destinationFolder + "/" + photoName + ".png"; if (!overwriteExistingPhotos && File.Exists(Application.dataPath + "/" + path)) { if (Debug.isDebugBuild) Debug.Log("Photo already existed for " + photoName + ". Turn on overwrite photos to replace existig ones"); continue; } wardrobeRecipeToPhoto.Clear(); wardrobeRecipeToPhoto.Add(wardrobeRecipe); avatarToPhoto.ClearSlots(); if (addUnderwearToBasePhoto) { foreach (KeyValuePair> kp in recipesToPhoto) { if (kp.Key.IndexOf("Underwear") > -1 || kp.Key.IndexOf("underwear") > -1) { avatarToPhoto.SetSlot(kp.Value[0]); break; } } } avatarToPhoto.SetSlot(wardrobeRecipe); canTakePhoto = false; avatarToPhoto.CharacterUpdated.AddListener(SetCharacterReady); avatarToPhoto.BuildCharacter(true); while (!canTakePhoto) { if (Debug.isDebugBuild) Debug.Log("Waiting to take photo..."); yield return new WaitForSeconds(1f); } yield return StartCoroutine("TakePhotoCoroutine"); StopCoroutine("TakePhotoCoroutine"); } slotsDone++; if (slotsDone == numKeys) { ResetCharacter(); doingTakePhoto = false; StopAllCoroutines(); yield break; } } } } private void ResetCharacter() { if (Debug.isDebugBuild) Debug.Log("Doing Final Reset"); if (originalColors.Count > 0) { avatarToPhoto.CharacterUpdated.RemoveListener(SetCharacterReadyAfterColorChange); UndoDimmingAnNeutralizing(); } if (freezeAnimation) { avatarToPhoto.umaData.animator.speed = 1f; avatarToPhoto.umaData.gameObject.GetComponent().enableBlinking = true; avatarToPhoto.umaData.gameObject.GetComponent().enableSaccades = true; } avatarToPhoto.LoadDefaultWardrobe(); avatarToPhoto.BuildCharacter(true); } public void SetCharacterReady(UMAData umaData) { avatarToPhoto.CharacterUpdated.RemoveListener(SetCharacterReady); if ((dimAllButTarget || neutralizeTargetColors) /*&& wardrobeRecipeToPhoto != null*/)//should we be making it possible to dim the base slot photos too? { if (originalColors.Count > 0) { avatarToPhoto.CharacterUpdated.RemoveListener(SetCharacterReadyAfterColorChange); UndoDimmingAnNeutralizing();//I think this is causing character updates maybe? } avatarToPhoto.CharacterUpdated.AddListener(SetCharacterReadyAfterColorChange); DoDimmingAndNeutralizing(); } else { if (freezeAnimation) { SetAnimationFrame(); } canTakePhoto = true; } } public void SetCharacterReadyAfterColorChange(UMAData umaData) { avatarToPhoto.CharacterUpdated.RemoveListener(SetCharacterReadyAfterColorChange); if (freezeAnimation) { SetAnimationFrame(); } canTakePhoto = true; } private void SetAnimationFrame() { var thisAnimatonClip = avatarToPhoto.umaData.animationController.animationClips[0]; avatarToPhoto.umaData.animator.Play(thisAnimatonClip.name, 0, animationFreezeFrame); avatarToPhoto.umaData.animator.speed = 0f; avatarToPhoto.umaData.gameObject.GetComponent().enableBlinking = false; avatarToPhoto.umaData.gameObject.GetComponent().enableSaccades = false; } IEnumerator TakePhotoCoroutine() { canTakePhoto = false; if (Debug.isDebugBuild) Debug.Log("Taking Photo..."); if (!autoPhotosEnabled) { if (dimAllButTarget && neutralizeTargetColors) { canTakePhoto = false; avatarToPhoto.CharacterUpdated.AddListener(SetCharacterReadyAfterColorChange); wardrobeRecipeToPhoto.Clear(); foreach (KeyValuePair kp in avatarToPhoto.WardrobeRecipes) { wardrobeRecipeToPhoto.Add(kp.Value); } DoDimmingAndNeutralizing(); while (!canTakePhoto) { yield return new WaitForSeconds(1f); } } } var path = destinationFolder + "/" + photoName + ".png"; if (!overwriteExistingPhotos && File.Exists(path)) { if (Debug.isDebugBuild) Debug.Log("could not overwrite existing Photo. Turn on Overwrite Existing Photos if you want to allow this"); canTakePhoto = true; doingTakePhoto = false; if (!autoPhotosEnabled) { ResetCharacter(); } yield return true; } else { Texture2D texToSave = new Texture2D(renderTextureToUse.width, renderTextureToUse.height,TextureFormat.ARGB32,false,false); RenderTexture prev = RenderTexture.active; RenderTexture.active = renderTextureToUse; texToSave.ReadPixels(new Rect(0, 0, renderTextureToUse.width, renderTextureToUse.height), 0, 0, true); texToSave.Apply(); byte[] texToSavePNG = texToSave.EncodeToPNG(); //path must be inside assets File.WriteAllBytes(path, texToSavePNG); RenderTexture.active = prev; var relativePath = path; if (path.StartsWith(Application.dataPath)) { relativePath = "Assets" + path.Substring(Application.dataPath.Length); } AssetDatabase.ImportAsset(relativePath, ImportAssetOptions.ForceUncompressedImport); TextureImporter textureImporter = AssetImporter.GetAtPath(relativePath) as TextureImporter; textureImporter.textureType = TextureImporterType.Sprite; textureImporter.mipmapEnabled = false; textureImporter.maxTextureSize = 256; AssetDatabase.ImportAsset(relativePath, ImportAssetOptions.ForceUpdate); canTakePhoto = true; doingTakePhoto = false; if (!autoPhotosEnabled) { ResetCharacter(); } yield return true; } } private void UndoDimmingAnNeutralizing() { int numSlots = avatarToPhoto.umaData.GetSlotArraySize(); for (int i = 0; i < numSlots; i++) { if (avatarToPhoto.umaData.GetSlot(i)) { var thisSlot = avatarToPhoto.umaData.GetSlot(i); var thisSlotOverlays = thisSlot.GetOverlayList(); for (int ii = 0; ii < thisSlotOverlays.Count; ii++) { if (originalColors.ContainsKey(i)) { if (originalColors[i].ContainsKey(ii)) { thisSlotOverlays[ii].SetColor(0, originalColors[i][ii]); } } } } } } private void DoDimmingAndNeutralizing() { if (wardrobeRecipeToPhoto.Count > 0) { if (Debug.isDebugBuild) Debug.Log("Doing Dimming And Neutralizing for " + wardrobeRecipeToPhoto[0].name); } else { if (Debug.isDebugBuild) Debug.Log("Doing Dimming And Neutralizing for Body shots"); } int numAvatarSlots = avatarToPhoto.umaData.GetSlotArraySize(); originalColors.Clear(); List slotsInRecipe = new List(); List overlaysInRecipe = new List(); foreach (UMATextRecipe utr in wardrobeRecipeToPhoto) { UMAData.UMARecipe tempLoadedRecipe = new UMAData.UMARecipe(); utr.Load(tempLoadedRecipe, avatarToPhoto.context); foreach (SlotData slot in tempLoadedRecipe.slotDataList) { if (slot != null) { slotsInRecipe.Add(slot.asset.name); foreach (OverlayData wOverlay in slot.GetOverlayList()) { if (!overlaysInRecipe.Contains(wOverlay.asset.name)) overlaysInRecipe.Add(wOverlay.asset.name); } } } } //Deal with skin color first if we are dimming if (dimAllButTarget) { OverlayColorData[] sharedColors = avatarToPhoto.umaData.umaRecipe.sharedColors; for (int i = 0; i < sharedColors.Length; i++) { if (sharedColors[i].name == "Skin" || sharedColors[i].name == "skin") { sharedColors[i].color = dimToColor; if (sharedColors[i].channelAdditiveMask.Length >= 3) { sharedColors[i].channelAdditiveMask[2] = dimToMetallic; } } } } for (int i = 0; i < numAvatarSlots; i++) { if (avatarToPhoto.umaData.GetSlot(i) != null) { var overlaysInAvatarSlot = avatarToPhoto.umaData.GetSlot(i).GetOverlayList(); if (slotsInRecipe.Contains(avatarToPhoto.umaData.GetSlot(i).asset.name)) { if (neutralizeTargetColors || dimAllButTarget) { for (int ii = 0; ii < overlaysInAvatarSlot.Count; ii++) { //there is a problem here where if the recipe also contains replacement body slots (like my toon CapriPants_LEGS) these also get set to white //so we need to check if the overlay contains any body part names I think bool overlayIsBody = false; var thisOverlayName = overlaysInAvatarSlot[ii].asset.name; if (thisOverlayName.IndexOf("Face", StringComparison.OrdinalIgnoreCase) > -1 // || thisOverlayName.IndexOf("Torso", StringComparison.OrdinalIgnoreCase) > -1 || thisOverlayName.IndexOf("Arms", StringComparison.OrdinalIgnoreCase) > -1 || thisOverlayName.IndexOf("Hands", StringComparison.OrdinalIgnoreCase) > -1 || thisOverlayName.IndexOf("Legs", StringComparison.OrdinalIgnoreCase) > -1 || thisOverlayName.IndexOf("Feet", StringComparison.OrdinalIgnoreCase) > -1 || thisOverlayName.IndexOf("Body", StringComparison.OrdinalIgnoreCase) > -1) { overlayIsBody = true; } if (overlaysInRecipe.Contains(overlaysInAvatarSlot[ii].asset.name) && overlayIsBody == false) { if (!originalColors.ContainsKey(i)) { originalColors.Add(i, new Dictionary()); } if (!originalColors[i].ContainsKey(ii)) originalColors[i].Add(ii, overlaysInAvatarSlot[ii].colorData.color); overlaysInAvatarSlot[ii].colorData.color = neutralizeToColor; if (overlaysInAvatarSlot[ii].colorData.channelAdditiveMask.Length >= 3) { overlaysInAvatarSlot[ii].colorData.channelAdditiveMask[2] = neutralizeToMetallic; } } else { if (dimAllButTarget) { if (!originalColors.ContainsKey(i)) { originalColors.Add(i, new Dictionary()); } if (!originalColors[i].ContainsKey(ii)) originalColors[i].Add(ii, overlaysInAvatarSlot[ii].colorData.color); overlaysInAvatarSlot[ii].colorData.color = dimToColor; if (overlaysInAvatarSlot[ii].colorData.channelAdditiveMask.Length >= 3) { overlaysInAvatarSlot[ii].colorData.channelAdditiveMask[2] = dimToMetallic; } } } } } } else { if (dimAllButTarget) { for (int ii = 0; ii < overlaysInAvatarSlot.Count; ii++) { if (!overlaysInRecipe.Contains(overlaysInAvatarSlot[ii].asset.name)) { if (!originalColors.ContainsKey(i)) { originalColors.Add(i, new Dictionary()); } if (!originalColors[i].ContainsKey(ii)) originalColors[i].Add(ii, overlaysInAvatarSlot[ii].colorData.color); overlaysInAvatarSlot[ii].colorData.color = dimToColor; if (overlaysInAvatarSlot[ii].colorData.channelAdditiveMask.Length >= 3) { overlaysInAvatarSlot[ii].colorData.channelAdditiveMask[2] = dimToMetallic; } } } } } } } avatarToPhoto.umaData.dirty = false; avatarToPhoto.umaData.Dirty(false, true, false); } private bool SetBestRenderTexture(string wardrobeSlot = "") { if (wardrobeSlot == "Body" || (!autoPhotosEnabled && textureToPhoto == renderTextureOpts.BodyRenderTexture)) { if (bodyRenderTexture == null) { if (Debug.isDebugBuild) Debug.Log("You need to set the Body Render Texture in the inspector!"); return false; } else { renderTextureToUse = bodyRenderTexture; return true; } } else if (wardrobeSlot.IndexOf("Hair", StringComparison.OrdinalIgnoreCase) > -1 || wardrobeSlot.IndexOf("Face", StringComparison.OrdinalIgnoreCase) > -1 || wardrobeSlot.IndexOf("Head", StringComparison.OrdinalIgnoreCase) > -1 || wardrobeSlot.IndexOf("Helmet", StringComparison.OrdinalIgnoreCase) > -1 || wardrobeSlot.IndexOf("Complexion", StringComparison.OrdinalIgnoreCase) > -1 || wardrobeSlot.IndexOf("Eyebrows", StringComparison.OrdinalIgnoreCase) > -1 || wardrobeSlot.IndexOf("Beard", StringComparison.OrdinalIgnoreCase) > -1 || wardrobeSlot.IndexOf("Ears", StringComparison.OrdinalIgnoreCase) > -1 || (!autoPhotosEnabled && textureToPhoto == renderTextureOpts.HeadRenderTexture)) { if (headRenderTexture == null) { if (Debug.isDebugBuild) Debug.Log("You need to set the Head Render Texture in the inspector!"); return false; } else { renderTextureToUse = headRenderTexture; return true; } } else if (wardrobeSlot.IndexOf("Shoulders", StringComparison.OrdinalIgnoreCase) > -1 || wardrobeSlot.IndexOf("Chest", StringComparison.OrdinalIgnoreCase) > -1 || wardrobeSlot.IndexOf("Arms", StringComparison.OrdinalIgnoreCase) > -1 || (!autoPhotosEnabled && textureToPhoto == renderTextureOpts.ChestRenderTexture) ) { if (chestRenderTexture == null) { if (Debug.isDebugBuild) Debug.Log("You need to set the Chest Render Texture in the inspector!"); return false; } else { renderTextureToUse = chestRenderTexture; return true; } } else if (wardrobeSlot.IndexOf("Hands", StringComparison.OrdinalIgnoreCase) > -1 || (!autoPhotosEnabled && textureToPhoto == renderTextureOpts.HandsRenderTexture)) { if (handsRenderTexture == null) { if (Debug.isDebugBuild) Debug.Log("You need to set the Hands Render Texture in the inspector!"); return false; } else { renderTextureToUse = handsRenderTexture; return true; } } else if (wardrobeSlot.IndexOf("Waist", StringComparison.OrdinalIgnoreCase) > -1 || wardrobeSlot.IndexOf("Legs", StringComparison.OrdinalIgnoreCase) > -1 || (!autoPhotosEnabled && textureToPhoto == renderTextureOpts.LegsRenderTexture)) { if (legsRenderTexture == null) { if (Debug.isDebugBuild) Debug.Log("You need to set the Legs Render Texture in the inspector!"); return false; } else { renderTextureToUse = legsRenderTexture; return true; } } else if (wardrobeSlot.IndexOf("Feet", StringComparison.OrdinalIgnoreCase) > -1 || (!autoPhotosEnabled && textureToPhoto == renderTextureOpts.FeetRenderTexture)) { if (feetRenderTexture == null) { if (Debug.isDebugBuild) Debug.Log("You need to set the Feet Render Texture in the inspector!"); return false; } else { renderTextureToUse = feetRenderTexture; return true; } } else if (wardrobeSlot.IndexOf("Outfit", StringComparison.OrdinalIgnoreCase) > -1 || wardrobeSlot.IndexOf("Underwear", StringComparison.OrdinalIgnoreCase) > -1 || (!autoPhotosEnabled && textureToPhoto == renderTextureOpts.OutfitRenderTexture)) { if (outfitRenderTexture == null) { if (Debug.isDebugBuild) Debug.Log("You need to set the Outfit Render Texture in the inspector!"); return false; } else { renderTextureToUse = outfitRenderTexture; return true; } } else { if (Debug.isDebugBuild) Debug.Log("No suitable render texture found for " + wardrobeSlot + " using Body rendertexture"); renderTextureToUse = bodyRenderTexture; return true; } } } } #endif