Added object distributuon calculationg

This commit is contained in:
Misaki
2024-09-22 00:11:03 +09:00
parent 77d16fbffa
commit df0194ff9c
22 changed files with 375 additions and 100 deletions

View File

@@ -9,6 +9,7 @@ using Unity.Mathematics;
using UnityEditor;
using UnityEngine;
using UnityEngine.Jobs;
using UnityEngine.Profiling;
using UnityEngine.Rendering;
using UnityEngine.Splines;
using Object = UnityEngine.Object;
@@ -48,7 +49,7 @@ namespace Misaki.ArtTool
private bool _isPointsDirty = false;
private const float GIZMOS_BASE_SIZE = 0.5f;
private const float GIZMOS_BASE_SIZE = 0.25f;
private void OnEnable()
{
@@ -132,6 +133,8 @@ namespace Misaki.ArtTool
public void GeneratePoints()
{
Profiler.BeginSample("GeneratePoints");
Clear();
switch (distributionMode)
@@ -144,6 +147,7 @@ namespace Misaki.ArtTool
}
objectDistributionSetting.meshData = new(inputMeshFilter, Allocator.TempJob);
_pointSize = objectDistributionSetting.DistributionCount;
break;
case DistributionMode.Spline:
@@ -176,21 +180,15 @@ namespace Misaki.ArtTool
break;
}
// Allocate a empty native spline to avoid job error
if (distributionMode != DistributionMode.Spline)
{
splineDistributionSetting.nativeSpline = new(new List<BezierKnot>(), false, transform.localToWorldMatrix, Allocator.TempJob);
}
// Allocate a empty native collection to avoid job error
EnsureNativeCollectionValid();
if (_pointSize == 0)
{
return;
}
if (_points == null || _pointSize > _points.Length)
{
_points = _pointPool.Rent(_pointSize);
}
_points = _pointPool.Rent(_pointSize);
foreach (var effectorData in effectors)
{
@@ -198,38 +196,6 @@ namespace Misaki.ArtTool
}
var worldMatrix = transform.localToWorldMatrix;
//Parallel.For(0, _pointSize, i =>
////for (var i = 0; i < _pointSize; i++)
//{
// var pointMatrix = float4x4.identity;
// var isValid = true;
// switch (distributionMode)
// {
// case DistributionMode.Object:
// break;
// case DistributionMode.Spline:
// Distribution.SplineDistribution(i, _pointSize, 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;
// //}
//});
// Since NativeSpline is not available in managed thread, we have to use jobs
var pointsArray = new NativeArray<PointData>(_points.Length, Allocator.TempJob);
@@ -240,6 +206,7 @@ namespace Misaki.ArtTool
distributionMode = distributionMode,
objectDistributionSetting = objectDistributionSetting,
splineDistributionSetting = splineDistributionSetting,
linearDistributionSetting = linearDistributionSetting,
gridDistributionSetting = gridDistributionSetting,
@@ -259,7 +226,7 @@ namespace Misaki.ArtTool
// Switch to managed thread for effectors because of interface
Parallel.For(0, _pointSize, i =>
{
for (var e = 0; e < effectors.Count; e++)
for (var e = effectors.Count - 1; e >= 0; e--)
{
if (!effectors[e].enable)
{
@@ -281,6 +248,21 @@ namespace Misaki.ArtTool
});
_isPointsDirty = false;
Profiler.EndSample();
}
private void EnsureNativeCollectionValid()
{
if (distributionMode != DistributionMode.Spline)
{
splineDistributionSetting.nativeSpline = new(new List<BezierKnot>(), false, transform.localToWorldMatrix, Allocator.TempJob);
}
if (distributionMode != DistributionMode.Object)
{
objectDistributionSetting.meshData = new MeshData(Allocator.TempJob);
}
}
public void InstantiateGameObjectOnPoints()

View File

@@ -48,8 +48,15 @@ namespace Misaki.ArtTool
public virtual void Initialize()
{
for (var i = 0; i < fieldDataList.Count; i++)
var fieldCount = fieldDataList.Count;
for (var i = fieldCount - 1; i >= 0; i--)
{
var fieldData = fieldDataList[i];
if (fieldData.field == null || !fieldData.enable || fieldData.opacity <= 0.0f)
{
continue;
}
fieldDataList[i].field.Initialize();
}
@@ -62,16 +69,16 @@ namespace Misaki.ArtTool
{
var weight = 1.0f;
var fieldCount = fieldDataList.Count;
for (var i = 0; i < fieldCount; i++)
for (var i = fieldCount - 1; i >= 0; i--)
{
var fieldData = fieldDataList[i];
if (!fieldData.enable || fieldData.opacity <= 0.0f)
if (fieldData.field == null || !fieldData.enable || fieldData.opacity <= 0.0f)
{
continue;
}
//weight = math.lerp(weight, fieldData.field.Operate(worldPosition), fieldData.opacity);
weight = FieldHelper.BlendField(weight, fieldData.field.Operate(worldPosition), fieldData.opacity, fieldData.blending);
var bValue = fieldData.field.Operate(worldPosition, weight);
weight = FieldHelper.BlendField(weight, bValue, fieldData.opacity, fieldData.blending);
}
weight *= strength;

View File

@@ -6,29 +6,13 @@ 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;
}
public abstract float Operate(float3 position, float weight);
private void Update()
{

View File

@@ -0,0 +1,28 @@
using System;
using Unity.Mathematics;
namespace Misaki.ArtTool
{
public interface IFieldLayer
{
public EventHandler PropertyChanged
{
get;
set;
}
public virtual void Initialize()
{
}
public virtual float Operate(float3 position)
{
return 0.0f;
}
public virtual float Operate(float weight)
{
return weight;
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 1155a51cb3c944442b68146ffde0edc8

View File

@@ -0,0 +1,15 @@
using System;
namespace Misaki.ArtTool.Packages
{
public abstract class ModifierBase : IFieldLayer
{
public EventHandler PropertyChanged
{
get;
set;
}
public abstract float Operate(float weight);
}
}

View File

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

View File

@@ -1,5 +1,5 @@
using System;
using Unity.Mathematics;
using Unity.Mathematics;
using Random = Unity.Mathematics.Random;
namespace Misaki.ArtTool
{
@@ -7,7 +7,74 @@ namespace Misaki.ArtTool
{
public static void ObjectDistribution(int index, ObjectDistributionSetting setting, out float4x4 localMatrix, out bool isValid)
{
throw new NotImplementedException();
var random = new Random();
if (index > uint.MaxValue - setting.seed)
{
random = Random.CreateFromIndex(setting.seed - (uint)index);
}
else
{
random = Random.CreateFromIndex(setting.seed + (uint)index);
}
var position = float3.zero;
var forwardDirection = new float3(0.0f, 0.0f, 1.0f);
var upDirection = new float3(0.0f, 1.0f, 0.0f);
isValid = false;
switch (setting.mode)
{
case ObjectDistributionMode.Surface:
break;
case ObjectDistributionMode.Volume:
var meshScale = setting.meshData.worldMatrix.GetScale();
var meshPosition = setting.meshData.worldMatrix.c3.xyz;
position = random.NextFloat3(-setting.meshData.bounds.extents * meshScale + meshPosition, setting.meshData.bounds.extents * meshScale + meshPosition);
var isInsideMesh = ShapeHelper.IsPointInsideMesh(position, ref setting.meshData);
while (!isInsideMesh)
{
position = random.NextFloat3(-setting.meshData.bounds.extents * meshScale + meshPosition, setting.meshData.bounds.extents * meshScale + meshPosition);
isInsideMesh = ShapeHelper.IsPointInsideMesh(position, ref setting.meshData);
}
isValid = true;
break;
case ObjectDistributionMode.Vertex:
if (ShapeHelper.GetMeshVertexPosition(index, ref setting.meshData, out var vertexPosition))
{
position = vertexPosition;
isValid = true;
}
break;
case ObjectDistributionMode.Edge:
if (ShapeHelper.GetMeshEdgePosition(index, ref setting.meshData, out var edgePosition, out var edgeForward, out var edgeUp))
{
position = edgePosition;
forwardDirection = edgeForward;
upDirection = edgeUp;
isValid = true;
}
break;
case ObjectDistributionMode.PolygonCenter:
if (ShapeHelper.GetMeshPolygonPosition(index, ref setting.meshData, out var polygonPosition))
{
position = polygonPosition;
isValid = true;
}
break;
default:
break;
}
var rotation = quaternion.LookRotationSafe(forwardDirection, upDirection);
localMatrix = float4x4.TRS(position, rotation, new float3(1.0f));
}
}
}

View File

@@ -6,6 +6,8 @@ namespace Misaki.ArtTool
[ExecuteInEditMode]
public class LinearField : FieldBase
{
public RemappingSetting remappingSetting = new();
public float length = 1.0f;
private float3 fieldForward;
@@ -17,13 +19,13 @@ namespace Misaki.ArtTool
fieldPosition = transform.position;
}
public override float Operate(float3 position)
public override float Operate(float3 position, float weight)
{
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);
weight = math.saturate(distance / 2.0f + 0.5f);
weight = FieldHelper.ApplyRemapping(weight, ref remappingSetting);
return weight;
}

View File

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

View File

@@ -0,0 +1,14 @@
using Unity.Mathematics;
namespace Misaki.ArtTool
{
public class Clamp : FieldBase
{
public float2 minMax;
public override float Operate(float3 position, float weight)
{
return math.clamp(weight, minMax.x, minMax.y);
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 315e5886979edba429c781f6a8a0a0cc

View File

@@ -5,6 +5,8 @@ namespace Misaki.ArtTool
{
public class SphereField : FieldBase
{
public RemappingSetting remappingSetting = new();
public float radius = 1.0f;
private float3 fieldPosition;
@@ -14,10 +16,10 @@ namespace Misaki.ArtTool
fieldPosition = transform.position;
}
public override float Operate(float3 position)
public override float Operate(float3 position, float weight)
{
var weight = ShapeHelper.Linear01DistanceToSphereCenter(position, fieldPosition, radius);
weight = Remapping(weight);
weight = ShapeHelper.Linear01DistanceToSphereCenter(position, fieldPosition, radius);
weight = FieldHelper.ApplyRemapping(weight, ref remappingSetting);
return weight;
}

View File

@@ -52,5 +52,19 @@ namespace Misaki.ArtTool
return result;
}
public static float ApplyRemapping(float weight, ref RemappingSetting remappingSetting)
{
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;
}
}
}

View File

@@ -4,7 +4,7 @@ namespace Misaki.ArtTool
{
internal static partial class ShapeHelper
{
internal static float3 GetCubePosition(int index, int3 size)
internal static float3 GetCubePosition(int index, float3 size)
{
float3 localPosition;
var yIndex = index / (size.x * size.z);
@@ -13,38 +13,96 @@ namespace Misaki.ArtTool
var xIndex = remain % size.x;
localPosition = new float3(xIndex, yIndex, zIndex);
localPosition -= (float3)(size - 1) * 0.5f;
localPosition -= (size - 1) * 0.5f;
return localPosition;
}
internal static bool GetMeshVertexPosition(int index, MeshData meshData, out float3 position)
internal static bool GetMeshVertexPosition(int index, ref MeshData meshData, out float3 position)
{
position = float3.zero;
if (!meshData.vertices.IsCreated || meshData.vertices.Length <= index)
{
position = float3.zero;
return false;
}
position = meshData.vertices[index];
var meshScale = meshData.worldMatrix.GetScale();
var meshPosition = meshData.worldMatrix.c3.xyz;
if (index >= meshData.vertices.Length)
{
return false;
}
position = meshData.vertices[index] * meshScale + meshPosition;
return true;
}
internal static bool GetMeshEdgePosition(int index, MeshData meshData, out float3 position)
internal static bool GetMeshEdgePosition(int index, ref MeshData meshData, out float3 position, out float3 forwardDirection, out float3 upDirection)
{
if (!meshData.edges.IsCreated || meshData.edges.Length <= index)
position = float3.zero;
forwardDirection = new float3(0.0f, 0.0f, 1.0f);
upDirection = new float3(0.0f, 1.0f, 0.0f);
if (!meshData.edges.IsCreated || index >= meshData.edges.Length)
{
position = float3.zero;
return false;
}
var edge = meshData.edges[index];
if (meshData.vertices.Length <= edge.x || meshData.vertices.Length <= edge.y)
if (edge.x >= meshData.vertices.Length || edge.y >= meshData.vertices.Length)
{
position = float3.zero;
return false;
}
position = (meshData.vertices[edge.x] + meshData.vertices[edge.y]) / 2.0f;
var meshScale = meshData.worldMatrix.GetScale();
var meshPosition = meshData.worldMatrix.c3.xyz;
var a = meshData.vertices[edge.x] * meshScale + meshPosition;
var b = meshData.vertices[edge.y] * meshScale + meshPosition;
position = (a + b) / 2.0f;
forwardDirection = a - b;
var interpNormal = math.normalize(meshData.normals[edge.x] + meshData.normals[edge.y]);
upDirection = math.normalize(math.cross(interpNormal, new float3(1.0f, 0.0f, 0.0f)));
if (math.all(upDirection <= float3.zero))
{
upDirection = math.normalize(math.cross(interpNormal, new float3(0.0f, 0.0f, 1.0f)));
}
return true;
}
internal static bool GetMeshPolygonPosition(int index, ref MeshData meshData, out float3 position)
{
position = float3.zero;
if (!meshData.edges.IsCreated || meshData.edges.Length <= index)
{
return false;
}
var meshScale = meshData.worldMatrix.GetScale();
var meshPosition = meshData.worldMatrix.c3.xyz;
var triangleIndex = index * 3;
if (triangleIndex >= meshData.triangles.Length - 2)
{
return false;
}
var pointIndexA = meshData.triangles[triangleIndex];
var pointIndexB = meshData.triangles[triangleIndex + 1];
var pointIndexC = meshData.triangles[triangleIndex + 2];
var a = meshData.vertices[pointIndexA] * meshScale + meshPosition;
var b = meshData.vertices[pointIndexB] * meshScale + meshPosition;
var c = meshData.vertices[pointIndexC] * meshScale + meshPosition;
position = (a + b + c) / 3.0f;
return true;
}
}

View File

@@ -41,15 +41,17 @@ namespace Misaki.ArtTool
return withinRadius && withinHeight;
}
internal static bool IsPointInsideMesh(float3 pointPosition, float3 meshPosition, MeshData meshData)
internal static bool IsPointInsideMesh(float3 pointPosition, ref MeshData meshData)
{
var windingNumber = 0;
var meshScale = meshData.worldMatrix.GetScale();
var meshPosition = meshData.worldMatrix.c3.xyz;
for (var i = 0; i < meshData.triangles.Length; i += 3)
{
var v1 = meshData.vertices[meshData.triangles[i]];
var v2 = meshData.vertices[meshData.triangles[i + 1]];
var v3 = meshData.vertices[meshData.triangles[i + 2]];
var v1 = meshData.vertices[meshData.triangles[i]] * meshScale + meshPosition;
var v2 = meshData.vertices[meshData.triangles[i + 1]] * meshScale + meshPosition;
var v3 = meshData.vertices[meshData.triangles[i + 2]] * meshScale + meshPosition;
if (IsPointInsideTriangle(pointPosition, v1, v2, v3))
{
@@ -57,9 +59,10 @@ namespace Misaki.ArtTool
}
}
return windingNumber % 2 != 0;
return windingNumber % 2 == 0;
}
//TODO: Fix it
private static bool IsPointInsideTriangle(float3 point, float3 a, float3 b, float3 c)
{
var v0 = c - a;
@@ -76,7 +79,7 @@ namespace Misaki.ArtTool
var u = (dot11 * dot02 - dot01 * dot12) * invDenom;
var v = (dot00 * dot12 - dot01 * dot02) * invDenom;
return (u >= 0) && (v >= 0) && (u + v < 1.0f);
return (u >= 0) && (v >= 0) && (u + v < 1);
}
}
}

View File

@@ -1,11 +1,11 @@
using Unity.Burst;
using Unity.Collections;
using Unity.Jobs;
using Unity.Mathematics;
using UnityEngine;
namespace Misaki.ArtTool
{
[ExecuteInEditMode]
[BurstCompile]
public struct PointsGenerationJob : IJobParallelForBatch
{
public float4x4 worldMatrix;
@@ -13,6 +13,7 @@ namespace Misaki.ArtTool
public DistributionMode distributionMode;
public ObjectDistributionSetting objectDistributionSetting;
public SplineDistributionSetting splineDistributionSetting;
public LinearDistributionSetting linearDistributionSetting;
public GridDistributionSetting gridDistributionSetting;
@@ -29,6 +30,7 @@ namespace Misaki.ArtTool
switch (distributionMode)
{
case DistributionMode.Object:
Distribution.ObjectDistribution(i, objectDistributionSetting, out pointMatrix, out isValid);
break;
case DistributionMode.Spline:
Distribution.SplineDistribution(i, pointSize, splineDistributionSetting, out pointMatrix, out isValid);

View File

@@ -1,9 +1,11 @@
using Unity.Burst;
using Unity.Collections;
using Unity.Mathematics;
using UnityEngine.Jobs;
namespace Misaki.ArtTool
{
[BurstCompile]
public struct TransformAccessJob : IJobParallelForTransform
{
[ReadOnly]

View File

@@ -7,8 +7,40 @@ namespace Misaki.ArtTool
{
public MeshData meshData;
public ObjectDistributionMode mode;
public int count;
public uint count;
public uint seed;
public bool alignNormal;
public int DistributionCount
{
get
{
var result = 0;
switch (mode)
{
case ObjectDistributionMode.Surface:
case ObjectDistributionMode.Volume:
result = (int)count;
break;
case ObjectDistributionMode.Vertex:
result = meshData.vertexCount;
break;
case ObjectDistributionMode.Edge:
result = meshData.edges.Length;
break;
case ObjectDistributionMode.PolygonCenter:
result = meshData.triangles.Length / 3;
break;
default:
break;
}
return result;
}
}
}
}

View File

@@ -8,4 +8,4 @@ namespace Misaki.ArtTool
public bool enable = true;
public EffectorBase effector;
}
}
}

View File

@@ -8,14 +8,33 @@ namespace Misaki.ArtTool
public struct MeshData : IDisposable
{
public Bounds bounds;
[ReadOnly]
public NativeArray<int> triangles;
public NativeArray<float4> tangents;
[ReadOnly]
public NativeArray<float3> normals;
[ReadOnly]
public NativeArray<float3> vertices;
[ReadOnly]
public NativeList<int2> edges;
public int vertexCount;
public float4x4 worldMatrix;
public MeshData(Allocator allocator)
{
bounds = default;
triangles = new(0, allocator);
normals = new(0, allocator);
vertices = new(0, allocator);
edges = new(0, allocator);
vertexCount = 0;
worldMatrix = float4x4.identity;
}
public MeshData(MeshFilter meshFilter, Allocator allocator)
{
var mesh = meshFilter.sharedMesh;
@@ -28,10 +47,10 @@ namespace Misaki.ArtTool
triangles[i] = mesh.triangles[i];
}
tangents = new(mesh.tangents.Length, allocator);
for (var i = 0; i < tangents.Length; i++)
normals = new(mesh.tangents.Length, allocator);
for (var i = 0; i < normals.Length; i++)
{
tangents[i] = mesh.tangents[i];
normals[i] = mesh.normals[i];
}
vertices = new(mesh.vertices.Length, allocator);
@@ -68,7 +87,7 @@ namespace Misaki.ArtTool
public void Dispose()
{
triangles.Dispose();
tangents.Dispose();
normals.Dispose();
vertices.Dispose();
edges.Dispose();
}