Add ultimate xr
This commit is contained in:
@@ -0,0 +1,182 @@
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// <copyright file="Color32Ext.cs" company="VRMADA">
|
||||
// Copyright (c) VRMADA, All rights reserved.
|
||||
// </copyright>
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using UltimateXR.Extensions.System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UltimateXR.Extensions.Unity.Render
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="Color32" /> extensions.
|
||||
/// </summary>
|
||||
public static class Color32Ext
|
||||
{
|
||||
#region Public Methods
|
||||
|
||||
/// <summary>
|
||||
/// Transforms an array of bytes to a <see cref="Color32" /> component by component. If there are not enough values to
|
||||
/// read, the remaining values are set to <see cref="byte.MinValue" /> (0) for RGB and <see cref="byte.MaxValue" />
|
||||
/// (255) for A.
|
||||
/// </summary>
|
||||
/// <param name="data">Source data</param>
|
||||
/// <returns>Result color</returns>
|
||||
public static Color32 ToColor32(this byte[] data)
|
||||
{
|
||||
switch (data.Length)
|
||||
{
|
||||
case 0: return default;
|
||||
case 1: return new Color32(data[0], byte.MinValue, byte.MinValue, byte.MaxValue);
|
||||
case 2: return new Color32(data[0], data[1], byte.MinValue, byte.MaxValue);
|
||||
case 3: return new Color32(data[0], data[1], data[2], byte.MaxValue);
|
||||
default: return new Color32(data[0], data[1], data[2], data[3]);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Transforms a <see cref="Color32" /> value into the int value it encodes the color in.
|
||||
/// </summary>
|
||||
/// <param name="self">Color</param>
|
||||
/// <returns>Int value</returns>
|
||||
public static int ToInt(this in Color32 self)
|
||||
{
|
||||
return self.r << 24 | self.g << 16 | self.b << 8 | self.a;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clamps <see cref="Color32" /> values component by component.
|
||||
/// </summary>
|
||||
/// <param name="self">Color whose components to clamp</param>
|
||||
/// <param name="min">Minimum RGB values</param>
|
||||
/// <param name="max">Maximum RGB values</param>
|
||||
/// <returns>Clamped color</returns>
|
||||
public static Color32 Clamp(this in Color32 self, in Color32 min, in Color32 max)
|
||||
{
|
||||
byte[] result = new byte[VectorLength];
|
||||
for (int i = 0; i < VectorLength; ++i)
|
||||
{
|
||||
result[i] = (byte)Mathf.Clamp(self[i], min[i], max[i]);
|
||||
}
|
||||
|
||||
return result.ToColor32();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Multiplies two colors by multiplying each component.
|
||||
/// </summary>
|
||||
/// <param name="self">Operand A</param>
|
||||
/// <param name="other">Operand B</param>
|
||||
/// <returns>Result color</returns>
|
||||
public static Color32 Multiply(this in Color32 self, in Color32 other)
|
||||
{
|
||||
return new Color32((byte)(self.r * other.r),
|
||||
(byte)(self.g * other.g),
|
||||
(byte)(self.b * other.b),
|
||||
(byte)(self.a * other.a));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compares two colors.
|
||||
/// </summary>
|
||||
/// <param name="self">First color to compare</param>
|
||||
/// <param name="other">Second color to compare</param>
|
||||
/// <returns>Whether the two colors are the same</returns>
|
||||
public static bool IsSameColor(this in Color32 self, in Color32 other)
|
||||
{
|
||||
return self.r == other.r && self.g == other.g && self.b == other.b && self.a == other.a;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts the color to a HTML color value (#RRGGBB or #RRGGBBAA).
|
||||
/// </summary>
|
||||
/// <param name="self">Color to convert</param>
|
||||
/// <returns>HTML color string</returns>
|
||||
public static string ToHtml(this in Color32 self)
|
||||
{
|
||||
return self.a == 255 ? string.Format(StringFormatRGB, self.r, self.g, self.b) : string.Format(StringFormatRGBA, self.r, self.g, self.b, self.a);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to parse a <see cref="Color32" /> from an HTML string (#RRGGBB or #RRGGBBAA).
|
||||
/// </summary>
|
||||
/// <param name="html">Source HTML string</param>
|
||||
/// <param name="result">Parsed color or the default color value if there was an error</param>
|
||||
/// <returns>Whether the color was parsed successfully</returns>
|
||||
public static bool TryParse(string html, out Color32 result)
|
||||
{
|
||||
try
|
||||
{
|
||||
result = Parse(html);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
result = default;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses a <see cref="Color32" /> from an HTML string (#RRGGBB or #RRGGBBAA).
|
||||
/// </summary>
|
||||
/// <param name="html">Source HTML string</param>
|
||||
/// <returns>The parsed color</returns>
|
||||
/// <exception cref="FormatException">The string had an incorrect format</exception>
|
||||
public static Color32 Parse(string html)
|
||||
{
|
||||
html.ThrowIfNull(nameof(html));
|
||||
|
||||
Match match = _regex.Match(html);
|
||||
if (!match.Success)
|
||||
{
|
||||
throw new FormatException($"Input string [{html}] does not have the right format: #RRGGBB or #RRGGBBAA");
|
||||
}
|
||||
|
||||
byte[] colorBytes = new byte[VectorLength];
|
||||
|
||||
for (int i = 0; i < VectorLength - 1; ++i)
|
||||
{
|
||||
string hex = match.Groups[i + 1].Value;
|
||||
colorBytes[i] = byte.Parse(hex, NumberStyles.HexNumber, CultureInfo.InvariantCulture.NumberFormat);
|
||||
}
|
||||
|
||||
string aa = match.Groups[VectorLength].Value;
|
||||
colorBytes[VectorLength - 1] = aa == string.Empty
|
||||
? byte.MaxValue
|
||||
: byte.Parse(aa, NumberStyles.HexNumber, CultureInfo.InvariantCulture.NumberFormat);
|
||||
|
||||
return colorBytes.ToColor32();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses asynchronously a <see cref="Color32" /> from an HTML string (#RRGGBB or #RRGGBBAA).
|
||||
/// </summary>
|
||||
/// <param name="html">Source HTML string</param>
|
||||
/// <param name="ct">Optional cancellation token, to cancel the operation</param>
|
||||
/// <returns>An awaitable <see cref="Task" /> that returns the parsed color</returns>
|
||||
/// <exception cref="FormatException">The string had an incorrect format</exception>
|
||||
public static Task<Color32?> ParseAsync(string html, CancellationToken ct = default)
|
||||
{
|
||||
return Task.Run(() => TryParse(html, out Color32 result) ? result : (Color32?)null, ct);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Types & Data
|
||||
|
||||
private const int VectorLength = 4;
|
||||
private const string StringFormatRGBA = "#{0:X2}{1:X2}{2:X2}{3:X2}";
|
||||
private const string StringFormatRGB = "#{0:X2}{1:X2}{2:X2}";
|
||||
private const string RegexPattern = "^#?([0-9A-F]{2})([0-9A-F]{2})([0-9A-F]{2})([0-9A-F]{2})?$";
|
||||
|
||||
private static readonly Regex _regex = new Regex(RegexPattern);
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6f69c5ccb17b9474c88c59e005eb072a
|
||||
timeCreated: 1621500585
|
||||
@@ -0,0 +1,205 @@
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// <copyright file="ColorExt.cs" company="VRMADA">
|
||||
// Copyright (c) VRMADA, All rights reserved.
|
||||
// </copyright>
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using UltimateXR.Extensions.System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UltimateXR.Extensions.Unity.Render
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="Color" /> extensions.
|
||||
/// </summary>
|
||||
public static class ColorExt
|
||||
{
|
||||
#region Public Methods
|
||||
|
||||
/// <summary>
|
||||
/// Transforms an array of floats to a <see cref="Color" /> component by component. If there are not enough values to
|
||||
/// read, the remaining values are set to 0.0 for RGB and 1.0 for A.
|
||||
/// </summary>
|
||||
/// <param name="data">Source data</param>
|
||||
/// <returns>Result color</returns>
|
||||
public static Color ToColor(this float[] data)
|
||||
{
|
||||
return data.Length switch
|
||||
{
|
||||
0 => default,
|
||||
1 => new Color(data[0], 0f, 0f, 1f),
|
||||
2 => new Color(data[0], data[1], 0f, 1f),
|
||||
3 => new Color(data[0], data[1], data[2], 1f),
|
||||
_ => new Color(data[0], data[1], data[2], data[3])
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clamps <see cref="Color" /> values component by component.
|
||||
/// </summary>
|
||||
/// <param name="self">Color whose components to clamp</param>
|
||||
/// <param name="min">Minimum RGB values</param>
|
||||
/// <param name="max">Maximum RGB values</param>
|
||||
/// <returns>Clamped color</returns>
|
||||
public static Color Clamp(this in Color self, in Color min, in Color max)
|
||||
{
|
||||
float[] result = new float[VectorLength];
|
||||
for (int i = 0; i < VectorLength; ++i)
|
||||
{
|
||||
result[i] = Mathf.Clamp(self[i], min[i], max[i]);
|
||||
}
|
||||
|
||||
return result.ToColor();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Multiplies two colors by multiplying each component.
|
||||
/// </summary>
|
||||
/// <param name="self">Operand A</param>
|
||||
/// <param name="other">Operand B</param>
|
||||
/// <returns>Result color</returns>
|
||||
public static Color Multiply(this in Color self, in Color other)
|
||||
{
|
||||
return new Color(self.r * other.r,
|
||||
self.g * other.g,
|
||||
self.b * other.b,
|
||||
self.a * other.a);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a color based on an already existing color and an alpha value.
|
||||
/// </summary>
|
||||
/// <param name="color">Color value</param>
|
||||
/// <param name="alpha">Alpha value</param>
|
||||
/// <returns>
|
||||
/// Result of combining the RGB of the <paramref name="color" /> value and alpha of <paramref name="alpha" />
|
||||
/// </returns>
|
||||
public static Color ColorAlpha(in Color color, float alpha)
|
||||
{
|
||||
return new Color(color.r, color.g, color.b, alpha);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a color based on an already existing color and an alpha value.
|
||||
/// </summary>
|
||||
/// <param name="self">Color value</param>
|
||||
/// <param name="alpha">Alpha value</param>
|
||||
/// <returns>
|
||||
/// Result of combining the RGBA of the color value and <paramref name="alpha" />
|
||||
/// </returns>
|
||||
public static Color WithAlpha(this in Color self, float alpha)
|
||||
{
|
||||
return ColorAlpha(self, alpha);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a color based on an already existing color and a brightness scale value.
|
||||
/// </summary>
|
||||
/// <param name="color">Color value</param>
|
||||
/// <param name="brightnessScale">The brightness scale factor</param>
|
||||
/// <returns>Color with adjusted brightness</returns>
|
||||
public static Color ScaleColorBrightness(this in Color color, float brightnessScale)
|
||||
{
|
||||
Color.RGBToHSV(color, out float h, out float s, out float v);
|
||||
v *= brightnessScale;
|
||||
return Color.HSVToRGB(h, s, v);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts the color to a HTML color value (#RRGGBB or #RRGGBBAA).
|
||||
/// </summary>
|
||||
/// <param name="self">Color to convert</param>
|
||||
/// <returns>HTML color string</returns>
|
||||
public static string ToHtml(this in Color self)
|
||||
{
|
||||
return Mathf.Approximately(self.a, 1f)
|
||||
? string.Format(StringFormatRGB,
|
||||
(byte)Mathf.Round(Mathf.Clamp01(self.r) * byte.MaxValue),
|
||||
(byte)Mathf.Round(Mathf.Clamp01(self.g) * byte.MaxValue),
|
||||
(byte)Mathf.Round(Mathf.Clamp01(self.b) * byte.MaxValue))
|
||||
: string.Format(StringFormatRGBA,
|
||||
(byte)Mathf.Round(Mathf.Clamp01(self.r) * byte.MaxValue),
|
||||
(byte)Mathf.Round(Mathf.Clamp01(self.g) * byte.MaxValue),
|
||||
(byte)Mathf.Round(Mathf.Clamp01(self.b) * byte.MaxValue),
|
||||
(byte)Mathf.Round(Mathf.Clamp01(self.a) * byte.MaxValue));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to parse a <see cref="Color" /> from an HTML string (#RRGGBB or #RRGGBBAA).
|
||||
/// </summary>
|
||||
/// <param name="html">Source HTML string</param>
|
||||
/// <param name="result">Parsed color or the default color value if there was an error</param>
|
||||
/// <returns>Whether the color was parsed successfully</returns>
|
||||
public static bool TryParse(string html, out Color result)
|
||||
{
|
||||
try
|
||||
{
|
||||
result = Parse(html);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
result = default;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses a <see cref="Color" /> from an HTML string (#RRGGBB or #RRGGBBAA).
|
||||
/// </summary>
|
||||
/// <param name="html">Source HTML string</param>
|
||||
/// <returns>The parsed color</returns>
|
||||
/// <exception cref="FormatException">The string had an incorrect format</exception>
|
||||
public static Color Parse(string html)
|
||||
{
|
||||
html.ThrowIfNull(nameof(html));
|
||||
|
||||
Match match = _regex.Match(html);
|
||||
if (!match.Success)
|
||||
{
|
||||
throw new FormatException($"Input string [{html}] does not have the right format: #RRGGBB or #RRGGBBAA");
|
||||
}
|
||||
|
||||
float[] result = new float[VectorLength];
|
||||
for (int i = 0; i < VectorLength - 1; ++i)
|
||||
{
|
||||
string hex = match.Groups[i + 1].Value;
|
||||
result[i] = byte.Parse(hex, NumberStyles.HexNumber, CultureInfo.InvariantCulture.NumberFormat) / (float)byte.MaxValue;
|
||||
}
|
||||
|
||||
string aa = match.Groups[VectorLength].Value;
|
||||
result[VectorLength - 1] = aa != string.Empty ? byte.Parse(aa, NumberStyles.HexNumber, CultureInfo.InvariantCulture.NumberFormat) / (float)byte.MaxValue : 1f;
|
||||
|
||||
return result.ToColor();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses asynchronously a <see cref="Color" /> from an HTML string (#RRGGBB or #RRGGBBAA).
|
||||
/// </summary>
|
||||
/// <param name="html">Source HTML string</param>
|
||||
/// <param name="ct">Optional cancellation token, to cancel the operation</param>
|
||||
/// <returns>An awaitable <see cref="Task" /> that returns the parsed color</returns>
|
||||
/// <exception cref="FormatException">The string had an incorrect format</exception>
|
||||
public static Task<Color?> ParseAsync(string html, CancellationToken ct = default)
|
||||
{
|
||||
return Task.Run(() => TryParse(html, out Color result) ? result : (Color?)null, ct);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Types & Data
|
||||
|
||||
private const int VectorLength = 4;
|
||||
private const string StringFormatRGBA = "#{0:X2}{1:X2}{2:X2}{3:X2}";
|
||||
private const string StringFormatRGB = "#{0:X2}{1:X2}{2:X2}";
|
||||
private const string RegexPattern = "^#?([0-9A-F]{2})([0-9A-F]{2})([0-9A-F]{2})([0-9A-F]{2})?$";
|
||||
|
||||
private static readonly Regex _regex = new Regex(RegexPattern);
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 29cad001faae79346949b7a9251a414a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,112 @@
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// <copyright file="ImageExt.cs" company="VRMADA">
|
||||
// Copyright (c) VRMADA, All rights reserved.
|
||||
// </copyright>
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using UltimateXR.Extensions.System;
|
||||
using UltimateXR.Extensions.System.IO;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace UltimateXR.Extensions.Unity.Render
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="Image" /> extensions.
|
||||
/// </summary>
|
||||
public static class ImageExt
|
||||
{
|
||||
#region Public Methods
|
||||
|
||||
/// <summary>
|
||||
/// Loads a sprite asynchronously from a base64 encoded string and assigns it to the
|
||||
/// <see cref="Image.overrideSprite" /> property of an <see cref="Image" />.
|
||||
/// </summary>
|
||||
/// <param name="self">Target <see cref="Image" /></param>
|
||||
/// <param name="base64">Base64 encoded string. See <see cref="SpriteExt.ReadSpriteBase64Async" /></param>
|
||||
/// <param name="ct">Optional cancellation token, to cancel the operation</param>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="base64" /> is null or empty</exception>
|
||||
/// <exception cref="OperationCanceledException">Task canceled using <paramref name="ct" /></exception>
|
||||
/// <exception cref="FormatException">
|
||||
/// The length of <paramref name="base64" />, ignoring white-space characters, is not
|
||||
/// zero or a multiple of 4
|
||||
/// </exception>
|
||||
public static async Task OverrideSpriteFromBase64Async(this Image self, string base64, CancellationToken ct = default)
|
||||
{
|
||||
self.ThrowIfNull(nameof(self));
|
||||
self.overrideSprite = await SpriteExt.ReadSpriteBase64Async(self, base64, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads a sprite asynchronously from an URI and assigns it to the <see cref="Image.overrideSprite" /> property of an
|
||||
/// <see cref="Image" />.
|
||||
/// </summary>
|
||||
/// <param name="self">Target image</param>
|
||||
/// <param name="uri">File location. See <see cref="FileExt.Read" /></param>
|
||||
/// <param name="ct">Optional cancellation token, to cancel the operation</param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="uri" /> is null or empty</exception>
|
||||
/// <exception cref="OperationCanceledException">Task canceled using <paramref name="ct" /></exception>
|
||||
/// <exception cref="FileNotFoundException">The file specified in <paramref name="uri" /> was not found</exception>
|
||||
/// <exception cref="NotSupportedException"><paramref name="uri" /> is in an invalid format</exception>
|
||||
/// <exception cref="IOException">An I/O error occurred while opening the file</exception>
|
||||
/// <exception cref="InvalidOperationException">The stream is currently in use by a previous read operation</exception>
|
||||
public static async Task OverrideSpriteFromUriAsync(this Image self, string uri, CancellationToken ct = default)
|
||||
{
|
||||
self.ThrowIfNull(nameof(self));
|
||||
self.overrideSprite = await SpriteExt.ReadSpriteFileAsync(self, uri, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to load a sprite asynchronously from an URI and assign it to the <see cref="Image.overrideSprite" /> property
|
||||
/// of an <see cref="Image" />.
|
||||
/// </summary>
|
||||
/// <param name="self">Target image</param>
|
||||
/// <param name="uri">File location. See <see cref="FileExt.Read" /></param>
|
||||
/// <param name="ct">Optional cancellation token, to cancel the operation</param>
|
||||
/// <returns>
|
||||
/// Whether the sprite was correctly load and the <see cref="Image" /> had its <see cref="Image.overrideSprite" />
|
||||
/// assigned
|
||||
/// </returns>
|
||||
public static async Task<bool> TryOverrideSpriteFromUriAsync(this Image self, string uri, CancellationToken ct = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
await self.OverrideSpriteFromUriAsync(uri, ct);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to load a sprite asynchronously from a base64 encoded string and assign it to the
|
||||
/// <see cref="Image.overrideSprite" /> property of an <see cref="Image" />.
|
||||
/// </summary>
|
||||
/// <param name="self">Target image</param>
|
||||
/// <param name="base64">Base64 encoded string with the image file content</param>
|
||||
/// <param name="ct">Optional cancellation token, to cancel the operation</param>
|
||||
/// <returns>
|
||||
/// Whether the sprite was correctly load and the <see cref="Image" /> had its <see cref="Image.overrideSprite" />
|
||||
/// assigned
|
||||
/// </returns>
|
||||
public static async Task<bool> TryOverrideSpriteFromBase64Async(this Image self, string base64, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
{
|
||||
await self.OverrideSpriteFromBase64Async(base64, ct);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a054e74bfe056a14bbbad631f4310243
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,155 @@
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// <copyright file="LODGroupExt.cs" company="VRMADA">
|
||||
// Copyright (c) VRMADA, All rights reserved.
|
||||
// </copyright>
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
using UnityEngine;
|
||||
|
||||
namespace UltimateXR.Extensions.Unity.Render
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="LODGroup" /> extensions.
|
||||
/// Most functionality has been copied from:
|
||||
/// https://github.com/JulienHeijmans/EditorScripts/blob/master/Scripts/Utility/Editor/LODExtendedUtility.cs, which
|
||||
/// in turn copied functionality from:
|
||||
/// https://github.com/Unity-Technologies/AutoLOD/blob/master/Scripts/Extensions/LODGroupExtensions.cs
|
||||
/// </summary>
|
||||
public static class LODGroupExt
|
||||
{
|
||||
#region Public Methods
|
||||
|
||||
/// <summary>
|
||||
/// Gets the LOD level index that should be enabled from a specific view.
|
||||
/// </summary>
|
||||
/// <param name="lodGroup">Component to check</param>
|
||||
/// <param name="camera">Camera to use as point of view</param>
|
||||
/// <returns>LOD level index that should be enabled</returns>
|
||||
public static int GetVisibleLevel(this LODGroup lodGroup, Camera camera)
|
||||
{
|
||||
if (camera == null)
|
||||
{
|
||||
return lodGroup.lodCount - 1;
|
||||
}
|
||||
|
||||
var lods = lodGroup.GetLODs();
|
||||
var relativeHeight = GetRelativeHeight(lodGroup, camera);
|
||||
|
||||
int lodIndex = lodGroup.lodCount - 1;
|
||||
|
||||
for (int i = 0; i < lods.Length; i++)
|
||||
{
|
||||
var lod = lods[i];
|
||||
|
||||
if (relativeHeight >= lod.screenRelativeTransitionHeight)
|
||||
{
|
||||
lodIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return lodIndex;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Manually enables all renderers belonging to a LOD level.
|
||||
/// </summary>
|
||||
/// <param name="lodGroup">Component to process</param>
|
||||
/// <param name="level">Level whose renderers to enable</param>
|
||||
public static void EnableLevelRenderers(this LODGroup lodGroup, int level)
|
||||
{
|
||||
var lods = lodGroup.GetLODs();
|
||||
|
||||
for (int i = 0; i < lods.Length; i++)
|
||||
{
|
||||
foreach (Renderer renderer in lods[i].renderers)
|
||||
{
|
||||
if (renderer != null)
|
||||
{
|
||||
renderer.enabled = i == level;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Manually enables the renderers from all LOD levels.
|
||||
/// </summary>
|
||||
/// <param name="lodGroup">Component to process</param>
|
||||
public static void EnableAllLevelRenderers(this LODGroup lodGroup)
|
||||
{
|
||||
var lods = lodGroup.GetLODs();
|
||||
|
||||
for (int i = 0; i < lods.Length; i++)
|
||||
{
|
||||
foreach (Renderer renderer in lods[i].renderers)
|
||||
{
|
||||
if (renderer != null)
|
||||
{
|
||||
renderer.enabled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Methods
|
||||
|
||||
/// <summary>
|
||||
/// Computes the relative height in the camera view.
|
||||
/// </summary>
|
||||
/// <param name="lodGroup">Component to check</param>
|
||||
/// <param name="camera">Camera to use as point of view</param>
|
||||
/// <returns>Relative height</returns>
|
||||
private static float GetRelativeHeight(LODGroup lodGroup, Camera camera)
|
||||
{
|
||||
var distance = (lodGroup.transform.TransformPoint(lodGroup.localReferencePoint) - camera.transform.position).magnitude;
|
||||
return DistanceToRelativeHeight(camera, distance / QualitySettings.lodBias, GetWorldSpaceSize(lodGroup));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes the relative height in the camera view.
|
||||
/// </summary>
|
||||
/// <param name="camera">Camera to use as point of view</param>
|
||||
/// <param name="distance">Distance to the camera</param>
|
||||
/// <param name="size">Largest axis in world-space</param>
|
||||
/// <returns>Relative height</returns>
|
||||
private static float DistanceToRelativeHeight(Camera camera, float distance, float size)
|
||||
{
|
||||
if (camera.orthographic)
|
||||
{
|
||||
return size * 0.5F / camera.orthographicSize;
|
||||
}
|
||||
|
||||
var halfAngle = Mathf.Tan(Mathf.Deg2Rad * camera.fieldOfView * 0.5F);
|
||||
var relativeHeight = size * 0.5F / (distance * halfAngle);
|
||||
return relativeHeight;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes the largest axis of the <see cref="LODGroup" /> in world-space.
|
||||
/// </summary>
|
||||
/// <param name="lodGroup">Component to process</param>
|
||||
/// <returns>World space size</returns>
|
||||
private static float GetWorldSpaceSize(LODGroup lodGroup)
|
||||
{
|
||||
return GetWorldSpaceScale(lodGroup.transform) * lodGroup.size;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes the largest scale axis.
|
||||
/// </summary>
|
||||
/// <param name="transform">Transform to get the largest scale of</param>
|
||||
/// <returns>Largest scale axis</returns>
|
||||
private static float GetWorldSpaceScale(Transform transform)
|
||||
{
|
||||
var scale = transform.lossyScale;
|
||||
float largestAxis = Mathf.Abs(scale.x);
|
||||
largestAxis = Mathf.Max(largestAxis, Mathf.Abs(scale.y));
|
||||
largestAxis = Mathf.Max(largestAxis, Mathf.Abs(scale.z));
|
||||
return largestAxis;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5258a0f4700615643a99e29dd5f01350
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,30 @@
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// <copyright file="MeshExt.ExtractSubMeshOperation.cs" company="VRMADA">
|
||||
// Copyright (c) VRMADA, All rights reserved.
|
||||
// </copyright>
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
namespace UltimateXR.Extensions.Unity.Render
|
||||
{
|
||||
public static partial class MeshExt
|
||||
{
|
||||
#region Public Types & Data
|
||||
|
||||
/// <summary>
|
||||
/// Enumerates possible mesh extraction algorithms.
|
||||
/// </summary>
|
||||
public enum ExtractSubMeshOperation
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new mesh copying all the mesh that is influenced by the bone or any of its children.
|
||||
/// </summary>
|
||||
BoneAndChildren,
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new mesh copying all the mesh that is not influenced by the reference bone or any of its children.
|
||||
/// </summary>
|
||||
NotFromBoneOrChildren
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7a64f8ffb3bd4e51b0f99a55e6a90f80
|
||||
timeCreated: 1667922130
|
||||
@@ -0,0 +1,399 @@
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// <copyright file="MeshExt.cs" company="VRMADA">
|
||||
// Copyright (c) VRMADA, All rights reserved.
|
||||
// </copyright>
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UltimateXR.Animation.Splines;
|
||||
using UltimateXR.Core;
|
||||
using UltimateXR.Extensions.System.Collections;
|
||||
using UltimateXR.Extensions.Unity.Math;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UltimateXR.Extensions.Unity.Render
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="Mesh" /> extensions.
|
||||
/// </summary>
|
||||
public static partial class MeshExt
|
||||
{
|
||||
#region Public Methods
|
||||
|
||||
/// <summary>
|
||||
/// Creates a quad mesh <see cref="Mesh" />.
|
||||
/// </summary>
|
||||
/// <param name="size">Quad size (total width and height)</param>
|
||||
/// <returns>Quad <see cref="Mesh" /></returns>
|
||||
public static Mesh CreateQuad(float size)
|
||||
{
|
||||
Mesh quadMesh = new Mesh();
|
||||
float halfSize = size * 0.5f;
|
||||
|
||||
quadMesh.vertices = new[] { new Vector3(halfSize, halfSize, 0.0f), new Vector3(halfSize, -halfSize, 0.0f), new Vector3(-halfSize, -halfSize, 0.0f), new Vector3(-halfSize, halfSize, 0.0f) };
|
||||
quadMesh.uv = new[] { new Vector2(1.0f, 1.0f), new Vector2(1.0f, 0.0f), new Vector2(0.0f, 0.0f), new Vector2(0.0f, 1.0f) };
|
||||
quadMesh.triangles = new[] { 0, 2, 1, 0, 3, 2 };
|
||||
|
||||
return quadMesh;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="Mesh" /> tessellating a <see cref="UxrSpline" />
|
||||
/// </summary>
|
||||
/// <param name="spline">Spline to evaluate</param>
|
||||
/// <param name="subdivisions">Number of subdivisions along the spline axis</param>
|
||||
/// <param name="sides">Number of subdivisions in the section</param>
|
||||
/// <param name="radius">Section radius</param>
|
||||
/// <returns><see cref="Mesh" /> with the tessellated <see cref="UxrSpline" /></returns>
|
||||
public static Mesh CreateSpline(UxrSpline spline, int subdivisions, int sides, float radius)
|
||||
{
|
||||
subdivisions = Mathf.Max(subdivisions, 2);
|
||||
sides = Mathf.Max(sides, 3);
|
||||
|
||||
Mesh splineMesh = new Mesh();
|
||||
|
||||
// Create mesh
|
||||
|
||||
Vector3[] vertices = new Vector3[subdivisions * (sides + 1)];
|
||||
Vector3[] normals = new Vector3[vertices.Length];
|
||||
Vector2[] mapping = new Vector2[vertices.Length];
|
||||
int[] indices = new int[(subdivisions - 1) * sides * 2 * 3];
|
||||
|
||||
for (int sub = 0; sub < subdivisions; ++sub)
|
||||
{
|
||||
float arcLength = sub / (subdivisions - 1.0f) * spline.ArcLength;
|
||||
float normalizedArcLength = arcLength / spline.ArcLength;
|
||||
|
||||
spline.EvaluateUsingArcLength(arcLength, out Vector3 splinePosition, out Vector3 splineDirection);
|
||||
|
||||
Vector3 perpendicular = splineDirection.GetPerpendicularVector().normalized * radius;
|
||||
Vector3 vertexStart = splinePosition + perpendicular;
|
||||
|
||||
for (int side = 0; side < sides + 1; ++side)
|
||||
{
|
||||
int vertexIndex = sub * (sides + 1) + side;
|
||||
int faceBase = sub * sides * 2 * 3 + side * 2 * 3;
|
||||
|
||||
float rotation = side / (float)sides;
|
||||
float degrees = 360.0f * rotation;
|
||||
|
||||
vertices[vertexIndex] = vertexStart.GetRotationAround(splinePosition, splineDirection, degrees);
|
||||
mapping[vertexIndex] = new Vector2(rotation, normalizedArcLength);
|
||||
normals[vertexIndex] = (vertices[vertexIndex] - splinePosition).normalized;
|
||||
|
||||
if (side < sides && sub < subdivisions - 1)
|
||||
{
|
||||
indices[faceBase + 0] = vertexIndex;
|
||||
indices[faceBase + 1] = vertexIndex + 1;
|
||||
indices[faceBase + 2] = vertexIndex + sides + 1;
|
||||
indices[faceBase + 3] = vertexIndex + 1;
|
||||
indices[faceBase + 4] = vertexIndex + sides + 2;
|
||||
indices[faceBase + 5] = vertexIndex + sides + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
splineMesh.vertices = vertices;
|
||||
splineMesh.uv = mapping;
|
||||
splineMesh.normals = normals;
|
||||
splineMesh.triangles = indices;
|
||||
|
||||
return splineMesh;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new mesh from a skinned mesh renderer based on a reference bone and an extract operation.
|
||||
/// </summary>
|
||||
/// <param name="skin">Skin to process</param>
|
||||
/// <param name="bone">Reference bone</param>
|
||||
/// <param name="extractOperation">Which part of the skinned mesh to extract</param>
|
||||
/// <param name="weightThreshold">Bone weight threshold above which the vertices will be extracted</param>
|
||||
/// <returns>New mesh</returns>
|
||||
public static Mesh ExtractSubMesh(SkinnedMeshRenderer skin, Transform bone, ExtractSubMeshOperation extractOperation, float weightThreshold = UxrConstants.Geometry.SignificantBoneWeight)
|
||||
{
|
||||
Mesh newMesh = new Mesh();
|
||||
|
||||
// Create dictionary to check which bones belong to the hierarchy
|
||||
|
||||
Dictionary<int, bool> areHierarchyBones = new Dictionary<int, bool>();
|
||||
|
||||
Vector3[] vertices = skin.sharedMesh.vertices;
|
||||
Vector3[] normals = skin.sharedMesh.normals;
|
||||
Vector2[] uv = skin.sharedMesh.uv;
|
||||
BoneWeight[] boneWeights = skin.sharedMesh.boneWeights;
|
||||
Transform[] bones = skin.bones;
|
||||
|
||||
for (int i = 0; i < bones.Length; ++i)
|
||||
{
|
||||
areHierarchyBones.Add(i, bones[i].HasParent(bone));
|
||||
}
|
||||
|
||||
// Create filtered mesh
|
||||
|
||||
List<List<int>> newTriangles = new List<List<int>>();
|
||||
Dictionary<int, int> old2New = new Dictionary<int, int>();
|
||||
List<Vector3> newVertices = new List<Vector3>();
|
||||
List<Vector3> newNormals = new List<Vector3>();
|
||||
List<Vector2> newUV = new List<Vector2>();
|
||||
List<BoneWeight> newBoneWeights = new List<BoneWeight>();
|
||||
|
||||
bool VertexMeetsRequirement(bool isFromHierarchy)
|
||||
{
|
||||
switch (extractOperation)
|
||||
{
|
||||
case ExtractSubMeshOperation.BoneAndChildren: return isFromHierarchy;
|
||||
case ExtractSubMeshOperation.NotFromBoneOrChildren: return !isFromHierarchy;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int submesh = 0; submesh < skin.sharedMesh.subMeshCount; ++submesh)
|
||||
{
|
||||
int[] submeshIndices = skin.sharedMesh.GetTriangles(submesh);
|
||||
List<int> newSubmeshIndices = new List<int>();
|
||||
|
||||
for (int t = 0; t < submeshIndices.Length / 3; t++)
|
||||
{
|
||||
float totalWeight = 0.0f;
|
||||
|
||||
for (int v = 0; v < 3; v++)
|
||||
{
|
||||
BoneWeight boneWeight = boneWeights[submeshIndices[t * 3 + v]];
|
||||
|
||||
if (areHierarchyBones.TryGetValue(boneWeight.boneIndex0, out bool isFromHierarchy) && VertexMeetsRequirement(isFromHierarchy))
|
||||
{
|
||||
totalWeight += boneWeight.weight0;
|
||||
}
|
||||
|
||||
if (areHierarchyBones.TryGetValue(boneWeight.boneIndex1, out isFromHierarchy) && VertexMeetsRequirement(isFromHierarchy))
|
||||
{
|
||||
totalWeight += boneWeight.weight1;
|
||||
}
|
||||
|
||||
if (areHierarchyBones.TryGetValue(boneWeight.boneIndex2, out isFromHierarchy) && VertexMeetsRequirement(isFromHierarchy))
|
||||
{
|
||||
totalWeight += boneWeight.weight2;
|
||||
}
|
||||
|
||||
if (areHierarchyBones.TryGetValue(boneWeight.boneIndex3, out isFromHierarchy) && VertexMeetsRequirement(isFromHierarchy))
|
||||
{
|
||||
totalWeight += boneWeight.weight3;
|
||||
}
|
||||
}
|
||||
|
||||
if (totalWeight > weightThreshold)
|
||||
{
|
||||
for (int v = 0; v < 3; v++)
|
||||
{
|
||||
int oldIndex = submeshIndices[t * 3 + v];
|
||||
|
||||
if (!old2New.ContainsKey(oldIndex))
|
||||
{
|
||||
old2New.Add(oldIndex, old2New.Count);
|
||||
|
||||
newVertices.Add(vertices[oldIndex]);
|
||||
newNormals.Add(normals[oldIndex]);
|
||||
newUV.Add(uv[oldIndex]);
|
||||
newBoneWeights.Add(boneWeights[oldIndex]);
|
||||
}
|
||||
|
||||
newSubmeshIndices.Add(old2New[oldIndex]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
newTriangles.Add(newSubmeshIndices);
|
||||
}
|
||||
|
||||
// Create new mesh
|
||||
|
||||
newMesh.vertices = newVertices.ToArray();
|
||||
newMesh.normals = newNormals.ToArray();
|
||||
newMesh.uv = newUV.ToArray();
|
||||
newMesh.boneWeights = newBoneWeights.ToArray();
|
||||
|
||||
// Create and assign new triangle list
|
||||
|
||||
newMesh.subMeshCount = newTriangles.Count;
|
||||
|
||||
for (int submesh = 0; submesh < newTriangles.Count; ++submesh)
|
||||
{
|
||||
newMesh.SetTriangles(newTriangles[submesh].ToArray(), submesh);
|
||||
}
|
||||
|
||||
return newMesh;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes the number of vertices that a bone influences in a skinned mesh.
|
||||
/// </summary>
|
||||
/// <param name="skin">Skinned mesh</param>
|
||||
/// <param name="bone">Bone to check</param>
|
||||
/// <param name="weightThreshold">Weight above which will be considered significant influence</param>
|
||||
/// <returns>
|
||||
/// Number of vertices influenced by <paramref name="bone" /> with a weight above
|
||||
/// <paramref name="weightThreshold" />.
|
||||
/// </returns>
|
||||
public static int GetBoneInfluenceVertexCount(SkinnedMeshRenderer skin, Transform bone, float weightThreshold = UxrConstants.Geometry.SignificantBoneWeight)
|
||||
{
|
||||
Transform[] skinBones = skin.bones;
|
||||
int boneIndex = skinBones.IndexOf(bone);
|
||||
|
||||
if (boneIndex == -1)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
BoneWeight[] boneWeights = skin.sharedMesh.boneWeights;
|
||||
|
||||
return boneWeights.Count(w => HasBoneInfluence(w, boneIndex, weightThreshold));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes the number of vertices that a bone influences in a skinned mesh.
|
||||
/// </summary>
|
||||
/// <param name="skin">Skinned mesh</param>
|
||||
/// <param name="bone">Bone to check</param>
|
||||
/// <param name="weightThreshold">Weight above which to consider significant influence</param>
|
||||
/// <returns>
|
||||
/// Number of vertices influenced by <paramref name="bone" /> with a weight above
|
||||
/// <paramref name="weightThreshold" />
|
||||
/// </returns>
|
||||
public static bool HasBoneInfluence(SkinnedMeshRenderer skin, Transform bone, float weightThreshold = UxrConstants.Geometry.SignificantBoneWeight)
|
||||
{
|
||||
Transform[] skinBones = skin.bones;
|
||||
int boneIndex = skinBones.IndexOf(bone);
|
||||
|
||||
if (boneIndex == -1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
BoneWeight[] boneWeights = skin.sharedMesh.boneWeights;
|
||||
|
||||
return boneWeights.Any(w => HasBoneInfluence(w, boneIndex, weightThreshold));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether a given bone index has influence on a skinned mesh vertex.
|
||||
/// </summary>
|
||||
/// <param name="boneWeight">Vertex's bone weight information</param>
|
||||
/// <param name="boneIndex">Bone index</param>
|
||||
/// <param name="weightThreshold">Weight above which will be considered significant influence</param>
|
||||
/// <returns>Whether the bone influences the vertex in a significant amount</returns>
|
||||
public static bool HasBoneInfluence(in BoneWeight boneWeight, int boneIndex, float weightThreshold = UxrConstants.Geometry.SignificantBoneWeight)
|
||||
{
|
||||
if (boneWeight.boneIndex0 == boneIndex && boneWeight.weight0 > weightThreshold)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (boneWeight.boneIndex1 == boneIndex && boneWeight.weight1 > weightThreshold)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (boneWeight.boneIndex2 == boneIndex && boneWeight.weight2 > weightThreshold)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (boneWeight.boneIndex3 == boneIndex && boneWeight.weight3 > weightThreshold)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes the bounding box that contains all the vertices that a bone has influence on in a skinned mesh. The
|
||||
/// bounding box is computed in local bone space.
|
||||
/// </summary>
|
||||
/// <param name="skin">Skinned mesh</param>
|
||||
/// <param name="bone">Bone to check</param>
|
||||
/// <param name="weightThreshold">Weight above which to consider significant influence</param>
|
||||
/// <returns>
|
||||
/// Bounding box in local <paramref name="bone" /> coordinates.
|
||||
/// </returns>
|
||||
public static Bounds GetBoneInfluenceBounds(SkinnedMeshRenderer skin, Transform bone, float weightThreshold = UxrConstants.Geometry.SignificantBoneWeight)
|
||||
{
|
||||
Transform[] skinBones = skin.bones;
|
||||
int boneIndex = skinBones.IndexOf(bone);
|
||||
|
||||
if (boneIndex == -1)
|
||||
{
|
||||
return new Bounds();
|
||||
}
|
||||
|
||||
Vector3[] vertices = skin.sharedMesh.vertices;
|
||||
BoneWeight[] boneWeights = skin.sharedMesh.boneWeights;
|
||||
Transform[] bones = skin.bones;
|
||||
Matrix4x4[] boneBindPoses = skin.sharedMesh.bindposes;
|
||||
Vector3 min = Vector3.zero;
|
||||
Vector3 max = Vector3.zero;
|
||||
bool initialized = false;
|
||||
|
||||
for (int i = 0; i < boneWeights.Length; ++i)
|
||||
{
|
||||
if (HasBoneInfluence(boneWeights[i], boneIndex, weightThreshold))
|
||||
{
|
||||
Vector3 localVertex = bones[boneIndex].InverseTransformPoint(GetSkinnedWorldVertex(skin, boneWeights[i], vertices[i], bones, boneBindPoses));
|
||||
|
||||
if (!initialized)
|
||||
{
|
||||
initialized = true;
|
||||
min = localVertex;
|
||||
max = localVertex;
|
||||
}
|
||||
else
|
||||
{
|
||||
min = Vector3Ext.Min(localVertex, min);
|
||||
max = Vector3Ext.Max(localVertex, max);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new Bounds((min + max) * 0.5f, max - min);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a skinned vertex in world coordinates.
|
||||
/// </summary>
|
||||
/// <param name="skin">Skin</param>
|
||||
/// <param name="boneWeight">Vertex bone weights info</param>
|
||||
/// <param name="vertex">Vertex in local skin coordinates when the skin is in the bind pose</param>
|
||||
/// <param name="bones">Bone list</param>
|
||||
/// <param name="boneBindPoses">Bone bind poses</param>
|
||||
/// <returns>Vertex in world coordinates</returns>
|
||||
public static Vector3 GetSkinnedWorldVertex(SkinnedMeshRenderer skin, BoneWeight boneWeight, Vector3 vertex, Transform[] bones, Matrix4x4[] boneBindPoses)
|
||||
{
|
||||
Vector3 result = Vector3.zero;
|
||||
|
||||
if (boneWeight.weight0 > UxrConstants.Geometry.SmallestBoneWeight)
|
||||
{
|
||||
result += bones[boneWeight.boneIndex0].localToWorldMatrix.MultiplyPoint(boneBindPoses[boneWeight.boneIndex0].MultiplyPoint(vertex)) * boneWeight.weight0;
|
||||
}
|
||||
|
||||
if (boneWeight.weight1 > UxrConstants.Geometry.SmallestBoneWeight)
|
||||
{
|
||||
result += bones[boneWeight.boneIndex1].localToWorldMatrix.MultiplyPoint(boneBindPoses[boneWeight.boneIndex1].MultiplyPoint(vertex)) * boneWeight.weight1;
|
||||
}
|
||||
|
||||
if (boneWeight.weight2 > UxrConstants.Geometry.SmallestBoneWeight)
|
||||
{
|
||||
result += bones[boneWeight.boneIndex2].localToWorldMatrix.MultiplyPoint(boneBindPoses[boneWeight.boneIndex2].MultiplyPoint(vertex)) * boneWeight.weight2;
|
||||
}
|
||||
|
||||
if (boneWeight.weight3 > UxrConstants.Geometry.SmallestBoneWeight)
|
||||
{
|
||||
result += bones[boneWeight.boneIndex3].localToWorldMatrix.MultiplyPoint(boneBindPoses[boneWeight.boneIndex3].MultiplyPoint(vertex)) * boneWeight.weight3;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e562213b88d80624a867d53e83926632
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,50 @@
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// <copyright file="RendererExt.cs" company="VRMADA">
|
||||
// Copyright (c) VRMADA, All rights reserved.
|
||||
// </copyright>
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
using System.Collections.Generic;
|
||||
using UltimateXR.Extensions.System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UltimateXR.Extensions.Unity.Render
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="Renderer" /> extensions.
|
||||
/// </summary>
|
||||
public static class RendererExt
|
||||
{
|
||||
#region Public Methods
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the <see cref="Bounds" /> encapsulating a set of renderers.
|
||||
/// </summary>
|
||||
/// <param name="renderers">Renderers to compute the bounds for</param>
|
||||
/// <returns><see cref="Bounds" /> encapsulating all renderers</returns>
|
||||
public static Bounds CalculateBounds(this IEnumerable<Renderer> renderers)
|
||||
{
|
||||
renderers.ThrowIfNull(nameof(renderers));
|
||||
|
||||
Bounds bounds = default;
|
||||
bool isFirst = true;
|
||||
|
||||
foreach (Renderer r in renderers)
|
||||
{
|
||||
Bounds b = r.bounds;
|
||||
if (isFirst)
|
||||
{
|
||||
bounds = r.bounds;
|
||||
isFirst = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
bounds.Encapsulate(b);
|
||||
}
|
||||
}
|
||||
|
||||
return bounds;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5aa895f60ddf8374f801421c8b5b8c45
|
||||
timeCreated: 1604664051
|
||||
@@ -0,0 +1,26 @@
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// <copyright file="ShaderExt.cs" company="VRMADA">
|
||||
// Copyright (c) VRMADA, All rights reserved.
|
||||
// </copyright>
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
using UnityEngine;
|
||||
|
||||
namespace UltimateXR.Extensions.Unity.Render
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="Shader" /> extensions.
|
||||
/// </summary>
|
||||
public static class ShaderExt
|
||||
{
|
||||
#region Public Types & Data
|
||||
|
||||
public const string ShaderBase = "UltimateXR/";
|
||||
|
||||
public static Shader UnlitAdditiveColor => Shader.Find($"{ShaderBase}Basic Unlit/Unlit Additive Color");
|
||||
public static Shader UnlitTransparentColor => Shader.Find($"{ShaderBase}Basic Unlit/Unlit Transparent Color");
|
||||
public static Shader UnlitTransparentColorNoDepthTest => Shader.Find($"{ShaderBase}Basic Unlit/Unlit Transparent Color (No Depth Test)");
|
||||
public static Shader UnlitOverlayFade => Shader.Find($"{ShaderBase}Basic Unlit/Overlay Fade");
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 109b8a7bffaf6ca4195de7a878d7ecef
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,89 @@
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// <copyright file="SpriteExt.cs" company="VRMADA">
|
||||
// Copyright (c) VRMADA, All rights reserved.
|
||||
// </copyright>
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using UltimateXR.Extensions.System.IO;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace UltimateXR.Extensions.Unity.Render
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="Sprite" /> extensions.
|
||||
/// </summary>
|
||||
public static class SpriteExt
|
||||
{
|
||||
#region Public Methods
|
||||
|
||||
/// <summary>
|
||||
/// Creates a sprite, for a given <see cref="Image" /> using a <see cref="Texture2D" />.
|
||||
/// </summary>
|
||||
/// <param name="targetImage">Image component the sprite will be used for</param>
|
||||
/// <param name="texture2D">Texture</param>
|
||||
/// <returns>Loaded sprite</returns>
|
||||
public static Sprite FromTexture(Image targetImage, Texture2D texture2D)
|
||||
{
|
||||
RectTransform t = targetImage.rectTransform;
|
||||
Vector2 size = t.sizeDelta;
|
||||
Rect rect = new Rect(0.0f, 0.0f, size.x, size.y);
|
||||
|
||||
return Sprite.Create(texture2D, rect, t.pivot);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads asynchronously a sprite from a given file <paramref name="uri" />. See <see cref="FileExt.Read" /> for
|
||||
/// information on the file location.
|
||||
/// </summary>
|
||||
/// <param name="targetImage">Image component the sprite will be used for</param>
|
||||
/// <param name="uri">File location. <see cref="FileExt.Read" /> for more information</param>
|
||||
/// <param name="ct">Optional cancellation token, to cancel the operation.</param>
|
||||
/// <returns>An awaitable <seealso cref="Task" /> that returns the loaded sprite</returns>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="uri" /> is null or empty</exception>
|
||||
/// <exception cref="OperationCanceledException">Task canceled using <paramref name="ct" /></exception>
|
||||
/// <exception cref="FileNotFoundException">The file specified in <paramref name="uri" /> was not found.</exception>
|
||||
/// <exception cref="NotSupportedException"><paramref name="uri" /> is in an invalid format.</exception>
|
||||
/// <exception cref="IOException">An I/O error occurred while opening the file.</exception>
|
||||
/// <exception cref="InvalidOperationException">The stream is currently in use by a previous read operation.</exception>
|
||||
public static async Task<Sprite> ReadSpriteFileAsync(Image targetImage, string uri, CancellationToken ct = default)
|
||||
{
|
||||
Texture2D texture2D = await Texture2DExt.FromFile(uri, ct);
|
||||
|
||||
RectTransform t = targetImage.rectTransform;
|
||||
Vector2 size = t.sizeDelta;
|
||||
Rect rect = new Rect(0.0f, 0.0f, size.x, size.y);
|
||||
|
||||
return Sprite.Create(texture2D, rect, t.pivot);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads asynchronously a sprite encoded in a base64 <see cref="string" />.
|
||||
/// </summary>
|
||||
/// <param name="targetImage">Image component the sprite will be used for</param>
|
||||
/// <param name="base64">String encoding the file in base64</param>
|
||||
/// <param name="ct">Optional cancellation token, to cancel the operation</param>
|
||||
/// <returns>An awaitable <seealso cref="Task" /> that returns the loaded sprite</returns>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="base64" /> is null or empty</exception>
|
||||
/// <exception cref="OperationCanceledException">Task canceled using <paramref name="ct" /></exception>
|
||||
/// <exception cref="FormatException">
|
||||
/// The length of <paramref name="base64" />, ignoring white-space characters, is not
|
||||
/// zero or a multiple of 4.
|
||||
/// </exception>
|
||||
public static async Task<Sprite> ReadSpriteBase64Async(Image targetImage, string base64, CancellationToken ct = default)
|
||||
{
|
||||
Texture2D texture2D = await Texture2DExt.FromBase64(base64, ct);
|
||||
|
||||
RectTransform t = targetImage.rectTransform;
|
||||
Vector2 size = t.sizeDelta;
|
||||
Rect rect = new Rect(0.0f, 0.0f, size.x, size.y);
|
||||
|
||||
return Sprite.Create(texture2D, rect, t.pivot);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0d67dd4d3dca574479f1d39dcd6f6049
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,102 @@
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// <copyright file="Texture2DExt.cs" company="VRMADA">
|
||||
// Copyright (c) VRMADA, All rights reserved.
|
||||
// </copyright>
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using UltimateXR.Extensions.System;
|
||||
using UltimateXR.Extensions.System.IO;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UltimateXR.Extensions.Unity.Render
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="Texture2D" /> extensions.
|
||||
/// </summary>
|
||||
public static class Texture2DExt
|
||||
{
|
||||
#region Public Methods
|
||||
|
||||
/// <summary>
|
||||
/// Creates a texture with a flat color.
|
||||
/// </summary>
|
||||
/// <param name="width">Width in pixels</param>
|
||||
/// <param name="height">Height in pixels</param>
|
||||
/// <param name="color">Color to fill the texture with</param>
|
||||
/// <returns>The created <see cref="Texture2D" /> object</returns>
|
||||
public static Texture2D Create(int width, int height, Color32 color)
|
||||
{
|
||||
Texture2D tex = new Texture2D(width, height);
|
||||
|
||||
Color32[] pixels = new Color32[width * height];
|
||||
for (int i = 0; i < pixels.Length; ++i)
|
||||
{
|
||||
pixels[i] = color;
|
||||
}
|
||||
|
||||
tex.SetPixels32(pixels);
|
||||
tex.Apply();
|
||||
|
||||
return tex;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads asynchronously a texture from a given file <paramref name="uri" />. See <see cref="FileExt.Read" /> for
|
||||
/// information on the file location.
|
||||
/// </summary>
|
||||
/// <param name="uri">Location of the texture file. See <see cref="FileExt.Read" /></param>
|
||||
/// <param name="ct">Optional cancellation token, to cancel the operation.</param>
|
||||
/// <returns>An awaitable <seealso cref="Task" /> that returns the loaded texture</returns>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="uri" /> is null or empty</exception>
|
||||
/// <exception cref="OperationCanceledException">Task canceled using <paramref name="ct" /></exception>
|
||||
/// <exception cref="FileNotFoundException">The file specified in <paramref name="uri" /> was not found.</exception>
|
||||
/// <exception cref="NotSupportedException"><paramref name="uri" /> is in an invalid format.</exception>
|
||||
/// <exception cref="IOException">An I/O error occurred while opening the file.</exception>
|
||||
/// <exception cref="InvalidOperationException">The stream is currently in use by a previous read operation. </exception>
|
||||
public static async Task<Texture2D> FromFile(string uri, CancellationToken ct = default)
|
||||
{
|
||||
byte[] bytes = await FileExt.Read(uri, ct);
|
||||
return FromBytes(bytes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads asynchronously a texture from a file encoded in a base64 <seealso cref="string" />.
|
||||
/// </summary>
|
||||
/// <param name="base64">The base 64 image string</param>
|
||||
/// <param name="ct">Optional cancellation token, to cancel the operation</param>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="base64" /> is null or empty</exception>
|
||||
/// <exception cref="OperationCanceledException">Task canceled using <paramref name="ct" /></exception>
|
||||
/// <exception cref="FormatException">
|
||||
/// The length of <paramref name="base64" />, ignoring white-space characters, is not
|
||||
/// zero or a multiple of 4.
|
||||
/// </exception>
|
||||
/// <returns>An awaitable <see cref="Task" /> that returns the loaded texture, or null if it could not be loaded</returns>
|
||||
public static async Task<Texture2D> FromBase64(string base64, CancellationToken ct = default)
|
||||
{
|
||||
base64.ThrowIfNullOrWhitespace(nameof(base64));
|
||||
|
||||
// Screenshot is from a file embedded in a string in base64 format
|
||||
byte[] bytes = await Task.Run(() => Convert.FromBase64String(base64), ct);
|
||||
return FromBytes(bytes);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Loads a texture from a file loaded in a byte array.
|
||||
/// </summary>
|
||||
/// <param name="bytes">Image file byte array</param>
|
||||
/// <returns>The loaded <see cref="Texture2D" /></returns>
|
||||
public static Texture2D FromBytes(byte[] bytes)
|
||||
{
|
||||
bytes.ThrowIfNull(nameof(bytes));
|
||||
var tex = new Texture2D(2, 2);
|
||||
tex.LoadImage(bytes);
|
||||
return tex;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c80a81688bca2614891cdf6dc8fe4136
|
||||
timeCreated: 1620807232
|
||||
Reference in New Issue
Block a user