using MTE.Undo; using System.Collections.Generic; using UnityEditor; using UnityEngine; using System.IO; namespace MTE { internal class FlowPainter : IEditor { public int Id { get; } = 8; public bool Enabled { get; set; } = true; public string Name { get; } = "FlowPainter"; public Texture Icon { get; } = MTEStyles.PaintFlowToolIcon; public bool WantMouseMove { get; } = false; public bool WillEditMesh { get; } = false; public enum PaintMode { Fixed, Movement, PinchInflate, Vortex, } #region Parameters #region Constant // default const PaintMode DefaultMode = PaintMode.Fixed; const int DefaultBrushIndex = 0; const float DefaultBrushSize = 1f; const float DefaultStrength = 0.5f; const float DefaultSpeed = 0.05f; const float DefaultDirection = 0f; const bool DefaultPinching = true; const bool DefaultVortexRotationClockWise = true; // min/max const float MinBrushSize = 0.1f; const float MaxBrushSize = 50f; const float MinSpeed = 0.01f; const float MaxSpeed = 1f; #endregion private PaintMode mode; private int brushIndex; private float strength; private float direction; private float brushSize; private float speed; private bool pinching; private bool vortexRotationClockWise; public PaintMode Mode { get { return mode; } set { if(mode != value) { EditorPrefs.SetInt("MTE_FlowPainter.mode", (int)value); mode = value; } } } /// /// Brush index /// public int BrushIndex { get { return brushIndex; } set { if (brushIndex != value) { //Update preview //if (previewObj != null) //{ // SetPreviewMaskTexture(value); //} EditorPrefs.SetInt("MTE_FlowPainter.brushIndex", value); brushIndex = value; } } } /// /// Brush size (unit: 1 BrushUnit) /// public float BrushSize { get { return brushSize; } set { value = Mathf.Clamp(value, MinBrushSize, MaxBrushSize); if (!MathEx.AmostEqual(brushSize, value)) { brushSize = value; EditorPrefs.SetFloat("MTE_FlowPainter.brushSize", value); //if (previewObj != null) //{ // SetPreviewSize(BrushSizeInU3D / 2); //} } } } private float BrushSizeInU3D { get { return BrushSize * Settings.BrushUnit; } } /// /// Speed /// public float Speed { get { return speed; } set { value = Mathf.Clamp(value, MinSpeed, MaxSpeed); if (!MathEx.AmostEqual(value, speed)) { speed = value; EditorPrefs.SetFloat("MTE_FlowPainter.speed", value); } } } /// /// Flow direction, angle to north(+u) /// public float Direction { get { return direction; } set { value = Mathf.Clamp(value, 0, 2 * Mathf.PI); if (!MathEx.AmostEqual(value, direction)) { EditorPrefs.SetFloat("MTE_FlowPainter.direction", direction); direction = value; } } } public float Strength { get { return strength; } set { value = Mathf.Clamp(value, 0, 1); if (value != strength) { EditorPrefs.SetFloat("MTE_FlowPainter.strength", value); strength = value; } } } /// /// the color /// public Color TheColor { get { var r = 0.5f - Strength * Mathf.Cos(direction) * 0.5f; var g = 0.5f + Strength * Mathf.Sin(direction) * 0.5f; return new Color(r, g, 0); } } public bool Pinching { get { return pinching; } set { if (value != pinching) { pinching = value; EditorPrefs.SetBool("MTE_FlowPainter.pinching", value); } } } public bool VortexRotationClockWise { get { return vortexRotationClockWise; } set { if (value != vortexRotationClockWise) { vortexRotationClockWise = value; EditorPrefs.SetBool("MTE_FlowPainter.vortexRotationClockWise", value); } } } #endregion public FlowPainter() { MTEContext.EnableEvent += (sender, args) => { if (MTEContext.editor == this) { LoadSavedParamter(); //LoadPreview(); } }; MTEContext.EditTypeChangedEvent += (sender, args) => { if (MTEContext.editor == this) { LoadSavedParamter(); //LoadPreview(); } else { //UnLoadPreview(); } }; //MTEContext.DisableEvent += (sender, args) => //{ // UnLoadPreview(); //}; // Load default parameters brushIndex = DefaultBrushIndex; brushSize = DefaultBrushSize; speed = DefaultSpeed; } private void LoadSavedParamter() { // Load parameters from the EditorPrefs mode = (PaintMode)EditorPrefs.GetInt("MTE_FlowPainter.mode", (int)DefaultMode); brushIndex = EditorPrefs.GetInt("MTE_FlowPainter.brushIndex", DefaultBrushIndex); brushSize = EditorPrefs.GetFloat("MTE_FlowPainter.brushSize", DefaultBrushSize); strength = EditorPrefs.GetFloat("MTE_FlowPainter.strength", DefaultStrength); speed = EditorPrefs.GetFloat("MTE_FlowPainter.speed", DefaultSpeed); direction = EditorPrefs.GetFloat("MTE_FlowPainter.direction", DefaultDirection); pinching = EditorPrefs.GetBool("MTE_FlowPainter.pinching", DefaultPinching); vortexRotationClockWise = EditorPrefs.GetBool("MTE_FlowPainter.vortexRotationClockWise", DefaultVortexRotationClockWise); } public HashSet DefineHotkeys() { return new HashSet { new Hotkey(this, KeyCode.LeftBracket, () => { BrushSize -= 1; MTEEditorWindow.Instance.Repaint(); }), new Hotkey(this, KeyCode.RightBracket, () => { BrushSize += 1; MTEEditorWindow.Instance.Repaint(); }), new Hotkey(this, KeyCode.Minus, () => { Speed -= 0.01f; MTEEditorWindow.Instance.Repaint(); }), new Hotkey(this, KeyCode.Equals, () => { Speed += 0.01f; MTEEditorWindow.Instance.Repaint(); }), new Hotkey(this, KeyCode.Space, () => { if (Mode == PaintMode.PinchInflate) { //toggle Pinching/Inflate Pinching = !Pinching; MTEEditorWindow.Instance.Repaint(); } else if (Mode == PaintMode.Vortex) { //toggle clockwise/counter-clockwise vortex VortexRotationClockWise = !VortexRotationClockWise; MTEEditorWindow.Instance.Repaint(); } }) }; } public string Header { get { return StringTable.Get(C.PaintFlow_Header); } } public string Description { get { return StringTable.Get(C.PaintFlow_Description); } } bool IsGUILoaded = false; #region GUI contents GUIContent[] ModeTextContents; #endregion Vector2 po; public void DoArgsGUI() { if (!IsGUILoaded) { ModeTextContents = new [] { new GUIContent(MTEStyles.flowToolTexture_Fixed), new GUIContent(MTEStyles.flowToolTexture_Movement), new GUIContent(MTEStyles.flowToolTexture_Pinch), new GUIContent(MTEStyles.flowToolTexture_Vortex), }; IsGUILoaded = true; } // Settings if (!Settings.CompactGUI) { GUILayout.Label(StringTable.Get(C.Mode), MTEStyles.SubHeader); } Mode = (PaintMode)GUILayout.Toolbar((int)Mode, ModeTextContents, Settings.CompactGUI ? GUILayout.Height(32) : GUILayout.Height(64)); if(mode == PaintMode.Fixed) { if (!Settings.CompactGUI) { EditorGUILayout.HelpBox(StringTable.Get(C.Info_ModeDescription_Fixed), MessageType.Info); GUILayout.Label(StringTable.Get(C.Direction), MTEStyles.SubHeader); } EditorGUILayout.BeginHorizontal("box", GUILayout.Height(130)); { Rect aRect = GUILayoutUtility.GetRect(128, 128, 128, 128, GUILayout.ExpandWidth(false), GUILayout.ExpandHeight(false)); po = aRect.center + new Vector2(Mathf.Cos(direction), -Mathf.Sin(direction)) * strength * 55;//55 is the outer radius of *flowDirectionSelectorTexture* GUI.DrawTexture(aRect, MTEStyles.flowDirectionSelectorTexture); if (Event.current.type == EventType.MouseDown || Event.current.type == EventType.MouseDrag) { if (aRect.Contains(Event.current.mousePosition)) { po = Event.current.mousePosition; var dir = (po - aRect.center); Strength = dir.magnitude / 55; Direction = MathEx.AngleFromTo(dir, Vector2.right, new Vector3(0, 0, 1)) * Mathf.Deg2Rad; MTEEditorWindow.Instance.Repaint(); SceneView.RepaintAll(); } } if ((aRect.center - po).sqrMagnitude > 0.01f) { Handles.BeginGUI(); GUIEx.DrawLine(new Vector3(aRect.center.x, aRect.center.y), new Vector3(po.x, po.y), 2); EditorGUI.DrawRect(new Rect(po.x - 2.5f, po.y - 2.5f, 5, 5), Color.blue); Handles.EndGUI(); } EditorGUILayout.BeginVertical(); { GUILayout.FlexibleSpace(); var old = EditorGUIUtility.labelWidth; EditorGUIUtility.labelWidth = 60; Strength = EditorGUILayout.FloatField(StringTable.Get(C.Strength), Strength); EditorGUILayout.Space(); var directionAngle = EditorGUILayout.FloatField(StringTable.Get(C.AngleInDegrees), Direction * Mathf.Rad2Deg); Direction = directionAngle * Mathf.Deg2Rad; EditorGUIUtility.labelWidth = old; GUILayout.FlexibleSpace(); } EditorGUILayout.EndVertical(); } EditorGUILayout.EndHorizontal(); EditorGUILayout.BeginVertical("box"); { EditorGUILayout.BeginHorizontal(); GUILayout.FlexibleSpace(); bool NW = GUILayout.Button("↖", GUILayout.Width(32), GUILayout.Height(32)); bool N = GUILayout.Button("↑", GUILayout.Width(32), GUILayout.Height(32)); bool NE = GUILayout.Button("↗", GUILayout.Width(32), GUILayout.Height(32)); GUILayout.FlexibleSpace(); EditorGUILayout.EndHorizontal(); EditorGUILayout.BeginHorizontal(); GUILayout.FlexibleSpace(); bool W = GUILayout.Button("←", GUILayout.Width(32), GUILayout.Height(32)); bool NOMOVE = GUILayout.Button("C", GUILayout.Width(32), GUILayout.Height(32)); bool E = GUILayout.Button("→", GUILayout.Width(32), GUILayout.Height(32)); GUILayout.FlexibleSpace(); EditorGUILayout.EndHorizontal(); EditorGUILayout.BeginHorizontal(); GUILayout.FlexibleSpace(); bool SW = GUILayout.Button("↙", GUILayout.Width(32), GUILayout.Height(32)); bool S = GUILayout.Button("↓", GUILayout.Width(32), GUILayout.Height(32)); bool SE = GUILayout.Button("↘", GUILayout.Width(32), GUILayout.Height(32)); GUILayout.FlexibleSpace(); EditorGUILayout.EndHorizontal(); if (NW) { Strength = 1; Direction = 0.75f * Mathf.PI; } if (N) { Strength = 1; Direction = 0.5f * Mathf.PI; } if (NE) { Strength = 1; Direction = 0.25f * Mathf.PI; } if (W) { Strength = 1; Direction = Mathf.PI; } if (NOMOVE) { Strength = 0; Direction = -1; } if (E) { Strength = 1; Direction = 0; } if (SW) { Strength = 1; Direction = 1.25f * Mathf.PI; } if (S) { Strength = 1; Direction = 1.5f * Mathf.PI; } if (SE) { Strength = 1; Direction = 1.75f * Mathf.PI; } } EditorGUILayout.EndVertical(); } else if (mode == PaintMode.Movement) { if (!Settings.CompactGUI) { EditorGUILayout.HelpBox(StringTable.Get(C.Info_ModeDescription_Movement), MessageType.Info); } Strength = EditorGUILayoutEx.Slider(StringTable.Get(C.Strength), Strength, 0, 1); } else if (mode == PaintMode.PinchInflate) { if (!Settings.CompactGUI) { EditorGUILayout.HelpBox(StringTable.Get(C.Info_ModeDescription_Pinch), MessageType.Info); } Strength = EditorGUILayoutEx.Slider(StringTable.Get(C.Strength), Strength, 0, 1); } else if (mode == PaintMode.Vortex) { if (!Settings.CompactGUI) { EditorGUILayout.HelpBox(StringTable.Get(C.Info_ModeDescription_Vortex), MessageType.Info); } Strength = EditorGUILayoutEx.Slider(StringTable.Get(C.Strength), Strength, 0, 1); } // Shared settings if (!Settings.CompactGUI) { GUILayout.Label(StringTable.Get(C.Settings), MTEStyles.SubHeader); } BrushSize = EditorGUILayoutEx.Slider(StringTable.Get(C.Size), "-", "+", BrushSize, MinBrushSize, MaxBrushSize); Speed = EditorGUILayoutEx.Slider(StringTable.Get(C.Speed), "[", "]", Speed, MinSpeed, MaxSpeed); // Tools if (!Settings.CompactGUI) { EditorGUILayout.Space(); GUILayout.Label(StringTable.Get(C.Tools), MTEStyles.SubHeader); } EditorGUILayout.BeginVertical(); { if(Mode == PaintMode.Fixed) { EditorGUILayout.BeginHorizontal(); { if (GUILayout.Button(StringTable.Get(C.CreateFlowMap), GUILayout.Width(100), GUILayout.Height(40))) { CreateFlowMap(TheColor); } GUILayout.Space(20); EditorGUILayout.LabelField(StringTable.Get(C.Info_ToolDescription_CreateFlowMap), MTEStyles.labelFieldWordwrap); GUI.enabled = true; } EditorGUILayout.EndHorizontal(); } } EditorGUILayout.EndVertical(); GUILayout.FlexibleSpace(); EditorGUILayout.HelpBox(StringTable.Get(C.Info_WillBeSavedInstantly), MessageType.Info, true); } internal struct TextureModifyGroup { public GameObject gameObject; public Texture2D texture; public Rect texelRect; public Vector2 center; public TextureModifyGroup(GameObject gameObject, Texture2D texture, Rect texelRect, Vector2 center) { this.gameObject = gameObject; this.texture = texture; this.texelRect = texelRect; this.center = center; } } // buffers of editing helpers private readonly List modifyGroups = new List(4); private readonly List modifyingSections = new List(2); Vector2 mouseDelta; public void OnSceneGUI() { var e = Event.current; mouseDelta = e.delta;//used by PaintMode.Movement if (e.commandName == "UndoRedoPerformed") { SceneView.RepaintAll(); return; } if (!(EditorWindow.mouseOverWindow is SceneView)) { return; } if (Mode == PaintMode.Fixed) { RaycastHit hit; Ray ray1 = HandleUtility.GUIPointToWorldRay(e.mousePosition); if (Physics.Raycast(ray1, out hit, Mathf.Infinity, 1 << MTEContext.TargetLayer//only hit target layer )) { //check tag if (!hit.transform.CompareTag(MTEContext.TargetTag)) { return; } if (strength > 0.01f) { Handles.ArrowHandleCap(0, hit.point, Quaternion.Euler(0, -(Direction - 0.5f * Mathf.PI) * Mathf.Rad2Deg, 0), BrushSizeInU3D + Strength * Settings.PointSize, EventType.Repaint); } else { Handles.color = Color.red; Handles.SphereHandleCap(0, hit.point, Quaternion.identity, Settings.PointSize, EventType.Repaint); } } } // do nothing when mouse middle/right button, alt key is pressed if (e.button != 0 || e.alt) return; if(Mode == PaintMode.Fixed) { // hold control key and scroll wheel to change flow direction if (e.control && !e.isKey && e.type == EventType.ScrollWheel) { float oldDirection = Direction; float direction = oldDirection; ChangeDirection(e.delta.y, ref direction); if (Mathf.Abs(direction - oldDirection) > Mathf.Epsilon) { MTEEditorWindow.Instance.Repaint(); Direction = direction; } e.Use(); } } if(Mode == PaintMode.Movement) { this.delta[Time.frameCount % smoothing] = new Vector3(-this.mouseDelta.x, 0f, -this.mouseDelta.y); this.vector = Vector3.zero; foreach (Vector3 b in this.delta) { this.vector += b; } this.vector /= (float)smoothing; this.vector.y = 0f; this.vector *= Time.fixedDeltaTime; this.vector.x = this.vector.x * 0.5f + 0.5f; this.vector.z = this.vector.z * 0.5f + 0.5f; } HandleUtility.AddDefaultControl(0); var ray = HandleUtility.GUIPointToWorldRay(e.mousePosition); RaycastHit raycastHit; if (Physics.Raycast(ray, out raycastHit, Mathf.Infinity, 1 << MTEContext.TargetLayer//only hit target layer )) { //check tag if (!raycastHit.transform.CompareTag(MTEContext.TargetTag)) { return; } var currentBrushSize = BrushSizeInU3D; if (Settings.ShowBrushRect) { Utility.ShowBrushRect(raycastHit.point, currentBrushSize); } var hitPoint = raycastHit.point; if (Settings.FlashAffectedVertex) { Handles.color = Utility.GetFlashingColor(); } else { Handles.color = Color.green; } Handles.CircleHandleCap(0, hitPoint, Quaternion.LookRotation(Vector3.up), BrushSizeInU3D/2, EventType.Repaint); // collect modifiy group modifyGroups.Clear(); foreach (var target in MTEContext.Targets) { // check if we can paint on target var meshRenderer = target.GetComponent(); if (meshRenderer == null) continue; var meshFilter = target.GetComponent(); if (meshFilter == null) continue; var mesh = meshFilter.sharedMesh; if (mesh == null) continue; var material = meshRenderer.sharedMaterial; if (!material.HasProperty("_FlowMap")) { continue; } Vector2 textureUVMin;//min texture uv that is to be modified Vector2 textureUVMax;//max texture uv that is to be modified Vector2 centerUV; { // check if the brush rect intersects with the `Mesh.bounds` of this target var hitPointLocal = target.transform.InverseTransformPoint(hitPoint);//convert hit point from world space to target mesh space Bounds brushBounds = new Bounds(center: new Vector3(hitPointLocal.x, 0, hitPointLocal.z), size: new Vector3(currentBrushSize, 99999, currentBrushSize)); Bounds meshBounds = mesh.bounds;//TODO rename this centerUV = new Vector2( MathEx.NormalizeTo01(meshBounds.min.x, meshBounds.max.x, hitPointLocal.x), MathEx.NormalizeTo01(meshBounds.min.z, meshBounds.max.z, hitPointLocal.z));//FIXME should we use a coordinate in world space? Bounds paintingBounds; var intersected = meshBounds.Intersect(brushBounds, out paintingBounds); if (!intersected) continue; Vector2 paintingBounds2D_min = new Vector2(paintingBounds.min.x, paintingBounds.min.z); Vector2 paintingBounds2D_max = new Vector2(paintingBounds.max.x, paintingBounds.max.z); Vector2 meshRendererBounds2D_min = new Vector2(meshBounds.min.x, meshBounds.min.z); Vector2 meshRendererBounds2D_max = new Vector2(meshBounds.max.x, meshBounds.max.z); //calculate which part of texture should be modified textureUVMin = MathEx.NormalizeTo01(rangeMin: meshRendererBounds2D_min, rangeMax: meshRendererBounds2D_max, value: paintingBounds2D_min); textureUVMax = MathEx.NormalizeTo01(rangeMin: meshRendererBounds2D_min, rangeMax: meshRendererBounds2D_max, value: paintingBounds2D_max); if (Settings.DebugMode) { Handles.color = Color.blue; HandlesEx.DrawRectangle(paintingBounds2D_min, paintingBounds2D_max); Handles.color = new Color(255, 128, 166); HandlesEx.DrawRectangle(meshRendererBounds2D_min, meshRendererBounds2D_max); } } if (e.button == 0 && (e.type == EventType.MouseDown || e.type == EventType.MouseDrag)) { Texture2D targetTexture = null; if (material.GetTexture("_FlowMap") != null) { targetTexture = (Texture2D)material.GetTexture("_FlowMap"); } else { MTEDebug.LogWarningFormat("\"_FlowMap\" is not assigned or existing in material<{0}>.", material.name); return; } //get modifying texel rect of the control texture var x = textureUVMin.x * targetTexture.width; var y = textureUVMin.y * targetTexture.height; var width = (textureUVMax.x - textureUVMin.x) * targetTexture.width; var height = (textureUVMax.y - textureUVMin.y) * targetTexture.height; var texelRect = new Rect(x, y, width, height); Vector2 center = new Vector2(centerUV.x * targetTexture.width, centerUV.y * targetTexture.height); modifyGroups.Add(new TextureModifyGroup(target, targetTexture, texelRect, center)); } if (Settings.DebugMode) { Handles.BeginGUI(); GUILayout.Button(string.Format("center uv: ({0},{1})", centerUV.x, centerUV.y)); Handles.EndGUI(); } } //record undo operation for targets that to be modified if (e.type == EventType.MouseDown) { using (new Undo.UndoTransaction("Paint Flow")) { foreach (var target in MTEContext.Targets) { var material = target.GetComponent().sharedMaterial; if (material.HasProperty("_FlowMap")) { Texture2D texture = (Texture2D)material.GetTexture("_FlowMap"); if (texture != null) { var originalColors = texture.GetPixels(); UndoRedoManager.Instance().Push(a => { texture.ModifyPixels(a); texture.Apply(); Save(texture); }, originalColors, "Paint flow map"); } } } } } // execute the modification if (modifyGroups.Count != 0) { for (int i = 0; i < modifyGroups.Count; i++) { var modifyGroup = modifyGroups[i]; var gameObject = modifyGroup.gameObject; var material = gameObject.GetComponent().sharedMaterial; if (material.HasProperty("_FlowMap")) { Utility.SetTextureReadable(material.GetTexture("_FlowMap")); if (mode == PaintMode.Fixed) { PaintTextureFixed(modifyGroup); } else if(mode == PaintMode.Movement) { PaintTextureMovement(modifyGroup.texture, modifyGroup.texelRect); } else if (mode == PaintMode.PinchInflate) { PaintTexturePinchInflate(modifyGroup.texture, modifyGroup.texelRect); } else if (mode == PaintMode.Vortex) { PaintTextureVortex(modifyGroup.texture, modifyGroup.texelRect); } } } } // auto save when mouse up if (e.type == EventType.MouseUp && e.button == 0) { foreach (var target in MTEContext.Targets) { var material = target.GetComponent().sharedMaterial; if (material.HasProperty("_FlowMap")) { Texture2D texture = (Texture2D)material.GetTexture("_FlowMap"); if (texture != null) { Save(texture); } } } } } SceneView.RepaintAll(); } private static void Save(Texture2D texture) { if (texture == null) { throw new System.ArgumentNullException("texture"); } var path = AssetDatabase.GetAssetPath(texture); var bytes = texture.EncodeToPNG(); File.WriteAllBytes(path, bytes); } private void ChangeDirection(float delta, ref float direction) { if(delta > 0) { direction -= Mathf.PI / 12; } else if(delta < 0) { direction += Mathf.PI / 12; } if(direction < 0) { direction += 2*Mathf.PI; } if (direction > 2*Mathf.PI) { direction -= 2*Mathf.PI; } } private static void CreateFlowMap(Color flowColor) { var texture = new Texture2D(512, 512, TextureFormat.ARGB32, true); var colors = new Color[512 * 512]; for (var t = 0; t < colors.Length; t++) { colors[t] = flowColor; } texture.SetPixels(colors); var relativePath = "Assets/FlowMap.png"; System.IO.File.WriteAllBytes(relativePath, texture.EncodeToPNG()); AssetDatabase.ImportAsset(relativePath, ImportAssetOptions.ForceUpdate); var textureImporter = (TextureImporter)AssetImporter.GetAtPath(relativePath); textureImporter.isReadable = true; textureImporter.SaveAndReimport(); texture = AssetDatabase.LoadAssetAtPath(relativePath) as Texture2D; Selection.activeObject = texture; } #region GUI const int LabelWidth = 90; #endregion #region Preview (not used) public GameObject previewObj; internal void LoadPreview() { UnLoadPreview(); previewObj = new GameObject("MTEPreview"); previewObj.SetActive(false); var projector = previewObj.AddComponent(); previewObj.hideFlags = HideFlags.HideAndDontSave; projector.material = new Material(Shader.Find("Hidden/MTE/PaintTexturePreview")); projector.orthographic = true; projector.nearClipPlane = -1000; projector.farClipPlane = 1000; projector.transform.Rotate(90, 0, 0); SetPreviewTexture(Vector2.one, MTEStyles.flowPainterMaskTextures[BrushIndex], null); SetPreviewSize(BrushSizeInU3D / 2); SetPreviewMaskTexture(BrushIndex); } internal void UnLoadPreview() { if (previewObj != null) { UnityEngine.Object.DestroyImmediate(previewObj); } } private void SetPreviewTexture(Vector2 textureScale, Texture texture, Texture normalTexture) { var projector = previewObj.GetComponent(); projector.material.SetTexture("_MainTex", texture); projector.material.SetTextureScale("_MainTex", textureScale); projector.material.SetTexture("_NormalTex", normalTexture); SceneView.RepaintAll(); } private void SetPreviewMaskTexture(int maskIndex) { var projector = previewObj.GetComponent(); projector.material.SetTexture("_MaskTex", MTEStyles.flowPainterMaskTextures[maskIndex]); projector.material.SetTextureScale("_MaskTex", Vector2.one); SceneView.RepaintAll(); } private void SetPreviewSize(float value) { var projector = previewObj.GetComponent(); projector.orthographicSize = value; SceneView.RepaintAll(); } #endregion private const int smoothing = 10; private Vector3[] delta = new Vector3[smoothing]; private Vector3 vector; private AnimationCurve falloff = AnimationCurve.EaseInOut(0f, 1f, 1f, 0f); private void PaintTextureFixed(TextureModifyGroup modifyGroup) { Texture2D texture = modifyGroup.texture; Rect texelRect = modifyGroup.texelRect; // clear modifying sections modifyingSections.Clear(); // get accurate texel rect int x = Mathf.Clamp(Mathf.RoundToInt(texelRect.x), 0, texture.width); int y = Mathf.Clamp(Mathf.RoundToInt(texelRect.y + 0.5f), 0, texture.height); int width = Mathf.Clamp(Mathf.RoundToInt(texelRect.width), 1, texture.width - x); int height = Mathf.Clamp(Mathf.RoundToInt(texelRect.height), 1, texture.height - y); // modify target var offset = texelRect.position; var replaced = texture.GetPixels(x, y, width, height, 0); var r = texelRect.width * 0.5f; var center = modifyGroup.center; // blend the pixel section for (var i = 0; i < height; i++) { for (var j = 0; j < width; j++) { var pixelIndex = i * width + j; var point = new Vector2(j, i); var distanceToCenter = Vector2.Distance(offset + point, center); if (distanceToCenter > r) { continue; } replaced[pixelIndex] = Color.Lerp(replaced[pixelIndex], TheColor, Speed); } } // modify the control texture texture.SetPixels(x, y, width, height, replaced); texture.Apply(); } private void PaintTextureMovement(Texture2D texture, Rect texelRect) { // check parameters if (texture == null) { throw new System.ArgumentNullException("texture"); } // clear modifying sections modifyingSections.Clear(); int x = (int)texelRect.x; int y = (int)texelRect.y; int width = (int)texelRect.width; int height = (int)texelRect.height; // modify target var replaced = texture.GetPixels(x, y, width, height, 0); var r = width*0.5f; // blend the pixel section for (var i = 0; i < height; i++) { for (var j = 0; j < width; j++) { var pixelIndex = i * width + j; var distanceToCenter = Mathf.Sqrt((i - r) * (i - r) + (j - r) * (j - r)); if(distanceToCenter > r) { continue; } var k = distanceToCenter / r; float t = Mathf.Clamp01(this.falloff.Evaluate(k)) * Strength; var oldColor = replaced[pixelIndex]; var newColor = new Color( Mathf.Lerp(oldColor.r, this.vector.x, t), Mathf.Lerp(oldColor.g, this.vector.z, t), oldColor.b); replaced[pixelIndex] = Color.Lerp(oldColor, newColor, Speed); } } // modify the control texture texture.SetPixels(x, y, width, height, replaced); texture.Apply(); } private void PaintTexturePinchInflate(Texture2D texture, Rect texelRect) { // check parameters if (texture == null) { throw new System.ArgumentNullException("texture"); } // clear modifying sections modifyingSections.Clear(); int x = (int)texelRect.x; int y = (int)texelRect.y; int width = (int)texelRect.width; int height = (int)texelRect.height; // modify target var replaced = texture.GetPixels(x, y, width, height, 0); var r = width * 0.5f; var center = new Vector2(r, r); var dir = Vector2.zero; // blend the pixel section for (var i = 0; i < height; i++) { for (var j = 0; j < width; j++) { var pixelIndex = i * width + j; var point = new Vector2(j, i); var distanceToCenter = Vector2.Distance(point, center); if (distanceToCenter > r) { continue; } var k = distanceToCenter / r; float t = Mathf.Clamp01(this.falloff.Evaluate(k)) * Strength; if (this.Pinching) { dir = (center - point) / r; } else { dir = (point - center) / r; } dir *= 2 * (Mathf.Cos(Mathf.PI * k) * 0.5f + 0.5f) * Strength; var oldColor = replaced[pixelIndex]; var newColor = new Color( Mathf.Lerp(oldColor.r, dir.x * 0.5f + 0.5f, t), Mathf.Lerp(oldColor.g, -dir.y * 0.5f + 0.5f, t), oldColor.b); replaced[pixelIndex] = Color.Lerp(oldColor, newColor, Speed); } } // modify the control texture texture.SetPixels(x, y, width, height, replaced); texture.Apply(); } private void PaintTextureVortex(Texture2D texture, Rect texelRect) { // check parameters if (texture == null) { throw new System.ArgumentNullException("texture"); } // clear modifying sections modifyingSections.Clear(); int x = (int)texelRect.x; int y = (int)texelRect.y; int width = (int)texelRect.width; int height = (int)texelRect.height; // modify target var replaced = texture.GetPixels(x, y, width, height, 0); var r = width * 0.5f; var center = new Vector2(r, r); var dir = Vector2.zero; // blend the pixel section for (var i = 0; i < height; i++) { for (var j = 0; j < width; j++) { var pixelIndex = i * width + j; var point = new Vector2(j, i); var distanceToCenter = Vector2.Distance(point, center); if (distanceToCenter > r) { continue; } var k = distanceToCenter / r; float t = Mathf.Clamp01(this.falloff.Evaluate(k)) * Strength; if (this.VortexRotationClockWise) { dir = (center - point) / r; } else { dir = (point - center) / r; } dir *= 2 * (Mathf.Cos(Mathf.PI * k) * 0.5f + 0.5f) * Strength; var oldColor = replaced[pixelIndex]; var newColor = new Color( Mathf.Lerp(oldColor.r, dir.y * 0.5f + 0.5f, t), Mathf.Lerp(oldColor.g, dir.x * 0.5f + 0.5f, t), oldColor.b); replaced[pixelIndex] = Color.Lerp(oldColor, newColor, Speed); } } // modify the control texture texture.SetPixels(x, y, width, height, replaced); texture.Apply(); } } }