// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System.Collections.Generic;
using UnityEngine;
namespace Microsoft.MixedReality.Toolkit.Utilities
{
///
/// A Scatter Object Collection is simply a set of child objects randomly laid out within a radius.
/// Pressing "update collection" will run the randomization, feel free to run as many times until you get the desired result.
///
[AddComponentMenu("Scripts/MRTK/SDK/ScatterObjectCollection")]
public class ScatterObjectCollection : GridObjectCollection
{
///
/// Overriding base function for laying out all the children when UpdateCollection is called.
///
protected override void LayoutChildren()
{
Vector3[] nodeGrid = new Vector3[NodeList.Count];
Vector3 newPos;
// Now lets lay out the grid
Columns = Mathf.CeilToInt((float)NodeList.Count / Rows);
HalfCell = new Vector2(CellWidth * 0.5f, CellHeight * 0.5f);
// First start with a grid then project onto surface
ResolveGridLayout(nodeGrid, Layout);
// Get randomized planar mapping
// Calculate radius of each node while we're here
// Then use the packer function to shift them into place
for (int i = 0; i < NodeList.Count; i++)
{
ObjectCollectionNode node = NodeList[i];
newPos = VectorExtensions.ScatterMapping(nodeGrid[i], Radius);
Collider nodeCollider = NodeList[i].Transform.GetComponentInChildren();
if (nodeCollider != null)
{
// Make the radius the largest of the object's dimensions to avoid overlap
Bounds bounds = nodeCollider.bounds;
node.Radius = Mathf.Max(Mathf.Max(bounds.size.x, bounds.size.y), bounds.size.z) * 0.5f;
}
else
{
// Make the radius a default value
node.Radius = 1f;
}
node.Transform.localPosition = newPos;
UpdateNodeFacing(node);
NodeList[i] = node;
}
// Iterate [x] times
for (int i = 0; i < 100; i++)
{
IterateScatterPacking(NodeList, Radius);
}
}
///
/// Pack randomly spaced nodes so they don't overlap
/// Usually requires about 25 iterations for decent packing
///
private static void IterateScatterPacking(List nodes, float radiusPadding)
{
// Sort by closest to center (don't worry about z axis)
// Use the position of the collection as the packing center
nodes.Sort(ScatterSort);
Vector3 difference;
Vector2 difference2D;
// Move them closer together
float radiusPaddingSquared = Mathf.Pow(radiusPadding, 2f);
for (int i = 0; i < nodes.Count - 1; i++)
{
for (int j = i + 1; j < nodes.Count; j++)
{
if (i != j)
{
difference = nodes[j].Transform.localPosition - nodes[i].Transform.localPosition;
// Ignore Z axis
difference2D.x = difference.x;
difference2D.y = difference.y;
float combinedRadius = nodes[i].Radius + nodes[j].Radius;
float distance = difference2D.SqrMagnitude() - radiusPaddingSquared;
float minSeparation = Mathf.Min(distance, radiusPaddingSquared);
distance -= minSeparation;
if (distance < (Mathf.Pow(combinedRadius, 2)))
{
difference2D.Normalize();
difference *= ((combinedRadius - Mathf.Sqrt(distance)) * 0.5f);
nodes[j].Transform.localPosition += difference;
nodes[i].Transform.localPosition -= difference;
}
}
}
}
}
private static int ScatterSort(ObjectCollectionNode circle1, ObjectCollectionNode circle2)
{
float distance1 = (circle1.Transform.localPosition).sqrMagnitude;
float distance2 = (circle2.Transform.localPosition).sqrMagnitude;
return distance1.CompareTo(distance2);
}
}
}