SanAndreasUnity/Assets/Scripts/Behaviours/World/Division.cs
2020-05-31 19:09:50 +02:00

372 lines
No EOL
11 KiB
C#

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using SanAndreasUnity.Utilities;
namespace SanAndreasUnity.Behaviours.World
{
public class Division : MonoBehaviour, IEnumerable<Division>, IComparable<Division>
{
private static readonly Comparison<MapObject> _sHorzSort =
(a, b) => Math.Sign(a.CellPos.x - b.CellPos.x);
private static readonly Comparison<MapObject> _sVertSort =
(a, b) => Math.Sign(a.CellPos.y - b.CellPos.y);
public static Division Create(Transform parent)
{
var obj = new GameObject();
var split = obj.AddComponent<Division>();
obj.transform.SetParent(parent);
return split;
}
private const int LeafObjectLimit = 127;
private Division _childA;
private Division _childB;
private List<MapObject> _objects;
public int NumObjects { get { return _objects != null ? _objects.Count : 0; } }
public int NumObjectsIncludingChildren
{
get
{
int count = this.NumObjects;
if (_childA != null)
count += _childA.NumObjectsIncludingChildren;
if (_childB != null)
count += _childB.NumObjectsIncludingChildren;
return count;
}
}
private bool _isVertSplit;
private float _splitVal;
private Vector3 _lastRefreshPos;
public Vector2 Min { get; private set; }
public Vector2 Max { get; private set; }
private Bounds _bounds;
public bool IsSubdivided { get { return _objects == null; } }
internal float LoadOrder { get; private set; }
public void SetBounds(Vector2 min, Vector2 max)
{
Min = min;
Max = max;
_bounds = new Bounds(((this.Min + this.Max) * 0.5f).ToVector3XZ(), (this.Max - this.Min).ToVector3XZ().WithY(10000f));
var mid = (Max + Min) * .5f;
if (float.IsNaN(mid.x) || float.IsInfinity(mid.x))
{
mid.x = 0f;
}
if (float.IsNaN(mid.y) || float.IsInfinity(mid.y))
{
mid.y = 0f;
}
transform.position = new Vector3(mid.x, 0f, mid.y);
name = String.Format("Split {0}, {1}", min, max);
_objects = _objects ?? new List<MapObject>();
}
private void Subdivide()
{
if (IsSubdivided)
{
throw new InvalidOperationException("Already subdivided");
}
if (_objects.Count == 0)
{
throw new InvalidOperationException("Cannot subdivide an empty leaf");
}
var min = Max;
var max = Min;
foreach (var obj in _objects)
{
var pos = obj.CellPos;
min.x = Mathf.Min(pos.x, min.x);
min.y = Mathf.Min(pos.y, min.y);
max.x = Mathf.Max(pos.x, max.x);
max.y = Mathf.Max(pos.y, max.y);
}
_isVertSplit = max.x - min.x >= max.y - min.y;
_objects.Sort(_isVertSplit ? _sHorzSort : _sVertSort);
_childA = Create(transform);
_childB = Create(transform);
var mid = _objects.Count / 2;
var median = (_objects[mid - 1].CellPos + _objects[mid].CellPos) * .5f;
if (_isVertSplit)
{
_splitVal = median.x;
_childA.SetBounds(Min, new Vector2(_splitVal, Max.y));
_childB.SetBounds(new Vector2(_splitVal, Min.y), Max);
}
else
{
_splitVal = median.y;
_childA.SetBounds(Min, new Vector2(Max.x, _splitVal));
_childB.SetBounds(new Vector2(Min.x, _splitVal), Max);
}
_childA._objects = _objects;
_childB._objects = new List<MapObject>();
_childB._objects.AddRange(_childA._objects.Skip(mid));
_childA._objects.RemoveRange(mid, _objects.Count - mid);
_objects = null;
}
private void AddInternal(MapObject obj)
{
if (IsSubdivided)
{
var comp = _isVertSplit ? obj.CellPos.x : obj.CellPos.y;
(comp < _splitVal ? _childA : _childB).AddInternal(obj);
return;
}
_objects.Add(obj);
if (_objects.Count > LeafObjectLimit) Subdivide();
}
public void Add(MapObject obj)
{
AddInternal(obj);
UpdateParents();
}
public void AddRange(IEnumerable<MapObject> objs)
{
foreach (var obj in objs.OrderBy(x => x.RandomInt))
{
AddInternal(obj);
}
UpdateParents();
}
private void UpdateParents()
{
if (IsSubdivided)
{
_childA.UpdateParents();
_childB.UpdateParents();
}
else
{
if (_objects.Count == 0) return;
var sum = _objects.Aggregate(new Vector2(), (s, x) => s + x.CellPos);
transform.position = new Vector3(sum.x / _objects.Count, 0f, sum.y / _objects.Count);
foreach (var obj in _objects)
{
obj.transform.SetParent(transform, true);
}
}
}
public bool Contains(MapObject obj)
{
return Contains(obj.CellPos);
}
public bool Contains(Vector3 pos)
{
return pos.x >= Min.x && pos.z >= Min.y && pos.x < Max.x && pos.z < Max.y;
}
public bool Contains(Vector2 pos)
{
return pos.x >= Min.x && pos.y >= Min.y && pos.x < Max.x && pos.y < Max.y;
}
private void OnDrawGizmosSelected()
{
Gizmos.color = Color.green;
Gizmos.DrawWireCube(_bounds.center, _bounds.size.WithY(100f));
F.HandlesDrawText(_bounds.center,
string.Format("total objects {0}, my objects {1}, load order {2}", this.NumObjectsIncludingChildren, this.NumObjects, this.LoadOrder),
Color.green);
/*
if (!IsSubdivided) return;
Gizmos.color = Color.green;
var min = new Vector2(Math.Max(Min.x, -8192f), Math.Max(Min.y, -8192f));
var max = new Vector2(Math.Min(Max.x, +8192f), Math.Min(Max.y, +8192f));
if (_isVertSplit)
{
Gizmos.DrawLine(new Vector3(_splitVal, 0f, min.y), new Vector3(_splitVal, 0f, max.y));
}
else
{
Gizmos.DrawLine(new Vector3(min.x, 0f, _splitVal), new Vector3(max.x, 0f, _splitVal));
}
*/
}
public float GetDistance(Vector3 pos)
{
return Mathf.Sqrt(GetDistanceSquared(pos));
}
public float GetDistanceSquared(Vector3 pos)
{
pos.y = 0f; // only count X and Z axis
// get the closest point on bounds
// if position is inside bounds, the resulting distance will be 0
Vector3 closestPos = _bounds.ClosestPoint(pos);
return Vector3.SqrMagnitude(pos - closestPos);
}
public Vector3 GetClosestPosition (List<Vector3> positions)
{
Vector3 closestPos = positions [0];
float smallestDist2 = float.MaxValue;
Vector2 center = (this.Min + this.Max) * 0.5f;
for (int i = 0; i < positions.Count; i++)
{
float dist2 = Vector2.SqrMagnitude (positions [i].ToVec2WithXAndZ() - center);
if (dist2 <= smallestDist2)
{
smallestDist2 = dist2;
closestPos = positions [i];
}
}
return closestPos;
}
public bool RefreshLoadOrder(Vector3 from, out int numMapObjectsUpdatedLoadOrder)
{
UnityEngine.Profiling.Profiler.BeginSample ("Division.RefreshLoadOrder", this);
var toLoad = false;
numMapObjectsUpdatedLoadOrder = 0;
if (GetDistanceSquared(from) <= Cell.Instance.maxDrawDistance * Cell.Instance.maxDrawDistance)
{
float divisionRefreshDistanceDeltaSquared = Cell.Instance.divisionRefreshDistanceDelta * Cell.Instance.divisionRefreshDistanceDelta;
// float factor = Cell.Instance.divisionLoadOrderDistanceFactor; //16;
// if (Vector3.SqrMagnitude(from - _lastRefreshPos) > GetDistanceSquared(from) / (factor*factor)) {
if (Vector3.SqrMagnitude(from - _lastRefreshPos) > divisionRefreshDistanceDeltaSquared)
{
_lastRefreshPos = from;
foreach (var obj in _objects)
{
bool b = obj.RefreshLoadOrder(from);
if (b)
{
toLoad = true;
numMapObjectsUpdatedLoadOrder++;
}
}
}
else
{
toLoad = _objects.Any(x => !float.IsPositiveInfinity(x.LoadOrder));
}
}
if (toLoad)
{
_objects.Sort(); // THIS MAY BE PERFORMANCE DROP
LoadOrder = _objects[0].LoadOrder;
}
else
{
LoadOrder = float.PositiveInfinity;
}
UnityEngine.Profiling.Profiler.EndSample ();
return toLoad;
}
public int LoadWhile(Func<bool> predicate)
{
UnityEngine.Profiling.Profiler.BeginSample ("LoadWhile", this);
int numLoaded = 0;
foreach (var toLoad in _objects)
{
if (float.IsPositiveInfinity(toLoad.LoadOrder))
break;
if (toLoad.HasLoaded)
{
// this object is loaded, just show it
toLoad.Show();
}
else
{
// this object is still not loaded from disk
// check if we should load it
if (predicate())
{
toLoad.Show();
numLoaded++;
}
}
}
UnityEngine.Profiling.Profiler.EndSample ();
// return predicate();
// return false ;
return numLoaded;
}
public IEnumerator<Division> GetEnumerator()
{
if (IsSubdivided)
{
return _childA.Concat(_childB).GetEnumerator();
}
return new[] { this }.AsEnumerable().GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public int CompareTo(Division other)
{
return LoadOrder > other.LoadOrder ? 1 : LoadOrder == other.LoadOrder ? 0 : -1;
}
}
}