//------------------------------------------------------------------------------------------------ // Edy's Vehicle Physics // (c) Angel Garcia "Edy" - Oviedo, Spain // http://www.edy.es //------------------------------------------------------------------------------------------------ using UnityEngine; namespace EVP { public class TextureCanvas { // Texture Texture2D m_texture; int m_pixelsWd; int m_pixelsHt; bool m_dirty; private Color32[] m_pixels; private Color32[] m_buffer; // Canvas rect Rect m_canvasRect; float m_scaleX; // Pixels per canvas unit float m_scaleY; // Clip area Rect m_clipArea = new Rect(); // In canvas units int m_pixelsXMin; // Drawable limits in physical pixels int m_pixelsXMax; int m_pixelsYMin; int m_pixelsYMax; // --------------------------------------------------------------------------------------------- // Constructors & setup public TextureCanvas (int pixelsWd, int pixelsHt, Rect canvasRect) { SetupCanvas(pixelsWd, pixelsHt); rect = canvasRect; } public TextureCanvas (int pixelsWd, int pixelsHt, float canvasWd, float canvasHt) { SetupCanvas(pixelsWd, pixelsHt); rect = new Rect(0.0f, 0.0f, canvasWd, canvasHt); } public TextureCanvas (int pixelsWd, int pixelsHt) { SetupCanvas(pixelsWd, pixelsHt); rect = new Rect(0.0f, 0.0f, 1.0f, 1.0f); } // Destructor - manually release the texture. // Use before discarding a temporary TextureCanvas object. // NOTE: Textures are not automatically released via GC! public void DestroyTexture () { Object.DestroyImmediate(m_texture); } // Logical dimensions public Rect rect { get { return m_canvasRect; } set { m_canvasRect = value; m_scaleX = m_pixelsWd / m_canvasRect.width; m_scaleY = m_pixelsHt / m_canvasRect.height; clipArea = m_canvasRect; } } // Drawable area // Set to "rect" for resetting it public Rect clipArea { get { return m_clipArea; } set { // It's clipped against the logical rect. If the clipped area falls outside the // rect the width/height will become negative (= full clip). m_clipArea.xMin = Mathf.Max(value.xMin, m_canvasRect.xMin); m_clipArea.xMax = Mathf.Min(value.xMax, m_canvasRect.xMax); m_clipArea.yMin = Mathf.Max(value.yMin, m_canvasRect.yMin); m_clipArea.yMax = Mathf.Min(value.yMax, m_canvasRect.yMax); m_pixelsXMin = GetPixelX(m_clipArea.xMin); m_pixelsXMax = GetPixelX(m_clipArea.xMax); m_pixelsYMin = GetPixelY(m_clipArea.yMin); m_pixelsYMax = GetPixelY(m_clipArea.yMax); } } // Unit conversions public float Pixels2CanvasX (int pixels) { return (float)pixels / m_scaleX; } public float Pixels2CanvasY (int pixels) { return (float)pixels / m_scaleY; } int GetPixelX (float x) { return Mathf.RoundToInt((x - m_canvasRect.xMin) * m_scaleX); } int GetPixelY (float y) { return Mathf.RoundToInt((y - m_canvasRect.yMin) * m_scaleY); } int GetPixelWidth (float width) { return Mathf.RoundToInt(width * m_scaleX); } int GetPixelHeight (float height) { return Mathf.RoundToInt(height * m_scaleY); } // --------------------------------------------------------------------------------------------- // Drawing // When alpha is -1 (default) the alpha channel of the color property is used Color32 m_color = Color.white; float m_alpha = -1.0f; float m_srcAlpha = 1.0f; float m_dstAlpha = 0.0f; public Color32 color { get { return m_color; } set { m_color = value; SetupAlpha(); } } public float alpha { get { return m_alpha; } set { m_alpha = value; SetupAlpha(); } } public bool alphaBlend { get; set; } public enum LineType { Solid, Dotted, Dashed } public LineType lineType { get; set; } public int dotInterval { get; set; } public int dashInterval { get; set; } // Line draw methods float m_moveX; float m_moveY; int m_step; public void MoveTo (float x0, float y0) { m_moveX = x0; m_moveY = y0; m_step = 0; } public void LineTo (float x1, float y1) { float x0 = m_moveX; float y0 = m_moveY; m_moveX = x1; m_moveY = y1; // Ensure that x0 <= x1 for better crop calculation if (x0 > x1) { float swap = x0; x0 = x1; x1 = swap; swap = y0; y0 = y1; y1 = swap; } // Left / right crop float sl = (y1 - y0) / (x1 - x0); if (x0 < m_clipArea.xMin) { y0 += (m_clipArea.xMin - x0) * sl; x0 = m_clipArea.xMin; } if (x1 > m_clipArea.xMax) { y1 += (m_clipArea.xMax - x1) * sl; x1 = m_clipArea.xMax; } // We can now discard the lines that won't cross the visible view. if (x0 > m_clipArea.xMax || x1 < m_clipArea.xMin || (y0 < m_clipArea.yMin && y1 < m_clipArea.yMin) || (y0 > m_clipArea.yMax && y1 > m_clipArea.yMax)) return; // At this point the line necessarily crosses the visible viewport. X coords are already cropped. // We now crop the Y coords that may be outside the view. if (y0 < m_clipArea.yMin) { x0 += (m_clipArea.yMin - y0) / sl; y0 = m_clipArea.yMin; } if (y0 > m_clipArea.yMax) { x0 += (m_clipArea.yMax - y0) / sl; y0 = m_clipArea.yMax; } if (y1 < m_clipArea.yMin) { x1 += (m_clipArea.yMin - y1) / sl; y1 = m_clipArea.yMin; } if (y1 > m_clipArea.yMax) { x1 += (m_clipArea.yMax - y1) / sl; y1 = m_clipArea.yMax; } // Draw the resulting line TexLine(GetPixelX(x0), GetPixelY(y0), GetPixelX(x1), GetPixelY(y1)); m_dirty = true; } public void Line (float x0, float y0, float x1, float y1) { MoveTo(x0, y0); LineTo(x1, y1); } public void HorizontalLine (float y) { m_step = 0; TexSegmentH(m_pixelsXMin, m_pixelsXMax, GetPixelY(y)); m_dirty = true; } public void VerticalLine (float x) { m_step = 0; TexSegmentV(GetPixelX(x), m_pixelsYMin, m_pixelsYMax); m_dirty = true; } // Circle and ellipse drawing public void Circumference (float x, float y, float radius) { m_step = 0; int r = GetPixelWidth(radius); TexEllipse(GetPixelX(x), GetPixelY(y), r, r); m_dirty = true; } public void Circle (float x, float y, float radius) { m_step = 0; int r = GetPixelWidth(radius); TexFillEllipse(GetPixelX(x), GetPixelY(y), r, r); m_dirty = true; } public void Ellipse (float x, float y, float rx, float ry) { m_step = 0; TexEllipse(GetPixelX(x), GetPixelY(y), GetPixelWidth(rx), GetPixelHeight(ry)); m_dirty = true; } public void FillEllipse (float x, float y, float rx, float ry) { m_step = 0; TexFillEllipse(GetPixelX(x), GetPixelY(y), GetPixelWidth(rx), GetPixelHeight(ry)); m_dirty = true; } // Utility drawing methods public void Clear () { for (int i=0, c=m_pixels.Length; i func, float x0, float x1) { float stepSize = Pixels2CanvasX(functionResolution); MoveTo(x0, func(x0)); float x; for (x = x0; x <= x1; x += stepSize) LineTo(x, func(x)); if (x < x1) LineTo(x1, func(x1)); } public void Function (System.Func func) { Function(func, m_canvasRect.xMin, m_canvasRect.xMax); } public void SolidFunction (System.Func func, float x0, float x1) { int px0 = GetPixelX(x0); int px1 = GetPixelX(x1); int pxWidth = px1 - px0; int py0 = GetPixelY(0); int py1; for (int px = 0; px <= pxWidth; px++) { m_step = 0; py1 = GetPixelHeight(func(x0 + Pixels2CanvasX(px))); TexSegmentV(px0+px, py0, py0+py1); } m_dirty = true; } public void SolidFunction (System.Func func) { SolidFunction(func, m_canvasRect.xMin, m_canvasRect.xMax); } // --------------------------------------------------------------------------------------------- // Tools & GUI drawing // Save current canvas & restore. // Pixels only! No logical rect / clip area. public void Save () { if (m_buffer == null) m_buffer = (m_pixels.Clone() as Color32[]); else m_pixels.CopyTo(m_buffer, 0); } public void Restore () { if (m_buffer != null) { m_buffer.CopyTo(m_pixels, 0); m_dirty = true; } } // GUI draw public void GUIDraw (int x, int y) { ApplyChanges(); GUI.DrawTexture(new Rect(x, y, m_pixelsWd, m_pixelsHt), m_texture); } #if UNITY_EDITOR public void EditorGUIDraw (Rect position) { ApplyChanges(); UnityEditor.EditorGUI.DropShadowLabel(position, new GUIContent(m_texture)); } #endif public void GUIStretchDraw (int x, int y, int width, int height) { ApplyChanges(); GUI.DrawTexture(new Rect(x, y, width, height), m_texture); } public void GUIStretchDraw (int x, int y, int width) { ApplyChanges(); float ratio = (float)m_pixelsHt / m_pixelsWd; GUI.DrawTexture(new Rect(x, y, width, width * ratio), m_texture); } // Access to the actual Texture2D object for custom usage public Texture2D texture { get { ApplyChanges(); return m_texture; } } // --------------------------------------------------------------------------------------------- // Private / Internal void ApplyChanges () { if (m_dirty) { m_texture.SetPixels32(m_pixels); m_texture.Apply(false); m_dirty = false; } } void SetupCanvas (int pixelsWd, int pixelsHt) { m_texture = new Texture2D(pixelsWd, pixelsHt, TextureFormat.ARGB32, false, true); m_texture.hideFlags = HideFlags.HideAndDontSave; // Pixels are stored in both int and float formats for optimizing each type of operation m_pixelsWd = pixelsWd; m_pixelsHt = pixelsHt; m_pixels = new Color32[pixelsWd * pixelsHt]; // Default value for auto-properties and fields alphaBlend = false; dotInterval = 5; dashInterval = 5; functionResolution = 3; } void SetupAlpha () { if (m_alpha >= 0.0f) m_color.a = (byte)(Mathf.Clamp01(m_alpha) * 255.0f); m_srcAlpha = m_color.a / 255.0f; m_dstAlpha = 1.0f - m_srcAlpha; } Color32 GetAlphaBlendedPixel (Color32 dst) { return new Color32( (byte)(m_color.r*m_srcAlpha + dst.r*m_dstAlpha), (byte)(m_color.g*m_srcAlpha + dst.g*m_dstAlpha), (byte)(m_color.b*m_srcAlpha + dst.b*m_dstAlpha), (byte)(m_color.a*m_srcAlpha + dst.a*m_dstAlpha)); } // --------------------------------------------------------------------------------------------- // Private low-level drawing functions bool CheckForPixel () { if (lineType == LineType.Solid) return true; if (lineType == LineType.Dotted) return (m_step++ % dotInterval) == 0; if (lineType == LineType.Dashed) { int n = dashInterval; return (m_step++ % (n * 2)) < n; } return true; } // PutPixel void TexPixel (int x, int y) { if (x >= m_pixelsXMin && x < m_pixelsXMax && y >= m_pixelsYMin && y < m_pixelsYMax) { int pixel = y * m_pixelsWd + x; m_pixels[pixel] = alphaBlend? GetAlphaBlendedPixel(m_pixels[pixel]) : m_color; } } // Line drawing void TexLine (int x0, int y0, int x1, int y1) { int dy = y1 - y0; int dx = x1 - x0; if (dx == 0) TexSegmentV(x0, y0, y1); else if (dy == 0) TexSegmentH(x0, x1, y0); else { int stepY; if (dy < 0) { dy = -dy; stepY = -1; } else { stepY = 1; } int stepX; if (dx < 0) { dx = -dx; stepX = -1; } else { stepX = 1; } dy <<= 1; dx <<= 1; if (CheckForPixel()) TexPixel(x0, y0); if (dx > dy) { int fraction = dy - (dx >> 1); while (x0 != x1) { if (fraction >= 0) { y0 += stepY; fraction -= dx; } x0 += stepX; fraction += dy; if (CheckForPixel()) TexPixel(x0, y0); } } else { int fraction = dx - (dy >> 1); while (y0 != y1) { if (fraction >= 0) { x0 += stepX; fraction -= dy; } y0 += stepY; fraction += dx; if (CheckForPixel()) TexPixel(x0, y0); } } } } // Fast segment drawing functions. // They avoid the CheckForPixel check when possible. void TexSegmentV (int x, int y0, int y1) { if (y0 > y1) { int swap = y0; y0 = y1; y1 = swap; } if (x < m_pixelsXMin || x >= m_pixelsXMax || y1 < m_pixelsYMin || y0 >= m_pixelsYMax) return; if (y0 < m_pixelsYMin) y0 = m_pixelsYMin; if (y1 >= m_pixelsYMax) y1 = m_pixelsYMax; int pixel = y0 * m_pixelsWd + x; if (!alphaBlend) { if (lineType == LineType.Solid) { for (int y = y0; y < y1; y++) { m_pixels[pixel] = m_color; pixel += m_pixelsWd; } } else { for (int y = y0; y < y1; y++) { if (CheckForPixel()) m_pixels[pixel] = m_color; pixel += m_pixelsWd; } } } else { if (lineType == LineType.Solid) { for (int y = y0; y < y1; y++) { m_pixels[pixel] = GetAlphaBlendedPixel(m_pixels[pixel]); pixel += m_pixelsWd; } } else { for (int y = y0; y < y1; y++) { if (CheckForPixel()) m_pixels[pixel] = GetAlphaBlendedPixel(m_pixels[pixel]); pixel += m_pixelsWd; } } } } void TexSegmentH (int x0, int x1, int y) { if (x0 > x1) { int swap = x0; x0 = x1; x1 = swap; } if (y < m_pixelsYMin || y >= m_pixelsYMax || x1 < m_pixelsXMin || x0 >= m_pixelsXMax) return; if (x0 < m_pixelsXMin) x0 = m_pixelsXMin; if (x1 > m_pixelsXMax) x1 = m_pixelsXMax; int pixel = y * m_pixelsWd + x0; if (!alphaBlend) { if (lineType == LineType.Solid) { for (int x = x0; x < x1; x++) m_pixels[pixel++] = m_color; } else { for (int x = x0; x < x1; x++) { if (CheckForPixel()) m_pixels[pixel] = m_color; pixel++; } } } else { if (lineType == LineType.Solid) { for (int x = x0; x < x1; x++) { m_pixels[pixel] = GetAlphaBlendedPixel(m_pixels[pixel]); pixel++; } } else { for (int x = x0; x < x1; x++) { if (CheckForPixel()) m_pixels[pixel] = GetAlphaBlendedPixel(m_pixels[pixel]); pixel++; } } } } // Ellipse / circle drawing void TexEllipse (int cx, int cy, int rx, int ry) { if (rx >= ry) { int y = rx; int d = -rx; int end = (int)Mathf.Ceil(rx / Mathf.Sqrt(2.0f)); float sy = (float)ry / rx; for (int x = 0; x <= end; x++) { TexPixel(cx+x, (int)(cy+y*sy)); TexPixel(cx+x, (int)(cy-y*sy)); TexPixel(cx-x, (int)(cy+y*sy)); TexPixel(cx-x, (int)(cy-y*sy)); TexPixel(cx+y, (int)(cy+x*sy)); TexPixel(cx-y, (int)(cy+x*sy)); TexPixel(cx+y, (int)(cy-x*sy)); TexPixel(cx-y, (int)(cy-x*sy)); d += 2*x + 1; if (d > 0) d += 2 - 2*y--; } } else { int x = ry; int d = -ry; int end = (int)Mathf.Ceil(ry / Mathf.Sqrt(2.0f)); float sx = (float)rx / ry; for (int y = 0; y <= end; y++) { TexPixel((int)(cx+y*sx), cy+x); TexPixel((int)(cx+y*sx), cy-x); TexPixel((int)(cx-y*sx), cy+x); TexPixel((int)(cx-y*sx), cy-x); TexPixel((int)(cx+x*sx), cy+y); TexPixel((int)(cx-x*sx), cy+y); TexPixel((int)(cx+x*sx), cy-y); TexPixel((int)(cx-x*sx), cy-y); d += 2*y + 1; if (d > 0) d += 2 - 2*x--; } } } void TexFillEllipse (int cx, int cy, int rx, int ry) { if (rx >= ry) { int y = rx; int d = -rx; int end = (int)Mathf.Ceil(rx / Mathf.Sqrt(2.0f)); float sy = (float)ry / rx; for (int x = 0; x <= end; x++) { TexSegmentV(cx+x, cy, (int)(cy+y*sy)); TexSegmentV(cx+x, cy, (int)(cy-y*sy)); TexSegmentV(cx-x, cy, (int)(cy+y*sy)); TexSegmentV(cx-x, cy, (int)(cy-y*sy)); TexSegmentV(cx+y, cy, (int)(cy+x*sy)); TexSegmentV(cx-y, cy, (int)(cy+x*sy)); TexSegmentV(cx+y, cy, (int)(cy-x*sy)); TexSegmentV(cx-y, cy, (int)(cy-x*sy)); d += 2*x + 1; if (d > 0) d += 2 - 2*y--; } } else { int x = ry; int d = -ry; int end = (int)Mathf.Ceil(ry / Mathf.Sqrt(2.0f)); float sx = (float)rx / ry; for (int y = 0; y <= end; y++) { TexSegmentH((int)(cx+y*sx), cx, cy+x); TexSegmentH((int)(cx+y*sx), cx, cy-x); TexSegmentH((int)(cx-y*sx), cx, cy+x); TexSegmentH((int)(cx-y*sx), cx, cy-x); TexSegmentH((int)(cx+x*sx), cx, cy+y); TexSegmentH((int)(cx-x*sx), cx, cy+y); TexSegmentH((int)(cx+x*sx), cx, cy-y); TexSegmentH((int)(cx-x*sx), cx, cy-y); d += 2*y + 1; if (d > 0) d += 2 - 2*x--; } } } } }