using UnityEngine; using System.Collections; using System.Collections.Generic; using UnityEngine.UI; namespace Exoa.Utils { [ExecuteInEditMode] [AddComponentMenu("Alpha Mask")] public class Mask : MonoBehaviour { public enum MappingAxis { X, Y, Z }; [SerializeField] private MappingAxis _maskMappingWorldAxis = MappingAxis.Z; public MappingAxis maskMappingWorldAxis { get { return _maskMappingWorldAxis; } set { ChangeMappingAxis(value, _maskMappingWorldAxis, _invertAxis); _maskMappingWorldAxis = value; } } [SerializeField] private bool _invertAxis = false; public bool invertAxis { get { return _invertAxis; } set { ChangeMappingAxis(_maskMappingWorldAxis, _maskMappingWorldAxis, value); _invertAxis = value; } } [SerializeField] private bool _clampAlphaHorizontally = false; public bool clampAlphaHorizontally { get { return _clampAlphaHorizontally; } set { SetMaskBoolValueInMaterials("_ClampHoriz", value); _clampAlphaHorizontally = value; } } [SerializeField] private bool _clampAlphaVertically = false; public bool clampAlphaVertically { get { return _clampAlphaVertically; } set { SetMaskBoolValueInMaterials("_ClampVert", value); _clampAlphaVertically = value; } } [SerializeField] private float _clampingBorder = 0.01f; public float clampingBorder { get { return _clampingBorder; } set { SetMaskFloatValueInMaterials("_ClampBorder", value); _clampingBorder = value; } } [SerializeField] private bool _useMaskAlphaChannel = false; public bool useMaskAlphaChannel { get { return _useMaskAlphaChannel; } set { SetMaskBoolValueInMaterials("_UseAlphaChannel", value); _useMaskAlphaChannel = value; } } private Shader _maskedSpriteWorldCoordsShader; private Shader _maskedUnlitWorldCoordsShader; void Start() { _maskedSpriteWorldCoordsShader = Shader.Find("Alpha Masked/Sprites Alpha Masked - World Coords"); _maskedUnlitWorldCoordsShader = Shader.Find("Alpha Masked/Unlit Alpha Masked - World Coords"); MeshRenderer maskMeshRenderer = GetComponent(); MeshFilter maskMeshFilter = GetComponent(); if (Application.isPlaying) { if (maskMeshRenderer != null) { maskMeshRenderer.enabled = false; } } #if UNITY_EDITOR else { if (maskMeshFilter == null) { maskMeshFilter = gameObject.AddComponent() as MeshFilter; maskMeshFilter.sharedMesh = new Mesh(); CreateAndAssignQuad(maskMeshFilter.sharedMesh); maskMeshFilter.sharedMesh.name = "Mask Quad"; } if (maskMeshRenderer == null) { maskMeshRenderer = gameObject.AddComponent() as MeshRenderer; maskMeshRenderer.sharedMaterial = new Material(Shader.Find("Unlit/Transparent")); maskMeshRenderer.sharedMaterial.name = "Mask Material"; } maskMappingWorldAxis = _maskMappingWorldAxis; invertAxis = _invertAxis; } #endif } public void ApplyMaskToChildren() { Mask maskTarget = (Mask)this; Shader maskedSpriteWorldCoordsShader = Shader.Find("Alpha Masked/Sprites Alpha Masked - World Coords"); Shader maskedUnlitWorldCoordsShader = Shader.Find("Alpha Masked/Unlit Alpha Masked - World Coords"); Shader spriteDefaultShader = Shader.Find("Sprites/Default"); Shader unlitTransparentShader = Shader.Find("Unlit/Transparent"); Shader UIDefaultShader = Shader.Find("UI/Default"); Shader UIDefaultFontShader = Shader.Find("UI/Default Font"); if ((maskedSpriteWorldCoordsShader == null) || (maskedUnlitWorldCoordsShader == null)) { Debug.Log("Shaders necessary for masking don't seem to be present in the project."); return; } Texture maskTexture = maskTarget.GetComponent().sharedMaterial.mainTexture; List components = new List(); components.AddRange(maskTarget.transform.parent.gameObject.GetComponentsInChildren()); components.AddRange(maskTarget.transform.parent.gameObject.GetComponentsInChildren()); List differentOriginalMaterials = new List(); List differentNewMaterials = new List(); List differentReusableMaterials = GetAllReusableMaterials(components, maskedSpriteWorldCoordsShader, maskedUnlitWorldCoordsShader);// new List(); foreach (Component component in components) { if (component.gameObject != maskTarget.gameObject) { List currSharedMaterials = new List(); List areCurrSharedMaterialsTexts = new List(); if (component is Renderer) { currSharedMaterials.AddRange(((Renderer)component).sharedMaterials); for (int i = 0; i < ((Renderer)component).sharedMaterials.Length; i++) { areCurrSharedMaterialsTexts.Add(false); } } else if (component is Graphic) { currSharedMaterials.Add(((Graphic)component).material); areCurrSharedMaterialsTexts.Add(component is Text); } bool materialsChanged = false; for (int i = 0; i < currSharedMaterials.Count; i++) { Material material = currSharedMaterials[i]; if (!differentOriginalMaterials.Contains(material)) { Material materialToBeUsed = null; if ((material.shader == spriteDefaultShader) || (material.shader == unlitTransparentShader) || (material.shader == UIDefaultShader) || (material.shader == UIDefaultFontShader)) { Material reusableMaterial = FindSuitableMaskedMaterial(material, differentReusableMaterials, maskedSpriteWorldCoordsShader, maskedUnlitWorldCoordsShader, spriteDefaultShader, unlitTransparentShader, UIDefaultShader, UIDefaultFontShader); if (reusableMaterial == null) { differentOriginalMaterials.Add(material); Material newMaterial = new Material(material); if (material.shader == spriteDefaultShader) { newMaterial.shader = maskedSpriteWorldCoordsShader; } else if (material.shader == unlitTransparentShader) { newMaterial.shader = maskedUnlitWorldCoordsShader; } else if (material.shader == UIDefaultShader) { newMaterial.shader = maskedSpriteWorldCoordsShader; } else if (material.shader == UIDefaultFontShader) { newMaterial.shader = maskedSpriteWorldCoordsShader; } if (areCurrSharedMaterialsTexts[i] == true) { newMaterial.SetFloat("_IsThisText", 1); } newMaterial.name = material.name + " Masked"; newMaterial.SetTexture("_AlphaTex", maskTexture); materialToBeUsed = newMaterial; differentNewMaterials.Add(newMaterial); currSharedMaterials[i] = newMaterial; materialsChanged = true; } else { currSharedMaterials[i] = reusableMaterial; materialsChanged = true; reusableMaterial.SetTexture("_AlphaTex", maskTexture); materialToBeUsed = reusableMaterial; } } else if ((material.shader == maskedSpriteWorldCoordsShader) || (material.shader == maskedUnlitWorldCoordsShader)) { if (material.GetTexture("_AlphaTex") != maskTexture) { material.SetTexture("_AlphaTex", maskTexture); } if (areCurrSharedMaterialsTexts[i] == true) { material.SetFloat("_IsThisText", 1); } materialToBeUsed = material; } if (materialToBeUsed != null) { maskTarget.SetMaskMappingAxisInMaterial(maskTarget.maskMappingWorldAxis, materialToBeUsed); maskTarget.SetMaskBoolValueInMaterial("_ClampHoriz", maskTarget.clampAlphaHorizontally, materialToBeUsed); maskTarget.SetMaskBoolValueInMaterial("_ClampVert", maskTarget.clampAlphaVertically, materialToBeUsed); maskTarget.SetMaskBoolValueInMaterial("_UseAlphaChannel", maskTarget.useMaskAlphaChannel, materialToBeUsed); } } else { int index = differentOriginalMaterials.IndexOf(material); currSharedMaterials[i] = differentNewMaterials[index]; materialsChanged = true; } } if (materialsChanged == true) { if (component is Renderer) { ((Renderer)component).sharedMaterials = currSharedMaterials.ToArray(); } else if (component is Graphic) { ((Graphic)component).material = currSharedMaterials[0]; } } } } Debug.Log("Mask applied."); } private Material FindSuitableMaskedMaterial(Material nonMaskedMaterial, List differentReusableMaterials, Shader maskedSpriteWorldCoordsShader, Shader maskedUnlitWorldCoordsShader, Shader spriteDefaultShader, Shader unlitTransparentShader, Shader UIDefaultShader, Shader UIDefaultFontShader) { foreach (Material material in differentReusableMaterials) { if ((nonMaskedMaterial.shader == spriteDefaultShader) && (material.shader == maskedSpriteWorldCoordsShader)) { if ((material.name == nonMaskedMaterial.name + " Masked") && (material.GetColor("_Color") == nonMaskedMaterial.GetColor("_Color")) && (material.GetFloat("PixelSnap") == nonMaskedMaterial.GetFloat("PixelSnap"))) { return material; } } else if ((nonMaskedMaterial.shader == unlitTransparentShader) && (material.shader == maskedUnlitWorldCoordsShader)) { if ((material.name == nonMaskedMaterial.name + " Masked") && (material.mainTexture == nonMaskedMaterial.mainTexture)) { return material; } } else if ((nonMaskedMaterial.shader == UIDefaultFontShader) && (material.shader == maskedSpriteWorldCoordsShader)) { if ((material.name == nonMaskedMaterial.name + " Masked") && (material.GetColor("_Color") == nonMaskedMaterial.GetColor("_Color"))) { return material; } } } return null; } private List GetAllReusableMaterials(List components, Shader maskedSpriteWorldCoordsShader, Shader maskedUnlitWorldCoordsShader) { List differentReusableMaterials = new List(); Mask maskTarget = (Mask)this; foreach (Component component in components) { if (component.gameObject != maskTarget.gameObject) { List currSharedMaterials = new List(); if (component is Renderer) { currSharedMaterials.AddRange(((Renderer)component).sharedMaterials); } else if (component is Graphic) { currSharedMaterials.Add(((Graphic)component).material); } //Material[] currSharedMaterials = renderer.sharedMaterials; for (int i = 0; i < currSharedMaterials.Count; i++) { Material material = currSharedMaterials[i]; if ((material.shader == maskedSpriteWorldCoordsShader) || (material.shader == maskedUnlitWorldCoordsShader)) { differentReusableMaterials.Add(material); } } } } return differentReusableMaterials; } void Update() { if (_maskedSpriteWorldCoordsShader == null) { _maskedSpriteWorldCoordsShader = Shader.Find("Alpha Masked/Sprites Alpha Masked - World Coords"); } if (_maskedUnlitWorldCoordsShader == null) { _maskedUnlitWorldCoordsShader = Shader.Find("Alpha Masked/Unlit Alpha Masked - World Coords"); } if ((_maskedSpriteWorldCoordsShader == null) || (_maskedUnlitWorldCoordsShader == null)) { Debug.Log("Shaders necessary for masking don't seem to be present in the project."); return; } if (transform.hasChanged == true) { if ((maskMappingWorldAxis == MappingAxis.X) && ((Mathf.Abs(Mathf.DeltaAngle(transform.eulerAngles.x, 0)) > 0.01f) || (Mathf.Abs(Mathf.DeltaAngle(transform.eulerAngles.y, invertAxis ? -90 : 90)) > 0.01f))) { Debug.Log("You cannot edit X and Y values of the Mask transform rotation!"); transform.eulerAngles = new Vector3(0, invertAxis ? 270 : 90, transform.eulerAngles.z); } else if ((maskMappingWorldAxis == MappingAxis.Y) && ((Mathf.Abs(Mathf.DeltaAngle(transform.eulerAngles.x, invertAxis ? -90 : 90)) > 0.01f) || (Mathf.Abs(Mathf.DeltaAngle(transform.eulerAngles.z, 0)) > 0.01f))) { Debug.Log("You cannot edit X and Z values of the Mask transform rotation!"); transform.eulerAngles = new Vector3(invertAxis ? -90 : 90, transform.eulerAngles.y, 0); } else if ((maskMappingWorldAxis == MappingAxis.Z) && ((Mathf.Abs(Mathf.DeltaAngle(transform.eulerAngles.x, 0)) > 0.01f) || (Mathf.Abs(Mathf.DeltaAngle(transform.eulerAngles.y, (invertAxis ? -180 : 0))) > 0.01f))) { Debug.Log("You cannot edit X and Y values of the Mask transform rotation!"); transform.eulerAngles = new Vector3(0, invertAxis ? -180 : 0, transform.eulerAngles.z); } if (transform.parent != null) { Renderer[] renderers = transform.parent.gameObject.GetComponentsInChildren(); Graphic[] UIComponents = transform.parent.gameObject.GetComponentsInChildren(); List differentMaterials = new List(); // Needed for UI Screen Space - Overlay support Dictionary UIMaterialToGraphic = new Dictionary(); foreach (Renderer renderer in renderers) { if (renderer.gameObject != gameObject) { foreach (Material material in renderer.sharedMaterials) { if (!differentMaterials.Contains(material)) { differentMaterials.Add(material); } } } } foreach (Graphic UIComponent in UIComponents) { if (UIComponent.gameObject != gameObject) { if (!differentMaterials.Contains(UIComponent.material)) { differentMaterials.Add(UIComponent.material); // Needed for UI "Screen Space - Overlay" support Canvas currCanvas = UIComponent.canvas; if ((currCanvas.renderMode == RenderMode.ScreenSpaceOverlay) || (currCanvas.renderMode == RenderMode.ScreenSpaceCamera) && (currCanvas.worldCamera == null)) { UIMaterialToGraphic.Add(differentMaterials[differentMaterials.Count - 1], UIComponent); } } } } foreach (Material material in differentMaterials) { if ((material.shader.ToString() == _maskedSpriteWorldCoordsShader.ToString()) && (material.shader.GetInstanceID() != _maskedSpriteWorldCoordsShader.GetInstanceID())) { Debug.Log("There seems to be more than one masked shader in the project with the same display name, and it's preventing the mask from being properly applied."); _maskedSpriteWorldCoordsShader = null; } if ((material.shader.ToString() == _maskedUnlitWorldCoordsShader.ToString()) && (material.shader.GetInstanceID() != _maskedUnlitWorldCoordsShader.GetInstanceID())) { Debug.Log("There seems to be more than one masked shader in the project with the same display name, and it's preventing the mask from being properly applied."); _maskedUnlitWorldCoordsShader = null; } if ((material.shader == _maskedSpriteWorldCoordsShader) || (material.shader == _maskedUnlitWorldCoordsShader)) { material.DisableKeyword("_SCREEN_SPACE_UI"); Vector2 scale = new Vector2(1f / transform.lossyScale.x, 1f / transform.lossyScale.y); Vector2 offset = Vector2.zero; float rotationAngle = 0; int sign = 1; if (maskMappingWorldAxis == MappingAxis.X) { sign = (invertAxis ? 1 : -1); offset = new Vector2(-transform.position.z, -transform.position.y); rotationAngle = sign * transform.eulerAngles.z; } else if (maskMappingWorldAxis == MappingAxis.Y) { offset = new Vector2(-transform.position.x, -transform.position.z); rotationAngle = -transform.eulerAngles.y; } else if (maskMappingWorldAxis == MappingAxis.Z) { sign = (invertAxis ? -1 : 1); offset = new Vector2(-transform.position.x, -transform.position.y); rotationAngle = sign * transform.eulerAngles.z; } // Needed for UI anchor support RectTransform maskRectTransform = GetComponent(); if (maskRectTransform != null) { Rect rect = maskRectTransform.rect; offset += (Vector2)(transform.right * (maskRectTransform.pivot.x - 0.5f) * rect.width * transform.lossyScale.x + transform.up * (maskRectTransform.pivot.y - 0.5f) * rect.height * transform.lossyScale.y); scale.x /= rect.width; scale.y /= rect.height; #if UNITY_EDITOR if (!Application.isPlaying) { MeshFilter maskMeshFilter = GetComponent(); if (maskMeshFilter != null) { CreateAndAssignQuad(maskMeshFilter.sharedMesh, rect.xMin, rect.xMax, rect.yMin, rect.yMax); } } #endif } #if UNITY_EDITOR else { if (!Application.isPlaying) { MeshFilter maskMeshFilter = GetComponent(); if (maskMeshFilter != null) { CreateAndAssignQuad(maskMeshFilter.sharedMesh); } } } #endif // UI "Screen Space - Overlay mode" or "Screen Space - Camera", where the camera is null (actually, falls back to "Overlay") if (UIMaterialToGraphic.ContainsKey(material)) { offset = UIMaterialToGraphic[material].transform.InverseTransformVector(offset); switch (maskMappingWorldAxis) { case MappingAxis.X: offset.x *= UIMaterialToGraphic[material].transform.lossyScale.z; offset.y *= UIMaterialToGraphic[material].transform.lossyScale.y; break; case MappingAxis.Y: offset.x *= UIMaterialToGraphic[material].transform.lossyScale.x; offset.y *= UIMaterialToGraphic[material].transform.lossyScale.z; break; case MappingAxis.Z: offset.x *= UIMaterialToGraphic[material].transform.lossyScale.x; offset.y *= UIMaterialToGraphic[material].transform.lossyScale.y; break; } Canvas currUIElementCanvas = UIMaterialToGraphic[material].canvas; offset /= currUIElementCanvas.scaleFactor; offset = RotateVector(offset, UIMaterialToGraphic[material].transform.eulerAngles); offset += currUIElementCanvas.GetComponent().sizeDelta * 0.5f; scale *= currUIElementCanvas.scaleFactor; material.EnableKeyword("_SCREEN_SPACE_UI"); } Vector2 scaleTexture = gameObject.GetComponent().sharedMaterial.mainTextureScale; scale.x *= scaleTexture.x; scale.y *= scaleTexture.y; scale.x *= sign; Vector2 offsetTemporary = offset; float s = Mathf.Sin(-rotationAngle * Mathf.Deg2Rad); float c = Mathf.Cos(-rotationAngle * Mathf.Deg2Rad); offset.x = (c * offsetTemporary.x - s * offsetTemporary.y) * scale.x + 0.5f * scaleTexture.x; offset.y = (s * offsetTemporary.x + c * offsetTemporary.y) * scale.y + 0.5f * scaleTexture.y; offset += gameObject.GetComponent().sharedMaterial.mainTextureOffset; material.SetTextureOffset("_AlphaTex", offset); material.SetTextureScale("_AlphaTex", scale); material.SetFloat("_MaskRotation", rotationAngle * Mathf.Deg2Rad); } } } } } private Vector3 RotateVector(Vector3 point, Vector3 angles) { return Quaternion.Euler(angles) * point; } private void SetMaskMappingAxisInMaterials(MappingAxis mappingAxis) { if (transform.parent == null) { return; } Renderer[] renderers = transform.parent.gameObject.GetComponentsInChildren(); Graphic[] UIComponents = transform.parent.gameObject.GetComponentsInChildren(); List differentMaterials = new List(); foreach (Renderer renderer in renderers) { if (renderer.gameObject != gameObject) { foreach (Material material in renderer.sharedMaterials) { if (!differentMaterials.Contains(material)) { differentMaterials.Add(material); SetMaskMappingAxisInMaterial(mappingAxis, material); } } } } foreach (Graphic UIComponent in UIComponents) { if (UIComponent.gameObject != gameObject) { if (!differentMaterials.Contains(UIComponent.material)) { differentMaterials.Add(UIComponent.material); SetMaskMappingAxisInMaterial(mappingAxis, UIComponent.material); } } } } public void SetMaskMappingAxisInMaterial(MappingAxis mappingAxis, Material material) { if ((material.shader == _maskedSpriteWorldCoordsShader) || (material.shader == _maskedUnlitWorldCoordsShader)) { switch (mappingAxis) { case MappingAxis.X: material.SetFloat("_Axis", 0); material.EnableKeyword("_AXIS_X"); material.DisableKeyword("_AXIS_Y"); material.DisableKeyword("_AXIS_Z"); break; case MappingAxis.Y: material.SetFloat("_Axis", 1); material.DisableKeyword("_AXIS_X"); material.EnableKeyword("_AXIS_Y"); material.DisableKeyword("_AXIS_Z"); break; case MappingAxis.Z: material.SetFloat("_Axis", 2); material.DisableKeyword("_AXIS_X"); material.DisableKeyword("_AXIS_Y"); material.EnableKeyword("_AXIS_Z"); break; } } } private void SetMaskFloatValueInMaterials(string variable, float value) { if (transform.parent == null) { return; } Renderer[] renderers = transform.parent.gameObject.GetComponentsInChildren(); Graphic[] UIComponents = transform.parent.gameObject.GetComponentsInChildren(); List differentMaterials = new List(); foreach (Renderer renderer in renderers) { if (renderer.gameObject != gameObject) { foreach (Material material in renderer.sharedMaterials) { if (!differentMaterials.Contains(material)) { differentMaterials.Add(material); material.SetFloat(variable, value); } } } } foreach (Graphic UIComponent in UIComponents) { if (UIComponent.gameObject != gameObject) { if (!differentMaterials.Contains(UIComponent.material)) { differentMaterials.Add(UIComponent.material); UIComponent.material.SetFloat(variable, value); } } } } private void SetMaskBoolValueInMaterials(string variable, bool value) { if (transform.parent == null) { return; } Renderer[] renderers = transform.parent.gameObject.GetComponentsInChildren(); Graphic[] UIComponents = transform.parent.gameObject.GetComponentsInChildren(); List differentMaterials = new List(); foreach (Renderer renderer in renderers) { if (renderer.gameObject != gameObject) { foreach (Material material in renderer.sharedMaterials) { if (!differentMaterials.Contains(material)) { differentMaterials.Add(material); SetMaskBoolValueInMaterial(variable, value, material); } } } } foreach (Graphic UIComponent in UIComponents) { if (UIComponent.gameObject != gameObject) { if (!differentMaterials.Contains(UIComponent.material)) { differentMaterials.Add(UIComponent.material); SetMaskBoolValueInMaterial(variable, value, UIComponent.material); } } } } public void SetMaskBoolValueInMaterial(string variable, bool value, Material material) { if ((material.shader == _maskedSpriteWorldCoordsShader) || (material.shader == _maskedUnlitWorldCoordsShader)) { material.SetFloat(variable, (value ? 1 : 0)); if (value == true) { material.EnableKeyword(variable.ToUpper() + "_ON"); } else { material.DisableKeyword(variable.ToUpper() + "_ON"); } } } private void CreateAndAssignQuad(Mesh mesh, float minX = -0.5f, float maxX = 0.5f, float minY = -0.5f, float maxY = 0.5f) { // assign vertices Vector3[] vertices = new Vector3[4]; vertices[0] = new Vector3(minX, minY, 0); vertices[1] = new Vector3(maxX, minY, 0); vertices[2] = new Vector3(minX, maxY, 0); vertices[3] = new Vector3(maxX, maxY, 0); mesh.vertices = vertices; // assign triangles int[] tri = new int[6]; // Lower left triangle. tri[0] = 0; tri[1] = 2; tri[2] = 1; // Upper right triangle. tri[3] = 2; tri[4] = 3; tri[5] = 1; mesh.triangles = tri; // assign normals Vector3[] normals = new Vector3[4]; normals[0] = -Vector3.forward; normals[1] = -Vector3.forward; normals[2] = -Vector3.forward; normals[3] = -Vector3.forward; mesh.normals = normals; // assign UVs Vector2[] uv = new Vector2[4]; uv[0] = new Vector2(0, 0); uv[1] = new Vector2(1, 0); uv[2] = new Vector2(0, 1); uv[3] = new Vector2(1, 1); mesh.uv = uv; } public void SetMaskRendererActive(bool value) { if (GetComponent() != null) { if (value == true) { GetComponent().enabled = true; } else { GetComponent().enabled = false; } } } private void ChangeMappingAxis(MappingAxis currMaskMappingWorldAxis, MappingAxis prevMaskMappingWorldAxis, bool currInvertAxis) { if (currMaskMappingWorldAxis == MappingAxis.X) { if (prevMaskMappingWorldAxis == MappingAxis.Y) { transform.eulerAngles = new Vector3(0, currInvertAxis ? -90 : 90, transform.eulerAngles.y); } else { transform.eulerAngles = new Vector3(0, currInvertAxis ? -90 : 90, transform.eulerAngles.z); } } else if (currMaskMappingWorldAxis == MappingAxis.Y) { if (prevMaskMappingWorldAxis == MappingAxis.Y) { transform.eulerAngles = new Vector3(currInvertAxis ? -90 : 90, transform.eulerAngles.y, 0); } else { transform.eulerAngles = new Vector3(currInvertAxis ? -90 : 90, transform.eulerAngles.z, 0); } } else if (currMaskMappingWorldAxis == MappingAxis.Z) { if (prevMaskMappingWorldAxis == MappingAxis.Y) { transform.eulerAngles = new Vector3(0, currInvertAxis ? -180 : 0, transform.eulerAngles.y); } else { transform.eulerAngles = new Vector3(0, currInvertAxis ? -180 : 0, transform.eulerAngles.z); } } SetMaskMappingAxisInMaterials(currMaskMappingWorldAxis); } } }