using UnityEngine; using IsoTools.Internal; using System.Collections.Generic; #if UNITY_EDITOR using UnityEditor; #endif namespace IsoTools { [ExecuteInEditMode, DisallowMultipleComponent] public class IsoWorld : MonoBehaviour { bool _dirty = false; IsoAssocList _objects = new IsoAssocList(); IsoAssocList _visibles = new IsoAssocList(); IsoAssocList _oldVisibles = new IsoAssocList(); bool _dirtyMat = true; Matrix4x4 _isoMatrix = Matrix4x4.identity; Matrix4x4 _isoRMatrix = Matrix4x4.identity; List _tmpRenderers = new List(); class Sector { public IsoList objects = new IsoList(); public void Reset() { objects.Clear(); } } IsoList _sectors = new IsoList(); float _sectorsSize = 0.0f; Vector2 _sectorsMinNumPos = Vector2.zero; Vector2 _sectorsMaxNumPos = Vector2.zero; Vector2 _sectorsNumPosCount = Vector2.zero; // --------------------------------------------------------------------- // // Constants // // --------------------------------------------------------------------- public static readonly float DefTileSize = 32.0f; public static readonly float MinTileSize = Mathf.Epsilon; public static readonly float MaxTileSize = float.MaxValue; public static readonly float DefTileRatio = 0.5f; public static readonly float MinTileRatio = 0.25f; public static readonly float MaxTileRatio = 1.0f; public static readonly float DefTileAngle = 45.0f; public static readonly float MinTileAngle = 0.0f; public static readonly float MaxTileAngle = 90.0f; public static readonly float DefTileHeight = DefTileSize; public static readonly float MinTileHeight = MinTileSize; public static readonly float MaxTileHeight = MaxTileSize; public static readonly float DefStepDepth = 0.1f; public static readonly float MinStepDepth = Mathf.Epsilon; public static readonly float MaxStepDepth = float.MaxValue; public static readonly float DefStartDepth = 1.0f; public static readonly float MinStartDepth = float.MinValue; public static readonly float MaxStartDepth = float.MaxValue; // --------------------------------------------------------------------- // // Sorting properties // // --------------------------------------------------------------------- [SerializeField] public float _tileSize = DefTileSize; public float tileSize { get { return _tileSize; } set { _tileSize = Mathf.Clamp(value, MinTileSize, MaxTileSize); ChangeSortingProperty(); } } [SerializeField] public float _tileRatio = DefTileRatio; public float tileRatio { get { return _tileRatio; } set { _tileRatio = Mathf.Clamp(value, MinTileRatio, MaxTileRatio); ChangeSortingProperty(); } } [SerializeField] public float _tileAngle = DefTileAngle; public float tileAngle { get { return _tileAngle; } set { _tileAngle = Mathf.Clamp(value, MinTileAngle, MaxTileAngle); ChangeSortingProperty(); } } [SerializeField] public float _tileHeight = DefTileHeight; public float tileHeight { get { return _tileHeight; } set { _tileHeight = Mathf.Clamp(value, MinTileHeight, MaxTileHeight); ChangeSortingProperty(); } } [SerializeField] public float _stepDepth = DefStepDepth; public float stepDepth { get { return _stepDepth; } set { _stepDepth = Mathf.Clamp(value, MinStepDepth, MaxStepDepth); ChangeSortingProperty(); } } [SerializeField] public float _startDepth = DefStartDepth; public float startDepth { get { return _startDepth; } set { _startDepth = Mathf.Clamp(value, MinStartDepth, MaxStartDepth); ChangeSortingProperty(); } } // --------------------------------------------------------------------- // // IsoToScreen/ScreenToIso // // --------------------------------------------------------------------- public Vector2 IsoToScreen(Vector3 iso_pnt) { if ( _dirtyMat ) { UpdateIsoMatrix(); _dirtyMat = false; } var screen_pos = _isoMatrix.MultiplyPoint(iso_pnt); return new Vector2( screen_pos.x, screen_pos.y + iso_pnt.z * tileHeight); } public Vector3 ScreenToIso(Vector2 pos) { if ( _dirtyMat ) { UpdateIsoMatrix(); _dirtyMat = false; } return _isoRMatrix.MultiplyPoint(new Vector3(pos.x, pos.y, 0.0f)); } public Vector3 ScreenToIso(Vector2 pos, float iso_z) { return IsoUtils.Vec3ChangeZ( ScreenToIso(new Vector2(pos.x, pos.y - iso_z * tileHeight)), iso_z); } // --------------------------------------------------------------------- // // TouchIsoPosition // // --------------------------------------------------------------------- public Vector3 TouchIsoPosition(int finger_id) { return TouchIsoPosition(finger_id, 0.0f); } public Vector3 TouchIsoPosition(int finger_id, float iso_z) { if ( !Camera.main ) { Debug.LogError("Main camera not found!", this); return Vector3.zero; } return TouchIsoPosition(finger_id, Camera.main, iso_z); } public Vector3 TouchIsoPosition(int finger_id, Camera camera) { return TouchIsoPosition(finger_id, camera, 0.0f); } public Vector3 TouchIsoPosition(int finger_id, Camera camera, float iso_z) { if ( !camera ) { Debug.LogError("Camera argument is incorrect!", this); return Vector3.zero; } for ( var i = 0; i < Input.touchCount; ++i ) { var touch = Input.GetTouch(i); if ( touch.fingerId == finger_id ) { return ScreenToIso( camera.ScreenToWorldPoint(touch.position), iso_z); } } Debug.LogError("Touch finger id argument is incorrect!", this); return Vector3.zero; } // --------------------------------------------------------------------- // // TouchIsoTilePosition // // --------------------------------------------------------------------- public Vector3 TouchIsoTilePosition(int finger_id) { return IsoUtils.Vec3Floor(TouchIsoPosition(finger_id)); } public Vector3 TouchIsoTilePosition(int finger_id, float iso_z) { return IsoUtils.Vec3Floor(TouchIsoPosition(finger_id, iso_z)); } public Vector3 TouchIsoTilePosition(int finger_id, Camera camera) { return IsoUtils.Vec3Floor(TouchIsoPosition(finger_id, camera)); } public Vector3 TouchIsoTilePosition(int finger_id, Camera camera, float iso_z) { return IsoUtils.Vec3Floor(TouchIsoPosition(finger_id, camera, iso_z)); } // --------------------------------------------------------------------- // // MouseIsoPosition // // --------------------------------------------------------------------- public Vector3 MouseIsoPosition() { return MouseIsoPosition(0.0f); } public Vector3 MouseIsoPosition(float iso_z) { if ( !Camera.main ) { Debug.LogError("Main camera not found!", this); return Vector3.zero; } return MouseIsoPosition(Camera.main, iso_z); } public Vector3 MouseIsoPosition(Camera camera) { return MouseIsoPosition(camera, 0.0f); } public Vector3 MouseIsoPosition(Camera camera, float iso_z) { if ( !camera ) { Debug.LogError("Camera argument is incorrect!", this); return Vector3.zero; } return ScreenToIso( camera.ScreenToWorldPoint(Input.mousePosition), iso_z); } // --------------------------------------------------------------------- // // MouseIsoTilePosition // // --------------------------------------------------------------------- public Vector3 MouseIsoTilePosition() { return IsoUtils.Vec3Floor(MouseIsoPosition()); } public Vector3 MouseIsoTilePosition(float iso_z) { return IsoUtils.Vec3Floor(MouseIsoPosition(iso_z)); } public Vector3 MouseIsoTilePosition(Camera camera) { return IsoUtils.Vec3Floor(MouseIsoPosition(camera)); } public Vector3 MouseIsoTilePosition(Camera camera, float iso_z) { return IsoUtils.Vec3Floor(MouseIsoPosition(camera, iso_z)); } // --------------------------------------------------------------------- // // Internal // // --------------------------------------------------------------------- public void MarkDirty() { if ( !_dirty ) { _dirty = true; #if UNITY_EDITOR EditorUtility.SetDirty(this); #endif } } public void MarkDirty(IsoObject iso_object) { if ( !iso_object.Internal.Dirty && _visibles.RawDict.ContainsKey(iso_object) ) { iso_object.Internal.Dirty = true; MarkDirty(); } } public void AddIsoObject(IsoObject iso_object) { _objects.Add(iso_object); } public void RemoveIsoObject(IsoObject iso_object) { ClearIsoObjectDepends(iso_object); _objects.Remove(iso_object); _visibles.Remove(iso_object); _oldVisibles.Remove(iso_object); } // --------------------------------------------------------------------- // // Private // // --------------------------------------------------------------------- void MarkDirtyIsoMatrix() { _dirtyMat = true; } void UpdateIsoMatrix() { _isoMatrix = Matrix4x4.Scale(new Vector3(1.0f, tileRatio, 1.0f)) * Matrix4x4.TRS( Vector3.zero, Quaternion.AngleAxis(90.0f - tileAngle, IsoUtils.vec3OneZ), new Vector3(tileSize * Mathf.Sqrt(2), tileSize * Mathf.Sqrt(2), tileHeight)); _isoRMatrix = _isoMatrix.inverse; } void FixAllTransforms() { for ( int i = 0, e = _objects.RawList.Count; i < e; ++i ) { _objects.RawList[i].FixTransform(); } } void ChangeSortingProperty() { MarkDirty(); MarkDirtyIsoMatrix(); FixAllTransforms(); } bool UpdateIsoObjectBounds3d(IsoObject iso_object) { if ( iso_object.mode == IsoObject.Mode.Mode3d ) { var minmax3d = IsoObjectMinMax3D(iso_object); var offset3d = iso_object.transform.position.z - minmax3d.center; if ( !Mathf.Approximately(iso_object.Internal.Offset3d, offset3d) || iso_object.Internal.MinMax3d.Approximately(minmax3d) ) { iso_object.Internal.Offset3d = offset3d; iso_object.Internal.MinMax3d = minmax3d; return true; } } return false; } IsoUtils.MinMax IsoObjectMinMax3D(IsoObject iso_object) { iso_object.GetComponentsInChildren(_tmpRenderers); if ( _tmpRenderers.Count < 1 ) { return IsoUtils.MinMax.zero; } // high performance tricks var bounds = _tmpRenderers[0].bounds; var center = bounds.center.z; var extent = bounds.extents.z; var minbounds = center - extent; var maxbounds = center + extent; var result_min = minbounds; var result_max = maxbounds; for ( int i = _tmpRenderers.Count - 1; i > 0; --i ) { bounds = _tmpRenderers[i].bounds; center = bounds.center.z; extent = bounds.extents.z; minbounds = center - extent; maxbounds = center + extent; if ( minbounds < result_min ) { result_min = minbounds; } if ( maxbounds > result_max ) { result_max = maxbounds; } } return new IsoUtils.MinMax(result_min, result_max); } bool IsIsoObjectVisible(IsoObject iso_object) { iso_object.GetComponentsInChildren(_tmpRenderers); for ( int i = 0, e = _tmpRenderers.Count; i < e; ++i ) { if ( _tmpRenderers[i].isVisible ) { return true; } } return false; } bool IsIsoObjectDepends(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; } bool IsIsoObjectDepends(IsoObject a, IsoObject b) { return a.Internal.ScreenRect.Overlaps(b.Internal.ScreenRect) && IsIsoObjectDepends(a.position, a.size, b.position, b.size); } Sector FindSector(Vector2 num_pos) { if ( num_pos.x < 0 || num_pos.y < 0 ) { return null; } if ( num_pos.x >= _sectorsNumPosCount.x || num_pos.y >= _sectorsNumPosCount.y ) { return null; } var sector_index = Mathf.FloorToInt(num_pos.x + _sectorsNumPosCount.x * num_pos.y); return _sectors[sector_index]; } void LookUpSectorDepends(Vector2 num_pos, IsoObject obj_a) { var sec = FindSector(num_pos); if ( sec != null ) { for ( int i = 0, e = sec.objects.Count; i < e; ++i ) { var obj_b = sec.objects[i]; if ( obj_a != obj_b && !obj_b.Internal.Dirty && IsIsoObjectDepends(obj_a, obj_b) ) { obj_a.Internal.SelfDepends.Add(obj_b); obj_b.Internal.TheirDepends.Add(obj_a); } } } } void LookUpSectorRDepends(Vector2 num_pos, IsoObject obj_a) { var sec = FindSector(num_pos); if ( sec != null ) { for ( int i = 0, e = sec.objects.Count; i < e; ++i ) { var obj_b = sec.objects[i]; if ( obj_a != obj_b && !obj_b.Internal.Dirty && IsIsoObjectDepends(obj_b, obj_a) ) { obj_b.Internal.SelfDepends.Add(obj_a); obj_a.Internal.TheirDepends.Add(obj_b); } } } } void SetupSectorSize() { _sectorsSize = 0.0f; for ( int i = 0, e = _visibles.RawList.Count; i < e; ++i ) { var iso_internal = _visibles.RawList[i].Internal; _sectorsSize += IsoUtils.Vec2MaxF(iso_internal.ScreenRect.size); } var min_sector_size = IsoToScreen(IsoUtils.vec3OneX).x - IsoToScreen(Vector3.zero).x; _sectorsSize = Mathf.Round(Mathf.Max(min_sector_size, _sectorsSize / _visibles.Count)); } void SetupObjectsSectors() { _sectorsMinNumPos = IsoUtils.Vec2From(float.MaxValue); _sectorsMaxNumPos = IsoUtils.Vec2From(float.MinValue); for ( int i = 0, e = _visibles.RawList.Count; i < e; ++i ) { var iso_internal = _visibles.RawList[i].Internal; iso_internal.MinSector = IsoUtils.Vec3DivFloor(iso_internal.ScreenRect.min, _sectorsSize); iso_internal.MaxSector = IsoUtils.Vec3DivCeil (iso_internal.ScreenRect.max, _sectorsSize); _sectorsMinNumPos = IsoUtils.Vec3Min(_sectorsMinNumPos, iso_internal.MinSector); _sectorsMaxNumPos = IsoUtils.Vec3Max(_sectorsMaxNumPos, iso_internal.MaxSector); } _sectorsNumPosCount = _sectorsMaxNumPos - _sectorsMinNumPos; } void ResizeSectors(int count) { if ( _sectors.Count < count ) { if ( _sectors.Capacity < count ) { _sectors.Capacity = count; } while ( _sectors.Count < _sectors.Capacity ) { _sectors.Push(new Sector()); } } for ( int i = 0, e = _sectors.Count; i < e; ++i ) { _sectors[i].Reset(); } } void TuneSectors() { for ( int i = 0, e = _visibles.RawList.Count; i < e; ++i ) { var iso_object = _visibles.RawList[i]; iso_object.Internal.MinSector -= _sectorsMinNumPos; iso_object.Internal.MaxSector -= _sectorsMinNumPos; var min = iso_object.Internal.MinSector; var max = iso_object.Internal.MaxSector; for ( var y = min.y; y < max.y; ++y ) { for ( var x = min.x; x < max.x; ++x ) { var sector = FindSector(new Vector2(x, y)); if ( sector != null ) { sector.objects.Push(iso_object); } }} } } void SetupSectors() { ResizeSectors(Mathf.FloorToInt(_sectorsNumPosCount.x * _sectorsNumPosCount.y)); TuneSectors(); } void StepSort() { Profiler.BeginSample("UpdateVisibles"); UpdateVisibles(); Profiler.EndSample(); if ( _dirty ) { Profiler.BeginSample("PlaceAllVisibles"); PlaceAllVisibles(); Profiler.EndSample(); _dirty = false; } } void UpdateVisibles() { Profiler.BeginSample("CalculateNewVisibles"); CalculateNewVisibles(); Profiler.EndSample(); Profiler.BeginSample("CalculateSectors"); SetupSectorSize(); SetupObjectsSectors(); SetupSectors(); Profiler.EndSample(); Profiler.BeginSample("ResolveVisibles"); ResolveVisibles(); Profiler.EndSample(); } void CalculateNewVisibles() { _oldVisibles.Clear(); for ( int i = 0, e = _objects.RawList.Count; i < e; ++i ) { var iso_object = _objects.RawList[i]; if ( IsIsoObjectVisible(iso_object) ) { iso_object.Internal.Placed = false; _oldVisibles.Add(iso_object); } } var old_visibles = _visibles; _visibles = _oldVisibles; _oldVisibles = old_visibles; } void ResolveVisibles() { for ( int i = 0, e = _visibles.RawList.Count; i < e; ++i ) { var iso_object = _visibles.RawList[i]; if ( iso_object.Internal.Dirty || !_oldVisibles.RawDict.ContainsKey(iso_object) ) { MarkDirty(); SetupIsoObjectDepends(iso_object); iso_object.Internal.Dirty = false; } if ( UpdateIsoObjectBounds3d(iso_object) ) { MarkDirty(); } } for ( int i = 0, e = _oldVisibles.RawList.Count; i < e; ++i ) { var iso_object = _oldVisibles.RawList[i]; if ( !_visibles.RawDict.ContainsKey(iso_object) ) { MarkDirty(); ClearIsoObjectDepends(iso_object); } } } void ClearIsoObjectDepends(IsoObject iso_object) { var their_depends_l = iso_object.Internal.TheirDepends.RawList; for ( int i = 0, e = their_depends_l.Count; i < e; ++i ) { var their_depend = their_depends_l[i]; if ( !their_depend.Internal.Dirty ) { their_depend.Internal.SelfDepends.Remove(iso_object); } } iso_object.Internal.SelfDepends.Clear(); iso_object.Internal.TheirDepends.Clear(); } void SetupIsoObjectDepends(IsoObject obj_a) { ClearIsoObjectDepends(obj_a); var min = obj_a.Internal.MinSector; var max = obj_a.Internal.MaxSector; for ( var y = min.y; y < max.y; ++y ) { for ( var x = min.x; x < max.x; ++x ) { var v = new Vector2(x, y); LookUpSectorDepends(v, obj_a); LookUpSectorRDepends(v, obj_a); }} } void PlaceAllVisibles() { var depth = startDepth; for ( int i = 0, e = _visibles.RawList.Count; i < e; ++i ) { depth = RecursivePlaceIsoObject(_visibles.RawList[i], depth); } } float RecursivePlaceIsoObject(IsoObject iso_object, float depth) { if ( iso_object.Internal.Placed ) { return depth; } iso_object.Internal.Placed = true; var self_depends_l = iso_object.Internal.SelfDepends.RawList; for ( int i = 0, e = self_depends_l.Count; i < e; ++i ) { depth = RecursivePlaceIsoObject(self_depends_l[i], depth); } if ( iso_object.mode == IsoObject.Mode.Mode3d ) { var zoffset = iso_object.Internal.Offset3d; var extents = iso_object.Internal.MinMax3d.size; PlaceIsoObject(iso_object, depth + extents * 0.5f + zoffset); return depth + extents + stepDepth; } else { PlaceIsoObject(iso_object, depth); return depth + stepDepth; } } void PlaceIsoObject(IsoObject iso_object, float depth) { var trans = iso_object.transform; trans.position = IsoUtils.Vec3ChangeZ(trans.position, depth); } // --------------------------------------------------------------------- // // Messages // // --------------------------------------------------------------------- void Start() { ChangeSortingProperty(); StepSort(); } void LateUpdate() { StepSort(); } void OnEnable() { var all_iso_objects = FindObjectsOfType(); _objects = new IsoAssocList(all_iso_objects.Length); for ( int i = 0, e = all_iso_objects.Length; i < e; ++i ) { var iso_object = all_iso_objects[i]; if ( iso_object.enabled ) { _objects.Add(iso_object); } } _visibles.Clear(); _oldVisibles.Clear(); _sectors.Clear(); MarkDirty(); } void OnDisable() { _objects.Clear(); _visibles.Clear(); _oldVisibles.Clear(); _sectors.Clear(); } #if UNITY_EDITOR void Reset() { tileSize = DefTileSize; tileRatio = DefTileRatio; tileAngle = DefTileAngle; tileHeight = DefTileHeight; stepDepth = DefStepDepth; startDepth = DefStartDepth; } void OnValidate() { tileSize = _tileSize; tileRatio = _tileRatio; tileAngle = _tileAngle; tileHeight = _tileHeight; stepDepth = _stepDepth; startDepth = _startDepth; } void OnRenderObject() { if ( Camera.current && Camera.current.name == "SceneCamera" ) { StepSort(); } } #endif } }