1489 lines
53 KiB
C#
1489 lines
53 KiB
C#
using UnityEngine;
|
|
using System.Collections.Generic;
|
|
|
|
namespace Dreamteck.Splines
|
|
{
|
|
public partial class SplineMesh : MeshGenerator
|
|
{
|
|
|
|
[System.Serializable]
|
|
public class Channel
|
|
{
|
|
public delegate float FloatHandler(double percent);
|
|
public delegate Vector2 Vector2Handler(double percent);
|
|
public delegate Vector3 Vector3Handler(double percent);
|
|
public delegate Quaternion QuaternionHandler(double percent);
|
|
|
|
public string name = "Channel";
|
|
public enum Type { Extrude, Place }
|
|
public enum UVOverride { None, ClampU, ClampV, UniformU, UniformV }
|
|
|
|
private System.Random iterationRandom;
|
|
[SerializeField]
|
|
[HideInInspector]
|
|
private int _iterationSeed = 0;
|
|
[SerializeField]
|
|
[HideInInspector]
|
|
private int _offsetSeed = 0;
|
|
private System.Random _offsetRandom;
|
|
private Vector2Handler _offsetHandler = null;
|
|
[SerializeField]
|
|
[HideInInspector]
|
|
private int _rotationSeed = 0;
|
|
private System.Random _rotationRandom;
|
|
private QuaternionHandler _placeRotationHandler = null;
|
|
private FloatHandler _extrudeRotationHandler = null;
|
|
[SerializeField]
|
|
[HideInInspector]
|
|
private int _scaleSeed = 0;
|
|
private System.Random _scaleRandom;
|
|
private Vector3Handler _scaleHandler = null;
|
|
|
|
[SerializeField]
|
|
internal SplineMesh owner = null;
|
|
|
|
[SerializeField]
|
|
[HideInInspector]
|
|
private List<MeshDefinition> meshes = new List<MeshDefinition>();
|
|
|
|
|
|
[SerializeField]
|
|
[HideInInspector]
|
|
private double _clipFrom = 0.0;
|
|
[SerializeField]
|
|
[HideInInspector]
|
|
private double _clipTo = 1.0;
|
|
[SerializeField]
|
|
[HideInInspector]
|
|
private bool _randomOrder = false;
|
|
[SerializeField]
|
|
[HideInInspector]
|
|
private UVOverride _overrideUVs = UVOverride.None;
|
|
[SerializeField]
|
|
[HideInInspector]
|
|
private Vector2 _uvScale = Vector2.one;
|
|
[SerializeField]
|
|
[HideInInspector]
|
|
private Vector2 _uvOffset = Vector2.zero;
|
|
[SerializeField]
|
|
[HideInInspector]
|
|
private bool _overrideNormal = false;
|
|
[SerializeField]
|
|
[HideInInspector]
|
|
private Vector3 _customNormal = Vector3.up;
|
|
[SerializeField]
|
|
[HideInInspector]
|
|
private Type _type = Type.Extrude;
|
|
|
|
[SerializeField]
|
|
[HideInInspector]
|
|
private int _count = 1;
|
|
[SerializeField]
|
|
[HideInInspector]
|
|
private bool _autoCount = false;
|
|
[SerializeField]
|
|
[HideInInspector]
|
|
private double _spacing = 0.0;
|
|
[SerializeField]
|
|
[HideInInspector]
|
|
private bool _randomRotation = false;
|
|
[SerializeField]
|
|
[HideInInspector]
|
|
private Vector3 _minRotation = Vector3.zero;
|
|
[SerializeField]
|
|
[HideInInspector]
|
|
private Vector3 _maxRotation = Vector3.zero;
|
|
[SerializeField]
|
|
[HideInInspector]
|
|
private bool _randomOffset = false;
|
|
[SerializeField]
|
|
[HideInInspector]
|
|
private Vector2 _minOffset = Vector2.one;
|
|
[SerializeField]
|
|
[HideInInspector]
|
|
private Vector2 _maxOffset = Vector2.one;
|
|
[SerializeField]
|
|
[HideInInspector]
|
|
private bool _randomScale = false;
|
|
[SerializeField]
|
|
[HideInInspector]
|
|
private bool _uniformRandomScale = false;
|
|
[SerializeField]
|
|
[HideInInspector]
|
|
private Vector3 _minScale = Vector3.one;
|
|
[SerializeField]
|
|
[HideInInspector]
|
|
private Vector3 _maxScale = Vector3.one;
|
|
private int iterator = 0;
|
|
[SerializeField]
|
|
[HideInInspector]
|
|
private bool _overrideMaterialID = false;
|
|
[SerializeField]
|
|
[HideInInspector]
|
|
private int _targetMaterialID = 0;
|
|
|
|
[SerializeField]
|
|
[HideInInspector]
|
|
protected MeshScaleModifier _scaleModifier = new MeshScaleModifier();
|
|
|
|
public double clipFrom
|
|
{
|
|
get { return _clipFrom; }
|
|
set
|
|
{
|
|
if (value != _clipFrom)
|
|
{
|
|
_clipFrom = value;
|
|
Rebuild();
|
|
}
|
|
}
|
|
}
|
|
|
|
public double clipTo
|
|
{
|
|
get { return _clipTo; }
|
|
set
|
|
{
|
|
if (value != _clipTo)
|
|
{
|
|
_clipTo = value;
|
|
Rebuild();
|
|
}
|
|
}
|
|
}
|
|
|
|
public bool randomOffset
|
|
{
|
|
get { return _randomOffset; }
|
|
set
|
|
{
|
|
if (value != _randomOffset)
|
|
{
|
|
_randomOffset = value;
|
|
Rebuild();
|
|
}
|
|
}
|
|
}
|
|
|
|
public Vector2Handler offsetHandler
|
|
{
|
|
get { return _offsetHandler; }
|
|
set
|
|
{
|
|
if (value != _offsetHandler)
|
|
{
|
|
_offsetHandler = value;
|
|
Rebuild();
|
|
}
|
|
}
|
|
}
|
|
|
|
public bool overrideMaterialID
|
|
{
|
|
get { return _overrideMaterialID; }
|
|
set
|
|
{
|
|
if (value != _overrideMaterialID)
|
|
{
|
|
_overrideMaterialID = value;
|
|
Rebuild();
|
|
}
|
|
}
|
|
}
|
|
|
|
public int targetMaterialID
|
|
{
|
|
get { return _targetMaterialID; }
|
|
set
|
|
{
|
|
if (value != _targetMaterialID)
|
|
{
|
|
_targetMaterialID = value;
|
|
Rebuild();
|
|
}
|
|
}
|
|
}
|
|
|
|
public bool randomRotation
|
|
{
|
|
get { return _randomRotation; }
|
|
set
|
|
{
|
|
if (value != _randomRotation)
|
|
{
|
|
_randomRotation = value;
|
|
Rebuild();
|
|
}
|
|
}
|
|
}
|
|
|
|
public QuaternionHandler placeRotationHandler
|
|
{
|
|
get { return _placeRotationHandler; }
|
|
set
|
|
{
|
|
if (value != _placeRotationHandler)
|
|
{
|
|
_placeRotationHandler = value;
|
|
Rebuild();
|
|
}
|
|
}
|
|
}
|
|
|
|
public FloatHandler extrudeRotationHandler
|
|
{
|
|
get { return _extrudeRotationHandler; }
|
|
set
|
|
{
|
|
if (value != _extrudeRotationHandler)
|
|
{
|
|
_extrudeRotationHandler = value;
|
|
Rebuild();
|
|
}
|
|
}
|
|
}
|
|
|
|
public bool randomScale
|
|
{
|
|
get { return _randomScale; }
|
|
set
|
|
{
|
|
if (value != _randomScale)
|
|
{
|
|
_randomScale = value;
|
|
Rebuild();
|
|
}
|
|
}
|
|
}
|
|
|
|
public Vector3Handler scaleHandler
|
|
{
|
|
get { return _scaleHandler; }
|
|
set
|
|
{
|
|
if (value != _scaleHandler)
|
|
{
|
|
_scaleHandler = value;
|
|
Rebuild();
|
|
}
|
|
}
|
|
}
|
|
|
|
public bool uniformRandomScale
|
|
{
|
|
get { return _uniformRandomScale; }
|
|
set
|
|
{
|
|
if (value != _uniformRandomScale)
|
|
{
|
|
_uniformRandomScale = value;
|
|
Rebuild();
|
|
}
|
|
}
|
|
}
|
|
|
|
public int offsetSeed
|
|
{
|
|
get { return _offsetSeed; }
|
|
set
|
|
{
|
|
if (value != _offsetSeed)
|
|
{
|
|
_offsetSeed = value;
|
|
Rebuild();
|
|
}
|
|
}
|
|
}
|
|
|
|
public int rotationSeed
|
|
{
|
|
get { return _rotationSeed; }
|
|
set
|
|
{
|
|
if (value != _rotationSeed)
|
|
{
|
|
_rotationSeed = value;
|
|
Rebuild();
|
|
}
|
|
}
|
|
}
|
|
|
|
public int scaleSeed
|
|
{
|
|
get { return _scaleSeed; }
|
|
set
|
|
{
|
|
if (value != _scaleSeed)
|
|
{
|
|
_scaleSeed = value;
|
|
Rebuild();
|
|
}
|
|
}
|
|
}
|
|
|
|
public double spacing
|
|
{
|
|
get { return _spacing; }
|
|
set
|
|
{
|
|
if (value != _spacing)
|
|
{
|
|
_spacing = value;
|
|
Rebuild();
|
|
}
|
|
}
|
|
}
|
|
|
|
public Vector2 minOffset
|
|
{
|
|
get { return _minOffset; }
|
|
set
|
|
{
|
|
if (value != _minOffset)
|
|
{
|
|
_minOffset = value;
|
|
Rebuild();
|
|
}
|
|
}
|
|
}
|
|
|
|
public Vector2 maxOffset
|
|
{
|
|
get { return _maxOffset; }
|
|
set
|
|
{
|
|
if (value != _maxOffset)
|
|
{
|
|
_maxOffset = value;
|
|
Rebuild();
|
|
}
|
|
}
|
|
}
|
|
|
|
public Vector3 minRotation
|
|
{
|
|
get { return _minRotation; }
|
|
set
|
|
{
|
|
if (value != _minRotation)
|
|
{
|
|
_minRotation = value;
|
|
Rebuild();
|
|
}
|
|
}
|
|
}
|
|
|
|
public Vector3 maxRotation
|
|
{
|
|
get { return _maxRotation; }
|
|
set
|
|
{
|
|
if (value != _maxRotation)
|
|
{
|
|
_maxRotation = value;
|
|
Rebuild();
|
|
}
|
|
}
|
|
}
|
|
|
|
public Vector3 minScale
|
|
{
|
|
get { return _minScale; }
|
|
set
|
|
{
|
|
if (value != _minScale)
|
|
{
|
|
_minScale = value;
|
|
Rebuild();
|
|
}
|
|
}
|
|
}
|
|
|
|
public Vector3 maxScale
|
|
{
|
|
get { return _maxScale; }
|
|
set
|
|
{
|
|
if (value != _maxScale)
|
|
{
|
|
_maxScale = value;
|
|
Rebuild();
|
|
}
|
|
}
|
|
}
|
|
|
|
public Type type
|
|
{
|
|
get { return _type; }
|
|
set
|
|
{
|
|
if (value != _type)
|
|
{
|
|
_type = value;
|
|
Rebuild();
|
|
}
|
|
}
|
|
}
|
|
|
|
public bool randomOrder
|
|
{
|
|
get { return _randomOrder; }
|
|
set
|
|
{
|
|
if (value != _randomOrder)
|
|
{
|
|
_randomOrder = value;
|
|
Rebuild();
|
|
}
|
|
}
|
|
}
|
|
|
|
public int randomSeed
|
|
{
|
|
get { return _iterationSeed; }
|
|
set
|
|
{
|
|
if (value != _iterationSeed)
|
|
{
|
|
_iterationSeed = value;
|
|
if (_randomOrder) Rebuild();
|
|
}
|
|
}
|
|
}
|
|
public int count
|
|
{
|
|
get { return _count; }
|
|
set
|
|
{
|
|
if (value != _count)
|
|
{
|
|
_count = value;
|
|
if (_count < 1) _count = 1;
|
|
Rebuild();
|
|
}
|
|
}
|
|
}
|
|
|
|
public bool autoCount
|
|
{
|
|
get { return _autoCount; }
|
|
set
|
|
{
|
|
if (value != _autoCount)
|
|
{
|
|
_autoCount = value;
|
|
Rebuild();
|
|
}
|
|
}
|
|
}
|
|
|
|
public UVOverride overrideUVs
|
|
{
|
|
get { return _overrideUVs; }
|
|
set
|
|
{
|
|
if (value != _overrideUVs)
|
|
{
|
|
_overrideUVs = value;
|
|
Rebuild();
|
|
}
|
|
}
|
|
}
|
|
|
|
public Vector2 uvOffset
|
|
{
|
|
get { return _uvOffset; }
|
|
set
|
|
{
|
|
if (value != _uvOffset)
|
|
{
|
|
_uvOffset = value;
|
|
Rebuild();
|
|
}
|
|
}
|
|
}
|
|
|
|
public Vector2 uvScale
|
|
{
|
|
get { return _uvScale; }
|
|
set
|
|
{
|
|
if (value != _uvScale)
|
|
{
|
|
_uvScale = value;
|
|
Rebuild();
|
|
}
|
|
}
|
|
}
|
|
|
|
public bool overrideNormal
|
|
{
|
|
get { return _overrideNormal; }
|
|
set
|
|
{
|
|
if (value != _overrideNormal)
|
|
{
|
|
_overrideNormal = value;
|
|
Rebuild();
|
|
}
|
|
}
|
|
}
|
|
|
|
public Vector3 customNormal
|
|
{
|
|
get { return _customNormal; }
|
|
set
|
|
{
|
|
if (value != _customNormal)
|
|
{
|
|
_customNormal = value;
|
|
Rebuild();
|
|
}
|
|
}
|
|
}
|
|
|
|
public MeshScaleModifier scaleModifier
|
|
{
|
|
get
|
|
{
|
|
return _scaleModifier;
|
|
}
|
|
}
|
|
|
|
public Channel(string n, SplineMesh parent)
|
|
{
|
|
name = n;
|
|
owner = parent;
|
|
Init();
|
|
}
|
|
|
|
public Channel(string n, Mesh inputMesh, SplineMesh parent)
|
|
{
|
|
name = n;
|
|
owner = parent;
|
|
meshes.Add(new MeshDefinition(inputMesh));
|
|
Init();
|
|
Rebuild();
|
|
}
|
|
|
|
void Init()
|
|
{
|
|
_minScale = _maxScale = Vector3.one;
|
|
_minOffset = _maxOffset = Vector3.zero;
|
|
_minRotation = _maxRotation = Vector3.zero;
|
|
}
|
|
|
|
public void CopyTo(Channel target)
|
|
{
|
|
target.meshes.Clear();
|
|
for (int i = 0; i < meshes.Count; i++) target.meshes.Add(meshes[i].Copy());
|
|
target._clipFrom = _clipFrom;
|
|
target._clipTo = _clipTo;
|
|
target._customNormal = _customNormal;
|
|
target._iterationSeed = _iterationSeed;
|
|
target._minOffset = _minOffset;
|
|
target._minRotation = _minRotation;
|
|
target._minScale = _minScale;
|
|
target._maxOffset = _maxOffset;
|
|
target._maxRotation = _maxRotation;
|
|
target._maxScale = _maxScale;
|
|
target._randomOffset = _randomOffset;
|
|
target._randomRotation = _randomRotation;
|
|
target._randomScale = _randomScale;
|
|
target._offsetSeed = _offsetSeed;
|
|
target._offsetHandler = _offsetHandler;
|
|
target._rotationSeed = _rotationSeed;
|
|
target._placeRotationHandler = _placeRotationHandler;
|
|
target._extrudeRotationHandler = _extrudeRotationHandler;
|
|
target._scaleSeed = _scaleSeed;
|
|
target._scaleHandler = _scaleHandler;
|
|
target._iterationSeed = _iterationSeed;
|
|
target._count = _count;
|
|
target._spacing = _spacing;
|
|
target._overrideUVs = _overrideUVs;
|
|
target._type = _type;
|
|
target._overrideMaterialID = _overrideMaterialID;
|
|
target._targetMaterialID = _targetMaterialID;
|
|
target._overrideNormal = _overrideNormal;
|
|
}
|
|
|
|
public int GetMeshCount()
|
|
{
|
|
return meshes.Count;
|
|
}
|
|
|
|
public void SwapMeshes(int a, int b)
|
|
{
|
|
if (a < 0 || a >= meshes.Count || b < 0 || b >= meshes.Count) return;
|
|
MeshDefinition temp = meshes[b];
|
|
meshes[b] = meshes[a];
|
|
meshes[a] = temp;
|
|
Rebuild();
|
|
}
|
|
|
|
public void DuplicateMesh(int index)
|
|
{
|
|
if (index < 0 || index >= meshes.Count) return;
|
|
meshes.Add(meshes[index].Copy());
|
|
Rebuild();
|
|
}
|
|
|
|
public MeshDefinition GetMesh(int index)
|
|
{
|
|
return meshes[index];
|
|
}
|
|
|
|
public void AddMesh(Mesh input)
|
|
{
|
|
meshes.Add(new MeshDefinition(input));
|
|
Rebuild();
|
|
}
|
|
|
|
public void AddMesh(MeshDefinition meshDefinition)
|
|
{
|
|
if (!meshes.Contains(meshDefinition))
|
|
{
|
|
meshes.Add(meshDefinition);
|
|
Rebuild();
|
|
}
|
|
}
|
|
|
|
public void RemoveMesh(int index)
|
|
{
|
|
meshes.RemoveAt(index);
|
|
Rebuild();
|
|
}
|
|
|
|
public void ResetIteration()
|
|
{
|
|
if (_randomOrder) iterationRandom = new System.Random(_iterationSeed);
|
|
if (_randomOffset) _offsetRandom = new System.Random(_offsetSeed);
|
|
if (_randomRotation) _rotationRandom = new System.Random(_rotationSeed);
|
|
if (_randomScale) _scaleRandom = new System.Random(_scaleSeed);
|
|
iterator = 0;
|
|
}
|
|
|
|
public (Vector2, Quaternion, Vector3) GetCustomPlaceValues(double percent)
|
|
{
|
|
(Vector2, Quaternion, Vector3) values = (Vector2.zero, Quaternion.identity, Vector3.one);
|
|
if (_offsetHandler != null)
|
|
{
|
|
values.Item1 = _offsetHandler(percent);
|
|
}
|
|
if (_placeRotationHandler != null)
|
|
{
|
|
values.Item2 = _placeRotationHandler(percent);
|
|
}
|
|
if (_scaleHandler != null)
|
|
{
|
|
values.Item3 = _scaleHandler(percent);
|
|
}
|
|
return values;
|
|
}
|
|
|
|
public (Vector2, float, Vector3) GetCustomExtrudeValues(double percent)
|
|
{
|
|
(Vector2, float, Vector3) values = (Vector2.zero, 0f, Vector3.one);
|
|
if (_offsetHandler != null)
|
|
{
|
|
values.Item1 = _offsetHandler(percent);
|
|
}
|
|
if (_extrudeRotationHandler != null)
|
|
{
|
|
values.Item2 = _extrudeRotationHandler(percent);
|
|
}
|
|
if (_scaleHandler != null)
|
|
{
|
|
values.Item3 = _scaleHandler(percent);
|
|
}
|
|
return values;
|
|
}
|
|
|
|
public Vector2 NextRandomOffset()
|
|
{
|
|
if (_randomOffset) return new Vector2(Mathf.Lerp(_minOffset.x, _maxOffset.x, (float)_offsetRandom.NextDouble()), Mathf.Lerp(_minOffset.y, _maxOffset.y, (float)_offsetRandom.NextDouble()));
|
|
return _minOffset;
|
|
}
|
|
|
|
public Quaternion NextRandomQuaternion()
|
|
{
|
|
if (_randomRotation) return Quaternion.Euler(new Vector3(Mathf.Lerp(_minRotation.x, _maxRotation.x, (float)_rotationRandom.NextDouble()), Mathf.Lerp(_minRotation.y, _maxRotation.y, (float)_rotationRandom.NextDouble()), Mathf.Lerp(_minRotation.z, _maxRotation.z, (float)_rotationRandom.NextDouble())));
|
|
return Quaternion.Euler(_minRotation);
|
|
}
|
|
|
|
public float NextRandomAngle()
|
|
{
|
|
if (_randomRotation) return Mathf.Lerp(_minRotation.z, _maxRotation.z, (float)_rotationRandom.NextDouble());
|
|
return _minRotation.z;
|
|
}
|
|
|
|
public Vector3 NextRandomScale()
|
|
{
|
|
if (_randomScale)
|
|
{
|
|
if (_uniformRandomScale) return Vector3.Lerp(new Vector3(_minScale.x, _minScale.y, 1f), new Vector3(_maxScale.x, _maxScale.y, 1f), (float)_scaleRandom.NextDouble());
|
|
return new Vector3(Mathf.Lerp(_minScale.x, _maxScale.x, (float)_scaleRandom.NextDouble()), Mathf.Lerp(_minScale.y, _maxScale.y, (float)_scaleRandom.NextDouble()), 1f);
|
|
}
|
|
return new Vector3(_minScale.x, _minScale.y, 1f);
|
|
}
|
|
|
|
public Vector3 NextPlaceScale()
|
|
{
|
|
if (_randomScale)
|
|
{
|
|
if (_uniformRandomScale) return Vector3.Lerp(_minScale, _maxScale, (float)_scaleRandom.NextDouble());
|
|
return new Vector3(Mathf.Lerp(_minScale.x, _maxScale.x, (float)_scaleRandom.NextDouble()), Mathf.Lerp(_minScale.y, _maxScale.y, (float)_scaleRandom.NextDouble()), Mathf.Lerp(_minScale.z, _maxScale.z, (float)_scaleRandom.NextDouble()));
|
|
}
|
|
return _minScale;
|
|
}
|
|
|
|
public MeshDefinition NextMesh()
|
|
{
|
|
if (_randomOrder) return meshes[iterationRandom.Next(meshes.Count)];
|
|
else
|
|
{
|
|
if (iterator >= meshes.Count) iterator = 0;
|
|
return meshes[iterator++];
|
|
}
|
|
}
|
|
|
|
internal void Rebuild()
|
|
{
|
|
if (owner != null) owner.Rebuild();
|
|
}
|
|
|
|
void Refresh()
|
|
{
|
|
for (int i = 0; i < meshes.Count; i++) meshes[i].Refresh();
|
|
Rebuild();
|
|
}
|
|
|
|
[System.Serializable]
|
|
public struct BoundsSpacing
|
|
{
|
|
public float front;
|
|
public float back;
|
|
}
|
|
|
|
[System.Serializable]
|
|
public class MeshDefinition
|
|
{
|
|
public enum MirrorMethod { None, X, Y, Z }
|
|
[SerializeField]
|
|
[HideInInspector]
|
|
public Vector3[] vertices = new Vector3[0];
|
|
[SerializeField]
|
|
[HideInInspector]
|
|
public Vector3[] normals = new Vector3[0];
|
|
[SerializeField]
|
|
[HideInInspector]
|
|
public Vector4[] tangents = new Vector4[0];
|
|
[SerializeField]
|
|
[HideInInspector]
|
|
public Color[] colors = new Color[0];
|
|
[SerializeField]
|
|
[HideInInspector]
|
|
public Vector2[] uv = new Vector2[0];
|
|
[SerializeField]
|
|
[HideInInspector]
|
|
public Vector2[] uv2 = new Vector2[0];
|
|
[SerializeField]
|
|
[HideInInspector]
|
|
public Vector2[] uv3 = new Vector2[0];
|
|
[SerializeField]
|
|
[HideInInspector]
|
|
public Vector2[] uv4 = new Vector2[0];
|
|
[SerializeField]
|
|
[HideInInspector]
|
|
public int[] triangles = new int[0];
|
|
[SerializeField]
|
|
[HideInInspector]
|
|
public List<Submesh> subMeshes = new List<Submesh>();
|
|
[SerializeField]
|
|
[HideInInspector]
|
|
public TS_Bounds bounds = new TS_Bounds(Vector3.zero, Vector3.zero);
|
|
[SerializeField]
|
|
[HideInInspector]
|
|
public List<VertexGroup> vertexGroups = new List<VertexGroup>();
|
|
[SerializeField]
|
|
[HideInInspector]
|
|
private Mesh _mesh = null;
|
|
[SerializeField]
|
|
[HideInInspector]
|
|
private Vector3 _rotation = Vector3.zero;
|
|
[SerializeField]
|
|
[HideInInspector]
|
|
private Vector3 _offset = Vector3.zero;
|
|
[SerializeField]
|
|
[HideInInspector]
|
|
private Vector3 _scale = Vector3.one;
|
|
[SerializeField]
|
|
[HideInInspector]
|
|
private Vector2 _uvScale = Vector2.one;
|
|
[SerializeField]
|
|
[HideInInspector]
|
|
private Vector2 _uvOffset = Vector2.zero;
|
|
[SerializeField]
|
|
[HideInInspector]
|
|
private float _uvRotation = 0f;
|
|
[SerializeField]
|
|
[HideInInspector]
|
|
private MirrorMethod _mirror = MirrorMethod.None;
|
|
[SerializeField]
|
|
[HideInInspector]
|
|
public BoundsSpacing _spacing = new BoundsSpacing();
|
|
|
|
[SerializeField]
|
|
[HideInInspector]
|
|
private float _vertexGroupingMargin = 0f;
|
|
[SerializeField]
|
|
[HideInInspector]
|
|
private bool _removeInnerFaces = false;
|
|
[SerializeField]
|
|
[HideInInspector]
|
|
private bool _flipFaces = false;
|
|
[SerializeField]
|
|
[HideInInspector]
|
|
private bool _doubleSided = false;
|
|
|
|
public Mesh mesh
|
|
{
|
|
get
|
|
{
|
|
return _mesh;
|
|
}
|
|
set
|
|
{
|
|
if (_mesh != value)
|
|
{
|
|
_mesh = value;
|
|
Refresh();
|
|
}
|
|
}
|
|
}
|
|
|
|
public Vector3 rotation
|
|
{
|
|
get
|
|
{
|
|
return _rotation;
|
|
}
|
|
set
|
|
{
|
|
if (rotation != value)
|
|
{
|
|
_rotation = value;
|
|
Refresh();
|
|
}
|
|
}
|
|
}
|
|
|
|
public Vector3 offset
|
|
{
|
|
get
|
|
{
|
|
return _offset;
|
|
}
|
|
set
|
|
{
|
|
if (_offset != value)
|
|
{
|
|
_offset = value;
|
|
Refresh();
|
|
}
|
|
}
|
|
}
|
|
|
|
public Vector3 scale
|
|
{
|
|
get
|
|
{
|
|
return _scale;
|
|
}
|
|
set
|
|
{
|
|
if (_scale != value)
|
|
{
|
|
_scale = value;
|
|
Refresh();
|
|
}
|
|
}
|
|
}
|
|
|
|
public BoundsSpacing spacing
|
|
{
|
|
get
|
|
{
|
|
return _spacing;
|
|
}
|
|
set
|
|
{
|
|
if (_spacing.back != value.back || _spacing.front != value.front)
|
|
{
|
|
_spacing = value;
|
|
Refresh();
|
|
}
|
|
}
|
|
}
|
|
|
|
public Vector2 uvScale
|
|
{
|
|
get
|
|
{
|
|
return _uvScale;
|
|
}
|
|
set
|
|
{
|
|
if (_uvScale != value)
|
|
{
|
|
_uvScale = value;
|
|
Refresh();
|
|
}
|
|
}
|
|
}
|
|
|
|
public Vector2 uvOffset
|
|
{
|
|
get
|
|
{
|
|
return _uvOffset;
|
|
}
|
|
set
|
|
{
|
|
if (_uvOffset != value)
|
|
{
|
|
_uvOffset = value;
|
|
Refresh();
|
|
}
|
|
}
|
|
}
|
|
|
|
public float uvRotation
|
|
{
|
|
get
|
|
{
|
|
return _uvRotation;
|
|
}
|
|
set
|
|
{
|
|
if (_uvRotation != value)
|
|
{
|
|
_uvRotation = value;
|
|
Refresh();
|
|
}
|
|
}
|
|
}
|
|
|
|
public float vertexGroupingMargin
|
|
{
|
|
get
|
|
{
|
|
return _vertexGroupingMargin;
|
|
}
|
|
set
|
|
{
|
|
if (_vertexGroupingMargin != value)
|
|
{
|
|
_vertexGroupingMargin = value;
|
|
Refresh();
|
|
}
|
|
}
|
|
}
|
|
|
|
public MirrorMethod mirror
|
|
{
|
|
get { return _mirror; }
|
|
set
|
|
{
|
|
if (_mirror != value)
|
|
{
|
|
_mirror = value;
|
|
Refresh();
|
|
}
|
|
}
|
|
}
|
|
|
|
public bool removeInnerFaces
|
|
{
|
|
get { return _removeInnerFaces; }
|
|
set
|
|
{
|
|
if (_removeInnerFaces != value)
|
|
{
|
|
_removeInnerFaces = value;
|
|
Refresh();
|
|
}
|
|
}
|
|
}
|
|
|
|
public bool flipFaces
|
|
{
|
|
get { return _flipFaces; }
|
|
set
|
|
{
|
|
if (_flipFaces != value)
|
|
{
|
|
_flipFaces = value;
|
|
Refresh();
|
|
}
|
|
}
|
|
}
|
|
|
|
public bool doubleSided
|
|
{
|
|
get { return _doubleSided; }
|
|
set
|
|
{
|
|
if (_doubleSided != value)
|
|
{
|
|
_doubleSided = value;
|
|
Refresh();
|
|
}
|
|
}
|
|
}
|
|
|
|
internal MeshDefinition Copy()
|
|
{
|
|
MeshDefinition target = new MeshDefinition(_mesh);
|
|
target.vertices = new Vector3[vertices.Length];
|
|
target.normals = new Vector3[normals.Length];
|
|
target.colors = new Color[colors.Length];
|
|
target.tangents = new Vector4[tangents.Length];
|
|
target.uv = new Vector2[uv.Length];
|
|
target.uv2 = new Vector2[uv2.Length];
|
|
target.uv3 = new Vector2[uv3.Length];
|
|
target.uv4 = new Vector2[uv4.Length];
|
|
target.triangles = new int[triangles.Length];
|
|
|
|
vertices.CopyTo(target.vertices, 0);
|
|
normals.CopyTo(target.normals, 0);
|
|
colors.CopyTo(target.colors, 0);
|
|
tangents.CopyTo(target.tangents, 0);
|
|
uv.CopyTo(target.uv, 0);
|
|
uv2.CopyTo(target.uv2, 0);
|
|
uv3.CopyTo(target.uv3, 0);
|
|
uv4.CopyTo(target.uv4, 0);
|
|
triangles.CopyTo(target.triangles, 0);
|
|
|
|
target.bounds = new TS_Bounds(bounds.min, bounds.max);
|
|
target.subMeshes = new List<Submesh>();
|
|
for (int i = 0; i < subMeshes.Count; i++)
|
|
{
|
|
target.subMeshes.Add(new Submesh(new int[subMeshes[i].triangles.Length]));
|
|
subMeshes[i].triangles.CopyTo(target.subMeshes[target.subMeshes.Count - 1].triangles, 0);
|
|
}
|
|
|
|
target._mirror = _mirror;
|
|
target._offset = _offset;
|
|
target._rotation = _rotation;
|
|
target._scale = _scale;
|
|
target._uvOffset = _uvOffset;
|
|
target._uvScale = _uvScale;
|
|
target._uvRotation = _uvRotation;
|
|
target._flipFaces = _flipFaces;
|
|
target._doubleSided = _doubleSided;
|
|
return target;
|
|
}
|
|
|
|
public MeshDefinition(Mesh input)
|
|
{
|
|
_mesh = input;
|
|
Refresh();
|
|
}
|
|
|
|
public void Refresh()
|
|
{
|
|
if (_mesh == null)
|
|
{
|
|
vertices = new Vector3[0];
|
|
normals = new Vector3[0];
|
|
colors = new Color[0];
|
|
uv = new Vector2[0];
|
|
uv2 = new Vector2[0];
|
|
uv3 = new Vector2[0];
|
|
uv4 = new Vector2[0];
|
|
tangents = new Vector4[0];
|
|
triangles = new int[0];
|
|
subMeshes = new List<Submesh>();
|
|
vertexGroups = new List<VertexGroup>();
|
|
return;
|
|
}
|
|
if (vertices.Length != _mesh.vertexCount) vertices = new Vector3[_mesh.vertexCount];
|
|
if (normals.Length != _mesh.normals.Length) normals = new Vector3[_mesh.normals.Length];
|
|
if (colors.Length != _mesh.colors.Length) colors = new Color[_mesh.colors.Length];
|
|
if (uv.Length != _mesh.uv.Length) uv = new Vector2[_mesh.uv.Length];
|
|
if (uv2.Length != _mesh.uv2.Length) uv2 = new Vector2[_mesh.uv2.Length];
|
|
if (uv3.Length != _mesh.uv3.Length) uv3 = new Vector2[_mesh.uv3.Length];
|
|
if (uv4.Length != _mesh.uv4.Length) uv4 = new Vector2[_mesh.uv4.Length];
|
|
if (tangents.Length != _mesh.tangents.Length) tangents = new Vector4[_mesh.tangents.Length];
|
|
if (triangles.Length != _mesh.triangles.Length) triangles = new int[_mesh.triangles.Length];
|
|
|
|
vertices = _mesh.vertices;
|
|
normals = _mesh.normals;
|
|
colors = _mesh.colors;
|
|
uv = _mesh.uv;
|
|
uv2 = _mesh.uv2;
|
|
uv3 = _mesh.uv3;
|
|
uv4 = _mesh.uv4;
|
|
tangents = _mesh.tangents;
|
|
triangles = _mesh.triangles;
|
|
colors = _mesh.colors;
|
|
|
|
while (subMeshes.Count > _mesh.subMeshCount) subMeshes.RemoveAt(0);
|
|
while (subMeshes.Count < _mesh.subMeshCount) subMeshes.Add(new Submesh(new int[0]));
|
|
for (int i = 0; i < subMeshes.Count; i++) subMeshes[i].triangles = _mesh.GetTriangles(i);
|
|
|
|
|
|
if (colors.Length != vertices.Length)
|
|
{
|
|
colors = new Color[vertices.Length];
|
|
for (int i = 0; i < colors.Length; i++) colors[i] = Color.white;
|
|
}
|
|
Mirror();
|
|
if (_doubleSided) DoubleSided();
|
|
else if (_flipFaces) FlipFaces();
|
|
TransformVertices();
|
|
CalculateBounds();
|
|
if (_removeInnerFaces) RemoveInnerFaces();
|
|
GroupVertices();
|
|
}
|
|
|
|
void RemoveInnerFaces()
|
|
{
|
|
float min = float.MaxValue, max = 0f;
|
|
for (int i = 0; i < vertices.Length; i++)
|
|
{
|
|
if (vertices[i].z < min) min = vertices[i].z;
|
|
if (vertices[i].z > max) max = vertices[i].z;
|
|
}
|
|
|
|
for (int i = 0; i < subMeshes.Count; i++)
|
|
{
|
|
List<int> newTris = new List<int>();
|
|
for (int j = 0; j < subMeshes[i].triangles.Length; j += 3)
|
|
{
|
|
bool innerMax = true, innerMin = true;
|
|
for (int k = j; k < j + 3; k++)
|
|
{
|
|
int index = subMeshes[i].triangles[k];
|
|
if (!Mathf.Approximately(vertices[index].z, max)) innerMax = false;
|
|
if (!Mathf.Approximately(vertices[index].z, min)) innerMin = false;
|
|
}
|
|
if (!innerMax && !innerMin)
|
|
{
|
|
newTris.Add(subMeshes[i].triangles[j]);
|
|
newTris.Add(subMeshes[i].triangles[j + 1]);
|
|
newTris.Add(subMeshes[i].triangles[j + 2]);
|
|
}
|
|
}
|
|
subMeshes[i].triangles = newTris.ToArray();
|
|
}
|
|
}
|
|
|
|
void FlipFaces()
|
|
{
|
|
TS_Mesh temp = new TS_Mesh();
|
|
temp.normals = normals;
|
|
temp.tangents = tangents;
|
|
temp.triangles = triangles;
|
|
for (int i = 0; i < subMeshes.Count; i++) temp.subMeshes.Add(subMeshes[i].triangles);
|
|
MeshUtility.FlipFaces(temp);
|
|
}
|
|
|
|
void DoubleSided()
|
|
{
|
|
TS_Mesh temp = new TS_Mesh();
|
|
temp.vertices = vertices;
|
|
temp.normals = normals;
|
|
temp.tangents = tangents;
|
|
temp.colors = colors;
|
|
temp.uv = uv;
|
|
temp.uv2 = uv2;
|
|
temp.uv3 = uv3;
|
|
temp.uv4 = uv4;
|
|
temp.triangles = triangles;
|
|
for (int i = 0; i < subMeshes.Count; i++) temp.subMeshes.Add(subMeshes[i].triangles);
|
|
MeshUtility.MakeDoublesided(temp);
|
|
vertices = temp.vertices;
|
|
normals = temp.normals;
|
|
tangents = temp.tangents;
|
|
colors = temp.colors;
|
|
uv = temp.uv;
|
|
uv2 = temp.uv2;
|
|
uv3 = temp.uv3;
|
|
uv4 = temp.uv4;
|
|
triangles = temp.triangles;
|
|
for (int i = 0; i < subMeshes.Count; i++) subMeshes[i].triangles = temp.subMeshes[i];
|
|
}
|
|
|
|
public void Write(TS_Mesh target, int forceMaterialId = -1)
|
|
{
|
|
if (target.vertices.Length != vertices.Length) target.vertices = new Vector3[vertices.Length];
|
|
if (target.normals.Length != normals.Length) target.normals = new Vector3[normals.Length];
|
|
if (target.colors.Length != colors.Length) target.colors = new Color[colors.Length];
|
|
if (target.uv.Length != uv.Length) target.uv = new Vector2[uv.Length];
|
|
if (target.uv2.Length != uv2.Length) target.uv2 = new Vector2[uv2.Length];
|
|
if (target.uv3.Length != uv3.Length) target.uv3 = new Vector2[uv3.Length];
|
|
if (target.uv4.Length != uv4.Length) target.uv4 = new Vector2[uv4.Length];
|
|
if (target.tangents.Length != tangents.Length) target.tangents = new Vector4[tangents.Length];
|
|
if (target.triangles.Length != triangles.Length) target.triangles = new int[triangles.Length];
|
|
|
|
vertices.CopyTo(target.vertices, 0);
|
|
normals.CopyTo(target.normals, 0);
|
|
colors.CopyTo(target.colors, 0);
|
|
uv.CopyTo(target.uv, 0);
|
|
uv2.CopyTo(target.uv2, 0);
|
|
uv3.CopyTo(target.uv3, 0);
|
|
uv4.CopyTo(target.uv4, 0);
|
|
tangents.CopyTo(target.tangents, 0);
|
|
triangles.CopyTo(target.triangles, 0);
|
|
|
|
if (target.subMeshes == null) target.subMeshes = new List<int[]>();
|
|
|
|
if (forceMaterialId >= 0)
|
|
{
|
|
while (target.subMeshes.Count > forceMaterialId + 1) target.subMeshes.RemoveAt(0);
|
|
while (target.subMeshes.Count < forceMaterialId + 1) target.subMeshes.Add(new int[0]);
|
|
for (int i = 0; i < target.subMeshes.Count; i++)
|
|
{
|
|
if (i != forceMaterialId)
|
|
{
|
|
if (target.subMeshes[i].Length > 0) target.subMeshes[i] = new int[0];
|
|
}
|
|
else
|
|
{
|
|
if (target.subMeshes[i].Length != triangles.Length) target.subMeshes[i] = new int[triangles.Length];
|
|
triangles.CopyTo(target.subMeshes[i], 0);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
while (target.subMeshes.Count > subMeshes.Count) target.subMeshes.RemoveAt(0);
|
|
while (target.subMeshes.Count < subMeshes.Count) target.subMeshes.Add(new int[0]);
|
|
for (int i = 0; i < subMeshes.Count; i++)
|
|
{
|
|
if (subMeshes[i].triangles.Length != target.subMeshes[i].Length) target.subMeshes[i] = new int[subMeshes[i].triangles.Length];
|
|
subMeshes[i].triangles.CopyTo(target.subMeshes[i], 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CalculateBounds()
|
|
{
|
|
Vector3 min = Vector3.zero;
|
|
Vector3 max = Vector3.zero;
|
|
for (int i = 0; i < vertices.Length; i++)
|
|
{
|
|
if (vertices[i].x < min.x) min.x = vertices[i].x;
|
|
else if (vertices[i].x > max.x) max.x = vertices[i].x;
|
|
if (vertices[i].y < min.y) min.y = vertices[i].y;
|
|
else if (vertices[i].y > max.y) max.y = vertices[i].y;
|
|
if (vertices[i].z < min.z) min.z = vertices[i].z;
|
|
else if (vertices[i].z > max.z) max.z = vertices[i].z;
|
|
}
|
|
|
|
min.z -= spacing.back;
|
|
max.z += spacing.front;
|
|
bounds.CreateFromMinMax(min, max);
|
|
}
|
|
|
|
private void Mirror()
|
|
{
|
|
if (_mirror == MirrorMethod.None) return;
|
|
switch (_mirror)
|
|
{
|
|
case MirrorMethod.X:
|
|
for (int i = 0; i < vertices.Length; i++)
|
|
{
|
|
vertices[i].x *= -1f;
|
|
normals[i].x = -normals[i].x;
|
|
}
|
|
break;
|
|
case MirrorMethod.Y:
|
|
for (int i = 0; i < vertices.Length; i++)
|
|
{
|
|
vertices[i].y *= -1f;
|
|
normals[i].y = -normals[i].y;
|
|
}
|
|
break;
|
|
case MirrorMethod.Z:
|
|
for (int i = 0; i < vertices.Length; i++)
|
|
{
|
|
vertices[i].z *= -1f;
|
|
normals[i].z = -normals[i].z;
|
|
}
|
|
break;
|
|
}
|
|
for (int i = 0; i < triangles.Length; i += 3)
|
|
{
|
|
int temp = triangles[i];
|
|
triangles[i] = triangles[i + 2];
|
|
triangles[i + 2] = temp;
|
|
}
|
|
for (int i = 0; i < subMeshes.Count; i++)
|
|
{
|
|
for (int j = 0; j < subMeshes[i].triangles.Length; j += 3)
|
|
{
|
|
int temp = subMeshes[i].triangles[j];
|
|
subMeshes[i].triangles[j] = subMeshes[i].triangles[j + 2];
|
|
subMeshes[i].triangles[j + 2] = temp;
|
|
}
|
|
}
|
|
CalculateTangents();
|
|
}
|
|
|
|
void TransformVertices()
|
|
{
|
|
Matrix4x4 vertexMatrix = new Matrix4x4();
|
|
vertexMatrix.SetTRS(_offset, Quaternion.Euler(_rotation), _scale);
|
|
Matrix4x4 normalMatrix = vertexMatrix.inverse.transpose;
|
|
for (int i = 0; i < vertices.Length; i++)
|
|
{
|
|
vertices[i] = vertexMatrix.MultiplyPoint3x4(vertices[i]);
|
|
normals[i] = normalMatrix.MultiplyVector(normals[i]).normalized;
|
|
}
|
|
for (int i = 0; i < tangents.Length; i++) tangents[i] = normalMatrix.MultiplyVector(tangents[i]);
|
|
for (int i = 0; i < uv.Length; i++)
|
|
{
|
|
uv[i].x *= _uvScale.x;
|
|
uv[i].y *= _uvScale.y;
|
|
uv[i] += _uvOffset;
|
|
uv[i] = Quaternion.AngleAxis(uvRotation, Vector3.forward) * uv[i];
|
|
}
|
|
}
|
|
|
|
void GroupVertices()
|
|
{
|
|
vertexGroups = new List<VertexGroup>();
|
|
|
|
for (int i = 0; i < vertices.Length; i++)
|
|
{
|
|
float value = vertices[i].z;
|
|
double percent = DMath.Clamp01(DMath.InverseLerp(bounds.min.z, bounds.max.z, value));
|
|
int index = FindInsertIndex(vertices[i], value);
|
|
if (index >= vertexGroups.Count) vertexGroups.Add(new VertexGroup(value, percent, new int[] { i }));
|
|
else
|
|
{
|
|
float valueDelta = Mathf.Abs(vertexGroups[index].value - value);
|
|
if (valueDelta < vertexGroupingMargin || Mathf.Approximately(valueDelta, vertexGroupingMargin)) vertexGroups[index].AddId(i);
|
|
else if (vertexGroups[index].value < value) vertexGroups.Insert(index, new VertexGroup(value, percent, new int[] { i }));
|
|
else
|
|
{
|
|
if (index < vertexGroups.Count - 1) vertexGroups.Insert(index + 1, new VertexGroup(value, percent, new int[] { i }));
|
|
else vertexGroups.Add(new VertexGroup(value, percent, new int[] { i }));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
int FindInsertIndex(Vector3 pos, float value)
|
|
{
|
|
int lower = 0;
|
|
int upper = vertexGroups.Count - 1;
|
|
|
|
while (lower <= upper)
|
|
{
|
|
int middle = lower + (upper - lower) / 2;
|
|
if (vertexGroups[middle].value == value) return middle;
|
|
else if (vertexGroups[middle].value < value) upper = middle - 1;
|
|
else lower = middle + 1;
|
|
}
|
|
return lower;
|
|
}
|
|
|
|
void CalculateTangents()
|
|
{
|
|
if (vertices.Length == 0)
|
|
{
|
|
tangents = new Vector4[0];
|
|
return;
|
|
}
|
|
tangents = new Vector4[vertices.Length];
|
|
Vector3[] tan1 = new Vector3[vertices.Length];
|
|
Vector3[] tan2 = new Vector3[vertices.Length];
|
|
for (int i = 0; i < subMeshes.Count; i++)
|
|
{
|
|
for (int j = 0; j < subMeshes[i].triangles.Length; j += 3)
|
|
{
|
|
int i1 = subMeshes[i].triangles[j];
|
|
int i2 = subMeshes[i].triangles[j + 1];
|
|
int i3 = subMeshes[i].triangles[j + 2];
|
|
float x1 = vertices[i2].x - vertices[i1].x;
|
|
float x2 = vertices[i3].x - vertices[i1].x;
|
|
float y1 = vertices[i2].y - vertices[i1].y;
|
|
float y2 = vertices[i3].y - vertices[i1].y;
|
|
float z1 = vertices[i2].z - vertices[i1].z;
|
|
float z2 = vertices[i3].z - vertices[i1].z;
|
|
float s1 = uv[i2].x - uv[i1].x;
|
|
float s2 = uv[i3].x - uv[i1].x;
|
|
float t1 = uv[i2].y - uv[i1].y;
|
|
float t2 = uv[i3].y - uv[i1].y;
|
|
float div = s1 * t2 - s2 * t1;
|
|
float r = div == 0f ? 0f : 1f / div;
|
|
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 (int i = 0; i < vertices.Length; i++)
|
|
{
|
|
Vector3 n = normals[i];
|
|
Vector3 t = tan1[i];
|
|
Vector3.OrthoNormalize(ref n, ref t);
|
|
tangents[i].x = t.x;
|
|
tangents[i].y = t.y;
|
|
tangents[i].z = t.z;
|
|
tangents[i].w = (Vector3.Dot(Vector3.Cross(n, t), tan2[i]) < 0.0f) ? -1.0f : 1.0f;
|
|
}
|
|
}
|
|
|
|
[System.Serializable]
|
|
public class Submesh
|
|
{
|
|
public int[] triangles = new int[0];
|
|
|
|
public Submesh()
|
|
{
|
|
|
|
}
|
|
|
|
public Submesh(int[] input)
|
|
{
|
|
triangles = new int[input.Length];
|
|
input.CopyTo(triangles, 0);
|
|
}
|
|
}
|
|
|
|
[System.Serializable]
|
|
public class VertexGroup
|
|
{
|
|
public float value = 0f;
|
|
public double percent = 0.0;
|
|
public int[] ids;
|
|
|
|
public VertexGroup(float val, double perc, int[] vertIds)
|
|
{
|
|
percent = perc;
|
|
value = val;
|
|
ids = vertIds;
|
|
}
|
|
|
|
public void AddId(int id)
|
|
{
|
|
int[] newIds = new int[ids.Length + 1];
|
|
ids.CopyTo(newIds, 0);
|
|
newIds[newIds.Length - 1] = id;
|
|
ids = newIds;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|