Added player character and step in/out of dialogue in scene 101
This commit is contained in:
@@ -0,0 +1,108 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEngine.ProBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains information about the results of a ProBuilder action (success, failure, and other notifications)
|
||||
/// </summary>
|
||||
public sealed class ActionResult
|
||||
{
|
||||
/// <summary>
|
||||
/// Describes the results of an action.
|
||||
/// </summary>
|
||||
public enum Status
|
||||
{
|
||||
/// <summary>
|
||||
/// The action was a success.
|
||||
/// </summary>
|
||||
Success,
|
||||
/// <summary>
|
||||
/// A critical failure prevented the action from running.
|
||||
/// </summary>
|
||||
Failure,
|
||||
/// <summary>
|
||||
/// The action was not completed due to invalid parameters.
|
||||
/// </summary>
|
||||
Canceled,
|
||||
/// <summary>
|
||||
/// The action did not run because there was no meaningful action to be made.
|
||||
/// </summary>
|
||||
NoChange
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the status of the action following the operation.
|
||||
/// </summary>
|
||||
public Status status { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the short description of the results (a few words long).
|
||||
/// </summary>
|
||||
public string notification { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new ActionResult with a specific status value.
|
||||
/// </summary>
|
||||
/// <param name="status">Status value to use for the action.</param>
|
||||
/// <param name="notification">A short summary of the action performed.</param>
|
||||
public ActionResult(ActionResult.Status status, string notification)
|
||||
{
|
||||
this.status = status;
|
||||
this.notification = notification;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts the specified result to a boolean value, where true indicates success.
|
||||
/// </summary>
|
||||
/// <param name="res">ActionResult to convert.</param>
|
||||
/// <returns>True if action was <see cref="Status.Success"/>; false otherwise.</returns>
|
||||
public static implicit operator bool(ActionResult res)
|
||||
{
|
||||
return res != null && res.status == Status.Success;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the current ActionResult is set to <see cref="Status.Success"/> or not.
|
||||
/// </summary>
|
||||
/// <returns>True if this ActionResult has a status of <see cref="Status.Success"/>; false otherwise.</returns>
|
||||
public bool ToBool()
|
||||
{
|
||||
return status == Status.Success;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the value of the specified `success` value.
|
||||
/// </summary>
|
||||
/// <param name="success">Boolean value to check.</param>
|
||||
/// <returns>Generic boolean value corresponding to the success parameter.</returns>
|
||||
public static bool FromBool(bool success)
|
||||
{
|
||||
return success ? ActionResult.Success : new ActionResult(ActionResult.Status.Failure, "Failure");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a generic "Success" action result with no notification text.
|
||||
/// </summary>
|
||||
public static ActionResult Success
|
||||
{
|
||||
get { return new ActionResult(ActionResult.Status.Success, ""); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a generic "No Selection" action result with "Nothing Selected" notification.
|
||||
/// </summary>
|
||||
public static ActionResult NoSelection
|
||||
{
|
||||
get { return new ActionResult(ActionResult.Status.Canceled, "Nothing Selected"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a generic "Canceled" action result with "User Canceled" notification.
|
||||
/// </summary>
|
||||
public static ActionResult UserCanceled
|
||||
{
|
||||
get { return new ActionResult(ActionResult.Status.Canceled, "User Canceled"); }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0c96b34c870fc49b6b189786a55e14ad
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,318 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace UnityEngine.ProBuilder
|
||||
{
|
||||
static class ArrayUtility
|
||||
{
|
||||
public static T[] ValuesWithIndexes<T>(this T[] arr, int[] indexes)
|
||||
{
|
||||
T[] vals = new T[indexes.Length];
|
||||
for (int i = 0; i < indexes.Length; i++)
|
||||
vals[i] = arr[indexes[i]];
|
||||
return vals;
|
||||
}
|
||||
|
||||
public static List<T> ValuesWithIndexes<T>(this List<T> arr, IList<int> indexes)
|
||||
{
|
||||
List<T> vals = new List<T>(indexes.Count);
|
||||
|
||||
foreach (var i in indexes)
|
||||
vals.Add(arr[i]);
|
||||
|
||||
return vals;
|
||||
}
|
||||
|
||||
public static IEnumerable<int> AllIndexesOf<T>(this IList<T> list, Func<T, bool> lambda)
|
||||
{
|
||||
var indexes = new List<int>();
|
||||
for (int i = 0, c = list.Count; i < c; i++)
|
||||
if (lambda(list[i]))
|
||||
indexes.Add(i);
|
||||
return indexes;
|
||||
}
|
||||
|
||||
public static T[] Add<T>(this T[] arr, T val)
|
||||
{
|
||||
T[] v = new T[arr.Length + 1];
|
||||
System.Array.ConstrainedCopy(arr, 0, v, 0, arr.Length);
|
||||
v[arr.Length] = val;
|
||||
return v;
|
||||
}
|
||||
|
||||
public static T[] AddRange<T>(this T[] arr, T[] val)
|
||||
{
|
||||
T[] ret = new T[arr.Length + val.Length];
|
||||
System.Array.ConstrainedCopy(arr, 0, ret, 0, arr.Length);
|
||||
System.Array.ConstrainedCopy(val, 0, ret, arr.Length, val.Length);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove @val from @arr.
|
||||
*/
|
||||
public static T[] Remove<T>(this T[] arr, T val)
|
||||
{
|
||||
List<T> list = new List<T>(arr);
|
||||
list.Remove(val);
|
||||
return list.ToArray();
|
||||
}
|
||||
|
||||
public static T[] Remove<T>(this T[] arr, IEnumerable<T> val)
|
||||
{
|
||||
return arr.Except(val).ToArray();
|
||||
}
|
||||
|
||||
public static T[] RemoveAt<T>(this T[] arr, int index)
|
||||
{
|
||||
T[] newArray = new T[arr.Length - 1];
|
||||
int n = 0;
|
||||
for (int i = 0; i < arr.Length; i++)
|
||||
{
|
||||
if (i != index)
|
||||
{
|
||||
newArray[n] = arr[i];
|
||||
n++;
|
||||
}
|
||||
}
|
||||
return newArray;
|
||||
}
|
||||
|
||||
public static T[] RemoveAt<T>(this IList<T> list, IEnumerable<int> indexes)
|
||||
{
|
||||
List<int> sorted = new List<int>(indexes);
|
||||
sorted.Sort();
|
||||
return SortedRemoveAt(list, sorted);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove elements at indexes from an array, but accepts a pre-sorted indexes list.
|
||||
*/
|
||||
public static T[] SortedRemoveAt<T>(this IList<T> list, IList<int> sorted)
|
||||
{
|
||||
int indexeSortedCount = sorted.Count;
|
||||
int len = list.Count;
|
||||
|
||||
T[] newArray = new T[len - indexeSortedCount];
|
||||
int n = 0;
|
||||
|
||||
for (int i = 0; i < len; i++)
|
||||
{
|
||||
if (n < indexeSortedCount && sorted[n] == i)
|
||||
{
|
||||
// handle duplicate indexes
|
||||
while (n < indexeSortedCount && sorted[n] == i)
|
||||
n++;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
newArray[i - n] = list[i];
|
||||
}
|
||||
|
||||
return newArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds a start and end index for a binary search.
|
||||
*/
|
||||
struct SearchRange
|
||||
{
|
||||
public int begin, end;
|
||||
|
||||
public SearchRange(int begin, int end)
|
||||
{
|
||||
this.begin = begin;
|
||||
this.end = end;
|
||||
}
|
||||
|
||||
public bool Valid() { return end - begin > 1; }
|
||||
public int Center() { return begin + (end - begin) / 2; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "{" + begin + ", " + end + "} : " + Center();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a sorted list and value, returns the index of the greatest value in sorted_list that is
|
||||
* less than value. Ex: List( { 0, 1, 4, 6, 7 } ) Value(5) returns 2, which is the index of value 4.
|
||||
* If value is less than sorted[0], -1 is returned. If value is greater than sorted[end], sorted.Count-1
|
||||
* is returned. If an exact match is found, the index prior to that match is returned.
|
||||
*/
|
||||
public static int NearestIndexPriorToValue<T>(IList<T> sorted_list, T value) where T : System.IComparable<T>
|
||||
{
|
||||
int count = sorted_list.Count;
|
||||
if (count < 1) return -1;
|
||||
|
||||
SearchRange range = new SearchRange(0, count - 1);
|
||||
|
||||
if (value.CompareTo(sorted_list[0]) < 0)
|
||||
return -1;
|
||||
|
||||
if (value.CompareTo(sorted_list[count - 1]) > 0)
|
||||
return count - 1;
|
||||
|
||||
while (range.Valid())
|
||||
{
|
||||
if (sorted_list[range.Center()].CompareTo(value) > 0)
|
||||
{
|
||||
// sb.AppendLine(sorted_list[range.Center()] + " > " + value + " [" + range.begin + ", " + range.end +"] -> [" + range.begin + ", " + range.Center() + "]");
|
||||
range.end = range.Center();
|
||||
}
|
||||
else
|
||||
{
|
||||
// sb.AppendLine(sorted_list[range.Center()] + " < " + value + " [" + range.begin + ", " + range.end +"] -> [" + range.Center() + ", " + range.end + "]");
|
||||
range.begin = range.Center();
|
||||
|
||||
if (sorted_list[range.begin + 1].CompareTo(value) >= 0)
|
||||
{
|
||||
return range.begin;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static List<T> Fill<T>(System.Func<int, T> ctor, int length)
|
||||
{
|
||||
List<T> list = new List<T>(length);
|
||||
for (int i = 0; i < length; i++)
|
||||
list.Add(ctor(i));
|
||||
return list;
|
||||
}
|
||||
|
||||
public static T[] Fill<T>(T val, int length)
|
||||
{
|
||||
T[] arr = new T[length];
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
arr[i] = val;
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
/**
|
||||
* True if any value is present in both arrays.
|
||||
*/
|
||||
public static bool ContainsMatch<T>(this T[] a, T[] b)
|
||||
{
|
||||
int ind = -1;
|
||||
for (int i = 0; i < a.Length; i++)
|
||||
{
|
||||
ind = System.Array.IndexOf(b, a[i]);
|
||||
if (ind > -1) return true;//ind;
|
||||
}
|
||||
return false;// ind;
|
||||
}
|
||||
|
||||
/**
|
||||
* True if any value is present in both arrays, setting index_a and index_b to the index in the array of each match.
|
||||
*/
|
||||
public static bool ContainsMatch<T>(this T[] a, T[] b, out int index_a, out int index_b)
|
||||
{
|
||||
index_b = -1;
|
||||
for (index_a = 0; index_a < a.Length; index_a++)
|
||||
{
|
||||
index_b = Array.IndexOf(b, a[index_a]);
|
||||
if (index_b > -1)
|
||||
return true; //ind;
|
||||
}
|
||||
|
||||
return false; // ind;
|
||||
}
|
||||
|
||||
/**
|
||||
* Concatenate two arrays.
|
||||
*/
|
||||
public static T[] Concat<T>(this T[] x, T[] y)
|
||||
{
|
||||
if (x == null) throw new ArgumentNullException("x");
|
||||
if (y == null) throw new ArgumentNullException("y");
|
||||
int oldLen = x.Length;
|
||||
Array.Resize<T>(ref x, x.Length + y.Length);
|
||||
Array.Copy(y, 0, x, oldLen, y.Length);
|
||||
return x;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the index of the array that contains this value. -1 if not found.
|
||||
*/
|
||||
public static int IndexOf<T>(this List<List<T>> InList, T InValue)
|
||||
{
|
||||
for (int i = 0; i < InList.Count; i++)
|
||||
{
|
||||
for (int x = 0; x < InList[i].Count; x++)
|
||||
if (InList[i][x].Equals(InValue))
|
||||
return i;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a new array with count using the constructor. ctor recieves the index and returns a new instance of T.
|
||||
*/
|
||||
public static T[] Fill<T>(int count, System.Func<int, T> ctor)
|
||||
{
|
||||
T[] arr = new T[count];
|
||||
for (int i = 0; i < count; i++)
|
||||
arr[i] = ctor(i);
|
||||
return arr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a value to a key in dictionary, adding a new entry if necessray.
|
||||
*/
|
||||
public static void AddOrAppend<T, K>(this Dictionary<T, List<K>> dictionary, T key, K value)
|
||||
{
|
||||
List<K> list;
|
||||
|
||||
if (dictionary.TryGetValue(key, out list))
|
||||
list.Add(value);
|
||||
else
|
||||
dictionary.Add(key, new List<K>() { value });
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a value to a key in dictionary, adding a new entry if necessray.
|
||||
*/
|
||||
public static void AddOrAppendRange<T, K>(this Dictionary<T, List<K>> dictionary, T key, List<K> value)
|
||||
{
|
||||
List<K> list;
|
||||
|
||||
if (dictionary.TryGetValue(key, out list))
|
||||
list.AddRange(value);
|
||||
else
|
||||
dictionary.Add(key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* http://stackoverflow.com/questions/1300088/distinct-with-lambda
|
||||
*/
|
||||
public static IEnumerable<TSource> DistinctBy<TSource, TKey>(
|
||||
this IEnumerable<TSource> source,
|
||||
Func<TSource, TKey> keySelector)
|
||||
{
|
||||
HashSet<TKey> knownKeys = new HashSet<TKey>();
|
||||
return source.Where(x => knownKeys.Add(keySelector(x)));
|
||||
}
|
||||
|
||||
public static string ToString<TKey, TValue>(this Dictionary<TKey, TValue> dict)
|
||||
{
|
||||
System.Text.StringBuilder sb = new System.Text.StringBuilder();
|
||||
|
||||
foreach (KeyValuePair<TKey, TValue> kvp in dict)
|
||||
sb.AppendLine(string.Format("Key: {0} Value: {1}", kvp.Key, kvp.Value));
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public static string ToString<T>(this IEnumerable<T> arr, string separator = ", ")
|
||||
{
|
||||
return string.Join(separator, arr.Select(x => x.ToString()).ToArray());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 11887933ce4b13e4f9f5ec4163298397
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Unity.ProBuilder.MeshOperations")]
|
||||
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Unity.ProBuilder.Editor")]
|
||||
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Unity.ProBuilder.Tests")]
|
||||
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Unity.ProBuilder.Tests.Framework")]
|
||||
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Unity.ProBuilder.Tests.Slow")]
|
||||
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Unity.ProBuilder.Editor.Tests")]
|
||||
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Unity.ProBuilder.Debug.Editor")]
|
||||
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Unity.ProBuilder.AddOns.Editor")]
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 08b58b81e1014e9a804abb4cd35c2eb3
|
||||
timeCreated: 1510169816
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,308 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.Serialization;
|
||||
|
||||
namespace UnityEngine.ProBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// A collection of settings describing how to project UV coordinates for a <see cref="Face"/>.
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public struct AutoUnwrapSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates and returns the default set of AutoUnwrapSettings.
|
||||
/// </summary>
|
||||
public static AutoUnwrapSettings defaultAutoUnwrapSettings
|
||||
{
|
||||
get
|
||||
{
|
||||
var settings = new AutoUnwrapSettings();
|
||||
settings.Reset();
|
||||
return settings;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines the point from which ProBuilder performs UV transform operations.
|
||||
///
|
||||
/// After the initial projection into 2D space, ProBuilder translates UVs to the anchor position.
|
||||
/// Next, ProBuilder applies the offset and rotation, followed by the other settings.
|
||||
/// </summary>
|
||||
public enum Anchor
|
||||
{
|
||||
/// <summary>
|
||||
/// The top left bounding point of the projected UVs is aligned with UV coordinate {0, 1}.
|
||||
/// </summary>
|
||||
UpperLeft,
|
||||
/// <summary>
|
||||
/// The center top bounding point of the projected UVs is aligned with UV coordinate {.5, 1}.
|
||||
/// </summary>
|
||||
UpperCenter,
|
||||
/// <summary>
|
||||
/// The right top bounding point of the projected UVs is aligned with UV coordinate {1, 1}.
|
||||
/// </summary>
|
||||
UpperRight,
|
||||
/// <summary>
|
||||
/// The middle left bounding point of the projected UVs is aligned with UV coordinate {0, .5}.
|
||||
/// </summary>
|
||||
MiddleLeft,
|
||||
/// <summary>
|
||||
/// The center bounding point of the projected UVs is aligned with UV coordinate {.5, .5}.
|
||||
/// </summary>
|
||||
MiddleCenter,
|
||||
/// <summary>
|
||||
/// The middle right bounding point of the projected UVs is aligned with UV coordinate {1, .5}.
|
||||
/// </summary>
|
||||
MiddleRight,
|
||||
/// <summary>
|
||||
/// The lower left bounding point of the projected UVs is aligned with UV coordinate {0, 0}.
|
||||
/// </summary>
|
||||
LowerLeft,
|
||||
/// <summary>
|
||||
/// The lower center bounding point of the projected UVs is aligned with UV coordinate {.5, 0}.
|
||||
/// </summary>
|
||||
LowerCenter,
|
||||
/// <summary>
|
||||
/// The lower right bounding point of the projected UVs is aligned with UV coordinate {1, 0}.
|
||||
/// </summary>
|
||||
LowerRight,
|
||||
/// <summary>
|
||||
/// UVs are not aligned with any projection.
|
||||
/// </summary>
|
||||
None
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Describes how ProBuilder optionally stretches the projected UV bounds to fill the normalized coordinate space.
|
||||
/// </summary>
|
||||
public enum Fill
|
||||
{
|
||||
/// <summary>
|
||||
/// Retain original aspect ratio while resizing UV bounds to fit within a 1 unit square.
|
||||
/// </summary>
|
||||
Fit,
|
||||
/// <summary>
|
||||
/// Don't resize UV bounds.
|
||||
/// </summary>
|
||||
Tile,
|
||||
/// <summary>
|
||||
/// Don't retain aspect ratio while resizing UV bounds to fit within a 1 unit square.
|
||||
/// </summary>
|
||||
Stretch
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
[FormerlySerializedAs("useWorldSpace")]
|
||||
bool m_UseWorldSpace;
|
||||
|
||||
[SerializeField]
|
||||
[FormerlySerializedAs("flipU")]
|
||||
bool m_FlipU;
|
||||
|
||||
[SerializeField]
|
||||
[FormerlySerializedAs("flipV")]
|
||||
bool m_FlipV;
|
||||
|
||||
[SerializeField]
|
||||
[FormerlySerializedAs("swapUV")]
|
||||
bool m_SwapUV;
|
||||
|
||||
[SerializeField]
|
||||
[FormerlySerializedAs("fill")]
|
||||
Fill m_Fill;
|
||||
|
||||
[SerializeField]
|
||||
[FormerlySerializedAs("scale")]
|
||||
Vector2 m_Scale;
|
||||
|
||||
[SerializeField]
|
||||
[FormerlySerializedAs("offset")]
|
||||
Vector2 m_Offset;
|
||||
|
||||
[SerializeField]
|
||||
[FormerlySerializedAs("rotation")]
|
||||
float m_Rotation;
|
||||
|
||||
[SerializeField]
|
||||
[FormerlySerializedAs("anchor")]
|
||||
Anchor m_Anchor;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether to transform vertex positions into world space for UV projection.
|
||||
/// By default, ProBuilder projects UVs in local (or model) coordinates.
|
||||
/// </summary>
|
||||
public bool useWorldSpace
|
||||
{
|
||||
get { return m_UseWorldSpace; }
|
||||
set { m_UseWorldSpace = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether to invert UV coordinates horizontally.
|
||||
/// </summary>
|
||||
public bool flipU
|
||||
{
|
||||
get { return m_FlipU; }
|
||||
set { m_FlipU = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether to invert UV coordinates vertically.
|
||||
/// </summary>
|
||||
public bool flipV
|
||||
{
|
||||
get { return m_FlipV; }
|
||||
set { m_FlipV = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether to exchange the U and V coordinate parameters.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// {U, V} becomes {V, U}
|
||||
/// </example>
|
||||
public bool swapUV
|
||||
{
|
||||
get { return m_SwapUV; }
|
||||
set { m_SwapUV = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="AutoUnwrapSettings.Fill"/> mode.
|
||||
/// </summary>
|
||||
public Fill fill
|
||||
{
|
||||
get { return m_Fill; }
|
||||
set { m_Fill = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the scaling value to apply to coordinates after applying projection and anchor settings.
|
||||
/// </summary>
|
||||
public Vector2 scale
|
||||
{
|
||||
get { return m_Scale; }
|
||||
set { m_Scale = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the value to add to UV coordinates after applying projection and anchor settings.
|
||||
/// </summary>
|
||||
public Vector2 offset
|
||||
{
|
||||
get { return m_Offset; }
|
||||
set { m_Offset = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the amount in degrees to rotate UV coordinates clockwise.
|
||||
/// </summary>
|
||||
public float rotation
|
||||
{
|
||||
get { return m_Rotation; }
|
||||
set { m_Rotation = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the starting point to use for UV transform operations.
|
||||
/// </summary>
|
||||
public Anchor anchor
|
||||
{
|
||||
get { return m_Anchor; }
|
||||
set { m_Anchor = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new set of AutoUnwrapSettings from the specified source object.
|
||||
/// </summary>
|
||||
/// <param name="unwrapSettings">The settings to copy to the new instance.</param>
|
||||
public AutoUnwrapSettings(AutoUnwrapSettings unwrapSettings)
|
||||
{
|
||||
m_UseWorldSpace = unwrapSettings.m_UseWorldSpace;
|
||||
m_FlipU = unwrapSettings.m_FlipU;
|
||||
m_FlipV = unwrapSettings.m_FlipV;
|
||||
m_SwapUV = unwrapSettings.m_SwapUV;
|
||||
m_Fill = unwrapSettings.m_Fill;
|
||||
m_Scale = unwrapSettings.m_Scale;
|
||||
m_Offset = unwrapSettings.m_Offset;
|
||||
m_Rotation = unwrapSettings.m_Rotation;
|
||||
m_Anchor = unwrapSettings.m_Anchor;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a set of unwrap parameters that tiles UVs.
|
||||
/// </summary>
|
||||
public static AutoUnwrapSettings tile
|
||||
{
|
||||
get
|
||||
{
|
||||
var res = new AutoUnwrapSettings();
|
||||
res.Reset();
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a set of unwrap parameters that strectches the face texture to fill a normalized coordinate space, maintaining the aspect ratio.
|
||||
/// </summary>
|
||||
public static AutoUnwrapSettings fit
|
||||
{
|
||||
get
|
||||
{
|
||||
var res = new AutoUnwrapSettings();
|
||||
res.Reset();
|
||||
res.fill = Fill.Fit;
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a set of unwrap parameters that stretches the face texture to fill a normalized coordinate space, disregarding the aspect ratio.
|
||||
/// </summary>
|
||||
public static AutoUnwrapSettings stretch
|
||||
{
|
||||
get
|
||||
{
|
||||
var res = new AutoUnwrapSettings();
|
||||
res.Reset();
|
||||
res.fill = Fill.Stretch;
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets all parameters to their default values.
|
||||
/// </summary>
|
||||
public void Reset()
|
||||
{
|
||||
m_UseWorldSpace = false;
|
||||
m_FlipU = false;
|
||||
m_FlipV = false;
|
||||
m_SwapUV = false;
|
||||
m_Fill = Fill.Tile;
|
||||
m_Scale = new Vector2(1f, 1f);
|
||||
m_Offset = new Vector2(0f, 0f);
|
||||
m_Rotation = 0f;
|
||||
m_Anchor = Anchor.None;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a string representation of the AutoUnwrapSettings.
|
||||
/// </summary>
|
||||
/// <returns>A multi-line string containing the values for each setting.</returns>
|
||||
public override string ToString()
|
||||
{
|
||||
string str =
|
||||
"Use World Space: " + useWorldSpace + "\n" +
|
||||
"Flip U: " + flipU + "\n" +
|
||||
"Flip V: " + flipV + "\n" +
|
||||
"Swap UV: " + swapUV + "\n" +
|
||||
"Fill Mode: " + fill + "\n" +
|
||||
"Anchor: " + anchor + "\n" +
|
||||
"Scale: " + scale + "\n" +
|
||||
"Offset: " + offset + "\n" +
|
||||
"Rotation: " + rotation;
|
||||
return str;
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fdadef59afbc0447793550ae30addc32
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,125 @@
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace UnityEngine.ProBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// How bezier handles behave when being manipulated in the scene view.
|
||||
/// </summary>
|
||||
enum BezierTangentMode
|
||||
{
|
||||
Free,
|
||||
Aligned,
|
||||
Mirrored
|
||||
}
|
||||
|
||||
enum BezierTangentDirection
|
||||
{
|
||||
In,
|
||||
Out
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A bezier knot.
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
struct BezierPoint
|
||||
{
|
||||
public Vector3 position;
|
||||
public Vector3 tangentIn;
|
||||
public Vector3 tangentOut;
|
||||
public Quaternion rotation;
|
||||
|
||||
public BezierPoint(Vector3 position, Vector3 tangentIn, Vector3 tangentOut, Quaternion rotation)
|
||||
{
|
||||
this.position = position;
|
||||
this.tangentIn = tangentIn;
|
||||
this.tangentOut = tangentOut;
|
||||
this.rotation = rotation;
|
||||
}
|
||||
|
||||
public void EnforceTangentMode(BezierTangentDirection master, BezierTangentMode mode)
|
||||
{
|
||||
if (mode == BezierTangentMode.Aligned)
|
||||
{
|
||||
if (master == BezierTangentDirection.In)
|
||||
tangentOut = position + (tangentOut - position).normalized * (tangentIn - position).magnitude;
|
||||
else
|
||||
tangentIn = position + (tangentIn - position).normalized * (tangentOut - position).magnitude;
|
||||
}
|
||||
else if (mode == BezierTangentMode.Mirrored)
|
||||
{
|
||||
if (master == BezierTangentDirection.In)
|
||||
tangentOut = position - (tangentIn - position);
|
||||
else
|
||||
tangentIn = position - (tangentOut - position);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the position while also moving tangent points.
|
||||
/// </summary>
|
||||
/// <param name="position"></param>
|
||||
public void SetPosition(Vector3 position)
|
||||
{
|
||||
Vector3 delta = position - this.position;
|
||||
this.position = position;
|
||||
this.tangentIn += delta;
|
||||
this.tangentOut += delta;
|
||||
}
|
||||
|
||||
public void SetTangentIn(Vector3 tangent, BezierTangentMode mode)
|
||||
{
|
||||
this.tangentIn = tangent;
|
||||
EnforceTangentMode(BezierTangentDirection.In, mode);
|
||||
}
|
||||
|
||||
public void SetTangentOut(Vector3 tangent, BezierTangentMode mode)
|
||||
{
|
||||
this.tangentOut = tangent;
|
||||
EnforceTangentMode(BezierTangentDirection.Out, mode);
|
||||
}
|
||||
|
||||
public static Vector3 QuadraticPosition(BezierPoint a, BezierPoint b, float t)
|
||||
{
|
||||
float x = (1f - t) * (1f - t) * a.position.x + 2f * (1f - t) * t * a.tangentOut.x + t * t * b.position.x;
|
||||
float y = (1f - t) * (1f - t) * a.position.y + 2f * (1f - t) * t * a.tangentOut.y + t * t * b.position.y;
|
||||
float z = (1f - t) * (1f - t) * a.position.z + 2f * (1f - t) * t * a.tangentOut.z + t * t * b.position.z;
|
||||
return new Vector3(x, y, z);
|
||||
}
|
||||
|
||||
public static Vector3 CubicPosition(BezierPoint a, BezierPoint b, float t)
|
||||
{
|
||||
t = Mathf.Clamp01(t);
|
||||
|
||||
float oneMinusT = 1f - t;
|
||||
|
||||
return oneMinusT * oneMinusT * oneMinusT * a.position +
|
||||
3f * oneMinusT * oneMinusT * t * a.tangentOut +
|
||||
3f * oneMinusT * t * t * b.tangentIn +
|
||||
t * t * t * b.position;
|
||||
}
|
||||
|
||||
public static Vector3 GetLookDirection(IList<BezierPoint> points, int index, int previous, int next)
|
||||
{
|
||||
if (previous < 0)
|
||||
{
|
||||
return (points[index].position - QuadraticPosition(points[index], points[next], .1f)).normalized;
|
||||
}
|
||||
else if (next < 0)
|
||||
{
|
||||
return (QuadraticPosition(points[index], points[previous], .1f) - points[index].position).normalized;
|
||||
}
|
||||
else if (next > -1 && previous > -1)
|
||||
{
|
||||
Vector3 a = (QuadraticPosition(points[index], points[previous], .1f) - points[index].position).normalized;
|
||||
Vector3 b = (QuadraticPosition(points[index], points[next], .1f) - points[index].position).normalized;
|
||||
return ((a + b) * .5f).normalized;
|
||||
}
|
||||
else
|
||||
{
|
||||
return Vector3.forward;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 92c923034fe494e2d83cc342e7f8020b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,76 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace UnityEngine.ProBuilder
|
||||
{
|
||||
[AddComponentMenu("")]
|
||||
[HelpURL(k_HelpUrl)]
|
||||
[DisallowMultipleComponent, ExcludeFromPreset, ExcludeFromObjectFactory]
|
||||
[RequireComponent(typeof(ProBuilderMesh))]
|
||||
sealed class BezierShape : MonoBehaviour
|
||||
{
|
||||
const string k_HelpUrl = "https://docs.unity3d.com/Packages/com.unity.probuilder@latest";
|
||||
|
||||
public List<BezierPoint> points = new List<BezierPoint>();
|
||||
public bool closeLoop = false;
|
||||
public float radius = .5f;
|
||||
public int rows = 8;
|
||||
public int columns = 16;
|
||||
public bool smooth = true;
|
||||
[SerializeField]
|
||||
bool m_IsEditing;
|
||||
public bool isEditing
|
||||
{
|
||||
get { return m_IsEditing; }
|
||||
set { m_IsEditing = value; }
|
||||
}
|
||||
|
||||
ProBuilderMesh m_Mesh;
|
||||
|
||||
public ProBuilderMesh mesh
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_Mesh == null)
|
||||
m_Mesh = GetComponent<ProBuilderMesh>();
|
||||
|
||||
return m_Mesh;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
m_Mesh = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the points list with a default set.
|
||||
/// </summary>
|
||||
public void Init()
|
||||
{
|
||||
Vector3 tan = new Vector3(0f, 0f, 2f);
|
||||
Vector3 p1 = new Vector3(3f, 0f, 0f);
|
||||
points.Add(new BezierPoint(Vector3.zero, -tan, tan, Quaternion.identity));
|
||||
points.Add(new BezierPoint(p1, p1 + tan, p1 + -tan, Quaternion.identity));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rebuild the ProBuilderMesh with the extruded spline.
|
||||
/// </summary>
|
||||
public void Refresh()
|
||||
{
|
||||
if (points.Count < 2)
|
||||
{
|
||||
mesh.Clear();
|
||||
mesh.ToMesh();
|
||||
mesh.Refresh();
|
||||
}
|
||||
else
|
||||
{
|
||||
ProBuilderMesh m = mesh;
|
||||
Spline.Extrude(points, radius, columns, rows, closeLoop, smooth, ref m);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7b0368f3d533f4c379d9a83478864e0d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,421 @@
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace UnityEngine.ProBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Internal bounds class.
|
||||
/// </summary>
|
||||
sealed class Bounds2D
|
||||
{
|
||||
public Vector2 center = Vector2.zero;
|
||||
[SerializeField] Vector2 m_Size = Vector2.zero;
|
||||
[SerializeField] Vector2 m_Extents = Vector2.zero;
|
||||
|
||||
public Vector2 size
|
||||
{
|
||||
get { return m_Size; }
|
||||
|
||||
set
|
||||
{
|
||||
m_Size = value;
|
||||
|
||||
m_Extents.x = m_Size.x * .5f;
|
||||
m_Extents.y = m_Size.y * .5f;
|
||||
}
|
||||
}
|
||||
|
||||
public Vector2 extents
|
||||
{
|
||||
get { return m_Extents; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an array of Vector2[] points for each corner, in the order right to left, top to bottom.
|
||||
/// </summary>
|
||||
public Vector2[] corners
|
||||
{
|
||||
get
|
||||
{
|
||||
return new Vector2[] {
|
||||
new Vector2(center.x - extents.x, center.y + extents.y),
|
||||
new Vector2(center.x + extents.x, center.y + extents.y),
|
||||
new Vector2(center.x - extents.x, center.y - extents.y),
|
||||
new Vector2(center.x + extents.x, center.y - extents.y)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public Bounds2D()
|
||||
{}
|
||||
|
||||
public Bounds2D(Vector2 center, Vector2 size)
|
||||
{
|
||||
this.center = center;
|
||||
this.size = size;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create bounds from a set of 2d points.
|
||||
/// </summary>
|
||||
/// <param name="points"></param>
|
||||
public Bounds2D(IList<Vector2> points)
|
||||
{
|
||||
SetWithPoints(points);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create bounds from a set of 2d points.
|
||||
/// </summary>
|
||||
/// <param name="points"></param>
|
||||
/// <param name="indexes"></param>
|
||||
public Bounds2D(IList<Vector2> points, IList<int> indexes)
|
||||
{
|
||||
SetWithPoints(points, indexes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create bounds from a set of 3d points cast to 2d.
|
||||
/// </summary>
|
||||
/// <param name="points"></param>
|
||||
/// <param name="edges"></param>
|
||||
internal Bounds2D(Vector3[] points, Edge[] edges)
|
||||
{
|
||||
float xMin = 0f,
|
||||
xMax = 0f,
|
||||
yMin = 0f,
|
||||
yMax = 0f;
|
||||
|
||||
if (points.Length > 0 && edges.Length > 0)
|
||||
{
|
||||
xMin = points[edges[0].a].x;
|
||||
yMin = points[edges[0].a].y;
|
||||
xMax = xMin;
|
||||
yMax = yMin;
|
||||
|
||||
for (int i = 0; i < edges.Length; i++)
|
||||
{
|
||||
xMin = Mathf.Min(xMin, points[edges[i].a].x);
|
||||
xMin = Mathf.Min(xMin, points[edges[i].b].x);
|
||||
yMin = Mathf.Min(yMin, points[edges[i].a].y);
|
||||
yMin = Mathf.Min(yMin, points[edges[i].b].y);
|
||||
|
||||
xMax = Mathf.Max(xMax, points[edges[i].a].x);
|
||||
xMax = Mathf.Max(xMax, points[edges[i].b].x);
|
||||
yMax = Mathf.Max(yMax, points[edges[i].a].y);
|
||||
yMax = Mathf.Max(yMax, points[edges[i].b].y);
|
||||
}
|
||||
}
|
||||
|
||||
this.center = new Vector2((xMin + xMax) / 2f, (yMin + yMax) / 2f);
|
||||
this.size = new Vector3(xMax - xMin, yMax - yMin);
|
||||
}
|
||||
|
||||
public Bounds2D(Vector2[] points, int length)
|
||||
{
|
||||
float xMin = 0f,
|
||||
xMax = 0f,
|
||||
yMin = 0f,
|
||||
yMax = 0f;
|
||||
|
||||
if (points.Length > 0)
|
||||
{
|
||||
xMin = points[0].x;
|
||||
yMin = points[0].y;
|
||||
xMax = xMin;
|
||||
yMax = yMin;
|
||||
|
||||
for (int i = 1; i < length; i++)
|
||||
{
|
||||
xMin = Mathf.Min(xMin, points[i].x);
|
||||
yMin = Mathf.Min(yMin, points[i].y);
|
||||
|
||||
xMax = Mathf.Max(xMax, points[i].x);
|
||||
yMax = Mathf.Max(yMax, points[i].y);
|
||||
}
|
||||
}
|
||||
|
||||
this.center = new Vector2((xMin + xMax) / 2f, (yMin + yMax) / 2f);
|
||||
this.size = new Vector3(xMax - xMin, yMax - yMin);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the point is contained within the bounds. False otherwise.
|
||||
/// </summary>
|
||||
/// <param name="point"></param>
|
||||
/// <returns></returns>
|
||||
public bool ContainsPoint(Vector2 point)
|
||||
{
|
||||
return !(point.x > center.x + extents.x ||
|
||||
point.x < center.x - extents.x ||
|
||||
point.y > center.y + extents.y ||
|
||||
point.y < center.y - extents.y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if any part of the line segment is contained within this bounding box.
|
||||
/// </summary>
|
||||
/// <param name="lineStart"></param>
|
||||
/// <param name="lineEnd"></param>
|
||||
/// <returns></returns>
|
||||
public bool IntersectsLineSegment(Vector2 lineStart, Vector2 lineEnd)
|
||||
{
|
||||
if (ContainsPoint(lineStart) || ContainsPoint(lineEnd))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Vector2[] aabb = corners;
|
||||
return (Math.GetLineSegmentIntersect(aabb[0], aabb[1], lineStart, lineEnd) ||
|
||||
Math.GetLineSegmentIntersect(aabb[1], aabb[3], lineStart, lineEnd) ||
|
||||
Math.GetLineSegmentIntersect(aabb[3], aabb[2], lineStart, lineEnd) ||
|
||||
Math.GetLineSegmentIntersect(aabb[2], aabb[0], lineStart, lineEnd));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if bounds overlap.
|
||||
/// </summary>
|
||||
/// <param name="bounds"></param>
|
||||
/// <returns></returns>
|
||||
public bool Intersects(Bounds2D bounds)
|
||||
{
|
||||
Vector2 dist = this.center - bounds.center;
|
||||
Vector2 size = this.size + bounds.size;
|
||||
|
||||
return Mathf.Abs(dist.x) * 2f < size.x &&
|
||||
Mathf.Abs(dist.y) * 2f < size.y;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if this rect is intersected by another.
|
||||
/// </summary>
|
||||
/// <param name="rect"></param>
|
||||
/// <returns>True if bounds overlaps rect.</returns>
|
||||
public bool Intersects(Rect rect)
|
||||
{
|
||||
Vector2 dist = this.center - rect.center;
|
||||
Vector2 size = this.size + rect.size;
|
||||
|
||||
return Mathf.Abs(dist.x) * 2f < size.x &&
|
||||
Mathf.Abs(dist.y) * 2f < size.y;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set this bounds center and size to encapsulate points.
|
||||
/// </summary>
|
||||
/// <param name="points"></param>
|
||||
public void SetWithPoints(IList<Vector2> points)
|
||||
{
|
||||
float xMin = 0f,
|
||||
xMax = 0f,
|
||||
yMin = 0f,
|
||||
yMax = 0f;
|
||||
|
||||
int len = points.Count;
|
||||
|
||||
if (len > 0)
|
||||
{
|
||||
xMin = points[0].x;
|
||||
yMin = points[0].y;
|
||||
xMax = xMin;
|
||||
yMax = yMin;
|
||||
|
||||
for (int i = 1; i < len; i++)
|
||||
{
|
||||
float x = points[i].x;
|
||||
float y = points[i].y;
|
||||
|
||||
if (x < xMin) xMin = x;
|
||||
if (x > xMax) xMax = x;
|
||||
|
||||
if (y < yMin) yMin = y;
|
||||
if (y > yMax) yMax = y;
|
||||
}
|
||||
}
|
||||
|
||||
center.x = (xMin + xMax) / 2f;
|
||||
center.y = (yMin + yMax) / 2f;
|
||||
|
||||
m_Size.x = xMax - xMin;
|
||||
m_Size.y = yMax - yMin;
|
||||
|
||||
m_Extents.x = m_Size.x * .5f;
|
||||
m_Extents.y = m_Size.y * .5f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set this bounds center and size to encapsulate points.
|
||||
/// </summary>
|
||||
/// <param name="points"></param>
|
||||
/// <param name="indexes"></param>
|
||||
public void SetWithPoints(IList<Vector2> points, IList<int> indexes)
|
||||
{
|
||||
float xMin = 0f,
|
||||
xMax = 0f,
|
||||
yMin = 0f,
|
||||
yMax = 0f;
|
||||
|
||||
if (points.Count > 0 && indexes.Count > 0)
|
||||
{
|
||||
xMin = points[indexes[0]].x;
|
||||
yMin = points[indexes[0]].y;
|
||||
xMax = xMin;
|
||||
yMax = yMin;
|
||||
|
||||
for (int i = 1; i < indexes.Count; i++)
|
||||
{
|
||||
float x = points[indexes[i]].x;
|
||||
float y = points[indexes[i]].y;
|
||||
|
||||
if (x < xMin) xMin = x;
|
||||
if (x > xMax) xMax = x;
|
||||
|
||||
if (y < yMin) yMin = y;
|
||||
if (y > yMax) yMax = y;
|
||||
}
|
||||
}
|
||||
|
||||
center.x = (xMin + xMax) / 2f;
|
||||
center.y = (yMin + yMax) / 2f;
|
||||
|
||||
m_Size.x = xMax - xMin;
|
||||
m_Size.y = yMax - yMin;
|
||||
|
||||
m_Extents.x = m_Size.x * .5f;
|
||||
m_Extents.y = m_Size.y * .5f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the center of the bounding box of points. Optional parameter @length limits the bounds calculations
|
||||
/// to only the points up to length in array.
|
||||
/// </summary>
|
||||
/// <param name="points"></param>
|
||||
/// <param name="length"></param>
|
||||
/// <returns></returns>
|
||||
public static Vector2 Center(IList<Vector2> points)
|
||||
{
|
||||
float xMin = 0f,
|
||||
xMax = 0f,
|
||||
yMin = 0f,
|
||||
yMax = 0f;
|
||||
|
||||
int size = points.Count;
|
||||
|
||||
xMin = points[0].x;
|
||||
yMin = points[0].y;
|
||||
xMax = xMin;
|
||||
yMax = yMin;
|
||||
|
||||
for (int i = 1; i < size; i++)
|
||||
{
|
||||
float x = points[i].x;
|
||||
float y = points[i].y;
|
||||
|
||||
if (x < xMin) xMin = x;
|
||||
if (x > xMax) xMax = x;
|
||||
|
||||
if (y < yMin) yMin = y;
|
||||
if (y > yMax) yMax = y;
|
||||
}
|
||||
|
||||
return new Vector2((xMin + xMax) / 2f, (yMin + yMax) / 2f);
|
||||
}
|
||||
|
||||
public static Vector2 Center(IList<Vector2> points, IList<int> indexes)
|
||||
{
|
||||
float xMin = 0f,
|
||||
xMax = 0f,
|
||||
yMin = 0f,
|
||||
yMax = 0f;
|
||||
|
||||
int size = indexes.Count;
|
||||
|
||||
xMin = points[indexes[0]].x;
|
||||
yMin = points[indexes[0]].y;
|
||||
xMax = xMin;
|
||||
yMax = yMin;
|
||||
|
||||
for (int i = 1; i < size; i++)
|
||||
{
|
||||
float x = points[indexes[i]].x;
|
||||
float y = points[indexes[i]].y;
|
||||
|
||||
if (x < xMin) xMin = x;
|
||||
if (x > xMax) xMax = x;
|
||||
|
||||
if (y < yMin) yMin = y;
|
||||
if (y > yMax) yMax = y;
|
||||
}
|
||||
|
||||
return new Vector2((xMin + xMax) / 2f, (yMin + yMax) / 2f);
|
||||
}
|
||||
|
||||
public static Vector2 Size(IList<Vector2> points, IList<int> indexes)
|
||||
{
|
||||
float xMin = 0f,
|
||||
xMax = 0f,
|
||||
yMin = 0f,
|
||||
yMax = 0f;
|
||||
|
||||
int size = indexes.Count;
|
||||
|
||||
xMin = points[indexes[0]].x;
|
||||
yMin = points[indexes[0]].y;
|
||||
xMax = xMin;
|
||||
yMax = yMin;
|
||||
|
||||
for (int i = 1; i < size; i++)
|
||||
{
|
||||
float x = points[indexes[i]].x;
|
||||
float y = points[indexes[i]].y;
|
||||
|
||||
if (x < xMin) xMin = x;
|
||||
if (x > xMax) xMax = x;
|
||||
|
||||
if (y < yMin) yMin = y;
|
||||
if (y > yMax) yMax = y;
|
||||
}
|
||||
|
||||
return new Vector2(xMax - xMin, yMax - yMin);
|
||||
}
|
||||
|
||||
internal static Vector2 Center(IList<Vector4> points, IEnumerable<int> indexes)
|
||||
{
|
||||
float xMin = 0f,
|
||||
xMax = 0f,
|
||||
yMin = 0f,
|
||||
yMax = 0f;
|
||||
|
||||
if (indexes.Any())
|
||||
{
|
||||
var first = indexes.First();
|
||||
|
||||
xMin = points[first].x;
|
||||
yMin = points[first].y;
|
||||
xMax = xMin;
|
||||
yMax = yMin;
|
||||
|
||||
foreach (var index in indexes)
|
||||
{
|
||||
float x = points[index].x;
|
||||
float y = points[index].y;
|
||||
|
||||
if (x < xMin) xMin = x;
|
||||
if (x > xMax) xMax = x;
|
||||
|
||||
if (y < yMin) yMin = y;
|
||||
if (y > yMax) yMax = y;
|
||||
}
|
||||
}
|
||||
return new Vector2((xMin + xMax) / 2f, (yMin + yMax) / 2f);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "[cen: " + center + " size: " + size + "]";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2e8a07bcd05f44a0aa578517e042d91b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,312 @@
|
||||
using System;
|
||||
using UnityEngine.Rendering;
|
||||
using System.Reflection;
|
||||
|
||||
namespace UnityEngine.ProBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides access to the built-in materials that ProBuilder uses. In the Editor, see
|
||||
/// <see cref="UnityEditor.ProBuilder.EditorMaterialUtility"/> for access to the full array of materials
|
||||
/// and shaders.
|
||||
/// </summary>
|
||||
public static class BuiltinMaterials
|
||||
{
|
||||
static bool s_IsInitialized;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the shader used to highlight <see cref="Face" /> selections.
|
||||
/// </summary>
|
||||
public const string faceShader = "Hidden/ProBuilder/FaceHighlight";
|
||||
|
||||
/// <summary>
|
||||
/// Represents the shader used to highlight <see cref="Edge" /> selections.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If the graphics device does not support geometry shaders, this shader doesn't compile.
|
||||
/// In that case, use <see cref="wireShader"/>.
|
||||
/// </remarks>
|
||||
public const string lineShader = "Hidden/ProBuilder/LineBillboard";
|
||||
|
||||
/// <summary>
|
||||
/// Represents a line shader for use with `CreateEdgeBillboardMesh` when geometry
|
||||
/// shaders are not available. Use <see cref="lineShader"/> where possible.
|
||||
/// </summary>
|
||||
public const string lineShaderMetal = "Hidden/ProBuilder/LineBillboardMetal";
|
||||
|
||||
/// <summary>
|
||||
/// Represents the shader used to draw camera facing billboards from a single vertex.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If the graphics device does not support geometry shaders, this shader doesn't compile.
|
||||
/// In that case, use <see cref="dotShader"/>.
|
||||
/// </remarks>
|
||||
public const string pointShader = "Hidden/ProBuilder/PointBillboard";
|
||||
|
||||
/// <summary>
|
||||
/// Represents the fallback shader used to draw lines when the graphics device
|
||||
/// does not support geometry shaders.
|
||||
/// </summary>
|
||||
/// <seealso cref="lineShader"/>
|
||||
public const string wireShader = "Hidden/ProBuilder/FaceHighlight";
|
||||
|
||||
/// <summary>
|
||||
/// Represents a fallback shader used to draw billboards when the graphics
|
||||
/// device does not support geometry shaders.
|
||||
/// </summary>
|
||||
/// <seealso cref="pointShader"/>
|
||||
public const string dotShader = "Hidden/ProBuilder/VertexShader";
|
||||
|
||||
internal static readonly Color previewColor = new Color(.5f, .9f, 1f, .56f);
|
||||
|
||||
static Shader s_SelectionPickerShader;
|
||||
|
||||
static bool s_GeometryShadersSupported;
|
||||
|
||||
static Material s_DefaultMaterial;
|
||||
static Material s_FacePickerMaterial;
|
||||
static Material s_VertexPickerMaterial;
|
||||
static Material s_EdgePickerMaterial;
|
||||
static Material s_UnityDefaultDiffuse;
|
||||
static Material s_ShapePreviewMaterial;
|
||||
|
||||
#if PB_URP_MODE
|
||||
static string k_EdgePickerShader = "Hidden/ProBuilder/EdgePickerURP";
|
||||
static string k_VertexPickerShader = "Hidden/ProBuilder/VertexPickerURP";
|
||||
static string k_FacePickerShader = "Hidden/ProBuilder/FacePickerURP";
|
||||
|
||||
static string k_EdgePickerMaterial = "com.unity.probuilder/Materials/EdgePickerURP";
|
||||
static string k_FacePickerMaterial = "com.unity.probuilder/Materials/FacePickerURP";
|
||||
static string k_VertexPickerMaterial = "com.unity.probuilder/Materials/VertexPickerURP";
|
||||
|
||||
const string k_SelectionPickerShader = "Hidden/ProBuilder/SelectionPickerURP";
|
||||
#else
|
||||
static string k_EdgePickerShader = "Hidden/ProBuilder/EdgePicker";
|
||||
static string k_FacePickerShader = "Hidden/ProBuilder/FacePicker";
|
||||
static string k_VertexPickerShader = "Hidden/ProBuilder/VertexPicker";
|
||||
|
||||
static string k_EdgePickerMaterial = "com.unity.probuilder/Materials/EdgePicker";
|
||||
static string k_FacePickerMaterial = "com.unity.probuilder/Materials/FacePicker";
|
||||
static string k_VertexPickerMaterial = "com.unity.probuilder/Materials/VertexPicker";
|
||||
|
||||
const string k_SelectionPickerShader = "Hidden/ProBuilder/SelectionPicker";
|
||||
#endif
|
||||
|
||||
#if UNITY_EDITOR
|
||||
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
|
||||
static void ResetStaticsOnLoad()
|
||||
{
|
||||
// Reset the static variables to their initial values, these 3 static variables will be re-initialized when accessed
|
||||
s_DefaultMaterial = null;
|
||||
s_UnityDefaultDiffuse = null;
|
||||
s_ShapePreviewMaterial = null;
|
||||
|
||||
// Re-initialize the rest of the static variables
|
||||
s_IsInitialized = false;
|
||||
Init();
|
||||
}
|
||||
#endif
|
||||
|
||||
static void Init()
|
||||
{
|
||||
if (s_IsInitialized)
|
||||
return;
|
||||
|
||||
s_IsInitialized = true;
|
||||
|
||||
var geo = Shader.Find(lineShader);
|
||||
s_GeometryShadersSupported = geo != null && geo.isSupported;
|
||||
|
||||
// SelectionPicker shader
|
||||
s_SelectionPickerShader = (Shader)Shader.Find(k_SelectionPickerShader);
|
||||
|
||||
if ((s_FacePickerMaterial = Resources.Load<Material>(k_FacePickerMaterial)) == null)
|
||||
{
|
||||
Log.Error("FacePicker material not loaded... please re-install ProBuilder to fix this error.");
|
||||
s_FacePickerMaterial = new Material(Shader.Find(k_FacePickerShader));
|
||||
}
|
||||
|
||||
if ((s_VertexPickerMaterial = Resources.Load<Material>(k_VertexPickerMaterial)) == null)
|
||||
{
|
||||
Log.Error("VertexPicker material not loaded... please re-install ProBuilder to fix this error.");
|
||||
s_VertexPickerMaterial = new Material(Shader.Find(k_VertexPickerShader));
|
||||
}
|
||||
|
||||
if ((s_EdgePickerMaterial = Resources.Load<Material>(k_EdgePickerMaterial)) == null)
|
||||
{
|
||||
Log.Error("EdgePicker material not loaded... please re-install ProBuilder to fix this error.");
|
||||
s_EdgePickerMaterial = new Material(Shader.Find(k_EdgePickerShader));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether the current graphics device supports geometry shaders.
|
||||
/// </summary>
|
||||
/// <value>True if the current graphics device supports geometry shaders, and false if it does not.</value>
|
||||
public static bool geometryShadersSupported
|
||||
{
|
||||
get
|
||||
{
|
||||
Init();
|
||||
return s_GeometryShadersSupported;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents the default ProBuilder material.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// When using the Scriptable Render Pipeline this returns the default material for that pipeline.
|
||||
/// </remarks>
|
||||
public static Material defaultMaterial
|
||||
{
|
||||
get
|
||||
{
|
||||
Init();
|
||||
if(s_DefaultMaterial == null)
|
||||
s_DefaultMaterial = GetDefaultMaterial();
|
||||
|
||||
return s_DefaultMaterial;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents the shader used in selection picking functions.
|
||||
/// </summary>
|
||||
internal static Shader selectionPickerShader
|
||||
{
|
||||
get
|
||||
{
|
||||
Init();
|
||||
return s_SelectionPickerShader;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents the material used for face picking functions.
|
||||
/// </summary>
|
||||
internal static Material facePickerMaterial
|
||||
{
|
||||
get
|
||||
{
|
||||
Init();
|
||||
return s_FacePickerMaterial;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents the material used for vertex picking functions.
|
||||
/// </summary>
|
||||
internal static Material vertexPickerMaterial
|
||||
{
|
||||
get
|
||||
{
|
||||
Init();
|
||||
return s_VertexPickerMaterial;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents the material used for edge picking functions.
|
||||
/// </summary>
|
||||
internal static Material edgePickerMaterial
|
||||
{
|
||||
get
|
||||
{
|
||||
Init();
|
||||
return s_EdgePickerMaterial;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents the material used for the ProBuilder <see cref="Entity_Trigger">Trigger</see>
|
||||
/// entity type material.
|
||||
/// </summary>
|
||||
internal static Material triggerMaterial
|
||||
{
|
||||
get
|
||||
{
|
||||
Init();
|
||||
return (Material)Resources.Load("Materials/Trigger", typeof(Material));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents the material used for the ProBuilder <see cref="Entity_Trigger">Collider</see>
|
||||
/// entity type material.
|
||||
/// </summary>
|
||||
internal static Material colliderMaterial
|
||||
{
|
||||
get
|
||||
{
|
||||
Init();
|
||||
return (Material)Resources.Load("Materials/Collider", typeof(Material));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents the default Unity diffuse material.
|
||||
/// </summary>
|
||||
internal static Material GetLegacyDiffuse()
|
||||
{
|
||||
Init();
|
||||
|
||||
if (s_UnityDefaultDiffuse == null)
|
||||
{
|
||||
var mi = typeof(Material).GetMethod("GetDefaultMaterial",
|
||||
BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static);
|
||||
|
||||
if (mi != null)
|
||||
s_UnityDefaultDiffuse = mi.Invoke(null, null) as Material;
|
||||
|
||||
if (s_UnityDefaultDiffuse == null)
|
||||
{
|
||||
var go = GameObject.CreatePrimitive(PrimitiveType.Cube);
|
||||
s_UnityDefaultDiffuse = go.GetComponent<MeshRenderer>().sharedMaterial;
|
||||
Object.DestroyImmediate(go);
|
||||
}
|
||||
}
|
||||
|
||||
return s_UnityDefaultDiffuse;
|
||||
}
|
||||
|
||||
internal static Material GetDefaultMaterial()
|
||||
{
|
||||
var material = (Material) Resources.Load("Materials/ProBuilderDefault", typeof(Material));
|
||||
material.shader = Shader.Find("ProBuilder6/Standard Vertex Color");
|
||||
|
||||
if (material == null || !material.shader.isSupported)
|
||||
material = GetLegacyDiffuse();
|
||||
|
||||
return material;
|
||||
}
|
||||
|
||||
static Material GetPreviewMaterial()
|
||||
{
|
||||
if (defaultMaterial == null)
|
||||
return null;
|
||||
|
||||
var material = new Material(defaultMaterial.shader);
|
||||
material.hideFlags = HideFlags.HideAndDontSave;
|
||||
|
||||
if (material.HasProperty("_MainTex"))
|
||||
material.mainTexture = (Texture2D)Resources.Load("Textures/GridBox_Default");
|
||||
|
||||
if (material.HasProperty("_Color"))
|
||||
material.SetColor("_Color", previewColor);
|
||||
|
||||
return material;
|
||||
}
|
||||
|
||||
internal static Material ShapePreviewMaterial
|
||||
{
|
||||
get
|
||||
{
|
||||
if(s_ShapePreviewMaterial == null)
|
||||
s_ShapePreviewMaterial = GetPreviewMaterial();
|
||||
|
||||
return s_ShapePreviewMaterial;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 65392811987f9438d8e3d2510255814f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,95 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace UnityEngine.ProBuilder
|
||||
{
|
||||
[Serializable]
|
||||
class ChangelogEntry
|
||||
{
|
||||
[SerializeField]
|
||||
SemVer m_VersionInfo;
|
||||
|
||||
[SerializeField]
|
||||
string m_ReleaseNotes;
|
||||
|
||||
public SemVer versionInfo
|
||||
{
|
||||
get { return m_VersionInfo; }
|
||||
}
|
||||
|
||||
public string releaseNotes
|
||||
{
|
||||
get { return m_ReleaseNotes; }
|
||||
}
|
||||
|
||||
public ChangelogEntry(SemVer version, string releaseNotes)
|
||||
{
|
||||
m_VersionInfo = version;
|
||||
m_ReleaseNotes = releaseNotes;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return m_VersionInfo.ToString() + "\n\n" + m_ReleaseNotes;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
class Changelog
|
||||
{
|
||||
const string k_ChangelogEntryPattern = @"(##\s\[[0-9]+\.[0-9]+\.[0-9]+(\-[a-zA-Z]+(\.[0-9]+)*)*\])";
|
||||
const string k_VersionInfoPattern = @"(?<=##\s\[).*(?=\])";
|
||||
const string k_VersionDatePattern = @"(?<=##\s\[.*\]\s-\s)[0-9-]*";
|
||||
|
||||
[SerializeField]
|
||||
List<ChangelogEntry> m_Entries;
|
||||
|
||||
public ReadOnlyCollection<ChangelogEntry> entries
|
||||
{
|
||||
get { return new ReadOnlyCollection<ChangelogEntry>(m_Entries); }
|
||||
}
|
||||
|
||||
public Changelog(string log)
|
||||
{
|
||||
string version = string.Empty;
|
||||
StringBuilder contents = null;
|
||||
m_Entries = new List<ChangelogEntry>();
|
||||
ChangelogEntry entry;
|
||||
|
||||
foreach (var line in log.Split('\n'))
|
||||
{
|
||||
if (Regex.Match(line, k_ChangelogEntryPattern).Success)
|
||||
{
|
||||
if ((entry = CreateEntry(version, contents != null ? contents.ToString() : "")) != null)
|
||||
m_Entries.Add(entry);
|
||||
|
||||
version = line;
|
||||
contents = new StringBuilder();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (contents != null)
|
||||
contents.AppendLine(line);
|
||||
}
|
||||
}
|
||||
|
||||
if ((entry = CreateEntry(version, contents.ToString())) != null)
|
||||
m_Entries.Add(entry);
|
||||
}
|
||||
|
||||
ChangelogEntry CreateEntry(string version, string contents)
|
||||
{
|
||||
var mark = Regex.Match(version, k_VersionInfoPattern);
|
||||
var date = Regex.Match(version, k_VersionDatePattern);
|
||||
|
||||
if (mark.Success)
|
||||
return new ChangelogEntry(new SemVer(mark.Value, date.Value), contents.Trim());
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1c567d9c5b825d24ba6a00bef45ec0dc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,130 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEngine.ProBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Rect to line segment clipping implementation.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// https://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm
|
||||
/// </remarks>
|
||||
static class Clipping
|
||||
{
|
||||
[System.Flags]
|
||||
enum OutCode
|
||||
{
|
||||
Inside = 0, // 0000
|
||||
Left = 1, // 0001
|
||||
Right = 2, // 0010
|
||||
Bottom = 4, // 0100
|
||||
Top = 8, // 1000
|
||||
}
|
||||
|
||||
// Compute the bit code for a point (x, y) using the clip rectangle
|
||||
// bounded diagonally by (xmin, ymin), and (xmax, ymax)
|
||||
|
||||
static OutCode ComputeOutCode(Rect rect, float x, float y)
|
||||
{
|
||||
OutCode code = OutCode.Inside; // initialised as being inside of [[clip window]]
|
||||
|
||||
if (x < rect.xMin) // to the left of clip window
|
||||
code |= OutCode.Left;
|
||||
else if (x > rect.xMax) // to the right of clip window
|
||||
code |= OutCode.Right;
|
||||
if (y < rect.yMin) // below the clip window
|
||||
code |= OutCode.Bottom;
|
||||
else if (y > rect.yMax) // above the clip window
|
||||
code |= OutCode.Top;
|
||||
|
||||
return code;
|
||||
}
|
||||
|
||||
// Cohen–Sutherland clipping algorithm clips a line from
|
||||
// P0 = (x0, y0) to P1 = (x1, y1) against a rectangle with
|
||||
// diagonal from (xmin, ymin) to (xmax, ymax).
|
||||
internal static bool RectContainsLineSegment(Rect rect, float x0, float y0, float x1, float y1)
|
||||
{
|
||||
// compute outcodes for P0, P1, and whatever point lies outside the clip rectangle
|
||||
OutCode outcode0 = ComputeOutCode(rect, x0, y0);
|
||||
OutCode outcode1 = ComputeOutCode(rect, x1, y1);
|
||||
|
||||
bool accept = false;
|
||||
|
||||
while (true)
|
||||
{
|
||||
if ((outcode0 | outcode1) == OutCode.Inside)
|
||||
{
|
||||
// bitwise OR is 0: both points inside window; trivially accept and exit loop
|
||||
accept = true;
|
||||
break;
|
||||
}
|
||||
else if ((outcode0 & outcode1) != OutCode.Inside)
|
||||
{
|
||||
// bitwise AND is not 0: both points share an outside zone (LEFT, RIGHT, TOP,
|
||||
// or BOTTOM), so both must be outside window; exit loop (accept is false)
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
// failed both tests, so calculate the line segment to clip
|
||||
// from an outside point to an intersection with clip edge
|
||||
float x = 0f, y = 0f;
|
||||
|
||||
// At least one endpoint is outside the clip rectangle; pick it.
|
||||
OutCode outcodeOut = outcode0 != OutCode.Inside ? outcode0 : outcode1;
|
||||
|
||||
// Now find the intersection point;
|
||||
// use formulas:
|
||||
// slope = (y1 - y0) / (x1 - x0)
|
||||
// x = x0 + (1 / slope) * (ym - y0), where ym is ymin or ymax
|
||||
// y = y0 + slope * (xm - x0), where xm is xmin or xmax
|
||||
// No need to worry about divide-by-zero because, in each case, the
|
||||
// outcode bit being tested guarantees the denominator is non-zero
|
||||
if ((outcodeOut & OutCode.Top) == OutCode.Top)
|
||||
{
|
||||
// point is above the clip window
|
||||
x = x0 + (x1 - x0) * (rect.yMax - y0) / (y1 - y0);
|
||||
y = rect.yMax;
|
||||
}
|
||||
else if ((outcodeOut & OutCode.Bottom) == OutCode.Bottom)
|
||||
{
|
||||
// point is below the clip window
|
||||
x = x0 + (x1 - x0) * (rect.yMin - y0) / (y1 - y0);
|
||||
y = rect.yMin;
|
||||
}
|
||||
else if ((outcodeOut & OutCode.Right) == OutCode.Right)
|
||||
{
|
||||
// point is to the right of clip window
|
||||
y = y0 + (y1 - y0) * (rect.xMax - x0) / (x1 - x0);
|
||||
x = rect.xMax;
|
||||
}
|
||||
else if ((outcodeOut & OutCode.Left) == OutCode.Left)
|
||||
{
|
||||
// point is to the left of clip window
|
||||
y = y0 + (y1 - y0) * (rect.xMin - x0) / (x1 - x0);
|
||||
x = rect.xMin;
|
||||
}
|
||||
|
||||
// Now we move outside point to intersection point to clip
|
||||
// and get ready for next pass.
|
||||
if (outcodeOut == outcode0)
|
||||
{
|
||||
x0 = x;
|
||||
y0 = y;
|
||||
outcode0 = ComputeOutCode(rect, x0, y0);
|
||||
}
|
||||
else
|
||||
{
|
||||
x1 = x;
|
||||
y1 = y;
|
||||
outcode1 = ComputeOutCode(rect, x1, y1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return accept;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5c72c2f0fd523463292dc6eeb1fd7ab0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,39 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
|
||||
namespace UnityEngine.ProBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// A MonoBehaviour that automatically enables and disables the parent GameObject on exiting and enterin playmode, respectively.
|
||||
/// </summary>
|
||||
[DisallowMultipleComponent]
|
||||
sealed class ColliderBehaviour : EntityBehaviour
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
var collision = gameObject.GetComponent<Collider>();
|
||||
if (!collision)
|
||||
collision = gameObject.AddComponent<MeshCollider>();
|
||||
collision.isTrigger = false;
|
||||
SetMaterial(BuiltinMaterials.colliderMaterial);
|
||||
}
|
||||
|
||||
public override void OnEnterPlayMode()
|
||||
{
|
||||
var r = GetComponent<Renderer>();
|
||||
|
||||
if (r != null)
|
||||
r.enabled = false;
|
||||
}
|
||||
|
||||
public override void OnSceneLoaded(Scene scene, LoadSceneMode mode)
|
||||
{
|
||||
var r = GetComponent<Renderer>();
|
||||
|
||||
if (r != null)
|
||||
r.enabled = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 57444c075000242ea99e263cee940de2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,95 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using UnityEngine.Serialization;
|
||||
|
||||
namespace UnityEngine.ProBuilder
|
||||
{
|
||||
/// <inheritdoc cref="UnityEngine.ScriptableObject"/>
|
||||
/// <inheritdoc cref="IHasDefault"/>
|
||||
/// <summary>
|
||||
/// A set of colors for use in the color palette editor.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
sealed class ColorPalette : ScriptableObject, IHasDefault
|
||||
{
|
||||
/// <value>
|
||||
/// The currently selected color.
|
||||
/// </value>
|
||||
public Color current { get; set; }
|
||||
|
||||
[FormerlySerializedAs("colors")]
|
||||
[SerializeField]
|
||||
List<Color> m_Colors;
|
||||
|
||||
/// <value>
|
||||
/// The colors present in this palette.
|
||||
/// </value>
|
||||
public ReadOnlyCollection<Color> colors
|
||||
{
|
||||
get { return new ReadOnlyCollection<Color>(m_Colors); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the colors in this palette.
|
||||
/// </summary>
|
||||
/// <param name="colors"></param>
|
||||
/// <exception cref="ArgumentNullException">Thrown when the colors argument is null.</exception>
|
||||
public void SetColors(IEnumerable<Color> colors)
|
||||
{
|
||||
if (colors == null)
|
||||
throw new ArgumentNullException("colors");
|
||||
|
||||
m_Colors = colors.ToList();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
/// Reset the colors property to a collection of default values.
|
||||
/// </summary>
|
||||
public void SetDefaultValues()
|
||||
{
|
||||
m_Colors = new List<Color>()
|
||||
{
|
||||
new Color(0.000f, 0.122f, 0.247f, 1f),
|
||||
new Color(0.000f, 0.455f, 0.851f, 1f),
|
||||
new Color(0.498f, 0.859f, 1.000f, 1f),
|
||||
new Color(0.224f, 0.800f, 0.800f, 1f),
|
||||
new Color(0.239f, 0.600f, 0.439f, 1f),
|
||||
new Color(0.180f, 0.800f, 0.251f, 1f),
|
||||
new Color(0.004f, 1.000f, 0.439f, 1f),
|
||||
new Color(1.000f, 0.863f, 0.000f, 1f),
|
||||
new Color(1.000f, 0.522f, 0.106f, 1f),
|
||||
new Color(1.000f, 0.255f, 0.212f, 1f),
|
||||
new Color(0.522f, 0.078f, 0.294f, 1f),
|
||||
new Color(0.941f, 0.071f, 0.745f, 1f),
|
||||
new Color(0.694f, 0.051f, 0.788f, 1f),
|
||||
new Color(0.067f, 0.067f, 0.067f, 1f),
|
||||
new Color(0.667f, 0.667f, 0.667f, 1f),
|
||||
new Color(0.867f, 0.867f, 0.867f, 1f)
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Access the colors in this palette by index.
|
||||
/// </summary>
|
||||
/// <seealso cref="Count"/>
|
||||
/// <param name="i">The index to access.</param>
|
||||
/// <returns>The color at index i.</returns>
|
||||
public Color this[int i]
|
||||
{
|
||||
get { return m_Colors[i]; }
|
||||
set { m_Colors[i] = value; }
|
||||
}
|
||||
|
||||
/// <value>
|
||||
/// Return the number of colors in this palette.
|
||||
/// </value>
|
||||
public int Count
|
||||
{
|
||||
get { return m_Colors.Count; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dda436a9c8bbd4d109478acf25cd48d8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,350 @@
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace UnityEngine.ProBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Hue (0,360), Saturation (0,1), Value (0,1)
|
||||
/// </summary>
|
||||
sealed class HSVColor
|
||||
{
|
||||
public float h, s, v;
|
||||
|
||||
public HSVColor(float h, float s, float v)
|
||||
{
|
||||
this.h = h;
|
||||
this.s = s;
|
||||
this.v = v;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wikipedia colors are from 0-100 %, so this constructor includes and S, V normalizes the values.
|
||||
* modifier value that affects saturation and value, making it useful for any SV value range.
|
||||
*/
|
||||
public HSVColor(float h, float s, float v, float sv_modifier)
|
||||
{
|
||||
this.h = h;
|
||||
this.s = s * sv_modifier;
|
||||
this.v = v * sv_modifier;
|
||||
}
|
||||
|
||||
public static HSVColor FromRGB(Color col)
|
||||
{
|
||||
return ColorUtility.RGBtoHSV(col);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("( {0}, {1}, {2} )", h, s, v);
|
||||
}
|
||||
|
||||
public float SqrDistance(HSVColor InColor)
|
||||
{
|
||||
return (InColor.h / 360f - this.h / 360f) + (InColor.s - this.s) + (InColor.v - this.v);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// XYZ color
|
||||
/// <remarks>http://www.easyrgb.com/index.php?X=MATH&H=07#text7</remarks>
|
||||
/// </summary>
|
||||
sealed class XYZColor
|
||||
{
|
||||
public float x, y, z;
|
||||
|
||||
public XYZColor(float x, float y, float z)
|
||||
{
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z;
|
||||
}
|
||||
|
||||
public static XYZColor FromRGB(Color col)
|
||||
{
|
||||
return ColorUtility.RGBToXYZ(col);
|
||||
}
|
||||
|
||||
public static XYZColor FromRGB(float R, float G, float B)
|
||||
{
|
||||
return ColorUtility.RGBToXYZ(R, G, B);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("( {0}, {1}, {2} )", x, y, z);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// CIE_Lab* color
|
||||
/// </summary>
|
||||
sealed class CIELabColor
|
||||
{
|
||||
public float L, a, b;
|
||||
|
||||
public CIELabColor(float L, float a, float b)
|
||||
{
|
||||
this.L = L;
|
||||
this.a = a;
|
||||
this.b = b;
|
||||
}
|
||||
|
||||
public static CIELabColor FromXYZ(XYZColor xyz)
|
||||
{
|
||||
return ColorUtility.XYZToCIE_Lab(xyz);
|
||||
}
|
||||
|
||||
public static CIELabColor FromRGB(Color col)
|
||||
{
|
||||
XYZColor xyz = XYZColor.FromRGB(col);
|
||||
|
||||
return ColorUtility.XYZToCIE_Lab(xyz);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("( {0}, {1}, {2} )", L, a, b);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Conversion methods for RGB, HSV, XYZ, CIE-Lab
|
||||
/// </summary>
|
||||
static class ColorUtility
|
||||
{
|
||||
/// <summary>
|
||||
/// Compare float values within Epsilon distance.
|
||||
/// </summary>
|
||||
/// <param name="lhs"></param>
|
||||
/// <param name="rhs"></param>
|
||||
/// <returns></returns>
|
||||
static bool approx(float lhs, float rhs)
|
||||
{
|
||||
return Mathf.Abs(lhs - rhs) < Mathf.Epsilon;
|
||||
}
|
||||
|
||||
public static Color GetColor(Vector3 vec)
|
||||
{
|
||||
vec.Normalize();
|
||||
return new Color(Mathf.Abs(vec.x), Mathf.Abs(vec.y), Mathf.Abs(vec.z), 1f);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert RGBA color to XYZ
|
||||
/// </summary>
|
||||
/// <param name="col"></param>
|
||||
/// <returns></returns>
|
||||
public static XYZColor RGBToXYZ(Color col)
|
||||
{
|
||||
return RGBToXYZ(col.r, col.g, col.b);
|
||||
}
|
||||
|
||||
public static XYZColor RGBToXYZ(float r, float g, float b)
|
||||
{
|
||||
if (r > 0.04045f)
|
||||
r = Mathf.Pow(((r + 0.055f) / 1.055f), 2.4f);
|
||||
else
|
||||
r = r / 12.92f;
|
||||
|
||||
if (g > 0.04045f)
|
||||
g = Mathf.Pow(((g + 0.055f) / 1.055f), 2.4f);
|
||||
else
|
||||
g = g / 12.92f;
|
||||
|
||||
if (b > 0.04045f)
|
||||
b = Mathf.Pow(((b + 0.055f) / 1.055f), 2.4f);
|
||||
else
|
||||
b = b / 12.92f;
|
||||
|
||||
r = r * 100f;
|
||||
g = g * 100f;
|
||||
b = b * 100f;
|
||||
|
||||
// Observer. = 2°, Illuminant = D65
|
||||
float x = r * 0.4124f + g * 0.3576f + b * 0.1805f;
|
||||
float y = r * 0.2126f + g * 0.7152f + b * 0.0722f;
|
||||
float z = r * 0.0193f + g * 0.1192f + b * 0.9505f;
|
||||
|
||||
return new XYZColor(x, y, z);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert XYZ color to CIE_Lab
|
||||
/// </summary>
|
||||
/// <param name="xyz"></param>
|
||||
/// <returns></returns>
|
||||
public static CIELabColor XYZToCIE_Lab(XYZColor xyz)
|
||||
{
|
||||
float var_X = xyz.x / 95.047f; // ref_X = 95.047 Observer= 2°, Illuminant= D65
|
||||
float var_Y = xyz.y / 100.000f; // ref_Y = 100.000
|
||||
float var_Z = xyz.z / 108.883f; // ref_Z = 108.883
|
||||
|
||||
if (var_X > 0.008856f)
|
||||
var_X = Mathf.Pow(var_X, (1 / 3f));
|
||||
else
|
||||
var_X = (7.787f * var_X) + (16f / 116f);
|
||||
|
||||
if (var_Y > 0.008856f)
|
||||
var_Y = Mathf.Pow(var_Y, (1 / 3f));
|
||||
else
|
||||
var_Y = (7.787f * var_Y) + (16f / 116f);
|
||||
|
||||
if (var_Z > 0.008856f)
|
||||
var_Z = Mathf.Pow(var_Z, (1 / 3f));
|
||||
else
|
||||
var_Z = (7.787f * var_Z) + (16f / 116f);
|
||||
|
||||
float L = (116f * var_Y) - 16f;
|
||||
float a = 500f * (var_X - var_Y);
|
||||
float b = 200f * (var_Y - var_Z);
|
||||
|
||||
return new CIELabColor(L, a, b);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculate the euclidean distance between two Cie-Lab colors (DeltaE).
|
||||
/// http://www.easyrgb.com/index.php?X=DELT&H=03#text3
|
||||
/// </summary>
|
||||
/// <param name="lhs"></param>
|
||||
/// <param name="rhs"></param>
|
||||
/// <returns></returns>
|
||||
public static float DeltaE(CIELabColor lhs, CIELabColor rhs)
|
||||
{
|
||||
return Mathf.Sqrt(
|
||||
Mathf.Pow((lhs.L - rhs.L), 2) +
|
||||
Mathf.Pow((lhs.a - rhs.a), 2) +
|
||||
Mathf.Pow((lhs.b - rhs.b), 2));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert HSV to RGB.
|
||||
/// http://www.cs.rit.edu/~ncs/color/t_convert.html
|
||||
/// r,g,b values are from 0 to 1
|
||||
/// h = [0,360], s = [0,1], v = [0,1]
|
||||
/// if s == 0, then h = -1 (undefined)
|
||||
/// </summary>
|
||||
/// <param name="hsv"></param>
|
||||
/// <returns></returns>
|
||||
public static Color HSVtoRGB(HSVColor hsv)
|
||||
{
|
||||
return HSVtoRGB(hsv.h, hsv.s, hsv.v);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert HSV color to RGB.
|
||||
/// </summary>
|
||||
/// <param name="h"></param>
|
||||
/// <param name="s"></param>
|
||||
/// <param name="v"></param>
|
||||
/// <returns></returns>
|
||||
public static Color HSVtoRGB(float h, float s, float v)
|
||||
{
|
||||
float r, g, b;
|
||||
int i;
|
||||
float f, p, q, t;
|
||||
if (s == 0)
|
||||
{
|
||||
// achromatic (grey)
|
||||
return new Color(v, v, v, 1f);
|
||||
}
|
||||
h /= 60; // sector 0 to 5
|
||||
i = (int)Mathf.Floor(h);
|
||||
f = h - i; // factorial part of h
|
||||
p = v * (1 - s);
|
||||
q = v * (1 - s * f);
|
||||
t = v * (1 - s * (1 - f));
|
||||
|
||||
switch (i)
|
||||
{
|
||||
case 0:
|
||||
r = v;
|
||||
g = t;
|
||||
b = p;
|
||||
break;
|
||||
case 1:
|
||||
r = q;
|
||||
g = v;
|
||||
b = p;
|
||||
break;
|
||||
case 2:
|
||||
r = p;
|
||||
g = v;
|
||||
b = t;
|
||||
break;
|
||||
case 3:
|
||||
r = p;
|
||||
g = q;
|
||||
b = v;
|
||||
break;
|
||||
case 4:
|
||||
r = t;
|
||||
g = p;
|
||||
b = v;
|
||||
break;
|
||||
default: // case 5:
|
||||
r = v;
|
||||
g = p;
|
||||
b = q;
|
||||
break;
|
||||
}
|
||||
|
||||
return new Color(r, g, b, 1f);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// http://www.cs.rit.edu/~ncs/color/t_convert.html
|
||||
/// r,g,b values are from 0 to 1
|
||||
/// h = [0,360], s = [0,1], v = [0,1]
|
||||
/// if s == 0, then h = -1 (undefined)
|
||||
/// </summary>
|
||||
/// <param name="color"></param>
|
||||
/// <returns></returns>
|
||||
public static HSVColor RGBtoHSV(Color color)
|
||||
{
|
||||
float h, s, v;
|
||||
float r = color.r, b = color.b, g = color.g;
|
||||
|
||||
float min, max, delta;
|
||||
min = Mathf.Min(Mathf.Min(r, g), b);
|
||||
max = Mathf.Max(Mathf.Max(r, g), b);
|
||||
|
||||
v = max; // v
|
||||
|
||||
delta = max - min;
|
||||
|
||||
if (max != 0f)
|
||||
{
|
||||
s = delta / max; // s
|
||||
}
|
||||
else
|
||||
{
|
||||
// r = g = b = 0 // s = 0, v is undefined
|
||||
s = 0f;
|
||||
h = 0f;
|
||||
return new HSVColor(h, s, v);
|
||||
}
|
||||
|
||||
if (approx(r, max))
|
||||
{
|
||||
h = (g - b) / delta; // between yellow & magenta
|
||||
if (float.IsNaN(h))
|
||||
h = 0f;
|
||||
}
|
||||
else if (approx(g, max))
|
||||
{
|
||||
h = 2f + (b - r) / delta; // between cyan & yellow
|
||||
}
|
||||
else
|
||||
{
|
||||
h = 4f + (r - g) / delta; // between magenta & cyan
|
||||
}
|
||||
|
||||
h *= 60f; // degrees
|
||||
|
||||
if (h < 0)
|
||||
h += 360;
|
||||
|
||||
return new HSVColor(h, s, v);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 98bb5d899143343ec8ccadebc268824c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,245 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace UnityEngine.ProBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an edge connecting two vertices.
|
||||
///
|
||||
/// This might point to an index in the <see cref="ProBuilderMesh.GetVertices">vertices</see> (local) or the <see cref="ProBuilderMesh.sharedVertices" /> (common) array. The ProBuilder terminology "local" and "common" refers to whether this is an index from the list of all vertices in the ProBuilderMesh or an index from the list of only shared vertices.
|
||||
/// </summary>
|
||||
/// <seealso cref="ProBuilderMesh.sharedVertices">UnityEngine.ProBuilder.ProBuilderMesh.sharedVertices</seealso>
|
||||
/// <seealso cref="EdgeLookup">UnityEngine.ProBuilder.EdgeLookup</seealso>
|
||||
[System.Serializable]
|
||||
public struct Edge : System.IEquatable<Edge>
|
||||
{
|
||||
/// <summary>
|
||||
/// Stores an index that corresponds to a mesh vertex array.
|
||||
/// </summary>
|
||||
public int a;
|
||||
|
||||
/// <summary>
|
||||
/// Stores an index that corresponds to a mesh vertex array.
|
||||
/// </summary>
|
||||
public int b;
|
||||
|
||||
/// <summary>
|
||||
/// Creates an empty edge defined as `(-1, -1)`.
|
||||
/// </summary>
|
||||
public static readonly Edge Empty = new Edge(-1, -1);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new edge from two vertex indexes.
|
||||
/// </summary>
|
||||
/// <param name="a">An index corresponding to a mesh vertex array.</param>
|
||||
/// <param name="b">An index corresponding to a mesh vertex array.</param>
|
||||
public Edge(int a, int b)
|
||||
{
|
||||
this.a = a;
|
||||
this.b = b;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether this edge points to valid vertex indexes.
|
||||
/// </summary>
|
||||
/// <returns>True if x and y are both greater than -1.</returns>
|
||||
public bool IsValid()
|
||||
{
|
||||
return a > -1 && b > -1 && a != b;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a string representation of the edge.
|
||||
/// </summary>
|
||||
/// <returns>String formatted as `[a, b]`.</returns>
|
||||
public override string ToString()
|
||||
{
|
||||
return "[" + a + ", " + b + "]";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether this Edge is equal to another Edge object.
|
||||
/// </summary>
|
||||
/// <param name="other">The Edge to compare against.</param>
|
||||
/// <returns>True if the edges are equal, false if not.</returns>
|
||||
public bool Equals(Edge other)
|
||||
{
|
||||
return (a == other.a && b == other.b) || (a == other.b && b == other.a);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether this object is equal to another object.
|
||||
/// </summary>
|
||||
/// <param name="obj">The object to compare against.</param>
|
||||
/// <returns>True if the edges are equal, false if not.</returns>
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is Edge && Equals((Edge)obj);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the hash code for this instance.
|
||||
/// </summary>
|
||||
/// <returns>An integer that is the hash code for this instance.</returns>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
// http://stackoverflow.com/questions/263400/what-is-the-best-algorithm-for-an-overridden-system-object-gethashcode/263416#263416
|
||||
int hash = 27;
|
||||
|
||||
unchecked
|
||||
{
|
||||
hash = hash * 29 + (a < b ? a : b);
|
||||
hash = hash * 29 + (a < b ? b : a);
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new Edge by adding the two left indices together and the two right indices together from both Edge objects.
|
||||
/// </summary>
|
||||
/// <param name="a">Left edge.</param>
|
||||
/// <param name="b">Right edge.</param>
|
||||
/// <returns>A new edge where `{x, y} = {(a.a + b.a), (a.b + b.b)}`.</returns>
|
||||
public static Edge operator+(Edge a, Edge b)
|
||||
{
|
||||
return new Edge(a.a + b.a, a.b + b.b);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new Edge by subtracting the two left indices together and the two right indices together from both Edge objects.
|
||||
/// </summary>
|
||||
/// <param name="a">Left edge.</param>
|
||||
/// <param name="b">Right edge.</param>
|
||||
/// <returns>A new edge where `{x, y} = {(a.a - b.a), (a.b - b.b)}`.</returns>
|
||||
public static Edge operator-(Edge a, Edge b)
|
||||
{
|
||||
return new Edge(a.a - b.a, a.b - b.b);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new Edge by adding an integer to both indices on an Edge object.
|
||||
/// </summary>
|
||||
/// <param name="a">The Edge to add to.</param>
|
||||
/// <param name="b">The value to add.</param>
|
||||
/// <returns>A new edge where `{x, y} = {(a.a + b), (a.b + b)}`.</returns>
|
||||
public static Edge operator+(Edge a, int b)
|
||||
{
|
||||
return new Edge(a.a + b, a.b + b);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new Edge by subtracting an integer from both indices on an Edge object.
|
||||
/// </summary>
|
||||
/// <param name="a">The Edge to subtract from.</param>
|
||||
/// <param name="b">The value to subtract.</param>
|
||||
/// <returns>A new edge where `{x, y} = {(a.a - b), (a.b - b)}`.</returns>
|
||||
public static Edge operator-(Edge a, int b)
|
||||
{
|
||||
return new Edge(a.a - b, a.b - b);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compares two objects for equality.
|
||||
/// </summary>
|
||||
/// <param name="a">The first Edge instance.</param>
|
||||
/// <param name="b">The second Edge instance.</param>
|
||||
/// <returns>True if the objects are equal; false if not.</returns>
|
||||
public static bool operator==(Edge a, Edge b)
|
||||
{
|
||||
return a.Equals(b);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the two objects are not equal.
|
||||
/// </summary>
|
||||
/// <param name="a">The first Edge instance.</param>
|
||||
/// <param name="b">The second Edge instance.</param>
|
||||
/// <returns>True if the objects are not equal; false if they are equal.</returns>
|
||||
public static bool operator!=(Edge a, Edge b)
|
||||
{
|
||||
return !(a == b);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds two edges index values.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// {0, 1} + {4, 5} = {5, 6}
|
||||
/// </example>
|
||||
/// <param name="a">Left edge parameter.</param>
|
||||
/// <param name="b">Right edge parameter.</param>
|
||||
/// <returns>The sum of `a + b`.</returns>
|
||||
public static Edge Add(Edge a, Edge b)
|
||||
{
|
||||
return a + b;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Subtracts edge b from a.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// Subtract( {7, 10}, {4, 5} ) = {3, 5}
|
||||
/// </example>
|
||||
/// <param name="a">The edge to subtract from.</param>
|
||||
/// <param name="b">The value to subtract.</param>
|
||||
/// <returns>The difference of `a - b`.</returns>
|
||||
public static Edge Subtract(Edge a, Edge b)
|
||||
{
|
||||
return a - b;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compares edges and takes shared triangles into account.
|
||||
/// </summary>
|
||||
/// <param name="other">The edge to compare against.</param>
|
||||
/// <param name="lookup">A common vertex indexes lookup dictionary. See pb_IntArray for more information.</param>
|
||||
/// <remarks>Generally you just pass ProBuilderMesh.sharedIndexes.ToDictionary() to lookup, but it's more efficient to do it once and reuse that dictionary if possible.</remarks>
|
||||
/// <returns>True if edges are perceptually equal (that is, they point to the same common indexes).</returns>
|
||||
public bool Equals(Edge other, Dictionary<int, int> lookup)
|
||||
{
|
||||
if (lookup == null)
|
||||
return Equals(other);
|
||||
int x0 = lookup[a], y0 = lookup[b], x1 = lookup[other.a], y1 = lookup[other.b];
|
||||
return (x0 == x1 && y0 == y1) || (x0 == y1 && y0 == x1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether this edge contains an index.
|
||||
/// </summary>
|
||||
/// <param name="index">The index to compare against x and y.</param>
|
||||
/// <returns>True if x or y is equal to a. False if not.</returns>
|
||||
public bool Contains(int index)
|
||||
{
|
||||
return (a == index || b == index);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether this edge has any matching index to the other edge `b`.
|
||||
/// </summary>
|
||||
/// <param name="other">The edge to compare against.</param>
|
||||
/// <returns>True if x or y matches either b.x or b.y.</returns>
|
||||
public bool Contains(Edge other)
|
||||
{
|
||||
return (a == other.a || b == other.a || a == other.b || b == other.a);
|
||||
}
|
||||
|
||||
internal bool Contains(int index, Dictionary<int, int> lookup)
|
||||
{
|
||||
var common = lookup[index];
|
||||
return lookup[a] == common || lookup[b] == common;
|
||||
}
|
||||
|
||||
internal static void GetIndices(IEnumerable<Edge> edges, List<int> indices)
|
||||
{
|
||||
indices.Clear();
|
||||
|
||||
foreach (var edge in edges)
|
||||
{
|
||||
indices.Add(edge.a);
|
||||
indices.Add(edge.b);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 563a1db348c114475a2d8289cc3e5a84
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,155 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace UnityEngine.ProBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an edge composed of both the local index and the common index.
|
||||
///
|
||||
/// Use this to compare vertex indices that are coincident.
|
||||
/// [Coincident](../manual/gloss.html#coincident) vertices share the same coordinate space, but are separate values
|
||||
/// in the vertex array. ProBuilder tracks these coincident values in the <see cref="ProBuilderMesh.sharedVertices" />
|
||||
/// array. A "common" (also called "shared") index is the index of a vertex in the sharedVertices array.
|
||||
/// </summary>
|
||||
/// <seealso cref="ProBuilderMesh.sharedVertices">UnityEngine.ProBuilder.ProBuilderMesh.sharedVertices</seealso>
|
||||
/// <seealso cref="Edge">UnityEngine.ProBuilder.Edge</seealso>
|
||||
public struct EdgeLookup : IEquatable<EdgeLookup>
|
||||
{
|
||||
Edge m_Local;
|
||||
Edge m_Common;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the local edges.
|
||||
///
|
||||
/// Local edges point to an index in the <see cref="ProBuilderMesh.GetVertices">vertices</see> array.
|
||||
/// </summary>
|
||||
public Edge local
|
||||
{
|
||||
get { return m_Local; }
|
||||
set { m_Local = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the common edges.
|
||||
///
|
||||
/// Commmon edges point to the vertex index in the sharedVertices array.
|
||||
/// </summary>
|
||||
public Edge common
|
||||
{
|
||||
get { return m_Common; }
|
||||
set { m_Common = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an edge lookup from common and local Edge instances.
|
||||
/// </summary>
|
||||
/// <param name="common">An edge composed of common indexes (corresponds to <see cref="ProBuilderMesh.sharedVertices" />).</param>
|
||||
/// <param name="local">An edge composed of vertex indexes (corresponds to <see cref="ProBuilderMesh.GetVertices">mesh vertex arrays</see>).</param>
|
||||
public EdgeLookup(Edge common, Edge local)
|
||||
{
|
||||
m_Common = common;
|
||||
m_Local = local;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an edge lookup from two set of vertices that represent the common and local edges.
|
||||
/// </summary>
|
||||
/// <param name="cx">Common edge x.</param>
|
||||
/// <param name="cy">Common edge y.</param>
|
||||
/// <param name="x">Local edge x.</param>
|
||||
/// <param name="y">Local edge y.</param>
|
||||
public EdgeLookup(int cx, int cy, int x, int y)
|
||||
{
|
||||
m_Common = new Edge(cx, cy);
|
||||
m_Local = new Edge(x, y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compares each EdgeLookup common edge (does not take into account local edge differences).
|
||||
/// </summary>
|
||||
/// <param name="other">The EdgeLookup to compare against.</param>
|
||||
/// <returns>True if the common edges are equal, false if not.</returns>
|
||||
public bool Equals(EdgeLookup other)
|
||||
{
|
||||
return other.common.Equals(common);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compares each EdgeLookup common edge (does not take into account local edge differences).
|
||||
/// </summary>
|
||||
/// <param name="obj">The EdgeLookup to compare against. False if obj is not an EdgeLookup type.</param>
|
||||
/// <returns>True if the common edges are equal; false if not.</returns>
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return !ReferenceEquals(obj, null) && Equals((EdgeLookup)obj);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the hash code for this instance.
|
||||
/// </summary>
|
||||
/// <returns>An integer that is the hash code for this instance.</returns>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return common.GetHashCode();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compares two objects for equality.
|
||||
/// </summary>
|
||||
/// <param name="a">The first EdgeLookup instance.</param>
|
||||
/// <param name="b">The second EdgeLookup instance.</param>
|
||||
/// <returns>True if the objects are equal; false if not.</returns>
|
||||
public static bool operator==(EdgeLookup a, EdgeLookup b)
|
||||
{
|
||||
return Equals(a, b);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the two objects are not equal.
|
||||
/// </summary>
|
||||
/// <param name="a">The first EdgeLookup instance.</param>
|
||||
/// <param name="b">The second EdgeLookup instance.</param>
|
||||
/// <returns>True if the objects are not equal; false if they are equal.</returns>
|
||||
public static bool operator!=(EdgeLookup a, EdgeLookup b)
|
||||
{
|
||||
return !Equals(a, b);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a string representation of the common edge property.
|
||||
/// </summary>
|
||||
/// <returns>String formatted as: "Common: (`common.a`, `common.b`), local: (`local.a`, `local.b`)"</returns>
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("Common: ({0}, {1}), local: ({2}, {3})", common.a, common.b, local.a, local.b);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a list of EdgeLookup edges from a set of local edges and a sharedVertices dictionary.
|
||||
/// </summary>
|
||||
/// <param name="edges">A collection of local edges.</param>
|
||||
/// <param name="lookup">A shared index lookup dictionary (see <see cref="ProBuilderMesh.sharedVertices" />).</param>
|
||||
/// <returns>A set of EdgeLookup edges that you can iterate over.</returns>
|
||||
public static IEnumerable<EdgeLookup> GetEdgeLookup(IEnumerable<Edge> edges, Dictionary<int, int> lookup)
|
||||
{
|
||||
return edges.Select(x => new EdgeLookup(new Edge(lookup[x.a], lookup[x.b]), x));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a hashset of edge lookup values from a collection of local edges and a shared indexes lookup.
|
||||
/// </summary>
|
||||
/// <param name="edges">A collection of local edges.</param>
|
||||
/// <param name="lookup">A shared index lookup dictionary (see <see cref="ProBuilderMesh.sharedVertices" />).</param>
|
||||
/// <returns>A HashSet of EdgeLookup edges. EdgeLookup values are compared by their common property only: local edges are not compared.</returns>
|
||||
public static HashSet<EdgeLookup> GetEdgeLookupHashSet(IEnumerable<Edge> edges, Dictionary<int, int> lookup)
|
||||
{
|
||||
if (lookup == null || edges == null)
|
||||
return null;
|
||||
var hash = new HashSet<EdgeLookup>();
|
||||
foreach (var local in edges)
|
||||
hash.Add(new EdgeLookup(new Edge(lookup[local.a], lookup[local.b]), local));
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 71908286421424641b33861431bc9b9d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,154 @@
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace UnityEngine.ProBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Utilities for working with pb_Edge.
|
||||
/// </summary>
|
||||
static class EdgeUtility
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns new edges where each edge is composed not of vertex indexes, but rather the index in ProBuilderMesh.sharedIndexes of each vertex.
|
||||
/// </summary>
|
||||
/// <param name="mesh"></param>
|
||||
/// <param name="edges"></param>
|
||||
/// <returns></returns>
|
||||
public static IEnumerable<Edge> GetSharedVertexHandleEdges(this ProBuilderMesh mesh, IEnumerable<Edge> edges)
|
||||
{
|
||||
return edges.Select(x => GetSharedVertexHandleEdge(mesh, x));
|
||||
}
|
||||
|
||||
public static Edge GetSharedVertexHandleEdge(this ProBuilderMesh mesh, Edge edge)
|
||||
{
|
||||
return new Edge(mesh.sharedVertexLookup[edge.a], mesh.sharedVertexLookup[edge.b]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a universal edge to local. Does *not* guarantee that edges will be valid (indexes belong to the same face and edge).
|
||||
/// </summary>
|
||||
/// <param name="mesh"></param>
|
||||
/// <param name="edge"></param>
|
||||
/// <returns></returns>
|
||||
internal static Edge GetEdgeWithSharedVertexHandles(this ProBuilderMesh mesh, Edge edge)
|
||||
{
|
||||
return new Edge(mesh.sharedVerticesInternal[edge.a][0], mesh.sharedVerticesInternal[edge.b][0]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Given a local edge, this guarantees that both indexes belong to the same face.
|
||||
/// Note that this will only return the first valid edge found - there will usually
|
||||
/// be multiple matches (well, 2 if your geometry is sane).
|
||||
/// </summary>
|
||||
/// <param name="mesh"></param>
|
||||
/// <param name="edge"></param>
|
||||
/// <param name="validEdge"></param>
|
||||
/// <returns></returns>
|
||||
public static bool ValidateEdge(ProBuilderMesh mesh, Edge edge, out SimpleTuple<Face, Edge> validEdge)
|
||||
{
|
||||
Face[] faces = mesh.facesInternal;
|
||||
SharedVertex[] sharedIndexes = mesh.sharedVerticesInternal;
|
||||
|
||||
Edge universal = GetSharedVertexHandleEdge(mesh, edge);
|
||||
|
||||
for (int i = 0; i < faces.Length; i++)
|
||||
{
|
||||
int dist_x = -1,
|
||||
dist_y = -1,
|
||||
shared_x = -1,
|
||||
shared_y = -1;
|
||||
|
||||
if (faces[i].distinctIndexesInternal.ContainsMatch(sharedIndexes[universal.a].arrayInternal, out dist_x, out shared_x) &&
|
||||
faces[i].distinctIndexesInternal.ContainsMatch(sharedIndexes[universal.b].arrayInternal, out dist_y, out shared_y))
|
||||
{
|
||||
int x = faces[i].distinctIndexesInternal[dist_x];
|
||||
int y = faces[i].distinctIndexesInternal[dist_y];
|
||||
|
||||
validEdge = new SimpleTuple<Face, Edge>(faces[i], new Edge(x, y));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
validEdge = new SimpleTuple<Face, Edge>();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fast contains. Doesn't account for shared indexes
|
||||
/// </summary>
|
||||
internal static bool Contains(this Edge[] edges, Edge edge)
|
||||
{
|
||||
for (int i = 0; i < edges.Length; i++)
|
||||
{
|
||||
if (edges[i].Equals(edge))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fast contains. Doesn't account for shared indexes
|
||||
/// </summary>
|
||||
/// <param name="edges"></param>
|
||||
/// <param name="x"></param>
|
||||
/// <param name="y"></param>
|
||||
/// <returns></returns>
|
||||
internal static bool Contains(this Edge[] edges, int x, int y)
|
||||
{
|
||||
for (int i = 0; i < edges.Length; i++)
|
||||
{
|
||||
if ((x == edges[i].a && y == edges[i].b) || (x == edges[i].b && y == edges[i].a))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
internal static int IndexOf(this ProBuilderMesh mesh, IList<Edge> edges, Edge edge)
|
||||
{
|
||||
for (int i = 0; i < edges.Count; i++)
|
||||
{
|
||||
if (edges[i].Equals(edge, mesh.sharedVertexLookup))
|
||||
return i;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
internal static int[] AllTriangles(this Edge[] edges)
|
||||
{
|
||||
int[] arr = new int[edges.Length * 2];
|
||||
int n = 0;
|
||||
|
||||
for (int i = 0; i < edges.Length; i++)
|
||||
{
|
||||
arr[n++] = edges[i].a;
|
||||
arr[n++] = edges[i].b;
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
internal static Face GetFace(this ProBuilderMesh mesh, Edge edge)
|
||||
{
|
||||
Face res = null;
|
||||
|
||||
foreach (var face in mesh.facesInternal)
|
||||
{
|
||||
var edges = face.edgesInternal;
|
||||
|
||||
for (int i = 0, c = edges.Length; i < c; i++)
|
||||
{
|
||||
if (edge.Equals(edges[i]))
|
||||
return face;
|
||||
|
||||
if (edges.Contains(edges[i]))
|
||||
res = face;
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1da3e972186d944ca81f7581615c650d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,58 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.Serialization;
|
||||
|
||||
namespace UnityEngine.ProBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides some additional functionality to GameObjects, like managing visiblity and colliders.
|
||||
/// </summary>
|
||||
[DisallowMultipleComponent]
|
||||
[AddComponentMenu("")]
|
||||
[HelpURL(k_HelpUrl)]
|
||||
sealed class Entity : MonoBehaviour
|
||||
{
|
||||
const string k_HelpUrl = "https://docs.unity3d.com/Packages/com.unity.probuilder@latest";
|
||||
public EntityType entityType { get { return m_EntityType; } }
|
||||
|
||||
[SerializeField]
|
||||
[HideInInspector]
|
||||
[FormerlySerializedAs("_entityType")]
|
||||
EntityType m_EntityType;
|
||||
|
||||
/// <summary>
|
||||
/// Performs Entity specific initialization tasks (turn off renderer for nodraw faces, hide colliders, etc)
|
||||
/// </summary>
|
||||
public void Awake()
|
||||
{
|
||||
MeshRenderer mr = GetComponent<MeshRenderer>();
|
||||
|
||||
if (!mr) return;
|
||||
|
||||
switch (entityType)
|
||||
{
|
||||
case EntityType.Occluder:
|
||||
break;
|
||||
|
||||
case EntityType.Detail:
|
||||
break;
|
||||
|
||||
case EntityType.Trigger:
|
||||
mr.enabled = false;
|
||||
break;
|
||||
|
||||
case EntityType.Collider:
|
||||
mr.enabled = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the entity type.
|
||||
/// </summary>
|
||||
/// <param name="t"></param>
|
||||
public void SetEntity(EntityType t)
|
||||
{
|
||||
m_EntityType = t;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 69fbd00be8d904b3da640e502905c345
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,30 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
|
||||
namespace UnityEngine.ProBuilder
|
||||
{
|
||||
abstract class EntityBehaviour : MonoBehaviour
|
||||
{
|
||||
[Tooltip("Allow ProBuilder to automatically hide and show this object when entering or exiting play mode.")]
|
||||
public bool manageVisibility = true;
|
||||
|
||||
public abstract void Initialize();
|
||||
|
||||
public abstract void OnEnterPlayMode();
|
||||
|
||||
public abstract void OnSceneLoaded(Scene scene, LoadSceneMode mode);
|
||||
|
||||
protected void SetMaterial(Material material)
|
||||
{
|
||||
if (GetComponent<Renderer>())
|
||||
GetComponent<Renderer>().sharedMaterial = material;
|
||||
else
|
||||
gameObject.AddComponent<MeshRenderer>().sharedMaterial = material;
|
||||
}
|
||||
|
||||
// Not necessary because OnEnterPlayMode is operating on an instance of the scene, not the actual scene
|
||||
// public abstract void OnExitPlayMode();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cd4d30ee1f2db48c6be97ed8d2fc9da4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,539 @@
|
||||
using UnityEngine;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine.Serialization;
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace UnityEngine.ProBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// A face is composed of a set of triangles, and a material.
|
||||
///
|
||||
/// Triangle indices may point to the same vertex index as long as the vertices are unique to the face. That is,
|
||||
/// every vertex that a face references should only be used by that face's indices. To associate vertices that
|
||||
/// share common attributes (usually position), use the <see cref="ProBuilderMesh.sharedVertices">sharedIndexes</see> property.
|
||||
///
|
||||
/// ProBuilder automatically manages condensing common vertices in the
|
||||
/// <see cref="UnityEditor.ProBuilder.EditorMeshUtility.Optimize">EditorMeshUtility.Optimize</see> function.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public sealed class Face
|
||||
{
|
||||
[FormerlySerializedAs("_indices")]
|
||||
[SerializeField]
|
||||
int[] m_Indexes;
|
||||
|
||||
/// <summary>
|
||||
/// Stores the ID of the smoothing group that this Face is part of. ProBuilder averages the edge
|
||||
/// normals for all faces that share a [Smoothing Group](../manual/workflow-edit-smoothing.md).
|
||||
/// </summary>
|
||||
[SerializeField]
|
||||
[FormerlySerializedAs("_smoothingGroup")]
|
||||
int m_SmoothingGroup;
|
||||
|
||||
/// <summary>
|
||||
/// Determines how ProBuilder projects this face's vertices 2D space when <see cref="manualUV" /> is false.
|
||||
/// </summary>
|
||||
[SerializeField]
|
||||
[FormerlySerializedAs("_uv")]
|
||||
AutoUnwrapSettings m_Uv;
|
||||
|
||||
/// <summary>
|
||||
/// Stores the material for this face to use.
|
||||
/// </summary>
|
||||
[SerializeField]
|
||||
[FormerlySerializedAs("_mat")]
|
||||
Material m_Material;
|
||||
|
||||
[SerializeField]
|
||||
int m_SubmeshIndex;
|
||||
|
||||
|
||||
[SerializeField]
|
||||
[FormerlySerializedAs("manualUV")]
|
||||
bool m_ManualUV;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether to map this face's UV coordinates manually or automatically. See
|
||||
/// [Mapping Textures with UVs](../manual/workflow-uvs.html) for an overview of the differences.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// True to set UV coordinates manually; false to use <see cref="AutoUnwrapSettings" />.
|
||||
/// </value>
|
||||
public bool manualUV
|
||||
{
|
||||
get { return m_ManualUV; }
|
||||
set { m_ManualUV = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// UV element group. Used by the UV editor to group faces.
|
||||
/// </summary>
|
||||
[SerializeField]
|
||||
internal int elementGroup;
|
||||
|
||||
|
||||
[SerializeField]
|
||||
int m_TextureGroup;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets which texture group this face belongs to. ProBuilder uses texture groups when
|
||||
/// projecting Auto UVs. See [Continuous tiling](../manual/workflow-uvs.html#continuous-tiling).
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// ID of the texture group for this face.
|
||||
/// </value>
|
||||
public int textureGroup
|
||||
{
|
||||
get { return m_TextureGroup;}
|
||||
set { m_TextureGroup = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a reference to the array of triangle indices that make up this face.
|
||||
/// </summary>
|
||||
internal int[] indexesInternal
|
||||
{
|
||||
get { return m_Indexes; }
|
||||
set
|
||||
{
|
||||
if (m_Indexes == null)
|
||||
throw new ArgumentNullException("value");
|
||||
if (m_Indexes.Length % 3 != 0)
|
||||
throw new ArgumentException("Face indexes must be a multiple of 3.");
|
||||
m_Indexes = value;
|
||||
InvalidateCache();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the triangle indices that compose this face.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The array of indices representing this face's triangles.
|
||||
/// </value>
|
||||
public ReadOnlyCollection<int> indexes
|
||||
{
|
||||
get { return new ReadOnlyCollection<int>(m_Indexes); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the triangles that compose this face.
|
||||
/// </summary>
|
||||
/// <param name="indices">The new triangle array.</param>
|
||||
public void SetIndexes(IEnumerable<int> indices)
|
||||
{
|
||||
if (indices == null)
|
||||
throw new ArgumentNullException("indices");
|
||||
var array = indices.ToArray();
|
||||
int len = array.Length;
|
||||
if (len % 3 != 0)
|
||||
throw new ArgumentException("Face indexes must be a multiple of 3.");
|
||||
m_Indexes = array;
|
||||
InvalidateCache();
|
||||
}
|
||||
|
||||
[NonSerialized]
|
||||
int[] m_DistinctIndexes;
|
||||
|
||||
[NonSerialized]
|
||||
Edge[] m_Edges;
|
||||
|
||||
/// <summary>
|
||||
/// Returns a reference to the cached distinct indexes (each vertex index is only referenced once in m_DistinctIndexes).
|
||||
/// </summary>
|
||||
internal int[] distinctIndexesInternal
|
||||
{
|
||||
get { return m_DistinctIndexes == null ? CacheDistinctIndexes() : m_DistinctIndexes; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a collection of the vertex indexes that the indexes array references, made distinct.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// A unique collection of vertices.
|
||||
/// </value>
|
||||
public ReadOnlyCollection<int> distinctIndexes
|
||||
{
|
||||
get { return new ReadOnlyCollection<int>(distinctIndexesInternal); }
|
||||
}
|
||||
|
||||
internal Edge[] edgesInternal
|
||||
{
|
||||
get { return m_Edges == null ? CacheEdges() : m_Edges; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the perimeter edges that compose this face.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The collection of edges on this face.
|
||||
/// </value>
|
||||
public ReadOnlyCollection<Edge> edges
|
||||
{
|
||||
get { return new ReadOnlyCollection<Edge>(edgesInternal); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets which smoothing group this face belongs to, if any. This is used to calculate vertex normals.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The ID of this smoothing group as an integer.
|
||||
/// </value>
|
||||
public int smoothingGroup
|
||||
{
|
||||
get { return m_SmoothingGroup; }
|
||||
set { m_SmoothingGroup = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the material that this face uses.
|
||||
/// </summary>
|
||||
[Obsolete("Face.material is deprecated. Please use submeshIndex instead.")]
|
||||
public Material material
|
||||
{
|
||||
get { return m_Material; }
|
||||
set { m_Material = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the index of the submesh that this face belongs to.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The ID of the submesh as an integer.
|
||||
/// </value>
|
||||
public int submeshIndex
|
||||
{
|
||||
get { return m_SubmeshIndex; }
|
||||
set { m_SubmeshIndex = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a reference to the [Auto UV](../manual/workflow-uvs.html#auto-uv-mode-features) mapping parameters.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The ID of this submesh as an integer.
|
||||
/// </value>
|
||||
public AutoUnwrapSettings uv
|
||||
{
|
||||
get { return m_Uv; }
|
||||
set { m_Uv = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the index for the specified triangle in this face's array of triangle indices.
|
||||
/// </summary>
|
||||
/// <param name="i">The triangle to access</param>
|
||||
/// <value>
|
||||
/// The index of the specified triangle.
|
||||
/// </value>
|
||||
public int this[int i]
|
||||
{
|
||||
get { return indexesInternal[i]; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a Face with an empty triangles array.
|
||||
/// </summary>
|
||||
public Face()
|
||||
{
|
||||
m_SubmeshIndex = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a face with default values and the specified set of triangles.
|
||||
/// </summary>
|
||||
/// <param name="indices">The new triangles array.</param>
|
||||
public Face(IEnumerable<int> indices)
|
||||
{
|
||||
SetIndexes(indices);
|
||||
m_Uv = AutoUnwrapSettings.tile;
|
||||
m_Material = BuiltinMaterials.defaultMaterial;
|
||||
m_SmoothingGroup = Smoothing.smoothingGroupNone;
|
||||
m_SubmeshIndex = 0;
|
||||
textureGroup = -1;
|
||||
elementGroup = 0;
|
||||
}
|
||||
|
||||
[Obsolete("Face.material is deprecated. Please use \"submeshIndex\" instead.")]
|
||||
internal Face(int[] triangles, Material m, AutoUnwrapSettings u, int smoothing, int texture, int element, bool manualUVs)
|
||||
{
|
||||
SetIndexes(triangles);
|
||||
m_Uv = new AutoUnwrapSettings(u);
|
||||
m_Material = m;
|
||||
m_SmoothingGroup = smoothing;
|
||||
textureGroup = texture;
|
||||
elementGroup = element;
|
||||
manualUV = manualUVs;
|
||||
m_SubmeshIndex = 0;
|
||||
}
|
||||
|
||||
internal Face(IEnumerable<int> triangles, int submeshIndex, AutoUnwrapSettings u, int smoothing, int texture, int element, bool manualUVs)
|
||||
{
|
||||
SetIndexes(triangles);
|
||||
m_Uv = new AutoUnwrapSettings(u);
|
||||
m_SmoothingGroup = smoothing;
|
||||
textureGroup = texture;
|
||||
elementGroup = element;
|
||||
manualUV = manualUVs;
|
||||
m_SubmeshIndex = submeshIndex;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new Face as a copy of another face.
|
||||
/// </summary>
|
||||
/// <param name="other">The Face from which to copy properties and triangles.</param>
|
||||
public Face(Face other)
|
||||
{
|
||||
CopyFrom(other);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies properties and triangles from the specified face to this face.
|
||||
/// </summary>
|
||||
/// <param name="other">The Face from which to copy properties and triangles.</param>
|
||||
public void CopyFrom(Face other)
|
||||
{
|
||||
if (other == null)
|
||||
throw new ArgumentNullException("other");
|
||||
|
||||
int len = other.indexesInternal.Length;
|
||||
m_Indexes = new int[len];
|
||||
Array.Copy(other.indexesInternal, m_Indexes, len);
|
||||
|
||||
m_SmoothingGroup = other.smoothingGroup;
|
||||
m_Uv = new AutoUnwrapSettings(other.uv);
|
||||
#pragma warning disable 618
|
||||
m_Material = other.material;
|
||||
#pragma warning restore 618
|
||||
manualUV = other.manualUV;
|
||||
m_TextureGroup = other.textureGroup;
|
||||
elementGroup = other.elementGroup;
|
||||
m_SubmeshIndex = other.m_SubmeshIndex;
|
||||
InvalidateCache();
|
||||
}
|
||||
|
||||
internal void InvalidateCache()
|
||||
{
|
||||
m_Edges = null;
|
||||
m_DistinctIndexes = null;
|
||||
}
|
||||
|
||||
Edge[] CacheEdges()
|
||||
{
|
||||
if (m_Indexes == null)
|
||||
return null;
|
||||
|
||||
HashSet<Edge> dist = new HashSet<Edge>();
|
||||
List<Edge> dup = new List<Edge>();
|
||||
|
||||
for (int i = 0; i < indexesInternal.Length; i += 3)
|
||||
{
|
||||
Edge a = new Edge(indexesInternal[i + 0], indexesInternal[i + 1]);
|
||||
Edge b = new Edge(indexesInternal[i + 1], indexesInternal[i + 2]);
|
||||
Edge c = new Edge(indexesInternal[i + 2], indexesInternal[i + 0]);
|
||||
|
||||
if (!dist.Add(a)) dup.Add(a);
|
||||
if (!dist.Add(b)) dup.Add(b);
|
||||
if (!dist.Add(c)) dup.Add(c);
|
||||
}
|
||||
|
||||
dist.ExceptWith(dup);
|
||||
m_Edges = dist.ToArray();
|
||||
return m_Edges;
|
||||
}
|
||||
|
||||
int[] CacheDistinctIndexes()
|
||||
{
|
||||
if (m_Indexes == null)
|
||||
return null;
|
||||
m_DistinctIndexes = m_Indexes.Distinct().ToArray();
|
||||
return distinctIndexesInternal;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether a triangle matches one of the triangles of this face.
|
||||
/// </summary>
|
||||
/// <param name="a">First index in the triangle</param>
|
||||
/// <param name="b">Second index in the triangle</param>
|
||||
/// <param name="c">Third index in the triangle</param>
|
||||
/// <returns>True if {a,b,c} is found in this face's list of triangles; otherwise false.</returns>
|
||||
public bool Contains(int a, int b, int c)
|
||||
{
|
||||
for (int i = 0, cnt = indexesInternal.Length; i < cnt; i += 3)
|
||||
{
|
||||
if (a == indexesInternal[i + 0]
|
||||
&& b == indexesInternal[i + 1]
|
||||
&& c == indexesInternal[i + 2])
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether this face can be converted to a quad (a polygon with four sides).
|
||||
/// </summary>
|
||||
/// <returns>True if this face is divisible by 4; false otherwise.</returns>
|
||||
public bool IsQuad()
|
||||
{
|
||||
return edgesInternal != null && edgesInternal.Length == 4;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a two-triangle face to a quad representation.
|
||||
/// </summary>
|
||||
/// <returns>A quad (an array of four indices); or null if indices are not able to be represented as a quad.</returns>
|
||||
public int[] ToQuad()
|
||||
{
|
||||
if (!IsQuad())
|
||||
throw new InvalidOperationException("Face is not representable as a quad. Use Face.IsQuad to check for validity.");
|
||||
|
||||
int[] quad = new int[4] { edgesInternal[0].a, edgesInternal[0].b, -1, -1 };
|
||||
|
||||
if (edgesInternal[1].a == quad[1])
|
||||
quad[2] = edgesInternal[1].b;
|
||||
else if (edgesInternal[2].a == quad[1])
|
||||
quad[2] = edgesInternal[2].b;
|
||||
else if (edgesInternal[3].a == quad[1])
|
||||
quad[2] = edgesInternal[3].b;
|
||||
|
||||
if (edgesInternal[1].a == quad[2])
|
||||
quad[3] = edgesInternal[1].b;
|
||||
else if (edgesInternal[2].a == quad[2])
|
||||
quad[3] = edgesInternal[2].b;
|
||||
else if (edgesInternal[3].a == quad[2])
|
||||
quad[3] = edgesInternal[3].b;
|
||||
|
||||
return quad;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a string representation of the face.
|
||||
/// </summary>
|
||||
/// <returns>String formatted as `[a, b, c], ...`.</returns>
|
||||
public override string ToString()
|
||||
{
|
||||
System.Text.StringBuilder sb = new System.Text.StringBuilder();
|
||||
|
||||
for (int i = 0; i < indexesInternal.Length; i += 3)
|
||||
{
|
||||
sb.Append("[");
|
||||
sb.Append(indexesInternal[i]);
|
||||
sb.Append(", ");
|
||||
sb.Append(indexesInternal[i + 1]);
|
||||
sb.Append(", ");
|
||||
sb.Append(indexesInternal[i + 2]);
|
||||
sb.Append("]");
|
||||
|
||||
if (i < indexesInternal.Length - 3)
|
||||
sb.Append(", ");
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds an offset to each value in the indices array.
|
||||
/// </summary>
|
||||
/// <param name="offset">The value to add to each index.</param>
|
||||
public void ShiftIndexes(int offset)
|
||||
{
|
||||
for (int i = 0, c = m_Indexes.Length; i < c; i++)
|
||||
m_Indexes[i] += offset;
|
||||
|
||||
InvalidateCache();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds the smallest value in the triangles array.
|
||||
/// </summary>
|
||||
/// <returns>The smallest value in the indices array.</returns>
|
||||
int SmallestIndexValue()
|
||||
{
|
||||
int smallest = m_Indexes[0];
|
||||
|
||||
for (int i = 1; i < m_Indexes.Length; i++)
|
||||
{
|
||||
if (m_Indexes[i] < smallest)
|
||||
smallest = m_Indexes[i];
|
||||
}
|
||||
|
||||
return smallest;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds the smallest value in the indices array, then offsets by subtracting that value from each index.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// ```
|
||||
/// // sets the indexes array to `{0, 1, 2}`.
|
||||
/// new Face(3,4,5).ShiftIndexesToZero();
|
||||
/// ```
|
||||
/// </example>
|
||||
public void ShiftIndexesToZero()
|
||||
{
|
||||
int offset = SmallestIndexValue();
|
||||
|
||||
for (int i = 0; i < m_Indexes.Length; i++)
|
||||
m_Indexes[i] -= offset;
|
||||
|
||||
InvalidateCache();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reverses the order of the triangle array. This has the effect of reversing the direction that this face renders.
|
||||
/// </summary>
|
||||
public void Reverse()
|
||||
{
|
||||
Array.Reverse(m_Indexes);
|
||||
InvalidateCache();
|
||||
}
|
||||
|
||||
internal static void GetIndices(IEnumerable<Face> faces, List<int> indices)
|
||||
{
|
||||
indices.Clear();
|
||||
|
||||
foreach (var face in faces)
|
||||
{
|
||||
for (int i = 0, c = face.indexesInternal.Length; i < c; ++i)
|
||||
indices.Add(face.indexesInternal[i]);
|
||||
}
|
||||
}
|
||||
|
||||
internal static void GetDistinctIndices(IEnumerable<Face> faces, List<int> indices)
|
||||
{
|
||||
indices.Clear();
|
||||
|
||||
foreach (var face in faces)
|
||||
{
|
||||
for (int i = 0, c = face.distinctIndexesInternal.Length; i < c; ++i)
|
||||
indices.Add(face.distinctIndexesInternal[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Advances to the next connected edge given a source edge and the index connect.
|
||||
/// </summary>
|
||||
internal bool TryGetNextEdge(Edge source, int index, ref Edge nextEdge, ref int nextIndex)
|
||||
{
|
||||
for (int i = 0, c = edgesInternal.Length; i < c; i++)
|
||||
{
|
||||
if (edgesInternal[i] == source)
|
||||
continue;
|
||||
|
||||
nextEdge = edgesInternal[i];
|
||||
|
||||
if (nextEdge.Contains(index))
|
||||
{
|
||||
nextIndex = nextEdge.a == index ? nextEdge.b : nextEdge.a;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cd0eb60dd72b74385a28317c44ac6377
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,110 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace UnityEngine.ProBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Information required to append a face to a pb_Object.
|
||||
/// </summary>
|
||||
sealed class FaceRebuildData
|
||||
{
|
||||
#pragma warning disable 0649
|
||||
// new pb_Face
|
||||
public Face face;
|
||||
// new vertices (all vertices required to rebuild, not just new)
|
||||
public List<Vertex> vertices;
|
||||
// shared indexes pointers (must match vertices length)
|
||||
public List<int> sharedIndexes;
|
||||
// shared UV indexes pointers (must match vertices length)
|
||||
public List<int> sharedIndexesUV;
|
||||
// The offset applied to this face via Apply() call.
|
||||
private int _appliedOffset = 0;
|
||||
#pragma warning restore 0649
|
||||
|
||||
/**
|
||||
* If this face has been applied to a pb_Object via Apply() this returns the index offset applied.
|
||||
*/
|
||||
public int Offset()
|
||||
{
|
||||
return _appliedOffset;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("{0}\n{1}", vertices.ToString(", "), sharedIndexes.ToString(", "));
|
||||
}
|
||||
|
||||
public static void Apply(
|
||||
IEnumerable<FaceRebuildData> newFaces,
|
||||
ProBuilderMesh mesh,
|
||||
List<Vertex> vertices = null,
|
||||
List<Face> faces = null)
|
||||
{
|
||||
if (faces == null)
|
||||
faces = new List<Face>(mesh.facesInternal);
|
||||
|
||||
if (vertices == null)
|
||||
vertices = new List<Vertex>(mesh.GetVertices());
|
||||
|
||||
var lookup = mesh.sharedVertexLookup;
|
||||
var lookupUV = mesh.sharedTextureLookup;
|
||||
|
||||
Apply(newFaces, vertices, faces, lookup, lookupUV);
|
||||
|
||||
mesh.SetVertices(vertices);
|
||||
mesh.faces = faces;
|
||||
mesh.SetSharedVertices(lookup);
|
||||
mesh.SetSharedTextures(lookupUV);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shift face rebuild data to appropriate positions and update the vertex, face, and shared indexes arrays.
|
||||
/// </summary>
|
||||
/// <param name="newFaces"></param>
|
||||
/// <param name="vertices"></param>
|
||||
/// <param name="faces"></param>
|
||||
/// <param name="sharedVertexLookup"></param>
|
||||
/// <param name="sharedTextureLookup"></param>
|
||||
public static void Apply(
|
||||
IEnumerable<FaceRebuildData> newFaces,
|
||||
List<Vertex> vertices,
|
||||
List<Face> faces,
|
||||
Dictionary<int, int> sharedVertexLookup,
|
||||
Dictionary<int, int> sharedTextureLookup = null)
|
||||
{
|
||||
int index = vertices.Count;
|
||||
|
||||
foreach (FaceRebuildData rd in newFaces)
|
||||
{
|
||||
Face face = rd.face;
|
||||
int faceVertexCount = rd.vertices.Count;
|
||||
|
||||
bool hasSharedIndexes = sharedVertexLookup != null && rd.sharedIndexes != null && rd.sharedIndexes.Count == faceVertexCount;
|
||||
bool hasSharedIndexesUV = sharedTextureLookup != null && rd.sharedIndexesUV != null && rd.sharedIndexesUV.Count == faceVertexCount;
|
||||
|
||||
for (int n = 0; n < faceVertexCount; n++)
|
||||
{
|
||||
int localIndex = n;
|
||||
|
||||
if (sharedVertexLookup != null)
|
||||
sharedVertexLookup.Add(localIndex + index, hasSharedIndexes ? rd.sharedIndexes[localIndex] : -1);
|
||||
|
||||
if (sharedTextureLookup != null && hasSharedIndexesUV)
|
||||
sharedTextureLookup.Add(localIndex + index, rd.sharedIndexesUV[localIndex]);
|
||||
}
|
||||
|
||||
rd._appliedOffset = index;
|
||||
int[] faceIndexes = face.indexesInternal;
|
||||
|
||||
for (int n = 0, c = faceIndexes.Length; n < c; n++)
|
||||
faceIndexes[n] += index;
|
||||
|
||||
index += rd.vertices.Count;
|
||||
face.indexesInternal = faceIndexes;
|
||||
faces.Add(face);
|
||||
vertices.AddRange(rd.vertices);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dfce99e9d622b416d8522fc504aa4459
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,65 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
|
||||
namespace UnityEngine.ProBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// A class for storing and applying Vector2 masks.
|
||||
/// </summary>
|
||||
sealed class HandleConstraint2D
|
||||
{
|
||||
public int x, y;
|
||||
|
||||
public HandleConstraint2D(int x, int y)
|
||||
{
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
public HandleConstraint2D Inverse()
|
||||
{
|
||||
return new HandleConstraint2D(x == 1 ? 0 : 1, y == 1 ? 0 : 1);
|
||||
}
|
||||
|
||||
public Vector2 Mask(Vector2 v)
|
||||
{
|
||||
v.x *= this.x;
|
||||
v.y *= this.y;
|
||||
return v;
|
||||
}
|
||||
|
||||
public Vector2 InverseMask(Vector2 v)
|
||||
{
|
||||
v.x *= this.x == 1 ? 0f : 1f;
|
||||
v.y *= this.y == 1 ? 0f : 1f;
|
||||
return v;
|
||||
}
|
||||
|
||||
public static readonly HandleConstraint2D None = new HandleConstraint2D(1, 1);
|
||||
|
||||
public static bool operator==(HandleConstraint2D a, HandleConstraint2D b)
|
||||
{
|
||||
return a.x == b.x && a.y == b.y;
|
||||
}
|
||||
|
||||
public static bool operator!=(HandleConstraint2D a, HandleConstraint2D b)
|
||||
{
|
||||
return a.x != b.x || a.y != b.y;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return base.GetHashCode();
|
||||
}
|
||||
|
||||
public override bool Equals(object o)
|
||||
{
|
||||
return o is HandleConstraint2D && ((HandleConstraint2D)o).x == this.x && ((HandleConstraint2D)o).y == this.y;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "(" + x + ", " + y + ")";
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a88380604e5234adaa98158c5e10950c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,32 @@
|
||||
namespace UnityEngine.ProBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines how the handle gizmo is oriented with regards to the current element selection.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This overrides the Unity Pivot / Global setting when editing vertices, faces, or edges.
|
||||
/// Editor only.
|
||||
/// </remarks>
|
||||
public enum HandleOrientation
|
||||
{
|
||||
/// <summary>
|
||||
/// The gizmo is aligned to identity in world space.
|
||||
/// </summary>
|
||||
World = 0,
|
||||
|
||||
/// <summary>
|
||||
/// The gizmo is aligned relative to the active mesh transform. This is also called "coordinate" or "model" space.
|
||||
/// </summary>
|
||||
ActiveObject = 1,
|
||||
|
||||
/// <summary>
|
||||
/// The gizmo is aligned relative to the currently selected face. When editing vertices or edges, this falls back to <see cref="ActiveObject"/> alignment.
|
||||
/// </summary>
|
||||
ActiveElement = 2,
|
||||
|
||||
// /// <summary>
|
||||
// /// The transform gizmo is user-set.
|
||||
// /// </summary>
|
||||
// Custom = 3
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2e52c205e35f30d4c81ff62d3f94e234
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,579 @@
|
||||
using UnityEngine;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
|
||||
namespace UnityEngine.ProBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides static methods for working with ProBuilderMesh objects in the Editor.
|
||||
/// </summary>
|
||||
public static class HandleUtility
|
||||
{
|
||||
/// <summary>
|
||||
/// Convert a screen point (0,0 bottom left, in pixels) to a GUI point (0,0 top left, in points).
|
||||
/// </summary>
|
||||
/// <param name="camera"></param>
|
||||
/// <param name="point"></param>
|
||||
/// <param name="pixelsPerPoint"></param>
|
||||
/// <returns></returns>
|
||||
internal static Vector3 ScreenToGuiPoint(this Camera camera, Vector3 point, float pixelsPerPoint)
|
||||
{
|
||||
return new Vector3(point.x / pixelsPerPoint, (camera.pixelHeight - point.y) / pixelsPerPoint, point.z);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find a triangle intersected by InRay on InMesh. InRay is in world space.
|
||||
/// Returns the index in mesh.faces of the hit face, or -1. Optionally can ignore backfaces.
|
||||
/// </summary>
|
||||
/// <param name="worldRay"></param>
|
||||
/// <param name="mesh"></param>
|
||||
/// <param name="hit"></param>
|
||||
/// <param name="ignore"></param>
|
||||
/// <returns></returns>
|
||||
internal static bool FaceRaycast(Ray worldRay, ProBuilderMesh mesh, out RaycastHit hit, HashSet<Face> ignore = null)
|
||||
{
|
||||
return FaceRaycast(worldRay, mesh, out hit, Mathf.Infinity, CullingMode.Back, ignore);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find the nearest face intersected by InWorldRay on this pb_Object.
|
||||
/// </summary>
|
||||
/// <param name="worldRay">A ray in world space.</param>
|
||||
/// <param name="mesh">The ProBuilder object to raycast against.</param>
|
||||
/// <param name="hit">If the mesh was intersected, hit contains information about the intersect point in local coordinate space.</param>
|
||||
/// <param name="distance">The distance from the ray origin to the intersection point.</param>
|
||||
/// <param name="cullingMode">Which sides of a face are culled when hit testing. Default is back faces are culled.</param>
|
||||
/// <param name="ignore">Optional collection of faces to ignore when raycasting.</param>
|
||||
/// <returns>True if the ray intersects with the mesh, false if not.</returns>
|
||||
internal static bool FaceRaycast(Ray worldRay, ProBuilderMesh mesh, out RaycastHit hit, float distance, CullingMode cullingMode, HashSet<Face> ignore = null)
|
||||
{
|
||||
// Transform ray into model space
|
||||
worldRay.origin -= mesh.transform.position; // Why doesn't worldToLocalMatrix apply translation?
|
||||
worldRay.origin = mesh.transform.worldToLocalMatrix * worldRay.origin;
|
||||
worldRay.direction = mesh.transform.worldToLocalMatrix * worldRay.direction;
|
||||
|
||||
var positions = mesh.positionsInternal;
|
||||
var faces = mesh.facesInternal;
|
||||
|
||||
float OutHitPoint = Mathf.Infinity;
|
||||
int OutHitFace = -1;
|
||||
Vector3 OutNrm = Vector3.zero;
|
||||
|
||||
// Iterate faces, testing for nearest hit to ray origin. Optionally ignores backfaces.
|
||||
for (int i = 0, fc = faces.Length; i < fc; ++i)
|
||||
{
|
||||
if (ignore != null && ignore.Contains(faces[i]))
|
||||
continue;
|
||||
|
||||
int[] indexes = mesh.facesInternal[i].indexesInternal;
|
||||
|
||||
for (int j = 0, ic = indexes.Length; j < ic; j += 3)
|
||||
{
|
||||
Vector3 a = positions[indexes[j + 0]];
|
||||
Vector3 b = positions[indexes[j + 1]];
|
||||
Vector3 c = positions[indexes[j + 2]];
|
||||
|
||||
Vector3 nrm = Vector3.Cross(b - a, c - a);
|
||||
float dot = Vector3.Dot(worldRay.direction, nrm);
|
||||
|
||||
bool skip = false;
|
||||
|
||||
switch (cullingMode)
|
||||
{
|
||||
case CullingMode.Front:
|
||||
if (dot < 0f) skip = true;
|
||||
break;
|
||||
|
||||
case CullingMode.Back:
|
||||
if (dot > 0f) skip = true;
|
||||
break;
|
||||
}
|
||||
|
||||
var dist = 0f;
|
||||
|
||||
Vector3 point;
|
||||
if (!skip && Math.RayIntersectsTriangle(worldRay, a, b, c, out dist, out point))
|
||||
{
|
||||
if (dist > OutHitPoint || dist > distance)
|
||||
continue;
|
||||
|
||||
OutNrm = nrm;
|
||||
OutHitFace = i;
|
||||
OutHitPoint = dist;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hit = new RaycastHit(OutHitPoint,
|
||||
worldRay.GetPoint(OutHitPoint),
|
||||
OutNrm,
|
||||
OutHitFace);
|
||||
|
||||
return OutHitFace > -1;
|
||||
}
|
||||
|
||||
internal static bool FaceRaycastBothCullModes(Ray worldRay, ProBuilderMesh mesh, ref SimpleTuple<Face, Vector3> back, ref SimpleTuple<Face, Vector3> front)
|
||||
{
|
||||
// Transform ray into model space
|
||||
worldRay.origin -= mesh.transform.position; // Why doesn't worldToLocalMatrix apply translation?
|
||||
worldRay.origin = mesh.transform.worldToLocalMatrix * worldRay.origin;
|
||||
worldRay.direction = mesh.transform.worldToLocalMatrix * worldRay.direction;
|
||||
|
||||
var positions = mesh.positionsInternal;
|
||||
var faces = mesh.facesInternal;
|
||||
|
||||
back.item1 = null;
|
||||
front.item1 = null;
|
||||
|
||||
float backDistance = Mathf.Infinity;
|
||||
float frontDistance = Mathf.Infinity;
|
||||
|
||||
// Iterate faces, testing for nearest hit to ray origin. Optionally ignores backfaces.
|
||||
for (int i = 0, fc = faces.Length; i < fc; ++i)
|
||||
{
|
||||
int[] indexes = mesh.facesInternal[i].indexesInternal;
|
||||
|
||||
for (int j = 0, ic = indexes.Length; j < ic; j += 3)
|
||||
{
|
||||
Vector3 a = positions[indexes[j + 0]];
|
||||
Vector3 b = positions[indexes[j + 1]];
|
||||
Vector3 c = positions[indexes[j + 2]];
|
||||
|
||||
float dist;
|
||||
Vector3 point;
|
||||
|
||||
if (Math.RayIntersectsTriangle(worldRay, a, b, c, out dist, out point))
|
||||
{
|
||||
if (dist < backDistance || dist < frontDistance)
|
||||
{
|
||||
Vector3 nrm = Vector3.Cross(b - a, c - a);
|
||||
float dot = Vector3.Dot(worldRay.direction, nrm);
|
||||
|
||||
if (dot < 0f)
|
||||
{
|
||||
if (dist < backDistance)
|
||||
{
|
||||
backDistance = dist;
|
||||
back.item1 = faces[i];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (dist < frontDistance)
|
||||
{
|
||||
frontDistance = dist;
|
||||
front.item1 = faces[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (back.item1 != null)
|
||||
back.item2 = worldRay.GetPoint(backDistance);
|
||||
|
||||
if (front.item1 != null)
|
||||
front.item2 = worldRay.GetPoint(frontDistance);
|
||||
|
||||
return back.item1 != null || front.item1 != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find the all faces intersected by InWorldRay on this pb_Object.
|
||||
/// </summary>
|
||||
/// <param name="InWorldRay">A ray in world space.</param>
|
||||
/// <param name="mesh">The ProBuilder object to raycast against.</param>
|
||||
/// <param name="hits">If the mesh was intersected, hits contains all intersection point RaycastHit information.</param>
|
||||
/// <param name="cullingMode">What sides of triangles does the ray intersect with.</param>
|
||||
/// <param name="ignore">Optional collection of faces to ignore when raycasting.</param>
|
||||
/// <returns>True if the ray intersects with the mesh, false if not.</returns>
|
||||
internal static bool FaceRaycast(
|
||||
Ray InWorldRay,
|
||||
ProBuilderMesh mesh,
|
||||
out List<RaycastHit> hits,
|
||||
CullingMode cullingMode,
|
||||
HashSet<Face> ignore = null)
|
||||
{
|
||||
// Transform ray into model space
|
||||
InWorldRay.origin -= mesh.transform.position; // Why doesn't worldToLocalMatrix apply translation?
|
||||
|
||||
InWorldRay.origin = mesh.transform.worldToLocalMatrix * InWorldRay.origin;
|
||||
InWorldRay.direction = mesh.transform.worldToLocalMatrix * InWorldRay.direction;
|
||||
|
||||
Vector3[] vertices = mesh.positionsInternal;
|
||||
|
||||
hits = new List<RaycastHit>();
|
||||
|
||||
// Iterate faces, testing for nearest hit to ray origin. Optionally ignores backfaces.
|
||||
for (int CurFace = 0; CurFace < mesh.facesInternal.Length; ++CurFace)
|
||||
{
|
||||
if (ignore != null && ignore.Contains(mesh.facesInternal[CurFace]))
|
||||
continue;
|
||||
|
||||
int[] indexes = mesh.facesInternal[CurFace].indexesInternal;
|
||||
|
||||
for (int CurTriangle = 0; CurTriangle < indexes.Length; CurTriangle += 3)
|
||||
{
|
||||
Vector3 a = vertices[indexes[CurTriangle + 0]];
|
||||
Vector3 b = vertices[indexes[CurTriangle + 1]];
|
||||
Vector3 c = vertices[indexes[CurTriangle + 2]];
|
||||
|
||||
var dist = 0f;
|
||||
Vector3 point;
|
||||
|
||||
if (Math.RayIntersectsTriangle(InWorldRay, a, b, c, out dist, out point))
|
||||
{
|
||||
Vector3 nrm = Vector3.Cross(b - a, c - a);
|
||||
|
||||
float dot; // vars used in loop
|
||||
switch (cullingMode)
|
||||
{
|
||||
case CullingMode.Front:
|
||||
dot = Vector3.Dot(InWorldRay.direction, nrm);
|
||||
|
||||
if (dot > 0f)
|
||||
goto case CullingMode.FrontBack;
|
||||
break;
|
||||
|
||||
case CullingMode.Back:
|
||||
dot = Vector3.Dot(InWorldRay.direction, nrm);
|
||||
|
||||
if (dot < 0f)
|
||||
goto case CullingMode.FrontBack;
|
||||
break;
|
||||
|
||||
case CullingMode.FrontBack:
|
||||
hits.Add(new RaycastHit(dist,
|
||||
InWorldRay.GetPoint(dist),
|
||||
nrm,
|
||||
CurFace));
|
||||
break;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return hits.Count > 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Transform a ray from world space to a transform local space.
|
||||
/// </summary>
|
||||
/// <param name="transform"></param>
|
||||
/// <param name="InWorldRay"></param>
|
||||
/// <returns></returns>
|
||||
internal static Ray InverseTransformRay(this Transform transform, Ray InWorldRay)
|
||||
{
|
||||
Vector3 o = InWorldRay.origin;
|
||||
o -= transform.position;
|
||||
o = transform.worldToLocalMatrix * o;
|
||||
Vector3 d = transform.worldToLocalMatrix.MultiplyVector(InWorldRay.direction);
|
||||
return new Ray(o, d);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find the nearest triangle intersected by InWorldRay on this mesh.
|
||||
/// </summary>
|
||||
/// <param name="InWorldRay"></param>
|
||||
/// <param name="hit"></param>
|
||||
/// <param name="distance"></param>
|
||||
/// <returns></returns>
|
||||
internal static bool MeshRaycast(Ray InWorldRay, GameObject gameObject, out RaycastHit hit, float distance = Mathf.Infinity)
|
||||
{
|
||||
var meshFilter = gameObject.GetComponent<MeshFilter>();
|
||||
var mesh = meshFilter != null ? meshFilter.sharedMesh : null;
|
||||
|
||||
if (!mesh)
|
||||
{
|
||||
hit = default(RaycastHit);
|
||||
return false;
|
||||
}
|
||||
|
||||
var transform = gameObject.transform;
|
||||
var ray = transform.InverseTransformRay(InWorldRay);
|
||||
return MeshRaycast(ray, mesh.vertices, mesh.triangles, out hit, distance);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cast a ray (in model space) against a mesh.
|
||||
/// </summary>
|
||||
/// <param name="InRay"></param>
|
||||
/// <param name="mesh"></param>
|
||||
/// <param name="triangles"></param>
|
||||
/// <param name="hit"></param>
|
||||
/// <param name="distance"></param>
|
||||
/// <returns></returns>
|
||||
internal static bool MeshRaycast(Ray InRay, Vector3[] mesh, int[] triangles, out RaycastHit hit, float distance = Mathf.Infinity)
|
||||
{
|
||||
// float dot; // vars used in loop
|
||||
float hitDistance = Mathf.Infinity;
|
||||
Vector3 hitNormal = Vector3.zero; // vars used in loop
|
||||
Vector3 a, b, c, n = Vector3.zero;
|
||||
int hitFace = -1;
|
||||
Vector3 o = InRay.origin, d = InRay.direction;
|
||||
|
||||
// Iterate faces, testing for nearest hit to ray origin.
|
||||
for (int CurTri = 0; CurTri < triangles.Length; CurTri += 3)
|
||||
{
|
||||
a = mesh[triangles[CurTri + 0]];
|
||||
b = mesh[triangles[CurTri + 1]];
|
||||
c = mesh[triangles[CurTri + 2]];
|
||||
|
||||
if (Math.RayIntersectsTriangle2(o, d, a, b, c, ref distance, ref n))
|
||||
{
|
||||
if(distance < hitDistance)
|
||||
{
|
||||
hitFace = CurTri / 3;
|
||||
hitDistance = distance;
|
||||
hitNormal = n;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hit = new RaycastHit(hitDistance,
|
||||
InRay.GetPoint(hitDistance),
|
||||
hitNormal,
|
||||
hitFace);
|
||||
|
||||
return hitFace > -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if this point in world space is occluded by a triangle on this object.
|
||||
/// </summary>
|
||||
/// <remarks>This is very slow, do not use.</remarks>
|
||||
/// <param name="cam"></param>
|
||||
/// <param name="pb"></param>
|
||||
/// <param name="worldPoint"></param>
|
||||
/// <returns></returns>
|
||||
internal static bool PointIsOccluded(Camera cam, ProBuilderMesh pb, Vector3 worldPoint)
|
||||
{
|
||||
Vector3 dir = (cam.transform.position - worldPoint).normalized;
|
||||
|
||||
// move the point slightly towards the camera to avoid colliding with its own triangle
|
||||
Ray ray = new Ray(worldPoint + dir * .0001f, dir);
|
||||
|
||||
RaycastHit hit;
|
||||
|
||||
return FaceRaycast(ray, pb, out hit, Vector3.Distance(cam.transform.position, worldPoint), CullingMode.Front);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Collects coincident vertices and returns a rotation calculated from the average normal and bitangent.
|
||||
/// </summary>
|
||||
/// <param name="mesh">The target mesh.</param>
|
||||
/// <param name="indices">Vertex indices to consider in the rotation calculations.</param>
|
||||
/// <returns>A rotation calculated from the average normal of each vertex.</returns>
|
||||
public static Quaternion GetRotation(ProBuilderMesh mesh, IEnumerable<int> indices)
|
||||
{
|
||||
if (!mesh.HasArrays(MeshArrays.Normal))
|
||||
Normals.CalculateNormals(mesh);
|
||||
|
||||
if (!mesh.HasArrays(MeshArrays.Tangent))
|
||||
Normals.CalculateTangents(mesh);
|
||||
|
||||
var normals = mesh.normalsInternal;
|
||||
var tangents = mesh.tangentsInternal;
|
||||
|
||||
var nrm = Vector3.zero;
|
||||
var tan = Vector4.zero;
|
||||
float count = 0;
|
||||
|
||||
foreach (var index in indices)
|
||||
{
|
||||
var n = normals[index];
|
||||
var t = tangents[index];
|
||||
|
||||
nrm.x += n.x;
|
||||
nrm.y += n.y;
|
||||
nrm.z += n.z;
|
||||
|
||||
tan.x += t.x;
|
||||
tan.y += t.y;
|
||||
tan.z += t.z;
|
||||
tan.w += t.w;
|
||||
|
||||
count++;
|
||||
}
|
||||
|
||||
nrm.x /= count;
|
||||
nrm.y /= count;
|
||||
nrm.z /= count;
|
||||
|
||||
tan.x /= count;
|
||||
tan.y /= count;
|
||||
tan.z /= count;
|
||||
tan.w /= count;
|
||||
|
||||
if (nrm == Vector3.zero || tan == Vector4.zero)
|
||||
return mesh.transform.rotation;
|
||||
|
||||
var bit = Vector3.Cross(nrm, tan * tan.w);
|
||||
|
||||
return mesh.transform.rotation * Quaternion.LookRotation(nrm, bit);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a rotation suitable for orienting a handle or gizmo relative to the Face selection.
|
||||
/// </summary>
|
||||
/// <param name="mesh">The target mesh.</param>
|
||||
/// <param name="orientation">The type of <see cref="HandleOrientation"/> to calculate.</param>
|
||||
/// <param name="faces">Which faces to consider in the rotation calculations. This is only used when the
|
||||
/// <see cref="HandleOrientation"/> is set to <see cref="HandleOrientation.ActiveElement"/>.</param>
|
||||
/// <returns>A rotation appropriate to the orientation and element selection.</returns>
|
||||
public static Quaternion GetFaceRotation(ProBuilderMesh mesh, HandleOrientation orientation, IEnumerable<Face> faces)
|
||||
{
|
||||
if (mesh == null)
|
||||
return Quaternion.identity;
|
||||
|
||||
switch (orientation)
|
||||
{
|
||||
case HandleOrientation.ActiveElement:
|
||||
// Intentionally not using coincident vertices here. We want the normal of just the face, not an
|
||||
// average of it's neighbors.
|
||||
return GetFaceRotation(mesh, faces.Last());
|
||||
|
||||
case HandleOrientation.ActiveObject:
|
||||
return mesh.transform.rotation;
|
||||
|
||||
default:
|
||||
return Quaternion.identity;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the rotation of a <see cref="Face"/> in world space.
|
||||
/// </summary>
|
||||
/// <param name="mesh">The mesh that the face belongs to.</param>
|
||||
/// <param name="face">The face you want to calculate the rotation for.</param>
|
||||
/// <returns>The rotation of the face in world space coordinates.</returns>
|
||||
public static Quaternion GetFaceRotation(ProBuilderMesh mesh, Face face)
|
||||
{
|
||||
if (mesh == null)
|
||||
return Quaternion.identity;
|
||||
|
||||
if (face == null)
|
||||
return mesh.transform.rotation;
|
||||
|
||||
// Intentionally not using coincident vertices here. We want the normal of just the face, not an
|
||||
// average of it's neighbors.
|
||||
Normal nrm = Math.NormalTangentBitangent(mesh, face);
|
||||
|
||||
if (nrm.normal == Vector3.zero || nrm.bitangent == Vector3.zero)
|
||||
return mesh.transform.rotation;
|
||||
|
||||
return mesh.transform.rotation * Quaternion.LookRotation(nrm.normal, nrm.bitangent);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a rotation suitable for orienting a handle or gizmo relative to the Edge selection.
|
||||
/// </summary>
|
||||
/// <param name="mesh">The target mesh.</param>
|
||||
/// <param name="orientation">The type of <see cref="HandleOrientation"/> to calculate.</param>
|
||||
/// <param name="edges">Which edges to consider in the rotation calculations. This is only used when the
|
||||
/// <see cref="HandleOrientation"/> is set to <see cref="HandleOrientation.ActiveElement"/>.</param>
|
||||
/// <returns>A rotation appropriate to the orientation and element selection.</returns>
|
||||
public static Quaternion GetEdgeRotation(ProBuilderMesh mesh, HandleOrientation orientation, IEnumerable<Edge> edges)
|
||||
{
|
||||
if (mesh == null)
|
||||
return Quaternion.identity;
|
||||
|
||||
switch (orientation)
|
||||
{
|
||||
case HandleOrientation.ActiveElement:
|
||||
// Getting an average of the edge normals isn't very helpful in real world uses, so we just use the
|
||||
// first selected edge for orientation.
|
||||
// This function accepts an enumerable because in the future we may want to do something more
|
||||
// sophisticated, and it's convenient because selections are stored as collections.
|
||||
return GetEdgeRotation(mesh, edges.Last());
|
||||
|
||||
case HandleOrientation.ActiveObject:
|
||||
return mesh.transform.rotation;
|
||||
|
||||
default:
|
||||
return Quaternion.identity;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the rotation of an <see cref="Edge"/> in world space.
|
||||
/// </summary>
|
||||
/// <param name="mesh">The mesh that edge belongs to.</param>
|
||||
/// <param name="edge">The edge you want to calculate the rotation for.</param>
|
||||
/// <returns>The rotation of the edge in world space coordinates.</returns>
|
||||
public static Quaternion GetEdgeRotation(ProBuilderMesh mesh, Edge edge)
|
||||
{
|
||||
if (mesh == null)
|
||||
return Quaternion.identity;
|
||||
|
||||
return GetFaceRotation(mesh, EdgeUtility.GetFace(mesh, edge));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a rotation suitable for orienting a handle or gizmo relative to the Vertex selection.
|
||||
/// </summary>
|
||||
/// <param name="mesh">The target mesh.</param>
|
||||
/// <param name="orientation">The type of <see cref="HandleOrientation"/> to calculate.</param>
|
||||
/// <param name="vertices">Array of <see cref="Vertex"/> indices pointing to the vertices to consider in the rotation calculations. This is only used when the
|
||||
/// <see cref="HandleOrientation"/> is set to <see cref="HandleOrientation.ActiveElement"/>.</param>
|
||||
/// <returns>A rotation appropriate to the orientation and element selection.</returns>
|
||||
public static Quaternion GetVertexRotation(ProBuilderMesh mesh, HandleOrientation orientation, IEnumerable<int> vertices)
|
||||
{
|
||||
if (mesh == null)
|
||||
return Quaternion.identity;
|
||||
|
||||
switch (orientation)
|
||||
{
|
||||
case HandleOrientation.ActiveElement:
|
||||
if (mesh.selectedVertexCount < 1)
|
||||
goto case HandleOrientation.ActiveObject;
|
||||
return GetRotation(mesh, vertices);
|
||||
|
||||
case HandleOrientation.ActiveObject:
|
||||
return mesh.transform.rotation;
|
||||
|
||||
default:
|
||||
return Quaternion.identity;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the rotation of a vertex in world space.
|
||||
/// </summary>
|
||||
/// <param name="mesh">The mesh that the vertex belongs to.</param>
|
||||
/// <param name="vertex">The index that points to the vertex to calculate the rotation for.</param>
|
||||
/// <returns>The rotation of a vertex normal in world space coordinates.</returns>
|
||||
public static Quaternion GetVertexRotation(ProBuilderMesh mesh, int vertex)
|
||||
{
|
||||
if (mesh == null)
|
||||
return Quaternion.identity;
|
||||
|
||||
if (vertex < 0)
|
||||
return mesh.transform.rotation;
|
||||
|
||||
return GetRotation(mesh, new int[] { vertex });
|
||||
}
|
||||
|
||||
internal static Vector3 GetActiveElementPosition(ProBuilderMesh mesh, IEnumerable<Face> faces)
|
||||
{
|
||||
return mesh.transform.TransformPoint(Math.GetBounds(mesh.positionsInternal, faces.Last().distinctIndexesInternal).center);
|
||||
}
|
||||
|
||||
internal static Vector3 GetActiveElementPosition(ProBuilderMesh mesh, IEnumerable<Edge> edges)
|
||||
{
|
||||
var edge = edges.Last();
|
||||
return mesh.transform.TransformPoint(Math.GetBounds(mesh.positionsInternal, new int[] { edge.a, edge.b }).center);
|
||||
}
|
||||
|
||||
internal static Vector3 GetActiveElementPosition(ProBuilderMesh mesh, IEnumerable<int> vertices)
|
||||
{
|
||||
return mesh.transform.TransformPoint(mesh.positionsInternal[vertices.First()]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3e8bafb60e2ac40ef92c924f855c65b3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,15 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEngine.ProBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for objects that contain a set of default values. Used by generated scriptable objects.
|
||||
/// </summary>
|
||||
interface IHasDefault
|
||||
{
|
||||
/// <summary>
|
||||
/// Set this object to use default values.
|
||||
/// </summary>
|
||||
void SetDefaultValues();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cd9b9131766d54225a24d2221d1f49b5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,73 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEngine.ProBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Vertex positions are sorted as integers to avoid floating point precision errors.
|
||||
/// </summary>
|
||||
struct IntVec2 : System.IEquatable<IntVec2>
|
||||
{
|
||||
public Vector2 value;
|
||||
|
||||
public float x { get { return value.x; } }
|
||||
public float y { get { return value.y; } }
|
||||
|
||||
public IntVec2(Vector2 vector)
|
||||
{
|
||||
this.value = vector;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("({0:F2}, {1:F2})", x, y);
|
||||
}
|
||||
|
||||
public static bool operator==(IntVec2 a, IntVec2 b)
|
||||
{
|
||||
return a.Equals(b);
|
||||
}
|
||||
|
||||
public static bool operator!=(IntVec2 a, IntVec2 b)
|
||||
{
|
||||
return !(a == b);
|
||||
}
|
||||
|
||||
public bool Equals(IntVec2 p)
|
||||
{
|
||||
return round(x) == round(p.x) &&
|
||||
round(y) == round(p.y);
|
||||
}
|
||||
|
||||
public bool Equals(Vector2 p)
|
||||
{
|
||||
return round(x) == round(p.x) &&
|
||||
round(y) == round(p.y);
|
||||
}
|
||||
|
||||
public override bool Equals(System.Object b)
|
||||
{
|
||||
return (b is IntVec2 && (this.Equals((IntVec2)b))) ||
|
||||
(b is Vector2 && this.Equals((Vector2)b));
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return VectorHash.GetHashCode(value);
|
||||
}
|
||||
|
||||
private static int round(float v)
|
||||
{
|
||||
return System.Convert.ToInt32(v * VectorHash.FltCompareResolution);
|
||||
}
|
||||
|
||||
public static implicit operator Vector2(IntVec2 p)
|
||||
{
|
||||
return p.value;
|
||||
}
|
||||
|
||||
public static implicit operator IntVec2(Vector2 p)
|
||||
{
|
||||
return new IntVec2(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9f21d9e5e308b409f9173967b37d19be
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,76 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEngine.ProBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Vertex positions are sorted as integers to avoid floating point precision errors.
|
||||
/// </summary>
|
||||
struct IntVec3 : System.IEquatable<IntVec3>
|
||||
{
|
||||
public Vector3 value;
|
||||
|
||||
public float x { get { return value.x; } }
|
||||
public float y { get { return value.y; } }
|
||||
public float z { get { return value.z; } }
|
||||
|
||||
public IntVec3(Vector3 vector)
|
||||
{
|
||||
this.value = vector;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("({0:F2}, {1:F2}, {2:F2})", x, y, z);
|
||||
}
|
||||
|
||||
public static bool operator==(IntVec3 a, IntVec3 b)
|
||||
{
|
||||
return a.Equals(b);
|
||||
}
|
||||
|
||||
public static bool operator!=(IntVec3 a, IntVec3 b)
|
||||
{
|
||||
return !(a == b);
|
||||
}
|
||||
|
||||
public bool Equals(IntVec3 p)
|
||||
{
|
||||
return round(x) == round(p.x) &&
|
||||
round(y) == round(p.y) &&
|
||||
round(z) == round(p.z);
|
||||
}
|
||||
|
||||
public bool Equals(Vector3 p)
|
||||
{
|
||||
return round(x) == round(p.x) &&
|
||||
round(y) == round(p.y) &&
|
||||
round(z) == round(p.z);
|
||||
}
|
||||
|
||||
public override bool Equals(System.Object b)
|
||||
{
|
||||
return (b is IntVec3 && (this.Equals((IntVec3)b))) ||
|
||||
(b is Vector3 && this.Equals((Vector3)b));
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return VectorHash.GetHashCode(value);
|
||||
}
|
||||
|
||||
private static int round(float v)
|
||||
{
|
||||
return System.Convert.ToInt32(v * VectorHash.FltCompareResolution);
|
||||
}
|
||||
|
||||
public static implicit operator Vector3(IntVec3 p)
|
||||
{
|
||||
return p.value;
|
||||
}
|
||||
|
||||
public static implicit operator IntVec3(Vector3 p)
|
||||
{
|
||||
return new IntVec3(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 071d390610ea548df87a9c03c370ced2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,79 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEngine.ProBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Vertex positions are sorted as integers to avoid floating point precision errors.
|
||||
/// </summary>
|
||||
struct IntVec4 : System.IEquatable<IntVec4>
|
||||
{
|
||||
public Vector4 value;
|
||||
|
||||
public float x { get { return value.x; } }
|
||||
public float y { get { return value.y; } }
|
||||
public float z { get { return value.z; } }
|
||||
public float w { get { return value.w; } }
|
||||
|
||||
public IntVec4(Vector4 vector)
|
||||
{
|
||||
this.value = vector;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("({0:F2}, {1:F2}, {2:F2}, {3:F2})", x, y, z, w);
|
||||
}
|
||||
|
||||
public static bool operator==(IntVec4 a, IntVec4 b)
|
||||
{
|
||||
return a.Equals(b);
|
||||
}
|
||||
|
||||
public static bool operator!=(IntVec4 a, IntVec4 b)
|
||||
{
|
||||
return !(a == b);
|
||||
}
|
||||
|
||||
public bool Equals(IntVec4 p)
|
||||
{
|
||||
return round(x) == round(p.x) &&
|
||||
round(y) == round(p.y) &&
|
||||
round(z) == round(p.z) &&
|
||||
round(w) == round(p.w);
|
||||
}
|
||||
|
||||
public bool Equals(Vector4 p)
|
||||
{
|
||||
return round(x) == round(p.x) &&
|
||||
round(y) == round(p.y) &&
|
||||
round(z) == round(p.z) &&
|
||||
round(w) == round(p.w);
|
||||
}
|
||||
|
||||
public override bool Equals(System.Object b)
|
||||
{
|
||||
return (b is IntVec4 && (this.Equals((IntVec4)b))) ||
|
||||
(b is Vector4 && this.Equals((Vector4)b));
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return VectorHash.GetHashCode(value);
|
||||
}
|
||||
|
||||
private static int round(float v)
|
||||
{
|
||||
return System.Convert.ToInt32(v * VectorHash.FltCompareResolution);
|
||||
}
|
||||
|
||||
public static implicit operator Vector4(IntVec4 p)
|
||||
{
|
||||
return p.value;
|
||||
}
|
||||
|
||||
public static implicit operator IntVec4(Vector4 p)
|
||||
{
|
||||
return new IntVec4(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 36dfd625ac38b4b62a04264b3819dc42
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,173 @@
|
||||
using UnityEngine;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine.Assertions;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor.SceneManagement;
|
||||
#endif
|
||||
|
||||
namespace UnityEngine.ProBuilder
|
||||
{
|
||||
static class InternalUtility
|
||||
{
|
||||
// @todo
|
||||
public static T[] GetComponents<T>(GameObject go) where T : Component
|
||||
{
|
||||
return go.transform.GetComponentsInChildren<T>();
|
||||
}
|
||||
|
||||
// @todo
|
||||
public static T[] GetComponents<T>(this IEnumerable<Transform> transforms) where T : Component
|
||||
{
|
||||
List<T> c = new List<T>();
|
||||
foreach (Transform t in transforms)
|
||||
c.AddRange(t.GetComponentsInChildren<T>());
|
||||
return c.ToArray() as T[];
|
||||
}
|
||||
|
||||
public static GameObject EmptyGameObjectWithTransform(Transform t)
|
||||
{
|
||||
GameObject go = new GameObject();
|
||||
go.transform.localPosition = t.localPosition;
|
||||
go.transform.localRotation = t.localRotation;
|
||||
go.transform.localScale = t.localScale;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
StageUtility.PlaceGameObjectInCurrentStage(go);
|
||||
#endif
|
||||
|
||||
return go;
|
||||
}
|
||||
|
||||
public static GameObject MeshGameObjectWithTransform(string name, Transform t, Mesh mesh, Material mat, bool inheritParent)
|
||||
{
|
||||
GameObject go = InternalUtility.EmptyGameObjectWithTransform(t);
|
||||
go.name = name;
|
||||
go.AddComponent<MeshFilter>().sharedMesh = mesh;
|
||||
go.AddComponent<MeshRenderer>().sharedMaterial = mat;
|
||||
go.hideFlags = HideFlags.HideAndDontSave;
|
||||
|
||||
if (inheritParent)
|
||||
go.transform.SetParent(t.parent, false);
|
||||
|
||||
return go;
|
||||
}
|
||||
|
||||
public static T NextEnumValue<T>(this T current) where T : IConvertible
|
||||
{
|
||||
Assert.IsTrue(current is Enum);
|
||||
|
||||
var values = Enum.GetValues(current.GetType());
|
||||
|
||||
for (int i = 0, c = values.Length; i < c; i++)
|
||||
if (current.Equals(values.GetValue((i))))
|
||||
return (T)values.GetValue((i + 1) % c);
|
||||
|
||||
return current;
|
||||
}
|
||||
|
||||
public static string ControlKeyString(char character)
|
||||
{
|
||||
if (character == PreferenceKeys.CMD_SUPER)
|
||||
return "Control";
|
||||
else if (character == PreferenceKeys.CMD_SHIFT)
|
||||
return "Shift";
|
||||
else if (character == PreferenceKeys.CMD_OPTION)
|
||||
return "Alt";
|
||||
else if (character == PreferenceKeys.CMD_ALT)
|
||||
return "Alt";
|
||||
else if (character == PreferenceKeys.CMD_DELETE)
|
||||
#if UNITY_EDITOR_WIN
|
||||
return "Backspace";
|
||||
#else
|
||||
return "Delete";
|
||||
#endif
|
||||
else
|
||||
return character.ToString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to parse a color from string input.
|
||||
*/
|
||||
public static bool TryParseColor(string value, ref Color col)
|
||||
{
|
||||
string valid = "01234567890.,";
|
||||
value = new string(value.Where(c => valid.Contains(c)).ToArray());
|
||||
string[] rgba = value.Split(',');
|
||||
|
||||
if (rgba.Length < 4)
|
||||
return false;
|
||||
|
||||
try
|
||||
{
|
||||
float r = float.Parse(rgba[0]);
|
||||
float g = float.Parse(rgba[1]);
|
||||
float b = float.Parse(rgba[2]);
|
||||
float a = float.Parse(rgba[3]);
|
||||
|
||||
col.r = r;
|
||||
col.g = g;
|
||||
col.b = b;
|
||||
col.a = a;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a reference to an existing component, or add a new component if one does not already exist.
|
||||
/// </summary>
|
||||
public static T DemandComponent<T>(this Component component) where T : Component
|
||||
{
|
||||
return component.gameObject.DemandComponent<T>();
|
||||
}
|
||||
|
||||
public static T DemandComponent<T>(this GameObject gameObject) where T : Component
|
||||
{
|
||||
T component;
|
||||
if (!gameObject.TryGetComponent<T>(out component))
|
||||
component = gameObject.AddComponent<T>();
|
||||
return component;
|
||||
}
|
||||
|
||||
#if UNITY_6000_4_OR_NEWER
|
||||
/// <summary>
|
||||
/// Returns the <see cref="EntityId"/> identifier for the given <see cref="UnityEngine.Object"/>.
|
||||
/// This method abstracts the Unity version differences between <c>GetEntityId()</c>
|
||||
/// (available in Unity 6000.4 or newer) and <c>GetInstanceID()</c> in older Unity versions.
|
||||
///
|
||||
/// Use this method to obtain an object identifier that is compatible with APIs expecting
|
||||
/// <see cref="EntityId"/> in newer Unity versions, such as
|
||||
/// <see cref="UnityEditor.AssetDatabase.GetAssetPath(EntityId)"/>.
|
||||
/// </summary>
|
||||
/// <param name="o">The Unity object to get an identifier for.</param>
|
||||
/// <returns>The object's unique <see cref="EntityId"/>.</returns>
|
||||
internal static EntityId GetObjectId(this Object o)
|
||||
{
|
||||
return o.GetEntityId();
|
||||
}
|
||||
#else
|
||||
/// <summary>
|
||||
/// Returns the integer identifier for the given <see cref="UnityEngine.Object"/>.
|
||||
/// This method abstracts the Unity version differences between <c>GetEntityId()</c>
|
||||
/// (available in Unity 6000.4 or newer) and <c>GetInstanceID()</c> in older Unity versions.
|
||||
///
|
||||
/// Use this method to obtain an object identifier that is compatible with APIs expecting
|
||||
/// an integer in older Unity versions, such as
|
||||
/// <see cref="UnityEditor.AssetDatabase.GetAssetPath(int)"/>.
|
||||
/// </summary>
|
||||
/// <param name="o">The Unity object to get an identifier for.</param>
|
||||
/// <returns>The object's unique integer identifier.</returns>
|
||||
internal static int GetObjectId(this Object o)
|
||||
{
|
||||
return o.GetInstanceID();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9e11b000caeb5451a8d9160606edb8e5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,297 @@
|
||||
using UnityEngine;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace UnityEngine.ProBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Describes the various states of chatty-ness.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
enum LogLevel
|
||||
{
|
||||
None = 0x0,
|
||||
Error = 0x1,
|
||||
Warning = 0x2,
|
||||
Info = 0x4,
|
||||
Default = Error | Warning,
|
||||
All = 0xFF
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Where the pb_Log writes to (default is Unity Console).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If logging to a File, the pb_Log.outputFile must be set.
|
||||
/// You may log to one or multiple output sources.
|
||||
/// </remarks>
|
||||
[Flags]
|
||||
enum LogOutput
|
||||
{
|
||||
None = 0x0,
|
||||
Console = 0x1,
|
||||
File = 0x2,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Debug log replacement.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// IMPORTANT - pb_LogEditor initializes this class from the Editor side (so preferences can be accessed)!
|
||||
/// </remarks>
|
||||
static class Log
|
||||
{
|
||||
public const string k_ProBuilderLogFileName = "ProBuilderLog.txt";
|
||||
|
||||
// Retain a stack of previous log levels.
|
||||
static Stack<LogLevel> s_logStack = new Stack<LogLevel>();
|
||||
|
||||
// Current log level.
|
||||
static LogLevel s_LogLevel = LogLevel.All;
|
||||
|
||||
// Where to write log strings.
|
||||
static LogOutput s_Output = LogOutput.Console;
|
||||
|
||||
// Path to the log file.
|
||||
static string s_LogFilePath = k_ProBuilderLogFileName;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
|
||||
static void ResetStaticsOnLoad()
|
||||
{
|
||||
s_logStack.Clear();
|
||||
s_LogLevel = LogLevel.All;
|
||||
s_Output = LogOutput.Console;
|
||||
s_LogFilePath = k_ProBuilderLogFileName;
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Push the current log level in the stack. See also PopLogLevel.
|
||||
/// </summary>
|
||||
/// <param name="level"></param>
|
||||
public static void PushLogLevel(LogLevel level)
|
||||
{
|
||||
s_logStack.Push(s_LogLevel);
|
||||
s_LogLevel = level;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pop the current log level in the stack. See also PushLogLevel.
|
||||
/// </summary>
|
||||
public static void PopLogLevel()
|
||||
{
|
||||
s_LogLevel = s_logStack.Pop();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the log level without modifying the stack.
|
||||
/// </summary>
|
||||
/// <param name="level"></param>
|
||||
public static void SetLogLevel(LogLevel level)
|
||||
{
|
||||
s_LogLevel = level;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the output destination for logs.
|
||||
/// If output is file, make sure to also set the log file path (otherwise it defaults to ProBuilderLog.txt in project directory).
|
||||
/// </summary>
|
||||
/// <param name="output"></param>
|
||||
public static void SetOutput(LogOutput output)
|
||||
{
|
||||
s_Output = output;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the path of the log file that pb_Log writes messages to.
|
||||
/// </summary>
|
||||
/// <param name="path"></param>
|
||||
public static void SetLogFile(string path)
|
||||
{
|
||||
s_LogFilePath = path;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Output a debug message.
|
||||
/// </summary>
|
||||
/// <remarks>These should not be committed to trunk.</remarks>
|
||||
/// <param name="value"></param>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
[Conditional("DEBUG")]
|
||||
public static void Debug<T>(T value)
|
||||
{
|
||||
Debug(value.ToString());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Output a debug message.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// These should not be committed to trunk.
|
||||
/// </remarks>
|
||||
/// <param name="message"></param>
|
||||
[Conditional("DEBUG")]
|
||||
public static void Debug(string message)
|
||||
{
|
||||
DoPrint(message, LogType.Log);
|
||||
}
|
||||
|
||||
[Conditional("DEBUG")]
|
||||
public static void Debug(string format, params object[] values)
|
||||
{
|
||||
Debug(string.Format(format, values));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Output an informational message.
|
||||
/// </summary>
|
||||
/// <param name="format"></param>
|
||||
/// <param name="values"></param>
|
||||
public static void Info(string format, params object[] values)
|
||||
{
|
||||
Info(string.Format(format, values));
|
||||
}
|
||||
|
||||
public static void Info(string message)
|
||||
{
|
||||
if ((s_LogLevel & LogLevel.Info) > 0)
|
||||
DoPrint(message, LogType.Log);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Output a warning message.
|
||||
/// </summary>
|
||||
/// <param name="format"></param>
|
||||
/// <param name="values"></param>
|
||||
public static void Warning(string format, params object[] values)
|
||||
{
|
||||
Warning(string.Format(format, values));
|
||||
}
|
||||
|
||||
public static void Warning(string message)
|
||||
{
|
||||
if ((s_LogLevel & LogLevel.Warning) > 0)
|
||||
DoPrint(message, LogType.Warning);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Output an error message.
|
||||
/// </summary>
|
||||
/// <param name="format"></param>
|
||||
/// <param name="values"></param>
|
||||
public static void Error(string format, params object[] values)
|
||||
{
|
||||
Error(string.Format(format, values));
|
||||
}
|
||||
|
||||
public static void Error(string message)
|
||||
{
|
||||
if ((s_LogLevel & LogLevel.Error) > 0)
|
||||
DoPrint(message, LogType.Error);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ConsolePro3 specific functionality - update a single log continuously.
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="value"></param>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <typeparam name="K"></typeparam>
|
||||
[Conditional("CONSOLE_PRO_ENABLED")]
|
||||
internal static void Watch<T, K>(T key, K value)
|
||||
{
|
||||
UnityEngine.Debug.Log(string.Format("{0} : {1}\nCPAPI:{{\"cmd\":\"Watch\" \"name\":\"{0}\"}}", key.ToString(), value.ToString()));
|
||||
}
|
||||
|
||||
static void DoPrint(string message, LogType type)
|
||||
{
|
||||
if ((s_Output & LogOutput.Console) > 0)
|
||||
PrintToConsole(message, type);
|
||||
|
||||
if ((s_Output & LogOutput.File) > 0)
|
||||
PrintToFile(message, s_LogFilePath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Print a message to a file.
|
||||
/// </summary>
|
||||
/// <param name="message"></param>
|
||||
/// <param name="path"></param>
|
||||
static void PrintToFile(string message, string path)
|
||||
{
|
||||
if (string.IsNullOrEmpty(path))
|
||||
return;
|
||||
|
||||
string full_path = Path.GetFullPath(path);
|
||||
|
||||
if (string.IsNullOrEmpty(full_path))
|
||||
{
|
||||
Log.PrintToConsole("m_LogFilePath bad: " + full_path);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!File.Exists(full_path))
|
||||
{
|
||||
string directory = Path.GetDirectoryName(full_path);
|
||||
|
||||
if (string.IsNullOrEmpty(directory))
|
||||
{
|
||||
Log.PrintToConsole("m_LogFilePath bad: " + full_path);
|
||||
return;
|
||||
}
|
||||
|
||||
Directory.CreateDirectory(directory);
|
||||
|
||||
using (StreamWriter sw = File.CreateText(full_path))
|
||||
{
|
||||
sw.WriteLine(message);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
using (StreamWriter sw = File.AppendText(full_path))
|
||||
{
|
||||
// sw.WriteLine();
|
||||
sw.WriteLine(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delete the log file if it exists.
|
||||
/// </summary>
|
||||
public static void ClearLogFile()
|
||||
{
|
||||
if (File.Exists(s_LogFilePath))
|
||||
File.Delete(s_LogFilePath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Print a message to the Unity console.
|
||||
/// </summary>
|
||||
/// <param name="message"></param>
|
||||
/// <param name="type"></param>
|
||||
static void PrintToConsole(string message, LogType type = LogType.Log)
|
||||
{
|
||||
if (type == LogType.Log)
|
||||
UnityEngine.Debug.Log(message);
|
||||
else if (type == LogType.Warning)
|
||||
UnityEngine.Debug.LogWarning(message);
|
||||
else if (type == LogType.Error)
|
||||
UnityEngine.Debug.LogError(message);
|
||||
else if (type == LogType.Assert)
|
||||
UnityEngine.Debug.LogAssertion(message);
|
||||
else
|
||||
UnityEngine.Debug.Log(message);
|
||||
}
|
||||
|
||||
internal static void NotNull<T>(T obj, string message)
|
||||
{
|
||||
if (obj == null)
|
||||
throw new ArgumentNullException(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a91947f03a49147a49e5f05ea5737edc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,36 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEngine.ProBuilder
|
||||
{
|
||||
static class MaterialUtility
|
||||
{
|
||||
internal static List<Material> s_MaterialArray = new List<Material>();
|
||||
|
||||
#if UNITY_EDITOR
|
||||
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
|
||||
static void ResetStaticsOnLoad()
|
||||
{
|
||||
s_MaterialArray.Clear();
|
||||
}
|
||||
#endif
|
||||
|
||||
internal static int GetMaterialCount(Renderer renderer)
|
||||
{
|
||||
s_MaterialArray.Clear();
|
||||
renderer.GetSharedMaterials(s_MaterialArray);
|
||||
return s_MaterialArray.Count;
|
||||
}
|
||||
|
||||
internal static Material GetSharedMaterial(Renderer renderer, int index)
|
||||
{
|
||||
s_MaterialArray.Clear();
|
||||
renderer.GetSharedMaterials(s_MaterialArray);
|
||||
var count = s_MaterialArray.Count;
|
||||
if (count < 1)
|
||||
return null;
|
||||
return s_MaterialArray[Math.Clamp(index, 0, count - 1)];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a419cef8f67fa48b8a2420ecc4ea4936
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0b49a83783b3e5944b25d6c66eaa11e9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,30 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
|
||||
namespace UnityEngine.ProBuilder
|
||||
{
|
||||
sealed class MeshHandle
|
||||
{
|
||||
Transform m_Transform;
|
||||
Mesh m_Mesh;
|
||||
|
||||
public Mesh mesh
|
||||
{
|
||||
get { return m_Mesh; }
|
||||
}
|
||||
|
||||
public MeshHandle(Transform transform, Mesh mesh)
|
||||
{
|
||||
m_Transform = transform;
|
||||
m_Mesh = mesh;
|
||||
}
|
||||
|
||||
public void DrawMeshNow(int submeshIndex)
|
||||
{
|
||||
if (m_Transform == null || m_Mesh == null)
|
||||
return;
|
||||
|
||||
Graphics.DrawMeshNow(m_Mesh, m_Transform.localToWorldMatrix, submeshIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1113544478cee6a4caafac0b3b7a06f1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,322 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine.Rendering;
|
||||
|
||||
namespace UnityEngine.ProBuilder
|
||||
{
|
||||
static class MeshHandles
|
||||
{
|
||||
static List<Vector3> s_Vector2List = new List<Vector3>();
|
||||
static List<Vector3> s_Vector3List = new List<Vector3>();
|
||||
static List<Vector4> s_Vector4List = new List<Vector4>();
|
||||
static List<int> s_IndexList = new List<int>();
|
||||
static List<int> s_SharedVertexIndexList = new List<int>();
|
||||
|
||||
static readonly Vector2 k_Billboard0 = new Vector2(-1f, -1f);
|
||||
static readonly Vector2 k_Billboard1 = new Vector2(-1f, 1f);
|
||||
static readonly Vector2 k_Billboard2 = new Vector2( 1f, -1f);
|
||||
static readonly Vector2 k_Billboard3 = new Vector2( 1f, 1f);
|
||||
|
||||
#if UNITY_EDITOR
|
||||
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
|
||||
static void ResetStaticsOnLoad()
|
||||
{
|
||||
s_Vector2List.Clear();
|
||||
s_Vector3List.Clear();
|
||||
s_Vector4List.Clear();
|
||||
s_IndexList.Clear();
|
||||
s_SharedVertexIndexList.Clear();
|
||||
}
|
||||
#endif
|
||||
|
||||
internal static void CreateFaceMesh(ProBuilderMesh mesh, Mesh target)
|
||||
{
|
||||
target.Clear();
|
||||
target.vertices = mesh.positionsInternal;
|
||||
target.triangles = mesh.selectedFacesInternal.SelectMany(x => x.indexes).ToArray();
|
||||
}
|
||||
|
||||
internal static void CreateFaceMeshFromFaces(ProBuilderMesh mesh, IList<Face> faces, Mesh target)
|
||||
{
|
||||
target.Clear();
|
||||
target.vertices = mesh.positionsInternal;
|
||||
target.triangles = faces.SelectMany(x => x.indexes).ToArray();
|
||||
}
|
||||
|
||||
internal static void CreateEdgeMesh(ProBuilderMesh mesh, Mesh target)
|
||||
{
|
||||
int edgeCount = 0;
|
||||
int faceCount = mesh.faceCount;
|
||||
|
||||
for (int i = 0; i < faceCount; i++)
|
||||
edgeCount += mesh.facesInternal[i].edgesInternal.Length;
|
||||
|
||||
s_IndexList.Clear();
|
||||
s_IndexList.Capacity = edgeCount * 2;
|
||||
|
||||
int edgeIndex = 0;
|
||||
|
||||
for (int i = 0; i < faceCount && edgeIndex < edgeCount; i++)
|
||||
{
|
||||
for (int n = 0; n < mesh.facesInternal[i].edgesInternal.Length && edgeIndex < edgeCount; n++)
|
||||
{
|
||||
var edge = mesh.facesInternal[i].edgesInternal[n];
|
||||
s_IndexList.Add(edge.a);
|
||||
s_IndexList.Add(edge.b);
|
||||
|
||||
edgeIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
target.Clear();
|
||||
target.indexFormat = edgeCount * 2 > ushort.MaxValue ? Rendering.IndexFormat.UInt32 : Rendering.IndexFormat.UInt16;
|
||||
target.name = "ProBuilder::EdgeMesh" + target.GetObjectId();
|
||||
target.vertices = mesh.positionsInternal;
|
||||
target.subMeshCount = 1;
|
||||
target.SetIndices(s_IndexList, MeshTopology.Lines, 0);
|
||||
}
|
||||
|
||||
internal static void CreateEdgeMesh(ProBuilderMesh mesh, Mesh target, Edge[] edges)
|
||||
{
|
||||
int edgeCount = edges.Length;
|
||||
int vertexCount = edgeCount * 2;
|
||||
|
||||
s_IndexList.Clear();
|
||||
s_IndexList.Capacity = vertexCount;
|
||||
|
||||
for (int n = 0; n < edgeCount; n++)
|
||||
{
|
||||
var edge = edges[n];
|
||||
s_IndexList.Add(edge.a);
|
||||
s_IndexList.Add(edge.b);
|
||||
}
|
||||
|
||||
target.Clear();
|
||||
target.indexFormat = vertexCount > ushort.MaxValue ? Rendering.IndexFormat.UInt32 : Rendering.IndexFormat.UInt16;
|
||||
target.name = "ProBuilder::EdgeMesh" + target.GetObjectId();
|
||||
target.vertices = mesh.positionsInternal;
|
||||
target.subMeshCount = 1;
|
||||
target.SetIndices(s_IndexList, MeshTopology.Lines, 0);
|
||||
}
|
||||
|
||||
internal static void CreateVertexMesh(ProBuilderMesh mesh, Mesh target)
|
||||
{
|
||||
s_SharedVertexIndexList.Clear();
|
||||
int sharedVertexCount = mesh.sharedVerticesInternal.Length;
|
||||
s_SharedVertexIndexList.Capacity = sharedVertexCount;
|
||||
|
||||
for (int i = 0; i < sharedVertexCount; i++)
|
||||
s_SharedVertexIndexList.Add(mesh.sharedVerticesInternal[i][0]);
|
||||
|
||||
CreateVertexMesh(mesh, target, s_SharedVertexIndexList);
|
||||
}
|
||||
|
||||
internal static void CreateVertexMesh(ProBuilderMesh mesh, Mesh target, IList<int> indexes)
|
||||
{
|
||||
if (BuiltinMaterials.geometryShadersSupported)
|
||||
CreatePointMesh(mesh.positionsInternal, indexes, target);
|
||||
else
|
||||
CreatePointBillboardMesh(mesh.positionsInternal, indexes, target);
|
||||
}
|
||||
|
||||
static void CreatePointMesh(Vector3[] positions, IList<int> indexes, Mesh target)
|
||||
{
|
||||
int vertexCount = positions.Length;
|
||||
target.Clear();
|
||||
target.indexFormat = vertexCount > ushort.MaxValue ? Rendering.IndexFormat.UInt32 : Rendering.IndexFormat.UInt16;
|
||||
target.name = "ProBuilder::PointMesh";
|
||||
target.vertices = positions;
|
||||
target.subMeshCount = 1;
|
||||
|
||||
if(indexes is int[])
|
||||
target.SetIndices((int[]) indexes, MeshTopology.Points, 0);
|
||||
else if(indexes is List<int>)
|
||||
target.SetIndices((List<int>) indexes, MeshTopology.Points, 0);
|
||||
else
|
||||
target.SetIndices(indexes.ToArray(), MeshTopology.Points, 0);
|
||||
}
|
||||
|
||||
internal static void CreatePointBillboardMesh(IList<Vector3> positions, Mesh target)
|
||||
{
|
||||
var pointCount = positions.Count;
|
||||
var vertexCount = pointCount * 4;
|
||||
|
||||
s_Vector2List.Clear();
|
||||
s_Vector3List.Clear();
|
||||
s_IndexList.Clear();
|
||||
s_Vector2List.Capacity = vertexCount;
|
||||
s_Vector3List.Capacity = vertexCount;
|
||||
s_IndexList.Capacity = vertexCount;
|
||||
|
||||
for (int i = 0; i < pointCount; i++)
|
||||
{
|
||||
s_Vector3List.Add(positions[i]);
|
||||
s_Vector3List.Add(positions[i]);
|
||||
s_Vector3List.Add(positions[i]);
|
||||
s_Vector3List.Add(positions[i]);
|
||||
|
||||
s_Vector2List.Add(k_Billboard0);
|
||||
s_Vector2List.Add(k_Billboard1);
|
||||
s_Vector2List.Add(k_Billboard2);
|
||||
s_Vector2List.Add(k_Billboard3);
|
||||
|
||||
s_IndexList.Add(i * 4 + 0);
|
||||
s_IndexList.Add(i * 4 + 1);
|
||||
s_IndexList.Add(i * 4 + 3);
|
||||
s_IndexList.Add(i * 4 + 2);
|
||||
}
|
||||
|
||||
target.Clear();
|
||||
target.indexFormat = vertexCount > ushort.MaxValue ? Rendering.IndexFormat.UInt32 : Rendering.IndexFormat.UInt16;
|
||||
target.SetVertices(s_Vector3List);
|
||||
target.SetUVs(0, s_Vector2List);
|
||||
target.subMeshCount = 1;
|
||||
target.SetIndices(s_IndexList, MeshTopology.Quads, 0);
|
||||
}
|
||||
|
||||
static void CreatePointBillboardMesh(IList<Vector3> positions, IList<int> indexes, Mesh target)
|
||||
{
|
||||
var pointCount = indexes.Count;
|
||||
var vertexCount = pointCount * 4;
|
||||
|
||||
s_Vector2List.Clear();
|
||||
s_Vector3List.Clear();
|
||||
s_IndexList.Clear();
|
||||
s_Vector2List.Capacity = vertexCount;
|
||||
s_Vector3List.Capacity = vertexCount;
|
||||
s_IndexList.Capacity = vertexCount;
|
||||
|
||||
for (int i = 0; i < pointCount; i++)
|
||||
{
|
||||
var index = indexes[i];
|
||||
|
||||
s_Vector3List.Add(positions[index]);
|
||||
s_Vector3List.Add(positions[index]);
|
||||
s_Vector3List.Add(positions[index]);
|
||||
s_Vector3List.Add(positions[index]);
|
||||
|
||||
s_Vector2List.Add(k_Billboard0);
|
||||
s_Vector2List.Add(k_Billboard1);
|
||||
s_Vector2List.Add(k_Billboard2);
|
||||
s_Vector2List.Add(k_Billboard3);
|
||||
|
||||
s_IndexList.Add(i * 4 + 0);
|
||||
s_IndexList.Add(i * 4 + 1);
|
||||
s_IndexList.Add(i * 4 + 3);
|
||||
s_IndexList.Add(i * 4 + 2);
|
||||
}
|
||||
|
||||
target.Clear();
|
||||
target.indexFormat = vertexCount > ushort.MaxValue ? Rendering.IndexFormat.UInt32 : Rendering.IndexFormat.UInt16;
|
||||
target.SetVertices(s_Vector3List);
|
||||
target.SetUVs(0, s_Vector2List);
|
||||
target.subMeshCount = 1;
|
||||
target.SetIndices(s_IndexList, MeshTopology.Quads, 0);
|
||||
}
|
||||
|
||||
internal static void CreateEdgeBillboardMesh(ProBuilderMesh mesh, Mesh target)
|
||||
{
|
||||
target.Clear();
|
||||
const ushort k_MaxPointCountUShort = ushort.MaxValue / 4;
|
||||
var lineCount = mesh.edgeCount;
|
||||
|
||||
target.indexFormat = lineCount > k_MaxPointCountUShort
|
||||
? Rendering.IndexFormat.UInt32
|
||||
: Rendering.IndexFormat.UInt16;
|
||||
|
||||
var vertices = mesh.positionsInternal;
|
||||
|
||||
s_Vector3List.Clear();
|
||||
s_Vector4List.Clear();
|
||||
s_IndexList.Clear();
|
||||
s_Vector3List.Capacity = lineCount * 4;
|
||||
s_Vector4List.Capacity = lineCount * 4;
|
||||
s_IndexList.Capacity = lineCount * 4;
|
||||
|
||||
int n = 0;
|
||||
|
||||
foreach(var face in mesh.facesInternal)
|
||||
{
|
||||
foreach (var edge in face.edgesInternal)
|
||||
{
|
||||
Vector3 a = vertices[edge.a], b = vertices[edge.b];
|
||||
Vector3 c = b + (b - a);
|
||||
|
||||
s_Vector3List.Add(a);
|
||||
s_Vector3List.Add(a);
|
||||
s_Vector3List.Add(b);
|
||||
s_Vector3List.Add(b);
|
||||
|
||||
s_Vector4List.Add(new Vector4(b.x, b.y, b.z, 1f));
|
||||
s_Vector4List.Add(new Vector4(b.x, b.y, b.z, -1f));
|
||||
s_Vector4List.Add(new Vector4(c.x, c.y, c.z, 1f));
|
||||
s_Vector4List.Add(new Vector4(c.x, c.y, c.z, -1f));
|
||||
|
||||
s_IndexList.Add(n + 0);
|
||||
s_IndexList.Add(n + 1);
|
||||
s_IndexList.Add(n + 3);
|
||||
s_IndexList.Add(n + 2);
|
||||
|
||||
n += 4;
|
||||
}
|
||||
}
|
||||
|
||||
target.SetVertices(s_Vector3List);
|
||||
target.SetTangents(s_Vector4List);
|
||||
target.subMeshCount = 1;
|
||||
target.SetIndices(s_IndexList, MeshTopology.Quads, 0);
|
||||
}
|
||||
|
||||
internal static void CreateEdgeBillboardMesh(ProBuilderMesh mesh, Mesh target, ICollection<Edge> edges)
|
||||
{
|
||||
target.Clear();
|
||||
|
||||
const ushort k_MaxPointCountUShort = ushort.MaxValue / 4;
|
||||
|
||||
var lineCount = edges.Count;
|
||||
|
||||
target.indexFormat = lineCount > k_MaxPointCountUShort
|
||||
? Rendering.IndexFormat.UInt32
|
||||
: Rendering.IndexFormat.UInt16;
|
||||
|
||||
var vertices = mesh.positionsInternal;
|
||||
|
||||
s_Vector3List.Clear();
|
||||
s_Vector4List.Clear();
|
||||
s_IndexList.Clear();
|
||||
s_Vector3List.Capacity = lineCount * 4;
|
||||
s_Vector4List.Capacity = lineCount * 4;
|
||||
s_IndexList.Capacity = lineCount * 4;
|
||||
|
||||
int n = 0;
|
||||
|
||||
foreach (var edge in edges)
|
||||
{
|
||||
Vector3 a = vertices[edge.a], b = vertices[edge.b];
|
||||
Vector3 c = b + (b - a);
|
||||
|
||||
s_Vector3List.Add(a);
|
||||
s_Vector3List.Add(a);
|
||||
s_Vector3List.Add(b);
|
||||
s_Vector3List.Add(b);
|
||||
|
||||
s_Vector4List.Add(new Vector4(b.x, b.y, b.z, 1f));
|
||||
s_Vector4List.Add(new Vector4(b.x, b.y, b.z, -1f));
|
||||
s_Vector4List.Add(new Vector4(c.x, c.y, c.z, 1f));
|
||||
s_Vector4List.Add(new Vector4(c.x, c.y, c.z, -1f));
|
||||
|
||||
s_IndexList.Add(n + 0);
|
||||
s_IndexList.Add(n + 1);
|
||||
s_IndexList.Add(n + 3);
|
||||
s_IndexList.Add(n + 2);
|
||||
|
||||
n += 4;
|
||||
}
|
||||
|
||||
target.SetVertices(s_Vector3List);
|
||||
target.SetTangents(s_Vector4List);
|
||||
target.subMeshCount = 1;
|
||||
target.SetIndices(s_IndexList, MeshTopology.Quads, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2734476e9b233c14987be5fbd0b43264
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,648 @@
|
||||
using UnityEngine;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections;
|
||||
using System.Text;
|
||||
|
||||
namespace UnityEngine.ProBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides functions for generating mesh attributes and utilities.
|
||||
/// </summary>
|
||||
public static class MeshUtility
|
||||
{
|
||||
/// <summary>
|
||||
/// Create an array of @"UnityEngine.ProBuilder.Vertex" values that are ordered as individual triangles. This modifies the source mesh to match the new individual triangles format.
|
||||
/// </summary>
|
||||
/// <param name="mesh">The mesh to extract vertices from, and apply per-triangle topology to.</param>
|
||||
/// <returns>A @"UnityEngine.ProBuilder.Vertex" array of the per-triangle vertices.</returns>
|
||||
internal static Vertex[] GeneratePerTriangleMesh(Mesh mesh)
|
||||
{
|
||||
if (mesh == null)
|
||||
throw new ArgumentNullException("mesh");
|
||||
|
||||
Vertex[] vertices = mesh.GetVertices();
|
||||
int smc = mesh.subMeshCount;
|
||||
Vertex[] tv = new Vertex[mesh.triangles.Length];
|
||||
int[][] triangles = new int[smc][];
|
||||
int triIndex = 0;
|
||||
|
||||
for (int s = 0; s < smc; s++)
|
||||
{
|
||||
triangles[s] = mesh.GetTriangles(s);
|
||||
int tl = triangles[s].Length;
|
||||
|
||||
for (int i = 0; i < tl; i++)
|
||||
{
|
||||
tv[triIndex++] = new Vertex(vertices[triangles[s][i]]);
|
||||
triangles[s][i] = triIndex - 1;
|
||||
}
|
||||
}
|
||||
|
||||
Vertex.SetMesh(mesh, tv);
|
||||
|
||||
mesh.subMeshCount = smc;
|
||||
|
||||
for (int s = 0; s < smc; s++)
|
||||
mesh.SetTriangles(triangles[s], s);
|
||||
|
||||
return tv;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates tangents and applies them on the specified mesh.
|
||||
/// </summary>
|
||||
/// <param name="mesh">The <see cref="UnityEngine.Mesh"/> mesh target.</param>
|
||||
public static void GenerateTangent(Mesh mesh)
|
||||
{
|
||||
if (mesh == null)
|
||||
throw new System.ArgumentNullException("mesh");
|
||||
|
||||
// http://answers.unity3d.com/questions/7789/calculating-tangents-vector4.html
|
||||
|
||||
// speed up math by copying the mesh arrays
|
||||
int[] triangles = mesh.triangles;
|
||||
Vector3[] vertices = mesh.vertices;
|
||||
Vector2[] uv = mesh.uv;
|
||||
Vector3[] normals = mesh.normals;
|
||||
|
||||
//variable definitions
|
||||
int triangleCount = triangles.Length;
|
||||
int vertexCount = vertices.Length;
|
||||
|
||||
Vector3[] tan1 = new Vector3[vertexCount];
|
||||
Vector3[] tan2 = new Vector3[vertexCount];
|
||||
|
||||
Vector4[] tangents = new Vector4[vertexCount];
|
||||
|
||||
for (long a = 0; a < triangleCount; a += 3)
|
||||
{
|
||||
long i1 = triangles[a + 0];
|
||||
long i2 = triangles[a + 1];
|
||||
long i3 = triangles[a + 2];
|
||||
|
||||
Vector3 v1 = vertices[i1];
|
||||
Vector3 v2 = vertices[i2];
|
||||
Vector3 v3 = vertices[i3];
|
||||
|
||||
Vector2 w1 = uv[i1];
|
||||
Vector2 w2 = uv[i2];
|
||||
Vector2 w3 = uv[i3];
|
||||
|
||||
float x1 = v2.x - v1.x;
|
||||
float x2 = v3.x - v1.x;
|
||||
float y1 = v2.y - v1.y;
|
||||
float y2 = v3.y - v1.y;
|
||||
float z1 = v2.z - v1.z;
|
||||
float z2 = v3.z - v1.z;
|
||||
|
||||
float s1 = w2.x - w1.x;
|
||||
float s2 = w3.x - w1.x;
|
||||
float t1 = w2.y - w1.y;
|
||||
float t2 = w3.y - w1.y;
|
||||
|
||||
float r = 1.0f / (s1 * t2 - s2 * t1);
|
||||
|
||||
Vector3 sdir = new Vector3((t2 * x1 - t1 * x2) * r, (t2 * y1 - t1 * y2) * r, (t2 * z1 - t1 * z2) * r);
|
||||
Vector3 tdir = new Vector3((s1 * x2 - s2 * x1) * r, (s1 * y2 - s2 * y1) * r, (s1 * z2 - s2 * z1) * r);
|
||||
|
||||
tan1[i1] += sdir;
|
||||
tan1[i2] += sdir;
|
||||
tan1[i3] += sdir;
|
||||
|
||||
tan2[i1] += tdir;
|
||||
tan2[i2] += tdir;
|
||||
tan2[i3] += tdir;
|
||||
}
|
||||
|
||||
|
||||
for (long a = 0; a < vertexCount; ++a)
|
||||
{
|
||||
Vector3 n = normals[a];
|
||||
Vector3 t = tan1[a];
|
||||
|
||||
Vector3.OrthoNormalize(ref n, ref t);
|
||||
tangents[a].x = t.x;
|
||||
tangents[a].y = t.y;
|
||||
tangents[a].z = t.z;
|
||||
|
||||
tangents[a].w = (Vector3.Dot(Vector3.Cross(n, t), tan2[a]) < 0.0f) ? -1.0f : 1.0f;
|
||||
}
|
||||
|
||||
mesh.tangents = tangents;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a new mesh containing all attributes and values copied from the specified source mesh.
|
||||
/// </summary>
|
||||
/// <param name="source">The mesh to copy from.</param>
|
||||
/// <returns>A new <see cref="UnityEngine.Mesh"/> object with the same values as the source mesh.</returns>
|
||||
public static Mesh DeepCopy(Mesh source)
|
||||
{
|
||||
Mesh m = new Mesh();
|
||||
CopyTo(source, m);
|
||||
return m;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies mesh attribute values from one mesh to another.
|
||||
/// </summary>
|
||||
/// <param name="source">The mesh from which to copy attribute values.</param>
|
||||
/// <param name="destination">The destination mesh to copy attribute values to.</param>
|
||||
/// <exception cref="ArgumentNullException">Throws if source or destination is null.</exception>
|
||||
public static void CopyTo(Mesh source, Mesh destination)
|
||||
{
|
||||
if (source == null)
|
||||
throw new System.ArgumentNullException("source");
|
||||
|
||||
if (destination == null)
|
||||
throw new System.ArgumentNullException("destination");
|
||||
|
||||
Vector3[] v = new Vector3[source.vertices.Length];
|
||||
int[][] t = new int[source.subMeshCount][];
|
||||
Vector2[] u = new Vector2[source.uv.Length];
|
||||
Vector2[] u2 = new Vector2[source.uv2.Length];
|
||||
Vector4[] tan = new Vector4[source.tangents.Length];
|
||||
Vector3[] n = new Vector3[source.normals.Length];
|
||||
Color32[] c = new Color32[source.colors32.Length];
|
||||
|
||||
System.Array.Copy(source.vertices, v, v.Length);
|
||||
|
||||
for (int i = 0; i < t.Length; i++)
|
||||
t[i] = source.GetTriangles(i);
|
||||
|
||||
System.Array.Copy(source.uv, u, u.Length);
|
||||
System.Array.Copy(source.uv2, u2, u2.Length);
|
||||
System.Array.Copy(source.normals, n, n.Length);
|
||||
System.Array.Copy(source.tangents, tan, tan.Length);
|
||||
System.Array.Copy(source.colors32, c, c.Length);
|
||||
|
||||
destination.Clear();
|
||||
destination.name = source.name;
|
||||
|
||||
destination.vertices = v;
|
||||
|
||||
destination.subMeshCount = t.Length;
|
||||
|
||||
for (int i = 0; i < t.Length; i++)
|
||||
destination.SetTriangles(t[i], i);
|
||||
|
||||
destination.uv = u;
|
||||
destination.uv2 = u2;
|
||||
destination.tangents = tan;
|
||||
destination.normals = n;
|
||||
destination.colors32 = c;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a mesh attribute from either the MeshFilter.sharedMesh or the MeshRenderer.additionalVertexStreams mesh. The additional vertex stream mesh has priority.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the attribute to fetch.</typeparam>
|
||||
/// <param name="gameObject">The GameObject with the MeshFilter and (optional) MeshRenderer to search for mesh attributes.</param>
|
||||
/// <param name="attributeGetter">The function used to extract mesh attribute.</param>
|
||||
/// <returns>A List of the mesh attribute values from the Additional Vertex Streams mesh if it exists and contains the attribute, or the MeshFilter.sharedMesh attribute values.</returns>
|
||||
internal static T GetMeshChannel<T>(GameObject gameObject, Func<Mesh, T> attributeGetter) where T : IList
|
||||
{
|
||||
if (gameObject == null)
|
||||
throw new System.ArgumentNullException("gameObject");
|
||||
|
||||
if (attributeGetter == null)
|
||||
throw new System.ArgumentNullException("attributeGetter");
|
||||
|
||||
MeshFilter mf = gameObject.GetComponent<MeshFilter>();
|
||||
Mesh mesh = mf != null ? mf.sharedMesh : null;
|
||||
T res = default(T);
|
||||
|
||||
if (mesh == null)
|
||||
return res;
|
||||
|
||||
int vertexCount = mesh.vertexCount;
|
||||
|
||||
MeshRenderer renderer = gameObject.GetComponent<MeshRenderer>();
|
||||
Mesh vertexStream = renderer != null ? renderer.additionalVertexStreams : null;
|
||||
|
||||
if (vertexStream != null)
|
||||
{
|
||||
res = attributeGetter(vertexStream);
|
||||
|
||||
if (res != null && res.Count == vertexCount)
|
||||
return res;
|
||||
}
|
||||
res = attributeGetter(mesh);
|
||||
|
||||
return res != null && res.Count == vertexCount ? res : default(T);
|
||||
}
|
||||
|
||||
static void PrintAttribute<T>(StringBuilder sb, string title, IEnumerable<T> attrib, string fmt)
|
||||
{
|
||||
sb.AppendLine($" - {title}");
|
||||
if (attrib != null && attrib.Any())
|
||||
{
|
||||
foreach (var value in attrib)
|
||||
sb.AppendLine(string.Format($" {fmt}", value));
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.AppendLine("\tnull");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prints a detailed string summary of the mesh attributes.
|
||||
/// </summary>
|
||||
/// <param name="mesh">The mesh to print information for.</param>
|
||||
/// <returns>A tab-delimited string (positions, normals, colors, tangents, and UV coordinates).</returns>
|
||||
public static string Print(Mesh mesh)
|
||||
{
|
||||
if (mesh == null)
|
||||
throw new ArgumentNullException("mesh");
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
Vector3[] positions = mesh.vertices;
|
||||
Vector3[] normals = mesh.normals;
|
||||
Color[] colors = mesh.colors;
|
||||
Vector4[] tangents = mesh.tangents;
|
||||
List<Vector4> uv0 = new List<Vector4>();
|
||||
Vector2[] uv2 = mesh.uv2;
|
||||
List<Vector4> uv3 = new List<Vector4>();
|
||||
List<Vector4> uv4 = new List<Vector4>();
|
||||
|
||||
mesh.GetUVs(0, uv0);
|
||||
mesh.GetUVs(2, uv3);
|
||||
mesh.GetUVs(3, uv4);
|
||||
|
||||
sb.AppendLine($"# Sanity Check");
|
||||
sb.AppendLine(MeshUtility.SanityCheck(mesh));
|
||||
|
||||
sb.AppendLine($"# Attributes ({mesh.vertexCount})");
|
||||
|
||||
PrintAttribute(sb, $"positions ({positions.Length})", positions, "pos: {0:F2}");
|
||||
PrintAttribute(sb, $"normals ({normals.Length})", normals, "nrm: {0:F2}");
|
||||
PrintAttribute(sb, $"colors ({colors.Length})", colors, "col: {0:F2}");
|
||||
PrintAttribute(sb, $"tangents ({tangents.Length})", tangents, "tan: {0:F2}");
|
||||
PrintAttribute(sb, $"uv0 ({uv0.Count})", uv0, "uv0: {0:F2}");
|
||||
PrintAttribute(sb, $"uv2 ({uv2.Length})", uv2, "uv2: {0:F2}");
|
||||
PrintAttribute(sb, $"uv3 ({uv3.Count})", uv3, "uv3: {0:F2}");
|
||||
PrintAttribute(sb, $"uv4 ({uv4.Count})", uv4, "uv4: {0:F2}");
|
||||
|
||||
sb.AppendLine("# Topology");
|
||||
|
||||
for (int i = 0; i < mesh.subMeshCount; i++)
|
||||
{
|
||||
var topo = mesh.GetTopology(i);
|
||||
var submesh = mesh.GetIndices(i);
|
||||
sb.AppendLine($" Submesh[{i}] ({topo})");
|
||||
|
||||
switch (topo)
|
||||
{
|
||||
case MeshTopology.Points:
|
||||
for (int n = 0; n < submesh.Length; n += 1)
|
||||
sb.AppendLine(string.Format("\t{0}", submesh[n]));
|
||||
break;
|
||||
case MeshTopology.Lines:
|
||||
for (int n = 0; n < submesh.Length; n += 2)
|
||||
sb.AppendLine(string.Format("\t{0}, {1}", submesh[n], submesh[n + 1]));
|
||||
break;
|
||||
case MeshTopology.Triangles:
|
||||
for (int n = 0; n < submesh.Length; n += 3)
|
||||
sb.AppendLine(string.Format("\t{0}, {1}, {2}", submesh[n], submesh[n + 1], submesh[n + 2]));
|
||||
break;
|
||||
case MeshTopology.Quads:
|
||||
for (int n = 0; n < submesh.Length; n += 4)
|
||||
sb.AppendLine(string.Format("\t{0}, {1}, {2}, {3}", submesh[n], submesh[n + 1], submesh[n + 2], submesh[n + 3]));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the number of indices this mesh contains.
|
||||
/// </summary>
|
||||
/// <param name="mesh">The source mesh to sum submesh index counts from.</param>
|
||||
/// <returns>The count of all indices contained within this mesh's submeshes.</returns>
|
||||
public static uint GetIndexCount(Mesh mesh)
|
||||
{
|
||||
uint sum = 0;
|
||||
|
||||
if (mesh == null)
|
||||
return sum;
|
||||
|
||||
for (int i = 0, c = mesh.subMeshCount; i < c; i++)
|
||||
sum += mesh.GetIndexCount(i);
|
||||
|
||||
return sum;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the number of triangles or quads this mesh contains. No other mesh topologies are considered.
|
||||
/// </summary>
|
||||
/// <param name="mesh">The source mesh to sum submesh primitive counts from.</param>
|
||||
/// <returns>The count of all triangles or quads contained within this mesh's submeshes.</returns>
|
||||
public static uint GetPrimitiveCount(Mesh mesh)
|
||||
{
|
||||
uint sum = 0;
|
||||
|
||||
if (mesh == null)
|
||||
return sum;
|
||||
|
||||
for (int i = 0, c = mesh.subMeshCount; i < c; i++)
|
||||
{
|
||||
if (mesh.GetTopology(i) == MeshTopology.Triangles)
|
||||
sum += mesh.GetIndexCount(i) / 3;
|
||||
else if (mesh.GetTopology(i) == MeshTopology.Quads)
|
||||
sum += mesh.GetIndexCount(i) / 4;
|
||||
}
|
||||
|
||||
return sum;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compiles a <see cref="UnityEngine.Mesh"/> from a ProBuilderMesh.
|
||||
/// </summary>
|
||||
/// <param name="probuilderMesh">The source mesh.</param>
|
||||
/// <param name="targetMesh">The destination `UnityEngine.Mesh`.</param>
|
||||
/// <param name="preferredTopology">True to try to create topology that matches the requested format. If the method can't create topology using the requested format, it uses triangles where necessary.</param>
|
||||
public static void Compile(ProBuilderMesh probuilderMesh, Mesh targetMesh, MeshTopology preferredTopology = MeshTopology.Triangles)
|
||||
{
|
||||
if (probuilderMesh == null)
|
||||
throw new ArgumentNullException("probuilderMesh");
|
||||
|
||||
if (targetMesh == null)
|
||||
throw new ArgumentNullException("targetMesh");
|
||||
|
||||
targetMesh.Clear();
|
||||
|
||||
targetMesh.vertices = probuilderMesh.positionsInternal;
|
||||
targetMesh.uv = probuilderMesh.texturesInternal;
|
||||
|
||||
if (probuilderMesh.HasArrays(MeshArrays.Texture2))
|
||||
{
|
||||
List<Vector4> uvChannel = new List<Vector4>();
|
||||
probuilderMesh.GetUVs(2, uvChannel);
|
||||
targetMesh.SetUVs(2, uvChannel);
|
||||
}
|
||||
|
||||
if (probuilderMesh.HasArrays(MeshArrays.Texture3))
|
||||
{
|
||||
List<Vector4> uvChannel = new List<Vector4>();
|
||||
probuilderMesh.GetUVs(3, uvChannel);
|
||||
targetMesh.SetUVs(3, uvChannel);
|
||||
}
|
||||
|
||||
targetMesh.normals = probuilderMesh.GetNormals();
|
||||
targetMesh.tangents = probuilderMesh.GetTangents();
|
||||
|
||||
if (probuilderMesh.HasArrays(MeshArrays.Color))
|
||||
targetMesh.colors = probuilderMesh.colorsInternal;
|
||||
|
||||
var materialCount = probuilderMesh.GetComponent<Renderer>().sharedMaterials.Length;
|
||||
var submeshes = Submesh.GetSubmeshes(probuilderMesh.facesInternal, materialCount, preferredTopology);
|
||||
targetMesh.subMeshCount = submeshes.Length;
|
||||
|
||||
for (int i = 0; i < targetMesh.subMeshCount; i++)
|
||||
targetMesh.SetIndices(submeshes[i].m_Indexes, submeshes[i].m_Topology, i, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new array of vertices with values from a <see cref="UnityEngine.Mesh"/> object.
|
||||
/// </summary>
|
||||
/// <param name="mesh">The source mesh.</param>
|
||||
/// <returns>An array of vertices.</returns>
|
||||
public static Vertex[] GetVertices(this Mesh mesh)
|
||||
{
|
||||
if (mesh == null)
|
||||
return null;
|
||||
|
||||
int vertexCount = mesh.vertexCount;
|
||||
Vertex[] v = new Vertex[vertexCount];
|
||||
|
||||
Vector3[] positions = mesh.vertices;
|
||||
Color[] colors = mesh.colors;
|
||||
Vector3[] normals = mesh.normals;
|
||||
Vector4[] tangents = mesh.tangents;
|
||||
Vector2[] uv0s = mesh.uv;
|
||||
Vector2[] uv2s = mesh.uv2;
|
||||
List<Vector4> uv3s = new List<Vector4>();
|
||||
List<Vector4> uv4s = new List<Vector4>();
|
||||
mesh.GetUVs(2, uv3s);
|
||||
mesh.GetUVs(3, uv4s);
|
||||
|
||||
bool _hasPositions = positions != null && positions.Length == vertexCount;
|
||||
bool _hasColors = colors != null && colors.Length == vertexCount;
|
||||
bool _hasNormals = normals != null && normals.Length == vertexCount;
|
||||
bool _hasTangents = tangents != null && tangents.Length == vertexCount;
|
||||
bool _hasUv0 = uv0s != null && uv0s.Length == vertexCount;
|
||||
bool _hasUv2 = uv2s != null && uv2s.Length == vertexCount;
|
||||
bool _hasUv3 = uv3s.Count == vertexCount;
|
||||
bool _hasUv4 = uv4s.Count == vertexCount;
|
||||
|
||||
for (int i = 0; i < vertexCount; i++)
|
||||
{
|
||||
v[i] = new Vertex();
|
||||
|
||||
if (_hasPositions)
|
||||
v[i].position = positions[i];
|
||||
|
||||
if (_hasColors)
|
||||
v[i].color = colors[i];
|
||||
|
||||
if (_hasNormals)
|
||||
v[i].normal = normals[i];
|
||||
|
||||
if (_hasTangents)
|
||||
v[i].tangent = tangents[i];
|
||||
|
||||
if (_hasUv0)
|
||||
v[i].uv0 = uv0s[i];
|
||||
|
||||
if (_hasUv2)
|
||||
v[i].uv2 = uv2s[i];
|
||||
|
||||
if (_hasUv3)
|
||||
v[i].uv3 = uv3s[i];
|
||||
|
||||
if (_hasUv4)
|
||||
v[i].uv4 = uv4s[i];
|
||||
}
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Merges coincident vertices where possible, optimizing the vertex count of a <see cref="UnityEngine.Mesh"/> object.
|
||||
/// </summary>
|
||||
/// <param name="mesh">The mesh to optimize.</param>
|
||||
/// <param name="vertices">
|
||||
/// Specify an array of <see cref="Vertex"/> objects to use instead of extracting attributes from the mesh.
|
||||
///
|
||||
/// This is a performance optimization for when this array already exists. If not specified, ProBuilder generates this array automatically.
|
||||
/// </param>
|
||||
public static void CollapseSharedVertices(Mesh mesh, Vertex[] vertices = null)
|
||||
{
|
||||
if (mesh == null)
|
||||
throw new System.ArgumentNullException("mesh");
|
||||
|
||||
bool hasCollapsedVertices = vertices != null;
|
||||
|
||||
if(vertices == null)
|
||||
vertices = mesh.GetVertices();
|
||||
|
||||
int smc = mesh.subMeshCount;
|
||||
List<Dictionary<Vertex, int>> subVertices = new List<Dictionary<Vertex, int>>();
|
||||
int[][] tris = new int[smc][];
|
||||
int subIndex = 0;
|
||||
|
||||
for (int i = 0; i < smc; ++i)
|
||||
{
|
||||
tris[i] = mesh.GetTriangles(i);
|
||||
Dictionary<Vertex, int> newVertices = new Dictionary<Vertex, int>();
|
||||
|
||||
for (int n = 0; n < tris[i].Length; n++)
|
||||
{
|
||||
Vertex v = vertices[tris[i][n]];
|
||||
int index;
|
||||
|
||||
if (newVertices.TryGetValue(v, out index))
|
||||
{
|
||||
tris[i][n] = index;
|
||||
}
|
||||
else
|
||||
{
|
||||
tris[i][n] = subIndex;
|
||||
newVertices.Add(v, subIndex);
|
||||
subIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
subVertices.Add(newVertices);
|
||||
}
|
||||
|
||||
Vertex[] collapsed = subVertices.SelectMany(x => x.Keys).ToArray();
|
||||
//Check if new vertices have been collapsed
|
||||
hasCollapsedVertices |= (collapsed.Length != vertices.Length);
|
||||
if(hasCollapsedVertices)
|
||||
{
|
||||
Vertex.SetMesh(mesh, collapsed);
|
||||
mesh.subMeshCount = smc;
|
||||
for(int i = 0; i < smc; i++)
|
||||
mesh.SetTriangles(tris[i], i);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Scales mesh vertices to fit within a bounding box.
|
||||
/// </summary>
|
||||
/// <param name="mesh">The mesh to scale.</param>
|
||||
/// <param name="currentSize">The bounding box that defines the mesh's shape. </param>
|
||||
/// <param name="sizeToFit">The new size to fit mesh contents within.</param>
|
||||
public static void FitToSize(ProBuilderMesh mesh, Bounds currentSize, Vector3 sizeToFit)
|
||||
{
|
||||
if (mesh.vertexCount < 1)
|
||||
return;
|
||||
|
||||
var scale = Math.Abs(sizeToFit).DivideBy(currentSize.size);
|
||||
if (scale == Vector3.one || scale == Vector3.zero)
|
||||
return;
|
||||
|
||||
var positions = mesh.positionsInternal;
|
||||
|
||||
if (System.Math.Abs(currentSize.size.x) < 0.001f)
|
||||
scale.x = 0;
|
||||
if (System.Math.Abs(currentSize.size.y) < 0.001f)
|
||||
scale.y = 0;
|
||||
if (System.Math.Abs(currentSize.size.z) < 0.001f)
|
||||
scale.z = 0;
|
||||
|
||||
for (int i = 0, c = mesh.vertexCount; i < c; i++)
|
||||
{
|
||||
positions[i] -= currentSize.center;
|
||||
positions[i].Scale(scale);
|
||||
positions[i] += currentSize.center;
|
||||
}
|
||||
|
||||
mesh.Rebuild();
|
||||
}
|
||||
|
||||
internal static string SanityCheck(ProBuilderMesh mesh)
|
||||
{
|
||||
return SanityCheck(mesh.GetVertices());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check mesh for invalid properties.
|
||||
/// </summary>
|
||||
/// <param name="mesh"></param>
|
||||
/// <returns>Returns true if mesh is valid, false if a problem was found.</returns>
|
||||
internal static string SanityCheck(Mesh mesh)
|
||||
{
|
||||
return SanityCheck(mesh.GetVertices());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check mesh for invalid properties.
|
||||
/// </summary>
|
||||
/// <returns>Returns true if mesh is valid, false if a problem was found.</returns>
|
||||
internal static string SanityCheck(IList<Vertex> vertices)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
|
||||
for (int i = 0, c = vertices.Count; i < c; i++)
|
||||
{
|
||||
var vertex = vertices[i];
|
||||
|
||||
if (Math.IsNumber(vertex.position)
|
||||
&& Math.IsNumber(vertex.color)
|
||||
&& Math.IsNumber(vertex.uv0)
|
||||
&& Math.IsNumber(vertex.normal)
|
||||
&& Math.IsNumber(vertex.tangent)
|
||||
&& Math.IsNumber(vertex.uv2)
|
||||
&& Math.IsNumber(vertex.uv3)
|
||||
&& Math.IsNumber(vertex.uv4))
|
||||
continue;
|
||||
|
||||
sb.AppendFormat("vertex {0} contains invalid values:\n{1}\n\n", i, vertex.ToString());
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
internal static bool IsUsedInParticleSystem(ProBuilderMesh pbmesh)
|
||||
{
|
||||
#if USING_PARTICLE_SYSTEM
|
||||
ParticleSystem pSys;
|
||||
if(pbmesh.TryGetComponent(out pSys))
|
||||
{
|
||||
var shapeModule = pSys.shape;
|
||||
if(shapeModule.meshRenderer == pbmesh.renderer)
|
||||
{
|
||||
shapeModule.meshRenderer = null;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
internal static void RestoreParticleSystem(ProBuilderMesh pbmesh)
|
||||
{
|
||||
#if USING_PARTICLE_SYSTEM
|
||||
ParticleSystem pSys;
|
||||
if(pbmesh.TryGetComponent(out pSys))
|
||||
{
|
||||
var shapeModule = pSys.shape;
|
||||
shapeModule.meshRenderer = pbmesh.renderer;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
internal static Bounds GetBounds(this ProBuilderMesh mesh)
|
||||
{
|
||||
if (mesh.mesh != null)
|
||||
return mesh.mesh.bounds;
|
||||
return Math.GetBounds(mesh.positionsInternal);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3208ba508d5e047c7bd843fc986a4f7a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,84 @@
|
||||
using System;
|
||||
|
||||
namespace UnityEngine.ProBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// A container for normal, tangent, and bitangent values.
|
||||
/// </summary>
|
||||
public struct Normal : IEquatable<Normal>
|
||||
{
|
||||
/// <summary>
|
||||
/// A unit normal.
|
||||
/// </summary>
|
||||
public Vector3 normal { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A unit tangent.
|
||||
/// </summary>
|
||||
public Vector4 tangent { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A unit bitangent (sometimes called binormal).
|
||||
/// </summary>
|
||||
public Vector3 bitangent { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether this object is equal to another object.
|
||||
/// </summary>
|
||||
/// <param name="obj">The object to compare against.</param>
|
||||
/// <returns>True if the objects are equal, false if not.</returns>
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is Normal && Equals((Normal)obj);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the hash code for this instance.
|
||||
/// </summary>
|
||||
/// <returns>An integer that is the hash code for this instance.</returns>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
int hashCode = VectorHash.GetHashCode(normal);
|
||||
hashCode = (hashCode * 397) ^ VectorHash.GetHashCode(tangent);
|
||||
hashCode = (hashCode * 397) ^ VectorHash.GetHashCode(bitangent);
|
||||
return hashCode;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether the specified Normal is equal to this one.
|
||||
/// </summary>
|
||||
/// <param name="other">The Normal object to compare against.</param>
|
||||
/// <returns>True if the objects are equal, false if not.</returns>
|
||||
public bool Equals(Normal other)
|
||||
{
|
||||
return Math.Approx3(normal, other.normal) &&
|
||||
Math.Approx3(tangent, other.tangent) &&
|
||||
Math.Approx3(bitangent, other.bitangent);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compares two objects for equality.
|
||||
/// </summary>
|
||||
/// <param name="a">The first Normal instance.</param>
|
||||
/// <param name="b">The second Normal instance.</param>
|
||||
/// <returns>True if the objects are equal; false if not.</returns>
|
||||
public static bool operator==(Normal a, Normal b)
|
||||
{
|
||||
return a.Equals(b);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compares two objects for inequality.
|
||||
/// </summary>
|
||||
/// <param name="a">The first Normal instance.</param>
|
||||
/// <param name="b">The second Normal instance.</param>
|
||||
/// <returns>True if the objects are not equal; false if not.</returns>
|
||||
public static bool operator!=(Normal a, Normal b)
|
||||
{
|
||||
return !(a == b);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9b0cf5260e6d70b428dab29748b1b3fe
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,265 @@
|
||||
using System;
|
||||
|
||||
namespace UnityEngine.ProBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides functions to help calculate normals and tangents for a mesh.
|
||||
/// </summary>
|
||||
public static class Normals
|
||||
{
|
||||
static Vector3[] s_SmoothAvg = new Vector3[Smoothing.smoothRangeMax];
|
||||
static float[] s_SmoothAvgCount = new float[Smoothing.smoothRangeMax];
|
||||
static int[] s_CachedIntArray = new int[ushort.MaxValue];
|
||||
|
||||
static void ClearIntArray(int count)
|
||||
{
|
||||
if (count > s_CachedIntArray.Length)
|
||||
Array.Resize(ref s_CachedIntArray, count);
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
s_CachedIntArray[i] = 0;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
|
||||
static void ResetStaticsOnLoad()
|
||||
{
|
||||
s_SmoothAvg = new Vector3[Smoothing.smoothRangeMax];
|
||||
s_SmoothAvgCount = new float[Smoothing.smoothRangeMax];
|
||||
s_CachedIntArray = new int[ushort.MaxValue];
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the tangents for a mesh.
|
||||
/// </summary>
|
||||
/// <param name="mesh">The mesh to calculate tangents for.</param>
|
||||
public static void CalculateTangents(ProBuilderMesh mesh)
|
||||
{
|
||||
int vc = mesh.vertexCount;
|
||||
|
||||
if (!mesh.HasArrays(MeshArrays.Tangent))
|
||||
mesh.tangentsInternal = new Vector4[vc];
|
||||
|
||||
if (!mesh.HasArrays(MeshArrays.Position) || !mesh.HasArrays(MeshArrays.Texture0))
|
||||
return;
|
||||
|
||||
// http://answers.unity3d.com/questions/7789/calculating-tangents-vector4.html
|
||||
Vector3[] normals = mesh.GetNormals();
|
||||
|
||||
var positions = mesh.positionsInternal;
|
||||
var textures = mesh.texturesInternal;
|
||||
|
||||
var tan1 = new Vector3[vc];
|
||||
var tan2 = new Vector3[vc];
|
||||
|
||||
var tangents = mesh.tangentsInternal;
|
||||
|
||||
foreach (var face in mesh.facesInternal)
|
||||
{
|
||||
int[] triangles = face.indexesInternal;
|
||||
|
||||
for (int a = 0, c = triangles.Length; a < c; a += 3)
|
||||
{
|
||||
long i1 = triangles[a + 0];
|
||||
long i2 = triangles[a + 1];
|
||||
long i3 = triangles[a + 2];
|
||||
|
||||
Vector3 v1 = positions[i1];
|
||||
Vector3 v2 = positions[i2];
|
||||
Vector3 v3 = positions[i3];
|
||||
|
||||
Vector2 w1 = textures[i1];
|
||||
Vector2 w2 = textures[i2];
|
||||
Vector2 w3 = textures[i3];
|
||||
|
||||
float x1 = v2.x - v1.x;
|
||||
float x2 = v3.x - v1.x;
|
||||
float y1 = v2.y - v1.y;
|
||||
float y2 = v3.y - v1.y;
|
||||
float z1 = v2.z - v1.z;
|
||||
float z2 = v3.z - v1.z;
|
||||
|
||||
float s1 = w2.x - w1.x;
|
||||
float s2 = w3.x - w1.x;
|
||||
float t1 = w2.y - w1.y;
|
||||
float t2 = w3.y - w1.y;
|
||||
|
||||
float r = 1.0f / (s1 * t2 - s2 * t1);
|
||||
|
||||
Vector3 sdir = new Vector3((t2 * x1 - t1 * x2) * r, (t2 * y1 - t1 * y2) * r, (t2 * z1 - t1 * z2) * r);
|
||||
Vector3 tdir = new Vector3((s1 * x2 - s2 * x1) * r, (s1 * y2 - s2 * y1) * r, (s1 * z2 - s2 * z1) * r);
|
||||
|
||||
tan1[i1] += sdir;
|
||||
tan1[i2] += sdir;
|
||||
tan1[i3] += sdir;
|
||||
|
||||
tan2[i1] += tdir;
|
||||
tan2[i2] += tdir;
|
||||
tan2[i3] += tdir;
|
||||
}
|
||||
}
|
||||
|
||||
for (long a = 0; a < vc; ++a)
|
||||
{
|
||||
Vector3 n = normals[a];
|
||||
Vector3 t = Math.EnsureUnitVector(tan1[a]);
|
||||
|
||||
Vector3.OrthoNormalize(ref n, ref t);
|
||||
|
||||
tangents[a].x = t.x;
|
||||
tangents[a].y = t.y;
|
||||
tangents[a].z = t.z;
|
||||
tangents[a].w = (Vector3.Dot(Vector3.Cross(n, t), tan2[a]) < 0.0f) ? -1.0f : 1.0f;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculate mesh normals without taking into account smoothing groups.
|
||||
/// </summary>
|
||||
/// <returns>A new array of the vertex normals.</returns>
|
||||
/// <seealso cref="CalculateNormals"/>
|
||||
/// <summary>
|
||||
/// Calculates the normals for a mesh, taking into account smoothing groups.
|
||||
/// </summary>
|
||||
/// <param name="mesh">The mesh to calculate normals for.</param>
|
||||
static void CalculateHardNormals(ProBuilderMesh mesh)
|
||||
{
|
||||
var vertexCount = mesh.vertexCount;
|
||||
var positions = mesh.positionsInternal;
|
||||
var faces = mesh.facesInternal;
|
||||
// s_CachedIntArray acts as the average count per-normal
|
||||
ClearIntArray(vertexCount);
|
||||
|
||||
if (!mesh.HasArrays(MeshArrays.Normal))
|
||||
mesh.normalsInternal = new Vector3[vertexCount];
|
||||
|
||||
var normals = mesh.normalsInternal;
|
||||
|
||||
for (int i = 0; i < vertexCount; i++)
|
||||
{
|
||||
normals[i].x = 0f;
|
||||
normals[i].y = 0f;
|
||||
normals[i].z = 0f;
|
||||
}
|
||||
|
||||
for (int faceIndex = 0, fc = faces.Length; faceIndex < fc; faceIndex++)
|
||||
{
|
||||
int[] indexes = faces[faceIndex].indexesInternal;
|
||||
|
||||
for (var tri = 0; tri < indexes.Length; tri += 3)
|
||||
{
|
||||
int a = indexes[tri], b = indexes[tri + 1], c = indexes[tri + 2];
|
||||
|
||||
Vector3 cross = Math.Normal(positions[a], positions[b], positions[c]);
|
||||
cross.Normalize();
|
||||
|
||||
normals[a].x += cross.x;
|
||||
normals[b].x += cross.x;
|
||||
normals[c].x += cross.x;
|
||||
|
||||
normals[a].y += cross.y;
|
||||
normals[b].y += cross.y;
|
||||
normals[c].y += cross.y;
|
||||
|
||||
normals[a].z += cross.z;
|
||||
normals[b].z += cross.z;
|
||||
normals[c].z += cross.z;
|
||||
|
||||
s_CachedIntArray[a]++;
|
||||
s_CachedIntArray[b]++;
|
||||
s_CachedIntArray[c]++;
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < vertexCount; i++)
|
||||
{
|
||||
normals[i].x = normals[i].x / s_CachedIntArray[i];
|
||||
normals[i].y = normals[i].y / s_CachedIntArray[i];
|
||||
normals[i].z = normals[i].z / s_CachedIntArray[i];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the normals for a mesh, taking into account smoothing groups.
|
||||
/// </summary>
|
||||
/// <param name="mesh">The mesh to calculate normals for.</param>
|
||||
public static void CalculateNormals(ProBuilderMesh mesh)
|
||||
{
|
||||
CalculateHardNormals(mesh);
|
||||
|
||||
var sharedVertices = mesh.sharedVerticesInternal;
|
||||
var faces = mesh.facesInternal;
|
||||
// CalculateHardNormals ensures that normals array is initialized
|
||||
var normals = mesh.normalsInternal;
|
||||
int smoothGroupMax = 24;
|
||||
// s_CachedIntArray acts as the smoothingGroup lookup for each vertex
|
||||
ClearIntArray(mesh.vertexCount);
|
||||
|
||||
// Create a lookup of each triangles smoothing group.
|
||||
for (int i = 0, c = mesh.faceCount; i < c; i++)
|
||||
{
|
||||
var face = faces[i];
|
||||
var indices = face.distinctIndexesInternal;
|
||||
|
||||
for (int n = 0, d = indices.Length; n < d; n++)
|
||||
{
|
||||
s_CachedIntArray[indices[n]] = face.smoothingGroup;
|
||||
|
||||
if (face.smoothingGroup >= smoothGroupMax)
|
||||
smoothGroupMax = face.smoothingGroup + 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Increase buffers size if we have more smoothing groups than usual.
|
||||
if (smoothGroupMax > s_SmoothAvg.Length)
|
||||
{
|
||||
Array.Resize(ref s_SmoothAvg, smoothGroupMax);
|
||||
Array.Resize(ref s_SmoothAvgCount, smoothGroupMax);
|
||||
}
|
||||
|
||||
// For each sharedIndexes group (individual vertex), find vertices that are in the same smoothing
|
||||
// group and average their normals.
|
||||
for (var i = 0; i < sharedVertices.Length; i++)
|
||||
{
|
||||
for (var n = 0; n < smoothGroupMax; n++)
|
||||
{
|
||||
s_SmoothAvg[n].x = 0f;
|
||||
s_SmoothAvg[n].y = 0f;
|
||||
s_SmoothAvg[n].z = 0f;
|
||||
s_SmoothAvgCount[n] = 0f;
|
||||
}
|
||||
|
||||
for (var n = 0; n < sharedVertices[i].Count; n++)
|
||||
{
|
||||
int index = sharedVertices[i][n];
|
||||
int group = s_CachedIntArray[index];
|
||||
|
||||
// Ideally this should only continue on group == NONE, but historically negative values have also
|
||||
// been treated as no smoothing.
|
||||
if (group <= Smoothing.smoothingGroupNone)
|
||||
continue;
|
||||
|
||||
s_SmoothAvg[group].x += normals[index].x;
|
||||
s_SmoothAvg[group].y += normals[index].y;
|
||||
s_SmoothAvg[group].z += normals[index].z;
|
||||
s_SmoothAvgCount[group] += 1f;
|
||||
}
|
||||
|
||||
for (int n = 0; n < sharedVertices[i].Count; n++)
|
||||
{
|
||||
int index = sharedVertices[i][n];
|
||||
int group = s_CachedIntArray[index];
|
||||
|
||||
if (group <= Smoothing.smoothingGroupNone)
|
||||
continue;
|
||||
|
||||
normals[index].x = s_SmoothAvg[group].x / s_SmoothAvgCount[group];
|
||||
normals[index].y = s_SmoothAvg[group].y / s_SmoothAvgCount[group];
|
||||
normals[index].z = s_SmoothAvg[group].z / s_SmoothAvgCount[group];
|
||||
normals[index] = Math.EnsureUnitVector(normals[index]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 13fe1d05e56c65b4ca8c935f648e6cbb
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,73 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace UnityEngine.ProBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Simple object pool implementation.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
sealed class ObjectPool<T> : IDisposable
|
||||
{
|
||||
bool m_IsDisposed;
|
||||
Queue<T> m_Pool = new Queue<T>();
|
||||
|
||||
public int desiredSize;
|
||||
public Func<T> constructor;
|
||||
public Action<T> destructor;
|
||||
|
||||
public ObjectPool(int initialSize, int desiredSize, Func<T> constructor, Action<T> destructor, bool lazyInitialization = false)
|
||||
{
|
||||
if (constructor == null)
|
||||
throw new ArgumentNullException("constructor");
|
||||
|
||||
if (destructor == null)
|
||||
throw new ArgumentNullException("destructor");
|
||||
|
||||
this.constructor = constructor;
|
||||
this.destructor = destructor;
|
||||
this.desiredSize = desiredSize;
|
||||
|
||||
for (int i = 0; i < initialSize && i < desiredSize && !lazyInitialization; i++)
|
||||
m_Pool.Enqueue(constructor());
|
||||
}
|
||||
|
||||
public T Dequeue()
|
||||
{
|
||||
if (m_Pool.Count > 0)
|
||||
return m_Pool.Dequeue();
|
||||
return constructor();
|
||||
}
|
||||
|
||||
public void Enqueue(T obj)
|
||||
{
|
||||
if (m_Pool.Count < desiredSize)
|
||||
m_Pool.Enqueue(obj);
|
||||
else
|
||||
destructor(obj);
|
||||
}
|
||||
|
||||
public void Empty()
|
||||
{
|
||||
int count = m_Pool.Count;
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
destructor(m_Pool.Dequeue());
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
}
|
||||
|
||||
void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing && !m_IsDisposed)
|
||||
{
|
||||
Empty();
|
||||
m_IsDisposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 72ae47ebb978a4f6fad1a2ba92c3c110
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,29 @@
|
||||
namespace UnityEngine.ProBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the location of the pivot point on a ProBuilderMesh object.
|
||||
/// </summary>
|
||||
public enum PivotPoint
|
||||
{
|
||||
/// <summary>
|
||||
/// Transforms are applied from the center point of the selection bounding box.
|
||||
/// Corresponds with <see cref="UnityEditor.PivotMode.Center"/>.
|
||||
/// </summary>
|
||||
Center,
|
||||
|
||||
/// <summary>
|
||||
/// Transforms are applied from the origin of each selection group.
|
||||
/// </summary>
|
||||
IndividualOrigins,
|
||||
|
||||
/// <summary>
|
||||
/// Transforms are applied from the active selection center.
|
||||
/// </summary>
|
||||
ActiveElement,
|
||||
|
||||
// /// <summary>
|
||||
// /// Transforms are applied from a user-defined pivot point.
|
||||
// /// </summary>
|
||||
// Custom
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3abdb0569285bbf4389d38655d5a90a3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,125 @@
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine.Serialization;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
|
||||
namespace UnityEngine.ProBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a component that handles the creation of <see cref="ProBuilderMesh"/> shapes
|
||||
/// from a set of contiguous points.
|
||||
/// </summary>
|
||||
[AddComponentMenu("")]
|
||||
[HelpURL(k_HelpUrl)]
|
||||
[DisallowMultipleComponent]
|
||||
[ExcludeFromPreset, ExcludeFromObjectFactory]
|
||||
[Icon(k_IconPath)]
|
||||
[ProGridsConditionalSnap]
|
||||
public sealed class PolyShape : MonoBehaviour
|
||||
{
|
||||
const string k_HelpUrl = "https://docs.unity3d.com/Packages/com.unity.probuilder@latest";
|
||||
|
||||
/// <summary>
|
||||
/// Describes the different input states this tool operates in.
|
||||
/// </summary>
|
||||
internal enum PolyEditMode
|
||||
{
|
||||
None,
|
||||
Path,
|
||||
Height,
|
||||
Edit
|
||||
}
|
||||
const string k_IconPath = "Packages/com.unity.probuilder/Editor Default Resources/Icons/EditableMesh/EditableMesh.png";
|
||||
|
||||
ProBuilderMesh m_Mesh;
|
||||
|
||||
[FormerlySerializedAs("points")]
|
||||
[SerializeField]
|
||||
internal List<Vector3> m_Points = new List<Vector3>();
|
||||
|
||||
[FormerlySerializedAs("extrude")]
|
||||
[SerializeField]
|
||||
float m_Extrude = 0f;
|
||||
|
||||
[FormerlySerializedAs("polyEditMode")]
|
||||
[SerializeField]
|
||||
PolyEditMode m_EditMode;
|
||||
|
||||
[FormerlySerializedAs("flipNormals")]
|
||||
[SerializeField]
|
||||
bool m_FlipNormals;
|
||||
|
||||
[SerializeField]
|
||||
internal bool isOnGrid = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the points that form the path for the base of this shape.
|
||||
/// </summary>
|
||||
public ReadOnlyCollection<Vector3> controlPoints
|
||||
{
|
||||
get { return new ReadOnlyCollection<Vector3>(m_Points); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the list of points that form the path for the base of this shape.
|
||||
/// </summary>
|
||||
/// <param name="points">List of positions that define this PolyShape.</param>
|
||||
public void SetControlPoints(IList<Vector3> points)
|
||||
{
|
||||
m_Points = points.ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the distance that this shape should extrude from the base. After setting this value,
|
||||
/// you need to invoke <see cref="MeshOperations.AppendElements.CreateShapeFromPolygon"/> to
|
||||
/// rebuild the <see cref="ProBuilderMesh"/> component.
|
||||
/// </summary>
|
||||
public float extrude
|
||||
{
|
||||
get { return m_Extrude; }
|
||||
set { m_Extrude = value; }
|
||||
}
|
||||
|
||||
internal PolyEditMode polyEditMode
|
||||
{
|
||||
get { return m_EditMode; }
|
||||
set { m_EditMode = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines the direction for this shape's normals. Use this to invert the normals, creating a
|
||||
/// volume with the normals facing inwards.
|
||||
/// </summary>
|
||||
public bool flipNormals
|
||||
{
|
||||
get { return m_FlipNormals; }
|
||||
set { m_FlipNormals = value; }
|
||||
}
|
||||
|
||||
internal ProBuilderMesh mesh
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_Mesh == null)
|
||||
m_Mesh = GetComponent<ProBuilderMesh>();
|
||||
|
||||
return m_Mesh;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
m_Mesh = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ProGridsConditionalSnap tells pg_Editor to reflect this value.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
bool IsSnapEnabled()
|
||||
{
|
||||
return isOnGrid;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bb977b126bc1b4f15813dcf9da9bb600
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,399 @@
|
||||
using UnityEngine;
|
||||
using System.Linq;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace UnityEngine.ProBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Store settings in a project local manner.
|
||||
/// </summary>
|
||||
sealed class PreferenceDictionary :
|
||||
ScriptableObject,
|
||||
ISerializationCallbackReceiver,
|
||||
IHasDefault
|
||||
{
|
||||
Dictionary<string, bool> m_Bool = new Dictionary<string, bool>();
|
||||
Dictionary<string, int> m_Int = new Dictionary<string, int>();
|
||||
Dictionary<string, float> m_Float = new Dictionary<string, float>();
|
||||
Dictionary<string, string> m_String = new Dictionary<string, string>();
|
||||
Dictionary<string, Color> m_Color = new Dictionary<string, Color>();
|
||||
Dictionary<string, Material> m_Material = new Dictionary<string, Material>();
|
||||
|
||||
[SerializeField] List<string> m_Bool_keys;
|
||||
[SerializeField] List<string> m_Int_keys;
|
||||
[SerializeField] List<string> m_Float_keys;
|
||||
[SerializeField] List<string> m_String_keys;
|
||||
[SerializeField] List<string> m_Color_keys;
|
||||
[SerializeField] List<string> m_Material_keys;
|
||||
|
||||
[SerializeField] List<bool> m_Bool_values;
|
||||
[SerializeField] List<int> m_Int_values;
|
||||
[SerializeField] List<float> m_Float_values;
|
||||
[SerializeField] List<string> m_String_values;
|
||||
[SerializeField] List<Color> m_Color_values;
|
||||
[SerializeField] List<Material> m_Material_values;
|
||||
|
||||
/// <summary>
|
||||
/// Perform the ritual "Please Serialize My Dictionary" dance.
|
||||
/// </summary>
|
||||
public void OnBeforeSerialize()
|
||||
{
|
||||
m_Bool_keys = m_Bool.Keys.ToList();
|
||||
m_Int_keys = m_Int.Keys.ToList();
|
||||
m_Float_keys = m_Float.Keys.ToList();
|
||||
m_String_keys = m_String.Keys.ToList();
|
||||
m_Color_keys = m_Color.Keys.ToList();
|
||||
m_Material_keys = m_Material.Keys.ToList();
|
||||
|
||||
m_Bool_values = m_Bool.Values.ToList();
|
||||
m_Int_values = m_Int.Values.ToList();
|
||||
m_Float_values = m_Float.Values.ToList();
|
||||
m_String_values = m_String.Values.ToList();
|
||||
m_Color_values = m_Color.Values.ToList();
|
||||
m_Material_values = m_Material.Values.ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reconstruct preference dictionaries from serialized lists.
|
||||
/// </summary>
|
||||
public void OnAfterDeserialize()
|
||||
{
|
||||
for (int i = 0; i < m_Bool_keys.Count; i++)
|
||||
m_Bool.Add(m_Bool_keys[i], m_Bool_values[i]);
|
||||
|
||||
for (int i = 0; i < m_Int_keys.Count; i++)
|
||||
m_Int.Add(m_Int_keys[i], m_Int_values[i]);
|
||||
|
||||
for (int i = 0; i < m_Float_keys.Count; i++)
|
||||
m_Float.Add(m_Float_keys[i], m_Float_values[i]);
|
||||
|
||||
for (int i = 0; i < m_String_keys.Count; i++)
|
||||
m_String.Add(m_String_keys[i], m_String_values[i]);
|
||||
|
||||
for (int i = 0; i < m_Color_keys.Count; i++)
|
||||
m_Color.Add(m_Color_keys[i], m_Color_values[i]);
|
||||
|
||||
for (int i = 0; i < m_Material_keys.Count; i++)
|
||||
m_Material.Add(m_Material_keys[i], m_Material_values[i]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clear dictionary values.
|
||||
/// </summary>
|
||||
public void SetDefaultValues()
|
||||
{
|
||||
m_Bool.Clear();
|
||||
m_Int.Clear();
|
||||
m_Float.Clear();
|
||||
m_String.Clear();
|
||||
m_Color.Clear();
|
||||
m_Material.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if a key is contained within any type dictionary.
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <returns></returns>
|
||||
public bool HasKey(string key)
|
||||
{
|
||||
return m_Bool.ContainsKey(key) ||
|
||||
m_Int.ContainsKey(key) ||
|
||||
m_Float.ContainsKey(key) ||
|
||||
m_String.ContainsKey(key) ||
|
||||
m_Color.ContainsKey(key) ||
|
||||
m_Material.ContainsKey(key);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test if specific type dictionary contains a key.
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <returns></returns>
|
||||
public bool HasKey<T>(string key)
|
||||
{
|
||||
System.Type type = typeof(T);
|
||||
|
||||
if (type == typeof(int))
|
||||
return m_Int.ContainsKey(key);
|
||||
else if (type == typeof(float))
|
||||
return m_Float.ContainsKey(key);
|
||||
else if (type == typeof(bool))
|
||||
return m_Bool.ContainsKey(key);
|
||||
else if (type == typeof(string))
|
||||
return m_String.ContainsKey(key);
|
||||
else if (type == typeof(Color))
|
||||
return m_Color.ContainsKey(key);
|
||||
else if (type == typeof(Material))
|
||||
return m_Material.ContainsKey(key);
|
||||
else
|
||||
{
|
||||
Debug.LogWarning(string.Format("HasKey<{0}>({1}) not valid preference type.",
|
||||
typeof(T).ToString(),
|
||||
key));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void DeleteKey(string key)
|
||||
{
|
||||
if (m_Bool.ContainsKey(key))
|
||||
m_Bool.Remove(key);
|
||||
if (m_Int.ContainsKey(key))
|
||||
m_Int.Remove(key);
|
||||
if (m_Float.ContainsKey(key))
|
||||
m_Float.Remove(key);
|
||||
if (m_String.ContainsKey(key))
|
||||
m_String.Remove(key);
|
||||
if (m_Color.ContainsKey(key))
|
||||
m_Color.Remove(key);
|
||||
if (m_Material.ContainsKey(key))
|
||||
m_Material.Remove(key);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// "Generic" Get preference for key function.
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="fallback"></param>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <returns></returns>
|
||||
public T Get<T>(string key, T fallback = default(T))
|
||||
{
|
||||
System.Type type = typeof(T);
|
||||
|
||||
if (type == typeof(int))
|
||||
{
|
||||
if (m_Int.ContainsKey(key))
|
||||
return (T)(object)GetInt(key);
|
||||
}
|
||||
else if (type == typeof(float))
|
||||
{
|
||||
if (m_Float.ContainsKey(key))
|
||||
return (T)(object)GetFloat(key);
|
||||
}
|
||||
else if (type == typeof(bool))
|
||||
{
|
||||
if (m_Bool.ContainsKey(key))
|
||||
return (T)(object)GetBool(key);
|
||||
}
|
||||
else if (type == typeof(string))
|
||||
{
|
||||
if (m_String.ContainsKey(key))
|
||||
return (T)(object)GetString(key);
|
||||
}
|
||||
else if (type == typeof(Color))
|
||||
{
|
||||
if (m_Color.ContainsKey(key))
|
||||
return (T)(object)GetColor(key);
|
||||
}
|
||||
else if (type == typeof(Material))
|
||||
{
|
||||
if (m_Material.ContainsKey(key))
|
||||
return (T)(object)GetMaterial(key);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning(string.Format("Get<{0}>({1}) not valid preference type.",
|
||||
typeof(T).ToString(),
|
||||
key));
|
||||
}
|
||||
|
||||
return fallback;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// "Generic" set value. Only accepts:
|
||||
/// - int,
|
||||
/// - float,
|
||||
/// - bool,
|
||||
/// - string,
|
||||
/// - Color,
|
||||
/// - Material
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="value"></param>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public void Set<T>(string key, T value)
|
||||
{
|
||||
object o = (object)value;
|
||||
|
||||
if (value is int)
|
||||
SetInt(key, (int)o);
|
||||
else if (value is float)
|
||||
SetFloat(key, (float)o);
|
||||
else if (value is bool)
|
||||
SetBool(key, (bool)o);
|
||||
else if (value is string)
|
||||
SetString(key, (string)o);
|
||||
else if (value is Color)
|
||||
SetColor(key, (Color)o);
|
||||
else if (value is Material)
|
||||
SetMaterial(key, (Material)o);
|
||||
else
|
||||
Debug.LogWarning(string.Format("Set<{0}>({1}, {2}) not valid preference type.",
|
||||
typeof(T).ToString(),
|
||||
key,
|
||||
value.ToString()));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fetch a value from the stored preferences. If key is not found, a default value is returned.
|
||||
/// </summary>
|
||||
public bool GetBool(string key, bool fallback = default(bool))
|
||||
{
|
||||
bool value;
|
||||
if (m_Bool.TryGetValue(key, out value))
|
||||
return value;
|
||||
return fallback;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fetch a value from the stored preferences. If key is not found, a default value is returned.
|
||||
/// </summary>
|
||||
public int GetInt(string key, int fallback = default(int))
|
||||
{
|
||||
int value;
|
||||
if (m_Int.TryGetValue(key, out value))
|
||||
return value;
|
||||
return fallback;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fetch a value from the stored preferences. If key is not found, a default value is returned.
|
||||
/// </summary>
|
||||
public float GetFloat(string key, float fallback = default(float))
|
||||
{
|
||||
float value;
|
||||
if (m_Float.TryGetValue(key, out value))
|
||||
return value;
|
||||
return fallback;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fetch a value from the stored preferences. If key is not found, a default value is returned.
|
||||
/// </summary>
|
||||
public string GetString(string key, string fallback = default(string))
|
||||
{
|
||||
string value;
|
||||
if (m_String.TryGetValue(key, out value))
|
||||
return value;
|
||||
return fallback;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fetch a value from the stored preferences. If key is not found, a default value is returned.
|
||||
/// </summary>
|
||||
public Color GetColor(string key, Color fallback = default(Color))
|
||||
{
|
||||
Color value;
|
||||
if (m_Color.TryGetValue(key, out value))
|
||||
return value;
|
||||
return fallback;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fetch a value from the stored preferences. If key is not found, a default value is returned.
|
||||
/// </summary>
|
||||
public Material GetMaterial(string key, Material fallback = default(Material))
|
||||
{
|
||||
Material value;
|
||||
if (m_Material.TryGetValue(key, out value))
|
||||
return value;
|
||||
return fallback;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set a value for key in the saved prefs.
|
||||
/// </summary>
|
||||
public void SetBool(string key, bool value)
|
||||
{
|
||||
m_Bool[key] = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set a value for key in the saved prefs.
|
||||
/// </summary>
|
||||
public void SetInt(string key, int value)
|
||||
{
|
||||
m_Int[key] = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set a value for key in the saved prefs.
|
||||
/// </summary>
|
||||
public void SetFloat(string key, float value)
|
||||
{
|
||||
m_Float[key] = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set a value for key in the saved prefs.
|
||||
/// </summary>
|
||||
public void SetString(string key, string value)
|
||||
{
|
||||
m_String[key] = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set a value for key in the saved prefs.
|
||||
/// </summary>
|
||||
public void SetColor(string key, Color value)
|
||||
{
|
||||
m_Color[key] = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set a value for key in the saved prefs.
|
||||
/// </summary>
|
||||
public void SetMaterial(string key, Material value)
|
||||
{
|
||||
m_Material[key] = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the internal <key, bool> dictionary.
|
||||
/// </summary>
|
||||
public Dictionary<string, bool> GetBoolDictionary() { return m_Bool; }
|
||||
|
||||
/// <summary>
|
||||
/// Get the internal <key, int> dictionary.
|
||||
/// </summary>
|
||||
public Dictionary<string, int> GetIntDictionary() { return m_Int; }
|
||||
|
||||
/// <summary>
|
||||
/// Get the internal <key, float> dictionary.
|
||||
/// </summary>
|
||||
public Dictionary<string, float> GetFloatDictionary() { return m_Float; }
|
||||
|
||||
/// <summary>
|
||||
/// Get the internal <key, string> dictionary.
|
||||
/// </summary>
|
||||
public Dictionary<string, string> GetStringDictionary() { return m_String; }
|
||||
|
||||
/// <summary>
|
||||
/// Get the internal <key, Color> dictionary.
|
||||
/// </summary>
|
||||
public Dictionary<string, Color> GetColorDictionary() { return m_Color; }
|
||||
|
||||
/// <summary>
|
||||
/// Get the internal <key, Material> dictionary.
|
||||
/// </summary>
|
||||
public Dictionary<string, Material> GetMaterialDictionary() { return m_Material; }
|
||||
|
||||
/// <summary>
|
||||
/// Clear all stored preference key value pairs.
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
m_Bool.Clear();
|
||||
m_Int.Clear();
|
||||
m_Float.Clear();
|
||||
m_String.Clear();
|
||||
m_Color.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c04d9be0543234e9cbb340e7ee259a5c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,242 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEngine.ProBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// A collection of constant values the ProBuilder uses regularly.
|
||||
/// </summary>
|
||||
static class PreferenceKeys
|
||||
{
|
||||
/// <summary>
|
||||
/// The product name ("ProBuilder" or "ProBuilder Basic" depending on assembly definition).
|
||||
/// </summary>
|
||||
public const string pluginTitle = "ProBuilder";
|
||||
|
||||
internal const float k_MaxPointDistanceFromControl = 20f;
|
||||
internal const char DEGREE_SYMBOL = (char)176;
|
||||
internal const char CMD_SUPER = '\u2318';
|
||||
internal const char CMD_SHIFT = '\u21E7';
|
||||
internal const char CMD_OPTION = '\u2325';
|
||||
internal const char CMD_ALT = '\u2387';
|
||||
internal const char CMD_DELETE = '\u232B';
|
||||
|
||||
internal static readonly Color proBuilderBlue = new Color(0f, .682f, .937f, 1f);
|
||||
internal static readonly Color proBuilderLightGray = new Color(.35f, .35f, .35f, .4f);
|
||||
internal static readonly Color proBuilderDarkGray = new Color(.1f, .1f, .1f, .3f);
|
||||
|
||||
/// <summary>
|
||||
/// The starting range for editor menu items.
|
||||
/// </summary>
|
||||
public const int menuEditor = 100;
|
||||
|
||||
/// <summary>
|
||||
/// The starting range for selection menu items.
|
||||
/// </summary>
|
||||
public const int menuSelection = 200;
|
||||
|
||||
/// <summary>
|
||||
/// Starting range for geometry menu actions.
|
||||
/// </summary>
|
||||
public const int menuGeometry = 200;
|
||||
|
||||
/// <summary>
|
||||
/// Starting range for action menu actions.
|
||||
/// </summary>
|
||||
public const int menuActions = 300;
|
||||
|
||||
/// <summary>
|
||||
/// Starting range for material color menu items.
|
||||
/// </summary>
|
||||
public const int menuMaterialColors = 400;
|
||||
|
||||
/// <summary>
|
||||
/// Starting range for vertex color menu items.
|
||||
/// </summary>
|
||||
public const int menuVertexColors = 400;
|
||||
|
||||
/// <summary>
|
||||
/// Starting range for repair menu items.
|
||||
/// </summary>
|
||||
public const int menuRepair = 600;
|
||||
|
||||
/// <summary>
|
||||
/// Starting range for other misc. menu items that belong in the ProBuilder menu subtree.
|
||||
/// </summary>
|
||||
public const int menuMisc = 600;
|
||||
|
||||
/// <summary>
|
||||
/// Starting range for export menu items.
|
||||
/// </summary>
|
||||
public const int menuExport = 800;
|
||||
|
||||
[Obsolete("Use Pref<T> or Settings class directly.")]
|
||||
internal const string pbDefaultEditLevel = "pbDefaultEditLevel";
|
||||
[Obsolete("Use Pref<T> or Settings class directly.")]
|
||||
internal const string pbDefaultSelectionMode = "pbDefaultSelectionMode";
|
||||
[Obsolete("Use Pref<T> or Settings class directly.")]
|
||||
internal const string pbHandleAlignment = "pbHandleAlignment";
|
||||
[Obsolete("Use Pref<T> or Settings class directly.")]
|
||||
internal const string pbVertexColorTool = "pbVertexColorTool";
|
||||
[Obsolete("Use Pref<T> or Settings class directly.")]
|
||||
internal const string pbToolbarLocation = "pbToolbarLocation";
|
||||
[Obsolete("Use Pref<T> or Settings class directly.")]
|
||||
internal const string pbDefaultEntity = "pbDefaultEntity";
|
||||
[Obsolete("Use Pref<T> or Settings class directly.")]
|
||||
internal const string pbExtrudeMethod = "pbExtrudeMethod";
|
||||
[Obsolete("Use Pref<T> or Settings class directly.")]
|
||||
internal const string pbDefaultStaticFlags = "pbDefaultStaticFlags";
|
||||
[Obsolete("Use Pref<T> or Settings class directly.")]
|
||||
internal const string pbSelectedFaceColor = "pbDefaultFaceColor";
|
||||
[Obsolete("Use Pref<T> or Settings class directly.")]
|
||||
internal const string pbWireframeColor = "pbDefaultEdgeColor";
|
||||
[Obsolete("Use Pref<T> or Settings class directly.")]
|
||||
internal const string pbUnselectedEdgeColor = "pbUnselectedEdgeColor";
|
||||
[Obsolete("Use Pref<T> or Settings class directly.")]
|
||||
internal const string pbSelectedEdgeColor = "pbSelectedEdgeColor";
|
||||
[Obsolete("Use Pref<T> or Settings class directly.")]
|
||||
internal const string pbSelectedVertexColor = "pbDefaultSelectedVertexColor";
|
||||
[Obsolete("Use Pref<T> or Settings class directly.")]
|
||||
internal const string pbUnselectedVertexColor = "pbDefaultVertexColor";
|
||||
[Obsolete("Use Pref<T> or Settings class directly.")]
|
||||
internal const string pbPreselectionColor = "pbPreselectionColor";
|
||||
[Obsolete("Use Pref<T> or Settings class directly.")]
|
||||
internal const string pbDefaultOpenInDockableWindow = "pbDefaultOpenInDockableWindow";
|
||||
[Obsolete("Use Pref<T> or Settings class directly.")]
|
||||
internal const string pbEditorPrefVersion = "pbEditorPrefVersion";
|
||||
[Obsolete("Use Pref<T> or Settings class directly.")]
|
||||
internal const string pbEditorShortcutsVersion = "pbEditorShortcutsVersion";
|
||||
[Obsolete("Use Pref<T> or Settings class directly.")]
|
||||
internal const string pbDefaultCollider = "pbDefaultCollider";
|
||||
[Obsolete("Use Pref<T> or Settings class directly.")]
|
||||
internal const string pbForceConvex = "pbForceConvex";
|
||||
[Obsolete("Use Pref<T> or Settings class directly.")]
|
||||
internal const string pbVertexColorPrefs = "pbVertexColorPrefs";
|
||||
[Obsolete("Use Pref<T> or Settings class directly.")]
|
||||
internal const string pbShowEditorNotifications = "pbShowEditorNotifications";
|
||||
[Obsolete("Use Pref<T> or Settings class directly.")]
|
||||
internal const string pbDragCheckLimit = "pbDragCheckLimit";
|
||||
[Obsolete("Use Pref<T> or Settings class directly.")]
|
||||
internal const string pbForceVertexPivot = "pbForceVertexPivot";
|
||||
[Obsolete("Use Pref<T> or Settings class directly.")]
|
||||
internal const string pbForceGridPivot = "pbForceGridPivot";
|
||||
[Obsolete("Use Pref<T> or Settings class directly.")]
|
||||
internal const string pbManifoldEdgeExtrusion = "pbManifoldEdgeExtrusion";
|
||||
[Obsolete("Use Pref<T> or Settings class directly.")]
|
||||
internal const string pbPerimeterEdgeBridgeOnly = "pbPerimeterEdgeBridgeOnly";
|
||||
[Obsolete("Use Pref<T> or Settings class directly.")]
|
||||
internal const string pbPBOSelectionOnly = "pbPBOSelectionOnly";
|
||||
[Obsolete("Use Pref<T> or Settings class directly.")]
|
||||
internal const string pbCloseShapeWindow = "pbCloseShapeWindow";
|
||||
[Obsolete("Use Pref<T> or Settings class directly.")]
|
||||
internal const string pbUVEditorFloating = "pbUVEditorFloating";
|
||||
[Obsolete("Use Pref<T> or Settings class directly.")]
|
||||
internal const string pbUVMaterialPreview = "pbUVMaterialPreview";
|
||||
[Obsolete("Use Pref<T> or Settings class directly.")]
|
||||
internal const string pbShowSceneToolbar = "pbShowSceneToolbar";
|
||||
[Obsolete("Use Pref<T> or Settings class directly.")]
|
||||
internal const string pbNormalizeUVsOnPlanarProjection = "pbNormalizeUVsOnPlanarProjection";
|
||||
[Obsolete("Use Pref<T> or Settings class directly.")]
|
||||
internal const string pbStripProBuilderOnBuild = "pbStripProBuilderOnBuild";
|
||||
[Obsolete("Use Pref<T> or Settings class directly.")]
|
||||
internal const string pbDisableAutoUV2Generation = "pbDisableAutoUV2Generation";
|
||||
[Obsolete("Use Pref<T> or Settings class directly.")]
|
||||
internal const string pbShowSceneInfo = "pbShowSceneInfo";
|
||||
[Obsolete("Use Pref<T> or Settings class directly.")]
|
||||
internal const string pbEnableBackfaceSelection = "pbEnableBackfaceSelection";
|
||||
[Obsolete("Use Pref<T> or Settings class directly.")]
|
||||
internal const string pbVertexPaletteDockable = "pbVertexPaletteDockable";
|
||||
[Obsolete("Use Pref<T> or Settings class directly.")]
|
||||
internal const string pbExtrudeAsGroup = "pbExtrudeAsGroup";
|
||||
[Obsolete("Use Pref<T> or Settings class directly.")]
|
||||
internal const string pbUniqueModeShortcuts = "pbUniqueModeShortcuts";
|
||||
[Obsolete("Use Pref<T> or Settings class directly.")]
|
||||
internal const string pbMaterialEditorFloating = "pbMaterialEditorFloating";
|
||||
[Obsolete("Use Pref<T> or Settings class directly.")]
|
||||
internal const string pbShapeWindowFloating = "pbShapeWindowFloating";
|
||||
[Obsolete("Use Pref<T> or Settings class directly.")]
|
||||
internal const string pbIconGUI = "pbIconGUI";
|
||||
[Obsolete("Use Pref<T> or Settings class directly.")]
|
||||
internal const string pbShiftOnlyTooltips = "pbShiftOnlyTooltips";
|
||||
[Obsolete("Use Pref<T> or Settings class directly.")]
|
||||
internal const string pbDrawAxisLines = "pbDrawAxisLines";
|
||||
[Obsolete("Use Pref<T> or Settings class directly.")]
|
||||
internal const string pbCollapseVertexToFirst = "pbCollapseVertexToFirst";
|
||||
[Obsolete("Use Pref<T> or Settings class directly.")]
|
||||
internal const string pbMeshesAreAssets = "pbMeshesAreAssets";
|
||||
[Obsolete("Use Pref<T> or Settings class directly.")]
|
||||
internal const string pbElementSelectIsHamFisted = "pbElementSelectIsHamFisted";
|
||||
[Obsolete("Use Pref<T> or Settings class directly.")]
|
||||
internal const string pbFillHoleSelectsEntirePath = "pbFillHoleSelectsEntirePath";
|
||||
[Obsolete("Use Pref<T> or Settings class directly.")]
|
||||
internal const string pbDetachToNewObject = "pbDetachToNewObject";
|
||||
[Obsolete("Use Pref<T> or Settings class directly.")]
|
||||
internal const string pbPreserveFaces = "pbPreserveFaces";
|
||||
[Obsolete("Use Pref<T> or Settings class directly.")]
|
||||
internal const string pbDragSelectWholeElement = "pbDragSelectWholeElement";
|
||||
[Obsolete("Use Pref<T> or Settings class directly.")]
|
||||
internal const string pbShowPreselectionHighlight = "pbShowPreselectionHighlight";
|
||||
[Obsolete("Use Pref<T> or Settings class directly.")]
|
||||
internal const string pbRectSelectMode = "pbRectSelectMode";
|
||||
[Obsolete("Use Pref<T> or Settings class directly.")]
|
||||
internal const string pbDragSelectMode = "pbDragSelectMode";
|
||||
[Obsolete("Use Pref<T> or Settings class directly.")]
|
||||
internal const string pbShadowCastingMode = "pbShadowCastingMode";
|
||||
[Obsolete("Use Pref<T> or Settings class directly.")]
|
||||
internal const string pbEnableExperimental = "pbEnableExperimental";
|
||||
[Obsolete("Use Pref<T> or Settings class directly.")]
|
||||
internal const string pbCheckForProBuilderUpdates = "pbCheckForProBuilderUpdates";
|
||||
[Obsolete("Use Pref<T> or Settings class directly.")]
|
||||
internal const string pbManageLightmappingStaticFlag = "pbManageLightmappingStaticFlag";
|
||||
[Obsolete("Use Pref<T> or Settings class directly.")]
|
||||
internal const string pbShowMissingLightmapUvWarning = "pb_Lightmapping::showMissingLightmapUvWarning";
|
||||
[Obsolete("Use Pref<T> or Settings class directly.")]
|
||||
internal const string pbSelectedFaceDither = "pbSelectedFaceDither";
|
||||
[Obsolete("Use Pref<T> or Settings class directly.")]
|
||||
internal const string pbUseUnityColors = "pbUseUnityColors";
|
||||
[Obsolete("Use Pref<T> or Settings class directly.")]
|
||||
internal const string pbVertexHandleSize = "pbVertexHandleSize";
|
||||
[Obsolete("Use Pref<T> or Settings class directly.")]
|
||||
internal const string pbUVGridSnapValue = "pbUVGridSnapValue";
|
||||
[Obsolete("Use Pref<T> or Settings class directly.")]
|
||||
internal const string pbUVWeldDistance = "pbUVWeldDistance";
|
||||
[Obsolete("Use Pref<T> or Settings class directly.")]
|
||||
internal const string pbLineHandleSize = "pbLineHandleSize";
|
||||
[Obsolete("Use Pref<T> or Settings class directly.")]
|
||||
internal const string pbWireframeSize = "pbWireframeSize";
|
||||
[Obsolete("Use Pref<T> or Settings class directly.")]
|
||||
internal const string pbWeldDistance = "pbWeldDistance";
|
||||
[Obsolete("Use Pref<T> or Settings class directly.")]
|
||||
internal const string pbExtrudeDistance = "pbExtrudeDistance";
|
||||
[Obsolete("Use Pref<T> or Settings class directly.")]
|
||||
internal const string pbBevelAmount = "pbBevelAmount";
|
||||
[Obsolete("Use Pref<T> or Settings class directly.")]
|
||||
internal const string pbEdgeSubdivisions = "pbEdgeSubdivisions";
|
||||
[Obsolete("Use Pref<T> or Settings class directly.")]
|
||||
internal const string pbDefaultShortcuts = "pbDefaultShortcuts";
|
||||
[Obsolete("Use Pref<T> or Settings class directly.")]
|
||||
internal const string pbDefaultMaterial = "pbDefaultMaterial";
|
||||
[Obsolete("Use Pref<T> or Settings class directly.")]
|
||||
internal const string pbCurrentMaterialPalette = "pbCurrentMaterialPalette";
|
||||
[Obsolete("Use Pref<T> or Settings class directly.")]
|
||||
internal const string pbGrowSelectionUsingAngle = "pbGrowSelectionUsingAngle";
|
||||
[Obsolete("Use Pref<T> or Settings class directly.")]
|
||||
internal const string pbGrowSelectionAngle = "pbGrowSelectionAngle";
|
||||
[Obsolete("Use Pref<T> or Settings class directly.")]
|
||||
internal const string pbGrowSelectionAngleIterative = "pbGrowSelectionAngleIterative";
|
||||
[Obsolete("Use Pref<T> or Settings class directly.")]
|
||||
internal const string pbShowDetail = "pbShowDetail";
|
||||
[Obsolete("Use Pref<T> or Settings class directly.")]
|
||||
internal const string pbShowOccluder = "pbShowOccluder";
|
||||
[Obsolete("Use Pref<T> or Settings class directly.")]
|
||||
internal const string pbShowMover = "pbShowMover";
|
||||
[Obsolete("Use Pref<T> or Settings class directly.")]
|
||||
internal const string pbShowCollider = "pbShowCollider";
|
||||
[Obsolete("Use Pref<T> or Settings class directly.")]
|
||||
internal const string pbShowTrigger = "pbShowTrigger";
|
||||
[Obsolete("Use Pref<T> or Settings class directly.")]
|
||||
internal const string pbShowNoDraw = "pbShowNoDraw";
|
||||
[Obsolete("Use Pref<T> or Settings class directly.")]
|
||||
internal const string defaultUnwrapParameters = "pbDefaultUnwrapParameters";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 914c2d056412b4f1ca64740fc2cb1c66
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,447 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
|
||||
namespace UnityEngine.ProBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines what objects are selectable for the scene tool.
|
||||
/// </summary>
|
||||
[System.Flags]
|
||||
public enum SelectMode
|
||||
{
|
||||
/// <summary>
|
||||
/// No selection mode defined.
|
||||
/// </summary>
|
||||
None = 0x0,
|
||||
|
||||
/// <summary>
|
||||
/// Vertices are selectable.
|
||||
/// </summary>
|
||||
Vertex = 1 << 0,
|
||||
|
||||
/// <summary>
|
||||
/// Edges are selectable.
|
||||
/// </summary>
|
||||
Edge = 1 << 1,
|
||||
|
||||
/// <summary>
|
||||
/// Faces are selectable.
|
||||
/// </summary>
|
||||
Face = 1 << 2,
|
||||
|
||||
/// <summary>
|
||||
/// Texture coordinates are selectable.
|
||||
/// </summary>
|
||||
TextureVertex = 1 << 3,
|
||||
|
||||
/// <summary>
|
||||
/// Texture coordinates are selectable.
|
||||
/// </summary>
|
||||
TextureEdge = 1 << 4,
|
||||
|
||||
/// <summary>
|
||||
/// Texture coordinates are selectable.
|
||||
/// </summary>
|
||||
TextureFace = 1 << 5,
|
||||
|
||||
/// <summary>
|
||||
/// Other input tool (Poly Shape editor, Bezier editor, etc)
|
||||
/// </summary>
|
||||
InputTool = 1 << 6,
|
||||
|
||||
/// <summary>
|
||||
/// Match any value.
|
||||
/// </summary>
|
||||
Any = 0xFF
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines the element selection mode.
|
||||
/// </summary>
|
||||
enum ComponentMode
|
||||
{
|
||||
/// <summary>
|
||||
/// Vertices are selectable.
|
||||
/// </summary>
|
||||
Vertex = 0x0,
|
||||
/// <summary>
|
||||
/// Edges are selectable.
|
||||
/// </summary>
|
||||
Edge = 0x1,
|
||||
/// <summary>
|
||||
/// Faces are selectable.
|
||||
/// </summary>
|
||||
Face = 0x2
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines what the current tool interacts with in the scene view.
|
||||
/// </summary>'
|
||||
internal enum EditLevel
|
||||
{
|
||||
/// <summary>
|
||||
/// The transform tools interact with GameObjects.
|
||||
/// </summary>
|
||||
Top = 0,
|
||||
/// <summary>
|
||||
/// The current tool interacts with mesh geometry (faces, edges, vertices).
|
||||
/// </summary>
|
||||
Geometry = 1,
|
||||
/// <summary>
|
||||
/// Tools are affecting mesh UVs. This corresponds to UVEditor in-scene editing.
|
||||
/// </summary>
|
||||
Texture = 2,
|
||||
/// <summary>
|
||||
/// A custom ProBuilder tool mode is engaged.
|
||||
/// </summary>
|
||||
Plugin = 3
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines what GameObject flags this object has.
|
||||
/// </summary>
|
||||
enum EntityType
|
||||
{
|
||||
/// <summary>
|
||||
/// The "Detail" flag.
|
||||
/// </summary>
|
||||
Detail,
|
||||
/// <summary>
|
||||
/// The "Occluder" flag.
|
||||
/// </summary>
|
||||
Occluder,
|
||||
/// <summary>
|
||||
/// The "Trigger" flag.
|
||||
/// </summary>
|
||||
Trigger,
|
||||
/// <summary>
|
||||
/// The "Collider" flag.
|
||||
/// </summary>
|
||||
Collider,
|
||||
/// <summary>
|
||||
/// The "Mover" flag.
|
||||
/// </summary>
|
||||
Mover
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines what kind of collider this object has.
|
||||
/// </summary>
|
||||
enum ColliderType
|
||||
{
|
||||
/// <summary>
|
||||
/// The object has no collider.
|
||||
/// </summary>
|
||||
None,
|
||||
/// <summary>
|
||||
/// The object's collider shape is a box.
|
||||
/// </summary>
|
||||
BoxCollider,
|
||||
/// <summary>
|
||||
/// The object's collider shape matches the mesh shape.
|
||||
/// </summary>
|
||||
MeshCollider
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates the axis used for projecting UVs.
|
||||
/// </summary>
|
||||
public enum ProjectionAxis
|
||||
{
|
||||
/// <summary>
|
||||
/// Projects on the positive x-axis.
|
||||
/// </summary>
|
||||
X,
|
||||
/// <summary>
|
||||
/// Projects on the positive y-axis.
|
||||
/// </summary>
|
||||
Y,
|
||||
/// <summary>
|
||||
/// Projects on the positive z-axis.
|
||||
/// </summary>
|
||||
Z,
|
||||
/// <summary>
|
||||
/// Projects on the negative x-axis.
|
||||
/// </summary>
|
||||
XNegative,
|
||||
/// <summary>
|
||||
/// Projects on the negative y-axis.
|
||||
/// </summary>
|
||||
YNegative,
|
||||
/// <summary>
|
||||
/// Projects on the negative z-axis.
|
||||
/// </summary>
|
||||
ZNegative
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates which grid axis is active.
|
||||
/// </summary>
|
||||
enum HandleAxis
|
||||
{
|
||||
/// <summary>
|
||||
/// X-axis is active.
|
||||
/// </summary>
|
||||
X = 1 << 0,
|
||||
/// <summary>
|
||||
/// Y-axis is active.
|
||||
/// </summary>
|
||||
Y = 1 << 1,
|
||||
/// <summary>
|
||||
/// Z-axis is active.
|
||||
/// </summary>
|
||||
Z = 1 << 2,
|
||||
/// <summary>
|
||||
/// All axes are active simultaneously.
|
||||
/// </summary>
|
||||
Free = 1 << 3
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Describes the axis in human-readable terms.
|
||||
/// </summary>
|
||||
public enum Axis
|
||||
{
|
||||
/// <summary>
|
||||
/// X axis.
|
||||
/// </summary>
|
||||
Right,
|
||||
/// <summary>
|
||||
/// -X axis.
|
||||
/// </summary>
|
||||
Left,
|
||||
/// <summary>
|
||||
/// Y axis.
|
||||
/// </summary>
|
||||
Up,
|
||||
/// <summary>
|
||||
/// -Y axis.
|
||||
/// </summary>
|
||||
Down,
|
||||
/// <summary>
|
||||
/// Z axis.
|
||||
/// </summary>
|
||||
Forward,
|
||||
/// <summary>
|
||||
/// -Z axis.
|
||||
/// </summary>
|
||||
Backward
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Describes the winding order of mesh triangles.
|
||||
/// </summary>
|
||||
public enum WindingOrder
|
||||
{
|
||||
/// <summary>
|
||||
/// Winding order could not be determined.
|
||||
/// </summary>
|
||||
Unknown,
|
||||
/// <summary>
|
||||
/// Winding is clockwise (right handed).
|
||||
/// </summary>
|
||||
Clockwise,
|
||||
/// <summary>
|
||||
/// Winding is counter-clockwise (left handed).
|
||||
/// </summary>
|
||||
CounterClockwise
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Describes methods of sorting points in 2d space.
|
||||
/// </summary>
|
||||
public enum SortMethod
|
||||
{
|
||||
/// <summary>
|
||||
/// Order the vertices clockwise.
|
||||
/// </summary>
|
||||
Clockwise,
|
||||
/// <summary>
|
||||
/// Order the vertices counter-clockwise.
|
||||
/// </summary>
|
||||
CounterClockwise
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Defines the triangle culling mode.
|
||||
/// </summary>
|
||||
[System.Flags]
|
||||
public enum CullingMode
|
||||
{
|
||||
/// <summary>
|
||||
/// Both front and back faces are rendered.
|
||||
/// </summary>
|
||||
None = 0 << 0,
|
||||
/// <summary>
|
||||
/// Back faces are culled.
|
||||
/// </summary>
|
||||
Back = 1 << 0,
|
||||
/// <summary>
|
||||
/// Front faces are culled.
|
||||
/// </summary>
|
||||
Front = 1 << 1,
|
||||
/// <summary>
|
||||
/// Both front and back faces are culled.
|
||||
/// </summary>
|
||||
FrontBack = Front | Back,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines the behavior of drag selection in the Scene view for mesh elements.
|
||||
/// </summary>
|
||||
public enum RectSelectMode
|
||||
{
|
||||
/// <summary>
|
||||
/// Selects any mesh element that touches the drag rectangle.
|
||||
/// </summary>
|
||||
Partial,
|
||||
/// <summary>
|
||||
/// Selects only those mesh elements that are completely enveloped by the drag rect.
|
||||
/// </summary>
|
||||
Complete
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Describes why a <see cref="ProBuilderMesh"/> is out of sync with its <see cref="UnityEngine.MeshFilter"/> component.
|
||||
/// </summary>
|
||||
public enum MeshSyncState
|
||||
{
|
||||
/// <summary>
|
||||
/// The MeshFilter mesh is null.
|
||||
/// </summary>
|
||||
Null,
|
||||
/// <summary>
|
||||
/// The MeshFilter mesh is not owned by the ProBuilderMesh component.
|
||||
/// To fix this, use <see cref="ProBuilderMesh.MakeUnique"/>.
|
||||
/// </summary>
|
||||
/// <remarks>This is only used in the Editor.</remarks>
|
||||
[Obsolete("InstanceIDMismatch is no longer used. Mesh references are not tracked by Instance ID.")]
|
||||
InstanceIDMismatch,
|
||||
/// <summary>
|
||||
/// The mesh is valid, but does not have a UV2 channel.
|
||||
/// </summary>
|
||||
/// <remarks>This is only used in the Editor.</remarks>
|
||||
Lightmap,
|
||||
/// <summary>
|
||||
/// The mesh is in sync.
|
||||
/// </summary>
|
||||
InSync,
|
||||
/// <summary>
|
||||
/// The component data is not up to date with the compiled mesh.
|
||||
/// </summary>
|
||||
NeedsRebuild
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines a bitmask describing the mesh attributes.
|
||||
/// </summary>
|
||||
[System.Flags]
|
||||
public enum MeshArrays
|
||||
{
|
||||
/// <summary>
|
||||
/// Vertex positions.
|
||||
/// </summary>
|
||||
Position = 0x1,
|
||||
/// <summary>
|
||||
/// First UV channel.
|
||||
/// </summary>
|
||||
Texture0 = 0x2,
|
||||
/// <summary>
|
||||
/// Second UV channel. Commonly called UV2 or Lightmap UVs in Unity terms.
|
||||
/// </summary>
|
||||
Texture1 = 0x4,
|
||||
/// <summary>
|
||||
/// Second UV channel. Commonly called UV2 or Lightmap UVs in Unity terms.
|
||||
/// </summary>
|
||||
Lightmap = 0x4,
|
||||
/// <summary>
|
||||
/// Third UV channel.
|
||||
/// </summary>
|
||||
Texture2 = 0x8,
|
||||
/// <summary>
|
||||
/// Vertex UV4.
|
||||
/// </summary>
|
||||
Texture3 = 0x10,
|
||||
/// <summary>
|
||||
/// Vertex colors.
|
||||
/// </summary>
|
||||
Color = 0x20,
|
||||
/// <summary>
|
||||
/// Vertex normals.
|
||||
/// </summary>
|
||||
Normal = 0x40,
|
||||
/// <summary>
|
||||
/// Vertex tangents.
|
||||
/// </summary>
|
||||
Tangent = 0x80,
|
||||
/// <summary>
|
||||
/// All ProBuilder stored mesh attributes.
|
||||
/// </summary>
|
||||
All = 0xFF
|
||||
};
|
||||
|
||||
enum IndexFormat
|
||||
{
|
||||
Local = 0x0,
|
||||
Common = 0x1,
|
||||
Both = 0x2
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Selectively rebuilds and applies mesh attributes to the UnityEngine.Mesh asset.
|
||||
/// </summary>
|
||||
/// <seealso cref="ProBuilderMesh.Refresh"/>
|
||||
[System.Flags]
|
||||
public enum RefreshMask
|
||||
{
|
||||
/// <summary>
|
||||
/// Rebuild textures channel.
|
||||
/// </summary>
|
||||
UV = 0x1,
|
||||
/// <summary>
|
||||
/// Rebuild colors.
|
||||
/// </summary>
|
||||
Colors = 0x2,
|
||||
/// <summary>
|
||||
/// Recalculate and apply normals.
|
||||
/// </summary>
|
||||
Normals = 0x4,
|
||||
/// <summary>
|
||||
/// Recalculate and apply tangents.
|
||||
/// </summary>
|
||||
Tangents = 0x8,
|
||||
/// <summary>
|
||||
/// Re-assign the MeshCollider sharedMesh.
|
||||
/// </summary>
|
||||
Collisions = 0x10,
|
||||
/// <summary>
|
||||
/// Recalculate bounds.
|
||||
/// </summary>
|
||||
Bounds = 0x16,
|
||||
/// <summary>
|
||||
/// Refresh all optional mesh attributes.
|
||||
/// </summary>
|
||||
All = UV | Colors | Normals | Tangents | Collisions | Bounds
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Describes the different methods of face extrusion.
|
||||
/// </summary>
|
||||
/// <seealso cref="MeshOperations.ExtrudeElements"/>
|
||||
public enum ExtrudeMethod
|
||||
{
|
||||
/// <summary>
|
||||
/// Extrude each face separately.
|
||||
/// </summary>
|
||||
IndividualFaces = 0,
|
||||
/// <summary>
|
||||
/// Merge adjacent faces as a group along the averaged normals.
|
||||
/// </summary>
|
||||
VertexNormal = 1,
|
||||
/// <summary>
|
||||
/// Merge adjacent faces as a group, but extrude faces from each face normal.
|
||||
/// </summary>
|
||||
FaceNormal = 2
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 42d1d51e272f34868beefb1d9a2688f6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8233d90336aea43098adf6dbabd606a2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: e1ef768b00f5ea543a25f2bf9c1f9070, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+1004
File diff suppressed because it is too large
Load Diff
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cd856323da4211845ae7b4c7a54b294f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+316
@@ -0,0 +1,316 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace UnityEngine.ProBuilder
|
||||
{
|
||||
public sealed partial class ProBuilderMesh
|
||||
{
|
||||
[SerializeField]
|
||||
bool m_IsSelectable = true;
|
||||
|
||||
// Serialized for undo in the editor
|
||||
[SerializeField]
|
||||
int[] m_SelectedFaces = new int[] {};
|
||||
[SerializeField]
|
||||
Edge[] m_SelectedEdges = new Edge[] {};
|
||||
[SerializeField]
|
||||
int[] m_SelectedVertices = new int[] {};
|
||||
|
||||
bool m_SelectedCacheDirty;
|
||||
int m_SelectedSharedVerticesCount = 0;
|
||||
int m_SelectedCoincidentVertexCount = 0;
|
||||
HashSet<int> m_SelectedSharedVertices = new HashSet<int>();
|
||||
List<int> m_SelectedCoincidentVertices = new List<int>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether elements can be selected.
|
||||
/// Used by <see cref="UnityEditor.ProBuilder.ProBuilderEditor" />.
|
||||
/// </summary>
|
||||
/// <value>False if mesh elements are not selectable. </value>
|
||||
public bool selectable
|
||||
{
|
||||
get { return m_IsSelectable; }
|
||||
set { m_IsSelectable = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of faces that are currently selected on this object.
|
||||
/// </summary>
|
||||
/// <value>Number of selected faces. </value>
|
||||
public int selectedFaceCount
|
||||
{
|
||||
get { return m_SelectedFaces.Length; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of selected vertex indices.
|
||||
/// </summary>
|
||||
/// <value>Number of selected vertices. </value>
|
||||
public int selectedVertexCount
|
||||
{
|
||||
get { return m_SelectedVertices.Length; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of selected edges.
|
||||
/// </summary>
|
||||
/// <value>Number of selected edges. </value>
|
||||
public int selectedEdgeCount
|
||||
{
|
||||
get { return m_SelectedEdges.Length; }
|
||||
}
|
||||
|
||||
internal int selectedSharedVerticesCount
|
||||
{
|
||||
get
|
||||
{
|
||||
CacheSelection();
|
||||
return m_SelectedSharedVerticesCount;
|
||||
}
|
||||
}
|
||||
|
||||
internal int selectedCoincidentVertexCount
|
||||
{
|
||||
get
|
||||
{
|
||||
CacheSelection();
|
||||
return m_SelectedCoincidentVertexCount;
|
||||
}
|
||||
}
|
||||
|
||||
internal IEnumerable<int> selectedSharedVertices
|
||||
{
|
||||
get
|
||||
{
|
||||
CacheSelection();
|
||||
return m_SelectedSharedVertices;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all selected vertices and their coincident neighbors.
|
||||
/// </summary>
|
||||
internal IEnumerable<int> selectedCoincidentVertices
|
||||
{
|
||||
get
|
||||
{
|
||||
CacheSelection();
|
||||
return m_SelectedCoincidentVertices;
|
||||
}
|
||||
}
|
||||
|
||||
void CacheSelection()
|
||||
{
|
||||
if (m_SelectedCacheDirty)
|
||||
{
|
||||
m_SelectedCacheDirty = false;
|
||||
m_SelectedSharedVertices.Clear();
|
||||
m_SelectedCoincidentVertices.Clear();
|
||||
var lookup = sharedVertexLookup;
|
||||
m_SelectedSharedVerticesCount = 0;
|
||||
m_SelectedCoincidentVertexCount = 0;
|
||||
|
||||
try
|
||||
{
|
||||
foreach (var i in m_SelectedVertices)
|
||||
{
|
||||
if (m_SelectedSharedVertices.Add(lookup[i]))
|
||||
{
|
||||
var coincident = sharedVerticesInternal[lookup[i]];
|
||||
|
||||
m_SelectedSharedVerticesCount++;
|
||||
m_SelectedCoincidentVertexCount += coincident.Count;
|
||||
|
||||
m_SelectedCoincidentVertices.AddRange(coincident);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
ClearSelection();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a copy of the array of selected faces.
|
||||
/// </summary>
|
||||
/// <returns>Array of currently selected faces. </returns>
|
||||
public Face[] GetSelectedFaces()
|
||||
{
|
||||
int len = m_SelectedFaces.Length;
|
||||
var selected = new Face[len];
|
||||
for (var i = 0; i < len; i++)
|
||||
selected[i] = m_Faces[m_SelectedFaces[i]];
|
||||
return selected;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a collection of the currently selected faces by their index in the <see cref="ProBuilderMesh.faces" /> array.
|
||||
/// </summary>
|
||||
/// <value>Array of indices representing the currently selected faces. </value>
|
||||
public ReadOnlyCollection<int> selectedFaceIndexes
|
||||
{
|
||||
get { return new ReadOnlyCollection<int>(m_SelectedFaces); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a collection of the currently selected vertices by their index in the <see cref="ProBuilderMesh.positions" /> array.
|
||||
/// </summary>
|
||||
/// <value>Array of indices representing the currently selected vertices. </value>
|
||||
public ReadOnlyCollection<int> selectedVertices
|
||||
{
|
||||
get { return new ReadOnlyCollection<int>(m_SelectedVertices); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a collection of the currently selected edges.
|
||||
/// </summary>
|
||||
/// <value>Collection of <see cref="Edge" /> objects representing the currently selected edges. </value>
|
||||
public ReadOnlyCollection<Edge> selectedEdges
|
||||
{
|
||||
get { return new ReadOnlyCollection<Edge>(m_SelectedEdges); }
|
||||
}
|
||||
|
||||
internal Face[] selectedFacesInternal
|
||||
{
|
||||
get { return GetSelectedFaces(); }
|
||||
set { m_SelectedFaces = value.Select(x => Array.IndexOf(m_Faces, x)).ToArray(); }
|
||||
}
|
||||
|
||||
internal int[] selectedFaceIndicesInternal
|
||||
{
|
||||
get { return m_SelectedFaces; }
|
||||
set { m_SelectedFaces = value; }
|
||||
}
|
||||
|
||||
internal Edge[] selectedEdgesInternal
|
||||
{
|
||||
get { return m_SelectedEdges; }
|
||||
set { m_SelectedEdges = value; }
|
||||
}
|
||||
|
||||
internal int[] selectedIndexesInternal
|
||||
{
|
||||
get { return m_SelectedVertices; }
|
||||
set { m_SelectedVertices = value; }
|
||||
}
|
||||
|
||||
internal Face GetActiveFace()
|
||||
{
|
||||
if (selectedFaceCount < 1)
|
||||
return null;
|
||||
return m_Faces[selectedFaceIndicesInternal[selectedFaceCount - 1]];
|
||||
}
|
||||
|
||||
internal Edge GetActiveEdge()
|
||||
{
|
||||
if (selectedEdgeCount < 1)
|
||||
return Edge.Empty;
|
||||
return m_SelectedEdges[selectedEdgeCount - 1];
|
||||
}
|
||||
|
||||
internal int GetActiveVertex()
|
||||
{
|
||||
if (selectedVertexCount < 1)
|
||||
return -1;
|
||||
return m_SelectedVertices[selectedVertexCount - 1];
|
||||
}
|
||||
|
||||
internal void AddToFaceSelection(int index)
|
||||
{
|
||||
if (index > -1)
|
||||
SetSelectedFaces(m_SelectedFaces.Add(index));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the face selection for this mesh. Also sets the vertex and edge selection to match.
|
||||
/// </summary>
|
||||
/// <param name="selected">A set of faces to select.</param>
|
||||
public void SetSelectedFaces(IEnumerable<Face> selected)
|
||||
{
|
||||
SetSelectedFaces(selected != null ? selected.Select(x => Array.IndexOf(facesInternal, x)) : null);
|
||||
}
|
||||
|
||||
internal void SetSelectedFaces(IEnumerable<int> selected)
|
||||
{
|
||||
if (selected == null)
|
||||
{
|
||||
ClearSelection();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_SelectedFaces = selected.ToArray();
|
||||
m_SelectedVertices = m_SelectedFaces.SelectMany(x => facesInternal[x].distinctIndexesInternal).ToArray();
|
||||
m_SelectedEdges = m_SelectedFaces.SelectMany(x => facesInternal[x].edges).ToArray();
|
||||
}
|
||||
|
||||
m_SelectedCacheDirty = true;
|
||||
|
||||
if (elementSelectionChanged != null)
|
||||
elementSelectionChanged(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the edge selection for this mesh. Also sets the vertex selection to match and clears the selected faces.
|
||||
/// </summary>
|
||||
/// <param name="edges">A set of edges to select.</param>
|
||||
public void SetSelectedEdges(IEnumerable<Edge> edges)
|
||||
{
|
||||
if (edges == null)
|
||||
{
|
||||
ClearSelection();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_SelectedFaces = new int[0];
|
||||
m_SelectedEdges = edges.ToArray();
|
||||
m_SelectedVertices = m_SelectedEdges.AllTriangles();
|
||||
}
|
||||
|
||||
m_SelectedCacheDirty = true;
|
||||
|
||||
if (elementSelectionChanged != null)
|
||||
elementSelectionChanged(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the selected vertices array. Clears the selected faces and selected edges arrays.
|
||||
/// </summary>
|
||||
/// <param name="vertices">The new vertices to select.</param>
|
||||
public void SetSelectedVertices(IEnumerable<int> vertices)
|
||||
{
|
||||
m_SelectedFaces = new int[0];
|
||||
m_SelectedEdges = new Edge[0];
|
||||
m_SelectedVertices = vertices != null ? vertices.Distinct().ToArray() : new int[0];
|
||||
|
||||
m_SelectedCacheDirty = true;
|
||||
|
||||
if (elementSelectionChanged != null)
|
||||
elementSelectionChanged(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the specified face from the selection. Also updates the SelectedTriangles and SelectedEdges arrays to match.
|
||||
/// </summary>
|
||||
/// <param name="index">The index from the selected faces array that corresponds to the face to remove from the selection.</param>
|
||||
internal void RemoveFromFaceSelectionAtIndex(int index)
|
||||
{
|
||||
SetSelectedFaces(m_SelectedFaces.RemoveAt(index));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears the arrays of selected faces, edges, and vertices. You don't need to call this when setting
|
||||
/// an individual array, as the setter methods handle updating the associated caches.
|
||||
/// </summary>
|
||||
public void ClearSelection()
|
||||
{
|
||||
m_SelectedFaces = new int[0];
|
||||
m_SelectedEdges = new Edge[0];
|
||||
m_SelectedVertices = new int[0];
|
||||
m_SelectedCacheDirty = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 957c62bbae19ee54cb800714912d2a2a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,97 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace UnityEngine.ProBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Snapping functions (didn't exist in UnityEngine prior to 2019.3)
|
||||
/// </summary>
|
||||
static class ProBuilderSnapping
|
||||
{
|
||||
const float k_MaxRaySnapDistance = Mathf.Infinity;
|
||||
|
||||
internal static bool IsCardinalDirection(Vector3 direction)
|
||||
{
|
||||
return
|
||||
Mathf.Abs(direction.x) > 0f && Mathf.Approximately(direction.y, 0f) && Mathf.Approximately(direction.z, 0f)
|
||||
|| Mathf.Abs(direction.y) > 0f && Mathf.Approximately(direction.x, 0f) && Mathf.Approximately(direction.z, 0f)
|
||||
|| Mathf.Abs(direction.z) > 0f && Mathf.Approximately(direction.x, 0f) && Mathf.Approximately(direction.y, 0f);
|
||||
}
|
||||
|
||||
public static float Snap(float val, float snap)
|
||||
{
|
||||
if (snap == 0)
|
||||
return val;
|
||||
|
||||
return snap * Mathf.Round(val / snap);
|
||||
}
|
||||
|
||||
public static Vector3 Snap(Vector3 val, Vector3 snap)
|
||||
{
|
||||
return new Vector3(
|
||||
(Mathf.Abs(snap.x) > 0.0001f) ? Snap(val.x, snap.x) : val.x,
|
||||
(Mathf.Abs(snap.y) > 0.0001f) ? Snap(val.y, snap.y) : val.y,
|
||||
(Mathf.Abs(snap.z) > 0.0001f) ? Snap(val.z, snap.z) : val.z
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Snap all vertices to an increment of @snapValue in world space.
|
||||
/// </summary>
|
||||
/// <param name="mesh"></param>
|
||||
/// <param name="indexes"></param>
|
||||
/// <param name="snap"></param>
|
||||
public static void SnapVertices(ProBuilderMesh mesh, IEnumerable<int> indexes, Vector3 snap)
|
||||
{
|
||||
Vector3[] verts = mesh.positionsInternal;
|
||||
|
||||
foreach (var v in indexes)
|
||||
verts[v] = mesh.transform.InverseTransformPoint(Snap(mesh.transform.TransformPoint(verts[v]), snap));
|
||||
}
|
||||
|
||||
internal static Vector3 GetSnappingMaskBasedOnNormalVector(Vector3 normal)
|
||||
{
|
||||
return new Vector3(
|
||||
(Mathf.Approximately(Mathf.Abs(normal.x), 1f)) ? 0f : 1f,
|
||||
(Mathf.Approximately(Mathf.Abs(normal.y), 1f)) ? 0f : 1f,
|
||||
(Mathf.Approximately(Mathf.Abs(normal.z), 1f)) ? 0f : 1f);
|
||||
}
|
||||
|
||||
internal static Vector3 SnapValueOnRay(Ray ray, float distance, float snap, Vector3Mask mask)
|
||||
{
|
||||
var nearest = k_MaxRaySnapDistance;
|
||||
|
||||
var forwardRay = new Ray(ray.origin, ray.direction);
|
||||
var backwardsRay = new Ray(ray.origin, -ray.direction);
|
||||
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
if (mask[i] > 0f)
|
||||
{
|
||||
var dir = new Vector3Mask(new Vector3Mask((byte) (1 << i)));
|
||||
|
||||
var prj = Vector3.Project(
|
||||
ray.direction * Math.MakeNonZero(distance),
|
||||
dir * Mathf.Sign(ray.direction[i]));
|
||||
|
||||
var pnt = ray.origin + prj;
|
||||
var plane = new Plane(dir, Snap(pnt, dir * snap));
|
||||
|
||||
if(Mathf.Abs(plane.GetDistanceToPoint(ray.origin)) < .0001f)
|
||||
{
|
||||
nearest = 0f;
|
||||
continue;
|
||||
}
|
||||
|
||||
float d;
|
||||
|
||||
if (plane.Raycast(forwardRay, out d) && Mathf.Abs(d) < Mathf.Abs(nearest))
|
||||
nearest = d;
|
||||
if (plane.Raycast(backwardsRay, out d) && Mathf.Abs(d) < Mathf.Abs(nearest))
|
||||
nearest = -d;
|
||||
}
|
||||
}
|
||||
|
||||
return ray.origin + ray.direction * (Mathf.Abs(nearest) >= k_MaxRaySnapDistance ? distance : nearest);
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e5d62e0d1462c4f02ae2aae1659cb748
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,29 @@
|
||||
using UnityEngine;
|
||||
using System;
|
||||
|
||||
namespace UnityEngine.ProBuilder
|
||||
{
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
/// ProGridsNoSnapAttribute tells ProGrids to skip snapping on this object.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This exists only as a stub for the ProGrids defined attribute.
|
||||
/// </remarks>
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
|
||||
sealed class ProGridsNoSnapAttribute : Attribute
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
/// ProGridsConditionalSnapAttribute tells ProGrids to check IsSnapEnabled function on this object.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This exists only as a stub for the ProGrids defined attribute.
|
||||
/// </remarks>
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
|
||||
sealed class ProGridsConditionalSnapAttribute : Attribute
|
||||
{
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 599cab2fb037142fd9fe246b0b364bc1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,448 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace UnityEngine.ProBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains functions for projecting 3D points to 2D space.
|
||||
/// </summary>
|
||||
public static class Projection
|
||||
{
|
||||
/// <summary>
|
||||
/// Projects a collection of 3D positions to a 2D plane. This method uses <see cref="FindBestPlane"/> to calculate
|
||||
/// the direction from which the vertices are projected.
|
||||
/// </summary>
|
||||
/// <param name="positions">A collection of positions to project based on a direction.</param>
|
||||
/// <param name="indexes">A collection of indices to project. The returned array matches the length of this collection.</param>
|
||||
/// <returns>The positions array projected into 2D coordinates.</returns>
|
||||
public static Vector2[] PlanarProject(IList<Vector3> positions, IList<int> indexes = null)
|
||||
{
|
||||
return PlanarProject(positions, indexes, FindBestPlane(positions, indexes).normal);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Projects a collection of 3D positions to a 2D plane in the specified direction.
|
||||
/// </summary>
|
||||
/// <param name="positions">A collection of positions to project based on a direction.</param>
|
||||
/// <param name="indexes">A collection of indices to project. The returned array matches the length of this collection.</param>
|
||||
/// <param name="direction">The direction from which vertex positions are projected into 2D space.</param>
|
||||
/// <returns>The positions array projected into 2D coordinates.</returns>
|
||||
public static Vector2[] PlanarProject(IList<Vector3> positions, IList<int> indexes, Vector3 direction)
|
||||
{
|
||||
List<Vector2> results = new List<Vector2>(indexes != null ? indexes.Count : positions.Count);
|
||||
PlanarProject(positions, indexes, direction, results);
|
||||
return results.ToArray();
|
||||
}
|
||||
|
||||
internal static void PlanarProject(IList<Vector3> positions, IList<int> indexes, Vector3 direction, List<Vector2> results)
|
||||
{
|
||||
if(positions == null)
|
||||
throw new ArgumentNullException("positions");
|
||||
|
||||
if(results == null)
|
||||
throw new ArgumentNullException("results");
|
||||
|
||||
var nrm = Math.EnsureUnitVector(direction);
|
||||
var axis = VectorToProjectionAxis(nrm);
|
||||
var prj = GetTangentToAxis(axis);
|
||||
var len = indexes == null ? positions.Count : indexes.Count;
|
||||
results.Clear();
|
||||
|
||||
var u = Vector3.Cross(nrm, prj);
|
||||
var v = Vector3.Cross(u, nrm);
|
||||
|
||||
u.Normalize();
|
||||
v.Normalize();
|
||||
|
||||
if (indexes != null)
|
||||
{
|
||||
for (int i = 0, ic = len; i < ic; ++i)
|
||||
results.Add(new Vector2(Vector3.Dot(u, positions[indexes[i]]), Vector3.Dot(v, positions[indexes[i]])));
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0, ic = len; i < ic; ++i)
|
||||
results.Add(new Vector2(Vector3.Dot(u, positions[i]), Vector3.Dot(v, positions[i])));
|
||||
}
|
||||
}
|
||||
|
||||
internal static void PlanarProject(ProBuilderMesh mesh, int textureGroup, AutoUnwrapSettings unwrapSettings)
|
||||
{
|
||||
var worldSpace = unwrapSettings.useWorldSpace;
|
||||
var trs = (Transform)null;
|
||||
var faces = mesh.facesInternal;
|
||||
|
||||
// Get a projection direction by averaging the normals of all selected faces
|
||||
var projectionDirection = Vector3.zero;
|
||||
|
||||
for (int f = 0, fc = faces.Length; f < fc; ++f)
|
||||
{
|
||||
if (faces[f].textureGroup != textureGroup)
|
||||
continue;
|
||||
|
||||
var nrm = Math.Normal(mesh, faces[f]);
|
||||
projectionDirection += nrm;
|
||||
}
|
||||
|
||||
if (worldSpace)
|
||||
{
|
||||
trs = mesh.transform;
|
||||
projectionDirection = trs.TransformDirection(projectionDirection);
|
||||
}
|
||||
|
||||
var axis = VectorToProjectionAxis(projectionDirection);
|
||||
var prj = GetTangentToAxis(axis);
|
||||
|
||||
var u = Vector3.Cross(projectionDirection, prj);
|
||||
var v = Vector3.Cross(u, projectionDirection);
|
||||
|
||||
u.Normalize();
|
||||
v.Normalize();
|
||||
|
||||
var positions = mesh.positionsInternal;
|
||||
var textures = mesh.texturesInternal;
|
||||
|
||||
for (int f = 0, fc = faces.Length; f < fc; ++f)
|
||||
{
|
||||
if (faces[f].textureGroup != textureGroup)
|
||||
continue;
|
||||
|
||||
var indexes = faces[f].distinctIndexesInternal;
|
||||
|
||||
for (int i = 0, ic = indexes.Length; i < ic; ++i)
|
||||
{
|
||||
var p = worldSpace ? trs.TransformPoint(positions[indexes[i]]) : positions[indexes[i]];
|
||||
|
||||
textures[indexes[i]].x = Vector3.Dot(u, p);
|
||||
textures[indexes[i]].y = Vector3.Dot(v, p);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static void PlanarProject(ProBuilderMesh mesh, Face face, Vector3 projection = default)
|
||||
{
|
||||
var nrm = Math.EnsureUnitVector(Math.Normal(mesh, face));
|
||||
var trs = (Transform)null;
|
||||
var worldSpace = face.uv.useWorldSpace;
|
||||
|
||||
if (worldSpace)
|
||||
{
|
||||
trs = mesh.transform;
|
||||
nrm = trs.TransformDirection(nrm);
|
||||
}
|
||||
|
||||
Vector3 prj = projection;
|
||||
if (prj == Vector3.zero)
|
||||
{
|
||||
var axis = VectorToProjectionAxis(nrm);
|
||||
prj = GetTangentToAxis(axis);
|
||||
}
|
||||
|
||||
var uAxis = Vector3.Cross(nrm, prj);
|
||||
var vAxis = Vector3.Cross(uAxis, nrm);
|
||||
|
||||
uAxis.Normalize();
|
||||
vAxis.Normalize();
|
||||
|
||||
var positions = mesh.positionsInternal;
|
||||
var textures = mesh.texturesInternal;
|
||||
|
||||
int[] indexes = face.distinctIndexesInternal;
|
||||
|
||||
for (int i = 0, ic = indexes.Length; i < ic; ++i)
|
||||
{
|
||||
var p = worldSpace ? trs.TransformPoint(positions[indexes[i]]) : positions[indexes[i]];
|
||||
|
||||
textures[indexes[i]].x = Vector3.Dot(uAxis, p);
|
||||
textures[indexes[i]].y = Vector3.Dot(vAxis, p);
|
||||
}
|
||||
}
|
||||
|
||||
internal static Vector2[] SphericalProject(IList<Vector3> vertices, IList<int> indexes = null)
|
||||
{
|
||||
int len = indexes == null ? vertices.Count : indexes.Count;
|
||||
Vector2[] uv = new Vector2[len];
|
||||
Vector3 cen = Math.Average(vertices, indexes);
|
||||
|
||||
for (int i = 0; i < len; i++)
|
||||
{
|
||||
int indx = indexes == null ? i : indexes[i];
|
||||
Vector3 p = (vertices[indx] - cen);
|
||||
p.Normalize();
|
||||
uv[i].x = .5f + (Mathf.Atan2(p.z, p.x) / (2f * Mathf.PI));
|
||||
uv[i].y = .5f - (Mathf.Asin(p.y) / Mathf.PI);
|
||||
}
|
||||
|
||||
return uv;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a new set of points wound as a contour counter-clockwise.
|
||||
/// </summary>
|
||||
/// <param name="verts"></param>
|
||||
/// <param name="method"></param>
|
||||
/// <returns></returns>
|
||||
internal static IList<Vector2> Sort(IList<Vector2> verts, SortMethod method = SortMethod.CounterClockwise)
|
||||
{
|
||||
Vector2 cen = Math.Average(verts);
|
||||
Vector2 up = Vector2.up;
|
||||
int count = verts.Count;
|
||||
|
||||
List<SimpleTuple<float, Vector2>> angles = new List<SimpleTuple<float, Vector2>>(count);
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
angles.Add(new SimpleTuple<float, Vector2>(Math.SignedAngle(up, verts[i] - cen), verts[i]));
|
||||
|
||||
angles.Sort((a, b) => { return a.item1 < b.item1 ? -1 : 1; });
|
||||
|
||||
IList<Vector2> values = angles.Select(x => x.item2).ToList();
|
||||
|
||||
if (method == SortMethod.Clockwise)
|
||||
values = values.Reverse().ToList();
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
internal static Vector3 GetTangentToAxis(ProjectionAxis axis)
|
||||
{
|
||||
// old probuilder didn't respect project axis settings properly, and changing it to the correct version
|
||||
// (ProjectionAxisToVector) would break existing models.
|
||||
switch (axis)
|
||||
{
|
||||
case ProjectionAxis.X:
|
||||
case ProjectionAxis.XNegative:
|
||||
return Vector3.up;
|
||||
|
||||
case ProjectionAxis.Y:
|
||||
case ProjectionAxis.YNegative:
|
||||
return Vector3.forward;
|
||||
|
||||
case ProjectionAxis.Z:
|
||||
case ProjectionAxis.ZNegative:
|
||||
return Vector3.up;
|
||||
|
||||
default:
|
||||
return Vector3.up;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Given a ProjectionAxis, return the appropriate Vector3 conversion.
|
||||
/// </summary>
|
||||
/// <param name="axis"></param>
|
||||
/// <returns></returns>
|
||||
internal static Vector3 ProjectionAxisToVector(ProjectionAxis axis)
|
||||
{
|
||||
switch (axis)
|
||||
{
|
||||
case ProjectionAxis.X:
|
||||
return Vector3.right;
|
||||
|
||||
case ProjectionAxis.Y:
|
||||
return Vector3.up;
|
||||
|
||||
case ProjectionAxis.Z:
|
||||
return Vector3.forward;
|
||||
|
||||
case ProjectionAxis.XNegative:
|
||||
return -Vector3.right;
|
||||
|
||||
case ProjectionAxis.YNegative:
|
||||
return -Vector3.up;
|
||||
|
||||
case ProjectionAxis.ZNegative:
|
||||
return -Vector3.forward;
|
||||
|
||||
default:
|
||||
return Vector3.forward;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a projection axis based on which axis is the largest
|
||||
/// </summary>
|
||||
/// <param name="direction"></param>
|
||||
/// <returns></returns>
|
||||
internal static ProjectionAxis VectorToProjectionAxis(Vector3 direction)
|
||||
{
|
||||
float x = System.Math.Abs(direction.x);
|
||||
float y = System.Math.Abs(direction.y);
|
||||
float z = System.Math.Abs(direction.z);
|
||||
|
||||
// Prior to 2019.1 Vector3.Normalize did not use System.Math for Magnitude calculations. There is a slight
|
||||
// difference between 2018.3 and 2019.1 in accuracy, with 2019.1 being more correct. This inaccuracy results
|
||||
// in float comparison problems (ex, .00700000001 > .007). To account for that we add an additional equality
|
||||
// check here.
|
||||
// In most other cases this doesn't matter, but since we need this function to be deterministic regardless
|
||||
// of floating point inaccuracy it is necessary here.
|
||||
if (!Math.Approx(x, y) && x > y && !Math.Approx(x, z) && x > z)
|
||||
return direction.x > 0 ? ProjectionAxis.X : ProjectionAxis.XNegative;
|
||||
|
||||
if (!Math.Approx(y, z) && y > z)
|
||||
return direction.y > 0 ? ProjectionAxis.Y : ProjectionAxis.YNegative;
|
||||
|
||||
return direction.z > 0 ? ProjectionAxis.Z : ProjectionAxis.ZNegative;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds the plane that best fits the specified set of 3D points.
|
||||
/// </summary>
|
||||
/// <remarks>http://www.ilikebigbits.com/blog/2015/3/2/plane-from-points</remarks>
|
||||
/// <param name="points">The points to find a plane for. Order does not matter.</param>
|
||||
/// <param name="indexes">If provided, this method considers only the vertices referenced by this array.</param>
|
||||
/// <returns>The plane that best matches the layout of the points array.</returns>
|
||||
public static Plane FindBestPlane(IList<Vector3> points, IList<int> indexes = null)
|
||||
{
|
||||
float xx = 0f, xy = 0f, xz = 0f,
|
||||
yy = 0f, yz = 0f, zz = 0f;
|
||||
|
||||
if (points == null)
|
||||
throw new System.ArgumentNullException("points");
|
||||
|
||||
bool ind = indexes != null && indexes.Count > 0;
|
||||
int len = ind ? indexes.Count : points.Count;
|
||||
|
||||
Vector3 c = Vector3.zero, n = Vector3.zero;
|
||||
|
||||
for (int i = 0; i < len; i++)
|
||||
{
|
||||
c.x += points[ind ? indexes[i] : i].x;
|
||||
c.y += points[ind ? indexes[i] : i].y;
|
||||
c.z += points[ind ? indexes[i] : i].z;
|
||||
}
|
||||
|
||||
c.x /= (float)len;
|
||||
c.y /= (float)len;
|
||||
c.z /= (float)len;
|
||||
|
||||
for (int i = 0; i < len; i++)
|
||||
{
|
||||
Vector3 r = points[ind ? indexes[i] : i] - c;
|
||||
|
||||
xx += r.x * r.x;
|
||||
xy += r.x * r.y;
|
||||
xz += r.x * r.z;
|
||||
yy += r.y * r.y;
|
||||
yz += r.y * r.z;
|
||||
zz += r.z * r.z;
|
||||
}
|
||||
|
||||
float det_x = yy * zz - yz * yz;
|
||||
float det_y = xx * zz - xz * xz;
|
||||
float det_z = xx * yy - xy * xy;
|
||||
|
||||
if (det_x > det_y && det_x > det_z)
|
||||
{
|
||||
n.x = 1f;
|
||||
n.y = (xz * yz - xy * zz) / det_x;
|
||||
n.z = (xy * yz - xz * yy) / det_x;
|
||||
}
|
||||
else if (det_y > det_z)
|
||||
{
|
||||
n.x = (yz * xz - xy * zz) / det_y;
|
||||
n.y = 1f;
|
||||
n.z = (xy * xz - yz * xx) / det_y;
|
||||
}
|
||||
else
|
||||
{
|
||||
n.x = (yz * xy - xz * yy) / det_z;
|
||||
n.y = (xz * xy - yz * xx) / det_z;
|
||||
n.z = 1f;
|
||||
}
|
||||
|
||||
n.Normalize();
|
||||
|
||||
return new Plane(n, c);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find a plane that best fits a set of faces within a texture group.
|
||||
/// </summary>
|
||||
/// <returns>A plane that best matches the layout of the points array.</returns>
|
||||
internal static Plane FindBestPlane(ProBuilderMesh mesh, int textureGroup)
|
||||
{
|
||||
float xx = 0f, xy = 0f, xz = 0f,
|
||||
yy = 0f, yz = 0f, zz = 0f;
|
||||
|
||||
if (mesh == null)
|
||||
throw new System.ArgumentNullException("mesh");
|
||||
|
||||
Vector3 c = Vector3.zero;
|
||||
int len = 0;
|
||||
Vector3[] positions = mesh.positionsInternal;
|
||||
int faceCount = mesh.faceCount;
|
||||
Face[] faces = mesh.facesInternal;
|
||||
|
||||
for (int faceIndex = 0; faceIndex < faceCount; faceIndex++)
|
||||
{
|
||||
if (faces[faceIndex].textureGroup != textureGroup)
|
||||
continue;
|
||||
|
||||
int[] indexes = faces[faceIndex].indexesInternal;
|
||||
|
||||
for (int index = 0, indexCount = indexes.Length; index < indexCount; index++)
|
||||
{
|
||||
c.x += positions[indexes[index]].x;
|
||||
c.y += positions[indexes[index]].y;
|
||||
c.z += positions[indexes[index]].z;
|
||||
|
||||
len++;
|
||||
}
|
||||
}
|
||||
|
||||
c.x /= len;
|
||||
c.y /= len;
|
||||
c.z /= len;
|
||||
|
||||
for (int faceIndex = 0; faceIndex < faceCount; faceIndex++)
|
||||
{
|
||||
if (faces[faceIndex].textureGroup != textureGroup)
|
||||
continue;
|
||||
|
||||
int[] indexes = faces[faceIndex].indexesInternal;
|
||||
|
||||
for (int index = 0, indexCount = indexes.Length; index < indexCount; index++)
|
||||
{
|
||||
Vector3 r = positions[indexes[index]] - c;
|
||||
|
||||
xx += r.x * r.x;
|
||||
xy += r.x * r.y;
|
||||
xz += r.x * r.z;
|
||||
yy += r.y * r.y;
|
||||
yz += r.y * r.z;
|
||||
zz += r.z * r.z;
|
||||
}
|
||||
}
|
||||
|
||||
float det_x = yy * zz - yz * yz;
|
||||
float det_y = xx * zz - xz * xz;
|
||||
float det_z = xx * yy - xy * xy;
|
||||
Vector3 n = Vector3.zero;
|
||||
|
||||
if (det_x > det_y && det_x > det_z)
|
||||
{
|
||||
n.x = 1f;
|
||||
n.y = (xz * yz - xy * zz) / det_x;
|
||||
n.z = (xy * yz - xz * yy) / det_x;
|
||||
}
|
||||
else if (det_y > det_z)
|
||||
{
|
||||
n.x = (yz * xz - xy * zz) / det_y;
|
||||
n.y = 1f;
|
||||
n.z = (xy * xz - yz * xx) / det_y;
|
||||
}
|
||||
else
|
||||
{
|
||||
n.x = (yz * xy - xz * yy) / det_z;
|
||||
n.y = (xz * xy - yz * xx) / det_z;
|
||||
n.z = 1f;
|
||||
}
|
||||
|
||||
n.Normalize();
|
||||
|
||||
return new Plane(n, c);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ac19f03e418a945ba9d37c7d32e5cecb
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,27 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEngine.ProBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// The results of a raycast hit.
|
||||
/// </summary>
|
||||
sealed class RaycastHit
|
||||
{
|
||||
public float distance;
|
||||
public Vector3 point;
|
||||
public Vector3 normal;
|
||||
public int face;
|
||||
|
||||
public RaycastHit(
|
||||
float distance,
|
||||
Vector3 point,
|
||||
Vector3 normal,
|
||||
int face)
|
||||
{
|
||||
this.distance = distance;
|
||||
this.point = point;
|
||||
this.normal = normal;
|
||||
this.face = face;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 77656e4f6ab6544caa81cbd3e5784e03
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,194 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace UnityEngine.ProBuilder
|
||||
{
|
||||
static class SelectPathFaces
|
||||
{
|
||||
static int[] s_cachedPredecessors;
|
||||
static int s_cachedStart;
|
||||
static ProBuilderMesh s_cachedMesh;
|
||||
static int s_cachedFacesCount;
|
||||
static List<WingedEdge> s_cachedWings;
|
||||
static Dictionary<Face, int> s_cachedFacesIndex = new Dictionary<Face, int>();
|
||||
|
||||
#if UNITY_EDITOR
|
||||
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
|
||||
static void ResetStaticsOnLoad()
|
||||
{
|
||||
s_cachedPredecessors = null;
|
||||
s_cachedStart = -1;
|
||||
s_cachedMesh = null;
|
||||
s_cachedFacesCount = -1;
|
||||
s_cachedWings = null;
|
||||
s_cachedFacesIndex.Clear();
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the indexes of all faces in the shortest path between start and end
|
||||
/// </summary>
|
||||
/// <param name="start">The index of the starting face</param>
|
||||
/// <param name="end">The index of the ending face</param>
|
||||
/// <param name="mesh">The mesh of the object</param>
|
||||
/// <returns>The indexes of all faces </returns>
|
||||
public static List<int> GetPath(ProBuilderMesh mesh, int start, int end)
|
||||
{
|
||||
if (mesh == null)
|
||||
throw new System.ArgumentException("Parameter cannot be null", "mesh");
|
||||
if (start < 0 || start > mesh.faceCount - 1)
|
||||
throw new System.ArgumentException("Parameter is out of bounds", "start");
|
||||
if (end < 0 || end > mesh.faceCount - 1)
|
||||
throw new System.ArgumentException("Parameter is out of bounds", "end");
|
||||
|
||||
List<int> path;
|
||||
|
||||
if (start == s_cachedStart && mesh == s_cachedMesh && mesh.faceCount == s_cachedFacesCount)
|
||||
return GetMinimalPath(s_cachedPredecessors, start, end);
|
||||
|
||||
var predecessors = Dijkstra(mesh, start);
|
||||
path = GetMinimalPath(predecessors, start, end);
|
||||
s_cachedPredecessors = predecessors;
|
||||
s_cachedStart = start;
|
||||
s_cachedMesh = mesh;
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds a list of predecessors from a given face index to all other faces
|
||||
/// Uses the Djikstra pathfinding algorithm
|
||||
/// </summary>
|
||||
/// <param name="mesh">The mesh of the object</param>
|
||||
/// <param name="start">The index of the starting face</param>
|
||||
/// <returns>A list of predecessors from a face index to all other faces</returns>
|
||||
static int[] Dijkstra(ProBuilderMesh mesh, int start)
|
||||
{
|
||||
HashSet<int> visited = new HashSet<int>();
|
||||
HashSet<int> toVisit = new HashSet<int>();
|
||||
if (s_cachedMesh != mesh || s_cachedFacesCount != mesh.faceCount)
|
||||
{
|
||||
s_cachedWings = WingedEdge.GetWingedEdges(mesh, true);
|
||||
s_cachedFacesIndex.Clear();
|
||||
s_cachedFacesCount = mesh.faceCount;
|
||||
|
||||
for (int i = 0; i < mesh.facesInternal.Length; i++)
|
||||
{
|
||||
s_cachedFacesIndex.Add(mesh.facesInternal[i], i);
|
||||
}
|
||||
}
|
||||
int wingCount = s_cachedWings.Count;
|
||||
|
||||
float[] weights = new float[wingCount];
|
||||
int[] predecessors = new int[wingCount];
|
||||
|
||||
for (int i = 0; i < wingCount; i++)
|
||||
{
|
||||
weights[i] = float.MaxValue;
|
||||
predecessors[i] = -1;
|
||||
}
|
||||
|
||||
int current = start;
|
||||
weights[current] = 0;
|
||||
visited.Add(current);
|
||||
|
||||
// Construct the paths between the start face and every other faces
|
||||
while (visited.Count < wingCount)
|
||||
{
|
||||
var currentWing = s_cachedWings[current];
|
||||
var otherWing = currentWing;
|
||||
// Update the weight array for each face next to the current one
|
||||
do
|
||||
{
|
||||
var opposite = otherWing.opposite;
|
||||
if (opposite == null)
|
||||
{
|
||||
otherWing = otherWing.next;
|
||||
continue;
|
||||
}
|
||||
|
||||
var idx = s_cachedFacesIndex[opposite.face];
|
||||
var weight = GetWeight(current, idx, mesh);
|
||||
// Change the predecessor and weight if the new path found if shorter
|
||||
if (weights[current] + weight < weights[idx])
|
||||
{
|
||||
weights[idx] = weights[current] + weight;
|
||||
predecessors[idx] = current;
|
||||
}
|
||||
// Add the face to the ones we can visit next, if not yet visited
|
||||
if (!toVisit.Contains(idx) && !visited.Contains(idx))
|
||||
{
|
||||
toVisit.Add(idx);
|
||||
}
|
||||
|
||||
otherWing = otherWing.next;
|
||||
|
||||
} while (otherWing != currentWing);
|
||||
|
||||
// This means there is an isolated face
|
||||
if (toVisit.Count == 0)
|
||||
{
|
||||
return predecessors;
|
||||
}
|
||||
// Look for the next face to visit, choosing the one with less weight
|
||||
float min = float.MaxValue;
|
||||
foreach (var i in toVisit)
|
||||
{
|
||||
if (weights[i] < min)
|
||||
{
|
||||
min = weights[i];
|
||||
current = i;
|
||||
}
|
||||
}
|
||||
visited.Add(current);
|
||||
toVisit.Remove(current);
|
||||
}
|
||||
|
||||
return predecessors;
|
||||
}
|
||||
|
||||
static float GetWeight(int face1, int face2, ProBuilderMesh mesh)
|
||||
{
|
||||
const float baseCost = 10f;
|
||||
const float normalMult = 2f;
|
||||
const float distMult = 1f;
|
||||
|
||||
// Calculates the difference between the normals of the faces
|
||||
var n1 = Math.Normal(mesh, mesh.facesInternal[face1]);
|
||||
var n2 = Math.Normal(mesh, mesh.facesInternal[face2]);
|
||||
float normalCost = (1f - Vector3.Dot(n1.normalized, n2.normalized)) * normalMult;
|
||||
|
||||
// Calculates the distance between the center of the faces
|
||||
Vector3 p1 = Vector3.zero;
|
||||
Vector3 p2 = Vector3.zero;
|
||||
foreach (var point in mesh.facesInternal[face1].indexesInternal)
|
||||
{
|
||||
p1 += mesh.positionsInternal[point] / mesh.facesInternal[face1].indexesInternal.Length;
|
||||
}
|
||||
foreach (var point in mesh.facesInternal[face2].indexesInternal)
|
||||
{
|
||||
p2 += mesh.positionsInternal[point] / mesh.facesInternal[face2].indexesInternal.Length;
|
||||
}
|
||||
|
||||
float distCost = (p2 - p1).magnitude * distMult;
|
||||
|
||||
return baseCost + distCost + normalCost;
|
||||
}
|
||||
|
||||
static List<int> GetMinimalPath(int[] predecessors, int start, int end)
|
||||
{
|
||||
if (predecessors[end] == -1)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
Stack<int> list = new Stack<int>();
|
||||
int a = end;
|
||||
while (a != start)
|
||||
{
|
||||
list.Push(a);
|
||||
a = predecessors[a];
|
||||
}
|
||||
return list.ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cf31008f4dc1fb548a315685bb054730
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user