using UnityEngine; using System; using System.Linq; using System.Collections.Generic; #if UNITY_EDITOR using UnityEditor; #endif namespace IsoTools { [ExecuteInEditMode] public class IsoWorld : MonoBehaviour { class ObjectInfo { public int Index; public IsoObject IsoObject; public Vector3 MinSector; public Vector3 MaxSector; public bool Visited; public int BeginDepend; public int EndDepend; public ObjectInfo(int index, IsoObject iso_object, Vector3 min_sector, Vector3 max_sector) { Index = index; IsoObject = iso_object; MinSector = min_sector; MaxSector = max_sector; } public void Init(int first_depend) { Visited = false; BeginDepend = first_depend; EndDepend = first_depend; } } class SectorInfo { public List Objects = new List(); } bool _dirty = true; float _lastTileSize = 0.0f; float _lastMinDepth = 0.0f; float _lastMaxDepth = 0.0f; List _sectors = new List(); List _objects = new List(); List _depends = new List(); HashSet _visibles = new HashSet(); float _objsSectorSize = 0.0f; Vector3 _objsMinNumPos = Vector3.zero; Vector3 _objsMaxNumPos = Vector3.zero; Vector3 _objsNumPosCount = Vector3.zero; [SerializeField] public float _tileSize = 32.0f; /// Isometric tile size. public float TileSize { get { return _tileSize; } set { _tileSize = Math.Max(value, Mathf.Epsilon); ChangeSortingProperty(); } } [SerializeField] public float _minDepth = 0.0f; /// Min sorting depth value. public float MinDepth { get { return _minDepth; } set { _minDepth = value; ChangeSortingProperty(); } } [SerializeField] public float _maxDepth = 100.0f; /// Max sorting depth value. public float MaxDepth { get { return _maxDepth; } set { _maxDepth = value; ChangeSortingProperty(); } } // ------------------------------------------------------------------------ /// /// Marks world for resorting. /// // ------------------------------------------------------------------------ public void MarkDirty() { _dirty = true; MarkEditorWorldDirty(); } // ------------------------------------------------------------------------ /// /// Marks world for resorting. /// /// Isometric object for resorting. // ------------------------------------------------------------------------ public void MarkDirty(IsoObject iso_object) { if ( iso_object && IsIsoObjectVisible(iso_object) ) { MarkDirty(); } } // ------------------------------------------------------------------------ /// /// Convert isometric coordinates to screen coordinates /// /// Screen coordinates /// Isometric coordinates. // ------------------------------------------------------------------------ public Vector2 IsoToScreen(Vector3 pos) { return new Vector2( (pos.x - pos.y), (pos.x + pos.y) * 0.5f + pos.z) * TileSize; } // ------------------------------------------------------------------------ /// /// Convert screen coordinates to isometric coordinates /// /// Isometric coordinates /// Screen coordinates. // ------------------------------------------------------------------------ public Vector3 ScreenToIso(Vector2 pos) { return new Vector3( (pos.x * 0.5f + pos.y), (pos.y - pos.x * 0.5f), 0.0f) / TileSize; } // ------------------------------------------------------------------------ /// /// Convert screen coordinates to isometric coordinates with specified isometric height /// /// Isometric coordinates /// Screen coordinates. /// Point isometric height. // ------------------------------------------------------------------------ public Vector3 ScreenToIso(Vector2 pos, float iso_z) { return IsoUtils.Vec3ChangeZ( ScreenToIso(new Vector2(pos.x, pos.y - iso_z * TileSize)), iso_z); } // ------------------------------------------------------------------------ // // Private // // ------------------------------------------------------------------------ bool IsIsoObjectVisible(IsoObject iso_object) { var renderer = iso_object.GetComponent(); if ( renderer && renderer.isVisible ) { return true; } var renderers = iso_object.GetComponentsInChildren(); return renderers.Any(r => r.isVisible); } void MarkEditorWorldDirty() { #if UNITY_EDITOR if ( Application.isEditor ) { EditorUtility.SetDirty(this); } #endif } void ApplyToAllIsoObjects(Action act) { var iso_objects = GameObject.FindObjectsOfType(); foreach ( var obj in iso_objects ) { act(obj); } } void FixAllTransforms() { ApplyToAllIsoObjects(obj => obj.FixTransform()); } void ChangeSortingProperty() { MarkDirty(); FixAllTransforms(); _lastTileSize = TileSize; _lastMinDepth = MinDepth; _lastMaxDepth = MaxDepth; } int SectorIndex(Vector3 num_pos) { return Mathf.FloorToInt( num_pos.x + _objsNumPosCount.x * (num_pos.y + num_pos.z * _objsNumPosCount.y)); } Vector3 SectorNumPos(int index) { var mz = _objsNumPosCount.x * _objsNumPosCount.y; var my = _objsNumPosCount.x; var vz = Mathf.FloorToInt(index / mz); var vy = Mathf.FloorToInt((index - vz * mz) / my); var vx = Mathf.FloorToInt(index - vz * mz - vy * my); return new Vector3(vx, vy, vz); } SectorInfo FindSector(Vector3 num_pos) { if ( num_pos.x < 0 || num_pos.y < 0 || num_pos.z < 0 ) { return null; } if ( num_pos.x >= _objsNumPosCount.x || num_pos.y >= _objsNumPosCount.y || num_pos.z >= _objsNumPosCount.z ) { return null; } return _sectors[SectorIndex(num_pos)]; } void LookUpSectorDepends(Vector3 num_pos, Action act) { var ms = FindSector(num_pos); if ( ms != null ) { act(ms); var s1 = FindSector(num_pos + new Vector3(-1, 0, 0)); var s2 = FindSector(num_pos + new Vector3( 0, -1, 0)); var s3 = FindSector(num_pos + new Vector3(-1, -1, 0)); if ( s1 != null ) act(s1); if ( s2 != null ) act(s2); if ( s3 != null ) act(s3); for ( var i = 0; i <= _objsNumPosCount.z; ++i ) { var ss1 = FindSector(num_pos + new Vector3( 0 - i, 0 - i, i + 1)); var ss2 = FindSector(num_pos + new Vector3(-1 - i, 0 - i, i + 1)); var ss3 = FindSector(num_pos + new Vector3( 0 - i, -1 - i, i + 1)); var ss4 = FindSector(num_pos + new Vector3(-1 - i, -1 - i, i + 1)); var ss5 = FindSector(num_pos + new Vector3(-2 - i, -1 - i, i + 1)); var ss6 = FindSector(num_pos + new Vector3(-1 - i, -2 - i, i + 1)); var ss7 = FindSector(num_pos + new Vector3(-2 - i, -2 - i, i + 1)); if ( ss1 != null ) act(ss1); if ( ss2 != null ) act(ss2); if ( ss3 != null ) act(ss3); if ( ss4 != null ) act(ss4); if ( ss5 != null ) act(ss5); if ( ss6 != null ) act(ss6); if ( ss7 != null ) act(ss7); } } } bool IsDepends(Vector3 a_min, Vector3 a_size, Vector3 b_min, Vector3 b_size) { var a_max = a_min + a_size; var b_max = b_min + b_size; var a_yesno = a_max.x > b_min.x && a_max.y > b_min.y && b_max.z > a_min.z; var b_yesno = b_max.x > a_min.x && b_max.y > a_min.y && a_max.z > b_min.z; if ( a_yesno && b_yesno ) { var da_p = new Vector3(a_max.x - b_min.x, a_max.y - b_min.y, b_max.z - a_min.z); var db_p = new Vector3(b_max.x - a_min.x, b_max.y - a_min.y, a_max.z - b_min.z); var dp_p = a_size + b_size - IsoUtils.Vec3Abs(da_p - db_p); if ( dp_p.x <= dp_p.y && dp_p.x <= dp_p.z ) { return da_p.x > db_p.x; } else if ( dp_p.y <= dp_p.x && dp_p.y <= dp_p.z ) { return da_p.y > db_p.y; } else { return da_p.z > db_p.z; } } return a_yesno; } void SetupVisibles(IsoObject[] iso_objects) { var new_visibles = new HashSet( iso_objects.Where(p => IsIsoObjectVisible(p))); if ( !_visibles.IsSupersetOf(new_visibles) ) { MarkDirty(); } _visibles = new_visibles; } void SetupSectorSize() { _objsSectorSize = 0.0f; var objsSum = 0; foreach ( var obj in _visibles ) { ++objsSum; _objsSectorSize += Mathf.Max(obj.Size.x, obj.Size.y, obj.Size.z); } _objsSectorSize = Mathf.Round(Mathf.Max(3.0f, _objsSectorSize / objsSum)); } void SetupObjects() { _objects.Clear(); _objsMinNumPos = Vector3.zero; _objsMaxNumPos = Vector3.one; foreach ( var obj in _visibles ) { var max_size = IsoUtils.Vec3Max(Vector3.one, obj.Size); var min_npos = IsoUtils.Vec3DivFloor(obj.Position, _objsSectorSize); var max_npos = IsoUtils.Vec3DivCeil(obj.Position + max_size, _objsSectorSize); _objsMinNumPos = IsoUtils.Vec3Min(_objsMinNumPos, min_npos); _objsMaxNumPos = IsoUtils.Vec3Max(_objsMaxNumPos, max_npos); _objects.Add(new ObjectInfo(_objects.Count, obj, min_npos, max_npos)); } _objsNumPosCount = _objsMaxNumPos - _objsMinNumPos; } void SetupSectors() { _sectors.Clear(); _sectors.Capacity = Mathf.FloorToInt(_objsNumPosCount.x * _objsNumPosCount.y * _objsNumPosCount.z); while ( _sectors.Count < _sectors.Capacity ) { _sectors.Add(new SectorInfo()); } foreach ( var obj in _objects ) { obj.MinSector -= _objsMinNumPos; obj.MaxSector -= _objsMinNumPos; IsoUtils.LookUpCube(obj.MinSector, obj.MaxSector, p => { var sector = FindSector(p); if ( sector != null ) { sector.Objects.Add(obj.Index); } }); } } void SetupObjectDepends() { _depends.Clear(); foreach ( var obj_a in _objects ) { obj_a.Init(_depends.Count); var obj_ao = obj_a.IsoObject; IsoUtils.LookUpCube(obj_a.MinSector, obj_a.MaxSector, num_pos => { LookUpSectorDepends(num_pos, sec => { foreach ( var obj_bi in sec.Objects ) { var obj_bo = _objects[obj_bi].IsoObject; if ( obj_ao != obj_bo && IsDepends(obj_ao.Position, obj_ao.Size, obj_bo.Position, obj_bo.Size) ) { _depends.Add(obj_bi); ++obj_a.EndDepend; } } }); }); } } void PlaceAllObjects() { var depth = MinDepth; foreach ( var info in _objects ) { depth = PlaceObject(info, depth); } _sectors.Clear(); _objects.Clear(); _depends.Clear(); } void PlaceObject(IsoObject obj, float depth) { var trans = obj.gameObject.transform; trans.position = IsoUtils.Vec3ChangeZ(trans.position, depth); } float PlaceObject(ObjectInfo info, float depth) { if ( info.Visited ) { return depth; } info.Visited = true; for ( var i = info.BeginDepend; i < info.EndDepend && i < _depends.Count; ++i ) { var obj_index = _depends[i]; var obj = _objects[obj_index]; depth = PlaceObject(obj, depth); } PlaceObject(info.IsoObject, depth); return depth + (MaxDepth - MinDepth) / _objects.Count; } void StepSort() { var iso_objects = GameObject.FindObjectsOfType(); SetupVisibles(iso_objects); if ( _dirty ) { SetupSectorSize(); SetupObjects(); SetupSectors(); SetupObjectDepends(); PlaceAllObjects(); _dirty = false; } } void Start() { ChangeSortingProperty(); StepSort(); } void LateUpdate() { if ( Application.isEditor ) { if ( !Mathf.Approximately(_lastTileSize, _tileSize) ) TileSize = _tileSize; if ( !Mathf.Approximately(_lastMinDepth, _minDepth) ) MinDepth = _minDepth; if ( !Mathf.Approximately(_lastMaxDepth, _maxDepth) ) MaxDepth = _maxDepth; } StepSort(); } void OnEnable() { MarkDirty(); } void OnDisable() { ApplyToAllIsoObjects(obj => obj.ResetIsoWorld()); } } } // namespace IsoTools