463 lines
13 KiB
C#
463 lines
13 KiB
C#
// Amplify Impostors
|
|
// Copyright (c) Amplify Creations, Lda <info@amplify.pt>
|
|
|
|
using UnityEngine;
|
|
using UnityEngine.Rendering;
|
|
#if UNITY_EDITOR
|
|
using UnityEditor;
|
|
#endif
|
|
using System.Reflection;
|
|
using System;
|
|
using System.IO;
|
|
|
|
namespace AmplifyImpostors
|
|
{
|
|
public static class BoundsEx
|
|
{
|
|
public static Bounds Transform( this Bounds bounds, Matrix4x4 matrix )
|
|
{
|
|
var center = matrix.MultiplyPoint3x4( bounds.center );
|
|
var extents = bounds.extents;
|
|
|
|
var axisX = matrix.MultiplyVector( new Vector3( extents.x, 0, 0 ) );
|
|
var axisY = matrix.MultiplyVector( new Vector3( 0, extents.y, 0 ) );
|
|
var axisZ = matrix.MultiplyVector( new Vector3( 0, 0, extents.z ) );
|
|
|
|
extents.x = Mathf.Abs( axisX.x ) + Mathf.Abs( axisY.x ) + Mathf.Abs( axisZ.x );
|
|
extents.y = Mathf.Abs( axisX.y ) + Mathf.Abs( axisY.y ) + Mathf.Abs( axisZ.y );
|
|
extents.z = Mathf.Abs( axisX.z ) + Mathf.Abs( axisY.z ) + Mathf.Abs( axisZ.z );
|
|
|
|
return new Bounds { center = center, extents = extents };
|
|
}
|
|
}
|
|
|
|
public static class MaterialEx
|
|
{
|
|
#if UNITY_EDITOR
|
|
public static void CopyPropertiesFrom( this Material to, Material from )
|
|
{
|
|
int count = from.shader.GetPropertyCount();
|
|
for( int i = 0; i < count; i++ )
|
|
{
|
|
var ty = from.shader.GetPropertyType( i );
|
|
var name = from.shader.GetPropertyName( i );
|
|
switch( ty )
|
|
{
|
|
case ShaderPropertyType.Color:
|
|
to.SetColor( name, from.GetColor( name ) );
|
|
break;
|
|
case ShaderPropertyType.Vector:
|
|
to.SetVector( name, from.GetVector( name ) );
|
|
break;
|
|
case ShaderPropertyType.Float:
|
|
to.SetFloat( name, from.GetFloat( name ) );
|
|
break;
|
|
case ShaderPropertyType.Range:
|
|
to.SetFloat( name, from.GetFloat( name ) );
|
|
break;
|
|
case ShaderPropertyType.Texture:
|
|
to.SetTexture( name, from.GetTexture( name ) );
|
|
to.SetTextureOffset( name, from.GetTextureOffset( name ) );
|
|
to.SetTextureScale( name, from.GetTextureScale( name ) );
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
to.renderQueue = from.renderQueue;
|
|
to.globalIlluminationFlags = from.globalIlluminationFlags;
|
|
to.shaderKeywords = from.shaderKeywords;
|
|
foreach( var keyword in to.shaderKeywords )
|
|
{
|
|
to.EnableKeyword( keyword );
|
|
}
|
|
to.enableInstancing = from.enableInstancing;
|
|
EditorUtility.SetDirty( to );
|
|
}
|
|
#endif
|
|
|
|
public static void EnsureTextureKeywordState( this Material material, string property, string keyword )
|
|
{
|
|
var tex = material.HasProperty( property ) ? material.GetTexture( property ) : null;
|
|
EnsureKeywordState( material, keyword, tex != null );
|
|
}
|
|
|
|
public static void EnsureKeywordState( this Material material, string keyword, bool state )
|
|
{
|
|
if ( state && !material.IsKeywordEnabled( keyword ) )
|
|
{
|
|
material.EnableKeyword( keyword );
|
|
}
|
|
else if ( !state && material.IsKeywordEnabled( keyword ) )
|
|
{
|
|
material.DisableKeyword( keyword );
|
|
}
|
|
}
|
|
}
|
|
|
|
public static class Texture2DEx
|
|
{
|
|
static readonly byte[] Footer = { 0x54, 0x52, 0x55, 0x45, 0x56, 0x49, 0x53, 0x49, 0x4F, 0x4E, 0x2D, 0x58, 0x46, 0x49, 0x4C, 0x45, 0x2E, 0x00 }; // TRUEVISION-XFILE.\0 signature (new TGA format)
|
|
|
|
public enum Compression
|
|
{
|
|
None,
|
|
RLE
|
|
}
|
|
|
|
public static byte[] EncodeToTGA( this Texture2D tex, Compression compression = Compression.RLE )
|
|
{
|
|
const int headerSize = 18;
|
|
const int bytesRGB24 = 3;
|
|
const int bytesRGBA32 = 4;
|
|
|
|
int bytesPerPixel = tex.format == TextureFormat.ARGB32 || tex.format == TextureFormat.RGBA32 ? bytesRGBA32 : bytesRGB24;
|
|
|
|
using( MemoryStream stream = new MemoryStream( headerSize + tex.width * tex.height * bytesPerPixel ) )
|
|
{
|
|
using( BinaryWriter writer = new BinaryWriter( stream ) )
|
|
{
|
|
writer.Write( (byte)0 ); // IDLength (not in use)
|
|
writer.Write( (byte)0 ); // ColorMapType (not in use)
|
|
writer.Write( (byte)( compression == Compression.None ? 2 : 10 ) ); // DataTypeCode (10 == Runlength encoded RGB images)
|
|
writer.Write( (short)0 ); // ColorMapOrigin (not in use)
|
|
writer.Write( (short)0 ); // ColorMapLength (not in use)
|
|
writer.Write( (byte)0 ); // ColorMapDepth (not in use)
|
|
writer.Write( (short)0 ); // Origin X
|
|
writer.Write( (short)0 ); // Origin Y
|
|
writer.Write( (short)tex.width ); // Width
|
|
writer.Write( (short)tex.height ); // Height
|
|
writer.Write( (byte)( bytesPerPixel * 8 ) ); // Bits Per Pixel
|
|
writer.Write( (byte)8 ); // ImageDescriptor (photoshop uses 8?)
|
|
|
|
Color32[] pixels = tex.GetPixels32();
|
|
|
|
if( compression == Compression.None )
|
|
{
|
|
for( int i = 0; i < pixels.Length; i++ )
|
|
{
|
|
Color32 pixel = pixels[ i ];
|
|
writer.Write( pixel.r );
|
|
writer.Write( pixel.g );
|
|
writer.Write( pixel.b );
|
|
|
|
if( bytesPerPixel == bytesRGBA32 )
|
|
writer.Write( pixel.a );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
const int maxPacket = 128;
|
|
int packetStart = 0;
|
|
int packetEnd = 0;
|
|
|
|
while( packetStart < pixels.Length )
|
|
{
|
|
Color32 currentPixel = pixels[ packetStart ];
|
|
|
|
bool isRLE = ( packetStart != pixels.Length - 1 ) && Equals( pixels[ packetStart ], pixels[ packetStart + 1 ] );
|
|
int endOfWidth = ( ( packetStart / tex.width ) + 1 ) * tex.width;
|
|
int readEnd = Mathf.Min( packetStart + maxPacket, pixels.Length, endOfWidth );
|
|
|
|
for( packetEnd = packetStart + 1; packetEnd < readEnd; ++packetEnd )
|
|
{
|
|
bool bPreviousEqualsCurrent = Equals( pixels[ packetEnd - 1 ], pixels[ packetEnd ] );
|
|
|
|
if( !isRLE && bPreviousEqualsCurrent || isRLE && !bPreviousEqualsCurrent )
|
|
break;
|
|
}
|
|
|
|
int packetLength = packetEnd - packetStart;
|
|
|
|
if( isRLE )
|
|
{
|
|
writer.Write( (byte)( ( packetLength - 1 ) | ( 1 << 7 ) ) );
|
|
writer.Write( currentPixel.r );
|
|
writer.Write( currentPixel.g );
|
|
writer.Write( currentPixel.b );
|
|
|
|
if( bytesPerPixel == bytesRGBA32 )
|
|
writer.Write( currentPixel.a );
|
|
}
|
|
else
|
|
{
|
|
writer.Write( (byte)( packetLength - 1 ) );
|
|
for( int i = packetStart; i < packetEnd; ++i )
|
|
{
|
|
Color32 pixel = pixels[ i ];
|
|
writer.Write( pixel.r );
|
|
writer.Write( pixel.g );
|
|
writer.Write( pixel.b );
|
|
|
|
if( bytesPerPixel == bytesRGBA32 )
|
|
writer.Write( pixel.a );
|
|
}
|
|
}
|
|
packetStart = packetEnd;
|
|
}
|
|
}
|
|
|
|
writer.Write( 0 ); // Offset of meta-information (not in use)
|
|
writer.Write( 0 ); // Offset of Developer-Area (not in use)
|
|
writer.Write( Footer ); // Signature
|
|
}
|
|
return stream.ToArray();
|
|
}
|
|
}
|
|
|
|
private static bool Equals( Color32 first, Color32 second )
|
|
{
|
|
return first.r == second.r && first.g == second.g && first.b == second.b && first.a == second.a;
|
|
}
|
|
}
|
|
|
|
public static class SpriteUtilityEx
|
|
{
|
|
private static System.Type type = null;
|
|
public static System.Type Type { get { return ( type == null ) ? type = System.Type.GetType( "UnityEditor.Sprites.SpriteUtility, UnityEditor" ) : type; } }
|
|
|
|
public static void GenerateOutline( Texture2D texture, Rect rect, float detail, byte alphaTolerance, bool holeDetection, out Vector2[][] paths )
|
|
{
|
|
Vector2[][] opaths = new Vector2[ 0 ][];
|
|
object[] parameters = new object[] { texture, rect, detail, alphaTolerance, holeDetection, opaths };
|
|
MethodInfo method = Type.GetMethod( "GenerateOutline", BindingFlags.Static | BindingFlags.NonPublic );
|
|
method.Invoke( null, parameters );
|
|
paths = (Vector2[][])parameters[ 5 ];
|
|
}
|
|
}
|
|
|
|
public static class RenderTextureEx
|
|
{
|
|
public static RenderTexture GetTemporary( RenderTexture renderTexture )
|
|
{
|
|
return RenderTexture.GetTemporary( renderTexture.width, renderTexture.height, renderTexture.depth, renderTexture.format );
|
|
}
|
|
}
|
|
|
|
public static class Vector2Ex
|
|
{
|
|
public static float Cross( this Vector2 O, Vector2 A, Vector2 B )
|
|
{
|
|
return ( A.x - O.x ) * ( B.y - O.y ) - ( A.y - O.y ) * ( B.x - O.x );
|
|
}
|
|
|
|
public static float TriangleArea( this Vector2 O, Vector2 A, Vector2 B )
|
|
{
|
|
return Mathf.Abs( ( A.x - B.x ) * ( O.y - A.y ) - ( A.x - O.x ) * ( B.y - A.y ) ) * 0.5f;
|
|
}
|
|
|
|
public static float TriangleArea( this Vector3 O, Vector3 A, Vector3 B )
|
|
{
|
|
return Mathf.Abs( ( A.x - B.x ) * ( O.y - A.y ) - ( A.x - O.x ) * ( B.y - A.y ) ) * 0.5f;
|
|
}
|
|
|
|
public static Vector2[] ConvexHull( Vector2[] P )
|
|
{
|
|
if( P.Length > 1 )
|
|
{
|
|
int n = P.Length, k = 0;
|
|
Vector2[] H = new Vector2[ 2 * n ];
|
|
|
|
Comparison<Vector2> comparison = new Comparison<Vector2>( ( a, b ) =>
|
|
{
|
|
if( a.x == b.x )
|
|
return a.y.CompareTo( b.y );
|
|
else
|
|
return a.x.CompareTo( b.x );
|
|
} );
|
|
Array.Sort<Vector2>( P, comparison );
|
|
|
|
// Build lower hull
|
|
for( int i = 0; i < n; ++i )
|
|
{
|
|
while( k >= 2 && P[ i ].Cross( H[ k - 2 ], H[ k - 1 ] ) <= 0 )
|
|
k--;
|
|
H[ k++ ] = P[ i ];
|
|
}
|
|
|
|
// Build upper hull
|
|
for( int i = n - 2, t = k + 1; i >= 0; i-- )
|
|
{
|
|
while( k >= t && P[ i ].Cross( H[ k - 2 ], H[ k - 1 ] ) <= 0 )
|
|
k--;
|
|
H[ k++ ] = P[ i ];
|
|
}
|
|
if( k > 1 )
|
|
Array.Resize<Vector2>( ref H, k - 1 );
|
|
|
|
return H;
|
|
}
|
|
else if( P.Length <= 1 )
|
|
{
|
|
return P;
|
|
}
|
|
else
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public static Vector2[] ScaleAlongNormals( Vector2[] P, float scaleAmount )
|
|
{
|
|
Vector2[] normals = new Vector2[ P.Length ];
|
|
for( int i = 0; i < normals.Length; i++ )
|
|
{
|
|
int prev = i - 1;
|
|
int next = i + 1;
|
|
if( i == 0 )
|
|
prev = P.Length - 1;
|
|
if( i == P.Length - 1 )
|
|
next = 0;
|
|
|
|
Vector2 ba = P[ i ] - P[ prev ];
|
|
Vector2 bc = P[ i ] - P[ next ];
|
|
Vector2 normal = ( ba.normalized + bc.normalized ).normalized;
|
|
normals[ i ] = normal;
|
|
}
|
|
|
|
for( int i = 0; i < normals.Length; i++ )
|
|
{
|
|
P[ i ] = P[ i ] + normals[ i ] * scaleAmount;
|
|
}
|
|
|
|
return P;
|
|
}
|
|
|
|
static Vector2[] ReduceLeastSignificantVertice( Vector2[] P )
|
|
{
|
|
float currentArea = 0;
|
|
int smallestIndex = 0;
|
|
int replacementIndex = 0;
|
|
Vector2 newPos = Vector2.zero;
|
|
for( int i = 0; i < P.Length; i++ )
|
|
{
|
|
int next = i + 1;
|
|
int upNext = i + 2;
|
|
int finalNext = i + 3;
|
|
if( next >= P.Length )
|
|
next -= P.Length;
|
|
if( upNext >= P.Length )
|
|
upNext -= P.Length;
|
|
if( finalNext >= P.Length )
|
|
finalNext -= P.Length;
|
|
|
|
Vector2 intersect = GetIntersectionPointCoordinates( P[ i ], P[ next ], P[ upNext ], P[ finalNext ] );
|
|
if( i == 0 )
|
|
{
|
|
currentArea = intersect.TriangleArea( P[ next ], P[ upNext ] );
|
|
|
|
if( OutsideBounds( intersect ) > 0 )
|
|
currentArea = currentArea + OutsideBounds( intersect ) * 1;
|
|
|
|
smallestIndex = next;
|
|
replacementIndex = upNext;
|
|
newPos = intersect;
|
|
}
|
|
else
|
|
{
|
|
float newArea = intersect.TriangleArea( P[ next ], P[ upNext ] );
|
|
|
|
if( OutsideBounds( intersect ) > 0 )
|
|
newArea = newArea + OutsideBounds( intersect ) * 1;
|
|
|
|
if( newArea < currentArea && OutsideBounds( intersect ) <= 0 )
|
|
{
|
|
currentArea = newArea;
|
|
smallestIndex = next;
|
|
replacementIndex = upNext;
|
|
newPos = intersect;
|
|
}
|
|
}
|
|
}
|
|
|
|
P[ replacementIndex ] = newPos;
|
|
return Array.FindAll<Vector2>( P, x => Array.IndexOf( P, x ) != smallestIndex );
|
|
}
|
|
|
|
|
|
public static Vector2[] ReduceVertices( Vector2[] P, int maxVertices )
|
|
{
|
|
if( maxVertices == 4 )
|
|
{
|
|
// turn into a box
|
|
Rect newBox = new Rect( P[ 0 ].x, P[ 0 ].y, 0f, 0f );
|
|
for( int i = 0; i < P.Length; i++ )
|
|
{
|
|
newBox.xMin = Mathf.Min( newBox.xMin, P[ i ].x );
|
|
newBox.xMax = Mathf.Max( newBox.xMax, P[ i ].x );
|
|
newBox.yMin = Mathf.Min( newBox.yMin, P[ i ].y );
|
|
newBox.yMax = Mathf.Max( newBox.yMax, P[ i ].y );
|
|
}
|
|
|
|
P = new Vector2[]
|
|
{
|
|
new Vector2(newBox.xMin, newBox.yMin),
|
|
new Vector2(newBox.xMax, newBox.yMin),
|
|
new Vector2(newBox.xMax, newBox.yMax),
|
|
new Vector2(newBox.xMin, newBox.yMax),
|
|
};
|
|
}
|
|
else
|
|
{
|
|
// remove vertices to target count (naive implementation)
|
|
int reduction = Math.Max( 0, P.Length - maxVertices );
|
|
for( int k = 0; k < reduction; k++ )
|
|
{
|
|
P = ReduceLeastSignificantVertice( P );
|
|
// OLD METHOD
|
|
//float prevArea = 0;
|
|
//int indexForRemoval = 0;
|
|
//for( int i = 0; i < P.Length; i++ )
|
|
//{
|
|
// int prev = i - 1;
|
|
// int next = i + 1;
|
|
// if( i == 0 )
|
|
// prev = P.Length - 1;
|
|
// if( i == P.Length - 1 )
|
|
// next = 0;
|
|
|
|
// float area = P[ i ].TriangleArea( P[ prev ], P[ next ] );
|
|
// if( i == 0 )
|
|
// prevArea = area;
|
|
|
|
// if( area < prevArea )
|
|
// {
|
|
// indexForRemoval = i;
|
|
// prevArea = area;
|
|
// }
|
|
//}
|
|
//ArrayUtility.RemoveAt<Vector2>( ref P, indexForRemoval );
|
|
}
|
|
}
|
|
|
|
return P;
|
|
}
|
|
|
|
static Vector2 GetIntersectionPointCoordinates( Vector2 A1, Vector2 A2, Vector2 B1, Vector2 B2 )
|
|
{
|
|
float tmp = ( B2.x - B1.x ) * ( A2.y - A1.y ) - ( B2.y - B1.y ) * ( A2.x - A1.x );
|
|
|
|
if( tmp == 0 )
|
|
{
|
|
return ( ( Vector2.Lerp( A2, B1, 0.5f ) - ( Vector2.one * 0.5f ) ) * 1000 ) + ( Vector2.one * 500f );//Vector2.positiveInfinity;// Vector2.zero;
|
|
}
|
|
|
|
float mu = ( ( A1.x - B1.x ) * ( A2.y - A1.y ) - ( A1.y - B1.y ) * ( A2.x - A1.x ) ) / tmp;
|
|
|
|
return new Vector2(
|
|
B1.x + ( B2.x - B1.x ) * mu,
|
|
B1.y + ( B2.y - B1.y ) * mu
|
|
);
|
|
}
|
|
|
|
static float OutsideBounds( Vector2 P )
|
|
{
|
|
P = P - ( Vector2.one * 0.5f );
|
|
float vert = Mathf.Clamp01( Mathf.Abs( P.y ) - 0.5f );
|
|
float hori = Mathf.Clamp01( Mathf.Abs( P.x ) - 0.5f );
|
|
return hori + vert;
|
|
}
|
|
|
|
}
|
|
}
|