//#define PB_RENDER_PICKER_TEXTURE
using System.Collections.Generic;
using System.Linq;
using UObject = UnityEngine.Object;
namespace UnityEngine.ProBuilder
{
///
/// A collection of settings that define how mesh element picking behaves.
///
public struct PickerOptions
{
///
/// Gets or sets whether to perform depth testing when testing elements with raycasting.
///
///
/// True to select only visible elements; false to select all elements regardless of visibility.
///
public bool depthTest { get; set; }
///
/// Gets or sets whether to require elements to be completely encompassed by the `rect` selection
/// (RectSelectMode.Complete) or to allow elements only partially inside the `rect` selection (RectSelectMode.Partial).
///
///
/// Does not apply to vertex picking.
///
public RectSelectMode rectSelectMode { get; set; }
static readonly PickerOptions k_Default = new PickerOptions()
{
depthTest = true,
rectSelectMode = RectSelectMode.Partial,
};
///
/// Represents a set of picking options with default values. By default, the property
/// is set to true and the property is set to .
///
public static PickerOptions Default
{
get { return k_Default; }
}
///
/// Evaluates whether the specified object is equivalent to this one.
///
/// The object to compare to this object.
/// True if both objects are PickerOptions and their property values match; false otherwise.
public override bool Equals(object obj)
{
if (!(obj is PickerOptions))
return false;
return Equals((PickerOptions)obj);
}
///
/// Evaluates whether the specified PickerOptions object contains the same settings as this one has.
///
/// The PickerOptions object to compare to this object.
/// True if the and property values match; false otherwise.
public bool Equals(PickerOptions other)
{
return depthTest == other.depthTest && rectSelectMode == other.rectSelectMode;
}
///
/// Returns the hash code for this object.
///
/// A hash code for the current object.
public override int GetHashCode()
{
unchecked
{
return (depthTest.GetHashCode() * 397) ^ (int)rectSelectMode;
}
}
///
/// Returns true if the two PickerOptions objects have matching values.
///
/// The first object to compare.
/// The second object to compare.
/// True if the objects have matching values; false otherwise.
public static bool operator==(PickerOptions a, PickerOptions b)
{
return a.Equals(b);
}
///
/// Returns true if the values for the two PickerOptions objects don't match.
///
/// The first object to compare.
/// The second object to compare.
/// True if the values for the two PickerOptions objects don't match; false otherwise.
public static bool operator!=(PickerOptions a, PickerOptions b)
{
return !a.Equals(b);
}
}
///
/// Provides functions for picking elements in a view by rendering a picker texture and testing pixels.
///
static partial class SelectionPickerRenderer
{
internal interface ISelectionPickerRenderer
{
Texture2D RenderLookupTexture(Camera camera, Shader shader, string tag, int width, int height);
}
const string k_FacePickerOcclusionTintUniform = "_Tint";
static readonly Color k_Blackf = new Color(0f, 0f, 0f, 1f);
static readonly Color k_Whitef = new Color(1f, 1f, 1f, 1f);
const uint k_PickerHashNone = 0x00;
const uint k_PickerHashMin = 0x1;
const uint k_PickerHashMax = 0x00FFFFFF;
const uint k_MinEdgePixelsForValidSelection = 1;
static bool s_Initialized = false;
static ISelectionPickerRenderer s_PickerRenderer = null;
static RenderTextureFormat renderTextureFormat
{
get
{
if (s_Initialized)
return s_RenderTextureFormat;
s_Initialized = true;
for (int i = 0; i < s_PreferredFormats.Length; i++)
{
if (SystemInfo.SupportsRenderTextureFormat(s_PreferredFormats[i]))
{
s_RenderTextureFormat = s_PreferredFormats[i];
break;
}
}
return s_RenderTextureFormat;
}
}
static TextureFormat textureFormat { get { return TextureFormat.ARGB32; } }
static RenderTextureFormat s_RenderTextureFormat = RenderTextureFormat.Default;
static RenderTextureFormat[] s_PreferredFormats = new RenderTextureFormat[]
{
RenderTextureFormat.ARGB32,
RenderTextureFormat.ARGBFloat,
};
///
/// Returns an appropriate implementation based on the graphic pipeline
/// to generate the lookup texture.
/// URP and Standard pipeline share the same picker implementation for now.
///
static ISelectionPickerRenderer pickerRenderer
{
get
{
if (s_PickerRenderer == null)
s_PickerRenderer =
ShouldUseHDRP()?
(ISelectionPickerRenderer)new SelectionPickerRendererHDRP()
: new SelectionPickerRendererStandard();
return s_PickerRenderer;
}
}
///
/// Given a camera and selection rect (in screen space) return a Dictionary containing the number of faces touched by the rect.
///
///
///
///
///
///
///
public static Dictionary> PickFacesInRect(
Camera camera,
Rect pickerRect,
IList selection,
int renderTextureWidth = -1,
int renderTextureHeight = -1)
{
Dictionary> map;
Texture2D tex = RenderSelectionPickerTexture(camera, selection, out map, renderTextureWidth, renderTextureHeight);
Color32[] pix = tex.GetPixels32();
int ox = System.Math.Max(0, Mathf.FloorToInt(pickerRect.x));
int oy = System.Math.Max(0, Mathf.FloorToInt((tex.height - pickerRect.y) - pickerRect.height));
int imageWidth = tex.width;
int imageHeight = tex.height;
int width = Mathf.FloorToInt(pickerRect.width);
int height = Mathf.FloorToInt(pickerRect.height);
UObject.DestroyImmediate(tex);
Dictionary> selected = new Dictionary>();
SimpleTuple hit;
HashSet faces = null;
HashSet used = new HashSet();
#if PB_RENDER_PICKER_TEXTURE
List rectImg = new List();
#endif
for (int y = oy; y < System.Math.Min(oy + height, imageHeight); y++)
{
for (int x = ox; x < System.Math.Min(ox + width, imageWidth); x++)
{
#if PB_RENDER_PICKER_TEXTURE
rectImg.Add(pix[y * imageWidth + x]);
#endif
uint v = SelectionPickerRenderer.DecodeRGBA(pix[y * imageWidth + x]);
if (used.Add(v) && map.TryGetValue(v, out hit))
{
if (selected.TryGetValue(hit.item1, out faces))
faces.Add(hit.item2);
else
selected.Add(hit.item1, new HashSet() { hit.item2 });
}
}
}
#if PB_RENDER_PICKER_TEXTURE
if (width > 0 && height > 0)
{
// Debug.Log("used: \n" + used.Select(x => string.Format("{0} ({1})", x, EncodeRGBA(x))).ToString("\n"));
Texture2D img = new Texture2D(width, height);
img.SetPixels(rectImg.ToArray());
img.Apply();
byte[] bytes = img.EncodeToPNG();
System.IO.File.WriteAllBytes("Assets/rect.png", bytes);
#if UNITY_EDITOR
UnityEditor.AssetDatabase.Refresh();
#endif
UObject.DestroyImmediate(img);
}
#endif
return selected;
}
///
/// Selects the vertex indexes contained within a rect (rectangular selection).
///
/// Use this camera to evaluate whether any vertices are inside the `rect`.
/// Rect is in GUI space, where 0,0 is the top left of the screen, and `width = cam.pixelWidth / pointsPerPixel`.
/// The collection of objects to check for selectability. ProBuilder verifies whether these objects fall inside the `rect`.
/// True to include mesh elements that are hidden; false otherwise
/// Width of the texture. If not specified, ProBuilder uses auto sizing (matches the camera's texture width).
/// Height of the texture. If not specified, ProBuilder uses auto sizing (matches the camera's texture height).
/// A dictionary of ProBuilderMesh objects and vertex indices that are in the selection `rect`.
public static Dictionary> PickVerticesInRect(
Camera camera,
Rect pickerRect,
IList selection,
bool doDepthTest,
int renderTextureWidth = -1,
int renderTextureHeight = -1)
{
Dictionary> map;
Dictionary> selected = new Dictionary>();
#if PB_RENDER_PICKER_TEXTURE
List rectImg = new List();
#endif
Texture2D tex = RenderSelectionPickerTexture(camera, selection, doDepthTest, out map, renderTextureWidth, renderTextureHeight);
Color32[] pix = tex.GetPixels32();
int ox = System.Math.Max(0, Mathf.FloorToInt(pickerRect.x));
int oy = System.Math.Max(0, Mathf.FloorToInt((tex.height - pickerRect.y) - pickerRect.height));
int imageWidth = tex.width;
int imageHeight = tex.height;
int width = Mathf.FloorToInt(pickerRect.width);
int height = Mathf.FloorToInt(pickerRect.height);
UObject.DestroyImmediate(tex);
SimpleTuple hit;
HashSet indexes = null;
HashSet used = new HashSet();
for (int y = oy; y < System.Math.Min(oy + height, imageHeight); y++)
{
for (int x = ox; x < System.Math.Min(ox + width, imageWidth); x++)
{
uint v = DecodeRGBA(pix[y * imageWidth + x]);
#if PB_RENDER_PICKER_TEXTURE
rectImg.Add(pix[y * imageWidth + x]);
#endif
if (used.Add(v) && map.TryGetValue(v, out hit))
{
if (selected.TryGetValue(hit.item1, out indexes))
indexes.Add(hit.item2);
else
selected.Add(hit.item1, new HashSet() { hit.item2 });
}
}
}
var coincidentSelection = new Dictionary>();
// workaround for picking vertices that share a position but are not shared
foreach (var meshSelection in selected)
{
var positions = meshSelection.Key.positionsInternal;
var sharedVertices = meshSelection.Key.sharedVerticesInternal;
var positionHash = new HashSet(meshSelection.Value.Select(x => VectorHash.GetHashCode(positions[sharedVertices[x][0]])));
var collected = new HashSet();
for (int i = 0, c = sharedVertices.Length; i < c; i++)
{
var hash = VectorHash.GetHashCode(positions[sharedVertices[i][0]]);
if (positionHash.Contains(hash))
collected.Add(i);
}
coincidentSelection.Add(meshSelection.Key, collected);
}
selected = coincidentSelection;
#if PB_RENDER_PICKER_TEXTURE
if (width > 0 && height > 0)
{
Texture2D img = new Texture2D(width, height);
img.SetPixels(rectImg.ToArray());
img.Apply();
System.IO.File.WriteAllBytes("Assets/rect_" + s_RenderTextureFormat.ToString() + ".png", img.EncodeToPNG());
#if UNITY_EDITOR
UnityEditor.AssetDatabase.Refresh();
#endif
UObject.DestroyImmediate(img);
}
#endif
return selected;
}
///
/// Selects the objects contained within a rect (rectangular selection).
///
/// Use this camera to evaluate whether any edge object(s) are inside the `rect`.
/// Rect is in GUI space, where 0,0 is the top left of the screen, and `width = cam.pixelWidth / pointsPerPixel`.
/// The collection of objects to check for selectability. ProBuilder verifies whether these objects fall inside the `rect`.
/// True to include mesh elements that are hidden; false otherwise
/// Width of the texture. If not specified, ProBuilder uses auto sizing (matches the camera's texture width).
/// Height of the texture. If not specified, ProBuilder uses auto sizing (matches the camera's texture height).
/// A dictionary of ProBuilderMesh and Edge objects that are in the selection `rect`.
public static Dictionary> PickEdgesInRect(
Camera camera,
Rect pickerRect,
IList selection,
bool doDepthTest,
int renderTextureWidth = -1,
int renderTextureHeight = -1)
{
var selected = new Dictionary>();
#if PB_RENDER_PICKER_TEXTURE
List rectImg = new List();
#endif
Dictionary> map;
Texture2D tex = RenderSelectionPickerTexture(camera, selection, doDepthTest, out map, renderTextureWidth, renderTextureHeight);
Color32[] pix = tex.GetPixels32();
#if PB_RENDER_PICKER_TEXTURE
System.IO.File.WriteAllBytes("Assets/edge_scene.png", tex.EncodeToPNG());
#endif
int ox = System.Math.Max(0, Mathf.FloorToInt(pickerRect.x));
int oy = System.Math.Max(0, Mathf.FloorToInt((tex.height - pickerRect.y) - pickerRect.height));
int imageWidth = tex.width;
int imageHeight = tex.height;
int width = Mathf.FloorToInt(pickerRect.width);
int height = Mathf.FloorToInt(pickerRect.height);
UObject.DestroyImmediate(tex);
var pixelCount = new Dictionary();
for (int y = oy; y < System.Math.Min(oy + height, imageHeight); y++)
{
for (int x = ox; x < System.Math.Min(ox + width, imageWidth); x++)
{
#if PB_RENDER_PICKER_TEXTURE
rectImg.Add(pix[y * imageWidth + x]);
#endif
uint v = DecodeRGBA(pix[y * imageWidth + x]);
if (v == k_PickerHashNone || v == k_PickerHashMax)
continue;
if (!pixelCount.ContainsKey(v))
pixelCount.Add(v, 1);
else
pixelCount[v] = pixelCount[v] + 1;
}
}
foreach (var kvp in pixelCount)
{
SimpleTuple hit;
if (kvp.Value > k_MinEdgePixelsForValidSelection && map.TryGetValue(kvp.Key, out hit))
{
HashSet edges = null;
if (selected.TryGetValue(hit.item1, out edges))
edges.Add(hit.item2);
else
selected.Add(hit.item1, new HashSet() {hit.item2});
}
}
#if PB_RENDER_PICKER_TEXTURE
if (width > 0 && height > 0)
{
Texture2D img = new Texture2D(width, height);
img.SetPixels(rectImg.ToArray());
img.Apply();
System.IO.File.WriteAllBytes("Assets/edge_rect_" + s_RenderTextureFormat.ToString() + ".png", img.EncodeToPNG());
#if UNITY_EDITOR
UnityEditor.AssetDatabase.Refresh();
#endif
UObject.DestroyImmediate(img);
}
#endif
return selected;
}
///
/// Render the pb_Object selection with the special selection picker shader and return a texture and color -> {object, face} dictionary.
///
///
///
///
///
///
///
internal static Texture2D RenderSelectionPickerTexture(
Camera camera,
IList selection,
out Dictionary> map,
int width = -1,
int height = -1)
{
var pickerObjects = GenerateFacePickingObjects(selection, out map);
BuiltinMaterials.facePickerMaterial.SetColor(k_FacePickerOcclusionTintUniform, k_Whitef);
Texture2D tex = pickerRenderer.RenderLookupTexture(camera, BuiltinMaterials.selectionPickerShader, "ProBuilderPicker", width, height);
foreach (GameObject go in pickerObjects)
{
UObject.DestroyImmediate(go.GetComponent().sharedMesh);
UObject.DestroyImmediate(go);
}
return tex;
}
///
/// Render the pb_Object selection with the special selection picker shader and return a texture and color -> {object, sharedIndex} dictionary.
///
///
///
///
///
///
///
///
internal static Texture2D RenderSelectionPickerTexture(
Camera camera,
IList selection,
bool doDepthTest,
out Dictionary> map,
int width = -1,
int height = -1)
{
GameObject[] depthObjects, pickerObjects;
GenerateVertexPickingObjects(selection, doDepthTest, out map, out depthObjects, out pickerObjects);
BuiltinMaterials.facePickerMaterial.SetColor(k_FacePickerOcclusionTintUniform, k_Blackf);
Texture2D tex = pickerRenderer.RenderLookupTexture(camera, BuiltinMaterials.selectionPickerShader, "ProBuilderPicker", width, height);
for (int i = 0, c = pickerObjects.Length; i < c; i++)
{
UObject.DestroyImmediate(pickerObjects[i].GetComponent().sharedMesh);
UObject.DestroyImmediate(pickerObjects[i]);
}
if (doDepthTest)
{
for (int i = 0, c = depthObjects.Length; i < c; i++)
{
UObject.DestroyImmediate(depthObjects[i]);
}
}
return tex;
}
///
/// Render the pb_Object selection with the special selection picker shader and return a texture and color -> {object, edge} dictionary.
///
///
///
///
///
///
///
///
internal static Texture2D RenderSelectionPickerTexture(
Camera camera,
IList selection,
bool doDepthTest,
out Dictionary> map,
int width = -1,
int height = -1)
{
GameObject[] depthObjects, pickerObjects;
GenerateEdgePickingObjects(selection, doDepthTest, out map, out depthObjects, out pickerObjects);
BuiltinMaterials.facePickerMaterial.SetColor(k_FacePickerOcclusionTintUniform, k_Blackf);
Texture2D tex = pickerRenderer.RenderLookupTexture(camera, BuiltinMaterials.selectionPickerShader, "ProBuilderPicker", width, height);
for (int i = 0, c = pickerObjects.Length; i < c; i++)
{
UObject.DestroyImmediate(pickerObjects[i].GetComponent().sharedMesh);
UObject.DestroyImmediate(pickerObjects[i]);
}
if (doDepthTest)
{
for (int i = 0, c = depthObjects.Length; i < c; i++)
{
UObject.DestroyImmediate(depthObjects[i]);
}
}
return tex;
}
static GameObject[] GenerateFacePickingObjects(
IList selection,
out Dictionary> map)
{
int selectionCount = selection.Count;
GameObject[] pickerObjects = new GameObject[selectionCount];
map = new Dictionary>();
uint index = 0;
for (int i = 0; i < selectionCount; i++)
{
var pb = selection[i];
Mesh m = new Mesh();
m.vertices = pb.positionsInternal;
m.triangles = pb.facesInternal.SelectMany(x => x.indexesInternal).ToArray();
Color32[] colors = new Color32[m.vertexCount];
foreach (Face f in pb.facesInternal)
{
Color32 color = EncodeRGBA(index++);
map.Add(DecodeRGBA(color), new SimpleTuple(pb, f));
for (int n = 0; n < f.distinctIndexesInternal.Length; n++)
colors[f.distinctIndexesInternal[n]] = color;
}
m.colors32 = colors;
GameObject go = InternalUtility.MeshGameObjectWithTransform(pb.name + " (Face Depth Test)", pb.transform, m,
BuiltinMaterials.facePickerMaterial, true);
pickerObjects[i] = go;
}
return pickerObjects;
}
static void GenerateVertexPickingObjects(
IList selection,
bool doDepthTest,
out Dictionary> map,
out GameObject[] depthObjects,
out GameObject[] pickerObjects)
{
map = new Dictionary>();
// don't start at 0 because that means one vertex would be black, matching
// the color used to cull hidden vertices.
uint index = 0x02;
int selectionCount = selection.Count;
pickerObjects = new GameObject[selectionCount];
for (int i = 0; i < selectionCount; i++)
{
// build vertex billboards
var pb = selection[i];
var mesh = BuildVertexMesh(pb, map, ref index);
GameObject go = InternalUtility.MeshGameObjectWithTransform(pb.name + " (Vertex Billboards)", pb.transform, mesh,
BuiltinMaterials.vertexPickerMaterial, true);
pickerObjects[i] = go;
}
if (doDepthTest)
{
depthObjects = new GameObject[selectionCount];
// copy the select gameobject just for z-write
for (int i = 0; i < selectionCount; i++)
{
var pb = selection[i];
GameObject go = InternalUtility.MeshGameObjectWithTransform(pb.name + " (Depth Mask)", pb.transform, pb.mesh,
BuiltinMaterials.facePickerMaterial, true);
depthObjects[i] = go;
}
}
else
{
depthObjects = null;
}
}
static void GenerateEdgePickingObjects(
IList selection,
bool doDepthTest,
out Dictionary> map,
out GameObject[] depthObjects,
out GameObject[] pickerObjects)
{
map = new Dictionary>();
uint index = 0x2;
int selectionCount = selection.Count;
pickerObjects = new GameObject[selectionCount];
for (int i = 0; i < selectionCount; i++)
{
// build edge billboards
var pb = selection[i];
var mesh = BuildEdgeMesh(pb, map, ref index);
GameObject go = InternalUtility.MeshGameObjectWithTransform(pb.name + " (Edge Billboards)", pb.transform, mesh,
BuiltinMaterials.edgePickerMaterial, true);
pickerObjects[i] = go;
}
if (doDepthTest)
{
depthObjects = new GameObject[selectionCount];
for (int i = 0; i < selectionCount; i++)
{
var pb = selection[i];
// copy the select gameobject just for z-write
GameObject go = InternalUtility.MeshGameObjectWithTransform(pb.name + " (Depth Mask)", pb.transform, pb.mesh,
BuiltinMaterials.facePickerMaterial, true);
depthObjects[i] = go;
}
}
else
{
depthObjects = null;
}
}
static Mesh BuildVertexMesh(ProBuilderMesh pb, Dictionary> map, ref uint index)
{
int length = System.Math.Min(pb.sharedVerticesInternal.Length, ushort.MaxValue / 4 - 1);
Vector3[] t_billboards = new Vector3[length * 4];
Vector2[] t_uvs = new Vector2[length * 4];
Vector2[] t_uv2 = new Vector2[length * 4];
Color[] t_col = new Color[length * 4];
int[] t_tris = new int[length * 6];
int n = 0;
int t = 0;
Vector3 up = Vector3.up;
Vector3 right = Vector3.right;
for (int i = 0; i < length; i++)
{
Vector3 v = pb.positionsInternal[pb.sharedVerticesInternal[i][0]];
t_billboards[t + 0] = v;
t_billboards[t + 1] = v;
t_billboards[t + 2] = v;
t_billboards[t + 3] = v;
t_uvs[t + 0] = Vector3.zero;
t_uvs[t + 1] = Vector3.right;
t_uvs[t + 2] = Vector3.up;
t_uvs[t + 3] = Vector3.one;
t_uv2[t + 0] = -up - right;
t_uv2[t + 1] = -up + right;
t_uv2[t + 2] = up - right;
t_uv2[t + 3] = up + right;
t_tris[n + 0] = t + 0;
t_tris[n + 1] = t + 1;
t_tris[n + 2] = t + 2;
t_tris[n + 3] = t + 1;
t_tris[n + 4] = t + 3;
t_tris[n + 5] = t + 2;
Color32 color = EncodeRGBA(index);
map.Add(index++, new SimpleTuple(pb, i));
t_col[t + 0] = color;
t_col[t + 1] = color;
t_col[t + 2] = color;
t_col[t + 3] = color;
t += 4;
n += 6;
}
Mesh mesh = new Mesh();
mesh.name = "Vertex Billboard";
mesh.vertices = t_billboards;
mesh.uv = t_uvs;
mesh.uv2 = t_uv2;
mesh.colors = t_col;
mesh.triangles = t_tris;
return mesh;
}
static Mesh BuildEdgeMesh(ProBuilderMesh pb, Dictionary> map, ref uint index)
{
int edgeCount = 0;
int faceCount = pb.faceCount;
for (int i = 0; i < faceCount; i++)
edgeCount += pb.facesInternal[i].edgesInternal.Length;
int elementCount = System.Math.Min(edgeCount, ushort.MaxValue / 2 - 1);
Vector3[] positions = new Vector3[elementCount * 2];
Color32[] color = new Color32[elementCount * 2];
int[] tris = new int[elementCount * 2];
int edgeIndex = 0;
for (int i = 0; i < faceCount && edgeIndex < elementCount; i++)
{
for (int n = 0; n < pb.facesInternal[i].edgesInternal.Length && edgeIndex < elementCount; n++)
{
var edge = pb.facesInternal[i].edgesInternal[n];
Vector3 a = pb.positionsInternal[edge.a];
Vector3 b = pb.positionsInternal[edge.b];
int positionIndex = edgeIndex * 2;
positions[positionIndex + 0] = a;
positions[positionIndex + 1] = b;
Color32 c = EncodeRGBA(index);
map.Add(index++, new SimpleTuple(pb, edge));
color[positionIndex + 0] = c;
color[positionIndex + 1] = c;
tris[positionIndex + 0] = positionIndex + 0;
tris[positionIndex + 1] = positionIndex + 1;
edgeIndex++;
}
}
Mesh mesh = new Mesh();
mesh.name = "Edge Billboard";
mesh.vertices = positions;
mesh.colors32 = color;
mesh.subMeshCount = 1;
mesh.SetIndices(tris, MeshTopology.Lines, 0);
return mesh;
}
///
/// Decodes Color32.RGB values to a 32-bit unsigned int, using the RGB as the little bytes. Discards the hi byte (alpha).
///
/// The color to decode.
/// 32-bit unsigned int containing the decoded RGB values.
public static uint DecodeRGBA(Color32 color)
{
uint r = (uint)color.r;
uint g = (uint)color.g;
uint b = (uint)color.b;
if (System.BitConverter.IsLittleEndian)
return r << 16 | g << 8 | b;
else
return r << 24 | g << 16 | b << 8;
}
///
/// Encodes the low 24 bits of a 32-bit unsigned int to Color32.RGB values, using 255 for the alpha.
///
///
/// Color32 containing the encoded values.
public static Color32 EncodeRGBA(uint hash)
{
// skip using BitConverter.GetBytes since this is super simple
// bit math, and allocating arrays for each conversion is expensive
if (System.BitConverter.IsLittleEndian)
return new Color32(
(byte)(hash >> 16 & 0xFF),
(byte)(hash >> 8 & 0xFF),
(byte)(hash & 0xFF),
(byte)(255));
else
return new Color32(
(byte)(hash >> 24 & 0xFF),
(byte)(hash >> 16 & 0xFF),
(byte)(hash >> 8 & 0xFF),
(byte)(255));
}
static bool ShouldUseHDRP()
{
#if HDRP_7_1_0_OR_NEWER
return true;
#else
return false;
#endif
}
}
}