You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
346 lines
11 KiB
C#
346 lines
11 KiB
C#
using UnityEngine;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
|
|
namespace UnityEngine.Polybrush
|
|
{
|
|
/// <summary>
|
|
/// Holds the affected mesh attribute arrays
|
|
/// </summary>
|
|
internal class SplatSet
|
|
{
|
|
const float WEIGHT_EPSILON = 0.0001f;
|
|
|
|
// how many vertices are stored in this splatset
|
|
private int weightCount;
|
|
|
|
// channel to index in weights array
|
|
private Dictionary<MeshChannel, int> channelMap;
|
|
|
|
// splatset doesn't store an array of splatweight because it's too slow
|
|
// to reconstruct mesh arrays from selecting each component of a splatweight.
|
|
private Vector4[][] weights;
|
|
|
|
// Assigns where each weight is applied on the mesh.
|
|
internal AttributeLayout[] attributeLayout;
|
|
|
|
// The number of values being passed to the mesh (ex, color.rgba = 4)
|
|
internal int attributeCount { get { return attributeLayout.Length; } }
|
|
|
|
/// <summary>
|
|
/// Initialize a new SplatSet with vertex count and attribute layout. Attributes should
|
|
/// match the length of weights applied (one attribute per value).
|
|
/// Weight values are initialized to zero (unless preAlloc is false, then only the channel
|
|
/// container array is initialized and arrays aren't allocated)
|
|
/// </summary>
|
|
/// <param name="vertexCount"></param>
|
|
/// <param name="attributes"></param>
|
|
/// <param name="preAlloc">Allocate and initialize Weight values to 0 ?</param>
|
|
internal SplatSet(int vertexCount, AttributeLayout[] attributes, bool preAlloc = true)
|
|
{
|
|
this.channelMap = SplatWeight.GetChannelMap(attributes);
|
|
int channels = channelMap.Count;
|
|
this.attributeLayout = attributes;
|
|
this.weights = new Vector4[channels][];
|
|
this.weightCount = vertexCount;
|
|
|
|
if(preAlloc)
|
|
{
|
|
for(int i = 0; i < channels; i++)
|
|
this.weights[i] = new Vector4[vertexCount];
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Copy constructor.
|
|
/// </summary>
|
|
/// <param name="other">The copied SplatSet</param>
|
|
internal SplatSet(SplatSet other)
|
|
{
|
|
int attribCount = other.attributeCount;
|
|
this.attributeLayout = new AttributeLayout[attribCount];
|
|
System.Array.Copy(other.attributeLayout, 0, this.attributeLayout, 0, attribCount);
|
|
|
|
this.channelMap = new Dictionary<MeshChannel, int>();
|
|
|
|
foreach(var kvp in other.channelMap)
|
|
this.channelMap.Add(kvp.Key, kvp.Value);
|
|
|
|
int channelCount = other.channelMap.Count;
|
|
this.weightCount = other.weightCount;
|
|
this.weights = new Vector4[channelCount][];
|
|
|
|
for(int i = 0; i < channelCount; i++)
|
|
{
|
|
if (other.weights[i] != null)
|
|
{
|
|
this.weights[i] = new Vector4[weightCount];
|
|
System.Array.Copy(other.weights[i], this.weights[i], weightCount);
|
|
}
|
|
}
|
|
}
|
|
|
|
private static Vector4 Color32ToVec4(Color32 color)
|
|
{
|
|
return new Vector4( color.r / 255f,
|
|
color.g / 255f,
|
|
color.b / 255f,
|
|
color.a / 255f );
|
|
}
|
|
|
|
private static Color32 Vec4ToColor32(Vector4 vec)
|
|
{
|
|
return new Color32( (byte) (255 * vec.x),
|
|
(byte) (255 * vec.y),
|
|
(byte) (255 * vec.z),
|
|
(byte) (255 * vec.w) );
|
|
}
|
|
|
|
internal void SetChannelBaseTextureWeights(Dictionary<MeshChannel, List<int>> channelsToBaseTex, Dictionary<int, int> baseTexToMask, Dictionary<int, List<int>> maskToIndices)
|
|
{
|
|
foreach(var channelKvp in channelsToBaseTex)
|
|
{
|
|
var channelWeights = weights[channelMap[channelKvp.Key]];
|
|
for(int i = 0; i < channelWeights.Length; i++)
|
|
{
|
|
var vertexWeight = channelWeights[i];
|
|
foreach(var baseTexIndex in channelKvp.Value)
|
|
{
|
|
if(!baseTexToMask.ContainsKey(baseTexIndex))
|
|
continue;
|
|
// Calculate the weight out of one already used for this mask,
|
|
// and set the weight of the base texture to the remainder.
|
|
// e.g. if all textures are on the same mask, the base texture is at
|
|
// index 0 and the vector looks like: (0, 0.2, 0.3, 0.4).
|
|
// Then 1 - (0.2 + 0.3 + 0.4) = 0.1, and the vector becomes: (0.1, 0.2, 0.3, 0.4).
|
|
float value = 0f;
|
|
List<int> indices;
|
|
if(maskToIndices.TryGetValue(baseTexToMask[baseTexIndex], out indices))
|
|
{
|
|
foreach(var ind in indices)
|
|
{
|
|
value += vertexWeight[ind];
|
|
}
|
|
}
|
|
|
|
vertexWeight[baseTexIndex] = 1 - value;
|
|
}
|
|
|
|
channelWeights[i] = vertexWeight;
|
|
}
|
|
weights[channelMap[channelKvp.Key]] = channelWeights;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initialize a SplatSet with mesh and attribute layout.
|
|
/// </summary>
|
|
/// <param name="mesh"></param>
|
|
/// <param name="attributes"></param>
|
|
internal SplatSet(PolyMesh mesh, AttributeLayout[] attributes) : this(mesh.vertexCount, attributes, false)
|
|
{
|
|
foreach(var kvp in channelMap)
|
|
{
|
|
switch(kvp.Key)
|
|
{
|
|
case MeshChannel.UV0:
|
|
case MeshChannel.UV2:
|
|
case MeshChannel.UV3:
|
|
case MeshChannel.UV4:
|
|
{
|
|
List<Vector4> uv = mesh.GetUVs( MeshChannelUtility.UVChannelToIndex(kvp.Key) );
|
|
weights[kvp.Value] = uv.Count == weightCount ? uv.ToArray() : new Vector4[weightCount];
|
|
}
|
|
break;
|
|
|
|
case MeshChannel.Color:
|
|
{
|
|
Color32[] color = mesh.colors;
|
|
weights[kvp.Value] = color != null && color.Length == weightCount ? System.Array.ConvertAll(color, x => Color32ToVec4(x) ) : new Vector4[weightCount];
|
|
}
|
|
break;
|
|
|
|
case MeshChannel.Tangent:
|
|
{
|
|
Vector4[] tangent = mesh.tangents;
|
|
weights[kvp.Value] = tangent != null && tangent.Length == weightCount ? tangent : new Vector4[weightCount];
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the default weights for each channel (the minimum)
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
internal SplatWeight GetMinWeights()
|
|
{
|
|
SplatWeight min = new SplatWeight(channelMap);
|
|
|
|
foreach(AttributeLayout al in attributeLayout)
|
|
{
|
|
Vector4 v = min[al.channel];
|
|
v[(int)al.index] = al.min;
|
|
min[al.channel] = v;
|
|
}
|
|
|
|
return min;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Lerp each attribute value with matching `mask` to `rhs`.
|
|
/// weights, lhs, and rhs must have matching layout attributes.
|
|
/// </summary>
|
|
/// <param name="lhs"></param>
|
|
/// <param name="rhs"></param>
|
|
/// <param name="mask"></param>
|
|
/// <param name="strength"></param>
|
|
internal void LerpWeights(SplatSet lhs, SplatSet rhs, int mask, float[] strength)
|
|
{
|
|
Dictionary<int, uint> affected = new Dictionary<int, uint>();
|
|
|
|
foreach(AttributeLayout al in attributeLayout)
|
|
{
|
|
int mapIndex = channelMap[al.channel];
|
|
|
|
if(al.mask == mask)
|
|
{
|
|
if(!affected.ContainsKey(mapIndex))
|
|
affected.Add(mapIndex, al.index.ToFlag());
|
|
else
|
|
affected[mapIndex] |= al.index.ToFlag();
|
|
}
|
|
}
|
|
|
|
foreach(var v in affected)
|
|
{
|
|
Vector4[] a = lhs.weights[v.Key];
|
|
Vector4[] b = rhs.weights[v.Key];
|
|
Vector4[] c = weights[v.Key];
|
|
|
|
for(int i = 0; i < weightCount; i++)
|
|
{
|
|
if((v.Value & 1) != 0) c[i].x = Mathf.Lerp(a[i].x, b[i].x, strength[i]);
|
|
if((v.Value & 2) != 0) c[i].y = Mathf.Lerp(a[i].y, b[i].y, strength[i]);
|
|
if((v.Value & 4) != 0) c[i].z = Mathf.Lerp(a[i].z, b[i].z, strength[i]);
|
|
if((v.Value & 8) != 0) c[i].w = Mathf.Lerp(a[i].w, b[i].w, strength[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Lerp weights between lhs and rhs
|
|
/// </summary>
|
|
/// <param name="lhs"></param>
|
|
/// <param name="rhs"></param>
|
|
/// <param name="strength"></param>
|
|
internal void LerpWeights(SplatSet lhs, SplatWeight rhs, float strength)
|
|
{
|
|
for(int i = 0; i < weightCount; i++)
|
|
{
|
|
foreach(var cm in channelMap)
|
|
this.weights[cm.Value][i] = Vector4.LerpUnclamped(lhs.weights[cm.Value][i], rhs[cm.Key], strength);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Lerp weights between lhs and rhs for value at the given index.
|
|
/// </summary>
|
|
/// <param name="lhs"></param>
|
|
/// <param name="rhs"></param>
|
|
/// <param name="strength"></param>
|
|
/// <param name="index"></param>
|
|
internal void LerpWeightOnSingleChannel(SplatSet lhs, SplatWeight rhs, float strength, MeshChannel channel, int index, int baseTexIndex)
|
|
{
|
|
if(!channelMap.ContainsKey(channel))
|
|
return;
|
|
|
|
var channelIndex = channelMap[channel];
|
|
for (int i = 0; i < weightCount; i++)
|
|
{
|
|
float lerpedValue = Mathf.LerpUnclamped(lhs.weights[channelIndex][i][index], rhs[channel][index], strength);
|
|
|
|
// replace the original value at index with the lerped value
|
|
var newWeightVector = lhs.weights[channelIndex][i];
|
|
newWeightVector[index] = lerpedValue;
|
|
|
|
if(baseTexIndex > -1)
|
|
{
|
|
newWeightVector[baseTexIndex] += (lhs.weights[channelIndex][i][index] - newWeightVector[index]);
|
|
}
|
|
|
|
this.weights[channelIndex][i] = newWeightVector;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Copy values to another SplatSet
|
|
/// </summary>
|
|
/// <param name="other">The other SplatSet we want to copy our values to</param>
|
|
internal void CopyTo(SplatSet other)
|
|
{
|
|
if(other.weightCount != weightCount)
|
|
{
|
|
Debug.LogError("Copying splat set to mis-matched container length");
|
|
return;
|
|
}
|
|
|
|
for(int i = 0; i < channelMap.Count; i++)
|
|
System.Array.Copy(this.weights[i], other.weights[i], weightCount);
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Apply the weights to "mesh"
|
|
/// </summary>
|
|
/// <param name="mesh">The mesh we want to apply weights to</param>
|
|
internal void Apply(PolyMesh mesh)
|
|
{
|
|
foreach(AttributeLayout al in attributeLayout)
|
|
{
|
|
switch(al.channel)
|
|
{
|
|
case MeshChannel.UV0:
|
|
case MeshChannel.UV2:
|
|
case MeshChannel.UV3:
|
|
case MeshChannel.UV4:
|
|
{
|
|
List<Vector4> uv = new List<Vector4>(weights[channelMap[al.channel]]);
|
|
mesh.SetUVs(MeshChannelUtility.UVChannelToIndex(al.channel), uv);
|
|
}
|
|
break;
|
|
|
|
case MeshChannel.Color:
|
|
{
|
|
// @todo consider storing Color array separate from Vec4 since this cast costs ~5ms
|
|
mesh.colors = System.Array.ConvertAll(weights[channelMap[al.channel]], x => Vec4ToColor32(x));
|
|
break;
|
|
}
|
|
|
|
case MeshChannel.Tangent:
|
|
{
|
|
mesh.tangents = weights[channelMap[MeshChannel.Tangent]];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
System.Text.StringBuilder sb = new System.Text.StringBuilder();
|
|
|
|
foreach(AttributeLayout al in attributeLayout)
|
|
sb.AppendLine(al.ToString());
|
|
|
|
sb.AppendLine("--");
|
|
|
|
for(int i = 0; i < weightCount; i++)
|
|
sb.AppendLine(weights[i].ToString());
|
|
|
|
return sb.ToString();
|
|
}
|
|
}
|
|
}
|