H_SafeExperienceDrivingSystem/U3D_DrivingSystem/Assets/EVP5/Scripts/Tools/TextureCanvas.cs

900 lines
17 KiB
C#

//------------------------------------------------------------------------------------------------
// 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<c; i++)
m_pixels[i] = m_color;
m_dirty = true;
}
public void Grid (float stepX, float stepY)
{
float f;
if (stepX < Pixels2CanvasX(2)) stepX = Pixels2CanvasX(2);
if (stepY < Pixels2CanvasY(2)) stepY = Pixels2CanvasY(2);
float x0 = (int)(m_canvasRect.x / stepX) * stepX;
float y0 = (int)(m_canvasRect.y / stepY) * stepY;
for (f=x0; f<=m_canvasRect.xMax; f+=stepX) VerticalLine(f);
for (f=y0; f<=m_canvasRect.yMax; f+=stepY) HorizontalLine(f);
}
public void Dot (float x, float y)
{
int px = GetPixelX(x);
int py = GetPixelY(y);
TexPixel(px, py-1);
TexPixel(px-1, py);
TexPixel(px, py);
TexPixel(px+1, py);
TexPixel(px, py+1);
m_dirty = true;
}
public void Cross (float x, float y, int radiusX, int radiusY)
{
int px = GetPixelX(x);
int py = GetPixelY(y);
for (int i = px-radiusX; i <= px+radiusX; i++)
TexPixel(i, py);
for (int j = py-radiusY; j <= py+radiusY; j++)
TexPixel(px, j);
m_dirty = true;
}
public void FillRect (float x, float y, float width, float height)
{
int x0 = GetPixelX(x);
int y0 = GetPixelY(y);
int x1 = GetPixelX(x + width);
int y1 = GetPixelY(y + height);
if (y1 < y0)
{
int swap = y0;
y0 = y1;
y1 = swap;
}
for (int i = y0; i <= y1; i++)
{
m_step = 0;
TexSegmentH(x0, x1, i);
}
m_dirty = true;
}
// Function plot
public int functionResolution { get; set; }
public void Function (System.Func<float, float> 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<float, float> func)
{
Function(func, m_canvasRect.xMin, m_canvasRect.xMax);
}
public void SolidFunction (System.Func<float, float> 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<float, float> 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--;
}
}
}
}
}