From 9501dc3a4da306e140ce46f9729a7f9e30010663 Mon Sep 17 00:00:00 2001 From: Marsette Vona Date: Tue, 1 Jun 2021 18:57:58 -0400 Subject: [PATCH 1/5] bugfix priority inversions where already loaded tiles might be lower priority than pending tiles; update stats collection --- .../Options/cache_1k_scene_options.json | 2 +- Assets/Examples/Scripts/DemoUX.cs | 2 +- Assets/Examples/Scripts/TilesetStatsHud.cs | 49 +++- .../Unity3DTiles/AbstractTilesetBehaviour.cs | 85 ++++-- Assets/Unity3DTiles/LRUCache.cs | 231 ---------------- Assets/Unity3DTiles/MultiTilesetBehaviour.cs | 17 +- Assets/Unity3DTiles/PriorityQueue.cs | 132 ---------- Assets/Unity3DTiles/PriorityQueue.cs.meta | 13 - Assets/Unity3DTiles/RequestManager.cs | 120 ++++++--- Assets/Unity3DTiles/TileCache.cs | 230 ++++++++++++++++ .../{LRUCache.cs.meta => TileCache.cs.meta} | 4 +- Assets/Unity3DTiles/TilesetBehaviour.cs | 5 + Assets/Unity3DTiles/Unity3DTile.cs | 249 ++++++++---------- Assets/Unity3DTiles/Unity3DTileContent.cs | 63 ++--- Assets/Unity3DTiles/Unity3DTileset.cs | 137 ++++------ Assets/Unity3DTiles/Unity3DTilesetOptions.cs | 8 +- .../Unity3DTiles/Unity3DTilesetStatistics.cs | 121 +++++---- .../Unity3DTiles/Unity3DTilesetTraversal.cs | 144 +++++----- README.md | 6 +- 19 files changed, 761 insertions(+), 857 deletions(-) delete mode 100644 Assets/Unity3DTiles/LRUCache.cs delete mode 100644 Assets/Unity3DTiles/PriorityQueue.cs delete mode 100644 Assets/Unity3DTiles/PriorityQueue.cs.meta create mode 100644 Assets/Unity3DTiles/TileCache.cs rename Assets/Unity3DTiles/{LRUCache.cs.meta => TileCache.cs.meta} (71%) diff --git a/Assets/Examples/Options/cache_1k_scene_options.json b/Assets/Examples/Options/cache_1k_scene_options.json index 98a1965..4d0a8e3 100644 --- a/Assets/Examples/Options/cache_1k_scene_options.json +++ b/Assets/Examples/Options/cache_1k_scene_options.json @@ -1,3 +1,3 @@ { - "LRUCacheMaxSize": 1000 + "CacheMaxSize": 1000 } diff --git a/Assets/Examples/Scripts/DemoUX.cs b/Assets/Examples/Scripts/DemoUX.cs index 776a063..b5d8a12 100644 --- a/Assets/Examples/Scripts/DemoUX.cs +++ b/Assets/Examples/Scripts/DemoUX.cs @@ -658,7 +658,7 @@ void drawBounds(Unity3DTile tile) { void drawBounds(Unity3DTile tile) { - if (tile.ContentActive) + if (tile.Content != null && tile.Content.IsActive) { tile.BoundingVolume.DebugDraw(Color.magenta, tile.Tileset.Behaviour.transform); } diff --git a/Assets/Examples/Scripts/TilesetStatsHud.cs b/Assets/Examples/Scripts/TilesetStatsHud.cs index 9eb02a5..c033ec7 100644 --- a/Assets/Examples/Scripts/TilesetStatsHud.cs +++ b/Assets/Examples/Scripts/TilesetStatsHud.cs @@ -86,25 +86,46 @@ public void Update () builder.AppendLine(); } #endif - builder.Append("active colliders: "); - builder.Append(stats.ColliderTileCount); + builder.Append(stats.NumberOfTilesTotal); + builder.Append(" total tiles, "); + builder.Append(stats.ReadyTiles); + builder.Append(" ready, "); + builder.Append(stats.ProcessingQueueLength); + builder.Append(" to process"); builder.AppendLine(); - builder.Append("used set: "); - builder.Append(stats.UsedSetCount); + + builder.Append(stats.UsedSet); + builder.Append(" used tiles, "); + builder.Append(stats.FrustumSet); + builder.Append(" in frustum, "); + builder.Append(stats.ColliderSet); + builder.Append(" with colliders"); builder.AppendLine(); - builder.Append("loaded tiles: "); - builder.Append(stats.LoadedContentCount); - builder.Append(", "); - builder.Append(stats.TilesLeftToLoad); - builder.Append(" remaining"); + + builder.Append("load progress "); + builder.Append((int)(stats.LoadProgress * 100)); + builder.Append("%, "); + builder.Append(stats.PendingTiles); + builder.Append(" pending"); builder.AppendLine(); + + builder.Append(stats.ActiveDownloads); + builder.Append(" active downloads, "); + builder.Append(stats.RequestQueueLength.ToString("d3")); + builder.Append(" queued"); + builder.AppendLine(); + builder.Append("cached tiles: "); - builder.Append(tileset.LRUCache.Count); - builder.Append(" / "); - builder.Append(tileset.SceneOptions.LRUCacheMaxSize); + builder.Append(stats.DownloadedTiles); + builder.Append("/"); + builder.Append(tileset.SceneOptions.CacheMaxSize); + builder.Append(", "); + builder.Append(tileset.TileCache.Unused); + builder.Append(" unused"); builder.AppendLine(); + builder.Append("visible tiles: "); - builder.Append(stats.VisibleTileCount); + builder.Append(stats.VisibleTiles); builder.Append(" (depth "); builder.Append(stats.MinVisibleTileDepth); builder.Append("-"); @@ -113,6 +134,7 @@ public void Update () builder.Append(tileset.DeepestDepth()); builder.Append(")"); builder.AppendLine(); + builder.Append("visible faces: "); builder.Append(stats.VisibleFaces / 1000); builder.Append(" k"); @@ -123,6 +145,7 @@ public void Update () builder.Append("visible megapixels: "); builder.Append((stats.VisiblePixels / 1000000f).ToString("0.00")); builder.AppendLine(); + if (!string.IsNullOrEmpty(ExtraMessage)) { builder.Append(ExtraMessage); diff --git a/Assets/Unity3DTiles/AbstractTilesetBehaviour.cs b/Assets/Unity3DTiles/AbstractTilesetBehaviour.cs index 3d5982b..0733caa 100644 --- a/Assets/Unity3DTiles/AbstractTilesetBehaviour.cs +++ b/Assets/Unity3DTiles/AbstractTilesetBehaviour.cs @@ -22,25 +22,19 @@ namespace Unity3DTiles { public abstract class AbstractTilesetBehaviour : MonoBehaviour { + //not readonly to show up in inspector public Unity3DTilesetSceneOptions SceneOptions = new Unity3DTilesetSceneOptions(); - public Unity3DTilesetStatistics Stats; + public Unity3DTilesetStatistics Stats; //not readonly to show up in inspector - public LRUCache LRUCache = new LRUCache(); - public Queue ProcessingQueue = new Queue(); + public RequestManager RequestManager { get; private set; } - private RequestManager _requestManager; - public RequestManager RequestManager - { - get - { - if (_requestManager == null) - { - _requestManager = new RequestManager(SceneOptions); - } - return _requestManager; - } - } + public TileCache TileCache { get; private set; } + + public readonly Queue ProcessingQueue = new Queue(); + + private bool unloadAssetsPending; + private AsyncOperation lastUnloadAssets; public abstract bool Ready(); @@ -50,32 +44,66 @@ public RequestManager RequestManager public abstract void ClearForcedTiles(); + public void RequestUnloadUnusedAssets() + { + unloadAssetsPending = true; + } + public void Update() { - this._update(); + _update(); } public void LateUpdate() { - LRUCache.MaxSize = SceneOptions.LRUCacheMaxSize; - LRUCache.MarkAllUnused(); - this._lateUpdate(); - this._requestManager?.Process(); - // Move any tiles with downloaded content to the ready state + TileCache.MarkAllUnused(); + + _lateUpdate(); + + int maxNewRequests = TileCache.HasMaxSize ? TileCache.MaxSize - TileCache.Count() : -1; + RequestManager.Process(maxNewRequests); + int processed = 0; - while (processed < this.SceneOptions.MaximumTilesToProcessPerFrame && this.ProcessingQueue.Count != 0) + while (processed < SceneOptions.MaximumTilesToProcessPerFrame && ProcessingQueue.Count != 0) { - var tile = this.ProcessingQueue.Dequeue(); - // We allow requests to terminate early if the (would be) tile goes out of view, so check if a tile is actually processed - if (tile.Process()) + var tile = ProcessingQueue.Dequeue(); + if (tile.Process()) //bake colliders, load indices, etc... { + // We allow requests to terminate early if the (would be) tile goes out of view, so check if a tile + // is actually processed processed++; } } - LRUCache.UnloadUnusedContent(SceneOptions.LRUCacheTargetSize, SceneOptions.LRUMaxFrameUnloadRatio, n => -n.Depth, t => t.UnloadContent()); + //if there are queued requests with higher priority than existing tiles but the TileCache cache is too full + //to allow them to load, unload a corresponding number of lowest-priority tiles + int ejected = 0; + if (maxNewRequests >= 0 && maxNewRequests < SceneOptions.MaxConcurrentRequests && + RequestManager.Count() > maxNewRequests) + { + var re = RequestManager.GetEnumerator(); //high priority (low value) first + ejected = TileCache.MarkLowPriorityUnused(t => //low priority (high value) first + re.MoveNext() && + t.FrameState.Priority > re.Current.FrameState.Priority, + SceneOptions.MaxConcurrentRequests - maxNewRequests); + } + + if (ejected > 0 || (TileCache.TargetSize > 0 && TileCache.Count() > TileCache.TargetSize)) + { + //this will trigger Resources.UnloadUnusedAssets() as needed + TileCache.UnloadUnusedContent(ejected); + } - this.UpdateStats(); + if (unloadAssetsPending) + { + if (lastUnloadAssets == null || lastUnloadAssets.isDone) + { + unloadAssetsPending = false; + lastUnloadAssets = Resources.UnloadUnusedAssets(); + } + } + + UpdateStats(); } protected virtual void UpdateStats() @@ -95,6 +123,9 @@ protected virtual void _lateUpdate() public void Start() { + RequestManager = new RequestManager(SceneOptions); + TileCache = new TileCache(SceneOptions); + if (SceneOptions.ClippingCameras.Count == 0) { SceneOptions.ClippingCameras.Add(Camera.main); diff --git a/Assets/Unity3DTiles/LRUCache.cs b/Assets/Unity3DTiles/LRUCache.cs deleted file mode 100644 index 2e38d55..0000000 --- a/Assets/Unity3DTiles/LRUCache.cs +++ /dev/null @@ -1,231 +0,0 @@ -/* - * Copyright 2018, by the California Institute of Technology. ALL RIGHTS - * RESERVED. United States Government Sponsorship acknowledged. Any - * commercial use must be negotiated with the Office of Technology - * Transfer at the California Institute of Technology. - * - * This software may be subject to U.S.export control laws.By accepting - * this software, the user agrees to comply with all applicable - * U.S.export laws and regulations. User has the responsibility to - * obtain export licenses, or other export authority as may be required - * before exporting such information to foreign countries or providing - * access to foreign persons. - */ -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using UnityEngine; - -namespace Unity3DTiles -{ - public enum CacheRequestStatus - { - ADDED, - DUPLICATE, - FULL - } - - /// - /// Represents a least recently used (LRU) cache - /// - public class LRUCache where T : class - { - public int MaxSize = -1; //unbounded - - // List looks like this - // [ unused, sentinal, used ] - - LinkedList list = new LinkedList(); - LinkedListNode sentinal = new LinkedListNode(null); - - Dictionary> nodeLookup = new Dictionary>(); - - /// - /// Number of items in cache O(1) - /// - public int Count - { - get { return list.Count - 1; } - } - - public bool HasMaxSize - { - get { return MaxSize > 0; } - } - - public bool Full - { - get { return HasMaxSize && Count >= MaxSize; } - } - - /// - /// Number of unused items in the cache O(N) where N is number of unused items - /// - public int Unused - { - get - { - int count = 0; - LinkedListNode node = this.sentinal; - while (node.Previous != null) - { - count++; - node = node.Previous; - } - return count; - } - } - - public int Used - { - get - { - int count = 0; - LinkedListNode node = this.sentinal; - while (node.Next != null) - { - count++; - node = node.Next; - } - return count; - } - } - - public LRUCache() - { - list.AddFirst(sentinal); - } - - /// - /// Adds a new element to the replacement list and marks it as used. Returns the node for that element - /// - /// - /// - public CacheRequestStatus Add(T element) - { - if (nodeLookup.ContainsKey(element)) - { - return CacheRequestStatus.DUPLICATE; - } - if (this.Full) - { - return CacheRequestStatus.FULL; - } - LinkedListNode node = new LinkedListNode(element); - nodeLookup.Add(element, node); - list.AddLast(node); - return CacheRequestStatus.ADDED; - } - - /// - /// Marks this node as used - /// - /// - public void MarkUsed(T element) - { - if (nodeLookup.ContainsKey(element)) - { - var node = nodeLookup[element]; - this.list.Remove(node); - this.list.AddLast(node); - } - } - - /// - /// Marks all nodes as unused - /// - public void MarkAllUnused() - { - this.list.Remove(sentinal); - this.list.AddLast(sentinal); - } - - /// - /// Removes the least recently used element and returns it - /// - /// - public T RemoveLeastRecentlyUsed() - { - if (this.list.First == this.sentinal) - { - return null; - } - T element = this.list.First.Value; - this.list.RemoveFirst(); - nodeLookup.Remove(element); - return element; - } - - /// - /// Removes all unused elements and returns them in order of least recently used to most recently used - /// - /// - public List RemoveUnused() - { - List list = new List(); - while (this.list.First != this.sentinal) - { - T element = this.list.First.Value; - nodeLookup.Remove(element); - this.list.RemoveFirst(); - list.Add(element); - } - return list; - } - - AsyncOperation lastUnloadAssets = null; - - /// - /// Unloads content from unused nodes - /// - public void UnloadUnusedContent(int targetSize, float unloadPercent, System.Func Priority, System.Action OnRemove) - { - if (this.Count > targetSize && this.Unused > 0) - { - List unused = this.GetUnused(); - var sortedUnused = unused.OrderBy(node => Priority(node)).ToArray(); - int nodesToUnload = (int)(targetSize * unloadPercent); - nodesToUnload = System.Math.Min(sortedUnused.Length, nodesToUnload); - for (int i = 0; i < nodesToUnload; i++) - { - Remove(sortedUnused[i]); - OnRemove?.Invoke(sortedUnused[i]); - } - if (lastUnloadAssets == null || lastUnloadAssets.isDone) - { - lastUnloadAssets = Resources.UnloadUnusedAssets(); - } //TODO: schedule unload instead of skip - } - } - - /// - /// Returns a list of unused nodes but does not remove them - /// - /// - public List GetUnused() - { - List result = new List(); - LinkedListNode curNode = this.list.First; - while (curNode != this.sentinal) - { - result.Add(curNode.Value); - curNode = curNode.Next; - } - return result; - } - - /// - /// Remove a specific node regardless of its state (used or unused) - /// - /// - public void Remove(T element) - { - if (nodeLookup.ContainsKey(element)) - { - var node = nodeLookup[element]; - nodeLookup.Remove(element); - this.list.Remove(node); - } - } - } -} diff --git a/Assets/Unity3DTiles/MultiTilesetBehaviour.cs b/Assets/Unity3DTiles/MultiTilesetBehaviour.cs index 224e2f3..85c9c12 100644 --- a/Assets/Unity3DTiles/MultiTilesetBehaviour.cs +++ b/Assets/Unity3DTiles/MultiTilesetBehaviour.cs @@ -78,23 +78,22 @@ protected override void _lateUpdate() { // Rotate processing order of tilesets each frame to avoid starvation (only upon request queue / cache full) startIndex = Mathf.Clamp(startIndex, 0, tilesets.Count - 1); - foreach (var t in tilesets.Skip(startIndex)) + for (int i = 0; i < tilesets.Count; i++) { - t.Update(); + tilesets[(i + startIndex) % tilesets.Count].Update(); } - foreach (var t in tilesets.Take(startIndex)) + if (tilesets.Count > 0) { - t.Update(); - } - startIndex++; - if (startIndex >= tilesets.Count) - { - startIndex = 0; + startIndex = (startIndex + 1) % tilesets.Count; } } protected override void UpdateStats() { + foreach (var tileset in tilesets) + { + tileset.UpdateStats(); + } Stats = Unity3DTilesetStatistics.Aggregate(tilesets.Select(t => t.Statistics).ToArray()); } diff --git a/Assets/Unity3DTiles/PriorityQueue.cs b/Assets/Unity3DTiles/PriorityQueue.cs deleted file mode 100644 index 5e3e8b1..0000000 --- a/Assets/Unity3DTiles/PriorityQueue.cs +++ /dev/null @@ -1,132 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using UnityEngine; - -namespace Unity3DTiles -{ - - public class PriorityQueueItem - { - readonly public T Data; - readonly public float Priority; - - public PriorityQueueItem(T data, float priority) - { - this.Data = data; - this.Priority = priority; - } - - } - - //https://visualstudiomagazine.com/Articles/2012/11/01/Priority-Queues-with-C.aspx - public class PriorityQueue - { - private List> data; - - public PriorityQueue() - { - this.data = new List>(); - } - - public void Enqueue(PriorityQueueItem item) - { - data.Add(item); - int ci = data.Count - 1; // child index; start at end - while (ci > 0) - { - int pi = (ci - 1) / 2; // parent index - if (data[ci].Priority - data[pi].Priority >= 0) - { - break; // child item is larger than (or equal) parent so we're done - } - PriorityQueueItem tmp = data[ci]; - data[ci] = data[pi]; - data[pi] = tmp; - ci = pi; - } - } - - public PriorityQueueItem Dequeue() - { - // assumes pq is not empty; up to calling code - int li = data.Count - 1; // last index (before removal) - PriorityQueueItem frontItem = data[0]; // fetch the front - data[0] = data[li]; - data.RemoveAt(li); - - --li; // last index (after removal) - int pi = 0; // parent index. start at front of pq - while (true) - { - int ci = pi * 2 + 1; // left child index of parent - if (ci > li) - { - break; // no children so done - } - int rc = ci + 1; // right child - if (rc <= li && data[rc].Priority - data[ci].Priority < 0) - { - // if there is a rc (ci + 1), and it is smaller than left child, use the rc instead - ci = rc; - } - if (data[pi].Priority - data[ci].Priority <= 0) - { - break; // parent is smaller than (or equal to) smallest child so done - } - PriorityQueueItem tmp = data[pi];// swap parent and child - data[pi] = data[ci]; - data[ci] = tmp; - pi = ci; - } - return frontItem; - } - - public PriorityQueueItem Peek() - { - PriorityQueueItem frontItem = data[0]; - return frontItem; - } - - public int Count() - { - return data.Count; - } - - public override string ToString() - { - string s = ""; - for (int i = 0; i < data.Count; ++i) - { - s += data[i].ToString() + " "; - } - s += "count = " + data.Count; - return s; - } - - public bool IsConsistent() - { - // is the heap property true for all data? - if (data.Count == 0) - { - return true; - } - int li = data.Count - 1; // last index - for (int pi = 0; pi < data.Count; ++pi) // each parent index - { - int lci = 2 * pi + 1; // left child index - int rci = 2 * pi + 2; // right child index - - if (lci <= li && data[pi].Priority - data[lci].Priority > 0) - { - return false; // if lc exists and it's greater than parent then bad. - } - if (rci <= li && data[pi].Priority - data[rci].Priority > 0) - { - return false; // check the right child too. - } - } - return true; // passed all checks - } - } -} \ No newline at end of file diff --git a/Assets/Unity3DTiles/PriorityQueue.cs.meta b/Assets/Unity3DTiles/PriorityQueue.cs.meta deleted file mode 100644 index 47051cc..0000000 --- a/Assets/Unity3DTiles/PriorityQueue.cs.meta +++ /dev/null @@ -1,13 +0,0 @@ -fileFormatVersion: 2 -guid: 2a4e7073da52ec748aae265f980ed468 -timeCreated: 1516656586 -licenseType: Pro -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Unity3DTiles/RequestManager.cs b/Assets/Unity3DTiles/RequestManager.cs index 1f0353f..9997649 100644 --- a/Assets/Unity3DTiles/RequestManager.cs +++ b/Assets/Unity3DTiles/RequestManager.cs @@ -11,6 +11,8 @@ * before exporting such information to foreign countries or providing * access to foreign persons. */ +using System; +using System.Linq; using System.Collections; using System.Collections.Generic; using UnityEngine; @@ -20,25 +22,28 @@ namespace Unity3DTiles { - public class Request : PriorityQueueItem + public class Request { + readonly public Unity3DTile Tile; readonly public Promise Started; readonly public Promise Finished; - public Request(Unity3DTile tile, float priority, Promise started, Promise finished) : base(tile, priority) + public Request(Unity3DTile tile, Promise started, Promise finished) { + Tile = tile; Started = started; Finished = finished; } } - public class RequestManager + public class RequestManager : IEnumerable { - private Unity3DTilesetSceneOptions sceneOptions; + private readonly Unity3DTilesetSceneOptions sceneOptions; - private int currentRequests; - - private PriorityQueue priorityQueue = new PriorityQueue(); + private Queue queue = new Queue(); + private HashSet activeDownloads = new HashSet(); + private List tmpList = new List(); + private HashSet tmpSet = new HashSet(); private float lastGC = -1; private bool paused; @@ -85,6 +90,36 @@ private set } } + public int Count(Func predicate = null) + { + if (predicate == null) + { + return queue.Count; + } + int num = 0; + foreach (var request in queue) + { + if (predicate(request.Tile)) + { + num++; + } + } + return num; + } + + public IEnumerator GetEnumerator() + { + foreach (var request in queue) + { + yield return request.Tile; + } + } + + IEnumerator IEnumerable.GetEnumerator() + { + return this.GetEnumerator(); + } + public string GetStatus() { var sb = new StringBuilder(); @@ -96,8 +131,8 @@ public string GetStatus() } //sb.Append($"\npause mem threshold: {FmtKMG(sceneOptions.PauseMemThreshold)}"); //sb.Append($"\nGC mem threshold: {FmtKMG(sceneOptions.GCMemThreshold)}"); - sb.Append($"\ntile requests: {currentRequests}/{sceneOptions.MaxConcurrentRequests} active, "); - sb.Append($"{priorityQueue.Count(),3}/{MAX_QUEUE_SIZE} queued" + (paused ? " (paused)" : "")); + sb.Append($"\ntile requests: {activeDownloads.Count}/{sceneOptions.MaxConcurrentRequests} active, "); + sb.Append(queue.Count.ToString("d3") + $"/{MAX_QUEUE_SIZE} queued" + (paused ? " (paused)" : "")); return sb.ToString(); } @@ -106,27 +141,22 @@ public RequestManager(Unity3DTilesetSceneOptions sceneOptions) this.sceneOptions = sceneOptions; } - public int RequestsInProgress() - { - return currentRequests; - } - - public int QueueSize() + public int CountActiveDownloads(Func predicate = null) { - return priorityQueue.Count(); - } - - public bool Full() - { - return priorityQueue.Count() >= MAX_QUEUE_SIZE; + if (predicate == null) + { + return activeDownloads.Count; + } + return activeDownloads.Count(predicate); } public void EnqueRequest(Request request) { - priorityQueue.Enqueue(request); + request.Tile.ContentState = Unity3DTileContentState.LOADING; + queue.Enqueue(request); } - public void Process() + public void Process(int maxNewRequests) { if (sceneOptions.GCMemThreshold > 0 && UsedMem > sceneOptions.GCMemThreshold && (lastGC < 0 || (Time.realtimeSinceStartup - lastGC) > MIN_GC_PERIOD_SEC)) { @@ -134,20 +164,50 @@ public void Process() GC(); lastGC = Time.realtimeSinceStartup; } + if (sceneOptions.PauseMemThreshold > 0 && UsedMem > sceneOptions.PauseMemThreshold) { paused = true; return; } paused = false; - while(currentRequests < sceneOptions.MaxConcurrentRequests && priorityQueue.Count() > 0) + + //re-sort queue from high to low priority (high priority = low value) + //because priorities may change from frame to frame + //also cull any duplicates, drop any requests that are now offscreen (priority = MaxValue) + //and drop any requests that exceed MAX_QUEUE_SIZE + tmpList.Clear(); + tmpSet.Clear(); + tmpList.AddRange(queue); + queue.Clear(); + tmpList.Sort((x, y) => (int)Mathf.Sign(x.Tile.FrameState.Priority - y.Tile.FrameState.Priority)); + tmpSet.Clear(); + for (int i = 0; i < tmpList.Count; i++) { - var curItem = (Request) priorityQueue.Dequeue(); - currentRequests++; - curItem.Finished.Then((success) => + if (queue.Count < MAX_QUEUE_SIZE && tmpList[i].Tile.FrameState.Priority < float.MaxValue) + { + if (!tmpSet.Contains(tmpList[i].Tile)) + { + queue.Enqueue(tmpList[i]); + tmpSet.Add(tmpList[i].Tile); + } + } + else { - currentRequests--; - }); - curItem.Started.Resolve(); + tmpList[i].Tile.ContentState = Unity3DTileContentState.UNLOADED; + } + } + tmpList.Clear(); + tmpSet.Clear(); + + int newRequests = 0; + while (activeDownloads.Count < sceneOptions.MaxConcurrentRequests && queue.Count > 0 && + (maxNewRequests < 0 || newRequests < maxNewRequests)) + { + var request = queue.Dequeue(); + activeDownloads.Add(request.Tile); + newRequests++; + request.Finished.Then((success) => { activeDownloads.Remove(request.Tile); }); + request.Started.Resolve(); } } diff --git a/Assets/Unity3DTiles/TileCache.cs b/Assets/Unity3DTiles/TileCache.cs new file mode 100644 index 0000000..900e031 --- /dev/null +++ b/Assets/Unity3DTiles/TileCache.cs @@ -0,0 +1,230 @@ +/* + * Copyright 2018, by the California Institute of Technology. ALL RIGHTS + * RESERVED. United States Government Sponsorship acknowledged. Any + * commercial use must be negotiated with the Office of Technology + * Transfer at the California Institute of Technology. + * + * This software may be subject to U.S.export control laws.By accepting + * this software, the user agrees to comply with all applicable + * U.S.export laws and regulations. User has the responsibility to + * obtain export licenses, or other export authority as may be required + * before exporting such information to foreign countries or providing + * access to foreign persons. + */ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; + +namespace Unity3DTiles +{ + public class TileCache + { + // List looks like this + // [ unused, sentinel, used ] + + private readonly Unity3DTilesetSceneOptions sceneOptions; + + private readonly LinkedList list = new LinkedList(); + + private readonly LinkedListNode sentinel = new LinkedListNode(null); + + private readonly Dictionary> nodeLookup = + new Dictionary>(); + + private readonly List tmpList = new List(); + + public int Count(Func predicate = null) + { + if (predicate == null) + { + return list.Count - 1; + } + return list.Count(t => t != null && predicate(t)); + } + + public bool HasMaxSize + { + get { return sceneOptions.CacheMaxSize > 0; } + } + + public int MaxSize + { + get { return sceneOptions.CacheMaxSize; } + } + + public int TargetSize + { + get { return sceneOptions.CacheTargetSize; } + } + + public bool Full + { + get { return HasMaxSize && Count() >= MaxSize; } + } + + public int Unused + { + get + { + int count = 0; + var node = sentinel; + while (node.Previous != null) + { + count++; + node = node.Previous; + } + return count; + } + } + + public int Used + { + get + { + int count = 0; + var node = sentinel; + while (node.Next != null) + { + count++; + node = node.Next; + } + return count; + } + } + + public TileCache(Unity3DTilesetSceneOptions sceneOptions) + { + this.sceneOptions = sceneOptions; + list.AddFirst(sentinel); + } + + public bool Add(Unity3DTile tile, out bool duplicate) + { + duplicate = false; + if (nodeLookup.ContainsKey(tile)) + { + duplicate = true; + return false; + } + if (Full) + { + MarkLowPriorityUnused(t => t.FrameState.Priority > tile.FrameState.Priority, 1); + if (UnloadUnusedContent(1, 1) == 0) + { + return false; + } + } + var node = new LinkedListNode(tile); + nodeLookup.Add(tile, node); + list.AddLast(node); + return true; + } + + public bool Remove(Unity3DTile tile) + { + if (!nodeLookup.ContainsKey(tile)) + { + return false; + } + var node = nodeLookup[tile]; + nodeLookup.Remove(tile); + list.Remove(node); + tile.UnloadContent(); + return true; + } + + public void MarkUsed(Unity3DTile tile) + { + if (nodeLookup.ContainsKey(tile)) + { + var node = nodeLookup[tile]; + list.Remove(node); + list.AddLast(node); + } + } + + public void MarkUnused(Unity3DTile tile) + { + if (nodeLookup.ContainsKey(tile)) + { + var node = nodeLookup[tile]; + list.Remove(node); + list.AddFirst(node); + } + } + + public int MarkLowPriorityUnused(Func predicate, int max = -1) + { + //collect used nodes in tmpList and sort low priority first (higher value = lower priority) + tmpList.Clear(); + var node = sentinel; + while (node.Next != null) + { + node = node.Next; + tmpList.Add(node.Value); + } + tmpList.Sort((x, y) => (int)Mathf.Sign(y.FrameState.Priority - x.FrameState.Priority)); + + //mark up to max unused, from lowest to highest priority, as long as predicate holds + int num = 0; + for (int i = 0; i < tmpList.Count && (max < 0 || i < max) && predicate(tmpList[i]); i++) + { + MarkUnused(tmpList[i]); + num++; + } + tmpList.Clear(); + return num; + } + + public void MarkAllUnused() + { + list.Remove(sentinel); + list.AddLast(sentinel); + } + + public int UnloadUnusedContent(int minNum = 0, int maxNum = -1) + { + //collect unused nodes in tmpList and sort low priority first (higher value = lower priority) + tmpList.Clear(); + var node = list.First; + while (node != sentinel) + { + tmpList.Add(node.Value); + node = node.Next; + } + tmpList.Sort((x, y) => (int)Mathf.Sign(y.FrameState.Priority - x.FrameState.Priority)); + + //apply rules to determine how many to unload + float maxRatio = Mathf.Max(0.01f, Mathf.Min(1, sceneOptions.MaxCacheUnloadRatio)); + int target = TargetSize; + if (target <= 0 || (MaxSize > 0 && target > MaxSize)) + { + target = MaxSize; + } + if (target <= 0 || target > Count()) + { + target = Count(); + } + int num = (int)Mathf.Ceil(target * maxRatio); + if (minNum > 0 && num < minNum) + { + num = minNum; + } + if (maxNum > 0 && num > maxNum) + { + num = maxNum; + } + num = Math.Min(tmpList.Count, num); + + //remove and unload nodes from lowest to highest priority + for (int i = 0; i < num; i++) + { + Remove(tmpList[i]); + } + tmpList.Clear(); + return num; + } + } +} diff --git a/Assets/Unity3DTiles/LRUCache.cs.meta b/Assets/Unity3DTiles/TileCache.cs.meta similarity index 71% rename from Assets/Unity3DTiles/LRUCache.cs.meta rename to Assets/Unity3DTiles/TileCache.cs.meta index 39d845f..c19f1db 100644 --- a/Assets/Unity3DTiles/LRUCache.cs.meta +++ b/Assets/Unity3DTiles/TileCache.cs.meta @@ -1,7 +1,5 @@ fileFormatVersion: 2 -guid: 7e6e5c498ffaeae4cab98ddbf47629ae -timeCreated: 1513302602 -licenseType: Pro +guid: 97c826d42976f194a86cd467909e17e6 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Assets/Unity3DTiles/TilesetBehaviour.cs b/Assets/Unity3DTiles/TilesetBehaviour.cs index 8c1d421..0618504 100644 --- a/Assets/Unity3DTiles/TilesetBehaviour.cs +++ b/Assets/Unity3DTiles/TilesetBehaviour.cs @@ -70,5 +70,10 @@ protected override void _lateUpdate() Tileset.Update(); } } + + protected override void UpdateStats() + { + Tileset.UpdateStats(); + } } } diff --git a/Assets/Unity3DTiles/Unity3DTile.cs b/Assets/Unity3DTiles/Unity3DTile.cs index aa21ae2..c320932 100644 --- a/Assets/Unity3DTiles/Unity3DTile.cs +++ b/Assets/Unity3DTiles/Unity3DTile.cs @@ -26,9 +26,7 @@ public enum Unity3DTileContentState { UNLOADED = 0, // Has never been requested LOADING = 1, // Is waiting on a pending request PROCESSING = 2, // Request received. Contents are being processed for rendering. Depending on the content, it might make its own requests for external data. - READY = 3, // Ready to render. - EXPIRED = 4, // Is expired and will be unloaded once new content is loaded. - FAILED = 5 // Request failed. + READY = 3 // Ready to render. }; public enum Unity3DTileContentType @@ -58,33 +56,35 @@ public class Unity3DTile public Unity3DTile Parent; public int Depth = 0; public string Id { get; private set; } - public Schema.Tile tile; + public Schema.Tile schemaTile; public Unity3DTilesetStyle Style; public float GeometricError { - get { return (float)tile.GeometricError; } + get { return (float)schemaTile.GeometricError; } } public Schema.TileRefine Refine { - get { return tile.Refine.Value; } + get { return schemaTile.Refine.Value; } } public bool HasEmptyContent { get { - return tile.Content == null; + return schemaTile.Content == null; } } + public string ContentUrl { get; private set; } + public Unity3DTileContentType ContentType { get { - string ext = Path.GetExtension(UrlUtils.RemoveQuery(this.ContentUrl)).ToLower(); + string ext = Path.GetExtension(UrlUtils.RemoveQuery(ContentUrl)).ToLower(); if(ext.Equals(".b3dm")) { return Unity3DTileContentType.B3DM; @@ -103,23 +103,7 @@ public Unity3DTileContentType ContentType public Unity3DTileContent Content { get; private set; } - public Unity3DTileContentState ContentState { get; private set; } - - public bool ContentActive { get { return Content != null && Content.IsActive; } } - - public string ContentUrl { get; private set; } - - public Promise ContentReadyToProcessPromise { get; private set; } - - public Promise ContentReadyPromise { get; private set; } - - public Unity3DTileContent ExpiredContent { get; private set; } - - public bool HasRenderableContent { get; private set; } - - public bool HasTilesetContent { get; private set; } - - public TileFrameState FrameState { get; private set; } + public Unity3DTileContentState ContentState; private int hashCode; @@ -137,6 +121,8 @@ public class TileFrameState public float PixelsToCameraCenter = float.MaxValue; public float ScreenSpaceError = 0; + public float Priority = float.MaxValue; // lower value means higher priority + public void MarkUsed() { InUsedSet = true; @@ -161,93 +147,94 @@ public void Reset(int frameCount) InColliderSet = false; DistanceToCamera = float.MaxValue; PixelsToCameraCenter = float.MaxValue; + ScreenSpaceError = 0; + Priority = float.MaxValue; } } + public TileFrameState FrameState = new TileFrameState(); + public void MarkUsed() { - // Mark as used in frame state - this.FrameState.MarkUsed(); - // If this node has content, it will also be tracked in the LRUContent datastructure. Mark it as used - // so it's content won't be selected for unloading - this.Tileset.LRUContent.MarkUsed(this); + FrameState.MarkUsed(); + Tileset.TileCache.MarkUsed(this); } - public Unity3DTile(Unity3DTileset tileset, string basePath, Schema.Tile tile, Unity3DTile parent) + public Unity3DTile(Unity3DTileset tileset, string basePath, Schema.Tile schemaTile, Unity3DTile parent) { - this.hashCode = (int)UnityEngine.Random.Range(0, int.MaxValue); - this.Tileset = tileset; - this.tile = tile; - this.FrameState = new TileFrameState(); - if (tile.Content != null) + hashCode = (int)UnityEngine.Random.Range(0, int.MaxValue); + Tileset = tileset; + this.schemaTile = schemaTile; + if (schemaTile.Content != null) { - this.Id = Path.GetFileNameWithoutExtension(tile.Content.GetUri()); + Id = Path.GetFileNameWithoutExtension(schemaTile.Content.GetUri()); } if (parent != null) { parent.Children.Add(this); - this.Depth = parent.Depth + 1; + Depth = parent.Depth + 1; } // TODO: Consider using a double percision Matrix library for doing 3d tiles root transform calculations // Set the local transform for this tile, default to identity matrix - this.transform = this.tile.UnityTransform(); + transform = schemaTile.UnityTransform(); var parentTransform = (parent != null) ? parent.computedTransform : tileset.GetRootTransform(); - this.computedTransform = parentTransform * this.transform; + computedTransform = parentTransform * transform; - this.BoundingVolume = CreateBoundingVolume(tile.BoundingVolume, this.computedTransform); + BoundingVolume = CreateBoundingVolume(schemaTile.BoundingVolume, computedTransform); // TODO: Add 2D bounding volumes - if (tile.Content != null && tile.Content.BoundingVolume.IsDefined()) + if (schemaTile.Content != null && schemaTile.Content.BoundingVolume.IsDefined()) { // Non-leaf tiles may have a content bounding-volume, which is a tight-fit bounding volume // around only the features in the tile. This box is useful for culling for rendering, // but not for culling for traversing the tree since it does not guarantee spatial coherence, i.e., // since it only bounds features in the tile, not the entire tile, children may be // outside of this box. - this.ContentBoundingVolume = CreateBoundingVolume(tile.Content.BoundingVolume, this.computedTransform); + ContentBoundingVolume = CreateBoundingVolume(schemaTile.Content.BoundingVolume, computedTransform); } else { // Default to tile bounding volume - this.ContentBoundingVolume = CreateBoundingVolume(tile.BoundingVolume, this.computedTransform); + ContentBoundingVolume = CreateBoundingVolume(schemaTile.BoundingVolume, computedTransform); } // TODO: Add viewer request volume support - //if(tile.ViewerRequestVolume != null && tile.ViewerRequestVolume.IsDefined()) + //if(schemaTile.ViewerRequestVolume != null && schemaTile.ViewerRequestVolume.IsDefined()) //{ - // this.viewerRequestVolume = CreateBoundingVolume(tile.ViewerRequestVolume, transform); + // viewerRequestVolume = CreateBoundingVolume(schemaTile.ViewerRequestVolume, transform); //} - if(!tile.Refine.HasValue) + if (!schemaTile.Refine.HasValue) { - tile.Refine = (parent == null) ? Schema.TileRefine.REPLACE : parent.tile.Refine.Value; + schemaTile.Refine = (parent == null) ? Schema.TileRefine.REPLACE : parent.schemaTile.Refine.Value; } - this.Parent = parent; + Parent = parent; - if(this.HasEmptyContent) + if (HasEmptyContent) { - this.ContentState = Unity3DTileContentState.READY; + ContentState = Unity3DTileContentState.READY; } else { ContentState = Unity3DTileContentState.UNLOADED; - this.ContentUrl = UrlUtils.JoinUrls(basePath, tile.Content.GetUri()); + ContentUrl = UrlUtils.JoinUrls(basePath, schemaTile.Content.GetUri()); } - - this.HasRenderableContent = false; - this.HasTilesetContent = false; } public bool Process() { - if (this.ContentState == Unity3DTileContentState.PROCESSING) + if (ContentState == Unity3DTileContentState.PROCESSING) { - this.ContentState = Unity3DTileContentState.READY; - this.Content.Initialize(this.Tileset.TilesetOptions.CreateColliders); + ContentState = Unity3DTileContentState.READY; + + Content.SetShadowMode(Tileset.TilesetOptions.ShadowCastingMode, + Tileset.TilesetOptions.RecieveShadows); - var indexMode = this.Tileset.TilesetOptions.LoadIndices; + Content.Initialize(Tileset.TilesetOptions.CreateColliders); + + var indexMode = Tileset.TilesetOptions.LoadIndices; if (indexMode != IndexMode.Default && indexMode != IndexMode.None) { Action fail = (mode, url, msg) => @@ -258,14 +245,13 @@ public bool Process() if (Unity3DTileIndex.EnableLoadWarnings) { #pragma warning disable 0162 - Debug.LogWarning("failed to load " + mode + " index for " + this.ContentUrl + ": " + msg); + Debug.LogWarning("failed to load " + mode + " index for " + ContentUrl + ": " + msg); #pragma warning restore 0162 } }; - Action success = index => { this.Content.Index = index; }; - this.Tileset.Behaviour - .StartCoroutine(Unity3DTileIndex.Load(indexMode, this.ContentUrl, success, fail)); + Action success = index => { Content.Index = index; }; + Tileset.Behaviour.StartCoroutine(Unity3DTileIndex.Load(indexMode, ContentUrl, success, fail)); } return true; @@ -279,102 +265,83 @@ public bool Process() /// public void RequestContent(float priority) { - if (this.Tileset.RequestManager.Full()) + if (HasEmptyContent || ContentState != Unity3DTileContentState.UNLOADED) { return; } - if(this.HasEmptyContent) + + Promise finished = new Promise(); + finished.Then((success) => { - return; - } - if (this.ContentState == Unity3DTileContentState.UNLOADED || - this.ContentState == Unity3DTileContentState.EXPIRED) + Tileset.Statistics.RequestsThisFrame++; + Tileset.Statistics.NetworkErrorsThisFrame += success ? 0 : 1; + bool duplicate = false; + if (success && Tileset.TileCache.Add(this, out duplicate)) + { + ContentState = Unity3DTileContentState.PROCESSING; + Tileset.ProcessingQueue.Enqueue(this); + } + else if (!duplicate) + { + UnloadContent(); + } + }); + + Promise started = new Promise(); + started.Then(() => { - this.ContentState = Unity3DTileContentState.LOADING; + GameObject go = new GameObject(Id); + go.transform.parent = Tileset.Behaviour.transform; + go.transform.localPosition = + new Vector3(computedTransform.m03, computedTransform.m13, computedTransform.m23); + go.transform.localRotation = computedTransform.rotation; + go.transform.localScale = computedTransform.lossyScale; + go.layer = Tileset.Behaviour.gameObject.layer; + go.SetActive(false); + var info = go.AddComponent(); + info.Tile = this; + Content = new Unity3DTileContent(go); - Promise finished = new Promise(); - finished.Then((success) => + if (ContentType == Unity3DTileContentType.B3DM) { - this.Tileset.Statistics.NetworkError = !success; - this.Tileset.Statistics.RequestsThisFrame += 1; - if (success) - { - this.ContentState = Unity3DTileContentState.PROCESSING; - this.Content.SetShadowMode(this.Tileset.TilesetOptions.ShadowCastingMode, this.Tileset.TilesetOptions.RecieveShadows); - this.Tileset.Statistics.LoadedContentCount += 1; - this.Tileset.Statistics.TotalTilesLoaded += 1; - // Track tile in cache as soon as it has downloaded content, but still queue it for processing - CacheRequestStatus status = this.Tileset.LRUContent.Add(this); - if (status == CacheRequestStatus.ADDED) - { - this.Tileset.ProcessingQueue.Enqueue(this); - } - else - { - UnloadContent(); - } - } - else + B3DMComponent b3dmCo = go.AddComponent(); + b3dmCo.Url = ContentUrl; + b3dmCo.Multithreaded = Tileset.TilesetOptions.GLTFMultithreadedLoad; + b3dmCo.MaximumLod = Tileset.TilesetOptions.GLTFMaximumLOD; + if (!string.IsNullOrEmpty(Tileset.TilesetOptions.ShaderOverride)) { - UnloadContent(); + b3dmCo.ShaderOverride = Shader.Find(Tileset.TilesetOptions.ShaderOverride); } - }); - - Promise started = new Promise(); - started.Then(() => + b3dmCo.AddColliders = false; + b3dmCo.DownloadOnStart = false; + Tileset.Behaviour.StartCoroutine(b3dmCo.Download(finished)); + } + else if (ContentType == Unity3DTileContentType.PNTS) { - GameObject go = new GameObject(Id); - go.transform.parent = this.Tileset.Behaviour.transform; - go.transform.localPosition = new Vector3(this.computedTransform.m03, this.computedTransform.m13, this.computedTransform.m23); - go.transform.localRotation = this.computedTransform.rotation; - go.transform.localScale = this.computedTransform.lossyScale; - go.layer = this.Tileset.Behaviour.gameObject.layer; - go.SetActive(false); - var info = go.AddComponent(); - info.Tile = this; - this.Content = new Unity3DTileContent(go); - - if (ContentType == Unity3DTileContentType.B3DM) - { - B3DMComponent b3dmCo = go.AddComponent(); - b3dmCo.Url = this.ContentUrl; - b3dmCo.Multithreaded = this.Tileset.TilesetOptions.GLTFMultithreadedLoad; - b3dmCo.MaximumLod = this.Tileset.TilesetOptions.GLTFMaximumLOD; - if (!string.IsNullOrEmpty(this.Tileset.TilesetOptions.ShaderOverride)) - { - b3dmCo.ShaderOverride = Shader.Find(this.Tileset.TilesetOptions.ShaderOverride); - } - b3dmCo.AddColliders = false; - b3dmCo.DownloadOnStart = false; - this.Tileset.Behaviour.StartCoroutine(b3dmCo.Download(finished)); - } - else if (ContentType == Unity3DTileContentType.PNTS) - { - PNTSComponent pntsCo = go.AddComponent(); - pntsCo.Url = UrlUtils.RemoveQuery(this.ContentUrl); - pntsCo.ShaderOverride = Shader.Find("Point Cloud/Point"); - pntsCo.DownloadOnStart = false; - this.Tileset.Behaviour.StartCoroutine(pntsCo.Download(finished)); - } + PNTSComponent pntsCo = go.AddComponent(); + pntsCo.Url = UrlUtils.RemoveQuery(ContentUrl); + pntsCo.ShaderOverride = Shader.Find("Point Cloud/Point"); + pntsCo.DownloadOnStart = false; + Tileset.Behaviour.StartCoroutine(pntsCo.Download(finished)); + } + + }); - }); - Request request = new Request(this, priority, started, finished); - this.Tileset.RequestManager.EnqueRequest(request); - } + Tileset.RequestManager.EnqueRequest(new Request(this, started, finished)); } public void UnloadContent() { - if (this.HasEmptyContent) + if (HasEmptyContent) { return; } - this.ContentState = Unity3DTileContentState.UNLOADED; - if (this.Content != null && this.Content.Go != null) + ContentState = Unity3DTileContentState.UNLOADED; + if (Content != null && Content.Go != null) { - this.Tileset.Statistics.LoadedContentCount -= 1; - GameObject.Destroy(this.Content.Go); - this.Content = null; + GameObject.Destroy(Content.Go); + Content = null; + Tileset.Behaviour.RequestUnloadUnusedAssets(); } } @@ -421,7 +388,7 @@ Unity3DTileBoundingVolume CreateBoundingVolume(Schema.BoundingVolume boundingVol public override int GetHashCode() { - return this.hashCode; + return hashCode; } } } diff --git a/Assets/Unity3DTiles/Unity3DTileContent.cs b/Assets/Unity3DTiles/Unity3DTileContent.cs index 9e62aca..534d6f9 100644 --- a/Assets/Unity3DTiles/Unity3DTileContent.cs +++ b/Assets/Unity3DTiles/Unity3DTileContent.cs @@ -74,10 +74,10 @@ public void Initialize(bool createColliders) } // Need to toggle active and then inactive to bake collider data // We do this here so that we can control the number of bakes per frame - // by limiting the number of times we call initialize per frame when processing new tiles - // otherwise we can have lots of colliders bake in one frame if many tiles become active for the first time - this.Go.SetActive(true); - this.Go.SetActive(false); + // otherwise we can have lots of colliders bake in one frame if many tiles become + // active for the first time + Go.SetActive(true); + Go.SetActive(false); } for (int i = 0; i < meshFilters.Length; i++) @@ -85,14 +85,14 @@ public void Initialize(bool createColliders) var m = meshFilters[i].sharedMesh; if (m.GetTopology(0) == MeshTopology.Triangles) { - this.FaceCount += m.triangles.Length / 3; + FaceCount += m.triangles.Length / 3; } } int maxPixels = 0; - for (int i = 0; i < this.renderers.Length; i++) + for (int i = 0; i < renderers.Length; i++) { - var r = this.renderers[i]; + var r = renderers[i]; for(int j = 0; j < r.materials.Length; j++) { if (r.materials[j].HasProperty("_MainTex")) @@ -103,17 +103,17 @@ public void Initialize(bool createColliders) int pixels = t.width * t.height; if (pixels > maxPixels) { - this.MaxTextureSize = new Vector2Int(t.width, t.height); + MaxTextureSize = new Vector2Int(t.width, t.height); } - this.PixelCount += pixels; - this.TextureCount += 1; + PixelCount += pixels; + TextureCount += 1; } } } } - this.colliders = this.Go.GetComponentsInChildren(); - this.collidersEnabled = true; - this.renderersEnabled = true; + colliders = Go.GetComponentsInChildren(); + collidersEnabled = true; + renderersEnabled = true; } /// @@ -123,9 +123,9 @@ public void Initialize(bool createColliders) /// public void SetActive(bool active) { - if(active != this.Go.activeSelf) + if (active != Go.activeSelf) { - this.Go.SetActive(active); + Go.SetActive(active); } } @@ -136,14 +136,14 @@ public void SetActive(bool active) /// public void EnableColliders(bool enabled) { - if (enabled != this.collidersEnabled) + if (enabled != collidersEnabled) { - this.collidersEnabled = enabled; - if (this.colliders != null) + collidersEnabled = enabled; + if (colliders != null) { - for (int i = 0; i < this.colliders.Length; i++) + for (int i = 0; i < colliders.Length; i++) { - this.colliders[i].enabled = enabled; + colliders[i].enabled = enabled; } } } @@ -156,14 +156,14 @@ public void EnableColliders(bool enabled) /// public void EnableRenderers(bool enabled) { - if (enabled != this.renderersEnabled) + if (enabled != renderersEnabled) { - this.renderersEnabled = enabled; - if (this.renderers != null) + renderersEnabled = enabled; + if (renderers != null) { - for (int i = 0; i < this.renderers.Length; i++) + for (int i = 0; i < renderers.Length; i++) { - this.renderers[i].enabled = enabled; + renderers[i].enabled = enabled; } } } @@ -171,16 +171,17 @@ public void EnableRenderers(bool enabled) public void SetShadowMode(ShadowCastingMode shadowMode, bool recieveShadows) { - if (!this.shadowMode.HasValue || !this.recieveShadows.HasValue || this.shadowMode.Value != shadowMode || this.recieveShadows.Value != recieveShadows) + if (!this.shadowMode.HasValue || !this.recieveShadows.HasValue || + this.shadowMode.Value != shadowMode || this.recieveShadows.Value != recieveShadows) { this.shadowMode = shadowMode; this.recieveShadows = recieveShadows; - if (this.renderers != null) + if (renderers != null) { - for (int i = 0; i < this.renderers.Length; i++) + for (int i = 0; i < renderers.Length; i++) { - this.renderers[i].receiveShadows = recieveShadows; - this.renderers[i].shadowCastingMode = shadowMode; + renderers[i].receiveShadows = recieveShadows; + renderers[i].shadowCastingMode = shadowMode; } } } @@ -188,7 +189,7 @@ public void SetShadowMode(ShadowCastingMode shadowMode, bool recieveShadows) public MeshRenderer[] GetRenderers() { - return this.renderers; + return renderers; } } } diff --git a/Assets/Unity3DTiles/Unity3DTileset.cs b/Assets/Unity3DTiles/Unity3DTileset.cs index 6883730..e043715 100644 --- a/Assets/Unity3DTiles/Unity3DTileset.cs +++ b/Assets/Unity3DTiles/Unity3DTileset.cs @@ -13,6 +13,7 @@ */ using System; +using System.Linq; using System.IO; using System.Collections; using System.Collections.Generic; @@ -31,58 +32,31 @@ namespace Unity3DTiles /// public class Unity3DTileset { - public Unity3DTilesetOptions TilesetOptions { private set; get; } + public Unity3DTilesetOptions TilesetOptions { get; private set;} + public Unity3DTile Root { get; private set; } - private string basePath; - private string tilesetUrl; - private int previousTilesRemaining = 0; + public TileCache TileCache { get; private set; } - private Schema.Tileset tileset; - public Queue ProcessingQueue; // Tiles whose content is being loaded/processed + public RequestManager RequestManager { get; private set;} - public MonoBehaviour Behaviour { get; private set; } + public Queue ProcessingQueue { get; private set; } - /// - /// Maintians a least recently used list of tiles that have content - /// - public LRUCache LRUContent { get; private set; } + public AbstractTilesetBehaviour Behaviour { get; private set; } /// - /// The deepest depth of the tree as specified by the loaded json structure. This may increase as recursive json - /// tilesets are loaded. This value does not depend on what renderable content has been loaded. + /// The deepest depth of the tree as specified by the loaded json structure. This may increase as + /// recursive json tilesets are loaded. This value does not depend on what renderable content has been loaded. /// public int DeepestDepth { get; private set; } - public Unity3DTilesetStatistics Statistics = new Unity3DTilesetStatistics(); + public readonly Unity3DTilesetStatistics Statistics = new Unity3DTilesetStatistics(); - public Unity3DTilesetTraversal Traversal; - private Promise readyPromise = new Promise(); + public Unity3DTilesetTraversal Traversal { get; private set; } - public delegate void LoadProgressDelegate(int tilesRemaining); - public event LoadProgressDelegate LoadProgress; + public bool Ready { get { return Root != null; } } - public delegate void AllTilesLoadedDelegate(bool loaded); - public event AllTilesLoadedDelegate AllTilesLoaded; - - public delegate void TileDelegate(Unity3DTile tile); - - public bool Ready { get { return this.Root != null; } } - - public RequestManager RequestManager { get; private set;} - - private DateTime loadTimestamp; - - /// - /// Time since tileset was loaded in seconds - /// - public double TimeSinceLoad - { - get - { - return Math.Max((DateTime.UtcNow - this.loadTimestamp).TotalSeconds, 0); - } - } + private Schema.Tileset schemaTileset; public void GetRootTransform(out Vector3 translation, out Quaternion rotation, out Vector3 scale, bool convertToUnityFrame = true) @@ -105,33 +79,26 @@ public Matrix4x4 GetRootTransform(bool convertToUnityFrame = true) public Unity3DTileset(Unity3DTilesetOptions tilesetOptions, AbstractTilesetBehaviour behaviour) { - this.TilesetOptions = tilesetOptions; - this.Behaviour = behaviour; - this.RequestManager = behaviour.RequestManager; - this.ProcessingQueue = behaviour.ProcessingQueue; - this.LRUContent = behaviour.LRUCache; - this.Traversal = new Unity3DTilesetTraversal(this, behaviour.SceneOptions); - this.DeepestDepth = 0; + TilesetOptions = tilesetOptions; + Behaviour = behaviour; + RequestManager = behaviour.RequestManager; + ProcessingQueue = behaviour.ProcessingQueue; + TileCache = behaviour.TileCache; + Traversal = new Unity3DTilesetTraversal(this, behaviour.SceneOptions); + DeepestDepth = 0; string url = UrlUtils.ReplaceDataProtocol(tilesetOptions.Url); - - if (UrlUtils.GetLastPathSegment(url).EndsWith(".json", StringComparison.OrdinalIgnoreCase)) - { - this.basePath = UrlUtils.GetBaseUri(url); - this.tilesetUrl = url; - } - else + string tilesetUrl = url; + if (!UrlUtils.GetLastPathSegment(url).EndsWith(".json", StringComparison.OrdinalIgnoreCase)) { - this.basePath = url; - this.tilesetUrl = UrlUtils.JoinUrls(url, "tileset.json"); + tilesetUrl = UrlUtils.JoinUrls(url, "tileset.json"); } - LoadTilesetJson(this.tilesetUrl).Then(json => + LoadTilesetJson(tilesetUrl).Then(json => { // Load Tileset (main tileset or a reference tileset) - this.tileset = Schema.Tileset.FromJson(json); - this.Root = LoadTileset(this.tilesetUrl, this.tileset, null); - this.readyPromise.Resolve(this); + schemaTileset = Schema.Tileset.FromJson(json); + Root = LoadTileset(tilesetUrl, schemaTileset, null); }).Catch(error => { Debug.LogError(error.Message + "\n" + error.StackTrace); @@ -141,7 +108,7 @@ public Unity3DTileset(Unity3DTilesetOptions tilesetOptions, AbstractTilesetBehav public Promise LoadTilesetJson(string url) { Promise promise = new Promise(); - this.Behaviour.StartCoroutine(DownloadTilesetJson(url, promise)); + Behaviour.StartCoroutine(DownloadTilesetJson(url, promise)); return promise; } @@ -186,8 +153,6 @@ private Unity3DTile LoadTileset(string tilesetUrl, Schema.Tileset tileset, Unity version = tileset.Asset.TilesetVersion; } string versionQuery = "v=" + version; - - this.basePath = UrlUtils.SetQuery(this.basePath, versionQuery); tilesetUrl = UrlUtils.SetQuery(tilesetUrl, versionQuery); } // A tileset.json referenced from a tile may exist in a different directory than the root tileset. @@ -202,52 +167,40 @@ private Unity3DTile LoadTileset(string tilesetUrl, Schema.Tileset tileset, Unity while (stack.Count > 0) { Unity3DTile tile3D = stack.Pop(); - for (int i = 0; i < tile3D.tile.Children.Count; i++) + for (int i = 0; i < tile3D.schemaTile.Children.Count; i++) { - Unity3DTile child = new Unity3DTile(this, basePath, tile3D.tile.Children[i], tile3D); - this.DeepestDepth = Math.Max(child.Depth, this.DeepestDepth); + Unity3DTile child = new Unity3DTile(this, basePath, tile3D.schemaTile.Children[i], tile3D); + DeepestDepth = Math.Max(child.Depth, DeepestDepth); Statistics.NumberOfTilesTotal++; stack.Push(child); } // TODO consider using CullWithChildrenBounds optimization here } - this.loadTimestamp = DateTime.UtcNow; return rootTile; } public void Update() { - if(!this.Ready) - { - return; - } Statistics.Clear(); - Traversal.Run(); - Statistics.RequestQueueLength = this.RequestManager.QueueSize(); - Statistics.ConcurrentRequests = this.RequestManager.RequestsInProgress(); - Statistics.ProcessingTiles = this.ProcessingQueue.Count; - int remaining = Statistics.RequestQueueLength + Statistics.ConcurrentRequests + Statistics.ProcessingTiles; - Statistics.TilesLeftToLoad = remaining; - if (AllTilesLoaded != null) + if (Ready) { - if (previousTilesRemaining != 0 && remaining == 0) - { - AllTilesLoaded(true); - } - if(previousTilesRemaining == 0 && remaining != 0) + Traversal.Run(); + if (TilesetOptions.DebugDrawBounds) { - AllTilesLoaded(false); + Traversal.DrawDebug(); } } - if (LoadProgress != null && remaining != previousTilesRemaining) - { - LoadProgress(remaining); - } - previousTilesRemaining = remaining; - if (this.TilesetOptions.DebugDrawBounds) - { - Traversal.DrawDebug(); - } + } + + public void UpdateStats() + { + Statistics.RequestQueueLength = RequestManager.Count(t => t.Tileset == this); + Statistics.ActiveDownloads = RequestManager.CountActiveDownloads(t => t.Tileset == this); + Statistics.ProcessingQueueLength = ProcessingQueue.Count(t => t.Tileset == this); + Statistics.DownloadedTiles = TileCache.Count(t => t.Tileset == this); + Statistics.ReadyTiles = + TileCache.Count(t => t.Tileset == this && t.ContentState == Unity3DTileContentState.READY); + Unity3DTilesetStatistics.MaxLoadedTiles = TileCache.MaxSize; } } } diff --git a/Assets/Unity3DTiles/Unity3DTilesetOptions.cs b/Assets/Unity3DTiles/Unity3DTilesetOptions.cs index 84d38f9..d486b8d 100644 --- a/Assets/Unity3DTiles/Unity3DTilesetOptions.cs +++ b/Assets/Unity3DTiles/Unity3DTilesetOptions.cs @@ -105,13 +105,13 @@ public class Unity3DTilesetSceneOptions public int MaximumTilesToProcessPerFrame = 1; [Tooltip("Sets the target maximum number of tiles that can be loaded into memory at any given time. Beyond this limit, unused tiles will be unloaded as new requests are made.")] - public int LRUCacheTargetSize = 600; + public int CacheTargetSize = 600; [Tooltip("Sets the maximum number of tiles (hard limit) that can be loaded into memory at any given time. Requests that would exceed this limit fail.")] - public int LRUCacheMaxSize = 1000; + public int CacheMaxSize = 1000; - [Tooltip("Controls the maximum number of unused tiles that will be unloaded at a time when the cache is full. This is specified as a ratio of the LRUMaxCacheSize. For example, if this is set to 0.2 and LRUMaxCacheSize is 600 then at most we will unload 120 (0.2*600) tiles in a single frame.")] - public float LRUMaxFrameUnloadRatio = 0.2f; + [Tooltip("Controls the maximum number of unused tiles that will be unloaded at a time when the cache is full. This is specified as a ratio of the MaxCacheSize. For example, if this is set to 0.2 and MaxCacheSize is 600 then at most we will unload 120 (0.2*600) tiles in a single frame.")] + public float MaxCacheUnloadRatio = 0.2f; [Tooltip("Manages how many downloads can occurs simultaneously. Larger results in faster load times but this should be tuned for the particular platform you are deploying to.")] public int MaxConcurrentRequests = 6; diff --git a/Assets/Unity3DTiles/Unity3DTilesetStatistics.cs b/Assets/Unity3DTiles/Unity3DTilesetStatistics.cs index 18af4d5..83f53bc 100644 --- a/Assets/Unity3DTiles/Unity3DTilesetStatistics.cs +++ b/Assets/Unity3DTiles/Unity3DTilesetStatistics.cs @@ -21,45 +21,47 @@ namespace Unity3DTiles public class Unity3DTilesetStatistics { [Header("Frame statistics")] - public int FrustumSetCount; - public int UsedSetCount; - public int VisibleTileCount; - public int ColliderTileCount; + public int UsedSet; + public int FrustumSet; + public int ColliderSet; + + public int VisibleTiles; public int VisibleFaces; public int VisibleTextures; public int VisiblePixels; - public int MinVisibleTileDepth; - public int MaxVisibleTileDepth; + public int MinVisibleTileDepth = -1; + public int MaxVisibleTileDepth = -1; + public int RequestsThisFrame; // Number of download requests processed this frame + public int NetworkErrorsThisFrame; // Number of network errors this frame + [Header("Dataset statistics")] - public int NumberOfTilesTotal; // Number of tiles in tileset.json (and other tileset.json files as they are loaded) - public int LoadedContentCount; // Number of tiles with loaded content - public int ProcessingTiles; // Number of tiles still being processed - public int RequestQueueLength; // Number of tiles waiting to be downloaded - public int ConcurrentRequests; // Number of tiles being downloaded - public int TotalTilesLoaded; // Number of tiles downloaded since start - public int TilesLeftToLoad; // Number of tiles left to load for the current viewpoint - public int LeafContentRequired;// Number of leaf tiles with content (loaded or not loaded) - public int LeafContentLoaded; // Number of leaf tiles with content that has been loaded - public int RequestsThisFrame; // Number of requests processed this frame - public bool NetworkError; // Indicates if the most recent network request succeeded or failed - - public float LeafLoadProgress + public int NumberOfTilesTotal; // #tiles in tileset(s) + + public int RequestQueueLength; // #tiles waiting to be downloaded + public int ActiveDownloads; // #tiles being downloaded + public int ProcessingQueueLength; // #tiles waiting to be processed + + public int DownloadedTiles; // #tiles with downloaded content (may still be processing) + public int ReadyTiles; // #tiles loaded and processed + + public static int MaxLoadedTiles; // maximum #tiles that can be loaded at a time + + //was TilesLeftToLoad + public int PendingTiles // #tiles waiting to be downloaded, being downloaded, or waiting to process { - get - { - if(LeafContentRequired == 0) - { - return 0; - } - return LeafContentLoaded / (float)LeafContentRequired; - } + get { return RequestQueueLength + ActiveDownloads + ProcessingQueueLength; } + } + + public float LoadProgress + { + get { return Mathf.Clamp01( ReadyTiles / (float)Mathf.Min(UsedSet, MaxLoadedTiles)); } } public void TallyVisibleTile(Unity3DTile tile) { - VisibleTileCount += 1; + VisibleTiles += 1; if (tile.Content != null) { VisibleFaces += tile.Content.FaceCount; @@ -75,59 +77,66 @@ public static Unity3DTilesetStatistics Aggregate(params Unity3DTilesetStatistics Unity3DTilesetStatistics ret = new Unity3DTilesetStatistics(); ret.Clear(); - if(stats.Length == 0) + if (stats.Length == 0) { return ret; } - ret.NumberOfTilesTotal = 0; - ret.LoadedContentCount = 0; - ret.RequestQueueLength = stats[0].RequestQueueLength; //Cache informed statistics shared between tilesets, do not sum - ret.ConcurrentRequests = stats[0].ConcurrentRequests; - ret.ProcessingTiles = stats[0].ProcessingTiles; //Single queue for all tilesets - ret.TilesLeftToLoad = ret.RequestQueueLength + ret.ConcurrentRequests + ret.ProcessingTiles; - ret.TotalTilesLoaded = 0; - foreach (var stat in stats) { - ret.FrustumSetCount += stat.FrustumSetCount; - ret.UsedSetCount += stat.UsedSetCount; - ret.VisibleTileCount += stat.VisibleTileCount; - ret.ColliderTileCount += stat.ColliderTileCount; + ret.UsedSet += stat.UsedSet; + ret.FrustumSet += stat.FrustumSet; + ret.VisibleTiles += stat.VisibleTiles; + ret.ColliderSet += stat.ColliderSet; + ret.VisibleFaces += stat.VisibleFaces; ret.VisibleTextures += stat.VisibleTextures; ret.VisiblePixels += stat.VisiblePixels; ret.MinVisibleTileDepth = MinPositive(ret.MinVisibleTileDepth, stat.MinVisibleTileDepth); ret.MaxVisibleTileDepth = MaxPositive(ret.MaxVisibleTileDepth, stat.MaxVisibleTileDepth); - - ret.NumberOfTilesTotal += stat.NumberOfTilesTotal; - ret.LoadedContentCount += stat.LoadedContentCount; - ret.TotalTilesLoaded += stat.TotalTilesLoaded; - ret.LeafContentRequired += stat.LeafContentRequired; - ret.LeafContentLoaded += stat.LeafContentLoaded; + ret.RequestsThisFrame += stat.RequestsThisFrame; - ret.NetworkError = ret.NetworkError || stat.NetworkError; + ret.NetworkErrorsThisFrame += stat.NetworkErrorsThisFrame; + + ret.NumberOfTilesTotal += stat.NumberOfTilesTotal; + + ret.RequestQueueLength += stat.RequestQueueLength; + ret.ActiveDownloads += stat.ActiveDownloads; + ret.ProcessingQueueLength += stat.ProcessingQueueLength; + + ret.DownloadedTiles += stat.DownloadedTiles; + ret.ReadyTiles += stat.ReadyTiles; } - + return ret; } public void Clear() { - FrustumSetCount = 0; - UsedSetCount = 0; - VisibleTileCount = 0; - ColliderTileCount = 0; + UsedSet = 0; + FrustumSet = 0; + ColliderSet = 0; + + VisibleTiles = 0; VisibleFaces = 0; VisibleTextures = 0; VisiblePixels = 0; + MinVisibleTileDepth = -1; MaxVisibleTileDepth = -1; - LeafContentRequired = 0; - LeafContentLoaded = 0; + RequestsThisFrame = 0; - NetworkError = false; + NetworkErrorsThisFrame = 0; + + //NumberOfTilesTotal = 0; + + RequestQueueLength = 0; + ActiveDownloads = 0; + ProcessingQueueLength = 0; + + DownloadedTiles = 0; + ReadyTiles = 0; } private static int MinPositive(int a, int b) diff --git a/Assets/Unity3DTiles/Unity3DTilesetTraversal.cs b/Assets/Unity3DTiles/Unity3DTilesetTraversal.cs index 96e4f03..30c4a66 100644 --- a/Assets/Unity3DTiles/Unity3DTilesetTraversal.cs +++ b/Assets/Unity3DTiles/Unity3DTilesetTraversal.cs @@ -14,6 +14,7 @@ using System.Collections; using System.Collections.Generic; using UnityEngine; +using UnityEngine.Rendering; using System.Linq; using System; @@ -51,11 +52,11 @@ public void Run() tile.FrameState.Reset(frameCount); tile.MarkUsed(); tile.FrameState.InFrustumSet = true; - tileset.Statistics.FrustumSetCount += 1; + tileset.Statistics.FrustumSet++; } } - SSECalculator sse = new SSECalculator(this.tileset); + SSECalculator sse = new SSECalculator(tileset); foreach (Camera cam in sceneOptions.ClippingCameras) { if (cam == null) @@ -73,11 +74,13 @@ public void Run() DetermineFrustumSet(tileset.Root, planes, sse, cameraPositionInTileset, cameraForwardInTileset, PlaneClipMask.GetDefaultMask()); } + AssignPrioritiesRecursively(tileset.Root); MarkUsedSetLeaves(tileset.Root); SkipTraversal(tileset.Root); ToggleTiles(tileset.Root); - //this.tileset.RequestManager.Process(); - //UnloadUnusedContent called once for all tilesets at end of AbstractTilesetBehaviour.LateUpdate() + //tileset.RequestManager.Process(); + //RequestManager.Process() and UnloadUnusedContent() + //called once for all tilesets at end of AbstractTilesetBehaviour.LateUpdate() } class SSECalculator @@ -116,7 +119,7 @@ public float PixelError(float tileError, float distFromCamera) public float ProjectDistanceOnTileToScreen(float distOnTile, float distFromCamera) { - if (this.cam.orthographic) + if (cam.orthographic) { return distOnTile / pixelSize; } @@ -153,7 +156,7 @@ bool DetermineFrustumSet(Unity3DTile tile, Plane[] planes, SSECalculator sse, Ve Vector3 cameraFwdInTilesetFrame, PlaneClipMask mask) { // Reset frame state if needed - tile.FrameState.Reset(this.frameCount); + tile.FrameState.Reset(frameCount); // Check to see if we are in the fustrum mask = tile.BoundingVolume.IntersectPlanes(planes, mask); if (mask.Intersection == IntersectionType.OUTSIDE) @@ -163,7 +166,7 @@ bool DetermineFrustumSet(Unity3DTile tile, Plane[] planes, SSECalculator sse, Ve // We are in frustum and at a rendereable level of detail, mark as used and as visible tile.MarkUsed(); // mark content as used in LRUContent so it won't be unloaded tile.FrameState.InFrustumSet = true; - this.tileset.Statistics.FrustumSetCount += 1; + tileset.Statistics.FrustumSet++; // Skip screen space error check if this node has empty content, // we need to keep recursing until we find a node with content regardless of error if (!tile.HasEmptyContent) @@ -198,7 +201,7 @@ bool DetermineFrustumSet(Unity3DTile tile, Plane[] planes, SSECalculator sse, Ve anyChildUsed = anyChildUsed || r; } // If any children are in the workingset, mark all of them as being used (siblings/atomic split criteria). - if (anyChildUsed && this.tileset.TilesetOptions.LoadSiblings) + if (anyChildUsed && tileset.TilesetOptions.LoadSiblings) { for (int i = 0; i < tile.Children.Count; i++) { @@ -218,7 +221,7 @@ void MarkUsedRecursively(Unity3DTile tile) { // We need to reset as we go in case we find tiles that weren't previously explored // If they have already been reset this frame this has no effect - tile.FrameState.Reset(this.frameCount); + tile.FrameState.Reset(frameCount); tile.MarkUsed(); if(tile.HasEmptyContent) { @@ -238,29 +241,21 @@ void MarkUsedRecursively(Unity3DTile tile) void MarkUsedSetLeaves(Unity3DTile tile) { // A used leaf is a node that is used but has no children in the used set - if (!tile.FrameState.IsUsedThisFrame(this.frameCount)) + if (!tile.FrameState.IsUsedThisFrame(frameCount)) { // Not used this frame, can't be a used leaf and neither can anything beneath us return; } - this.tileset.Statistics.UsedSetCount += 1; + tileset.Statistics.UsedSet++; // If any child is used, then we are not a leaf bool anyChildrenUsed = false; for (int i = 0; i < tile.Children.Count; i++) { - anyChildrenUsed = anyChildrenUsed || tile.Children[i].FrameState.IsUsedThisFrame(this.frameCount); + anyChildrenUsed = anyChildrenUsed || tile.Children[i].FrameState.IsUsedThisFrame(frameCount); } if (!anyChildrenUsed || ForceTiles.Contains(tile)) { tile.FrameState.IsUsedSetLeaf = true; - if (!tile.HasEmptyContent) - { - this.tileset.Statistics.LeafContentRequired++; - if(tile.ContentState == Unity3DTileContentState.READY) - { - this.tileset.Statistics.LeafContentLoaded++; - } - } } else { @@ -271,15 +266,6 @@ void MarkUsedSetLeaves(Unity3DTile tile) } } - private bool CanRequest - { - get - { - return this.tileset.LRUContent.HasMaxSize && - this.tileset.LRUContent.Count + this.tileset.ProcessingQueue.Count + this.tileset.RequestManager.QueueSize() < this.tileset.LRUContent.MaxSize; - } - } - /// /// Traverse the tree, request tiles, and enable visible tiles /// Skip parent tiles that have a screen space error larger than MaximumScreenSpaceError*SkipScreenSpaceErrorMultiplier @@ -287,7 +273,7 @@ private bool CanRequest /// void SkipTraversal(Unity3DTile tile) { - if (!tile.FrameState.IsUsedThisFrame(this.frameCount)) + if (!tile.FrameState.IsUsedThisFrame(frameCount)) { return; } @@ -301,9 +287,9 @@ void SkipTraversal(Unity3DTile tile) tileset.Statistics.TallyVisibleTile(tile); } tile.FrameState.InColliderSet = true; - this.tileset.Statistics.ColliderTileCount += 1; + tileset.Statistics.ColliderSet++; } - else if (this.CanRequest) + else { RequestTile(tile); } @@ -319,13 +305,13 @@ void SkipTraversal(Unity3DTile tile) bool allChildrenHaveContent = true; for (int i = 0; i < tile.Children.Count; i++) { - if (tile.Children[i].FrameState.IsUsedThisFrame(this.frameCount)) + if (tile.Children[i].FrameState.IsUsedThisFrame(frameCount)) { bool childContent = tile.Children[i].ContentState == Unity3DTileContentState.READY || tile.HasEmptyContent; allChildrenHaveContent = allChildrenHaveContent && childContent; } } - if(meetsSSE && !hasContent && this.CanRequest) + if(meetsSSE && !hasContent) { RequestTile(tile); } @@ -337,11 +323,11 @@ void SkipTraversal(Unity3DTile tile) tileset.Statistics.TallyVisibleTile(tile); } tile.FrameState.InColliderSet = true; - this.tileset.Statistics.ColliderTileCount += 1; + tileset.Statistics.ColliderSet++; // Request children for (int i = 0; i < tile.Children.Count; i++) { - if (tile.Children[i].FrameState.IsUsedThisFrame(this.frameCount) && this.CanRequest) + if (tile.Children[i].FrameState.IsUsedThisFrame(frameCount)) { RequestTile(tile.Children[i]); } @@ -351,7 +337,7 @@ void SkipTraversal(Unity3DTile tile) // Otherwise keep decending for (int i = 0; i < tile.Children.Count; i++) { - if (tile.Children[i].FrameState.IsUsedThisFrame(this.frameCount)) + if (tile.Children[i].FrameState.IsUsedThisFrame(frameCount)) { SkipTraversal(tile.Children[i]); } @@ -361,10 +347,10 @@ void SkipTraversal(Unity3DTile tile) void ToggleTiles(Unity3DTile tile) { // Only consider tiles that were used this frame or the previous frame - if (tile.FrameState.IsUsedThisFrame(this.frameCount) || tile.FrameState.UsedLastFrame) + if (tile.FrameState.IsUsedThisFrame(frameCount) || tile.FrameState.UsedLastFrame) { tile.FrameState.UsedLastFrame = false; - if (!tile.FrameState.IsUsedThisFrame(this.frameCount)) + if (!tile.FrameState.IsUsedThisFrame(frameCount)) { // This tile was active last frame but isn't active any more if (tile.Content != null) @@ -379,20 +365,22 @@ void ToggleTiles(Unity3DTile tile) { tile.Content.SetActive(tile.FrameState.InColliderSet || tile.FrameState.InRenderSet); tile.Content.EnableColliders(tile.FrameState.InColliderSet); - if (this.tileset.TilesetOptions.Show) + if (tileset.TilesetOptions.Show) { if (tile.FrameState.InRenderSet) { tile.Content.EnableRenderers(true); - tile.Content.SetShadowMode(this.tileset.TilesetOptions.ShadowCastingMode, this.tileset.TilesetOptions.RecieveShadows); - if (this.tileset.TilesetOptions.Style != null) + tile.Content.SetShadowMode(tileset.TilesetOptions.ShadowCastingMode, + tileset.TilesetOptions.RecieveShadows); + if (tileset.TilesetOptions.Style != null) { tileset.TilesetOptions.Style.ApplyStyle(tile); } } - else if (tile.FrameState.InColliderSet && this.tileset.TilesetOptions.ShadowCastingMode != UnityEngine.Rendering.ShadowCastingMode.Off) + else if (tile.FrameState.InColliderSet && + tileset.TilesetOptions.ShadowCastingMode != ShadowCastingMode.Off) { - tile.Content.SetShadowMode(UnityEngine.Rendering.ShadowCastingMode.ShadowsOnly, false); + tile.Content.SetShadowMode(ShadowCastingMode.ShadowsOnly, false); } } } @@ -407,8 +395,8 @@ void ToggleTiles(Unity3DTile tile) int DepthFromFirstUsedAncestor(Unity3DTile tile) { - if (!tile.FrameState.IsUsedThisFrame(this.frameCount) || - tile.Parent == null || !tile.Parent.FrameState.IsUsedThisFrame(this.frameCount)) + if (!tile.FrameState.IsUsedThisFrame(frameCount) || + tile.Parent == null || !tile.Parent.FrameState.IsUsedThisFrame(frameCount)) { return 0; } @@ -418,8 +406,8 @@ int DepthFromFirstUsedAncestor(Unity3DTile tile) float MinUsedAncestorPixelsToCameraCenter(Unity3DTile tile, float pixels = float.MaxValue) { - if (!tile.FrameState.IsUsedThisFrame(this.frameCount) || - tile.Parent == null || !tile.Parent.FrameState.IsUsedThisFrame(this.frameCount)) + if (!tile.FrameState.IsUsedThisFrame(frameCount) || + tile.Parent == null || !tile.Parent.FrameState.IsUsedThisFrame(frameCount)) { return pixels; } @@ -429,9 +417,14 @@ float MinUsedAncestorPixelsToCameraCenter(Unity3DTile tile, float pixels = float float TilePriority(Unity3DTile tile) { - if (this.tileset.TilesetOptions.TilePriority != null) + if (tileset.TilesetOptions.TilePriority != null) + { + return tileset.TilesetOptions.TilePriority(tile); + } + + if (ForceTiles.Contains(tile)) { - return this.tileset.TilesetOptions.TilePriority(tile); + return 0; } //prioritize by distance from camera @@ -461,34 +454,45 @@ float TilePriority(Unity3DTile tile) return quantizedDist + relDepth; } + /// - /// Request a tile - /// If the tile has siblings in the used set, request them at the same time since we will need all of them to split the parent tile - /// Set request priority based on the closest sibling, request all siblings with the same priority since we need all of them to load before - /// any of them can be made visible + /// If the tile has siblings in the used set, request them at the same time since we will need all of + /// them to split the parent tile. + /// Set request priority based on the closest sibling, request all siblings with + /// the same priority since we need all of them to load before any of them can be made visible. /// - /// + void AssignPrioritiesRecursively(Unity3DTile tile) + { + tile.FrameState.Priority = float.MaxValue; + if (tile.FrameState.IsUsedThisFrame(frameCount) && !tile.HasEmptyContent) + { + tile.FrameState.Priority = TilePriority(tile); + } + float minChildPriority = float.MaxValue; + foreach (var child in tile.Children) + { + AssignPrioritiesRecursively(child); + minChildPriority = Mathf.Min(minChildPriority, child.FrameState.Priority); + } + foreach (var child in tile.Children) + { + child.FrameState.Priority = minChildPriority; + } + } + void RequestTile(Unity3DTile tile) { if (tile.Parent == null) { - tile.RequestContent(TilePriority(tile)); + tile.RequestContent(tile.FrameState.Priority); } else { - float priority = float.MaxValue; - for (int i = 0; i < tile.Parent.Children.Count; i++) - { - if (tile.Parent.Children[i].FrameState.IsUsedThisFrame(this.frameCount)) - { - priority = Mathf.Min(TilePriority(tile.Parent.Children[i]), priority); - } - } for (int i = 0; i < tile.Parent.Children.Count; i++) { - if (tile.Parent.Children[i].FrameState.IsUsedThisFrame(this.frameCount)) + if (tile.Parent.Children[i].FrameState.IsUsedThisFrame(frameCount)) { - tile.Parent.Children[i].RequestContent(priority); + tile.Parent.Children[i].RequestContent(tile.FrameState.Priority); } } } @@ -502,11 +506,11 @@ public void DrawDebug() void DebugDrawUsedSet(Unity3DTile tile) { - if (tile.FrameState.IsUsedThisFrame(this.frameCount)) + if (tile.FrameState.IsUsedThisFrame(frameCount)) { if (tile.FrameState.IsUsedSetLeaf) { - tile.BoundingVolume.DebugDraw(Color.white, this.tileset.Behaviour.transform); + tile.BoundingVolume.DebugDraw(Color.white, tileset.Behaviour.transform); } for (int i = 0; i < tile.Children.Count; i++) { @@ -517,11 +521,11 @@ void DebugDrawUsedSet(Unity3DTile tile) void DebugDrawFrustumSet(Unity3DTile tile) { - if (tile.FrameState.IsUsedThisFrame(this.frameCount) && tile.FrameState.InFrustumSet) + if (tile.FrameState.IsUsedThisFrame(frameCount) && tile.FrameState.InFrustumSet) { if (tile.FrameState.IsUsedSetLeaf) { - tile.BoundingVolume.DebugDraw(Color.green, this.tileset.Behaviour.transform); + tile.BoundingVolume.DebugDraw(Color.green, tileset.Behaviour.transform); } for (int i = 0; i < tile.Children.Count; i++) { diff --git a/README.md b/README.md index facbd15..59136c5 100644 --- a/README.md +++ b/README.md @@ -64,9 +64,9 @@ To get started, simply add a TilesetBehaviour to an empty game object. You can #### Scene Options * Maximum Tiles To Process Per Frame - Loading large numbers of tiles per frame can decrease load times but introduce choppy performance (especially on the web) when many tiles are being loaded at once. -* LRU Cache Target Size - Controls the total number of tiles that will be kept in memory at any given time. The cache can exceed this size if more tiles are need to render the current viewpoint. Setting this too low will mean tiles are constantly re-requested and impact performance when changing views. Setting this too high will result in more memory being used which can cause crashes on platforms such as the web or hololens. -* LRU Cache Max Size - Sets the target maximum number of tiles that can be loaded into memory at any given time. Beyond this limit, unused tiles will be unloaded as new requests are made. -* LRU Max Frame Unload Ratio - Controls the number of tiles that will be unloaded per frame when the cache becomes too full. Setting too high can cause choppy performance. Setting too low will slow the loading of new tiles as they will wait for old tiles to be unloaded. +* Cache Target Size - Controls the total number of tiles that will be kept in memory at any given time. The cache can exceed this size if more tiles are need to render the current viewpoint. Setting this too low will mean tiles are constantly re-requested and impact performance when changing views. Setting this too high will result in more memory being used which can cause crashes on platforms such as the web or hololens. +* Cache Max Size - Sets the target maximum number of tiles that can be loaded into memory at any given time. Beyond this limit, unused tiles will be unloaded as new requests are made. +* Max Cache Unload Ratio - Controls the number of tiles that will be unloaded per frame when the cache becomes too full. Setting too high can cause choppy performance. Setting too low will slow the loading of new tiles as they will wait for old tiles to be unloaded. * Max Concurrent Requests - Manages how many downloads can occurs simultaneously. Larger results in faster load times but this should be tuned for the particular platform you are deploying to. * Clipping cameras - the set of cameras that should be used to determine which tiles to load. Typically this will just be the main camera (and that is the default if not specified). Adding more cameras will decrease performance. * GLTF Shader Override - Overrides shader override of individual tilesets. From 7b78449ada9cb662b9cabe11b95f180366873b7d Mon Sep 17 00:00:00 2001 From: Marsette Vona Date: Tue, 1 Jun 2021 21:02:25 -0400 Subject: [PATCH 2/5] fix perf regressions --- .../Unity3DTiles/AbstractTilesetBehaviour.cs | 21 ++ Assets/Unity3DTiles/MultiTilesetBehaviour.cs | 19 +- Assets/Unity3DTiles/RequestManager.cs | 50 +++-- Assets/Unity3DTiles/TileCache.cs | 33 ++- Assets/Unity3DTiles/Unity3DTile.cs | 13 +- .../Unity3DTiles/Unity3DTilesetTraversal.cs | 209 ++++++++++-------- 6 files changed, 221 insertions(+), 124 deletions(-) diff --git a/Assets/Unity3DTiles/AbstractTilesetBehaviour.cs b/Assets/Unity3DTiles/AbstractTilesetBehaviour.cs index 0733caa..4b4bfd8 100644 --- a/Assets/Unity3DTiles/AbstractTilesetBehaviour.cs +++ b/Assets/Unity3DTiles/AbstractTilesetBehaviour.cs @@ -60,6 +60,10 @@ public void LateUpdate() _lateUpdate(); + RequestManager.ForEachQueuedDownload(DerateUnusedTilePriority); + RequestManager.ForEachActiveDownload(DerateUnusedTilePriority); + TileCache.ForEach(DerateUnusedTilePriority); + int maxNewRequests = TileCache.HasMaxSize ? TileCache.MaxSize - TileCache.Count() : -1; RequestManager.Process(maxNewRequests); @@ -106,6 +110,23 @@ public void LateUpdate() UpdateStats(); } + private void DerateUnusedTilePriority(Unity3DTile tile) + { + if (!tile.FrameState.IsUsedThisFrame && !tile.FrameState.UsedLastFrame) + { + if (tile.FrameState.LastVisitedFrame < 0) + { + tile.FrameState.Priority = float.MaxValue; + } + else + { + float maxFrames = 1000; + float framesSinceUsed = Mathf.Min(Time.frameCount - tile.FrameState.LastVisitedFrame, maxFrames); + tile.FrameState.Priority = (1 + (framesSinceUsed / maxFrames)); + } + } + } + protected virtual void UpdateStats() { //override in subclass diff --git a/Assets/Unity3DTiles/MultiTilesetBehaviour.cs b/Assets/Unity3DTiles/MultiTilesetBehaviour.cs index 85c9c12..05844b1 100644 --- a/Assets/Unity3DTiles/MultiTilesetBehaviour.cs +++ b/Assets/Unity3DTiles/MultiTilesetBehaviour.cs @@ -90,10 +90,25 @@ protected override void _lateUpdate() protected override void UpdateStats() { - foreach (var tileset in tilesets) + //this works but is very inefficient and drags down framerate significantly when many tilesets are loaded + //foreach (var tileset in tilesets) + //{ + // tileset.UpdateStats(); + //} + RequestManager.ForEachQueuedDownload(t => { t.Tileset.Statistics.RequestQueueLength++; }); + RequestManager.ForEachActiveDownload(t => { t.Tileset.Statistics.ActiveDownloads++; }); + foreach (var tile in ProcessingQueue) { - tileset.UpdateStats(); + tile.Tileset.Statistics.ProcessingQueueLength++; } + TileCache.ForEach(t => { + t.Tileset.Statistics.DownloadedTiles++; + if (t.ContentState == Unity3DTileContentState.READY) + { + t.Tileset.Statistics.ReadyTiles++; + } + }); + Unity3DTilesetStatistics.MaxLoadedTiles = TileCache.MaxSize; Stats = Unity3DTilesetStatistics.Aggregate(tilesets.Select(t => t.Statistics).ToArray()); } diff --git a/Assets/Unity3DTiles/RequestManager.cs b/Assets/Unity3DTiles/RequestManager.cs index 9997649..649643b 100644 --- a/Assets/Unity3DTiles/RequestManager.cs +++ b/Assets/Unity3DTiles/RequestManager.cs @@ -90,23 +90,6 @@ private set } } - public int Count(Func predicate = null) - { - if (predicate == null) - { - return queue.Count; - } - int num = 0; - foreach (var request in queue) - { - if (predicate(request.Tile)) - { - num++; - } - } - return num; - } - public IEnumerator GetEnumerator() { foreach (var request in queue) @@ -141,6 +124,23 @@ public RequestManager(Unity3DTilesetSceneOptions sceneOptions) this.sceneOptions = sceneOptions; } + public int Count(Func predicate = null) + { + if (predicate == null) + { + return queue.Count; + } + int num = 0; + foreach (var request in queue) + { + if (predicate(request.Tile)) + { + num++; + } + } + return num; + } + public int CountActiveDownloads(Func predicate = null) { if (predicate == null) @@ -150,6 +150,22 @@ public int CountActiveDownloads(Func predicate = null) return activeDownloads.Count(predicate); } + public void ForEachQueuedDownload(Action action) + { + foreach (var request in queue) + { + action(request.Tile); + } + } + + public void ForEachActiveDownload(Action action) + { + foreach (var tile in activeDownloads) + { + action(tile); + } + } + public void EnqueRequest(Request request) { request.Tile.ContentState = Unity3DTileContentState.LOADING; diff --git a/Assets/Unity3DTiles/TileCache.cs b/Assets/Unity3DTiles/TileCache.cs index 900e031..c224642 100644 --- a/Assets/Unity3DTiles/TileCache.cs +++ b/Assets/Unity3DTiles/TileCache.cs @@ -35,15 +35,6 @@ public class TileCache private readonly List tmpList = new List(); - public int Count(Func predicate = null) - { - if (predicate == null) - { - return list.Count - 1; - } - return list.Count(t => t != null && predicate(t)); - } - public bool HasMaxSize { get { return sceneOptions.CacheMaxSize > 0; } @@ -100,9 +91,33 @@ public TileCache(Unity3DTilesetSceneOptions sceneOptions) list.AddFirst(sentinel); } + public int Count(Func predicate = null) + { + if (predicate == null) + { + return list.Count - 1; + } + return list.Count(t => t != null && predicate(t)); + } + + public void ForEach(Action action) + { + foreach (var tile in list) + { + if (tile != null) + { + action(tile); + } + } + } + public bool Add(Unity3DTile tile, out bool duplicate) { duplicate = false; + if (tile == null) + { + return false; + } if (nodeLookup.ContainsKey(tile)) { duplicate = true; diff --git a/Assets/Unity3DTiles/Unity3DTile.cs b/Assets/Unity3DTiles/Unity3DTile.cs index c320932..94cf4cc 100644 --- a/Assets/Unity3DTiles/Unity3DTile.cs +++ b/Assets/Unity3DTiles/Unity3DTile.cs @@ -128,18 +128,21 @@ public void MarkUsed() InUsedSet = true; } - public bool IsUsedThisFrame(int frameCount) + public bool IsUsedThisFrame { - return InUsedSet && LastVisitedFrame == frameCount; + get + { + return InUsedSet && LastVisitedFrame == Time.frameCount; + } } - public void Reset(int frameCount) + public void Reset() { - if(LastVisitedFrame == frameCount) + if (LastVisitedFrame == Time.frameCount) { return; } - LastVisitedFrame = frameCount; + LastVisitedFrame = Time.frameCount; InUsedSet = false; InFrustumSet = false; IsUsedSetLeaf = false; diff --git a/Assets/Unity3DTiles/Unity3DTilesetTraversal.cs b/Assets/Unity3DTiles/Unity3DTilesetTraversal.cs index 30c4a66..a93a87c 100644 --- a/Assets/Unity3DTiles/Unity3DTilesetTraversal.cs +++ b/Assets/Unity3DTiles/Unity3DTilesetTraversal.cs @@ -25,10 +25,14 @@ public class Unity3DTilesetTraversal { public HashSet ForceTiles = new HashSet(); - private int frameCount = 0; private Unity3DTileset tileset; private Unity3DTilesetSceneOptions sceneOptions; + private Unity3DTilesetOptions tilesetOptions + { + get { return tileset.TilesetOptions; } + } + public Unity3DTilesetTraversal(Unity3DTileset tileset, Unity3DTilesetSceneOptions sceneOptions) { this.tileset = tileset; @@ -37,25 +41,12 @@ public Unity3DTilesetTraversal(Unity3DTileset tileset, Unity3DTilesetSceneOption public void Run() { - frameCount++; - if (!tileset.TilesetOptions.Show) { ToggleTiles(tileset.Root); return; } - foreach (var ft in ForceTiles) - { - for (var tile = ft; tile != null; tile = tile.Parent) - { - tile.FrameState.Reset(frameCount); - tile.MarkUsed(); - tile.FrameState.InFrustumSet = true; - tileset.Statistics.FrustumSet++; - } - } - SSECalculator sse = new SSECalculator(tileset); foreach (Camera cam in sceneOptions.ClippingCameras) { @@ -63,24 +54,48 @@ public void Run() { continue; } - // All of our bounding boxes and tiles are using tileset coordinate frame so lets get our frustrum planes - // in tileset frame. This way we only need to transform our planes, not every bounding box we need to check against - Matrix4x4 cameraMatrix = cam.projectionMatrix * cam.worldToCameraMatrix * tileset.Behaviour.transform.localToWorldMatrix; + + // All of our bounding boxes and tiles are using tileset coordinate frame so lets get our frustrum + // planes in tileset frame. This way we only need to transform our planes, not every bounding box we + // need to check against + Matrix4x4 cameraMatrix = + cam.projectionMatrix * cam.worldToCameraMatrix * tileset.Behaviour.transform.localToWorldMatrix; Plane[] planes = GeometryUtility.CalculateFrustumPlanes(cameraMatrix); sse.Configure(cam); - Vector3 cameraPositionInTileset = tileset.Behaviour.transform.InverseTransformPoint(cam.transform.position); - Vector3 cameraForwardInTileset = tileset.Behaviour.transform.InverseTransformDirection(cam.transform.forward); + Vector3 cameraPositionInTileset = + tileset.Behaviour.transform.InverseTransformPoint(cam.transform.position); + Vector3 cameraForwardInTileset = + tileset.Behaviour.transform.InverseTransformDirection(cam.transform.forward); + DetermineFrustumSet(tileset.Root, planes, sse, cameraPositionInTileset, cameraForwardInTileset, PlaneClipMask.GetDefaultMask()); } + + foreach (var ft in ForceTiles) + { + for (var tile = ft; tile != null; tile = tile.Parent) + { + if (!tile.FrameState.IsUsedThisFrame) + { + tile.FrameState.Reset(); + tile.MarkUsed(); + tile.FrameState.InFrustumSet = true; + tileset.Statistics.FrustumSet++; + } + } + } + AssignPrioritiesRecursively(tileset.Root); + MarkUsedSetLeaves(tileset.Root); + SkipTraversal(tileset.Root); + ToggleTiles(tileset.Root); - //tileset.RequestManager.Process(); + //RequestManager.Process() and UnloadUnusedContent() - //called once for all tilesets at end of AbstractTilesetBehaviour.LateUpdate() + //are called once for all tilesets at end of AbstractTilesetBehaviour.LateUpdate() } class SSECalculator @@ -100,7 +115,8 @@ public void Configure(Camera cam) this.cam = cam; if (cam.orthographic) { - pixelSize = Mathf.Max(cam.orthographicSize * 2, cam.orthographicSize * 2 * cam.aspect) / Mathf.Max(cam.pixelHeight, cam.pixelWidth); + pixelSize = Mathf.Max(cam.orthographicSize * 2, cam.orthographicSize * 2 * cam.aspect) / + Mathf.Max(cam.pixelHeight, cam.pixelWidth); } else { @@ -130,7 +146,7 @@ public float ProjectDistanceOnTileToScreen(float distOnTile, float distFromCamer } } - float Fog(float distFromCamera, float density) + private float Fog(float distFromCamera, float density) { float scalar = distFromCamera * density; return 1.0f - Mathf.Exp(-(scalar * scalar)); @@ -138,25 +154,29 @@ float Fog(float distFromCamera, float density) } /// - /// After calling this method all tiles within a camera frustum will be marked as being in the frustum set and as being used starting at the root and stopping when: + /// After calling this method all tiles within a camera frustum will be marked as being in the frustum set and + /// as being used starting at the root and stopping when: + /// /// 1) A tile is found that has a screen space error less than or equal to our target SSE /// 2) MaxDepth is reached (Optional) + /// /// Tiles with no content are ignored (i.e. we recurse to the nearest complete set of descendents with content) /// - /// If the LoadSiblings criteria is enabled we add additional tiles to the used set. Specificlly, if a tile is in the Frustum set, we gurantee that all of its siblings - /// are marked as used. If the siblings have empty conetent, we mark the first set of decendents that have content as used. This is useful for tree traversals where - /// we want to load content or do computation on tiles that are outside the users current view but results in a slower traversal. + /// If the LoadSiblings criteria is enabled we add additional tiles to the used set. Specificlly, if a tile is + /// in the Frustum set, we gurantee that all of its siblings are marked as used. If the siblings have empty + /// conetent, we mark the first set of decendents that have content as used. This is useful for tree traversals + /// where we want to load content or do computation on tiles that are outside the users current view but results + /// in a slower traversal. /// - /// After this method is run, only tiles that are in the used set are considered by the rest of the traversal algorithm for this frame. Unused tiles may be subject to being unloaded. + /// After this method is run, only tiles that are in the used set are considered by the rest of the traversal + /// algorithm for this frame. Unused tiles may be subject to being unloaded. /// - /// - /// - /// - bool DetermineFrustumSet(Unity3DTile tile, Plane[] planes, SSECalculator sse, Vector3 cameraPosInTilesetFrame, - Vector3 cameraFwdInTilesetFrame, PlaneClipMask mask) + private bool DetermineFrustumSet(Unity3DTile tile, Plane[] planes, SSECalculator sse, + Vector3 cameraPosInTilesetFrame, Vector3 cameraFwdInTilesetFrame, + PlaneClipMask mask) { // Reset frame state if needed - tile.FrameState.Reset(frameCount); + tile.FrameState.Reset(); // Check to see if we are in the fustrum mask = tile.BoundingVolume.IntersectPlanes(planes, mask); if (mask.Intersection == IntersectionType.OUTSIDE) @@ -200,12 +220,12 @@ bool DetermineFrustumSet(Unity3DTile tile, Plane[] planes, SSECalculator sse, Ve cameraFwdInTilesetFrame, mask); anyChildUsed = anyChildUsed || r; } - // If any children are in the workingset, mark all of them as being used (siblings/atomic split criteria). + // If any children are in the working set, mark all of them as being used if (anyChildUsed && tileset.TilesetOptions.LoadSiblings) { for (int i = 0; i < tile.Children.Count; i++) { - MarkUsedRecursively(tile.Children[i]); + MarkUsed(tile.Children[i]); } } return true; @@ -216,18 +236,17 @@ bool DetermineFrustumSet(Unity3DTile tile, Plane[] planes, SSECalculator sse, Ve /// recurse until a complete set of leaf nodes with content are found /// This is only needed to handle the case of empty tiles /// - /// - void MarkUsedRecursively(Unity3DTile tile) + private void MarkUsed(Unity3DTile tile) { // We need to reset as we go in case we find tiles that weren't previously explored // If they have already been reset this frame this has no effect - tile.FrameState.Reset(frameCount); + tile.FrameState.Reset(); tile.MarkUsed(); - if(tile.HasEmptyContent) + if (tile.HasEmptyContent) { for(int i = 0; i < tile.Children.Count; i++) { - MarkUsedRecursively(tile.Children[i]); + MarkUsed(tile.Children[i]); } } } @@ -237,11 +256,10 @@ void MarkUsedRecursively(Unity3DTile tile) /// After this point we will not consider any tiles that are beyond a used set leaf. /// Leafs are the content we ideally want to show this frame /// - /// void MarkUsedSetLeaves(Unity3DTile tile) { // A used leaf is a node that is used but has no children in the used set - if (!tile.FrameState.IsUsedThisFrame(frameCount)) + if (!tile.FrameState.IsUsedThisFrame) { // Not used this frame, can't be a used leaf and neither can anything beneath us return; @@ -251,7 +269,7 @@ void MarkUsedSetLeaves(Unity3DTile tile) bool anyChildrenUsed = false; for (int i = 0; i < tile.Children.Count; i++) { - anyChildrenUsed = anyChildrenUsed || tile.Children[i].FrameState.IsUsedThisFrame(frameCount); + anyChildrenUsed = anyChildrenUsed || tile.Children[i].FrameState.IsUsedThisFrame; } if (!anyChildrenUsed || ForceTiles.Contains(tile)) { @@ -268,12 +286,13 @@ void MarkUsedSetLeaves(Unity3DTile tile) /// /// Traverse the tree, request tiles, and enable visible tiles - /// Skip parent tiles that have a screen space error larger than MaximumScreenSpaceError*SkipScreenSpaceErrorMultiplier + /// Skip parent tiles that have a screen space error larger than + /// MaximumScreenSpaceError*SkipScreenSpaceErrorMultiplier /// /// void SkipTraversal(Unity3DTile tile) { - if (!tile.FrameState.IsUsedThisFrame(frameCount)) + if (!tile.FrameState.IsUsedThisFrame) { return; } @@ -300,18 +319,20 @@ void SkipTraversal(Unity3DTile tile) // 1) meets SSE cuttoff // 2) has content and is not empty // 3) one or more of its chidlren don't have content - bool meetsSSE = tile.FrameState.ScreenSpaceError < (tileset.TilesetOptions.MaximumScreenSpaceError * tileset.TilesetOptions.SkipScreenSpaceErrorMultiplier); + bool meetsSSE = tile.FrameState.ScreenSpaceError < + (tilesetOptions.MaximumScreenSpaceError * tilesetOptions.SkipScreenSpaceErrorMultiplier); bool hasContent = tile.ContentState == Unity3DTileContentState.READY && !tile.HasEmptyContent; bool allChildrenHaveContent = true; for (int i = 0; i < tile.Children.Count; i++) { - if (tile.Children[i].FrameState.IsUsedThisFrame(frameCount)) + if (tile.Children[i].FrameState.IsUsedThisFrame) { - bool childContent = tile.Children[i].ContentState == Unity3DTileContentState.READY || tile.HasEmptyContent; + bool childContent = tile.Children[i].ContentState == Unity3DTileContentState.READY || + tile.HasEmptyContent; allChildrenHaveContent = allChildrenHaveContent && childContent; } } - if(meetsSSE && !hasContent) + if (meetsSSE && !hasContent) { RequestTile(tile); } @@ -327,7 +348,7 @@ void SkipTraversal(Unity3DTile tile) // Request children for (int i = 0; i < tile.Children.Count; i++) { - if (tile.Children[i].FrameState.IsUsedThisFrame(frameCount)) + if (tile.Children[i].FrameState.IsUsedThisFrame) { RequestTile(tile.Children[i]); } @@ -337,7 +358,7 @@ void SkipTraversal(Unity3DTile tile) // Otherwise keep decending for (int i = 0; i < tile.Children.Count; i++) { - if (tile.Children[i].FrameState.IsUsedThisFrame(frameCount)) + if (tile.Children[i].FrameState.IsUsedThisFrame) { SkipTraversal(tile.Children[i]); } @@ -347,10 +368,10 @@ void SkipTraversal(Unity3DTile tile) void ToggleTiles(Unity3DTile tile) { // Only consider tiles that were used this frame or the previous frame - if (tile.FrameState.IsUsedThisFrame(frameCount) || tile.FrameState.UsedLastFrame) + if (tile.FrameState.IsUsedThisFrame || tile.FrameState.UsedLastFrame) { tile.FrameState.UsedLastFrame = false; - if (!tile.FrameState.IsUsedThisFrame(frameCount)) + if (!tile.FrameState.IsUsedThisFrame) { // This tile was active last frame but isn't active any more if (tile.Content != null) @@ -365,20 +386,20 @@ void ToggleTiles(Unity3DTile tile) { tile.Content.SetActive(tile.FrameState.InColliderSet || tile.FrameState.InRenderSet); tile.Content.EnableColliders(tile.FrameState.InColliderSet); - if (tileset.TilesetOptions.Show) + if (tilesetOptions.Show) { if (tile.FrameState.InRenderSet) { tile.Content.EnableRenderers(true); - tile.Content.SetShadowMode(tileset.TilesetOptions.ShadowCastingMode, - tileset.TilesetOptions.RecieveShadows); - if (tileset.TilesetOptions.Style != null) + tile.Content.SetShadowMode(tilesetOptions.ShadowCastingMode, + tilesetOptions.RecieveShadows); + if (tilesetOptions.Style != null) { - tileset.TilesetOptions.Style.ApplyStyle(tile); + tilesetOptions.Style.ApplyStyle(tile); } } else if (tile.FrameState.InColliderSet && - tileset.TilesetOptions.ShadowCastingMode != ShadowCastingMode.Off) + tilesetOptions.ShadowCastingMode != ShadowCastingMode.Off) { tile.Content.SetShadowMode(ShadowCastingMode.ShadowsOnly, false); } @@ -395,8 +416,8 @@ void ToggleTiles(Unity3DTile tile) int DepthFromFirstUsedAncestor(Unity3DTile tile) { - if (!tile.FrameState.IsUsedThisFrame(frameCount) || - tile.Parent == null || !tile.Parent.FrameState.IsUsedThisFrame(frameCount)) + if (!tile.FrameState.IsUsedThisFrame || + tile.Parent == null || !tile.Parent.FrameState.IsUsedThisFrame) { return 0; } @@ -406,8 +427,7 @@ int DepthFromFirstUsedAncestor(Unity3DTile tile) float MinUsedAncestorPixelsToCameraCenter(Unity3DTile tile, float pixels = float.MaxValue) { - if (!tile.FrameState.IsUsedThisFrame(frameCount) || - tile.Parent == null || !tile.Parent.FrameState.IsUsedThisFrame(frameCount)) + if (!tile.FrameState.IsUsedThisFrame || tile.Parent == null || !tile.Parent.FrameState.IsUsedThisFrame) { return pixels; } @@ -417,25 +437,32 @@ float MinUsedAncestorPixelsToCameraCenter(Unity3DTile tile, float pixels = float float TilePriority(Unity3DTile tile) { - if (tileset.TilesetOptions.TilePriority != null) + if (ForceTiles.Contains(tile)) { - return tileset.TilesetOptions.TilePriority(tile); + return 0; } - if (ForceTiles.Contains(tile)) + if (!tile.FrameState.IsUsedThisFrame) { - return 0; + return float.MaxValue; + } + + if (tilesetOptions.TilePriority != null) + { + return tilesetOptions.TilePriority(tile); } //prioritize by distance from camera //return tile.FrameState.DistanceToCamera; + //return tile.FrameState.PixelsToCameraCenter; + //return MinUsedAncestorPixelsToCameraCenter(tile); //prioritize coarse to fine //return tile.Depth; //return DepthFromFirstUsedAncestor(tile); //prioritize first coarse to fine, then by distance - ////int depth = tileDepth; + //int depth = tileDepth; //int depth = DepthFromFirstUsedAncestor(tile); //integer part //float distLimit = 1000; //float dist = Mathf.Min(tile.FrameState.DistanceToCamera, distLimit); @@ -443,13 +470,11 @@ float TilePriority(Unity3DTile tile) //return depth + relDist; //prioritize first by distance, then coarse to fine - //float quantizedDist = (int)(tile.FrameState.DistanceToCamera * 100); //integer part - //float quantizedDist = (int)(tile.FrameState.DistanceToCameraAxis * 100); //integer part - //float quantizedDist = (int)(tile.FrameState.PixelsToCameraCenter / 100); - float quantizedDist = (int)(MinUsedAncestorPixelsToCameraCenter(tile) / 100); + float quantizedDist = (int)(tile.FrameState.PixelsToCameraCenter / 100); + //float quantizedDist = (int)(MinUsedAncestorPixelsToCameraCenter(tile) / 100); float depthLimit = 100; - //float depth = Math.Min(tile.Depth, depthLimit); - float depth = Math.Min(DepthFromFirstUsedAncestor(tile), depthLimit); + float depth = Math.Min(tile.Depth, depthLimit); + //float depth = Math.Min(DepthFromFirstUsedAncestor(tile), depthLimit); float relDepth = depth / depthLimit; //fractional part return quantizedDist + relDepth; } @@ -463,20 +488,22 @@ float TilePriority(Unity3DTile tile) /// void AssignPrioritiesRecursively(Unity3DTile tile) { - tile.FrameState.Priority = float.MaxValue; - if (tile.FrameState.IsUsedThisFrame(frameCount) && !tile.HasEmptyContent) - { - tile.FrameState.Priority = TilePriority(tile); - } - float minChildPriority = float.MaxValue; - foreach (var child in tile.Children) + if (tile.FrameState.IsUsedThisFrame) { - AssignPrioritiesRecursively(child); - minChildPriority = Mathf.Min(minChildPriority, child.FrameState.Priority); - } - foreach (var child in tile.Children) - { - child.FrameState.Priority = minChildPriority; + if (!tile.HasEmptyContent) + { + tile.FrameState.Priority = TilePriority(tile); + } + float minChildPriority = float.MaxValue; + foreach (var child in tile.Children) + { + AssignPrioritiesRecursively(child); + minChildPriority = Mathf.Min(minChildPriority, child.FrameState.Priority); + } + foreach (var child in tile.Children) + { + child.FrameState.Priority = minChildPriority; + } } } @@ -490,7 +517,7 @@ void RequestTile(Unity3DTile tile) { for (int i = 0; i < tile.Parent.Children.Count; i++) { - if (tile.Parent.Children[i].FrameState.IsUsedThisFrame(frameCount)) + if (tile.Parent.Children[i].FrameState.IsUsedThisFrame) { tile.Parent.Children[i].RequestContent(tile.FrameState.Priority); } @@ -506,7 +533,7 @@ public void DrawDebug() void DebugDrawUsedSet(Unity3DTile tile) { - if (tile.FrameState.IsUsedThisFrame(frameCount)) + if (tile.FrameState.IsUsedThisFrame) { if (tile.FrameState.IsUsedSetLeaf) { @@ -521,7 +548,7 @@ void DebugDrawUsedSet(Unity3DTile tile) void DebugDrawFrustumSet(Unity3DTile tile) { - if (tile.FrameState.IsUsedThisFrame(frameCount) && tile.FrameState.InFrustumSet) + if (tile.FrameState.IsUsedThisFrame && tile.FrameState.InFrustumSet) { if (tile.FrameState.IsUsedSetLeaf) { From 6c16bd7d74a255e076f6c12ba4a0fefa65599a58 Mon Sep 17 00:00:00 2001 From: Marsette Vona Date: Tue, 1 Jun 2021 21:03:21 -0400 Subject: [PATCH 3/5] demo scene changes --- Assets/Examples/Scenes/GenericWeb.unity | 29 +++++++++++-------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/Assets/Examples/Scenes/GenericWeb.unity b/Assets/Examples/Scenes/GenericWeb.unity index ade428a..149253b 100644 --- a/Assets/Examples/Scenes/GenericWeb.unity +++ b/Assets/Examples/Scenes/GenericWeb.unity @@ -665,9 +665,9 @@ MonoBehaviour: PauseMemThreshold: -1 GCMemThreshold: -1 MaximumTilesToProcessPerFrame: 1 - LRUCacheTargetSize: 600 - LRUCacheMaxSize: 1000 - LRUMaxFrameUnloadRatio: 0.2 + CacheTargetSize: 600 + CacheMaxSize: 1000 + MaxCacheUnloadRatio: 0.2 MaxConcurrentRequests: 6 ShaderOverride: LoadIndices: 0 @@ -675,26 +675,23 @@ MonoBehaviour: DefaultCameraTranslation: {x: 0, y: 0, z: -10} DefaultCameraRotation: {x: 0, y: 0, z: -0.70710677, w: 0.70710677} Stats: - FrustumSetCount: 0 - UsedSetCount: 0 - VisibleTileCount: 0 - ColliderTileCount: 0 + UsedSet: 0 + FrustumSet: 0 + ColliderSet: 0 + VisibleTiles: 0 VisibleFaces: 0 VisibleTextures: 0 VisiblePixels: 0 MinVisibleTileDepth: 0 MaxVisibleTileDepth: 0 + RequestsThisFrame: 0 + NetworkErrorsThisFrame: 0 NumberOfTilesTotal: 0 - LoadedContentCount: 0 - ProcessingTiles: 0 RequestQueueLength: 0 - ConcurrentRequests: 0 - TotalTilesLoaded: 0 - TilesLeftToLoad: 0 - LeafContentRequired: 0 - LeafContentLoaded: 0 - RequestsThisFrame: 0 - NetworkError: 0 + ActiveDownloads: 0 + ProcessingQueueLength: 0 + DownloadedTiles: 0 + ReadyTiles: 0 TilesetOptions: [] SceneUrl: --- !u!114 &1485971529 From beaa0103da3f7fa9bc7629c6b25dd2924fb72668 Mon Sep 17 00:00:00 2001 From: Marsette Vona Date: Tue, 1 Jun 2021 21:05:04 -0400 Subject: [PATCH 4/5] disable roll lock by default in orbit nav --- Assets/Examples/Scenes/GenericWeb.unity | 2 +- Assets/Examples/Scenes/MainHololens.unity | 112 ++++++++++++---------- Assets/Examples/Scenes/MainWeb.unity | 112 ++++++++++++---------- 3 files changed, 123 insertions(+), 103 deletions(-) diff --git a/Assets/Examples/Scenes/GenericWeb.unity b/Assets/Examples/Scenes/GenericWeb.unity index 149253b..bd1865c 100644 --- a/Assets/Examples/Scenes/GenericWeb.unity +++ b/Assets/Examples/Scenes/GenericWeb.unity @@ -735,7 +735,7 @@ MonoBehaviour: rotateButton: 0 scaleButton: 2 rollButton: 1 - lockRoll: 1 + lockRoll: 0 lockRollSpeed: 0.01 lockRollCrossover: 0.7 worldUp: {x: 0, y: 0, z: -1} diff --git a/Assets/Examples/Scenes/MainHololens.unity b/Assets/Examples/Scenes/MainHololens.unity index a2fc5f5..832e4ed 100644 --- a/Assets/Examples/Scenes/MainHololens.unity +++ b/Assets/Examples/Scenes/MainHololens.unity @@ -143,138 +143,138 @@ PrefabInstance: objectReference: {fileID: 0} - target: {fileID: 3376399834326130634, guid: 5b36edd38769f254e8ee3f629b1741a6, type: 3} - propertyPath: m_AnchorMin.y + propertyPath: m_AnchorMax.y value: 0 objectReference: {fileID: 0} - target: {fileID: 3376399834326130634, guid: 5b36edd38769f254e8ee3f629b1741a6, type: 3} - propertyPath: m_AnchorMax.y + propertyPath: m_AnchorMin.y value: 0 objectReference: {fileID: 0} - target: {fileID: 3376399834326130634, guid: 5b36edd38769f254e8ee3f629b1741a6, type: 3} - propertyPath: m_AnchoredPosition.x + propertyPath: m_SizeDelta.x value: 0 objectReference: {fileID: 0} - target: {fileID: 3376399834326130634, guid: 5b36edd38769f254e8ee3f629b1741a6, type: 3} - propertyPath: m_AnchoredPosition.y + propertyPath: m_SizeDelta.y value: 0 objectReference: {fileID: 0} - target: {fileID: 3376399834326130634, guid: 5b36edd38769f254e8ee3f629b1741a6, type: 3} - propertyPath: m_SizeDelta.x + propertyPath: m_AnchoredPosition.x value: 0 objectReference: {fileID: 0} - target: {fileID: 3376399834326130634, guid: 5b36edd38769f254e8ee3f629b1741a6, type: 3} - propertyPath: m_SizeDelta.y + propertyPath: m_AnchoredPosition.y value: 0 objectReference: {fileID: 0} - target: {fileID: 3376399835161366642, guid: 5b36edd38769f254e8ee3f629b1741a6, type: 3} - propertyPath: m_LocalPosition.x + propertyPath: m_Pivot.x value: 0 objectReference: {fileID: 0} - target: {fileID: 3376399835161366642, guid: 5b36edd38769f254e8ee3f629b1741a6, type: 3} - propertyPath: m_LocalPosition.y - value: 0 + propertyPath: m_Pivot.y + value: 1 objectReference: {fileID: 0} - target: {fileID: 3376399835161366642, guid: 5b36edd38769f254e8ee3f629b1741a6, type: 3} - propertyPath: m_LocalPosition.z + propertyPath: m_RootOrder value: 0 objectReference: {fileID: 0} - target: {fileID: 3376399835161366642, guid: 5b36edd38769f254e8ee3f629b1741a6, type: 3} - propertyPath: m_LocalRotation.x + propertyPath: m_AnchorMax.x value: 0 objectReference: {fileID: 0} - target: {fileID: 3376399835161366642, guid: 5b36edd38769f254e8ee3f629b1741a6, type: 3} - propertyPath: m_LocalRotation.y - value: 0 + propertyPath: m_AnchorMax.y + value: 1 objectReference: {fileID: 0} - target: {fileID: 3376399835161366642, guid: 5b36edd38769f254e8ee3f629b1741a6, type: 3} - propertyPath: m_LocalRotation.z + propertyPath: m_AnchorMin.x value: 0 objectReference: {fileID: 0} - target: {fileID: 3376399835161366642, guid: 5b36edd38769f254e8ee3f629b1741a6, type: 3} - propertyPath: m_LocalRotation.w + propertyPath: m_AnchorMin.y value: 1 objectReference: {fileID: 0} - target: {fileID: 3376399835161366642, guid: 5b36edd38769f254e8ee3f629b1741a6, type: 3} - propertyPath: m_RootOrder + propertyPath: m_SizeDelta.x value: 0 objectReference: {fileID: 0} - target: {fileID: 3376399835161366642, guid: 5b36edd38769f254e8ee3f629b1741a6, type: 3} - propertyPath: m_LocalEulerAnglesHint.x + propertyPath: m_SizeDelta.y value: 0 objectReference: {fileID: 0} - target: {fileID: 3376399835161366642, guid: 5b36edd38769f254e8ee3f629b1741a6, type: 3} - propertyPath: m_LocalEulerAnglesHint.y + propertyPath: m_LocalPosition.x value: 0 objectReference: {fileID: 0} - target: {fileID: 3376399835161366642, guid: 5b36edd38769f254e8ee3f629b1741a6, type: 3} - propertyPath: m_LocalEulerAnglesHint.z + propertyPath: m_LocalPosition.y value: 0 objectReference: {fileID: 0} - target: {fileID: 3376399835161366642, guid: 5b36edd38769f254e8ee3f629b1741a6, type: 3} - propertyPath: m_AnchoredPosition.x - value: 20 + propertyPath: m_LocalPosition.z + value: 0 objectReference: {fileID: 0} - target: {fileID: 3376399835161366642, guid: 5b36edd38769f254e8ee3f629b1741a6, type: 3} - propertyPath: m_AnchoredPosition.y - value: -20 + propertyPath: m_LocalRotation.w + value: 1 objectReference: {fileID: 0} - target: {fileID: 3376399835161366642, guid: 5b36edd38769f254e8ee3f629b1741a6, type: 3} - propertyPath: m_SizeDelta.x + propertyPath: m_LocalRotation.x value: 0 objectReference: {fileID: 0} - target: {fileID: 3376399835161366642, guid: 5b36edd38769f254e8ee3f629b1741a6, type: 3} - propertyPath: m_SizeDelta.y + propertyPath: m_LocalRotation.y value: 0 objectReference: {fileID: 0} - target: {fileID: 3376399835161366642, guid: 5b36edd38769f254e8ee3f629b1741a6, type: 3} - propertyPath: m_AnchorMin.x + propertyPath: m_LocalRotation.z value: 0 objectReference: {fileID: 0} - target: {fileID: 3376399835161366642, guid: 5b36edd38769f254e8ee3f629b1741a6, type: 3} - propertyPath: m_AnchorMin.y - value: 1 + propertyPath: m_AnchoredPosition.x + value: 20 objectReference: {fileID: 0} - target: {fileID: 3376399835161366642, guid: 5b36edd38769f254e8ee3f629b1741a6, type: 3} - propertyPath: m_AnchorMax.x - value: 0 + propertyPath: m_AnchoredPosition.y + value: -20 objectReference: {fileID: 0} - target: {fileID: 3376399835161366642, guid: 5b36edd38769f254e8ee3f629b1741a6, type: 3} - propertyPath: m_AnchorMax.y - value: 1 + propertyPath: m_LocalEulerAnglesHint.x + value: 0 objectReference: {fileID: 0} - target: {fileID: 3376399835161366642, guid: 5b36edd38769f254e8ee3f629b1741a6, type: 3} - propertyPath: m_Pivot.x + propertyPath: m_LocalEulerAnglesHint.y value: 0 objectReference: {fileID: 0} - target: {fileID: 3376399835161366642, guid: 5b36edd38769f254e8ee3f629b1741a6, type: 3} - propertyPath: m_Pivot.y - value: 1 + propertyPath: m_LocalEulerAnglesHint.z + value: 0 objectReference: {fileID: 0} - target: {fileID: 3376399835161366643, guid: 5b36edd38769f254e8ee3f629b1741a6, type: 3} @@ -798,10 +798,12 @@ MonoBehaviour: m_Name: m_EditorClassIdentifier: SceneOptions: + PauseMemThreshold: -1 + GCMemThreshold: -1 MaximumTilesToProcessPerFrame: 1 - LRUCacheTargetSize: 600 - LRUCacheMaxSize: 700 - LRUMaxFrameUnloadRatio: 0.2 + CacheTargetSize: 600 + CacheMaxSize: 1000 + MaxCacheUnloadRatio: 0.2 MaxConcurrentRequests: 6 ShaderOverride: LoadIndices: 0 @@ -809,26 +811,23 @@ MonoBehaviour: DefaultCameraTranslation: {x: 160, y: 32, z: 100} DefaultCameraRotation: {x: 0, y: 0.76604444, z: 0, w: 0.64278764} Stats: - FrustumSetCount: 0 - UsedSetCount: 0 - VisibleTileCount: 0 - ColliderTileCount: 0 + UsedSet: 0 + FrustumSet: 0 + ColliderSet: 0 + VisibleTiles: 0 VisibleFaces: 0 VisibleTextures: 0 VisiblePixels: 0 MinVisibleTileDepth: 0 MaxVisibleTileDepth: 0 + RequestsThisFrame: 0 + NetworkErrorsThisFrame: 0 NumberOfTilesTotal: 0 - LoadedContentCount: 0 - ProcessingTiles: 0 RequestQueueLength: 0 - ConcurrentRequests: 0 - TotalTilesLoaded: 0 - TilesLeftToLoad: 0 - LeafContentRequired: 0 - LeafContentLoaded: 0 - RequestsThisFrame: 0 - NetworkError: 0 + ActiveDownloads: 0 + ProcessingQueueLength: 0 + DownloadedTiles: 0 + ReadyTiles: 0 TilesetOptions: Name: Url: data://SampleTileset/tileset.json @@ -860,6 +859,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 6551e1c668008d044b4d380b72192ee6, type: 3} m_Name: m_EditorClassIdentifier: + targetFrameRate: 60 tileset: {fileID: 2111641893} hud: {fileID: 596472553} mouseFly: {fileID: 2111641896} @@ -888,6 +888,11 @@ MonoBehaviour: rotateButton: 0 scaleButton: 2 rollButton: 1 + lockRoll: 0 + lockRollSpeed: 0.1 + lockRollCrossover: 0.2 + worldUp: {x: 0, y: 1, z: 0} + worldNorth: {x: 0, y: 0, z: 1} rotateModifier: 4 scaleModifier: 2 accelModifier: 1 @@ -913,6 +918,11 @@ MonoBehaviour: rotateButton: 0 scaleButton: 2 rollButton: 1 + lockRoll: 1 + lockRollSpeed: 0.1 + lockRollCrossover: 0.2 + worldUp: {x: 0, y: 1, z: 0} + worldNorth: {x: 0, y: 0, z: 1} rotateModifier: 4 scaleModifier: 2 accelModifier: 1 diff --git a/Assets/Examples/Scenes/MainWeb.unity b/Assets/Examples/Scenes/MainWeb.unity index 44d216f..c9c290a 100644 --- a/Assets/Examples/Scenes/MainWeb.unity +++ b/Assets/Examples/Scenes/MainWeb.unity @@ -620,10 +620,12 @@ MonoBehaviour: m_Name: m_EditorClassIdentifier: SceneOptions: + PauseMemThreshold: -1 + GCMemThreshold: -1 MaximumTilesToProcessPerFrame: 1 - LRUCacheTargetSize: 600 - LRUCacheMaxSize: 700 - LRUMaxFrameUnloadRatio: 0.2 + CacheTargetSize: 600 + CacheMaxSize: 1000 + MaxCacheUnloadRatio: 0.2 MaxConcurrentRequests: 6 ShaderOverride: LoadIndices: 0 @@ -631,26 +633,23 @@ MonoBehaviour: DefaultCameraTranslation: {x: 160, y: 32, z: 100} DefaultCameraRotation: {x: 0, y: 0.76604444, z: 0, w: 0.64278764} Stats: - FrustumSetCount: 0 - UsedSetCount: 0 - VisibleTileCount: 0 - ColliderTileCount: 0 + UsedSet: 0 + FrustumSet: 0 + ColliderSet: 0 + VisibleTiles: 0 VisibleFaces: 0 VisibleTextures: 0 VisiblePixels: 0 MinVisibleTileDepth: 0 MaxVisibleTileDepth: 0 + RequestsThisFrame: 0 + NetworkErrorsThisFrame: 0 NumberOfTilesTotal: 0 - LoadedContentCount: 0 - ProcessingTiles: 0 RequestQueueLength: 0 - ConcurrentRequests: 0 - TotalTilesLoaded: 0 - TilesLeftToLoad: 0 - LeafContentRequired: 0 - LeafContentLoaded: 0 - RequestsThisFrame: 0 - NetworkError: 0 + ActiveDownloads: 0 + ProcessingQueueLength: 0 + DownloadedTiles: 0 + ReadyTiles: 0 TilesetOptions: Name: Url: data://SampleTileset/tileset.json @@ -682,6 +681,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 6551e1c668008d044b4d380b72192ee6, type: 3} m_Name: m_EditorClassIdentifier: + targetFrameRate: 60 tileset: {fileID: 1485971528} hud: {fileID: 835310070} mouseFly: {fileID: 1485971530} @@ -710,6 +710,11 @@ MonoBehaviour: rotateButton: 0 scaleButton: 2 rollButton: 1 + lockRoll: 1 + lockRollSpeed: 0.1 + lockRollCrossover: 0.2 + worldUp: {x: 0, y: 1, z: 0} + worldNorth: {x: 0, y: 0, z: 1} rotateModifier: 4 scaleModifier: 2 accelModifier: 1 @@ -733,6 +738,11 @@ MonoBehaviour: rotateButton: 0 scaleButton: 2 rollButton: 1 + lockRoll: 0 + lockRollSpeed: 0.1 + lockRollCrossover: 0.2 + worldUp: {x: 0, y: 1, z: 0} + worldNorth: {x: 0, y: 0, z: 1} rotateModifier: 4 scaleModifier: 2 accelModifier: 1 @@ -762,138 +772,138 @@ PrefabInstance: objectReference: {fileID: 0} - target: {fileID: 3376399834326130634, guid: 5b36edd38769f254e8ee3f629b1741a6, type: 3} - propertyPath: m_AnchorMin.y + propertyPath: m_AnchorMax.y value: 0 objectReference: {fileID: 0} - target: {fileID: 3376399834326130634, guid: 5b36edd38769f254e8ee3f629b1741a6, type: 3} - propertyPath: m_AnchorMax.y + propertyPath: m_AnchorMin.y value: 0 objectReference: {fileID: 0} - target: {fileID: 3376399834326130634, guid: 5b36edd38769f254e8ee3f629b1741a6, type: 3} - propertyPath: m_AnchoredPosition.x + propertyPath: m_SizeDelta.x value: 0 objectReference: {fileID: 0} - target: {fileID: 3376399834326130634, guid: 5b36edd38769f254e8ee3f629b1741a6, type: 3} - propertyPath: m_AnchoredPosition.y + propertyPath: m_SizeDelta.y value: 0 objectReference: {fileID: 0} - target: {fileID: 3376399834326130634, guid: 5b36edd38769f254e8ee3f629b1741a6, type: 3} - propertyPath: m_SizeDelta.x + propertyPath: m_AnchoredPosition.x value: 0 objectReference: {fileID: 0} - target: {fileID: 3376399834326130634, guid: 5b36edd38769f254e8ee3f629b1741a6, type: 3} - propertyPath: m_SizeDelta.y + propertyPath: m_AnchoredPosition.y value: 0 objectReference: {fileID: 0} - target: {fileID: 3376399835161366642, guid: 5b36edd38769f254e8ee3f629b1741a6, type: 3} - propertyPath: m_LocalPosition.x + propertyPath: m_Pivot.x value: 0 objectReference: {fileID: 0} - target: {fileID: 3376399835161366642, guid: 5b36edd38769f254e8ee3f629b1741a6, type: 3} - propertyPath: m_LocalPosition.y - value: 0 + propertyPath: m_Pivot.y + value: 1 objectReference: {fileID: 0} - target: {fileID: 3376399835161366642, guid: 5b36edd38769f254e8ee3f629b1741a6, type: 3} - propertyPath: m_LocalPosition.z + propertyPath: m_RootOrder value: 0 objectReference: {fileID: 0} - target: {fileID: 3376399835161366642, guid: 5b36edd38769f254e8ee3f629b1741a6, type: 3} - propertyPath: m_LocalRotation.x + propertyPath: m_AnchorMax.x value: 0 objectReference: {fileID: 0} - target: {fileID: 3376399835161366642, guid: 5b36edd38769f254e8ee3f629b1741a6, type: 3} - propertyPath: m_LocalRotation.y - value: 0 + propertyPath: m_AnchorMax.y + value: 1 objectReference: {fileID: 0} - target: {fileID: 3376399835161366642, guid: 5b36edd38769f254e8ee3f629b1741a6, type: 3} - propertyPath: m_LocalRotation.z + propertyPath: m_AnchorMin.x value: 0 objectReference: {fileID: 0} - target: {fileID: 3376399835161366642, guid: 5b36edd38769f254e8ee3f629b1741a6, type: 3} - propertyPath: m_LocalRotation.w + propertyPath: m_AnchorMin.y value: 1 objectReference: {fileID: 0} - target: {fileID: 3376399835161366642, guid: 5b36edd38769f254e8ee3f629b1741a6, type: 3} - propertyPath: m_RootOrder + propertyPath: m_SizeDelta.x value: 0 objectReference: {fileID: 0} - target: {fileID: 3376399835161366642, guid: 5b36edd38769f254e8ee3f629b1741a6, type: 3} - propertyPath: m_LocalEulerAnglesHint.x + propertyPath: m_SizeDelta.y value: 0 objectReference: {fileID: 0} - target: {fileID: 3376399835161366642, guid: 5b36edd38769f254e8ee3f629b1741a6, type: 3} - propertyPath: m_LocalEulerAnglesHint.y + propertyPath: m_LocalPosition.x value: 0 objectReference: {fileID: 0} - target: {fileID: 3376399835161366642, guid: 5b36edd38769f254e8ee3f629b1741a6, type: 3} - propertyPath: m_LocalEulerAnglesHint.z + propertyPath: m_LocalPosition.y value: 0 objectReference: {fileID: 0} - target: {fileID: 3376399835161366642, guid: 5b36edd38769f254e8ee3f629b1741a6, type: 3} - propertyPath: m_AnchoredPosition.x - value: 20 + propertyPath: m_LocalPosition.z + value: 0 objectReference: {fileID: 0} - target: {fileID: 3376399835161366642, guid: 5b36edd38769f254e8ee3f629b1741a6, type: 3} - propertyPath: m_AnchoredPosition.y - value: -20 + propertyPath: m_LocalRotation.w + value: 1 objectReference: {fileID: 0} - target: {fileID: 3376399835161366642, guid: 5b36edd38769f254e8ee3f629b1741a6, type: 3} - propertyPath: m_SizeDelta.x + propertyPath: m_LocalRotation.x value: 0 objectReference: {fileID: 0} - target: {fileID: 3376399835161366642, guid: 5b36edd38769f254e8ee3f629b1741a6, type: 3} - propertyPath: m_SizeDelta.y + propertyPath: m_LocalRotation.y value: 0 objectReference: {fileID: 0} - target: {fileID: 3376399835161366642, guid: 5b36edd38769f254e8ee3f629b1741a6, type: 3} - propertyPath: m_AnchorMin.x + propertyPath: m_LocalRotation.z value: 0 objectReference: {fileID: 0} - target: {fileID: 3376399835161366642, guid: 5b36edd38769f254e8ee3f629b1741a6, type: 3} - propertyPath: m_AnchorMin.y - value: 1 + propertyPath: m_AnchoredPosition.x + value: 20 objectReference: {fileID: 0} - target: {fileID: 3376399835161366642, guid: 5b36edd38769f254e8ee3f629b1741a6, type: 3} - propertyPath: m_AnchorMax.x - value: 0 + propertyPath: m_AnchoredPosition.y + value: -20 objectReference: {fileID: 0} - target: {fileID: 3376399835161366642, guid: 5b36edd38769f254e8ee3f629b1741a6, type: 3} - propertyPath: m_AnchorMax.y - value: 1 + propertyPath: m_LocalEulerAnglesHint.x + value: 0 objectReference: {fileID: 0} - target: {fileID: 3376399835161366642, guid: 5b36edd38769f254e8ee3f629b1741a6, type: 3} - propertyPath: m_Pivot.x + propertyPath: m_LocalEulerAnglesHint.y value: 0 objectReference: {fileID: 0} - target: {fileID: 3376399835161366642, guid: 5b36edd38769f254e8ee3f629b1741a6, type: 3} - propertyPath: m_Pivot.y - value: 1 + propertyPath: m_LocalEulerAnglesHint.z + value: 0 objectReference: {fileID: 0} - target: {fileID: 3376399835161366643, guid: 5b36edd38769f254e8ee3f629b1741a6, type: 3} From 29f6194274e7d02a71ee77edbd48bc247188a48a Mon Sep 17 00:00:00 2001 From: Marsette Vona Date: Wed, 2 Jun 2021 10:23:26 -0400 Subject: [PATCH 5/5] bugfix and tune tile priorities --- Assets/Examples/Scripts/DemoUX.cs | 8 +- .../Unity3DTiles/AbstractTilesetBehaviour.cs | 10 +- Assets/Unity3DTiles/RequestManager.cs | 5 +- Assets/Unity3DTiles/Unity3DTile.cs | 134 +++++++++++------- .../Unity3DTiles/Unity3DTilesetTraversal.cs | 73 ++++++---- 5 files changed, 143 insertions(+), 87 deletions(-) diff --git a/Assets/Examples/Scripts/DemoUX.cs b/Assets/Examples/Scripts/DemoUX.cs index b5d8a12..4e7ee0c 100644 --- a/Assets/Examples/Scripts/DemoUX.cs +++ b/Assets/Examples/Scripts/DemoUX.cs @@ -845,10 +845,16 @@ public void OnClick(Vector3 mousePosition) var go = hit.collider.transform.gameObject; while (go != null) { - var ti = go.GetComponent(); + var ti = go.GetComponent(); if (ti != null) { selectedTile = ti.Tile; +#if UNITY_EDITOR + if (Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl)) + { + UnityEditor.Selection.activeGameObject = go; + } +#endif break; } go = go.transform.parent != null ? go.transform.parent.gameObject : null; diff --git a/Assets/Unity3DTiles/AbstractTilesetBehaviour.cs b/Assets/Unity3DTiles/AbstractTilesetBehaviour.cs index 4b4bfd8..2df68c4 100644 --- a/Assets/Unity3DTiles/AbstractTilesetBehaviour.cs +++ b/Assets/Unity3DTiles/AbstractTilesetBehaviour.cs @@ -118,11 +118,15 @@ private void DerateUnusedTilePriority(Unity3DTile tile) { tile.FrameState.Priority = float.MaxValue; } - else + else if (!float.IsNaN(tile.FrameState.Priority) && tile.FrameState.Priority > 0 && + tile.FrameState.Priority < float.MaxValue) { float maxFrames = 1000; - float framesSinceUsed = Mathf.Min(Time.frameCount - tile.FrameState.LastVisitedFrame, maxFrames); - tile.FrameState.Priority = (1 + (framesSinceUsed / maxFrames)); + float framesSinceUsed = Time.frameCount - tile.FrameState.LastVisitedFrame; + if (framesSinceUsed > 0 && framesSinceUsed <= maxFrames) + { + tile.FrameState.Priority *= (maxFrames + framesSinceUsed) / (maxFrames + framesSinceUsed - 1); + } } } } diff --git a/Assets/Unity3DTiles/RequestManager.cs b/Assets/Unity3DTiles/RequestManager.cs index 649643b..824321b 100644 --- a/Assets/Unity3DTiles/RequestManager.cs +++ b/Assets/Unity3DTiles/RequestManager.cs @@ -189,8 +189,7 @@ public void Process(int maxNewRequests) //re-sort queue from high to low priority (high priority = low value) //because priorities may change from frame to frame - //also cull any duplicates, drop any requests that are now offscreen (priority = MaxValue) - //and drop any requests that exceed MAX_QUEUE_SIZE + //also cull any duplicates and drop any requests that exceed MAX_QUEUE_SIZE tmpList.Clear(); tmpSet.Clear(); tmpList.AddRange(queue); @@ -199,7 +198,7 @@ public void Process(int maxNewRequests) tmpSet.Clear(); for (int i = 0; i < tmpList.Count; i++) { - if (queue.Count < MAX_QUEUE_SIZE && tmpList[i].Tile.FrameState.Priority < float.MaxValue) + if (queue.Count < MAX_QUEUE_SIZE) { if (!tmpSet.Contains(tmpList[i].Tile)) { diff --git a/Assets/Unity3DTiles/Unity3DTile.cs b/Assets/Unity3DTiles/Unity3DTile.cs index 94cf4cc..fb77b5f 100644 --- a/Assets/Unity3DTiles/Unity3DTile.cs +++ b/Assets/Unity3DTiles/Unity3DTile.cs @@ -37,11 +37,90 @@ public enum Unity3DTileContentType Unknown } - public class TileInfo : MonoBehaviour + [Serializable] + public class Unity3DTileFrameState { + public int LastVisitedFrame = -1; + + public bool InFrustumSet = false; // Currently in view of a camera (And in this frames "Used set") + public bool InUsedSet = false; // should be checked with IsUsedThisFrame + public bool IsUsedSetLeaf = false; + public bool InRenderSet = false; // This tile should be rendered this frame + public bool InColliderSet = false; // This tile should have its collider enabled this frame + public bool UsedLastFrame = false; // This tile was in the used set last frame + public float DistanceToCamera = float.MaxValue; + public float PixelsToCameraCenter = float.MaxValue; + public float ScreenSpaceError = 0; + public float Priority = float.MaxValue; // lower value means higher priority + + public void MarkUsed() + { + InUsedSet = true; + } + + public bool IsUsedThisFrame + { + get + { + return InUsedSet && LastVisitedFrame == Time.frameCount; + } + } + + public void Reset() + { + if (LastVisitedFrame == Time.frameCount) + { + return; + } + LastVisitedFrame = Time.frameCount; + InUsedSet = false; + InFrustumSet = false; + IsUsedSetLeaf = false; + InRenderSet = false; + InColliderSet = false; + DistanceToCamera = float.MaxValue; + PixelsToCameraCenter = float.MaxValue; + ScreenSpaceError = 0; + Priority = float.MaxValue; + } + } + + public class Unity3DTileInfo : MonoBehaviour + { + public Unity3DTileFrameState FrameState; public Unity3DTile Tile; } +#if UNITY_EDITOR + [UnityEditor.CustomEditor(typeof(Unity3DTileInfo))] + public class Unity3DTileInfoEditor : UnityEditor.Editor + { + private UnityEditor.SerializedProperty frameState; + + public void OnEnable() + { + frameState = serializedObject.FindProperty("FrameState"); + } + + public override void OnInspectorGUI() + { + var ti = target as Unity3DTileInfo; + //DrawDefaultInspector(); + UnityEditor.EditorGUILayout.LabelField("Used This Frame", ti.FrameState.IsUsedThisFrame.ToString()); + UnityEditor.EditorGUILayout.LabelField("Id", ti.Tile.Id); + UnityEditor.EditorGUILayout.LabelField("Parent", ti.Tile.Parent != null ? ti.Tile.Parent.Id : "null"); + UnityEditor.EditorGUILayout.LabelField("Depth", ti.Tile.Depth.ToString()); + UnityEditor.EditorGUILayout.LabelField("Geometric Error", ti.Tile.GeometricError.ToString()); + UnityEditor.EditorGUILayout.LabelField("Refine", ti.Tile.Refine.ToString()); + UnityEditor.EditorGUILayout.LabelField("Has Empty Content", ti.Tile.HasEmptyContent.ToString()); + UnityEditor.EditorGUILayout.LabelField("Content Url", ti.Tile.ContentUrl); + UnityEditor.EditorGUILayout.LabelField("Content Type", ti.Tile.ContentType.ToString()); + UnityEditor.EditorGUILayout.LabelField("Content State", ti.Tile.ContentState.ToString()); + UnityEditor.EditorGUILayout.PropertyField(frameState); + } + } +#endif + public class Unity3DTile { @@ -107,55 +186,7 @@ public Unity3DTileContentType ContentType private int hashCode; - public class TileFrameState - { - public int LastVisitedFrame = -1; - - public bool InFrustumSet = false; // Currently in view of a camera (And in this frames "Used set") - private bool InUsedSet = false; // Relevant to rendering or collisons this frame, private because this should be checked with IsUsedThisFrame to void reading stale values - public bool IsUsedSetLeaf = false; // This tile is at the maximum LOD this frame given our screen space error requirements - public bool InRenderSet = false; // This tile should be rendered this frame - public bool InColliderSet = false; // This tile should have its collider enabled this frame - public bool UsedLastFrame = false; // This tile was in the used set last frame and may need to be deactivated next frame - public float DistanceToCamera = float.MaxValue; - public float PixelsToCameraCenter = float.MaxValue; - public float ScreenSpaceError = 0; - - public float Priority = float.MaxValue; // lower value means higher priority - - public void MarkUsed() - { - InUsedSet = true; - } - - public bool IsUsedThisFrame - { - get - { - return InUsedSet && LastVisitedFrame == Time.frameCount; - } - } - - public void Reset() - { - if (LastVisitedFrame == Time.frameCount) - { - return; - } - LastVisitedFrame = Time.frameCount; - InUsedSet = false; - InFrustumSet = false; - IsUsedSetLeaf = false; - InRenderSet = false; - InColliderSet = false; - DistanceToCamera = float.MaxValue; - PixelsToCameraCenter = float.MaxValue; - ScreenSpaceError = 0; - Priority = float.MaxValue; - } - } - - public TileFrameState FrameState = new TileFrameState(); + public Unity3DTileFrameState FrameState = new Unity3DTileFrameState(); public void MarkUsed() { @@ -301,8 +332,9 @@ public void RequestContent(float priority) go.transform.localScale = computedTransform.lossyScale; go.layer = Tileset.Behaviour.gameObject.layer; go.SetActive(false); - var info = go.AddComponent(); + var info = go.AddComponent(); info.Tile = this; + info.FrameState = FrameState; Content = new Unity3DTileContent(go); if (ContentType == Unity3DTileContentType.B3DM) diff --git a/Assets/Unity3DTiles/Unity3DTilesetTraversal.cs b/Assets/Unity3DTiles/Unity3DTilesetTraversal.cs index a93a87c..64fbfbc 100644 --- a/Assets/Unity3DTiles/Unity3DTilesetTraversal.cs +++ b/Assets/Unity3DTiles/Unity3DTilesetTraversal.cs @@ -86,10 +86,10 @@ public void Run() } } - AssignPrioritiesRecursively(tileset.Root); - MarkUsedSetLeaves(tileset.Root); + AssignPrioritiesRecursively(tileset.Root); + SkipTraversal(tileset.Root); ToggleTiles(tileset.Root); @@ -193,9 +193,11 @@ private bool DetermineFrustumSet(Unity3DTile tile, Plane[] planes, SSECalculator { // Check to see if this tile meets the on screen error level of detail requirement float distance = tile.BoundingVolume.MinDistanceTo(cameraPosInTilesetFrame); + // We take the min in case multiple cameras, reset dist to max float on frame reset tile.FrameState.DistanceToCamera = Mathf.Min(distance, tile.FrameState.DistanceToCamera); - tile.FrameState.ScreenSpaceError = sse.PixelError(tile.GeometricError, distance); + tile.FrameState.ScreenSpaceError = + sse.PixelError(tile.GeometricError, tile.FrameState.DistanceToCamera); Ray cameraRay = new Ray(cameraPosInTilesetFrame, cameraFwdInTilesetFrame); float distToAxis = tile.BoundingVolume.CenterDistanceTo(cameraRay); @@ -296,6 +298,7 @@ void SkipTraversal(Unity3DTile tile) { return; } + if (tile.FrameState.IsUsedSetLeaf) { if (tile.ContentState == Unity3DTileContentState.READY) @@ -332,10 +335,12 @@ void SkipTraversal(Unity3DTile tile) allChildrenHaveContent = allChildrenHaveContent && childContent; } } + if (meetsSSE && !hasContent) { RequestTile(tile); } + if (meetsSSE && hasContent && !allChildrenHaveContent) { if (tile.FrameState.InFrustumSet) @@ -355,6 +360,7 @@ void SkipTraversal(Unity3DTile tile) } return; } + // Otherwise keep decending for (int i = 0; i < tile.Children.Count; i++) { @@ -425,7 +431,7 @@ int DepthFromFirstUsedAncestor(Unity3DTile tile) return 1 + DepthFromFirstUsedAncestor(tile.Parent); } - float MinUsedAncestorPixelsToCameraCenter(Unity3DTile tile, float pixels = float.MaxValue) + float MinUsedAncestorPixelsToCameraCenter(Unity3DTile tile, float pixels = 10000) { if (!tile.FrameState.IsUsedThisFrame || tile.Parent == null || !tile.Parent.FrameState.IsUsedThisFrame) { @@ -442,7 +448,7 @@ float TilePriority(Unity3DTile tile) return 0; } - if (!tile.FrameState.IsUsedThisFrame) + if (!tile.FrameState.IsUsedThisFrame || !tile.FrameState.InFrustumSet || tile.HasEmptyContent) { return float.MaxValue; } @@ -452,31 +458,40 @@ float TilePriority(Unity3DTile tile) return tilesetOptions.TilePriority(tile); } - //prioritize by distance from camera - //return tile.FrameState.DistanceToCamera; - //return tile.FrameState.PixelsToCameraCenter; - //return MinUsedAncestorPixelsToCameraCenter(tile); - - //prioritize coarse to fine - //return tile.Depth; - //return DepthFromFirstUsedAncestor(tile); - - //prioritize first coarse to fine, then by distance - //int depth = tileDepth; - //int depth = DepthFromFirstUsedAncestor(tile); //integer part - //float distLimit = 1000; - //float dist = Mathf.Min(tile.FrameState.DistanceToCamera, distLimit); - //float relDist = dist / distLimit; //fractional part - //return depth + relDist; - - //prioritize first by distance, then coarse to fine - float quantizedDist = (int)(tile.FrameState.PixelsToCameraCenter / 100); - //float quantizedDist = (int)(MinUsedAncestorPixelsToCameraCenter(tile) / 100); + float distLimit = 1000; + float d2c = tile.FrameState.DistanceToCamera; + d2c = float.IsNaN(d2c) ? distLimit : Mathf.Max(0, Mathf.Min(distLimit, d2c)); + + float pixelsLimit = 10000; + float p2c = tile.FrameState.PixelsToCameraCenter; + //float p2c = MinUsedAncestorPixelsToCameraCenter(tile, pixelsLimit); + p2c = float.IsNaN(p2c) ? pixelsLimit : Mathf.Max(0, Mathf.Min(pixelsLimit, p2c)); + float depthLimit = 100; - float depth = Math.Min(tile.Depth, depthLimit); - //float depth = Math.Min(DepthFromFirstUsedAncestor(tile), depthLimit); - float relDepth = depth / depthLimit; //fractional part - return quantizedDist + relDepth; + float depth = tile.Depth; + //float depth = DepthFromFirstUsedAncestor(tile); + depth = float.IsNaN(depth) ? depthLimit : Mathf.Max(0, Mathf.Min(depthLimit, depth)); + + //prioritize by distance from camera + //return d2c; + + //prioritize by pixels from camera + //return p2c; + + //prioritize by depth + //return depth; + + //prioritize first by depth, then pixels to camera + //return depth + (p2c / pixelsLimit); + + //prioritize first by pixels to camera, then depth + //return (int)(p2c / 100) + (depth / depthLimit); + + //prioritize first by used set leaf, then distance to camera, then depth, then pixels to camera + return (tile.FrameState.IsUsedSetLeaf ? 0 : 100) + + (d2c < 1 ? 0 : 10) + + (int)(9 * depth / depthLimit) + + (p2c / pixelsLimit); }