2017-04-12 01:23:07 +09:00

524 lines
12 KiB
C#

using UnityEngine;
using System.Collections.Generic;
public enum D2D_SpriteSplitOrder
{
Default,
KeepLargest,
KeepSmallest
}
public abstract class D2D_Destructible : MonoBehaviour
{
public static List<D2D_Destructible> Destructibles = new List<D2D_Destructible>();
public static List<D2D_Destructible> DestructiblesCopy = new List<D2D_Destructible>();
public Texture2D DensityTex;
public bool AllowSplit;
public int SplitMinPixels = 20;
[D2D_RangeAttribute(0.0f, 1.0f)]
public float SplitThreshold = 0.25f;
public D2D_SpriteSplitOrder SplitOrder = D2D_SpriteSplitOrder.Default;
public int OriginalSolidPixelCount;
[System.NonSerialized]
private Texture2D alphaTex;
[SerializeField]
private float alphaScaleX;
[SerializeField]
private float alphaScaleY;
[SerializeField]
private byte[] alphaTexData;
[SerializeField]
private int alphaTexWidth;
[SerializeField]
private int alphaTexHeight;
[SerializeField]
private int cachedSolidPixelCount = -1;
private bool dirty;
public Texture2D AlphaTex
{
get
{
DeserializeAlphaTex();
return alphaTex;
}
}
public int SolidPixelCount
{
get
{
if (cachedSolidPixelCount == -1)
{
cachedSolidPixelCount = 0;
DeserializeAlphaTex();
if (alphaTex != null && alphaTex.width > 0 && alphaTex.height > 0)
{
/*
for (var y = 0; y < alphaTex.height; y++)
{
for (var x = 0; x < alphaTex.width; x++)
{
if (alphaTex.GetPixel(x, y).a >= 0.5f)
{
cachedSolidPixelCount += 1;
}
}
}
*/
// This is faster?
var pixels = alphaTex.GetPixels32();
for (var i = pixels.Length - 1; i >= 0; i--)
{
if (pixels[i].a >= 128)
{
cachedSolidPixelCount += 1;
}
}
}
}
return cachedSolidPixelCount;
}
}
public float SolidPixelRatio
{
get
{
return D2D_Helper.Divide(SolidPixelCount, OriginalSolidPixelCount);
}
}
public abstract Matrix4x4 WorldToPixelMatrix
{
get;
}
public void ResizeAlphaTex(int newWidth, int newHeight)
{
DeserializeAlphaTex();
if (alphaTex != null)
{
ReplaceAlphaWith(new D2D_Pixels(alphaTex).GetResized(newWidth, newHeight));
}
}
[ContextMenu("Halve Alpha Tex")]
public void HalveAlphaTex()
{
DeserializeAlphaTex();
if (alphaTex != null)
{
ResizeAlphaTex(alphaTex.width / 2, alphaTex.height / 2);
}
}
[ContextMenu("Blur Alpha Tex")]
public void BlurAlphaTex()
{
DeserializeAlphaTex();
if (alphaTex != null)
{
ReplaceAlphaWith(new D2D_Pixels(alphaTex).GetBlurredAlpha());
}
}
[ContextMenu("Recalculate Original Solid Pixel Count")]
public void RecalculateOriginalSolidPixelCount()
{
OriginalSolidPixelCount = SolidPixelCount;
}
public void ReplaceAlphaWith(Sprite newSprite)
{
ReplaceAlphaWith(newSprite != null ? new D2D_Pixels(newSprite) : null);
}
public void ReplaceAlphaWith(Texture2D newTexture)
{
ReplaceAlphaWith(newTexture != null ? new D2D_Pixels(newTexture) : null);
}
public void ReplaceAlphaWith(int newWidth, int newHeight, Color32[] newPixels)
{
ReplaceAlphaWith(new D2D_Pixels(newWidth, newHeight, newPixels));
}
public void ReplaceAlphaWith(D2D_Pixels newPixels)
{
#if UNITY_EDITOR
if (alphaTex != null)
{
D2D_Helper.Record(this, "Modify Alpha Tex");
}
#endif
if (newPixels != null)
{
// Remake texture?
//if (alphaTex == null || alphaTex.width != newPixels.Width || alphaTex.height != newPixels.Height)
{
DestroyAlphaTex();
alphaTexData = newPixels.ApplyAlpha();
alphaTexWidth = newPixels.Width;
alphaTexHeight = newPixels.Height;
alphaScaleX = D2D_Helper.Reciprocal(alphaTexWidth - 1);
alphaScaleY = D2D_Helper.Reciprocal(alphaTexHeight - 1);
}
// Copy settings over?
//else
{
// alphaTex.SetPixels32(newPixels.Pixels);
// alphaTex.Apply();
}
UpdateRect(0, alphaTexWidth, 0, alphaTexHeight);
}
else
{
DestroyAlphaTex();
UpdateRect(0, 0, 0, 0);
}
MarkAsDirty();
NotifyChanges();
#if UNITY_EDITOR
D2D_Helper.SetDirty(this);
#endif
}
public static Matrix4x4 CalculateStampMatrix(Vector2 position, Vector2 size, float angle)
{
var t = D2D_Helper.TranslationMatrix(position.x, position.y, 0.0f);
var r = D2D_Helper.RotationMatrix(Quaternion.Euler(0.0f, 0.0f, angle));
var s = D2D_Helper.ScalingMatrix(size.x, size.y, 1.0f);
var o = D2D_Helper.TranslationMatrix(-0.5f, -0.5f, 0.0f); // Centre stamp
return t * r * s * o;
}
public static void SliceAll(Vector2 startPos, Vector2 endPos, float thickness, Texture2D stampTex, float hardness, int layerMask = -1)
{
if (stampTex != null)
{
var mid = (startPos + endPos) / 2.0f;
var vec = endPos - startPos;
var size = new Vector2(thickness, vec.magnitude);
var angle = D2D_Helper.Atan2(vec) * -Mathf.Rad2Deg;
StampAll(CalculateStampMatrix(mid, size, angle), stampTex, hardness, layerMask);
}
}
public static void StampAll(Vector2 position, Vector2 size, float angle, Texture2D stampTex, float hardness, int layerMask = -1)
{
if (stampTex != null)
{
StampAll(CalculateStampMatrix(position, size, angle), stampTex, hardness, layerMask);
}
}
public static void StampAll(Matrix4x4 matrix, Texture2D stampTex, float hardness, int layerMask = -1)
{
if (stampTex != null)
{
// The original list may change during the stamping, so make a clone
DestructiblesCopy.Clear();
DestructiblesCopy.AddRange(Destructibles);
foreach (var destructible in DestructiblesCopy)
{
if (destructible != null)
{
var mask = 1 << destructible.gameObject.layer;
if ((layerMask & mask) != 0)
{
destructible.Stamp(matrix, stampTex, hardness);
}
}
}
}
}
public float GetAlphaAll(Vector2 position, int layerMask = -1)
{
var alpha = default(float);
GetAlphaAll(position, ref alpha);
return alpha;
}
public bool GetAlphaAll(Vector2 position, ref float alpha, int layerMask = -1)
{
foreach (var destructible in Destructibles)
{
if (destructible != null)
{
var mask = 1 << destructible.gameObject.layer;
if ((layerMask & mask) != 0)
{
if (destructible.GetAlpha(position, ref alpha) == true)
{
return true;
}
}
}
}
return false;
}
public float GetAlpha(Vector2 position)
{
var alpha = default(float);
GetAlpha(position, ref alpha);
return alpha;
}
public bool GetAlpha(Vector2 position, ref float alpha)
{
if (alphaTexData != null && alphaTexData.Length > 0)
{
var point = WorldToPixelMatrix.MultiplyPoint(position);
var x = Mathf.FloorToInt(point.x);
var y = Mathf.FloorToInt(point.y);
if (x >= 0 && x < alphaTexWidth)
{
if (y >= 0 && y < alphaTexHeight)
{
alpha = alphaTexData[x + y * alphaTexWidth]; return true;
}
}
}
return false;
}
public void Stamp(Vector2 position, Vector2 size, float angle, Texture2D stampTex, float hardness)
{
DeserializeAlphaTex();
if (alphaTex != null && stampTex != null)
{
Stamp(CalculateStampMatrix(position, size, angle), stampTex, hardness);
}
}
public void Stamp(Matrix4x4 stampMatrix, Texture2D stampTex, float hardness)
{
if (alphaTex != null && stampTex != null)
{
var stampToPixelMatrix = WorldToPixelMatrix * stampMatrix;
var pixelToStampMatrix = stampToPixelMatrix.inverse;
#if UNITY_EDITOR
D2D_Helper.MakeTextureReadable(stampTex);
D2D_Helper.MakeTextureReadable(DensityTex);
#endif
// Project corners of stamp
// TODO: account for non-orthogonal matrices?
var bl = stampToPixelMatrix.MultiplyPoint(new Vector3(0.0f, 0.0f, 0.0f));
var br = stampToPixelMatrix.MultiplyPoint(new Vector3(1.0f, 0.0f, 0.0f));
var tl = stampToPixelMatrix.MultiplyPoint(new Vector3(0.0f, 1.0f, 0.0f));
var tr = stampToPixelMatrix.MultiplyPoint(new Vector3(1.0f, 1.0f, 0.0f));
// Find AABB of stamp
var xMin = Mathf.FloorToInt(Mathf.Min(Mathf.Min(bl.x, br.x), Mathf.Min(tl.x, tr.x)));
var xMax = Mathf.FloorToInt(Mathf.Max(Mathf.Max(bl.x, br.x), Mathf.Max(tl.x, tr.x)));
var yMin = Mathf.FloorToInt(Mathf.Min(Mathf.Min(bl.y, br.y), Mathf.Min(tl.y, tr.y)));
var yMax = Mathf.FloorToInt(Mathf.Max(Mathf.Max(bl.y, br.y), Mathf.Max(tl.y, tr.y)));
xMin = Mathf.Clamp(xMin, 0, alphaTexWidth - 1);
xMax = Mathf.Clamp(xMax, 0, alphaTexWidth - 1);
yMin = Mathf.Clamp(yMin, 0, alphaTexHeight - 1);
yMax = Mathf.Clamp(yMax, 0, alphaTexHeight - 1);
if (xMax > xMin && yMax > yMin)
{
MarkAsDirty();
for (var y = yMin; y <= yMax; y++)
{
for (var x = xMin; x <= xMax; x++)
{
var uv = pixelToStampMatrix.MultiplyPoint(new Vector3(x, y, 0.0f));
// Is this pixel within the stamp?
if (uv.x >= 0.0f && uv.y >= 0.0f && uv.x < 1.0f && uv.y < 1.0f)
{
var stamp = stampTex.GetPixel(Mathf.FloorToInt(uv.x * stampTex.width), Mathf.FloorToInt(uv.y * stampTex.height));
FastPaint(x, y, stamp.a * hardness);
}
}
}
if (alphaTex != null)
{
alphaTex.Apply();
}
var split = false;
if (AllowSplit == true)
{
split = D2D_SplitCalculator.Generate(this, SplitOrder);
}
if (split == false)
{
UpdateRect(xMin, xMax, yMin, yMax);
NotifyChanges();
}
}
}
}
private void FastPaint(int x, int y, float opacity)
{
var index = x + y * alphaTexWidth;
var alpha = alphaTexData[index];
if (DensityTex != null)
{
var u = x * alphaScaleX * DensityTex.width;
var v = y * alphaScaleY * DensityTex.height;
var density = DensityTex.GetPixel(Mathf.FloorToInt(u), Mathf.FloorToInt(v));
opacity *= 1.0f - density.a;
}
var a = (int)(opacity * 255.0f);
alpha = (byte)Mathf.Clamp((int)alpha - a, 0, 255);
alphaTexData[index] = alpha;
}
protected virtual void OnEnable()
{
Destructibles.Add(this);
#if UNITY_EDITOR
if (UnityEditor.AssetDatabase.Contains(this) == true)
{
return;
}
#endif
CacheTexures();
}
protected virtual void OnDisable()
{
Destructibles.Remove(this);
}
protected virtual void OnDestroy()
{
DestroyAlphaTex();
}
#if UNITY_EDITOR
protected virtual void OnValidate()
{
D2D_Helper.MakeTextureReadable(DensityTex);
}
#endif
protected abstract void UpdateRect(int xMin, int xMax, int yMin, int yMax);
private void DeserializeAlphaTex()
{
if ((dirty == true || alphaTex == null) && alphaTexData != null && alphaTexData.Length > 0 && alphaTexWidth > 0 && alphaTexHeight > 0)
{
dirty = false;
var pixels = new Color32[alphaTexData.Length];
for (var i = pixels.Length - 1; i >= 0; i--)
{
pixels[i].a = alphaTexData[i];
}
alphaTex = new Texture2D(alphaTexWidth, alphaTexHeight, TextureFormat.Alpha8, false);
alphaTex.hideFlags = HideFlags.DontSave;
alphaTex.wrapMode = TextureWrapMode.Clamp;
alphaTex.SetPixels32(pixels);
alphaTex.Apply();
}
}
private void DestroyAlphaTex()
{
if (alphaTex != null)
{
D2D_Helper.Destroy(alphaTex);
alphaTex = null;
}
alphaTexData = null;
alphaTexWidth = 0;
alphaTexHeight = 0;
}
private void CacheTexures()
{
if (DensityTex != null)
{
if (DensityTex.width > 0 && DensityTex.height > 0)
{
DensityTex.GetPixel(0, 0);
}
}
}
private void MarkAsDirty()
{
dirty = true;
cachedSolidPixelCount = -1;
}
private void NotifyChanges()
{
#if UNITY_EDITOR
if (Application.isPlaying == false)
{
return;
}
#endif
BroadcastMessage("OnAlphaTexModified", SendMessageOptions.DontRequireReceiver);
}
}