Files
2025-05-07 15:07:44 +08:00

211 lines
7.5 KiB
C#

using System;
using System.Collections.Generic;
using UnityEngine;
#if UNITY_EDITOR
using System.ComponentModel;
using UnityEditor;
#endif
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using Object = UnityEngine.Object;
// Shapes © Freya Holmér - https://twitter.com/FreyaHolmer/
// Website & Documentation - https://acegikmo.com/shapes/
namespace Shapes {
internal static class ShapesExtensions {
public static void ForEach<T>( this IEnumerable<T> elems, Action<T> action ) {
foreach( T e in elems )
action( e );
}
public static Vector3 Rot90CCW( this Vector3 v ) => new Vector3( -v.y, v.x );
public static int AsInt( this bool b ) => b ? 1 : 0;
public static Vector4 ToVector4( this Rect r ) => new Vector4( r.x, r.y, r.width, r.height );
public static float TaxicabMagnitude( this Vector3 v ) => Mathf.Abs( v.x ) + Mathf.Abs( v.y ) + Mathf.Abs( v.z );
public static float AvgComponentMagnitude( this Vector3 v ) => ( Mathf.Abs( v.x ) + Mathf.Abs( v.y ) + Mathf.Abs( v.z ) ) / 3;
internal static Color ColorSpaceAdjusted( this Color c ) => QualitySettings.activeColorSpace == ColorSpace.Linear ? c.linear : c;
[MethodImpl( MethodImplOptions.AggressiveInlining )] public static void SetInt_Shapes( this Material m, int id, int value ) {
// #if UNITY_2021_1_OR_NEWER
// m.SetInteger( id, value );
// #else
m.SetInt( id, value );
// #endif
}
[MethodImpl( MethodImplOptions.AggressiveInlining )] public static void SetInt_Shapes( this MaterialPropertyBlock mpb, int id, int value ) {
// #if UNITY_2021_1_OR_NEWER
// mpb.SetInteger( id, value );
// #else
mpb.SetInt( id, value );
// #endif
}
// because outside of play mode, we have to use DestroyImmediate, but we want to use Destroy otherwise
public static void DestroyBranched( this Object obj ) {
#if UNITY_EDITOR
if( EditorApplication.isPlaying == false )
Object.DestroyImmediate( obj );
else
Object.Destroy( obj );
#else
Object.Destroy( obj );
#endif
}
public static void DestroyEndOfFrameEmulated( this Object obj ) {
#if UNITY_EDITOR
if( EditorApplication.isPlaying == false ) {
EditorApplication.delayCall += () => {
if( Application.isPlaying == false )
Object.DestroyImmediate( obj );
else
Object.Destroy( obj );
};
} else
Object.Destroy( obj );
#else
Object.Destroy( obj );
#endif
}
// based on https://answers.unity.com/questions/420772/how-to-destroy-linked-components-when-object-is-de.html
public static void TryDestroyInOnDestroy( this Object caller, Object obj ) {
if( obj == null ) return;
#if UNITY_EDITOR
if( Application.isEditor && Application.isPlaying == false )
EditorApplication.delayCall += () => {
if( Application.isPlaying == false && obj != null )
Object.DestroyImmediate( obj );
}; // execute late in this editor frame, if still not playing
else
Object.Destroy( obj );
#else
Object.Destroy( obj );
#endif
}
// linq style product, similar to Sum()
public static int Product<T>( this IEnumerable<T> arr, Func<T, int> mulVal ) {
int product = 1;
foreach( T obj in arr )
product *= mulVal( obj );
return product;
}
public static float Product<T>( this IEnumerable<T> arr, Func<T, float> mulVal ) {
float product = 1;
foreach( T obj in arr )
product *= mulVal( obj );
return product;
}
// LINQ Zip with three IEnumerables instead of two
// from https://stackoverflow.com/a/10297160
public static IEnumerable<TResult> Zip<T1, T2, T3, TResult>( this IEnumerable<T1> source, IEnumerable<T2> second, IEnumerable<T3> third, Func<T1, T2, T3, TResult> func ) {
using( var e1 = source.GetEnumerator() )
using( var e2 = second.GetEnumerator() )
using( var e3 = third.GetEnumerator() )
while( e1.MoveNext() && e2.MoveNext() && e3.MoveNext() )
yield return func( e1.Current, e2.Current, e3.Current );
}
// from https://gist.github.com/sebgod/708b49b96fd7ce4a2791
public static int PopCount( this uint i ) {
i = i - ( ( i >> 1 ) & 0x55555555 ); // reuse input as temporary
i = ( i & 0x33333333 ) + ( ( i >> 2 ) & 0x33333333 ); // temp
i = ( ( i + ( i >> 4 ) & 0xF0F0F0F ) * 0x1010101 ) >> 24; // count
return unchecked( (int)i );
}
#if UNITY_EDITOR
public static bool HasPrefabOverride( this SerializedProperty property ) => property != null && property.serializedObject.targetObjects.Length == 1 && property.isInstantiatedPrefab && property.prefabOverride;
// array deconstruction
// slightly modified version of from https://stackoverflow.com/a/47816647
public static void Deconstruct<T>( this IList<T> list, out T first, out T second ) {
first = list.Count > 0 ? list[0] : default(T); // or throw
second = list.Count > 1 ? list[1] : default(T); // or throw
}
public static int GetIntValue( this SerializedProperty prop, int[] indexOverride = null ) {
int IntNoot() {
switch( prop.propertyType ) {
case SerializedPropertyType.Enum: return prop.enumValueIndex;
case SerializedPropertyType.Integer: return prop.intValue;
default: throw new IndexOutOfRangeException( "no, illegal >:I" );
}
}
int value = IntNoot();
return indexOverride != null ? Array.IndexOf( indexOverride, value ) : value;
}
public static void SetIntValue( this SerializedProperty prop, int value, int[] indexOverride = null ) {
if( indexOverride != null )
value = indexOverride[value];
switch( prop.propertyType ) {
case SerializedPropertyType.Enum:
prop.enumValueIndex = value;
return;
case SerializedPropertyType.Integer:
prop.intValue = value;
return;
}
}
public static bool[] TryGetMultiselectPressedStates( this SerializedProperty prop, int[] indexOverride, int entryCount ) {
SerializedObject so = prop.serializedObject;
if( so.isEditingMultipleObjects ) {
bool[] bools = new bool[entryCount];
foreach( int shapeID in so.targetObjects.Select( obj => new SerializedObject( obj ).FindProperty( prop.propertyPath ).GetIntValue( indexOverride ) ) ) {
int index = indexOverride == null ? shapeID : Array.IndexOf( indexOverride, shapeID );
if( index >= entryCount || index < 0 )
continue;
bools[index] = true;
}
return bools;
} else {
return null;
}
}
// slightly fixed code from https://forum.unity.com/threads/loop-through-serializedproperty-children.435119/
public static IEnumerable<SerializedProperty> GetVisibleChildren( this SerializedProperty serializedProperty ) {
SerializedProperty currentProperty = serializedProperty.Copy();
SerializedProperty nextSiblingProperty = serializedProperty.Copy();
nextSiblingProperty.NextVisible( false );
if( currentProperty.NextVisible( true ) ) {
do {
if( SerializedProperty.EqualContents( currentProperty, nextSiblingProperty ) )
break;
yield return currentProperty.Copy();
} while( currentProperty.NextVisible( false ) );
}
}
// modified version of https://www.codingame.com/playgrounds/2487/c---how-to-display-friendly-names-for-enumerations
public static string GetDescription( this Enum GenericEnum ) {
Type genericEnumType = GenericEnum.GetType();
MemberInfo[] memberInfo = genericEnumType.GetMember( GenericEnum.ToString() );
if( memberInfo.Length > 0 ) {
object[] attributes = memberInfo[0].GetCustomAttributes( typeof(DescriptionAttribute), false );
if( attributes.Any() )
return ( (DescriptionAttribute)attributes.ElementAt( 0 ) ).Description;
}
return GenericEnum.ToString();
}
#endif
}
}