mirror of
https://github.com/GTA-ASM/SanAndreasUnity
synced 2024-11-27 14:30:17 +00:00
372 lines
No EOL
11 KiB
C#
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;
|
|
}
|
|
}
|
|
} |