Upload project files

This commit is contained in:
Misaki
2024-09-16 00:08:10 +09:00
commit 0a4745662a
218 changed files with 13387 additions and 0 deletions

445
Runtime/Cloner/Cloner.cs Normal file
View File

@@ -0,0 +1,445 @@
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;
}
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 35466656a0ebc42449023a440cfad411
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 858ef2b4b0d5f7e4fb055cc7102b5292, type: 3}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: de1401f6b38da8643abf01231a6395fb
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 107650ad99360724989708b1799990a5
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,84 @@
using System;
using System.Collections.Generic;
using Unity.Mathematics;
using UnityEngine;
namespace Misaki.ArtTool
{
public abstract class EffectorBase : MonoBehaviour
{
public float strength = 1.0f;
public List<FieldData> fieldDataList = new();
public EventHandler propertyChanged;
protected float4x4 effectorMatrix;
private void OnEnable()
{
foreach (var item in fieldDataList)
{
if (item == null)
{
return;
}
item.field.propertyChanged += OnPropertyChanged;
}
}
private void OnDisable()
{
foreach (var item in fieldDataList)
{
if (item == null)
{
return;
}
item.field.propertyChanged -= OnPropertyChanged;
}
}
private void OnPropertyChanged(object sender, EventArgs e)
{
propertyChanged?.Invoke(sender, e);
}
public virtual void Initialize()
{
for (var i = 0; i < fieldDataList.Count; i++)
{
fieldDataList[i].field.Initialize();
}
effectorMatrix = transform.localToWorldMatrix;
}
public virtual void Operate(int index, float4x4 nodeWorldMatrix, ReadOnlySpan<PointData> points, ref float4x4 pointWorldMatrix, ref bool isValid)
{
}
protected float CalculateFieldsWeight(float3 worldPosition)
{
var weight = 1.0f;
var fieldCount = fieldDataList.Count;
for (var i = 0; i < fieldCount; i++)
{
var fieldData = fieldDataList[i];
if (!fieldData.enable || fieldData.opacity <= 0.0f)
{
continue;
}
weight = math.lerp(weight, fieldData.field.Operate(worldPosition), fieldData.opacity);
}
weight *= strength;
return weight;
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: e7ccd97731dd96b4fa2c848321321710

View File

@@ -0,0 +1,6 @@
namespace Misaki.ArtTool
{
public class IterateEffectorBase : EffectorBase
{
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 5144695703dc3434c93bc038017a3ac3

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: b8ea1a2aeb0489a4babf1d34f2ff07c2
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,42 @@
using System;
using Unity.Mathematics;
using UnityEngine;
namespace Misaki.ArtTool
{
public abstract class FieldBase : MonoBehaviour
{
public RemappingSetting remappingSetting = new();
public EventHandler propertyChanged;
public virtual void Initialize()
{
}
public abstract float Operate(float3 position);
protected float Remapping(float weight)
{
if (!remappingSetting.enable)
{
return weight;
}
weight = math.saturate(weight / (1.0f - remappingSetting.innerOffset));
weight = math.lerp(remappingSetting.min, remappingSetting.max, weight);
weight = remappingSetting.invert ? 1.0f - weight : weight;
weight *= remappingSetting.strength;
return weight;
}
private void Update()
{
if (transform.hasChanged)
{
propertyChanged.Invoke(this, null);
transform.hasChanged = false;
}
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: c07d37f0b7524644aab721afc849c508

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 65fe35f62bdb64d4f8b4a2e4e8ec0a39
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,55 @@
using Unity.Mathematics;
namespace Misaki.ArtTool
{
public static partial class Distribution
{
public static void GridDistribution(int index, GridDistributionSetting setting, out float4x4 localMatrix, out bool isValid)
{
var random = Random.CreateFromIndex((uint)index);
var localPosition = GetCubePosition(index, setting.count) * setting.spacing;
switch (setting.shape)
{
case GridShape.Cube:
isValid = true;
break;
case GridShape.Sphere:
var isInsideSphere = ShapeHelper.IsInsideSphere(localPosition, 0.0f, setting.count * setting.spacing);
isValid = isInsideSphere;
break;
case GridShape.Cylinder:
var isInsideCylinder = ShapeHelper.IsInsideCylinder(localPosition, 0.0f, setting.count * setting.spacing);
isValid = isInsideCylinder;
break;
default:
isValid = false;
break;
}
if (random.NextFloat() > setting.fill && isValid == true)
{
isValid = false;
}
localMatrix = float4x4.TRS(localPosition, quaternion.identity, new float3(1.0f));
}
private static float3 GetCubePosition(int index, int3 size)
{
float3 localPosition;
var yIndex = index / (size.x * size.z);
var remain = index % (size.x * size.z);
var zIndex = remain / size.x;
var xIndex = remain % size.x;
localPosition = new float3(xIndex, yIndex, zIndex);
localPosition -= (float3)(size - 1) * 0.5f;
return localPosition;
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 776811becd42a27408b353bb0cc8e54e

View File

@@ -0,0 +1,21 @@
using Unity.Mathematics;
namespace Misaki.ArtTool
{
public static partial class Distribution
{
public static void LinearDistribution(int index, LinearDistributionSetting setting, out float4x4 localMatrix, out bool isValid)
{
var pointIndex = (uint)index + setting.indexOffset;
var localPosition = pointIndex * setting.positionSpacing;
var localEulerRotation = pointIndex * setting.rotationSpacing;
var localScale = 1.0f - (pointIndex * (1.0f - setting.scaleSpacing));
localPosition = math.mul(float3x3.EulerXYZ(math.radians(setting.stepRotation * pointIndex)), localPosition);
var localRotation = math.mul(quaternion.EulerXYZ(math.radians(localEulerRotation)), quaternion.EulerXYZ(math.radians(setting.stepRotation * pointIndex)));
localMatrix = float4x4.TRS(localPosition, localRotation, localScale);
isValid = true;
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: b6bf561350efd524a9a65100521eb388

View File

@@ -0,0 +1,47 @@
using Unity.Mathematics;
using UnityEngine.Splines;
namespace Misaki.ArtTool
{
public static partial class Distribution
{
public static void SplineDistribution(int index, int pointSize, float splineLength, float4x4 splineWorldMatrix, SplineDistributionSetting setting, out float4x4 localMatrix, out bool isValid)
{
var pointIndex = index + setting.indexOffset;
if (pointIndex > pointSize)
{
localMatrix = float4x4.zero;
isValid = false;
return;
}
var spline = setting.spline;
float t;
if (setting.isSpacingMode)
{
t = (pointIndex * setting.spacing) / splineLength;
}
else
{
t = pointIndex / (float)(pointSize - 1);
}
if (SplineUtility.Evaluate(spline.Spline, t, out var position, out var normal, out var upVector))
{
var localRotation = quaternion.LookRotationSafe(normal, upVector);
localMatrix = math.mul(splineWorldMatrix, float4x4.TRS(position, localRotation, new float3(1.0f)));
isValid = true;
}
else
{
localMatrix = float4x4.zero;
isValid = false;
}
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 843f013f896d37543a4d48166e71b6b6

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: f2781ff4287895d40835ad3a238647d8
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,98 @@
using System;
using Unity.Mathematics;
using UnityEngine;
namespace Misaki.ArtTool
{
[ExecuteInEditMode]
public class PlainEffector : EffectorBase
{
public TransformSpace transformSpace;
public bool isEnablePosition;
public float3 position;
public bool isEnableRotation;
public float3 rotation;
public bool isEnableScale;
public bool isAbsoluteScale;
public bool isUniformScale;
public float3 scale;
public float uniformScale;
public override void Operate(int index, float4x4 nodeWorldMatrix, ReadOnlySpan<PointData> points, ref float4x4 pointWorldMatrix, ref bool isValid)
{
if (!isEnablePosition && !isEnableRotation && !isEnableScale)
{
return;
}
MatrixHelper.DecomposeMatrix(pointWorldMatrix, out var position, out var rotation, out var scale);
var weight = CalculateFieldsWeight(pointWorldMatrix.c3.xyz);
if (weight == 0)
{
return;
}
if (isEnablePosition)
{
var newPosition = position;
switch (transformSpace)
{
case TransformSpace.Node:
newPosition += this.position;
break;
case TransformSpace.Effector:
newPosition += math.mul(effectorMatrix, new float4(this.position, 0)).xyz;
break;
case TransformSpace.Object:
newPosition += math.mul(pointWorldMatrix, new float4(this.position, 0)).xyz;
break;
default:
break;
}
position = math.lerp(position, newPosition, weight);
}
if (isEnableRotation)
{
var newRotation = rotation;
switch (transformSpace)
{
case TransformSpace.Node:
newRotation = math.mul(rotation, quaternion.EulerXYZ(math.radians(this.rotation)));
break;
case TransformSpace.Effector:
newRotation = math.mul(rotation,
quaternion.EulerXYZ(math.mul(effectorMatrix, new float4(math.radians(this.rotation), 0)).xyz));
break;
case TransformSpace.Object:
newRotation = math.mul(rotation,
quaternion.EulerXYZ(math.mul(pointWorldMatrix, new float4(math.radians(this.rotation), 0)).xyz));
break;
default:
break;
}
rotation = Quaternion.Lerp(rotation, newRotation, weight);
}
if (isEnableScale)
{
var newScale = scale;
if (isUniformScale)
{
newScale = isAbsoluteScale ? uniformScale : scale - uniformScale;
}
else
{
newScale = isAbsoluteScale ? this.scale : scale - this.scale;
}
scale = math.lerp(scale, newScale, weight);
}
pointWorldMatrix = float4x4.TRS(position, rotation, scale);
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 6601a6a85943a3444a503fdeb1cba30e

View File

@@ -0,0 +1,43 @@
using System;
using Unity.Mathematics;
namespace Misaki.ArtTool
{
public class PushApartEffector : EffectorBase
{
public float radius = 1.0f;
public uint iteration = 10;
// TODO: Average the push direction and distance of each point for more consistence result
public override void Operate(int index, float4x4 nodeWorldMatrix, ReadOnlySpan<PointData> points, ref float4x4 pointWorldMatrix, ref bool isValid)
{
var currentPoint = points[index];
var weight = CalculateFieldsWeight(pointWorldMatrix.c3.xyz);
if (weight == 0)
{
return;
}
for (var i = 0; i < iteration; i++)
{
for (var p = 0; p < points.Length; p++)
{
var targetPoint = points[p];
if (ReferenceEquals(currentPoint, targetPoint))
{
continue;
}
var distance = math.distance(currentPoint.matrix.c3.xyz, targetPoint.matrix.c3.xyz);
if (distance < radius)
{
var direction = math.normalizesafe(currentPoint.matrix.c3.xyz - targetPoint.matrix.c3.xyz);
pointWorldMatrix.c3.xyz += distance * weight * direction;
}
}
}
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: c74fdb9922181d940a34522dd3878624

View File

@@ -0,0 +1,123 @@
using System;
using Unity.Mathematics;
using UnityEngine;
using Random = Unity.Mathematics.Random;
namespace Misaki.ArtTool
{
public class RandomEffector : EffectorBase
{
public float2 minMax = new(-1.0f, 1.0f);
public bool synchronized;
public uint seed = 123456;
public TransformSpace transformSpace;
public bool isEnablePosition;
public float3 positionMinMax;
public bool isEnableRotation;
public float3 rotationMinMax;
public bool isEnableScale;
public bool isAbsoluteScale;
public bool isUniformScale;
public float3 scaleMinMax;
public float uniformScaleMinMax;
public override void Operate(int index, float4x4 nodeWorldMatrix, ReadOnlySpan<PointData> points, ref float4x4 pointWorldMatrix, ref bool isValid)
{
if (!isEnablePosition && !isEnableRotation && !isEnableScale)
{
return;
}
Random random;
if (synchronized)
{
random = Random.CreateFromIndex(seed);
}
else
{
if (index > uint.MaxValue - seed)
{
random = Random.CreateFromIndex(seed - (uint)index);
}
else
{
random = Random.CreateFromIndex(seed + (uint)index);
}
}
MatrixHelper.DecomposeMatrix(pointWorldMatrix, out var position, out var rotation, out var scale);
var weight = CalculateFieldsWeight(pointWorldMatrix.c3.xyz);
if (weight == 0)
{
return;
}
if (isEnablePosition)
{
var newPosition = random.NextFloat3(positionMinMax * minMax.x, positionMinMax * minMax.y);
switch (transformSpace)
{
case TransformSpace.Effector:
newPosition = math.mul(effectorMatrix, new float4(newPosition, 0.0f)).xyz;
break;
case TransformSpace.Object:
newPosition = math.mul(pointWorldMatrix, new float4(newPosition, 0.0f)).xyz;
break;
default:
break;
}
position = math.lerp(position, position + newPosition, weight);
}
if (isEnableRotation)
{
var angle = random.NextFloat3(rotationMinMax * minMax.x, rotationMinMax * minMax.y);
switch (transformSpace)
{
case TransformSpace.Effector:
angle = math.mul(effectorMatrix, new float4(angle, 0.0f)).xyz;
break;
case TransformSpace.Object:
angle = math.mul(pointWorldMatrix, new float4(angle, 0.0f)).xyz;
break;
default:
break;
}
var newRotation = quaternion.Euler(math.radians(angle));
rotation = Quaternion.Lerp(rotation, math.mul(rotation, newRotation), weight);
}
if (isEnableScale)
{
float3 newScale;
if (isUniformScale)
{
var minScale = isAbsoluteScale ? uniformScaleMinMax : 1.0f + (uniformScaleMinMax - 1.0f) * minMax.x;
var maxScale = isAbsoluteScale ? 0.0f : 1.0f + (uniformScaleMinMax - 1.0f) * minMax.y;
newScale = random.NextFloat(minScale, maxScale);
}
else
{
var minScale = isAbsoluteScale ? scaleMinMax : 1.0f + ((scaleMinMax - 1.0f) * minMax.x);
var maxScale = isAbsoluteScale ? 0.0f : 1.0f + ((scaleMinMax - 1.0f) * minMax.y);
newScale = random.NextFloat3(minScale, maxScale);
}
scale = math.lerp(scale, scale + newScale, weight);
}
pointWorldMatrix = float4x4.TRS(position, rotation, scale);
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: dd224efbdb8a5bb41bdc7a5db7a08533

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: ce4ee28bfe031d2438248933fcffc908
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,14 @@
namespace Misaki.ArtTool
{
public enum BlendingMode
{
Normal,
Min,
Subtract,
Multiply,
Overlay,
Max,
Add,
Screen
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: b6337269e529baa4a9554b85d8eb01b9

View File

@@ -0,0 +1,8 @@
namespace Misaki.ArtTool
{
public enum CloneMode
{
Iterate,
Random
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 12f9d13e6e10c8442abb4950e246e64c

View File

@@ -0,0 +1,10 @@
namespace Misaki.ArtTool
{
public enum DebugMode
{
None,
Transform,
Index,
Validity
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: b1cac92d7e0ef2b4ba204b72fd657c78

View File

@@ -0,0 +1,12 @@
namespace Misaki.ArtTool
{
public enum DistributionMode
{
Object,
Spline,
Linear,
Grid,
Radial,
Honeycomb
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 5a17ee44970fcc3479a0cdd04da3a8ad

View File

@@ -0,0 +1,9 @@
namespace Misaki.ArtTool
{
public enum GridShape
{
Cube,
Sphere,
Cylinder
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 860e24fd1e850654cac3e4bbe5bbde1d

View File

@@ -0,0 +1,8 @@
namespace Misaki.ArtTool
{
public enum TransformMode
{
Relative,
Absolute
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 9ed88eac104f9fb45b885db70217db5c

View File

@@ -0,0 +1,9 @@
namespace Misaki.ArtTool
{
public enum TransformSpace
{
Node,
Effector,
Object
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: fec82e7e110164249abb0f36870043a1

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 31693103866ca2745810c1cc323eb764
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,49 @@
using Unity.Mathematics;
using UnityEngine;
namespace Misaki.ArtTool
{
[ExecuteInEditMode]
public class LinearField : FieldBase
{
public float length = 1.0f;
private float3 fieldForward;
private float3 fieldPosition;
public override void Initialize()
{
fieldForward = transform.forward;
fieldPosition = transform.position;
}
public override float Operate(float3 position)
{
var plane = new Unity.Mathematics.Geometry.Plane(fieldForward, fieldPosition);
var distance = plane.SignedDistanceToPoint(position) / length;
var weight = math.saturate(distance / 2.0f + 0.5f);
weight = Remapping(weight);
return weight;
}
private void OnDrawGizmos()
{
Gizmos.matrix = transform.localToWorldMatrix;
var end = Vector3.forward * length;
var start = Vector3.forward * -length;
Gizmos.DrawLine(start, end);
var right = Quaternion.LookRotation(Vector3.forward) * Quaternion.Euler(0.0f, 180.0f + 30.0f, 0.0f) * Vector3.forward;
var left = Quaternion.LookRotation(Vector3.forward) * Quaternion.Euler(0.0f, 180.0f - 30.0f, 0.0f) * Vector3.forward;
Gizmos.DrawLine(end, end + right * 0.5f);
Gizmos.DrawLine(end, end + left * 0.5f);
Gizmos.DrawWireCube(end, Vector3.one);
Gizmos.DrawWireCube(start, Vector3.one);
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 73f4542d4baa2f94c87fea0390d5c524

View File

@@ -0,0 +1,31 @@
using Unity.Mathematics;
using UnityEngine;
namespace Misaki.ArtTool
{
public class SphereField : FieldBase
{
public float radius = 1.0f;
private float3 fieldPosition;
public override void Initialize()
{
fieldPosition = transform.position;
}
public override float Operate(float3 position)
{
var weight = ShapeHelper.Linear01DistanceToSphereCenter(position, fieldPosition, radius);
weight = Remapping(weight);
return weight;
}
private void OnDrawGizmos()
{
Gizmos.color = Color.cyan;
Gizmos.DrawWireSphere(fieldPosition, radius);
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: bca26ff5ff452044c873ff07452adb51

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: c0edc45ee28a6b240b4a458ef29e5b8b
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 0fb3963eff3a815418c93ea649c97569
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,30 @@
using UnityEngine.UIElements;
namespace Misaki.ArtTool
{
public struct BoolToDisplayConverter
{
public static StyleEnum<DisplayStyle> ConvertTo(bool value)
{
return value ? DisplayStyle.Flex : DisplayStyle.None;
}
public static bool ConvertBack(StyleEnum<DisplayStyle> value)
{
return value == DisplayStyle.Flex;
}
}
public struct InverseBoolToDisplayConverter
{
public static StyleEnum<DisplayStyle> ConvertTo(bool value)
{
return value ? DisplayStyle.None : DisplayStyle.Flex;
}
public static bool ConvertBack(StyleEnum<DisplayStyle> value)
{
return value == DisplayStyle.None;
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: b81c44f05fc751c48826ea9ce03b7c01

View File

@@ -0,0 +1,45 @@
using Unity.Mathematics;
using UnityEditor;
using UnityEngine;
using UnityEngine.UIElements;
namespace Misaki.ArtTool
{
internal class ConverterInitializer
{
[InitializeOnLoadMethod]
public static void Initialize()
{
var boolToDisplayGroup = new ConverterGroup("BoolToDisplayConvertor");
boolToDisplayGroup.AddConverter((ref bool v) => BoolToDisplayConverter.ConvertTo(v));
boolToDisplayGroup.AddConverter((ref StyleEnum<DisplayStyle> v) => BoolToDisplayConverter.ConvertBack(v));
ConverterGroups.RegisterConverterGroup(boolToDisplayGroup);
var inverseBoolToDisplayGroup = new ConverterGroup("InverseBoolToDisplayConverter");
inverseBoolToDisplayGroup.AddConverter((ref bool v) => InverseBoolToDisplayConverter.ConvertTo(v));
inverseBoolToDisplayGroup.AddConverter((ref StyleEnum<DisplayStyle> v) => InverseBoolToDisplayConverter.ConvertBack(v));
ConverterGroups.RegisterConverterGroup(inverseBoolToDisplayGroup);
var float2ToVector2Group = new ConverterGroup("Float2ToVector2Converter");
float2ToVector2Group.AddConverter((ref float2 v) => Float2ToVector2Converter.ConvertTo(v));
float2ToVector2Group.AddConverter((ref Vector2 v) => Float2ToVector2Converter.ConvertBack(v));
ConverterGroups.RegisterConverterGroup(float2ToVector2Group);
var float3ToVector3Group = new ConverterGroup("Float3ToVector3Converter");
float3ToVector3Group.AddConverter((ref float3 v) => Float3ToVector3Converter.ConvertTo(v));
float3ToVector3Group.AddConverter((ref Vector3 v) => Float3ToVector3Converter.ConvertBack(v));
ConverterGroups.RegisterConverterGroup(float3ToVector3Group);
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: c562049c7c1eae74cabb57e558e84cd6

View File

@@ -0,0 +1,7 @@
namespace Misaki.ArtTool
{
public struct DistributionModeToDisplayStyleConverter
{
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: cc563d199e512194591ea87c5554c7fd

View File

@@ -0,0 +1,31 @@
using Unity.Mathematics;
using UnityEngine;
namespace Misaki.ArtTool
{
public struct Float2ToVector2Converter
{
public static Vector2 ConvertTo(float2 value)
{
return (Vector2)value;
}
public static float2 ConvertBack(Vector2 value)
{
return (float2)value;
}
}
public struct Float3ToVector3Converter
{
public static Vector3 ConvertTo(float3 value)
{
return (Vector3)value;
}
public static float3 ConvertBack(Vector3 value)
{
return (float3)value;
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 5e4440b512db4634782a08acd72568a3

View File

@@ -0,0 +1,78 @@
using System;
using System.Collections.Generic;
using Unity.Mathematics;
using UnityEngine;
namespace Misaki.ArtTool
{
internal static class MatrixHelper
{
internal static void DecomposeMatrix(float4x4 matrix, out float3 position, out quaternion rotation, out float3 scale)
{
position = matrix.c3.xyz;
scale = new float3(
math.length(matrix.c0.xyz),
math.length(matrix.c1.xyz),
math.length(matrix.c2.xyz)
);
rotation = quaternion.LookRotation(matrix.c2.xyz / scale.z, matrix.c1.xyz / scale.y);
}
internal static void DecomposeMatrixToVector(float4x4 matrix, out Vector3 position, out Quaternion rotation, out Vector3 scale)
{
position = matrix.c3.xyz;
scale = new Vector3(
math.length(matrix.c0.xyz),
math.length(matrix.c1.xyz),
math.length(matrix.c2.xyz)
);
rotation = Quaternion.LookRotation(matrix.c2.xyz / scale.z, matrix.c1.xyz / scale.y);
}
internal static void DecomposeMatrixListAsSpan(in IList<Matrix4x4> matrixList, Span<Vector3> positions, Span<Quaternion> rotations, Span<Vector3> scales)
{
if (matrixList.Count != positions.Length || matrixList.Count != rotations.Length || matrixList.Count != scales.Length)
{
throw new ArgumentException("The length of the spans must match the number of matrices in the list.");
}
for (var i = 0; i < matrixList.Count; i++)
{
DecomposeMatrixToVector(matrixList[i], out positions[i], out rotations[i], out scales[i]);
}
}
internal static void DecomposeMatrixListAsSpan(ReadOnlySpan<float4x4> matrixList, Span<Vector3> positions, Span<Quaternion> rotations, Span<Vector3> scales)
{
if (matrixList.Length != positions.Length || matrixList.Length != rotations.Length || matrixList.Length != scales.Length)
{
throw new ArgumentException("The length of the spans must match the number of matrices in the list.");
}
for (var i = 0; i < matrixList.Length; i++)
{
DecomposeMatrixToVector(matrixList[i], out positions[i], out rotations[i], out scales[i]);
}
}
internal static void DecomposeMatrixListAsSpan(ReadOnlySpan<PointData> matrixList, Span<Vector3> positions, Span<Quaternion> rotations, Span<Vector3> scales)
{
if (matrixList.Length != positions.Length || matrixList.Length != rotations.Length || matrixList.Length != scales.Length)
{
throw new ArgumentException("The length of the spans must match the number of matrices in the list.");
}
for (var i = 0; i < matrixList.Length; i++)
{
DecomposeMatrixToVector(matrixList[i].matrix, out positions[i], out rotations[i], out scales[i]);
}
}
internal static float3 GetScale(this float4x4 matrix)
{
return new float3(math.length(matrix.c0.xyz), math.length(matrix.c1.xyz), math.length(matrix.c2.xyz));
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: d449f80c59d696f4d8cabfff475f3b7b

View File

@@ -0,0 +1,55 @@
using System;
using System.Runtime.CompilerServices;
using Unity.Mathematics;
using UnityEngine;
namespace Misaki.ArtTool
{
internal static class ShapeHelper
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static float Linear01DistanceToSphereCenter(float3 pointPosition, float3 spherePosition, float3 sphereSize)
{
var x = (pointPosition.x - spherePosition.x) / sphereSize.x;
var y = (pointPosition.y - spherePosition.y) / sphereSize.y;
var z = (pointPosition.z - spherePosition.z) / sphereSize.z;
var normalizedDistance = math.sqrt(x * x + y * y + z * z);
return math.saturate(normalizedDistance);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static bool IsInsideSphere(float3 pointPosition, float3 spherePosition, float3 sphereSize)
{
sphereSize /= 2.0f;
var x = (pointPosition.x - spherePosition.x) / sphereSize.x;
var y = (pointPosition.y - spherePosition.y) / sphereSize.y;
var z = (pointPosition.z - spherePosition.z) / sphereSize.z;
return (x * x + y * y + z * z) <= 1.0f;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static bool IsInsideCylinder(float3 pointPosition, float3 cylinderPosition, float3 cylinderSize)
{
cylinderSize /= 2.0f;
var dx = (pointPosition.x - cylinderPosition.x) / cylinderSize.x;
var dz = (pointPosition.z - cylinderPosition.z) / cylinderSize.z;
var distanceSquared = dx * dx + dz * dz;
var withinRadius = distanceSquared <= 1.0f;
var withinHeight = pointPosition.y >= (cylinderPosition.y - cylinderSize.y) && pointPosition.y <= (cylinderPosition.y + cylinderSize.y);
return withinRadius && withinHeight;
}
internal static bool IsInsideMesh(float3 pointPosition, float3 meshPosition, Mesh mesh)
{
throw new NotImplementedException();
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 11f9c68bdb3ebfa479f602ac3a988abe

8
Runtime/Cloner/Jobs.meta Normal file
View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 6e5501834c64c7945bf21962f03f129e
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,78 @@
using Unity.Collections;
using Unity.Jobs;
using Unity.Mathematics;
using UnityEngine;
namespace Misaki.ArtTool
{
[ExecuteInEditMode]
public struct PointsGenerationJob : IJobParallelForBatch
{
public float4x4 worldMatrix;
public DistributionMode distributionMode;
public GridDistributionSetting gridDistributionSetting;
[WriteOnly]
public NativeArray<float4x4> points;
public void Execute(int startIndex, int count)
{
switch (distributionMode)
{
case DistributionMode.Object:
break;
case DistributionMode.Linear:
break;
case DistributionMode.Grid:
GridDistribution(startIndex, count);
break;
case DistributionMode.Radial:
break;
case DistributionMode.Honeycomb:
break;
default:
break;
}
}
private void GridDistribution(int startIndex, int count)
{
var xIndex = 0;
var yIndex = 0;
var zIndex = 0;
switch (gridDistributionSetting.shape)
{
case GridShape.Cube:
for (var i = startIndex; i < startIndex + count; i++)
{
yIndex = i / (gridDistributionSetting.count.x * gridDistributionSetting.count.z);
var remain = i % (gridDistributionSetting.count.x * gridDistributionSetting.count.z);
zIndex = remain / gridDistributionSetting.count.x;
xIndex = remain % gridDistributionSetting.count.x;
var localPosition = new float3(xIndex * gridDistributionSetting.spacing.x, yIndex * gridDistributionSetting.spacing.y, zIndex * gridDistributionSetting.spacing.z);
localPosition.x -= (gridDistributionSetting.count.x - 1) * gridDistributionSetting.spacing.x / 2.0f;
localPosition.y -= (gridDistributionSetting.count.y - 1) * gridDistributionSetting.spacing.y / 2.0f;
localPosition.z -= (gridDistributionSetting.count.z - 1) * gridDistributionSetting.spacing.z / 2.0f;
var localMatrix = float4x4.TRS(localPosition, quaternion.identity, new float3(1.0f));
points[i] = math.mul(worldMatrix, localMatrix);
}
break;
case GridShape.Sphere:
break;
case GridShape.Cylinder:
break;
default:
break;
}
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: a3b40b764e1a7ee4cb60606f02dce34b

View File

@@ -0,0 +1,24 @@
using Unity.Collections;
using Unity.Mathematics;
using UnityEngine.Jobs;
namespace Misaki.ArtTool
{
public struct TransformAccessJob : IJobParallelForTransform
{
public NativeArray<float4x4> points;
public void Execute(int index, TransformAccess transform)
{
if (index > points.Length || !transform.isValid)
{
return;
}
MatrixHelper.DecomposeMatrix(points[index], out var position, out var rotation, out var scale);
transform.SetPositionAndRotation(position, rotation);
transform.localScale = scale;
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 56503ea812e8e9042aa36b3b6af0a9f7

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 2d9a05cd9824f9c4f8cfb39eac01ff1c
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 9634875f0aceb38448a7d6d08aca0386
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,18 @@
using System;
using Unity.Mathematics;
using UnityEngine;
namespace Misaki.ArtTool
{
[Serializable]
public class GridDistributionSetting
{
public int3 count = new(3, 3, 3);
public float3 spacing = new(1.0f, 1.0f, 1.0f);
public GridShape shape;
[Range(0.0f, 1.0f)]
public float fill = 1.0f;
public int DistributionCount => count.x * count.y * count.z;
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 117719617fb15994790913adcb8392ad

View File

@@ -0,0 +1,18 @@
using System;
using Unity.Mathematics;
namespace Misaki.ArtTool
{
[Serializable]
public class LinearDistributionSetting
{
public uint count = 10;
public uint indexOffset = 0;
public float3 positionSpacing = new(0.0f, 1.0f, 0.0f);
public float3 rotationSpacing = float3.zero;
public float3 scaleSpacing = new(1.0f, 1.0f, 1.0f);
public float3 stepRotation = float3.zero;
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: e489bd7add88acc4cb2bf8c30c851653

View File

@@ -0,0 +1,13 @@
using System;
using UnityEngine;
namespace Misaki.ArtTool
{
[Serializable]
public struct ObjectDistributionSetting
{
public MeshFilter meshFilter;
public int count;
public bool alignNormal;
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 818f2d0001f1e8f439e23e34ed6ae898

View File

@@ -0,0 +1,34 @@
using System;
using UnityEngine;
using UnityEngine.Splines;
namespace Misaki.ArtTool
{
[Serializable]
public class SplineDistributionSetting
{
public SplineContainer spline;
public int indexOffset;
public uint count = 10;
public float spacing = 1.0f;
public bool isSpacingMode;
public int DistributionCount
{
get
{
if (isSpacingMode)
{
return Mathf.RoundToInt(spline.CalculateLength() / spacing) + 1;
}
else
{
return (int)count;
}
}
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 7b5cfd6221b6b9a45bba99cefb806ccc

View File

@@ -0,0 +1,11 @@
using System;
namespace Misaki.ArtTool
{
[Serializable]
public class EffectorData
{
public bool enable = true;
public EffectorBase effector;
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 9e8152eaeb656654cbbe7859b44b12bf

View File

@@ -0,0 +1,13 @@
using System;
namespace Misaki.ArtTool
{
[Serializable]
public class FieldData
{
public bool enable = true;
public FieldBase field;
public BlendingMode blending;
public float opacity = 1.0f;
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 73bd368d309049d42be068896e7c1a58

View File

@@ -0,0 +1,41 @@
using System;
using UnityEngine;
namespace Misaki.ArtTool
{
[Serializable]
public class InputObjectData
{
public GameObject gameObject;
public uint frequency = 1;
private Mesh _mesh;
public Mesh Mesh
{
get
{
if (_mesh == null)
{
_mesh = gameObject.GetComponentInChildren<MeshFilter>().sharedMesh;
}
return _mesh;
}
}
private Material _material;
public Material Material
{
get
{
if (_material == null)
{
_material = gameObject.GetComponentInChildren<MeshRenderer>().sharedMaterial;
_material.enableInstancing = true;
}
return _material;
}
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 852b6e461bca6b64e9e2cb2735de1a19

View File

@@ -0,0 +1,31 @@
using System;
using Unity.Collections;
namespace Misaki.ArtTool
{
public struct ObjectIdInfo : IDisposable
{
public NativeArray<int> instanceIdArray;
public NativeArray<int> transformIdArray;
public bool IsCreated
{
get
{
return instanceIdArray.IsCreated && transformIdArray.IsCreated;
}
}
public ObjectIdInfo(int size)
{
instanceIdArray = new(size, Allocator.Persistent);
transformIdArray = new(size, Allocator.Persistent);
}
public void Dispose()
{
instanceIdArray.Dispose();
transformIdArray.Dispose();
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 198d97e9fb629864b910011c81d94375

View File

@@ -0,0 +1,12 @@
using System;
using Unity.Mathematics;
namespace Misaki.ArtTool
{
[Serializable]
public struct PointData
{
public bool isValid;
public float4x4 matrix;
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 94a436dd2ba363746abf1f24efd1d021

View File

@@ -0,0 +1,18 @@
using System;
namespace Misaki.ArtTool
{
[Serializable]
public class RemappingSetting
{
public bool enable = true;
public float strength = 1.0f;
public bool invert = false;
public float innerOffset = 0.0f;
public float min = 0.0f;
public float max = 1.0f;
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 404cd8f4b1af1114aaa5dae86d4a9123