445 lines
14 KiB
C#
445 lines
14 KiB
C#
using System;
|
|
using System.Buffers;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Threading.Tasks;
|
|
using Unity.Mathematics;
|
|
using UnityEditor;
|
|
using UnityEngine;
|
|
using UnityEngine.Rendering;
|
|
using Random = Unity.Mathematics.Random;
|
|
|
|
namespace Misaki.ArtTool
|
|
{
|
|
[ExecuteInEditMode]
|
|
public class Cloner : MonoBehaviour
|
|
{
|
|
public List<InputObjectData> inputObjects = new();
|
|
|
|
public DistributionMode distributionMode = DistributionMode.Grid;
|
|
public bool isRandomDistribution = false;
|
|
public uint seed = 123456;
|
|
public bool autoGenerate;
|
|
public bool isRenderInstancing;
|
|
|
|
public SplineDistributionSetting splineDistributionSetting = new();
|
|
public LinearDistributionSetting linearDistributionSetting = new();
|
|
public GridDistributionSetting gridDistributionSetting = new();
|
|
|
|
public List<EffectorData> effectors;
|
|
|
|
public DebugMode debugMode;
|
|
|
|
private static readonly ArrayPool<PointData> _pointPool = ArrayPool<PointData>.Shared;
|
|
|
|
private int _pointSize;
|
|
private PointData[] _points;
|
|
private List<GameObject> _instantiateObjects = new();
|
|
private readonly Dictionary<int, List<Matrix4x4>> _objectGroup = new();
|
|
|
|
private bool _isPointsDirty = false;
|
|
|
|
private const float GIZMOS_LINE_BASE_LENGTH = 0.5f;
|
|
|
|
private void OnEnable()
|
|
{
|
|
foreach (var item in effectors)
|
|
{
|
|
if (item.effector == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
item.effector.propertyChanged += OnPropertyChanged;
|
|
}
|
|
|
|
if (transform.childCount > 0 && _instantiateObjects.Count == 0)
|
|
{
|
|
for (var i = 0; i < transform.childCount; i++)
|
|
{
|
|
_instantiateObjects.Add(transform.GetChild(i).gameObject);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void OnDisable()
|
|
{
|
|
foreach (var item in effectors)
|
|
{
|
|
if (item.effector == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
item.effector.propertyChanged -= OnPropertyChanged;
|
|
}
|
|
}
|
|
|
|
//private void OnDestroy()
|
|
//{
|
|
// Clear();
|
|
//}
|
|
|
|
public void OnPropertyChanged(object sender, EventArgs e)
|
|
{
|
|
if (sender is not EffectorBase && sender is not FieldBase)
|
|
{
|
|
return;
|
|
}
|
|
|
|
_isPointsDirty = true;
|
|
}
|
|
|
|
private void Update()
|
|
{
|
|
if (_isPointsDirty && autoGenerate)
|
|
{
|
|
GeneratePoints();
|
|
InstantiateGameObjectOnPoints();
|
|
}
|
|
|
|
if (_objectGroup == null || _objectGroup.Count <= 0 || !isRenderInstancing)
|
|
{
|
|
return;
|
|
}
|
|
|
|
foreach (var item in _objectGroup)
|
|
{
|
|
if (inputObjects.Count <= item.Key || inputObjects[item.Key] == null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var objectData = inputObjects[item.Key];
|
|
var mat = objectData.Material;
|
|
|
|
var renderParams = new RenderParams(mat)
|
|
{
|
|
shadowCastingMode = ShadowCastingMode.On
|
|
};
|
|
Graphics.RenderMeshInstanced(renderParams, objectData.Mesh, 0, item.Value, _pointSize);
|
|
}
|
|
}
|
|
|
|
public void GeneratePoints()
|
|
{
|
|
Clear();
|
|
|
|
switch (distributionMode)
|
|
{
|
|
case DistributionMode.Object:
|
|
break;
|
|
case DistributionMode.Spline:
|
|
_pointSize = splineDistributionSetting.DistributionCount;
|
|
break;
|
|
case DistributionMode.Linear:
|
|
_pointSize = (int)linearDistributionSetting.count;
|
|
break;
|
|
case DistributionMode.Grid:
|
|
_pointSize = gridDistributionSetting.DistributionCount;
|
|
break;
|
|
case DistributionMode.Radial:
|
|
break;
|
|
case DistributionMode.Honeycomb:
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (_pointSize == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (_points == null || _pointSize > _points.Length)
|
|
{
|
|
_points = _pointPool.Rent(_pointSize);
|
|
}
|
|
|
|
foreach (var effectorData in effectors)
|
|
{
|
|
effectorData.effector.Initialize();
|
|
}
|
|
|
|
var splineLength = 0.0f;
|
|
var splineMatrix = float4x4.identity;
|
|
if (distributionMode == DistributionMode.Spline && splineDistributionSetting.spline != null)
|
|
{
|
|
splineLength = splineDistributionSetting.spline.CalculateLength();
|
|
splineMatrix = splineDistributionSetting.spline.transform.localToWorldMatrix;
|
|
}
|
|
|
|
var worldMatrix = transform.localToWorldMatrix;
|
|
Parallel.For(0, _pointSize, i =>
|
|
{
|
|
var pointMatrix = float4x4.identity;
|
|
var isValid = true;
|
|
switch (distributionMode)
|
|
{
|
|
case DistributionMode.Object:
|
|
break;
|
|
case DistributionMode.Spline:
|
|
Distribution.SplineDistribution(i, _pointSize, splineLength, splineMatrix, splineDistributionSetting, out pointMatrix, out isValid);
|
|
break;
|
|
case DistributionMode.Linear:
|
|
Distribution.LinearDistribution(i, linearDistributionSetting, out pointMatrix, out isValid);
|
|
break;
|
|
case DistributionMode.Grid:
|
|
Distribution.GridDistribution(i, gridDistributionSetting, out pointMatrix, out isValid);
|
|
break;
|
|
case DistributionMode.Radial:
|
|
break;
|
|
case DistributionMode.Honeycomb:
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
pointMatrix = math.mul(worldMatrix, pointMatrix);
|
|
|
|
_points[i].matrix = pointMatrix;
|
|
_points[i].isValid = isValid;
|
|
});
|
|
|
|
Parallel.For(0, _pointSize, i =>
|
|
{
|
|
for (var e = 0; e < effectors.Count; e++)
|
|
{
|
|
if (!effectors[e].enable)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (effectors[e].effector == null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
effectors[e].effector.Operate(i, worldMatrix, _points, ref _points[i].matrix, ref _points[i].isValid);
|
|
}
|
|
|
|
if (math.all(_points[i].matrix.GetScale() == float3.zero))
|
|
{
|
|
_points[i].isValid = false;
|
|
}
|
|
});
|
|
|
|
_isPointsDirty = false;
|
|
}
|
|
|
|
public void InstantiateGameObjectOnPoints()
|
|
{
|
|
if (_points == null && _pointSize <= 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (_instantiateObjects.Count > 0)
|
|
{
|
|
for (var i = 0; i < _instantiateObjects.Count; i++)
|
|
{
|
|
if (_instantiateObjects[i] == null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
#if UNITY_EDITOR
|
|
DestroyImmediate(_instantiateObjects[i]);
|
|
#else
|
|
Destroy(child.gameObject);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
if (isRandomDistribution)
|
|
{
|
|
// Use Fisher-Yates Shuffle algorithm to swap value
|
|
var random = new Random(seed);
|
|
for (var i = _pointSize - 1; i > 0; i--)
|
|
{
|
|
var k = random.NextInt(0, i + 1);
|
|
(_points[i], _points[k]) = (_points[k], _points[i]);
|
|
}
|
|
}
|
|
|
|
foreach (var item in _objectGroup)
|
|
{
|
|
item.Value.Clear();
|
|
}
|
|
|
|
var currentIndex = 0;
|
|
var objectIndex = 0;
|
|
while (currentIndex < _pointSize)
|
|
{
|
|
if (!_points[currentIndex].isValid)
|
|
{
|
|
currentIndex++;
|
|
continue;
|
|
}
|
|
|
|
objectIndex %= inputObjects.Count;
|
|
|
|
if (inputObjects[objectIndex].gameObject == null)
|
|
{
|
|
currentIndex++;
|
|
continue;
|
|
}
|
|
|
|
if (!_objectGroup.ContainsKey(objectIndex))
|
|
{
|
|
_objectGroup[objectIndex] = new();
|
|
}
|
|
|
|
for (var f = 0; f < inputObjects[objectIndex].frequency; f++)
|
|
{
|
|
_objectGroup[objectIndex].Add(_points[currentIndex].matrix);
|
|
currentIndex++;
|
|
|
|
if (currentIndex >= _pointSize)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
objectIndex++;
|
|
}
|
|
|
|
for (var i = 0; i < _objectGroup.Count; i++)
|
|
{
|
|
var group = _objectGroup.ElementAt(i);
|
|
if (group.Value.Count <= 0)
|
|
{
|
|
_objectGroup.Remove(group.Key);
|
|
}
|
|
}
|
|
|
|
if (!isRenderInstancing)
|
|
{
|
|
foreach (var item in _objectGroup)
|
|
{
|
|
Span<Vector3> positionsSpan = stackalloc Vector3[item.Value.Count];
|
|
Span<Quaternion> rotationsSpan = stackalloc Quaternion[item.Value.Count];
|
|
var scaleArray = new Vector3[item.Value.Count];
|
|
|
|
MatrixHelper.DecomposeMatrixListAsSpan(item.Value,
|
|
positionsSpan, rotationsSpan, scaleArray);
|
|
|
|
var iop = InstantiateAsync(
|
|
inputObjects[item.Key].gameObject, item.Value.Count,
|
|
transform, positionsSpan, rotationsSpan);
|
|
|
|
iop.completed += (op) =>
|
|
{
|
|
var instantiatedObjects = iop.GetAwaiter().GetResult();
|
|
for (var i = 0; i < instantiatedObjects.Length; i++)
|
|
{
|
|
var instantiatedObject = instantiatedObjects[i];
|
|
instantiatedObject.transform.localScale = scaleArray[i];
|
|
_instantiateObjects.Add(instantiatedObject);
|
|
}
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
public void Clear(bool isClearPoints = true, bool isClearObjects = true)
|
|
{
|
|
if (isClearPoints)
|
|
{
|
|
_pointPool.Return(_points, true);
|
|
}
|
|
|
|
if (isClearObjects)
|
|
{
|
|
_objectGroup.Clear();
|
|
|
|
if (_instantiateObjects.Count <= 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
for (var i = 0; i < _instantiateObjects.Count; i++)
|
|
{
|
|
if (_instantiateObjects[i] == null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
#if UNITY_EDITOR
|
|
DestroyImmediate(_instantiateObjects[i]);
|
|
#else
|
|
Destroy(child.gameObject);
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
private void OnDrawGizmosSelected()
|
|
{
|
|
if (_pointSize <= 0 || _points.Length < _pointSize)
|
|
{
|
|
return;
|
|
}
|
|
|
|
for (var i = 0; i < _pointSize; i++)
|
|
{
|
|
var point = _points[i];
|
|
|
|
switch (debugMode)
|
|
{
|
|
case DebugMode.None:
|
|
break;
|
|
case DebugMode.Transform:
|
|
if (!point.isValid)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var scale = point.matrix.GetScale();
|
|
Gizmos.matrix = point.matrix;
|
|
|
|
// Draw the x-axis in red
|
|
Gizmos.color = Color.red;
|
|
Gizmos.DrawLine(Vector3.zero, Vector3.right * scale * GIZMOS_LINE_BASE_LENGTH);
|
|
|
|
// Draw the y-axis in green
|
|
Gizmos.color = Color.green;
|
|
Gizmos.DrawLine(Vector3.zero, Vector3.up * scale * GIZMOS_LINE_BASE_LENGTH);
|
|
|
|
// Draw the z-axis in blue
|
|
Gizmos.color = Color.blue;
|
|
Gizmos.DrawLine(Vector3.zero, Vector3.forward * scale * GIZMOS_LINE_BASE_LENGTH);
|
|
break;
|
|
|
|
case DebugMode.Index:
|
|
if (!point.isValid)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
Handles.matrix = point.matrix;
|
|
Handles.Label(Vector3.zero, i.ToString());
|
|
break;
|
|
|
|
case DebugMode.Validity:
|
|
|
|
if (point.isValid)
|
|
{
|
|
Gizmos.color = Color.green;
|
|
}
|
|
else
|
|
{
|
|
Gizmos.color = Color.red;
|
|
}
|
|
|
|
Gizmos.DrawSphere(point.matrix.c3.xyz, GIZMOS_LINE_BASE_LENGTH / 2.0f);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
} |