From d683e4d0d8fa52abb91d000966aa44c3bb160f20 Mon Sep 17 00:00:00 2001
From: Sean Lilley
Date: Tue, 20 Feb 2018 16:54:21 -0500
Subject: [PATCH 01/53] Change request queue from a heap to a sorted array
---
Source/Core/RequestQueue.js | 186 ++++++++++++++++++++++++++++
Source/Core/RequestScheduler.js | 82 ++++++-------
Specs/Core/HeapSpec.js | 174 --------------------------
Specs/Core/RequestQueueSpec.js | 189 +++++++++++++++++++++++++++++
Specs/Core/RequestSchedulerSpec.js | 38 +++---
5 files changed, 435 insertions(+), 234 deletions(-)
create mode 100644 Source/Core/RequestQueue.js
delete mode 100644 Specs/Core/HeapSpec.js
create mode 100644 Specs/Core/RequestQueueSpec.js
diff --git a/Source/Core/RequestQueue.js b/Source/Core/RequestQueue.js
new file mode 100644
index 000000000000..169483d3881c
--- /dev/null
+++ b/Source/Core/RequestQueue.js
@@ -0,0 +1,186 @@
+define([
+ './Check',
+ './defineProperties'
+ ], function(
+ Check,
+ defineProperties) {
+ 'use strict';
+
+ /**
+ * Priority queue for the {@link RequestScheduler} implemented as a sorted array.
+ * The request with the highest priority is placed at index 0 and the request
+ * with lowest priority is placed at index length - 1
.
+ *
+ * A lower request.priority
value indicates that the request has higher priority. See {@link Request#priority}.
+ *
+ *
+ * @alias RequestQueue
+ * @constructor
+ * @private
+ *
+ * @param {Number} maximumLength The maximum length of the queue.
+ */
+ function RequestQueue(maximumLength) {
+ //>>includeStart('debug', pragmas.debug);
+ Check.typeOf.number('maximumLength', maximumLength);
+ //>>includeEnd('debug');
+
+ this._array = new Array(maximumLength);
+ this._length = 0;
+ this._maximumLength = maximumLength;
+ }
+
+ defineProperties(RequestQueue.prototype, {
+ /**
+ * Gets the length of the queue.
+ *
+ * @memberof RequestQueue.prototype
+ *
+ * @type {Number}
+ * @readonly
+ */
+ length : {
+ get : function() {
+ return this._length;
+ }
+ }
+ });
+
+ /**
+ * Get the request at the given index.
+ *
+ * @param {Number} index The index of the request.
+ *
+ * @return {Request} The request at the given index.
+ */
+ RequestQueue.prototype.get = function(index) {
+ //>>includeStart('debug', pragmas.debug);
+ Check.typeOf.number.greaterThanOrEquals('index', index, 0);
+ Check.typeOf.number.lessThan('index', index, this._length);
+ //>>includeEnd('debug');
+ return this._array[index];
+ };
+
+ /**
+ * Insert a request into the queue. If the length would grow greater than the maximum length
+ * of the queue, the lowest priority request is removed and returned.
+ *
+ * @param {Request} request The request to insert.
+ *
+ * @return {Request|undefined} The request that was removed from the queue if the queue is at full capacity.
+ */
+ RequestQueue.prototype.insert = function(request) {
+ //>>includeStart('debug', pragmas.debug);
+ Check.defined('request', request);
+ //>>includeEnd('debug');
+
+ var array = this._array;
+ var previousLength = this._length;
+ var length = this._length;
+ var maximumLength = this._maximumLength;
+
+ if (length < maximumLength)
+ {
+ ++this._length;
+ }
+
+ if (previousLength === 0)
+ {
+ array[0] = request;
+ return;
+ }
+
+ var removedRequest;
+ var lastIndex = previousLength - 1;
+
+ if (previousLength === maximumLength) {
+ var lastRequest = array[lastIndex];
+ if (request.priority >= lastRequest.priority) {
+ // The array is full and the priority value of this request is too high to be inserted.
+ return request;
+ }
+ // The array is full and the inserted request pushes off the last request
+ removedRequest = lastRequest;
+ --lastIndex;
+ }
+
+ while (lastIndex >= 0 && request.priority < array[lastIndex].priority) {
+ array[lastIndex + 1] = array[lastIndex]; // Shift element to the right
+ --lastIndex;
+ }
+ array[lastIndex + 1] = request;
+
+ return removedRequest;
+ };
+
+ /**
+ * Call the given function for each request in the queue.
+ *
+ * @type {RequestQueue~ForEachCallback} The function to call.
+ */
+ RequestQueue.prototype.forEach = function(callback) {
+ //>>includeStart('debug', pragmas.debug);
+ Check.typeOf.func('callback', callback);
+ //>>includeEnd('debug');
+
+ var array = this._array;
+ var length = this._length;
+ for (var i = 0; i < length; ++i) {
+ callback(array[i]);
+ }
+ };
+
+ /**
+ * Sorts the queue.
+ */
+ RequestQueue.prototype.sort = function() {
+ var array = this._array;
+ var length = this._length;
+
+ // Use insertion sort since our array is small and likely to be mostly sorted already.
+ // Additionally length may be smaller than the array's actual length, so calling array.sort will lead to incorrect results for uninitialized values.
+ for (var i = 1; i < length; ++i) {
+ var j = i;
+ while ((j > 0) && (array[j - 1].priority > array[j].priority)) {
+ var temp = array[j - 1];
+ array[j - 1] = array[j];
+ array[j] = temp;
+ --j;
+ }
+ }
+ };
+
+ /**
+ * Remove length
number of requests from the top of the queue.
+ *
+ * @param {Number} length The number of requests to remove.
+ */
+ RequestQueue.prototype.remove = function(length) {
+ //>>includeStart('debug', pragmas.debug);
+ Check.typeOf.number.greaterThanOrEquals('length', length, 0);
+ Check.typeOf.number.lessThanOrEquals('length', length, this._length);
+ //>>includeEnd('debug');
+ if (length === 0) {
+ return;
+ }
+ if (length === this._length) {
+ this._length = 0;
+ return;
+ }
+
+ // Shift remaining requests back to the left
+ var array = this._array;
+ for (var i = length; i < this._length; ++i) {
+ array[i - length] = array[i];
+ }
+ this._length -= length;
+ };
+
+ /**
+ * The callback to use in forEach.
+ * @callback RequestQueue~ForEachCallback
+ * @param {Request} request The request.
+ */
+
+ return RequestQueue;
+});
diff --git a/Source/Core/RequestScheduler.js b/Source/Core/RequestScheduler.js
index 10ccb51b9a0e..2f9320f899fa 100644
--- a/Source/Core/RequestScheduler.js
+++ b/Source/Core/RequestScheduler.js
@@ -5,9 +5,9 @@ define([
'./defined',
'./defineProperties',
'./Event',
- './Heap',
'./isBlobUri',
'./isDataUri',
+ './RequestQueue',
'./RequestState'
], function(
Uri,
@@ -16,16 +16,12 @@ define([
defined,
defineProperties,
Event,
- Heap,
isBlobUri,
isDataUri,
+ RequestQueue,
RequestState) {
'use strict';
- function sortRequests(a, b) {
- return a.priority - b.priority;
- }
-
var statistics = {
numberOfAttemptedRequests : 0,
numberOfActiveRequests : 0,
@@ -35,12 +31,8 @@ define([
numberOfActiveRequestsEver : 0
};
- var priorityHeapLength = 20;
- var requestHeap = new Heap({
- comparator : sortRequests
- });
- requestHeap.maximumLength = priorityHeapLength;
- requestHeap.reserve(priorityHeapLength);
+ var requestQueueLength = 20;
+ var requestQueue = new RequestQueue(requestQueueLength);
var activeRequests = [];
var numberOfActiveRequestsByServer = {};
@@ -112,29 +104,28 @@ define([
},
/**
- * The maximum size of the priority heap. This limits the number of requests that are sorted by priority. Only applies to requests that are not yet active.
+ * The maximum length of the request queue. This limits the number of requests that are sorted by priority. Only applies to requests that are not yet active.
*
* @memberof RequestScheduler
*
* @type {Number}
* @default 20
+ *
+ * @private
*/
- priorityHeapLength : {
+ requestQueueLength : {
get : function() {
- return priorityHeapLength;
+ return requestQueueLength;
},
set : function(value) {
- // If the new length shrinks the heap, need to cancel some of the requests.
- // Since this value is not intended to be tweaked regularly it is fine to just cancel the high priority requests.
- if (value < priorityHeapLength) {
- while (requestHeap.length > value) {
- var request = requestHeap.pop();
- cancelRequest(request);
- }
+ // Cancel all requests and resize the queue
+ var length = requestQueue.length;
+ for (var i = 0; i < length; ++i) {
+ var request = requestQueue.get(i);
+ cancelRequest(request);
}
- priorityHeapLength = value;
- requestHeap.maximumLength = value;
- requestHeap.reserve(value);
+ requestQueue = new RequestQueue(value);
+ RequestScheduler.requestQueue = requestQueue;
}
}
});
@@ -242,21 +233,19 @@ define([
}
activeRequests.length -= removeCount;
- // Update priority of issued requests and resort the heap
- var issuedRequests = requestHeap.internalArray;
- var issuedLength = requestHeap.length;
- for (i = 0; i < issuedLength; ++i) {
- updatePriority(issuedRequests[i]);
- }
- requestHeap.resort();
+ // Update priority of issued requests and resort the queue
+ requestQueue.forEach(updatePriority);
+ requestQueue.sort();
// Get the number of open slots and fill with the highest priority requests.
// Un-throttled requests are automatically added to activeRequests, so activeRequests.length may exceed maximumRequests
var openSlots = Math.max(RequestScheduler.maximumRequests - activeRequests.length, 0);
var filledSlots = 0;
- while (filledSlots < openSlots && requestHeap.length > 0) {
- // Loop until all open slots are filled or the heap becomes empty
- request = requestHeap.pop();
+ var processedRequests = 0;
+ var totalRequests = requestQueue.length;
+ while (filledSlots < openSlots && processedRequests < totalRequests) {
+ // Loop until all open slots are filled or the queue becomes empty
+ request = requestQueue.get(processedRequests++);
if (request.cancelled) {
// Request was explicitly cancelled
cancelRequest(request);
@@ -272,6 +261,7 @@ define([
startRequest(request);
++filledSlots;
}
+ requestQueue.remove(processedRequests);
updateStatistics();
};
@@ -344,10 +334,9 @@ define([
return undefined;
}
- // Insert into the priority heap and see if a request was bumped off. If this request is the lowest
- // priority it will be returned.
+ // Insert into the priority queue and see if a request was bumped off. If this request is the lowest priority it will be returned.
updatePriority(request);
- var removedRequest = requestHeap.insert(request);
+ var removedRequest = requestQueue.insert(request);
if (defined(removedRequest)) {
if (removedRequest === request) {
@@ -397,12 +386,19 @@ define([
* @private
*/
RequestScheduler.clearForSpecs = function() {
- while (requestHeap.length > 0) {
- var request = requestHeap.pop();
+ var request;
+ var length;
+ var i;
+
+ length = requestQueue.length;
+ for (i = 0; i < length; ++i) {
+ request = requestQueue.get(i);
cancelRequest(request);
}
- var length = activeRequests.length;
- for (var i = 0; i < length; ++i) {
+ requestQueue.remove(length);
+
+ length = activeRequests.length;
+ for (i = 0; i < length; ++i) {
cancelRequest(activeRequests[i]);
}
activeRequests.length = 0;
@@ -431,7 +427,7 @@ define([
*
* @private
*/
- RequestScheduler.requestHeap = requestHeap;
+ RequestScheduler.requestQueue = requestQueue;
return RequestScheduler;
});
diff --git a/Specs/Core/HeapSpec.js b/Specs/Core/HeapSpec.js
deleted file mode 100644
index 1b77e269652e..000000000000
--- a/Specs/Core/HeapSpec.js
+++ /dev/null
@@ -1,174 +0,0 @@
-defineSuite([
- 'Core/Heap'
- ], function(
- Heap) {
- 'use strict';
-
- var length = 100;
-
- function checkHeap(heap, comparator) {
- var array = heap.internalArray;
- var pass = true;
- var length = heap.length;
- for (var i = 0; i < length; ++i) {
- var left = 2 * (i + 1) - 1;
- var right = 2 * (i + 1);
- if (left < heap.length) {
- pass = pass && (comparator(array[i], array[left]) <= 0);
- }
- if (right < heap.length) {
- pass = pass && (comparator(array[i], array[right]) <= 0);
- }
- }
- return pass;
- }
-
- // min heap
- function comparator(a, b) {
- return a - b;
- }
-
- it('maintains heap property on insert', function() {
- var heap = new Heap({
- comparator : comparator
- });
- var pass = true;
- for (var i = 0; i < length; ++i) {
- heap.insert(Math.random());
- pass = pass && checkHeap(heap, comparator);
- }
-
- expect(pass).toBe(true);
- });
-
- it('maintains heap property on pop', function() {
- var heap = new Heap({
- comparator : comparator
- });
- var i;
- for (i = 0; i < length; ++i) {
- heap.insert(Math.random());
- }
- var pass = true;
- for (i = 0; i < length; ++i) {
- heap.pop();
- pass = pass && checkHeap(heap, comparator);
- }
- expect(pass).toBe(true);
- });
-
- it('limited by maximum length', function() {
- var heap = new Heap({
- comparator : comparator
- });
- heap.maximumLength = length / 2;
- var pass = true;
- for (var i = 0; i < length; ++i) {
- heap.insert(Math.random());
- pass = pass && checkHeap(heap, comparator);
- }
- expect(pass).toBe(true);
- expect(heap.length <= heap.maximumLength).toBe(true);
- // allowed one extra slot for swapping
- expect(heap.internalArray.length).toBeLessThanOrEqualTo(heap.maximumLength + 1);
- });
-
- it('pops in sorted order', function() {
- var heap = new Heap({
- comparator : comparator
- });
- var i;
- for (i = 0; i < length; ++i) {
- heap.insert(Math.random());
- }
- var curr = heap.pop();
- var pass = true;
- for (i = 0; i < length - 1; ++i) {
- var next = heap.pop();
- pass = pass && (comparator(curr, next) <= 0);
- curr = next;
- }
- expect(pass).toBe(true);
- });
-
- it('insert returns the removed element when maximumLength is set', function() {
- var heap = new Heap({
- comparator : comparator
- });
- heap.maximumLength = length;
-
- var i;
- var max = 0.0;
- var min = 1.0;
- var values = new Array(length);
- for (i = 0; i < length; ++i) {
- var value = Math.random();
- max = Math.max(max, value);
- min = Math.min(min, value);
- values[i] = value;
- }
-
- // Push 99 values
- for (i = 0; i < length - 1; ++i) {
- heap.insert(values[i]);
- }
-
- // Push 100th, nothing is removed so it returns undefined
- var removed = heap.insert(values[length - 1]);
- expect(removed).toBeUndefined();
-
- // Insert value, an element is removed
- removed = heap.insert(max - 0.1);
- expect(removed).toBeDefined();
-
- // If this value is the least priority it will be returned
- removed = heap.insert(max + 0.1);
- expect(removed).toBe(max + 0.1);
- });
-
- it('resort', function() {
- function comparator(a, b) {
- return a.distance - b.distance;
- }
-
- var i;
- var heap = new Heap({
- comparator : comparator
- });
- for (i = 0; i < length; ++i) {
- heap.insert({
- distance : i / (length - 1),
- id : i
- });
- }
-
- // Check that elements are initially sorted
- var element;
- var elements = [];
- var currentId = 0;
- while (heap.length > 0) {
- element = heap.pop();
- elements.push(element);
- expect(element.id).toBeGreaterThanOrEqualTo(currentId);
- currentId = element.id;
- }
-
- // Add back into heap
- for (i = 0; i < length; ++i) {
- heap.insert(elements[i]);
- }
-
- // Invert priority
- for (i = 0; i < length; ++i) {
- elements[i].distance = 1.0 - elements[i].distance;
- }
-
- // Resort and check the the elements are popped in the opposite order now
- heap.resort();
- while (heap.length > 0) {
- element = heap.pop();
- expect(element.id).toBeLessThanOrEqualTo(currentId);
- currentId = element.id;
- }
- });
-});
diff --git a/Specs/Core/RequestQueueSpec.js b/Specs/Core/RequestQueueSpec.js
new file mode 100644
index 000000000000..a7bb51107e7e
--- /dev/null
+++ b/Specs/Core/RequestQueueSpec.js
@@ -0,0 +1,189 @@
+defineSuite([
+ 'Core/RequestQueue',
+ 'Core/Math',
+ 'Core/Request'
+ ], function(
+ RequestQueue,
+ CesiumMath,
+ Request) {
+ 'use strict';
+
+ var length = 20;
+
+ function createRequest(distance) {
+ return new Request({
+ priority : distance
+ });
+ }
+
+ function isSorted(queue) {
+ var distance = Number.NEGATIVE_INFINITY;
+ for (var i = 0; i < queue.length; ++i) {
+ var requestDistance = queue.get(i).priority;
+ if (requestDistance < distance) {
+ return false;
+ }
+
+ distance = requestDistance;
+ }
+ return true;
+ }
+
+ it('sets initial values', function() {
+ var queue = new RequestQueue(length);
+ expect(queue._array.length).toBe(length);
+ expect(queue._length).toBe(0);
+ expect(queue._maximumLength).toBe(length);
+ });
+
+ it('gets length', function() {
+ var queue = new RequestQueue(length);
+ expect(queue.length).toBe(0);
+ queue.insert(createRequest(1.0));
+ expect(queue.length).toBe(1);
+ });
+
+ it('get', function() {
+ var queue = new RequestQueue(length);
+ queue.insert(createRequest(1));
+ queue.insert(createRequest(0));
+ expect(queue.get(0).priority).toBe(0);
+ expect(queue.get(1).priority).toBe(1);
+ });
+
+ it('insert', function() {
+ var removedRequest;
+ var request;
+ var i;
+
+ CesiumMath.setRandomNumberSeed(0.0);
+ var distances = new Array(length);
+ for (i = 0; i < length; ++i) {
+ distances[i] = CesiumMath.nextRandomNumber();
+ }
+ distances.sort();
+ var lowestDistance = distances[0];
+ var highestDistance = distances[distances.length - 1];
+
+ var queue = new RequestQueue(length);
+ for (i = 0; i < length; ++i) {
+ removedRequest = queue.insert(createRequest(distances[i]));
+ expect(removedRequest).toBeUndefined(); // Requests are not removed until the queue is full
+ }
+
+ expect(isSorted(queue)).toBe(true);
+
+ request = createRequest(highestDistance);
+ expect(queue.insert(request)).toBe(request);
+
+ request = createRequest(highestDistance + 1.0);
+ expect(queue.insert(request)).toBe(request);
+
+ request = createRequest(lowestDistance);
+ expect(queue.insert(request).priority).toBe(highestDistance);
+ expect(queue.get(0).priority).toBe(lowestDistance);
+ expect(queue.get(1).priority).toBe(lowestDistance);
+ expect(queue.get(2).priority).toBeGreaterThan(lowestDistance);
+
+ expect(isSorted(queue)).toBe(true);
+ });
+
+ it('forEach', function() {
+ var total = 0;
+ var queue = new RequestQueue(length);
+ for (var i = 0; i < length; ++i) {
+ queue.insert(createRequest(1));
+ }
+ queue.forEach(function(request) {
+ total += request.priority;
+ });
+ expect(total).toBe(length);
+ });
+
+ it('sort', function() {
+ var i;
+ CesiumMath.setRandomNumberSeed(0.0);
+ var queue = new RequestQueue(length);
+ for (i = 0; i < length / 2; ++i) {
+ queue.insert(createRequest(CesiumMath.nextRandomNumber()));
+ }
+ queue.forEach(function(request) {
+ request.priority = CesiumMath.nextRandomNumber();
+ });
+ expect(isSorted(queue)).toBe(false);
+ queue.sort();
+ expect(isSorted(queue)).toBe(true);
+ });
+
+ it('remove', function() {
+ var queue = new RequestQueue(length);
+ for (var i = 0; i < length; ++i) {
+ queue.insert(createRequest(i));
+ }
+ queue.remove(0);
+ expect(queue.get(0).priority).toBe(0);
+ expect(queue.get(1).priority).toBe(1);
+ expect(queue.length).toBe(length);
+
+ queue.remove(1);
+ expect(queue.get(0).priority).toBe(1);
+ expect(queue.get(1).priority).toBe(2);
+ expect(queue.length).toBe(length - 1);
+
+ queue.remove(2);
+ expect(queue.get(0).priority).toBe(3);
+ expect(queue.get(1).priority).toBe(4);
+ expect(queue.length).toBe(length - 3);
+
+ queue.remove(17);
+ expect(queue.length).toBe(0);
+ });
+
+ it('throws if maximumLength is undefined', function() {
+ expect(function() {
+ return new RequestQueue();
+ }).toThrowDeveloperError();
+ });
+
+ it('throws if get index is out of range', function() {
+ expect(function() {
+ var queue = new RequestQueue(length);
+ queue.get(0);
+ }).toThrowDeveloperError();
+
+ expect(function() {
+ var queue = new RequestQueue(length);
+ queue.insert(createRequest(0.0));
+ queue.get(1);
+ }).toThrowDeveloperError();
+
+ expect(function() {
+ var queue = new RequestQueue(length);
+ queue.insert(createRequest(0.0));
+ queue.get(-1);
+ }).toThrowDeveloperError();
+ });
+
+ it('throws if forEach callback is not a function', function() {
+ expect(function() {
+ var queue = new RequestQueue(length);
+ queue.forEach();
+ }).toThrowDeveloperError();
+ expect(function() {
+ var queue = new RequestQueue(length);
+ queue.forEach(5);
+ }).toThrowDeveloperError();
+ });
+
+ it('throws if remove length is out of range', function() {
+ expect(function() {
+ var queue = new RequestQueue(length);
+ queue.remove(1);
+ }).toThrowDeveloperError();
+
+ expect(function() {
+ var queue = new RequestQueue(length);
+ queue.remove(-1);
+ }).toThrowDeveloperError();
+ });
+});
diff --git a/Specs/Core/RequestSchedulerSpec.js b/Specs/Core/RequestSchedulerSpec.js
index fbdf872694c6..92c9ef7aafec 100644
--- a/Specs/Core/RequestSchedulerSpec.js
+++ b/Specs/Core/RequestSchedulerSpec.js
@@ -12,12 +12,12 @@ defineSuite([
var originalMaximumRequests;
var originalMaximumRequestsPerServer;
- var originalPriorityHeapLength;
+ var originalRequestQueueLength;
beforeAll(function() {
originalMaximumRequests = RequestScheduler.maximumRequests;
originalMaximumRequestsPerServer = RequestScheduler.maximumRequestsPerServer;
- originalPriorityHeapLength = RequestScheduler.priorityHeapLength;
+ originalRequestQueueLength = RequestScheduler.requestQueueLength;
});
beforeEach(function() {
@@ -27,7 +27,7 @@ defineSuite([
afterEach(function() {
RequestScheduler.maximumRequests = originalMaximumRequests;
RequestScheduler.maximumRequestsPerServer = originalMaximumRequestsPerServer;
- RequestScheduler.priorityHeapLength = originalPriorityHeapLength;
+ RequestScheduler.requestQueueLength = originalRequestQueueLength;
});
it('request throws when request is undefined', function() {
@@ -204,7 +204,7 @@ defineSuite([
}
});
- it('honors priorityHeapLength', function() {
+ it('honors requestQueue length', function() {
var deferreds = [];
var requests = [];
@@ -225,22 +225,23 @@ defineSuite([
return request;
}
- RequestScheduler.priorityHeapLength = 1;
+ RequestScheduler.requestQueueLength = 1;
var firstRequest = createRequest(0.0);
var promise = RequestScheduler.request(firstRequest);
expect(promise).toBeDefined();
promise = RequestScheduler.request(createRequest(1.0));
expect(promise).toBeUndefined();
- RequestScheduler.priorityHeapLength = 3;
+ RequestScheduler.requestQueueLength = 3;
+ promise = RequestScheduler.request(createRequest(1.0));
promise = RequestScheduler.request(createRequest(2.0));
promise = RequestScheduler.request(createRequest(3.0));
expect(promise).toBeDefined();
promise = RequestScheduler.request(createRequest(4.0));
expect(promise).toBeUndefined();
- // A request is cancelled to accommodate the new heap length
- RequestScheduler.priorityHeapLength = 2;
+ // The requests are cancelled when the queue length changes
+ RequestScheduler.requestQueueLength = 2;
expect(firstRequest.state).toBe(RequestState.CANCELLED);
var length = deferreds.length;
@@ -448,7 +449,7 @@ defineSuite([
});
}
- var length = RequestScheduler.priorityHeapLength;
+ var length = RequestScheduler.requestQueueLength;
for (var i = 0; i < length; ++i) {
var priority = Math.random();
RequestScheduler.request(createRequest(priority));
@@ -485,7 +486,7 @@ defineSuite([
var i;
var request;
- var length = RequestScheduler.priorityHeapLength;
+ var length = RequestScheduler.requestQueueLength;
for (i = 0; i < length; ++i) {
var priority = i / (length - 1);
request = createRequest(priority);
@@ -497,28 +498,31 @@ defineSuite([
RequestScheduler.maximumRequests = 0;
RequestScheduler.update();
- var requestHeap = RequestScheduler.requestHeap;
+ var requestQueue = RequestScheduler.requestQueue;
var requests = [];
var currentTestId = 0;
- while (requestHeap.length > 0) {
- request = requestHeap.pop();
+
+ for (i = 0; i < length; ++i) {
+ request = requestQueue.get(i);
requests.push(request);
expect(request.testId).toBeGreaterThanOrEqualTo(currentTestId);
currentTestId = request.testId;
}
+ requestQueue.remove(length);
for (i = 0; i < length; ++i) {
- requestHeap.insert(requests[i]);
+ requestQueue.insert(requests[i]);
}
invertPriority = true;
RequestScheduler.update();
- while (requestHeap.length > 0) {
- request = requestHeap.pop();
+ for (i = 0; i < length; ++i) {
+ request = requestQueue.get(i);
expect(request.testId).toBeLessThanOrEqualTo(currentTestId);
currentTestId = request.testId;
}
+ requestQueue.remove(length);
});
it('handles low priority requests', function() {
@@ -539,7 +543,7 @@ defineSuite([
var mediumPriority = 0.5;
var lowPriority = 1.0;
- var length = RequestScheduler.priorityHeapLength;
+ var length = RequestScheduler.requestQueueLength;
for (var i = 0; i < length; ++i) {
RequestScheduler.request(createRequest(mediumPriority));
}
From 366a1d2af1c7fbce32c4b5a8ef280682b5f1a894 Mon Sep 17 00:00:00 2001
From: Sean Lilley
Date: Wed, 21 Feb 2018 13:57:58 -0500
Subject: [PATCH 02/53] Set highest priority for globe tiles that you are
inside
---
Source/Scene/GlobeSurfaceTile.js | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/Source/Scene/GlobeSurfaceTile.js b/Source/Scene/GlobeSurfaceTile.js
index 1026832675b8..e9b9e76d1216 100644
--- a/Source/Scene/GlobeSurfaceTile.js
+++ b/Source/Scene/GlobeSurfaceTile.js
@@ -7,6 +7,7 @@ define([
'../Core/defineProperties',
'../Core/IntersectionTests',
'../Core/PixelFormat',
+ '../Core/Rectangle',
'../Renderer/PixelDatatype',
'../Renderer/Sampler',
'../Renderer/Texture',
@@ -28,6 +29,7 @@ define([
defineProperties,
IntersectionTests,
PixelFormat,
+ Rectangle,
PixelDatatype,
Sampler,
Texture,
@@ -254,6 +256,10 @@ define([
function createPriorityFunction(surfaceTile, frameState) {
return function() {
+ if (Rectangle.contains(surfaceTile.tileBoundingRegion.rectangle, frameState.camera.positionCartographic)) {
+ // If the camera is inside this tile's bounding region treat it as highest priority
+ return 0.0;
+ }
return surfaceTile.tileBoundingRegion.distanceToCamera(frameState);
};
}
From 3f09e0cef6094bc9cc1b73b9dc289c29e078cf5c Mon Sep 17 00:00:00 2001
From: Sean Lilley
Date: Thu, 22 Feb 2018 14:20:37 -0500
Subject: [PATCH 03/53] Move tileset cache to its own class
---
Source/Scene/Cesium3DTile.js | 8 +--
Source/Scene/Cesium3DTileset.js | 66 +++++---------------
Source/Scene/Cesium3DTilesetCache.js | 79 ++++++++++++++++++++++++
Source/Scene/Cesium3DTilesetTraversal.js | 15 ++---
Specs/Scene/Cesium3DTilesetSpec.js | 8 +--
5 files changed, 108 insertions(+), 68 deletions(-)
create mode 100644 Source/Scene/Cesium3DTilesetCache.js
diff --git a/Source/Scene/Cesium3DTile.js b/Source/Scene/Cesium3DTile.js
index 18101211d979..57e588774403 100644
--- a/Source/Scene/Cesium3DTile.js
+++ b/Source/Scene/Cesium3DTile.js
@@ -250,14 +250,16 @@ define([
this.hasTilesetContent = false;
/**
- * The corresponding node in the cache replacement list.
+ * The corresponding node in the cache.
+ *
+ * See {@link Cesium3DTilesetCache}
*
* @type {DoublyLinkedListNode}
* @readonly
*
* @private
*/
- this.replacementNode = undefined;
+ this.cacheNode = undefined;
var expire = header.expire;
var expireDuration;
@@ -732,8 +734,6 @@ define([
this._contentReadyToProcessPromise = undefined;
this._contentReadyPromise = undefined;
- this.replacementNode = undefined;
-
this.lastStyleTime = 0;
this._debugColorizeTiles = false;
diff --git a/Source/Scene/Cesium3DTileset.js b/Source/Scene/Cesium3DTileset.js
index e4590a8d60e1..25ef90c9dff2 100644
--- a/Source/Scene/Cesium3DTileset.js
+++ b/Source/Scene/Cesium3DTileset.js
@@ -29,6 +29,7 @@ define([
'./Cesium3DTile',
'./Cesium3DTileColorBlendMode',
'./Cesium3DTileOptimizations',
+ './Cesium3DTilesetCache',
'./Cesium3DTilesetStatistics',
'./Cesium3DTilesetTraversal',
'./Cesium3DTileStyleEngine',
@@ -73,6 +74,7 @@ define([
Cesium3DTile,
Cesium3DTileColorBlendMode,
Cesium3DTileOptimizations,
+ Cesium3DTilesetCache,
Cesium3DTilesetStatistics,
Cesium3DTilesetTraversal,
Cesium3DTileStyleEngine,
@@ -175,6 +177,7 @@ define([
this._properties = undefined; // Metadata for per-model/point/etc properties
this._geometricError = undefined; // Geometric error when the tree is not rendered at all
this._gltfUpAxis = undefined;
+ this._cache = new Cesium3DTilesetCache();
this._processingQueue = [];
this._selectedTiles = [];
this._requestedTiles = [];
@@ -183,14 +186,6 @@ define([
this._loadTimestamp = undefined;
this._timeSinceLoad = 0.0;
- var replacementList = new DoublyLinkedList();
-
- // [head, sentinel) -> tiles that weren't selected this frame and may be replaced
- // (sentinel, tail] -> tiles that were selected this frame
- this._replacementList = replacementList; // Tiles with content loaded. For cache management.
- this._replacementSentinel = replacementList.add();
- this._trimTiles = false;
-
this._cullWithChildrenBounds = defaultValue(options.cullWithChildrenBounds, true);
this._hasMixedContent = false;
@@ -1502,10 +1497,7 @@ define([
tileset._statistics.incrementLoadCounts(tile.content);
++tileset._statistics.numberOfTilesWithContentReady;
- // Add to the tile cache. Previously expired tiles are already in the cache.
- if (!defined(tile.replacementNode)) {
- tile.replacementNode = tileset._replacementList.add(tile);
- }
+ tileset._cache.add(tile);
}
};
}
@@ -1745,52 +1737,27 @@ define([
stack.push(children[i]);
}
if (tile !== root) {
- unloadTileFromCache(tileset, tile);
- tile.destroy();
+ destroyTile(tileset, tile);
--statistics.numberOfTilesTotal;
}
}
root.children = [];
}
- function unloadTileFromCache(tileset, tile) {
- var node = tile.replacementNode;
- if (!defined(node)) {
- return;
- }
-
- var statistics = tileset._statistics;
- var replacementList = tileset._replacementList;
- var tileUnload = tileset.tileUnload;
+ function unloadTile(tileset, tile) {
+ tileset.tileUnload.raiseEvent(tile);
+ tileset._statistics.decrementLoadCounts(tile.content);
+ --tileset._statistics.numberOfTilesWithContentReady;
+ tile.unloadContent();
+ }
- tileUnload.raiseEvent(tile);
- replacementList.remove(node);
- statistics.decrementLoadCounts(tile.content);
- --statistics.numberOfTilesWithContentReady;
+ function destroyTile(tileset, tile) {
+ tileset._cache.unloadTile(tileset, tile, unloadTile);
+ tile.destroy();
}
function unloadTiles(tileset) {
- var trimTiles = tileset._trimTiles;
- tileset._trimTiles = false;
-
- var replacementList = tileset._replacementList;
-
- var totalMemoryUsageInBytes = tileset.totalMemoryUsageInBytes;
- var maximumMemoryUsageInBytes = tileset._maximumMemoryUsage * 1024 * 1024;
-
- // Traverse the list only to the sentinel since tiles/nodes to the
- // right of the sentinel were used this frame.
- //
- // The sub-list to the left of the sentinel is ordered from LRU to MRU.
- var sentinel = tileset._replacementSentinel;
- var node = replacementList.head;
- while ((node !== sentinel) && ((totalMemoryUsageInBytes > maximumMemoryUsageInBytes) || trimTiles)) {
- var tile = node.item;
- node = node.next;
- unloadTileFromCache(tileset, tile);
- tile.unloadContent();
- totalMemoryUsageInBytes = tileset.totalMemoryUsageInBytes;
- }
+ tileset._cache.unloadTiles(tileset, unloadTile);
}
/**
@@ -1803,8 +1770,7 @@ define([
*
*/
Cesium3DTileset.prototype.trimLoadedTiles = function() {
- // Defer to next frame so WebGL delete calls happen inside the render loop
- this._trimTiles = true;
+ this._cache.trim();
};
///////////////////////////////////////////////////////////////////////////
diff --git a/Source/Scene/Cesium3DTilesetCache.js b/Source/Scene/Cesium3DTilesetCache.js
new file mode 100644
index 000000000000..848a41da2529
--- /dev/null
+++ b/Source/Scene/Cesium3DTilesetCache.js
@@ -0,0 +1,79 @@
+define([
+ '../Core/defined',
+ '../Core/DoublyLinkedList'
+ ], function(
+ defined,
+ DoublyLinkedList) {
+ 'use strict';
+
+ /**
+ * Stores tiles with content loaded.
+ *
+ * @private
+ */
+ function Cesium3DTilesetCache() {
+ // [head, sentinel) -> tiles that weren't selected this frame and may be removed from the cache
+ // (sentinel, tail] -> tiles that were selected this frame
+ this._list = new DoublyLinkedList();
+ this._sentinel = this._list.add();
+ this._trimTiles = false;
+ }
+
+ Cesium3DTilesetCache.prototype.reset = function() {
+ // Move sentinel node to the tail so, at the start of the frame, all tiles
+ // may be potentially replaced. Tiles are moved to the right of the sentinel
+ // when they are selected so they will not be replaced.
+ this._list.splice(this._list.tail, this._sentinel);
+ };
+
+ Cesium3DTilesetCache.prototype.touch = function(tile) {
+ var node = tile.cacheNode;
+ if (defined(node)) {
+ this._list.splice(this._sentinel, node);
+ }
+ };
+
+ Cesium3DTilesetCache.prototype.add = function(tile) {
+ if (!defined(tile.cacheNode)) {
+ tile.cacheNode = this._list.add(tile);
+ }
+ };
+
+ Cesium3DTilesetCache.prototype.unloadTile = function(tileset, tile, unloadCallback) {
+ var node = tile.cacheNode;
+ if (!defined(node)) {
+ return;
+ }
+
+ this._list.remove(node);
+ tile.cacheNode = undefined;
+ unloadCallback(tileset, tile);
+ };
+
+ Cesium3DTilesetCache.prototype.unloadTiles = function(tileset, unloadCallback) {
+ var trimTiles = this._trimTiles;
+ this._trimTiles = false;
+
+ var list = this._list;
+
+ var maximumMemoryUsageInBytes = tileset.maximumMemoryUsage * 1024 * 1024;
+
+ // Traverse the list only to the sentinel since tiles/nodes to the
+ // right of the sentinel were used this frame.
+ //
+ // The sub-list to the left of the sentinel is ordered from LRU to MRU.
+ var sentinel = this._sentinel;
+ var node = list.head;
+ while ((node !== sentinel) && ((tileset.totalMemoryUsageInBytes > maximumMemoryUsageInBytes) || trimTiles)) {
+ var tile = node.item;
+ node = node.next;
+ this.unloadTile(tileset, tile, unloadCallback);
+ }
+ };
+
+ Cesium3DTilesetCache.prototype.trim = function() {
+ this._trimTiles = true;
+ };
+
+ return Cesium3DTilesetCache;
+});
diff --git a/Source/Scene/Cesium3DTilesetTraversal.js b/Source/Scene/Cesium3DTilesetTraversal.js
index 3052b00ddb79..3acb1a2a66a3 100644
--- a/Source/Scene/Cesium3DTilesetTraversal.js
+++ b/Source/Scene/Cesium3DTilesetTraversal.js
@@ -25,7 +25,9 @@ define([
/**
* @private
*/
- var Cesium3DTilesetTraversal = {};
+ function Cesium3DTilesetTraversal() {
+ }
+
function selectTiles(tileset, frameState, outOfCore) {
if (tileset.debugFreezeFrame) {
@@ -40,11 +42,7 @@ define([
tileset._selectedTilesToStyle.length = 0;
tileset._hasMixedContent = false;
- // Move sentinel node to the tail so, at the start of the frame, all tiles
- // may be potentially replaced. Tiles are moved to the right of the sentinel
- // when they are selected so they will not be replaced.
- var replacementList = tileset._replacementList;
- replacementList.splice(replacementList.tail, tileset._replacementSentinel);
+ tileset._cache.reset();
var root = tileset._root;
root.updateTransform(tileset._modelMatrix);
@@ -625,10 +623,7 @@ define([
if (!outOfCore) {
return;
}
- var node = tile.replacementNode;
- if (defined(node)) {
- tileset._replacementList.splice(tileset._replacementSentinel, node);
- }
+ tileset._cache.touch(tile);
}
function computeSSE(tile, frameState) {
diff --git a/Specs/Scene/Cesium3DTilesetSpec.js b/Specs/Scene/Cesium3DTilesetSpec.js
index ce9f61a122cb..1e0330376b61 100644
--- a/Specs/Scene/Cesium3DTilesetSpec.js
+++ b/Specs/Scene/Cesium3DTilesetSpec.js
@@ -2299,14 +2299,14 @@ defineSuite([
it('Unloads cached tiles in a tileset with external tileset.json using maximumMemoryUsage', function() {
return Cesium3DTilesTester.loadTileset(scene, tilesetOfTilesetsUrl).then(function(tileset) {
var statistics = tileset._statistics;
- var replacementList = tileset._replacementList;
+ var cacheList = tileset._cache._list;
tileset.maximumMemoryUsage = 0.025;
scene.renderForSpecs();
expect(statistics.numberOfCommands).toEqual(5);
expect(statistics.numberOfTilesWithContentReady).toEqual(5);
- expect(replacementList.length - 1).toEqual(5); // Only tiles with content are on the replacement list. -1 for sentinel.
+ expect(cacheList.length - 1).toEqual(5); // Only tiles with content are on the replacement list. -1 for sentinel.
// Zoom out so only root tile is needed to meet SSE. This unloads
// all tiles except the root and one of the b3dm children
@@ -2315,7 +2315,7 @@ defineSuite([
expect(statistics.numberOfCommands).toEqual(1);
expect(statistics.numberOfTilesWithContentReady).toEqual(2);
- expect(replacementList.length - 1).toEqual(2);
+ expect(cacheList.length - 1).toEqual(2);
// Reset camera so all tiles are reloaded
viewAllTiles();
@@ -2324,7 +2324,7 @@ defineSuite([
expect(statistics.numberOfCommands).toEqual(5);
expect(statistics.numberOfTilesWithContentReady).toEqual(5);
- expect(replacementList.length - 1).toEqual(5);
+ expect(cacheList.length - 1).toEqual(5);
});
});
});
From 15700e49f2d03fac664a4e74096e2954e3261a59 Mon Sep 17 00:00:00 2001
From: Sean Lilley
Date: Tue, 27 Feb 2018 15:18:26 -0500
Subject: [PATCH 04/53] Temp
---
Source/Scene/Cesium3DTile.js | 40 +-
Source/Scene/Cesium3DTileBatchTable.js | 14 +-
Source/Scene/Cesium3DTileStyleEngine.js | 2 +-
Source/Scene/Cesium3DTileset.js | 81 +-
Source/Scene/Cesium3DTilesetTraversal.js | 997 +++++++++--------------
Specs/Scene/Cesium3DTilesetSpec.js | 2 +-
6 files changed, 451 insertions(+), 685 deletions(-)
diff --git a/Source/Scene/Cesium3DTile.js b/Source/Scene/Cesium3DTile.js
index 57e588774403..84cb889323ac 100644
--- a/Source/Scene/Cesium3DTile.js
+++ b/Source/Scene/Cesium3DTile.js
@@ -252,8 +252,6 @@ define([
/**
* The corresponding node in the cache.
*
- * See {@link Cesium3DTilesetCache}
- *
* @type {DoublyLinkedListNode}
* @readonly
*
@@ -285,15 +283,6 @@ define([
*/
this.expireDate = expireDate;
- /**
- * Marks if the tile is selected this frame.
- *
- * @type {Boolean}
- *
- * @private
- */
- this.selected = false;
-
/**
* The time when a style was last applied to this tile.
*
@@ -324,22 +313,21 @@ define([
// Members that are updated every frame for tree traversal and rendering optimizations:
this._distanceToCamera = 0;
- this._visibilityPlaneMask = 0;
- this._childrenVisibility = Cesium3DTileChildrenVisibility.VISIBLE;
- this._lastSelectedFrameNumber = -1;
+ this._centerZDepth = 0; // TODO : remove?
this._screenSpaceError = 0;
- this._screenSpaceErrorComputedFrame = -1;
+ this._visibilityPlaneMask = 0;
+
this._finalResolution = true;
this._depth = 0;
- this._centerZDepth = 0;
this._stackLength = 0;
- this._selectedFrame = -1;
this._selectionDepth = 0;
- this._lastSelectionDepth = undefined;
- this._requestedFrame = undefined;
- this._lastVisitedFrame = undefined;
- this._ancestorWithContent = undefined;
- this._ancestorWithLoadedContent = undefined;
+
+ this._updatedVisibilityFrame = 0;
+ this._touchedFrame = 0;
+ this._selectedFrame = 0;
+ this._ancestorWithContentAvailable = undefined;
+ this._refines = false;
+ this._priority = 0.0;
this._isClipped = true;
this._clippingPlanesState = 0; // encapsulates (_isClipped, clippingPlanes.enabled) and number/function
@@ -457,13 +445,13 @@ define([
*/
contentAvailable : {
get : function() {
- return this.contentReady || (defined(this._expiredContent) && this._contentState !== Cesium3DTileContentState.FAILED);
+ return (this.contentReady && this.hasRenderableContent) || (defined(this._expiredContent) && this._contentState !== Cesium3DTileContentState.FAILED);
}
},
/**
- * Determines if the tile is ready to render. true
if the tile
- * is ready to render; otherwise, false
.
+ * Determines if the tile's content is ready. This is automatically true
for
+ * tile's with empty content.
*
* @memberof Cesium3DTile.prototype
*
@@ -608,7 +596,7 @@ define([
function createPriorityFunction(tile) {
return function() {
- return tile._distanceToCamera;
+ return tile._priority;
};
}
diff --git a/Source/Scene/Cesium3DTileBatchTable.js b/Source/Scene/Cesium3DTileBatchTable.js
index 42c32407f0c9..87982d59062c 100644
--- a/Source/Scene/Cesium3DTileBatchTable.js
+++ b/Source/Scene/Cesium3DTileBatchTable.js
@@ -1355,9 +1355,15 @@ define([
}
tileset._backfaceCommands.push(derivedCommands.zback);
}
- if (!defined(derivedCommands.stencil) || tile._selectionDepth !== tile._lastSelectionDepth) {
+ if (!defined(derivedCommands.stencil) || tile._selectionDepth !== getLastSelectionDepth(derivedCommands.stencil)) {
+ // if (defined(derivedCommands.stencil)) {
+ // var currentSelectionDepth = tile._selectionDepth;
+ // var newSelectionDepth = getLastSelectionDepth(derivedCommands.stencil);
+ // console.log('My selection depth: ' + tile._selectionDepth);
+ // console.log('Previous selection depth: ' + (defined(derivedCommands.stencil) ? getLastSelectionDepth(derivedCommands.stencil) : 'None'));
+ //
+ // }
derivedCommands.stencil = deriveStencilCommand(derivedCommands.originalCommand, tile._selectionDepth);
- tile._lastSelectionDepth = tile._selectionDepth;
}
updateDerivedCommand(derivedCommands.stencil, command);
}
@@ -1482,6 +1488,10 @@ define([
return derivedCommand;
}
+ function getLastSelectionDepth(stencilCommand) {
+ return stencilCommand.renderState.stencilTest.reference >>> 4;
+ }
+
function getTranslucentRenderState(renderState) {
var rs = clone(renderState, true);
rs.cull.enabled = false;
diff --git a/Source/Scene/Cesium3DTileStyleEngine.js b/Source/Scene/Cesium3DTileStyleEngine.js
index 07ac449b0e52..e9fa8ee4af4f 100644
--- a/Source/Scene/Cesium3DTileStyleEngine.js
+++ b/Source/Scene/Cesium3DTileStyleEngine.js
@@ -65,7 +65,7 @@ define([
var length = tiles.length;
for (var i = 0; i < length; ++i) {
var tile = tiles[i];
- if (tile.selected && (tile.lastStyleTime !== lastStyleTime)) {
+ if (tile.lastStyleTime !== lastStyleTime) {
// Apply the style to this tile if it wasn't already applied because:
// 1) the user assigned a new style to the tileset
// 2) this tile is now visible, but it wasn't visible when the style was first assigned
diff --git a/Source/Scene/Cesium3DTileset.js b/Source/Scene/Cesium3DTileset.js
index 25ef90c9dff2..310f9ca94cc8 100644
--- a/Source/Scene/Cesium3DTileset.js
+++ b/Source/Scene/Cesium3DTileset.js
@@ -29,6 +29,7 @@ define([
'./Cesium3DTile',
'./Cesium3DTileColorBlendMode',
'./Cesium3DTileOptimizations',
+ './Cesium3DTileRefine',
'./Cesium3DTilesetCache',
'./Cesium3DTilesetStatistics',
'./Cesium3DTilesetTraversal',
@@ -74,6 +75,7 @@ define([
Cesium3DTile,
Cesium3DTileColorBlendMode,
Cesium3DTileOptimizations,
+ Cesium3DTileRefine,
Cesium3DTilesetCache,
Cesium3DTilesetStatistics,
Cesium3DTilesetTraversal,
@@ -187,14 +189,10 @@ define([
this._timeSinceLoad = 0.0;
this._cullWithChildrenBounds = defaultValue(options.cullWithChildrenBounds, true);
+ this._allTilesAdditive = true;
this._hasMixedContent = false;
- this._baseTraversal = new Cesium3DTilesetTraversal.BaseTraversal();
- this._skipTraversal = new Cesium3DTilesetTraversal.SkipTraversal({
- selectionHeuristic : selectionHeuristic
- });
-
this._backfaceCommands = new ManagedArray();
this._maximumScreenSpaceError = defaultValue(options.maximumScreenSpaceError, 16);
@@ -1269,14 +1267,12 @@ define([
// Append the tileset version to the tilesetResource
if (!defined(tilesetResource.queryParameters.v)) {
var versionQuery = {
- v: defaultValue(asset.tilesetVersion, '0.0')
+ v : defaultValue(asset.tilesetVersion, '0.0')
};
this._basePath += '?v=' + versionQuery.v;
tilesetResource.setQueryParameters(versionQuery);
}
- // A tileset.json referenced from a tile may exist in a different directory than the root tileset.
- // Get the basePath relative to the external tileset.
var rootTile = new Cesium3DTile(this, tilesetResource, tilesetJson.root, parentTile);
// If there is a parentTile, add the root of the currently loading tileset
@@ -1286,35 +1282,27 @@ define([
rootTile._depth = parentTile._depth + 1;
}
- ++statistics.numberOfTilesTotal;
-
var stack = [];
- stack.push({
- header : tilesetJson.root,
- tile3D : rootTile
- });
+ stack.push(rootTile);
while (stack.length > 0) {
var tile = stack.pop();
- var tile3D = tile.tile3D;
- var children = tile.header.children;
+ ++statistics.numberOfTilesTotal;
+ this._allTilesAdditive = this._allTilesAdditive && (tile.refine === Cesium3DTileRefine.ADD);
+ var children = tile._header.children;
if (defined(children)) {
var length = children.length;
for (var i = 0; i < length; ++i) {
var childHeader = children[i];
- var childTile = new Cesium3DTile(this, tilesetResource, childHeader, tile3D);
- tile3D.children.push(childTile);
- childTile._depth = tile3D._depth + 1;
- ++statistics.numberOfTilesTotal;
- stack.push({
- header : childHeader,
- tile3D : childTile
- });
+ var childTile = new Cesium3DTile(this, tilesetResource, childHeader, tile);
+ tile.children.push(childTile);
+ childTile._depth = tile._depth + 1;
+ stack.push(childTile);
}
}
if (this._cullWithChildrenBounds) {
- Cesium3DTileOptimizations.checkChildrenWithinParent(tile3D);
+ Cesium3DTileOptimizations.checkChildrenWithinParent(tile);
}
}
@@ -1400,15 +1388,6 @@ define([
tileset._dynamicScreenSpaceErrorComputedDensity = density;
}
- function selectionHeuristic(tileset, ancestor, tile) {
- var skipLevels = tileset._skipLevelOfDetail ? tileset.skipLevels : 0;
- var skipScreenSpaceErrorFactor = tileset._skipLevelOfDetail ? tileset.skipScreenSpaceErrorFactor : 1.0;
-
- return (ancestor !== tile && !tile.hasEmptyContent && !tileset.immediatelyLoadDesiredLevelOfDetail) &&
- (tile._screenSpaceError < ancestor._screenSpaceError / skipScreenSpaceErrorFactor) &&
- (tile._depth > ancestor._depth + skipLevels);
- }
-
///////////////////////////////////////////////////////////////////////////
function requestContent(tileset, tile) {
@@ -1457,10 +1436,7 @@ define([
});
}
- function requestTiles(tileset, outOfCore) {
- if (!outOfCore) {
- return;
- }
+ function requestTiles(tileset) {
var requestedTiles = tileset._requestedTiles;
var length = requestedTiles.length;
for (var i = 0; i < length; ++i) {
@@ -1635,6 +1611,8 @@ define([
var tileVisible = tileset.tileVisible;
var i;
+ console.log(tileset._hasMixedContent);
+
var bivariateVisibilityTest = tileset._skipLevelOfDetail && tileset._hasMixedContent && frameState.context.stencilBuffer && length > 0;
tileset._backfaceCommands.length = 0;
@@ -1646,15 +1624,12 @@ define([
var lengthBeforeUpdate = commandList.length;
for (i = 0; i < length; ++i) {
var tile = selectedTiles[i];
- // tiles may get unloaded and destroyed between selection and update
- if (tile.selected) {
- // Raise the tileVisible event before update in case the tileVisible event
- // handler makes changes that update needs to apply to WebGL resources
- tileVisible.raiseEvent(tile);
- tile.update(tileset, frameState);
- statistics.incrementSelectionCounts(tile.content);
- ++statistics.selected;
- }
+ // Raise the tileVisible event before update in case the tileVisible event
+ // handler makes changes that update needs to apply to WebGL resources
+ tileVisible.raiseEvent(tile);
+ tile.update(tileset, frameState);
+ statistics.incrementSelectionCounts(tile.content);
+ ++statistics.selected;
}
var lengthAfterUpdate = commandList.length;
var addedCommandsLength = lengthAfterUpdate - lengthBeforeUpdate;
@@ -1842,16 +1817,16 @@ define([
var statistics = this._statistics;
statistics.clear();
- if (outOfCore) {
- processTiles(this, frameState);
- }
-
if (this.dynamicScreenSpaceError) {
updateDynamicScreenSpaceError(this, frameState);
}
- Cesium3DTilesetTraversal.selectTiles(this, frameState, outOfCore);
- requestTiles(this, outOfCore);
+ if (outOfCore) {
+ Cesium3DTilesetTraversal.selectTiles(this, frameState);
+ requestTiles(this);
+ processTiles(this, frameState);
+ }
+
updateTiles(this, frameState);
if (outOfCore) {
diff --git a/Source/Scene/Cesium3DTilesetTraversal.js b/Source/Scene/Cesium3DTilesetTraversal.js
index 3acb1a2a66a3..d33746b9e23c 100644
--- a/Source/Scene/Cesium3DTilesetTraversal.js
+++ b/Source/Scene/Cesium3DTilesetTraversal.js
@@ -1,5 +1,6 @@
define([
'../Core/CullingVolume',
+ '../Core/defaultValue',
'../Core/defined',
'../Core/freezeObject',
'../Core/Intersect',
@@ -7,10 +8,12 @@ define([
'../Core/Math',
'../Core/OrthographicFrustum',
'./Cesium3DTileChildrenVisibility',
+ './Cesium3DTileOptimizationHint',
'./Cesium3DTileRefine',
'./SceneMode'
], function(
CullingVolume,
+ defaultValue,
defined,
freezeObject,
Intersect,
@@ -18,24 +21,44 @@ define([
CesiumMath,
OrthographicFrustum,
Cesium3DTileChildrenVisibility,
+ Cesium3DTileOptimizationHint,
Cesium3DTileRefine,
SceneMode) {
'use strict';
+ var baseTraversal = {
+ stack : new ManagedArray(),
+ stackMaximumLength : 0
+ };
+
+ var internalBaseTraversal = {
+ stack : new ManagedArray(),
+ stackMaximumLength : 0
+ };
+
+ var skipTraversal = {
+ stack : new ManagedArray(),
+ stackMaximumLength : 0
+ };
+
+ var selectionTraversal = {
+ stack : new ManagedArray(),
+ stackMaximumLength : 0,
+ ancestorStack : new ManagedArray(),
+ ancestorStackMaximumLength : 0
+ };
+
/**
* @private
*/
function Cesium3DTilesetTraversal() {
}
-
- function selectTiles(tileset, frameState, outOfCore) {
+ Cesium3DTilesetTraversal.selectTiles = function(tileset, frameState) {
if (tileset.debugFreezeFrame) {
return;
}
- var maximumScreenSpaceError = tileset._maximumScreenSpaceError;
-
tileset._desiredTiles.length = 0;
tileset._selectedTiles.length = 0;
tileset._requestedTiles.length = 0;
@@ -45,112 +68,129 @@ define([
tileset._cache.reset();
var root = tileset._root;
- root.updateTransform(tileset._modelMatrix);
-
- if (!root.insideViewerRequestVolume(frameState)) {
- return;
- }
-
- root._distanceToCamera = root.distanceToTile(frameState);
-
- if (getScreenSpaceError(tileset, tileset._geometricError, root, frameState) <= maximumScreenSpaceError) {
- // The SSE of not rendering the tree is small enough that the tree does not need to be rendered
+ var rootVisible = updateVisibility(tileset, root, frameState);
+ if (!rootVisible) {
return;
}
- root._visibilityPlaneMask = root.visibility(frameState, CullingVolume.MASK_INDETERMINATE);
- if (root._visibilityPlaneMask === CullingVolume.MASK_OUTSIDE) {
+ // The SSE of not rendering the tree is small enough that the tree does not need to be rendered
+ if (getScreenSpaceError(tileset, tileset._geometricError, root, frameState) <= tileset._maximumScreenSpaceError) {
return;
}
- loadTile(tileset, root, frameState, true);
-
- if (!tileset._skipLevelOfDetail) {
- // just execute base traversal and add tiles to _desiredTiles
- tileset._baseTraversal.execute(tileset, root, maximumScreenSpaceError, frameState, outOfCore);
- var leaves = tileset._baseTraversal.leaves;
- var length = leaves.length;
- for (var i = 0; i < length; ++i) {
- tileset._desiredTiles.push(leaves.get(i));
- }
+ if (!tileset._skipLevelOfDetail) {//} || tileset._allTilesAdditive) {
+ selectBaseTraversal(tileset, root, frameState);
} else if (tileset.immediatelyLoadDesiredLevelOfDetail) {
- tileset._skipTraversal.execute(tileset, root, frameState, outOfCore);
+ selectSkipTraversal(tileset, root, frameState);
} else {
- // leaves of the base traversal is where we start the skip traversal
- tileset._baseTraversal.leaves = tileset._skipTraversal.queue1;
+ selectBaseAndSkipTraversal(tileset, root, frameState);
+ }
+
+ // TODO : these aren't really correct because min/max never get reset.
+ tileset._desiredTiles.trim();
+ baseTraversal.stack.trim(baseTraversal.stackMaximumLength);
+ internalBaseTraversal.stack.trim(internalBaseTraversal.stackMaximumLength);
+ skipTraversal.stack.trim(skipTraversal.stackMaximumLength);
+ selectionTraversal.stack.trim(selectionTraversal.stackMaximumLength);
+ selectionTraversal.ancestorStack.trim(selectionTraversal.ancestorStackMaximumLength);
+ };
- // load and select tiles without skipping up to tileset.baseScreenSpaceError
- tileset._baseTraversal.execute(tileset, root, tileset.baseScreenSpaceError, frameState, outOfCore);
+ function selectBaseTraversal(tileset, root, frameState) {
+ var maximumScreenSpaceError = tileset._maximumScreenSpaceError;
+ executeBaseTraversal(tileset, root, maximumScreenSpaceError, maximumScreenSpaceError, undefined, frameState);
- // skip traversal starts from a prepopulated queue from the base traversal
- tileset._skipTraversal.execute(tileset, undefined, frameState, outOfCore);
+ // Select desired tiles
+ var desiredTiles = tileset._desiredTiles;
+ var length = desiredTiles.length;
+ for (var i = 0; i < length; ++i) {
+ selectTile(tileset, desiredTiles.get(i), frameState);
}
+ }
- // mark tiles for selection or their nearest loaded ancestor
- markLoadedTilesForSelection(tileset, frameState, outOfCore);
+ function selectSkipTraversal(tileset, root, frameState) {
+ // Start the skip traversal at the root
+ skipTraversal.stack.push(root);
- // sort selected tiles by distance to camera and call selectTile on each
- // set tile._selectionDepth on all tiles
- traverseAndSelect(tileset, root, frameState);
+ // Execute the skip traversal
+ executeSkipTraversal(tileset, tileset._maximumScreenSpaceError, frameState);
- tileset._desiredTiles.trim();
+ // Mark tiles for selection or their nearest loaded ancestor
+ markDesiredTilesForSelection(tileset, frameState);
+
+ // Sort selected tiles by distance to camera and call selectTile on each
+ traverseAndSelect(tileset, root, frameState);
}
- var descendantStack = [];
+ function selectBaseAndSkipTraversal(tileset, root, frameState) {
+ var baseScreenSpaceError = Math.max(tileset.baseScreenSpaceError, tileset.maximumScreenSpaceError);
+ var maximumScreenSpaceError = tileset.maximumScreenSpaceError;
+
+ // Load and select tiles without skipping up to baseScreenSpaceError
+ // Leaves of the base traversal is where we start the skip traversal
+ executeBaseTraversal(tileset, root, baseScreenSpaceError, maximumScreenSpaceError, skipTraversal.stack, frameState);
- function markLoadedTilesForSelection(tileset, frameState, outOfCore) {
+ executeSkipTraversal(tileset, maximumScreenSpaceError, frameState);
+
+ // Mark tiles for selection or their nearest loaded ancestor
+ markDesiredTilesForSelection(tileset, frameState);
+
+ // Sort selected tiles by distance to camera and call selectTile on each
+ traverseAndSelect(tileset, root, frameState);
+ }
+
+ function markDesiredTilesForSelection(tileset, frameState) {
var tiles = tileset._desiredTiles;
var length = tiles.length;
for (var i = 0; i < length; ++i) {
- var original = tiles.get(i);
-
- if (hasAdditiveContent(original)) {
- original.selected = true;
- original._selectedFrame = frameState.frameNumber;
- continue;
+ var tile = tiles.get(i);
+ var loadedTile = tile.contentAvailable ? tile : tile._ancestorWithContentAvailable;
+ if (defined(loadedTile)) {
+ markForSelection(tileset, tile, frameState);
}
+ }
+ }
- var loadedTile = original._ancestorWithLoadedContent;
- if (original.hasRenderableContent && original.contentAvailable) {
- loadedTile = original;
- }
+ function selectTile(tileset, tile, frameState) {
+ if (tile._selectedFrame === frameState.frameNumber) {
+ tileset._selectedTiles.push(tile);
+ }
+ }
- if (defined(loadedTile)) {
- loadedTile.selected = true;
- loadedTile._selectedFrame = frameState.frameNumber;
- } else {
- // if no ancestors are ready, traverse down and select ready tiles to minimize empty regions
- descendantStack.push(original);
- while (descendantStack.length > 0) {
- var tile = descendantStack.pop();
- var children = tile.children;
- var childrenLength = children.length;
- for (var j = 0; j < childrenLength; ++j) {
- var child = children[j];
- touch(tileset, child, outOfCore);
- if (child.contentAvailable) {
- child.selected = true;
- child._finalResolution = true;
- child._selectedFrame = frameState.frameNumber;
- }
- if (child._depth - original._depth < 2) { // prevent traversing too far
- if (!child.contentAvailable || child.refine === Cesium3DTileRefine.ADD) {
- descendantStack.push(child);
- }
- }
- }
- }
+ function markForSelection(tileset, tile, frameState) {
+ if (tile._selectedFrame === frameState.frameNumber) {
+ return;
+ }
+
+ // There may also be a tight box around just the tile's contents, e.g., for a city, we may be
+ // zoomed into a neighborhood and can cull the skyscrapers in the root tile.
+ if (tile.contentAvailable && contentVisible(tile, frameState)) {
+ var tileContent = tile.content;
+ if (tileContent.featurePropertiesDirty) {
+ // A feature's property in this tile changed, the tile needs to be re-styled.
+ tileContent.featurePropertiesDirty = false;
+ tile.lastStyleTime = 0; // Force applying the style to this tile
+ tileset._selectedTilesToStyle.push(tile);
+ } else if ((tile._selectedFrame !== frameState.frameNumber - 1)) {
+ // Tile is newly selected; it is selected this frame, but was not selected last frame.
+ tile.lastStyleTime = 0; // Force applying the style to this tile
+ tileset._selectedTilesToStyle.push(tile);
}
+ tile._selectedFrame = frameState.frameNumber;
}
}
- var scratchStack = [];
- var scratchStack2 = [];
+ function sortChildrenByDistanceToCamera(a, b) {
+ // Sort by farthest child first since this is going on a stack
+ if (b._distanceToCamera === 0 && a._distanceToCamera === 0) {
+ return b._centerZDepth - a._centerZDepth;
+ }
+
+ return b._distanceToCamera - a._distanceToCamera;
+ }
/**
* Traverse the tree while tiles are visible and check if their selected frame is the current frame.
* If so, add it to a selection queue.
- * Tiles are sorted near to far so we can take advantage of early Z.
* Furthermore, this is a preorder traversal so children tiles are selected before ancestor tiles.
*
* The reason for the preorder traversal is so that tiles can easily be marked with their
@@ -170,14 +210,18 @@ define([
* selected tiles must be no deeper than 15. This is still very unlikely.
*/
function traverseAndSelect(tileset, root, frameState) {
- var stack = scratchStack;
- var ancestorStack = scratchStack2;
-
+ var stack = selectionTraversal.stack;
+ var ancestorStack = selectionTraversal.ancestorStack;
var lastAncestor;
+
stack.push(root);
+
while (stack.length > 0 || ancestorStack.length > 0) {
+ selectionTraversal.stackMaximumLength = Math.max(selectionTraversal.stackMaximumLength, stack.length);
+ selectionTraversal.ancestorStackMaximumLength = Math.max(selectionTraversal.ancestorStackMaximumLength, ancestorStack.length);
+
if (ancestorStack.length > 0) {
- var waitingTile = ancestorStack[ancestorStack.length - 1];
+ var waitingTile = ancestorStack.get(ancestorStack.length - 1);
if (waitingTile._stackLength === stack.length) {
ancestorStack.pop();
if (waitingTile === lastAncestor) {
@@ -189,36 +233,32 @@ define([
}
var tile = stack.pop();
- if (!defined(tile) || !isVisited(tile, frameState)) {
+ if (!defined(tile)) {
+ // stack is empty but ancestorStack isn't
continue;
}
- var shouldSelect = tile.selected && tile._selectedFrame === frameState.frameNumber && tile.hasRenderableContent;
-
+ var add = tile.refine === Cesium3DTileRefine.ADD;
+ var markedForSelection = tile._selectedFrame === frameState.frameNumber;
var children = tile.children;
var childrenLength = children.length;
-
children.sort(sortChildrenByDistanceToCamera);
- if (shouldSelect) {
- if (tile.refine === Cesium3DTileRefine.ADD) {
+ if (markedForSelection) {
+ if (add) {
tile._finalResolution = true;
selectTile(tileset, tile, frameState);
} else {
tile._selectionDepth = ancestorStack.length;
-
if (tile._selectionDepth > 0) {
tileset._hasMixedContent = true;
}
-
lastAncestor = tile;
-
if (childrenLength === 0) {
tile._finalResolution = true;
selectTile(tileset, tile, frameState);
continue;
}
-
ancestorStack.push(tile);
tile._stackLength = stack.length;
}
@@ -231,456 +271,72 @@ define([
}
}
- function selectTile(tileset, tile, frameState) {
- // There may also be a tight box around just the tile's contents, e.g., for a city, we may be
- // zoomed into a neighborhood and can cull the skyscrapers in the root tile.
- if (tile.contentAvailable && (
- (tile._visibilityPlaneMask === CullingVolume.MASK_INSIDE) ||
- (tile.contentVisibility(frameState) !== Intersect.OUTSIDE)
- )) {
- tileset._selectedTiles.push(tile);
-
- var tileContent = tile.content;
- if (tileContent.featurePropertiesDirty) {
- // A feature's property in this tile changed, the tile needs to be re-styled.
- tileContent.featurePropertiesDirty = false;
- tile.lastStyleTime = 0; // Force applying the style to this tile
- tileset._selectedTilesToStyle.push(tile);
- } else if ((tile._lastSelectedFrameNumber !== frameState.frameNumber - 1) || tile.lastStyleTime === 0) {
- // Tile is newly selected; it is selected this frame, but was not selected last frame.
- tileset._selectedTilesToStyle.push(tile);
- }
- tile._lastSelectedFrameNumber = frameState.frameNumber;
- }
- }
-
- // PERFORMANCE_IDEA: is it worth exploiting frame-to-frame coherence in the sort, i.e., the
- // list of children are probably fully or mostly sorted unless the camera moved significantly?
- function sortChildrenByDistanceToCamera(a, b) {
- // Sort by farthest child first since this is going on a stack
- if (b._distanceToCamera === 0 && a._distanceToCamera === 0) {
- return b._centerZDepth - a._centerZDepth;
- }
-
- return b._distanceToCamera - a._distanceToCamera;
- }
-
- var emptyArray = freezeObject([]);
-
- function BaseTraversal() {
- this.tileset = undefined;
- this.frameState = undefined;
- this.outOfCore = undefined;
- this.stack = new ManagedArray();
- this.leaves = new ManagedArray();
- this.baseScreenSpaceError = undefined;
- this.internalDFS = new InternalBaseTraversal();
- }
-
- BaseTraversal.prototype.execute = function(tileset, root, baseScreenSpaceError, frameState, outOfCore) {
- this.tileset = tileset;
- this.frameState = frameState;
- this.outOfCore = outOfCore;
- this.leaves.length = 0;
- this.baseScreenSpaceError = Math.max(baseScreenSpaceError, this.tileset._maximumScreenSpaceError);
- this.internalDFS.tileset = this.tileset;
- this.internalDFS.frameState = this.frameState;
- this.internalDFS.outOfCore = this.outOfCore;
- this.internalDFS.baseScreenSpaceError = this.baseScreenSpaceError;
- depthFirstSearch(root, this);
- };
-
- BaseTraversal.prototype.visitStart = function(tile) {
- if (!isVisited(tile, this.frameState)) {
- visitTile(this.tileset, tile, this.frameState, this.outOfCore);
- }
- };
-
- BaseTraversal.prototype.visitEnd = function(tile) {
- tile._lastVisitedFrame = this.frameState.frameNumber;
- };
-
- BaseTraversal.prototype.getChildren = function(tile) {
- var tileset = this.tileset;
- var outOfCore = this.outOfCore;
- var frameState = this.frameState;
- if (!baseUpdateAndCheckChildren(tileset, tile, this.baseScreenSpaceError, frameState)) {
- return emptyArray;
- }
-
- var children = tile.children;
- var childrenLength = children.length;
- var allReady = true;
- var replacementWithContent = tile.refine === Cesium3DTileRefine.REPLACE && tile.hasRenderableContent;
- for (var i = 0; i < childrenLength; ++i) {
- var child = children[i];
- loadTile(tileset, child, frameState, true);
- touch(tileset, child, outOfCore);
-
- // content cannot be replaced until all of the nearest descendants with content are all loaded
- if (replacementWithContent) {
- if (!child.hasEmptyContent) {
- allReady = allReady && child.contentAvailable;
- } else {
- allReady = allReady && this.internalDFS.execute(child);
- }
- }
- }
-
- if (allReady) {
- return children;
- }
-
- return emptyArray;
- };
-
- function baseUpdateAndCheckChildren(tileset, tile, baseScreenSpaceError, frameState) {
- if (hasAdditiveContent(tile)) {
- tileset._desiredTiles.push(tile);
- }
-
- // Stop traversal on the subtree since it will be destroyed
- if (tile.hasTilesetContent && tile.contentExpired) {
- return false;
- }
-
- // stop traversal when we've attained the desired level of error
- if (tile._screenSpaceError <= baseScreenSpaceError && !tile.hasTilesetContent) {
- // update children so the leaf handler can check if any are visible for the children union bound optimization
- updateChildren(tile, frameState);
- return false;
- }
-
- var childrenVisibility = updateChildren(tile, frameState);
- var showAdditive = tile.refine === Cesium3DTileRefine.ADD;
- var showReplacement = tile.refine === Cesium3DTileRefine.REPLACE && (childrenVisibility & Cesium3DTileChildrenVisibility.VISIBLE_IN_REQUEST_VOLUME) !== 0;
-
- return showAdditive || showReplacement || tile.hasTilesetContent || !defined(tile._ancestorWithContent);
- }
-
- BaseTraversal.prototype.shouldVisit = function(tile) {
- return isVisible(tile._visibilityPlaneMask);
- };
-
- BaseTraversal.prototype.leafHandler = function(tile) {
- // if skipLevelOfDetail is off, leaves of the base traversal get pushed to tileset._desiredTiles. additive tiles have already been pushed
- if (this.tileset._skipLevelOfDetail || !hasAdditiveContent(tile)) {
- if (tile.refine === Cesium3DTileRefine.REPLACE && !childrenAreVisible(tile)) {
- ++this.tileset._statistics.numberOfTilesCulledWithChildrenUnion;
- return;
- }
- this.leaves.push(tile);
- }
- };
-
- function InternalBaseTraversal() {
- this.tileset = undefined;
- this.frameState = undefined;
- this.outOfCore = undefined;
- this.baseScreenSpaceError = undefined;
- this.stack = new ManagedArray();
- this.allLoaded = undefined;
- }
-
- InternalBaseTraversal.prototype.execute = function(root) {
- this.allLoaded = true;
- depthFirstSearch(root, this);
- return this.allLoaded;
- };
-
- InternalBaseTraversal.prototype.visitStart = function(tile) {
- if (!isVisited(tile, this.frameState)) {
- visitTile(this.tileset, tile, this.frameState, this.outOfCore);
- }
- };
-
- InternalBaseTraversal.prototype.visitEnd = BaseTraversal.prototype.visitEnd;
-
- // Continue traversing until we have renderable content. We want the first descendants with content of the root to load
- InternalBaseTraversal.prototype.shouldVisit = function(tile) {
- return !tile.hasRenderableContent && isVisible(tile._visibilityPlaneMask);
- };
-
- InternalBaseTraversal.prototype.getChildren = function(tile) {
- var tileset = this.tileset;
- var frameState = this.frameState;
- var outOfCore = this.outOfCore;
-
- if (!baseUpdateAndCheckChildren(tileset, tile, this.baseScreenSpaceError, frameState)) {
- return emptyArray;
- }
-
- var children = tile.children;
- var childrenLength = children.length;
- for (var i = 0; i < childrenLength; ++i) {
- var child = children[i];
- loadTile(tileset, child, frameState, true);
- touch(tileset, child, outOfCore);
- if (!tile.contentAvailable) {
- this.allLoaded = false;
- }
- }
- return children;
- };
-
- InternalBaseTraversal.prototype.updateAndCheckChildren = BaseTraversal.prototype.updateAndCheckChildren;
-
- function SkipTraversal(options) {
- this.tileset = undefined;
- this.frameState = undefined;
- this.outOfCore = undefined;
- this.queue1 = new ManagedArray();
- this.queue2 = new ManagedArray();
- this.internalDFS = new InternalSkipTraversal(options.selectionHeuristic);
- this.maxChildrenLength = 0;
- this.scratchQueue = new ManagedArray();
+ function contentVisible(tile, frameState) {
+ return (tile._visibilityPlaneMask === CullingVolume.MASK_INSIDE) ||
+ (tile.contentVisibility(frameState) !== Intersect.OUTSIDE);
}
- SkipTraversal.prototype.execute = function(tileset, root, frameState, outOfCore) {
- this.tileset = tileset;
- this.frameState = frameState;
- this.outOfCore = outOfCore;
- this.internalDFS.frameState = frameState;
- this.internalDFS.outOfCore = outOfCore;
-
- this.maxChildrenLength = 0;
- breadthFirstSearch(root, this);
- this.queue1.length = 0;
- this.queue2.length = 0;
- this.scratchQueue.length = 0;
- this.scratchQueue.trim(this.maxChildrenLength);
- };
-
- SkipTraversal.prototype.visitStart = function(tile) {
- if (!isVisited(tile, this.frameState)) {
- visitTile(this.tileset, tile, this.frameState, this.outOfCore);
- }
- };
-
- SkipTraversal.prototype.visitEnd = BaseTraversal.prototype.visitEnd;
-
- SkipTraversal.prototype.getChildren = function(tile) {
- this.scratchQueue.length = 0;
- this.internalDFS.execute(tile, this.scratchQueue);
- this.maxChildrenLength = Math.max(this.maxChildrenLength, this.scratchQueue.length);
- return this.scratchQueue;
- };
-
- SkipTraversal.prototype.leafHandler = function(tile) {
- // additive tiles have already been pushed
- if (!hasAdditiveContent(tile) && !isVisited(tile, this.frameState)) {
- this.tileset._desiredTiles.push(tile);
+ function addDesiredTile(tileset, tile, frameState) {
+ if (tile._touchedFrame === frameState.frameNumber) {
+ return;
}
- };
- function InternalSkipTraversal(selectionHeuristic) {
- this.selectionHeuristic = selectionHeuristic;
- this.tileset = undefined;
- this.frameState = undefined;
- this.outOfCore = undefined;
- this.root = undefined;
- this.queue = undefined;
- this.stack = new ManagedArray();
+ tileset._desiredTiles.push(tile);
}
- InternalSkipTraversal.prototype.execute = function(root, queue) {
- this.tileset = root._tileset;
- this.root = root;
- this.queue = queue;
- depthFirstSearch(root, this);
- };
-
- InternalSkipTraversal.prototype.visitStart = function(tile) {
- if (!isVisited(tile, this.frameState)) {
- visitTile(this.tileset, tile, this.frameState, this.outOfCore);
- }
- };
-
- InternalSkipTraversal.prototype.visitEnd = BaseTraversal.prototype.visitEnd;
-
- InternalSkipTraversal.prototype.getChildren = function(tile) {
- var tileset = this.tileset;
- var maximumScreenSpaceError = tileset._maximumScreenSpaceError;
-
- // Stop traversal on the subtree since it will be destroyed
- if (tile.hasTilesetContent && tile.contentExpired) {
- return emptyArray;
- }
-
- if (!tile.hasTilesetContent) {
- if (tile.refine === Cesium3DTileRefine.ADD) {
- // Always load additive tiles
- loadTile(tileset, tile, this.frameState, true);
- if (hasAdditiveContent(tile)) {
- tileset._desiredTiles.push(tile);
- }
- }
-
- // stop traversal when we've attained the desired level of error
- if (tile._screenSpaceError <= maximumScreenSpaceError) {
- updateChildren(tile, this.frameState);
- return emptyArray;
- }
-
- // if we have reached the skipping threshold without any loaded ancestors, return empty so this tile is loaded
- if (
- (!tile.hasEmptyContent && tile.contentUnloaded) &&
- defined(tile._ancestorWithLoadedContent) &&
- this.selectionHeuristic(tileset, tile._ancestorWithLoadedContent, tile)) {
- updateChildren(tile, this.frameState);
- return emptyArray;
- }
- }
-
- var childrenVisibility = updateChildren(tile, this.frameState);
- var showAdditive = tile.refine === Cesium3DTileRefine.ADD && tile._screenSpaceError > maximumScreenSpaceError;
- var showReplacement = tile.refine === Cesium3DTileRefine.REPLACE && (childrenVisibility & Cesium3DTileChildrenVisibility.VISIBLE_IN_REQUEST_VOLUME) !== 0;
-
- // at least one child is visible, but is not in request volume. the parent must be selected
- if (childrenVisibility & Cesium3DTileChildrenVisibility.VISIBLE_NOT_IN_REQUEST_VOLUME && tile.refine === Cesium3DTileRefine.REPLACE) {
- this.tileset._desiredTiles.push(tile);
- }
-
- if (showAdditive || showReplacement || tile.hasTilesetContent) {
- var children = tile.children;
- var childrenLength = children.length;
- for (var i = 0; i < childrenLength; ++i) {
- touch(tileset, children[i], this.outOfCore);
- }
- return children;
- }
-
- return emptyArray;
- };
-
- InternalSkipTraversal.prototype.shouldVisit = function(tile) {
- return isVisibleAndMeetsSSE(this.tileset, tile, this.frameState);
- };
-
- InternalSkipTraversal.prototype.leafHandler = function(tile) {
- if (tile !== this.root) {
- if (tile.refine === Cesium3DTileRefine.REPLACE && !childrenAreVisible(tile)) {
- ++this.tileset._statistics.numberOfTilesCulledWithChildrenUnion;
- return;
- }
- if (!tile.hasEmptyContent) {
- if (this.tileset.loadSiblings) {
- var parent = tile.parent;
- var tiles = parent.children;
- var length = tiles.length;
- for (var i = 0; i < length; ++i) {
- loadTile(this.tileset, tiles[i], this.frameState, false);
- touch(this.tileset, tiles[i], this.outOfCore);
- }
- } else {
- loadTile(this.tileset, tile, this.frameState, true);
- touch(this.tileset, tile, this.outOfCore);
- }
- }
- this.queue.push(tile);
- } else if (!hasAdditiveContent(tile)) {
- // additive tiles have already been pushed
- this.tileset._desiredTiles.push(tile);
- }
- };
-
- function updateChildren(tile, frameState) {
- if (isVisited(tile, frameState)) {
- return tile._childrenVisibility;
+ function visitTile(tileset, tile, frameState) {
+ if (tile._touchedFrame === frameState.frameNumber) {
+ return;
}
- var children = tile.children;
-
- updateTransforms(children, tile.computedTransform);
- computeDistanceToCamera(children, frameState);
-
- return computeChildrenVisibility(tile, frameState);
- }
-
- function isVisited(tile, frameState) {
- // because the leaves of one tree traversal are the root of the subsequent traversal, avoid double visitation
- return tile._lastVisitedFrame === frameState.frameNumber;
- }
-
- function visitTile(tileset, tile, frameState, outOfCore) {
++tileset._statistics.visited;
- tile.selected = false;
tile._finalResolution = false;
- computeSSE(tile, frameState);
- touch(tileset, tile, outOfCore);
+ tile._ancestorWithContentAvailable = undefined;
+
tile.updateExpiration();
- tile._ancestorWithContent = undefined;
- tile._ancestorWithLoadedContent = undefined;
+
var parent = tile.parent;
if (defined(parent)) {
var replace = parent.refine === Cesium3DTileRefine.REPLACE;
- tile._ancestorWithContent = (replace && parent.hasRenderableContent) ? parent : parent._ancestorWithContent;
- tile._ancestorWithLoadedContent = (replace && parent.hasRenderableContent && parent.contentAvailable) ? parent : parent._ancestorWithLoadedContent;
+ tile._ancestorWithContentAvailable = (replace && parent.contentAvailable) ? parent : parent._ancestorWithContentAvailable;
}
}
- function touch(tileset, tile, outOfCore) {
- if (!outOfCore) {
+ function touch(tileset, tile, frameState) {
+ if (tile._touchedFrame === frameState.frameNumber) {
return;
}
- tileset._cache.touch(tile);
- }
- function computeSSE(tile, frameState) {
- if (tile._screenSpaceErrorComputedFrame !== frameState.frameNumber) {
- tile._screenSpaceErrorComputedFrame = frameState.frameNumber;
- tile._screenSpaceError = getScreenSpaceError(tile._tileset, tile.geometricError, tile, frameState);
- }
+ tileset._cache.touch(tile);
}
- function checkAdditiveVisibility(tileset, tile, frameState) {
- if (defined(tile.parent) && (tile.parent.refine === Cesium3DTileRefine.ADD)) {
- return isVisibleAndMeetsSSE(tileset, tile, frameState);
- }
- return true;
+ function getPriority(tile, useParentPriority) {
+ // TODO : we want priority of base traversal to be SSE based so that it refines fast...
+ // TODO : doesn't really matter though if we have skiplods on, though it does help a bit
+ // TODO : somehow that means we need some comparison of distance and sse
+ // The base traversal sets useParentPriority to true so that child tiles can load as soon as possible so that their parent can refine sooner.
+ // Additive tiles always load based on distance because it subjectively looks better
+ // TODO : what to do about tileset with heavy mix of replace and add. The priorities will differ a lot.
+ var parent = tile.parent;
+ var replace = tile.refine === Cesium3DTileRefine.REPLACE;
+ var add = tile.refine === Cesium3DTileRefine.ADD;
+ //if (add) {
+ return tile._distanceToCamera;
+ //} else if (replace) {
+ // var priority = (defined(parent) && (useParentPriority || tile._screenSpaceError === 0.0)) ? parent._screenSpaceError : tile._screenSpaceError;
+ // return 100000.0 - priority; // TODO : doing this just because RequestScheduler wants lower priority
+ //}
}
- function loadTile(tileset, tile, frameState, checkVisibility) {
- if ((tile.contentUnloaded || tile.contentExpired) && tile._requestedFrame !== frameState.frameNumber) {
- if (!checkVisibility || checkAdditiveVisibility(tileset, tile, frameState)) {
- tile._requestedFrame = frameState.frameNumber;
- tileset._requestedTiles.push(tile);
- }
+ function loadTile(tileset, tile, useParentPriority, frameState) {
+ if (tile._touchedFrame === frameState.frameNumber) {
+ return;
}
- }
-
- function computeChildrenVisibility(tile, frameState) {
- var flag = Cesium3DTileChildrenVisibility.NONE;
- var children = tile.children;
- var childrenLength = children.length;
- var visibilityPlaneMask = tile._visibilityPlaneMask;
- for (var k = 0; k < childrenLength; ++k) {
- var child = children[k];
-
- var visibilityMask = child.visibility(frameState, visibilityPlaneMask);
-
- if (isVisible(visibilityMask)) {
- flag |= Cesium3DTileChildrenVisibility.VISIBLE;
- }
-
- if (!child.insideViewerRequestVolume(frameState)) {
- if (isVisible(visibilityMask)) {
- flag |= Cesium3DTileChildrenVisibility.VISIBLE_NOT_IN_REQUEST_VOLUME;
- }
- visibilityMask = CullingVolume.MASK_OUTSIDE;
- } else {
- flag |= Cesium3DTileChildrenVisibility.IN_REQUEST_VOLUME;
- if (isVisible(visibilityMask)) {
- flag |= Cesium3DTileChildrenVisibility.VISIBLE_IN_REQUEST_VOLUME;
- }
- }
- child._visibilityPlaneMask = visibilityMask;
+ if (tile.contentUnloaded || tile.contentExpired) {
+ tile._priority = getPriority(tile, useParentPriority);
+ tileset._requestedTiles.push(tile);
}
-
- tile._childrenVisibility = flag;
-
- return flag;
}
function getScreenSpaceError(tileset, geometricError, tile, frameState) {
@@ -719,133 +375,270 @@ define([
return error;
}
- function computeDistanceToCamera(children, frameState) {
- var length = children.length;
- for (var i = 0; i < length; ++i) {
- var child = children[i];
- child._distanceToCamera = child.distanceToTile(frameState);
- child._centerZDepth = child.distanceToTileCenter(frameState);
+ function updateVisibility(tileset, tile, frameState) {
+ if (tile._updatedVisibilityFrame === frameState.frameNumber) {
+ return tile._visibilityPlaneMask !== CullingVolume.MASK_OUTSIDE;
}
+
+ var parent = tile.parent;
+ var parentTransorm = defined(parent) ? parent.computedTransform : tileset._modelMatrix;
+ var parentVisibilityPlaneMask = defined(parent) ? parent._visibilityPlaneMask : CullingVolume.MASK_INDETERMINATE;
+
+ tile.updateTransform(parentTransorm);
+ tile._distanceToCamera = tile.distanceToTile(frameState);
+ tile._centerZDepth = tile.distanceToTileCenter(frameState);
+ tile._screenSpaceError = getScreenSpaceError(tileset, tile.geometricError, tile, frameState);
+
+ var visibilityPlaneMask = tile.visibility(frameState, parentVisibilityPlaneMask); // Use parent's plane mask to speed up visibility test
+ var visible = visibilityPlaneMask !== CullingVolume.MASK_OUTSIDE;
+ visible = visible && tile.insideViewerRequestVolume(frameState);
+ tile._visibilityPlaneMask = visible ? visibilityPlaneMask : CullingVolume.MASK_OUTSIDE;
+ tile._updatedFrame = frameState.frameNumber;
+ return visible;
}
- function updateTransforms(children, parentTransform) {
+ function updateChildrenVisibility(tileset, tile, frameState) {
+ var anyVisible = false;
+ var children = tile.children;
var length = children.length;
for (var i = 0; i < length; ++i) {
- var child = children[i];
- child.updateTransform(parentTransform);
+ var childVisible = updateVisibility(tileset, children[i], frameState);
+ anyVisible = anyVisible || childVisible;
}
+ return anyVisible;
}
- function isVisible(visibilityPlaneMask) {
- return visibilityPlaneMask !== CullingVolume.MASK_OUTSIDE;
- }
+ function getVisibility(tileset, tile, maximumScreenSpaceError, frameState) {
+ updateVisibility(tileset, tile, frameState);
- function isVisibleAndMeetsSSE(tileset, tile, frameState) {
- var maximumScreenSpaceError = tileset._maximumScreenSpaceError;
+ // Not visible. Visibility is updated in its parent's call to updateChildren.
+ if (tile._visibilityPlaneMask === CullingVolume.MASK_OUTSIDE) {
+ return false;
+ }
+
+ // Don't visit an expired subtree because it will be destroyed
+ if (tile.hasTilesetContent && tile.contentExpired) {
+ return false;
+ }
+
+ // Use parent's geometric error with child's box to see if we already meet the SSE
var parent = tile.parent;
- if (!defined(parent)) {
- return isVisible(tile._visibilityPlaneMask);
+ if (defined(parent) && (parent.refine === Cesium3DTileRefine.ADD) && getScreenSpaceError(tileset, parent.geometricError, tile, frameState) <= maximumScreenSpaceError) {
+ return false;
}
- var showAdditive = parent.refine === Cesium3DTileRefine.ADD && parent._screenSpaceError > maximumScreenSpaceError;
- return isVisible(tile._visibilityPlaneMask) && (!showAdditive || getScreenSpaceError(tileset, parent.geometricError, tile, frameState) > maximumScreenSpaceError);
- }
+ // Optimization - if none of the tile's children are visible then this tile isn't visible
+ var replace = tile.refine === Cesium3DTileRefine.REPLACE;
+ var useOptimization = tile._optimChildrenWithinParent === Cesium3DTileOptimizationHint.USE_OPTIMIZATION;
+ var hasChildren = tile.children.length > 0;
+ if (replace && useOptimization && hasChildren) {
+ var anyChildrenVisible = updateChildrenVisibility(tileset, tile, frameState);
+ if (!anyChildrenVisible) {
+ ++tileset._statistics.numberOfTilesCulledWithChildrenUnion;
+ return false;
+ }
+ }
- function childrenAreVisible(tile) {
- // optimization does not apply for additive refinement
- return tile.refine === Cesium3DTileRefine.ADD || tile.children.length === 0 || tile._childrenVisibility & Cesium3DTileChildrenVisibility.VISIBLE !== 0;
+ return true;
}
- function hasAdditiveContent(tile) {
- return tile.refine === Cesium3DTileRefine.ADD && tile.hasRenderableContent;
+ function updateTile(tileset, tile, maximumScreenSpaceError, frameState) {
+ if (tile._touchedFrame === frameState.frameNumber) {
+ return tile._visibilityPlaneMask !== CullingVolume.MASK_OUTSIDE;
+ }
+
+ var visible = getVisibility(tileset, tile, maximumScreenSpaceError, frameState);
+ tile._visibilityPlaneMask = visible ? tile._visibilityPlaneMask : CullingVolume.MASK_OUTSIDE;
+ return tile._visibilityPlaneMask !== CullingVolume.MASK_OUTSIDE;
}
- function depthFirstSearch(root, options) {
- var stack = options.stack;
+ function hasEmptyContent(tile) {
+ return tile.hasEmptyContent || tile.hasTilesetContent;
+ }
- if (defined(root) && (!defined(options.shouldVisit) || options.shouldVisit(root))) {
- stack.push(root);
- }
+ function executeBaseTraversal(tileset, root, baseScreenSpaceError, maximumScreenSpaceError, leaves, frameState) {
+ // TODO wording
+ // Depth-first traversal that loads all tiles that are visible and don't meet the screen space error.
+ // For replacement refinement: selects tiles to render that can't refine further - either because
+ // their children aren't loaded yet or they meet the screen space error.
+ // For additive refinement: select all tiles to render
+ var stack = baseTraversal.stack;
+ stack.push(root);
- var maxLength = 0;
while (stack.length > 0) {
- maxLength = Math.max(maxLength, stack.length);
+ baseTraversal.stackMaximumLength = Math.max(baseTraversal.stackMaximumLength, stack.length);
var tile = stack.pop();
- options.visitStart(tile);
- var children = options.getChildren(tile);
- var isNativeArray = !defined(children.get);
- var length = children.length;
- for (var i = 0; i < length; ++i) {
- var child = isNativeArray ? children[i] : children.get(i);
-
- if (!defined(options.shouldVisit) || options.shouldVisit(child)) {
- stack.push(child);
+ var add = tile.refine === Cesium3DTileRefine.ADD;
+ var replace = tile.refine === Cesium3DTileRefine.REPLACE;
+ var children = tile.children;
+ var childrenLength = children.length;
+ var contentAvailable = tile.contentAvailable;
+ var traverse = (childrenLength > 0) && (tile._screenSpaceError > baseScreenSpaceError);
+ var parent = tile.parent;
+ var parentRefines = !defined(parent) || parent._refines;
+ var refines = traverse && parentRefines;
+
+ if (traverse) {
+ for (var i = 0; i < childrenLength; ++i) {
+ var child = children[i];
+ var visible = updateTile(tileset, child, maximumScreenSpaceError, frameState);
+ if (visible) {
+ stack.push(child);
+ }
+ if (replace && !hasEmptyContent(tile)) {
+ // Check if the parent can refine to this child. If the child is empty we need to traverse further to
+ // load all descendants with content. Keep non-visible children loaded since they are still needed before the parent can refine.
+ // We don't do this for empty tiles because it looks better if children stream in as they are loaded to fill the empty space.
+ if (!visible) {
+ loadTile(tileset, child, true, frameState);
+ touch(tileset, child, frameState);
+ child._touchedFrame = frameState.frameNumber;
+ }
+
+ // Always run the internal base traversal even if we already know we can't refine. This keeps the tiles loaded while we wait to refine.
+ var refinesToChild = hasEmptyContent(child) ? executeInternalBaseTraversal(tileset, child, baseScreenSpaceError, maximumScreenSpaceError, frameState) : child.contentAvailable;
+ refines = refines && refinesToChild;
+ }
}
}
- if (length === 0 && defined(options.leafHandler)) {
- options.leafHandler(tile);
+ if (replace && contentAvailable && !refines && parentRefines) {
+ // Replacement tiles that have loaded content but can't refine further are desired
+ addDesiredTile(tileset, tile, frameState);
}
- options.visitEnd(tile);
- }
- stack.trim(maxLength);
- }
+ if (add && contentAvailable) {
+ // Additive tiles that have loaded content are always desired
+ addDesiredTile(tileset, tile, frameState);
+ }
- function breadthFirstSearch(root, options) {
- var queue1 = options.queue1;
- var queue2 = options.queue2;
+ if (defined(leaves) && !traverse && (childrenLength > 0) && (tile._screenSpaceError > maximumScreenSpaceError)) {
+ // If the tile cannot traverse further in the base traversal but can traverse further in the skip traversal, add it to leaves
+ // leaves is undefined if we are just running the base traversal
+ leaves.push(tile);
+ }
- if (defined(root) && (!defined(options.shouldVisit) || options.shouldVisit(root))) {
- queue1.push(root);
+ visitTile(tileset, tile, frameState);
+ loadTile(tileset, tile, true, frameState);
+ touch(tileset, tile, frameState);
+ tile._touchedFrame = frameState.frameNumber;
+ tile._refines = refines;
}
+ }
- var maxLength = 0;
- while (queue1.length > 0) {
- var length = queue1.length;
- maxLength = Math.max(maxLength, length);
-
- for (var i = 0; i < length; ++i) {
- var tile = queue1.get(i);
- options.visitStart(tile);
- var children = options.getChildren(tile);
- var isNativeArray = !defined(children.get);
- var childrenLength = children.length;
- for (var j = 0; j < childrenLength; ++j) {
- var child = isNativeArray ? children[j] : children.get(j);
-
- if (!defined(options.shouldVisit) || options.shouldVisit(child)) {
- queue2.push(child);
- }
- }
+ function executeInternalBaseTraversal(tileset, root, baseScreenSpaceError, maximumScreenSpaceError, frameState) {
+ // Depth-first traversal that checks if all nearest descendants with content are loaded. Ignores visibility.
+ // The reason we need to implement a full traversal is to handle chains of empty tiles.
+ var allDescendantsLoaded = true;
- if (childrenLength === 0 && defined(options.leafHandler)) {
- options.leafHandler(tile);
- }
- options.visitEnd(tile);
+ var stack = internalBaseTraversal.stack;
+ stack.push(root);
+
+ while (stack.length > 0) {
+ internalBaseTraversal.stackMaximumLength = Math.max(internalBaseTraversal.stackMaximumLength, stack.length);
+
+ var tile = stack.pop();
+ var children = tile.children;
+ var childrenLength = children.length;
+
+ // Only traverse if the tile is empty - we are trying to find descendants with content
+ var traverse = hasEmptyContent(tile) && (childrenLength > 0) && (tile._screenSpaceError > baseScreenSpaceError);
+
+ // When we reach a "leaf" that does not have content available we know that not all descendants are loaded
+ // i.e. there will be holes if the parent tries to refine to its children, so don't refine
+ if (!traverse && !tile.contentAvailable) {
+ allDescendantsLoaded = false;
}
- queue1.length = 0;
- var temp = queue1;
- queue1 = queue2;
- queue2 = temp;
- options.queue1 = queue1;
- options.queue2 = queue2;
+ var visible = updateTile(tileset, tile, maximumScreenSpaceError, frameState);
+ if (!visible) {
+ // Load tiles that aren't visible since they are still needed for the parent to refine
+ // Tiles that are visible will get loaded from within executeBaseTraversal
+ loadTile(tileset, tile, true, frameState);
+ touch(tileset, tile, frameState);
+ tile._touchedFrame = frameState.frameNumber;
+ }
+
+ if (traverse) {
+ for (var i = 0; i < childrenLength; ++i) {
+ var child = children[i];
+ stack.push(child);
+ }
+ }
}
- queue1.length = 0;
- queue2.length = 0;
+ return allDescendantsLoaded;
+ }
- queue1.trim(maxLength);
- queue2.trim(maxLength);
+ function reachedSkippingThreshold(tileset, tile) {
+ // TODO : dont understand this comment. Why is defined(tile)?
+ // If we have reached the skipping threshold without any loaded ancestors, return empty so this tile is loaded
+ var ancestor = tile._ancestorWithContentAvailable;
+ var skipLevels = tileset.skipLevelOfDetail ? tileset.skipLevels : 0;
+ var skipScreenSpaceErrorFactor = tileset.skipLevelOfDetail ? tileset.skipScreenSpaceErrorFactor : 1.0;
+
+ return !tileset.immediatelyLoadDesiredLevelOfDetail &&
+ tile.contentUnloaded &&
+ defined(ancestor) && (ancestor !== tile) &&
+ (tile._screenSpaceError < (ancestor._screenSpaceError / skipScreenSpaceErrorFactor)) &&
+ (tile._depth > (ancestor._depth + skipLevels));
}
- Cesium3DTilesetTraversal.selectTiles = selectTiles;
+ function executeSkipTraversal(tileset, maximumScreenSpaceError, frameState) {
+ // Depth-first traversal that skips tiles until in reaches the skipping threshold.
+ var i;
+
+ var stack = skipTraversal.stack;
+
+ while (stack.length > 0) {
+ skipTraversal.stackMaximumLength = Math.max(skipTraversal.stackMaximumLength, stack.length);
+
+ var tile = stack.pop();
+ var add = tile.refine === Cesium3DTileRefine.ADD;
+ var replace = tile.refine === Cesium3DTileRefine.REPLACE;
+
+ visitTile(tileset, tile, frameState);
+
+ var children = tile.children;
+ var childrenLength = children.length;
+ var traverse = (childrenLength > 0) && (tile._screenSpaceError > maximumScreenSpaceError);
+
+ if (traverse) {
+ for (i = 0; i < childrenLength; ++i) {
+ var child = children[i];
+ var visible = updateTile(tileset, child, maximumScreenSpaceError, frameState);
+ if (visible) {
+ stack.push(child);
+ }
+ }
+ }
- Cesium3DTilesetTraversal.BaseTraversal = BaseTraversal;
+ if (add) {
+ // Additive tiles are always desired
+ if (tile.contentAvailable) {
+ addDesiredTile(tileset, tile, frameState);
+ }
+ loadTile(tileset, tile, false, frameState);
+ touch(tileset, tile, frameState);
+ tile._touchedFrame = frameState.frameNumber;
+ }
- Cesium3DTilesetTraversal.SkipTraversal = SkipTraversal;
+ if (replace) {
+ if (!traverse || reachedSkippingThreshold(tileset, tile)) {
+ addDesiredTile(tileset, tile, frameState);
+ loadTile(tileset, tile, false, frameState);
+ touch(tileset, tile, frameState);
+ tile._touchedFrame = frameState.frameNumber;
+ } else {
+ // Skipped tiles may still have loaded content that we want to preserve
+ touch(tileset, tile, frameState);
+ tile._touchedFrame = frameState.frameNumber;
+ }
+ }
+ }
+ // TODO : ignoring loadSiblings right now
+ }
return Cesium3DTilesetTraversal;
});
diff --git a/Specs/Scene/Cesium3DTilesetSpec.js b/Specs/Scene/Cesium3DTilesetSpec.js
index 1e0330376b61..f24d67e4da0a 100644
--- a/Specs/Scene/Cesium3DTilesetSpec.js
+++ b/Specs/Scene/Cesium3DTilesetSpec.js
@@ -1021,7 +1021,7 @@ defineSuite([
// No longer renders the tile with a request volume
setZoom(1500.0);
- root.hasRenderableContent = true; // mock content
+ root.contentAvailable = true; // mock content
scene.renderForSpecs();
expect(statistics.numberOfCommands).toEqual(4);
expect(root.selected).toBe(true); // one child is no longer selected. root is chosen instead
From 7500d7d3ed080edddc206a003e683e2ba5c7d7e5 Mon Sep 17 00:00:00 2001
From: Sean Lilley
Date: Tue, 20 Mar 2018 15:54:10 -0400
Subject: [PATCH 05/53] Stable
---
Source/Scene/Cesium3DTileBatchTable.js | 7 ---
Source/Scene/Cesium3DTileset.js | 3 +-
Source/Scene/Cesium3DTilesetTraversal.js | 62 ++++++++++++++++++++----
3 files changed, 54 insertions(+), 18 deletions(-)
diff --git a/Source/Scene/Cesium3DTileBatchTable.js b/Source/Scene/Cesium3DTileBatchTable.js
index 87982d59062c..5c158e3ab61d 100644
--- a/Source/Scene/Cesium3DTileBatchTable.js
+++ b/Source/Scene/Cesium3DTileBatchTable.js
@@ -1356,13 +1356,6 @@ define([
tileset._backfaceCommands.push(derivedCommands.zback);
}
if (!defined(derivedCommands.stencil) || tile._selectionDepth !== getLastSelectionDepth(derivedCommands.stencil)) {
- // if (defined(derivedCommands.stencil)) {
- // var currentSelectionDepth = tile._selectionDepth;
- // var newSelectionDepth = getLastSelectionDepth(derivedCommands.stencil);
- // console.log('My selection depth: ' + tile._selectionDepth);
- // console.log('Previous selection depth: ' + (defined(derivedCommands.stencil) ? getLastSelectionDepth(derivedCommands.stencil) : 'None'));
- //
- // }
derivedCommands.stencil = deriveStencilCommand(derivedCommands.originalCommand, tile._selectionDepth);
}
updateDerivedCommand(derivedCommands.stencil, command);
diff --git a/Source/Scene/Cesium3DTileset.js b/Source/Scene/Cesium3DTileset.js
index 310f9ca94cc8..0e1452e0d670 100644
--- a/Source/Scene/Cesium3DTileset.js
+++ b/Source/Scene/Cesium3DTileset.js
@@ -1437,6 +1437,7 @@ define([
}
function requestTiles(tileset) {
+ // TODO : sort requested tiles by priority here so we can avoid requests from being cancelled excessively
var requestedTiles = tileset._requestedTiles;
var length = requestedTiles.length;
for (var i = 0; i < length; ++i) {
@@ -1611,8 +1612,6 @@ define([
var tileVisible = tileset.tileVisible;
var i;
- console.log(tileset._hasMixedContent);
-
var bivariateVisibilityTest = tileset._skipLevelOfDetail && tileset._hasMixedContent && frameState.context.stencilBuffer && length > 0;
tileset._backfaceCommands.length = 0;
diff --git a/Source/Scene/Cesium3DTilesetTraversal.js b/Source/Scene/Cesium3DTilesetTraversal.js
index d33746b9e23c..a12efe38bb71 100644
--- a/Source/Scene/Cesium3DTilesetTraversal.js
+++ b/Source/Scene/Cesium3DTilesetTraversal.js
@@ -145,7 +145,7 @@ define([
var tile = tiles.get(i);
var loadedTile = tile.contentAvailable ? tile : tile._ancestorWithContentAvailable;
if (defined(loadedTile)) {
- markForSelection(tileset, tile, frameState);
+ markForSelection(tileset, loadedTile, frameState);
}
}
}
@@ -276,9 +276,23 @@ define([
(tile.contentVisibility(frameState) !== Intersect.OUTSIDE);
}
+ // function addDesiredTile(tileset, tile, frameState) {
+ // if (tile._touchedFrame === frameState.frameNumber) {
+ // return;
+ // }
+ //
+ // tileset._desiredTiles.push(tile);
+ // }
+
function addDesiredTile(tileset, tile, frameState) {
- if (tile._touchedFrame === frameState.frameNumber) {
- return;
+ var desiredTiles = tileset._desiredTiles;
+ var length = desiredTiles.length;
+
+ for (var i = 0; i < length; ++i) {
+ var other = desiredTiles[i];
+ if (other === tile) {
+ return;
+ }
}
tileset._desiredTiles.push(tile);
@@ -503,8 +517,11 @@ define([
}
}
- if (replace && contentAvailable && !refines && parentRefines) {
+ // TODO : optimization of rendering visible children even if the parent hasn't refined
+
+ if (!defined(leaves) && replace && contentAvailable && !refines && parentRefines) {
// Replacement tiles that have loaded content but can't refine further are desired
+ // leaves is undefined if we are just running the base traversal
addDesiredTile(tileset, tile, frameState);
}
@@ -513,11 +530,11 @@ define([
addDesiredTile(tileset, tile, frameState);
}
- if (defined(leaves) && !traverse && (childrenLength > 0) && (tile._screenSpaceError > maximumScreenSpaceError)) {
- // If the tile cannot traverse further in the base traversal but can traverse further in the skip traversal, add it to leaves
- // leaves is undefined if we are just running the base traversal
- leaves.push(tile);
- }
+ // if (defined(leaves) && !traverse && (childrenLength > 0) && (tile._screenSpaceError > maximumScreenSpaceError)) {
+ // // If the tile cannot traverse further in the base traversal but can traverse further in the skip traversal, add it to leaves
+ // // leaves is undefined if we are just running the base traversal
+ // leaves.push(tile);
+ // }
visitTile(tileset, tile, frameState);
loadTile(tileset, tile, true, frameState);
@@ -578,6 +595,8 @@ define([
var skipLevels = tileset.skipLevelOfDetail ? tileset.skipLevels : 0;
var skipScreenSpaceErrorFactor = tileset.skipLevelOfDetail ? tileset.skipScreenSpaceErrorFactor : 1.0;
+ // TODO : why does ancestor need to be defined? When would the ancestor even equal the tile?
+ // TODO : should probably be using ancestorWithContent rather than contentAvailable?
return !tileset.immediatelyLoadDesiredLevelOfDetail &&
tile.contentUnloaded &&
defined(ancestor) && (ancestor !== tile) &&
@@ -591,6 +610,10 @@ define([
var stack = skipTraversal.stack;
+ // TODO : possibly remove this
+ stack.length = 0;
+ stack.push(tileset._root);
+
while (stack.length > 0) {
skipTraversal.stackMaximumLength = Math.max(skipTraversal.stackMaximumLength, stack.length);
@@ -604,6 +627,11 @@ define([
var childrenLength = children.length;
var traverse = (childrenLength > 0) && (tile._screenSpaceError > maximumScreenSpaceError);
+ // TODO : possibly remove this
+ if (traverse && replace && reachedSkippingThreshold(tileset, tile)) {
+ traverse = false;
+ }
+
if (traverse) {
for (i = 0; i < childrenLength; ++i) {
var child = children[i];
@@ -636,6 +664,22 @@ define([
tile._touchedFrame = frameState.frameNumber;
}
}
+
+ // if (replace) {
+ // if (!traverse) {
+ // addDesiredTile(tileset, tile, frameState);
+ // loadTile(tileset, tile, false, frameState);
+ // touch(tileset, tile, frameState);
+ // tile._touchedFrame = frameState.frameNumber;
+ // } else if (reachedSkippingThreshold(tileset, tile)) {
+ // loadTile(tileset, tile, false, frameState);
+ // touch(tileset, tile, frameState);
+ // tile._touchedFrame = frameState.frameNumber;
+ // } else {
+ // touch(tileset, tile, frameState);
+ // tile._touchedFrame = frameState.frameNumber;
+ // }
+ // }
}
// TODO : ignoring loadSiblings right now
}
From b2854393300e6ee2e2fdd7b46a4fa6212ae5e9a0 Mon Sep 17 00:00:00 2001
From: Sean Lilley
Date: Wed, 21 Mar 2018 13:55:11 -0400
Subject: [PATCH 06/53] Optimizations
---
Source/Scene/Cesium3DTile.js | 1 +
Source/Scene/Cesium3DTilesetTraversal.js | 72 ++++++++----------------
2 files changed, 25 insertions(+), 48 deletions(-)
diff --git a/Source/Scene/Cesium3DTile.js b/Source/Scene/Cesium3DTile.js
index 84cb889323ac..e39dd5fdb8b3 100644
--- a/Source/Scene/Cesium3DTile.js
+++ b/Source/Scene/Cesium3DTile.js
@@ -325,6 +325,7 @@ define([
this._updatedVisibilityFrame = 0;
this._touchedFrame = 0;
this._selectedFrame = 0;
+ this._ancestorWithContent = undefined;
this._ancestorWithContentAvailable = undefined;
this._refines = false;
this._priority = 0.0;
diff --git a/Source/Scene/Cesium3DTilesetTraversal.js b/Source/Scene/Cesium3DTilesetTraversal.js
index a12efe38bb71..48eace509ef1 100644
--- a/Source/Scene/Cesium3DTilesetTraversal.js
+++ b/Source/Scene/Cesium3DTilesetTraversal.js
@@ -305,14 +305,15 @@ define([
++tileset._statistics.visited;
tile._finalResolution = false;
+ tile._ancestorWithContent = undefined;
tile._ancestorWithContentAvailable = undefined;
tile.updateExpiration();
var parent = tile.parent;
if (defined(parent)) {
- var replace = parent.refine === Cesium3DTileRefine.REPLACE;
- tile._ancestorWithContentAvailable = (replace && parent.contentAvailable) ? parent : parent._ancestorWithContentAvailable;
+ tile._ancestorWithContent = !hasEmptyContent(parent) ? parent : parent._ancestorWithContent;
+ tile._ancestorWithContentAvailable = parent.contentAvailable ? parent : parent._ancestorWithContentAvailable;
}
}
@@ -589,17 +590,14 @@ define([
}
function reachedSkippingThreshold(tileset, tile) {
- // TODO : dont understand this comment. Why is defined(tile)?
- // If we have reached the skipping threshold without any loaded ancestors, return empty so this tile is loaded
- var ancestor = tile._ancestorWithContentAvailable;
+ // Look at the nearest ancestor that wasn't skipped. It doesn't need to be loaded yet.
+ var ancestor = tile._ancestorWithContent;
var skipLevels = tileset.skipLevelOfDetail ? tileset.skipLevels : 0;
var skipScreenSpaceErrorFactor = tileset.skipLevelOfDetail ? tileset.skipScreenSpaceErrorFactor : 1.0;
- // TODO : why does ancestor need to be defined? When would the ancestor even equal the tile?
- // TODO : should probably be using ancestorWithContent rather than contentAvailable?
return !tileset.immediatelyLoadDesiredLevelOfDetail &&
tile.contentUnloaded &&
- defined(ancestor) && (ancestor !== tile) &&
+ defined(ancestor) &&
(tile._screenSpaceError < (ancestor._screenSpaceError / skipScreenSpaceErrorFactor)) &&
(tile._depth > (ancestor._depth + skipLevels));
}
@@ -620,26 +618,23 @@ define([
var tile = stack.pop();
var add = tile.refine === Cesium3DTileRefine.ADD;
var replace = tile.refine === Cesium3DTileRefine.REPLACE;
-
- visitTile(tileset, tile, frameState);
-
var children = tile.children;
var childrenLength = children.length;
var traverse = (childrenLength > 0) && (tile._screenSpaceError > maximumScreenSpaceError);
- // TODO : possibly remove this
- if (traverse && replace && reachedSkippingThreshold(tileset, tile)) {
- traverse = false;
- }
+ visitTile(tileset, tile, frameState);
- if (traverse) {
- for (i = 0; i < childrenLength; ++i) {
- var child = children[i];
- var visible = updateTile(tileset, child, maximumScreenSpaceError, frameState);
- if (visible) {
- stack.push(child);
- }
+ if (replace) {
+ if (!traverse) {
+ addDesiredTile(tileset, tile, frameState);
+ loadTile(tileset, tile, false, frameState);
+ } else if (reachedSkippingThreshold(tileset, tile)) {
+ loadTile(tileset, tile, false, frameState);
+ traverse = false;
}
+ // Always touch tiles. Even tiles that are skipped should stay loaded.
+ touch(tileset, tile, frameState);
+ tile._touchedFrame = frameState.frameNumber;
}
if (add) {
@@ -652,34 +647,15 @@ define([
tile._touchedFrame = frameState.frameNumber;
}
- if (replace) {
- if (!traverse || reachedSkippingThreshold(tileset, tile)) {
- addDesiredTile(tileset, tile, frameState);
- loadTile(tileset, tile, false, frameState);
- touch(tileset, tile, frameState);
- tile._touchedFrame = frameState.frameNumber;
- } else {
- // Skipped tiles may still have loaded content that we want to preserve
- touch(tileset, tile, frameState);
- tile._touchedFrame = frameState.frameNumber;
+ if (traverse) {
+ for (i = 0; i < childrenLength; ++i) {
+ var child = children[i];
+ var visible = updateTile(tileset, child, maximumScreenSpaceError, frameState);
+ if (visible) {
+ stack.push(child);
+ }
}
}
-
- // if (replace) {
- // if (!traverse) {
- // addDesiredTile(tileset, tile, frameState);
- // loadTile(tileset, tile, false, frameState);
- // touch(tileset, tile, frameState);
- // tile._touchedFrame = frameState.frameNumber;
- // } else if (reachedSkippingThreshold(tileset, tile)) {
- // loadTile(tileset, tile, false, frameState);
- // touch(tileset, tile, frameState);
- // tile._touchedFrame = frameState.frameNumber;
- // } else {
- // touch(tileset, tile, frameState);
- // tile._touchedFrame = frameState.frameNumber;
- // }
- // }
}
// TODO : ignoring loadSiblings right now
}
From b44933d751b501e7f62e4e5ad2ea25e778f2c103 Mon Sep 17 00:00:00 2001
From: Sean Lilley
Date: Wed, 21 Mar 2018 16:19:43 -0400
Subject: [PATCH 07/53] Fix bug with skipping. Solid
---
Source/Scene/Cesium3DTile.js | 1 +
Source/Scene/Cesium3DTileset.js | 9 ++++++++-
Source/Scene/Cesium3DTilesetTraversal.js | 19 ++++++++++++-------
3 files changed, 21 insertions(+), 8 deletions(-)
diff --git a/Source/Scene/Cesium3DTile.js b/Source/Scene/Cesium3DTile.js
index e39dd5fdb8b3..ffb89050c24c 100644
--- a/Source/Scene/Cesium3DTile.js
+++ b/Source/Scene/Cesium3DTile.js
@@ -325,6 +325,7 @@ define([
this._updatedVisibilityFrame = 0;
this._touchedFrame = 0;
this._selectedFrame = 0;
+ this._requestedFrame = 0;
this._ancestorWithContent = undefined;
this._ancestorWithContentAvailable = undefined;
this._refines = false;
diff --git a/Source/Scene/Cesium3DTileset.js b/Source/Scene/Cesium3DTileset.js
index 0e1452e0d670..0d49bcda5df9 100644
--- a/Source/Scene/Cesium3DTileset.js
+++ b/Source/Scene/Cesium3DTileset.js
@@ -1436,10 +1436,16 @@ define([
});
}
+ function sortRequestByPriority(a, b) {
+ return a._priority - b._priority;
+ }
+
function requestTiles(tileset) {
- // TODO : sort requested tiles by priority here so we can avoid requests from being cancelled excessively
+ // Sort requests by priority before making any requests.
+ // This makes it less likely that requests will be cancelled after being issued.
var requestedTiles = tileset._requestedTiles;
var length = requestedTiles.length;
+ requestedTiles.sort(sortRequestByPriority);
for (var i = 0; i < length; ++i) {
requestContent(tileset, requestedTiles[i]);
}
@@ -1821,6 +1827,7 @@ define([
}
if (outOfCore) {
+ this._requestedTiles.length = 0;
Cesium3DTilesetTraversal.selectTiles(this, frameState);
requestTiles(this);
processTiles(this, frameState);
diff --git a/Source/Scene/Cesium3DTilesetTraversal.js b/Source/Scene/Cesium3DTilesetTraversal.js
index 48eace509ef1..cdfb9acc577e 100644
--- a/Source/Scene/Cesium3DTilesetTraversal.js
+++ b/Source/Scene/Cesium3DTilesetTraversal.js
@@ -61,7 +61,6 @@ define([
tileset._desiredTiles.length = 0;
tileset._selectedTiles.length = 0;
- tileset._requestedTiles.length = 0;
tileset._selectedTilesToStyle.length = 0;
tileset._hasMixedContent = false;
@@ -312,7 +311,11 @@ define([
var parent = tile.parent;
if (defined(parent)) {
- tile._ancestorWithContent = !hasEmptyContent(parent) ? parent : parent._ancestorWithContent;
+ // ancestorWithContent is an ancestor that has content or has the potential to have
+ // content. Used in conjunction with tileset.skipLevels to know when to skip a tile.
+ // ancestorWithContentAvailable is an ancestor that we can render if a desired tile is not loaded.
+ var hasContent = !hasUnloadedContent(parent) || (parent._requestedFrame === frameState.frameNumber);
+ tile._ancestorWithContent = hasContent ? parent : parent._ancestorWithContent;
tile._ancestorWithContentAvailable = parent.contentAvailable ? parent : parent._ancestorWithContentAvailable;
}
}
@@ -348,7 +351,8 @@ define([
return;
}
- if (tile.contentUnloaded || tile.contentExpired) {
+ if (hasUnloadedContent(tile) || tile.contentExpired) {
+ tile._requestedFrame = frameState.frameNumber;
tile._priority = getPriority(tile, useParentPriority);
tileset._requestedTiles.push(tile);
}
@@ -426,7 +430,7 @@ define([
function getVisibility(tileset, tile, maximumScreenSpaceError, frameState) {
updateVisibility(tileset, tile, frameState);
- // Not visible. Visibility is updated in its parent's call to updateChildren.
+ // Not visible
if (tile._visibilityPlaneMask === CullingVolume.MASK_OUTSIDE) {
return false;
}
@@ -471,6 +475,10 @@ define([
return tile.hasEmptyContent || tile.hasTilesetContent;
}
+ function hasUnloadedContent(tile) {
+ return !hasEmptyContent(tile) && tile.contentUnloaded;
+ }
+
function executeBaseTraversal(tileset, root, baseScreenSpaceError, maximumScreenSpaceError, leaves, frameState) {
// TODO wording
// Depth-first traversal that loads all tiles that are visible and don't meet the screen space error.
@@ -590,13 +598,11 @@ define([
}
function reachedSkippingThreshold(tileset, tile) {
- // Look at the nearest ancestor that wasn't skipped. It doesn't need to be loaded yet.
var ancestor = tile._ancestorWithContent;
var skipLevels = tileset.skipLevelOfDetail ? tileset.skipLevels : 0;
var skipScreenSpaceErrorFactor = tileset.skipLevelOfDetail ? tileset.skipScreenSpaceErrorFactor : 1.0;
return !tileset.immediatelyLoadDesiredLevelOfDetail &&
- tile.contentUnloaded &&
defined(ancestor) &&
(tile._screenSpaceError < (ancestor._screenSpaceError / skipScreenSpaceErrorFactor)) &&
(tile._depth > (ancestor._depth + skipLevels));
@@ -630,7 +636,6 @@ define([
loadTile(tileset, tile, false, frameState);
} else if (reachedSkippingThreshold(tileset, tile)) {
loadTile(tileset, tile, false, frameState);
- traverse = false;
}
// Always touch tiles. Even tiles that are skipped should stay loaded.
touch(tileset, tile, frameState);
From b975f1e980d20f4b1993e0596d3d71cc62d71a90 Mon Sep 17 00:00:00 2001
From: Sean Lilley
Date: Thu, 22 Mar 2018 13:51:48 -0400
Subject: [PATCH 08/53] Combine base and skip traversal together
---
Source/Scene/Cesium3DTilesetTraversal.js | 343 +++++++++++------------
1 file changed, 160 insertions(+), 183 deletions(-)
diff --git a/Source/Scene/Cesium3DTilesetTraversal.js b/Source/Scene/Cesium3DTilesetTraversal.js
index cdfb9acc577e..c11a8a5b8592 100644
--- a/Source/Scene/Cesium3DTilesetTraversal.js
+++ b/Source/Scene/Cesium3DTilesetTraversal.js
@@ -26,17 +26,23 @@ define([
SceneMode) {
'use strict';
- var baseTraversal = {
+ /**
+ * @private
+ */
+ function Cesium3DTilesetTraversal() {
+ }
+
+ var traversal = {
stack : new ManagedArray(),
stackMaximumLength : 0
};
- var internalBaseTraversal = {
+ var emptyTraversal = {
stack : new ManagedArray(),
stackMaximumLength : 0
};
- var skipTraversal = {
+ var descendantTraversal = {
stack : new ManagedArray(),
stackMaximumLength : 0
};
@@ -48,12 +54,6 @@ define([
ancestorStackMaximumLength : 0
};
- /**
- * @private
- */
- function Cesium3DTilesetTraversal() {
- }
-
Cesium3DTilesetTraversal.selectTiles = function(tileset, frameState) {
if (tileset.debugFreezeFrame) {
return;
@@ -66,77 +66,68 @@ define([
tileset._cache.reset();
+ var maximumScreenSpaceError = tileset._maximumScreenSpaceError;
+
var root = tileset._root;
- var rootVisible = updateVisibility(tileset, root, frameState);
+ var rootVisible = updateTile(tileset, root, frameState);
if (!rootVisible) {
return;
}
// The SSE of not rendering the tree is small enough that the tree does not need to be rendered
- if (getScreenSpaceError(tileset, tileset._geometricError, root, frameState) <= tileset._maximumScreenSpaceError) {
+ if (getScreenSpaceError(tileset, tileset._geometricError, root, frameState) <= maximumScreenSpaceError) {
return;
}
- if (!tileset._skipLevelOfDetail) {//} || tileset._allTilesAdditive) {
- selectBaseTraversal(tileset, root, frameState);
+ if (!tileset._skipLevelOfDetail) {//} || tileset._allTilesAdditive) { TODO
+ executeBaseTraversal(tileset, root, frameState);
} else if (tileset.immediatelyLoadDesiredLevelOfDetail) {
- selectSkipTraversal(tileset, root, frameState);
+ executeSkipTraversal(tileset, root, frameState);
} else {
- selectBaseAndSkipTraversal(tileset, root, frameState);
+ executeBaseAndSkipTraversal(tileset, root, frameState);
}
- // TODO : these aren't really correct because min/max never get reset.
tileset._desiredTiles.trim();
- baseTraversal.stack.trim(baseTraversal.stackMaximumLength);
- internalBaseTraversal.stack.trim(internalBaseTraversal.stackMaximumLength);
- skipTraversal.stack.trim(skipTraversal.stackMaximumLength);
+ traversal.stack.trim(traversal.stackMaximumLength);
+ emptyTraversal.stack.trim(emptyTraversal.stackMaximumLength);
+ descendantTraversal.stack.trim(descendantTraversal.stackMaximumLength);
selectionTraversal.stack.trim(selectionTraversal.stackMaximumLength);
selectionTraversal.ancestorStack.trim(selectionTraversal.ancestorStackMaximumLength);
};
- function selectBaseTraversal(tileset, root, frameState) {
+ function executeBaseTraversal(tileset, root, frameState) {
+ var baseScreenSpaceError = tileset._maximumScreenSpaceError;
var maximumScreenSpaceError = tileset._maximumScreenSpaceError;
- executeBaseTraversal(tileset, root, maximumScreenSpaceError, maximumScreenSpaceError, undefined, frameState);
-
- // Select desired tiles
- var desiredTiles = tileset._desiredTiles;
- var length = desiredTiles.length;
- for (var i = 0; i < length; ++i) {
- selectTile(tileset, desiredTiles.get(i), frameState);
- }
+ executeTraversal(tileset, root, baseScreenSpaceError, maximumScreenSpaceError, true, frameState);
+ selectDesiredTiles(tileset, frameState);
}
- function selectSkipTraversal(tileset, root, frameState) {
- // Start the skip traversal at the root
- skipTraversal.stack.push(root);
-
- // Execute the skip traversal
- executeSkipTraversal(tileset, tileset._maximumScreenSpaceError, frameState);
-
- // Mark tiles for selection or their nearest loaded ancestor
+ function executeSkipTraversal(tileset, root, frameState) {
+ var baseScreenSpaceError = Number.MAX_VALUE;
+ var maximumScreenSpaceError = tileset._maximumScreenSpaceError;
+ executeTraversal(tileset, root, baseScreenSpaceError, maximumScreenSpaceError, false, frameState);
markDesiredTilesForSelection(tileset, frameState);
-
- // Sort selected tiles by distance to camera and call selectTile on each
traverseAndSelect(tileset, root, frameState);
}
- function selectBaseAndSkipTraversal(tileset, root, frameState) {
+ function executeBaseAndSkipTraversal(tileset, root, frameState) {
var baseScreenSpaceError = Math.max(tileset.baseScreenSpaceError, tileset.maximumScreenSpaceError);
var maximumScreenSpaceError = tileset.maximumScreenSpaceError;
-
- // Load and select tiles without skipping up to baseScreenSpaceError
- // Leaves of the base traversal is where we start the skip traversal
- executeBaseTraversal(tileset, root, baseScreenSpaceError, maximumScreenSpaceError, skipTraversal.stack, frameState);
-
- executeSkipTraversal(tileset, maximumScreenSpaceError, frameState);
-
- // Mark tiles for selection or their nearest loaded ancestor
+ executeTraversal(tileset, root, baseScreenSpaceError, maximumScreenSpaceError, false, frameState);
markDesiredTilesForSelection(tileset, frameState);
-
- // Sort selected tiles by distance to camera and call selectTile on each
traverseAndSelect(tileset, root, frameState);
}
+ function selectDesiredTiles(tileset, frameState) {
+ var desiredTiles = tileset._desiredTiles;
+ var length = desiredTiles.length;
+ for (var i = 0; i < length; ++i) {
+ var tile = desiredTiles.get(i);
+ markForSelection(tileset, tile, frameState);
+ selectTile(tileset, tile, frameState);
+ }
+ }
+
function markDesiredTilesForSelection(tileset, frameState) {
var tiles = tileset._desiredTiles;
var length = tiles.length;
@@ -145,13 +136,34 @@ define([
var loadedTile = tile.contentAvailable ? tile : tile._ancestorWithContentAvailable;
if (defined(loadedTile)) {
markForSelection(tileset, loadedTile, frameState);
+ } else if (tileset.immediatelyLoadDesiredLevelOfDetail) {
+ // If no ancestors are ready traverse down and select tiles to minimize empty regions.
+ // This happens often in cases where the camera zooms out and we're waiting for parent tiles to load.
+ markDescendantsForSelection(tileset, tile, frameState);
}
}
}
- function selectTile(tileset, tile, frameState) {
- if (tile._selectedFrame === frameState.frameNumber) {
- tileset._selectedTiles.push(tile);
+ function markDescendantsForSelection(tileset, root, frameState) {
+ var stack = descendantTraversal.stack;
+ stack.push(root);
+ while (stack.length > 0) {
+ descendantTraversal.stackMaximumLength = Math.max(descendantTraversal.stackMaximumLength, stack.length);
+ var tile = stack.pop();
+ var children = tile.children;
+ var childrenLength = children.length;
+ for (var i = 0; i < childrenLength; ++i) {
+ var child = children[i];
+ if (child.contentAvailable) {
+ updateTile(tileset, child, frameState);
+ touchTile(tileset, child, frameState);
+ markForSelection(tileset, child, frameState);
+ tile._touchedFrame = frameState.frameNumber;
+ } else if (child._depth - root._depth < 2) {
+ // Continue traversing, but not too far
+ stack.push(child);
+ }
+ }
}
}
@@ -178,6 +190,14 @@ define([
}
}
+ function selectTile(tileset, tile, frameState) {
+ if (tile._selectedFrame === frameState.frameNumber) {
+ tileset._selectedTiles.push(tile);
+ } else {
+ console.log('TODO : why am i here');
+ }
+ }
+
function sortChildrenByDistanceToCamera(a, b) {
// Sort by farthest child first since this is going on a stack
if (b._distanceToCamera === 0 && a._distanceToCamera === 0) {
@@ -188,8 +208,8 @@ define([
}
/**
- * Traverse the tree while tiles are visible and check if their selected frame is the current frame.
- * If so, add it to a selection queue.
+ * Traverse the tree and check if their selected frame is the current frame. If so, add it to a selection queue.
+ * Tiles are sorted near to far so we can take advantage of early Z.
* Furthermore, this is a preorder traversal so children tiles are selected before ancestor tiles.
*
* The reason for the preorder traversal is so that tiles can easily be marked with their
@@ -199,7 +219,7 @@ define([
*
* We want to select children before their ancestors because there is no guarantee on the relationship between
* the children's z-depth and the ancestor's z-depth. We cannot rely on Z because we want the child to appear on top
- * of ancestor regardless of true depth. The stencil tests used require children to be drawn first. @see {@link updateTiles}
+ * of ancestor regardless of true depth. The stencil tests used require children to be drawn first.
*
* NOTE: this will no longer work when there is a chain of selected tiles that is longer than the size of the
* stencil buffer (usually 8 bits). In other words, the subset of the tree containing only selected tiles must be
@@ -275,23 +295,9 @@ define([
(tile.contentVisibility(frameState) !== Intersect.OUTSIDE);
}
- // function addDesiredTile(tileset, tile, frameState) {
- // if (tile._touchedFrame === frameState.frameNumber) {
- // return;
- // }
- //
- // tileset._desiredTiles.push(tile);
- // }
-
function addDesiredTile(tileset, tile, frameState) {
- var desiredTiles = tileset._desiredTiles;
- var length = desiredTiles.length;
-
- for (var i = 0; i < length; ++i) {
- var other = desiredTiles[i];
- if (other === tile) {
- return;
- }
+ if (tile._touchedFrame === frameState.frameNumber) {
+ return;
}
tileset._desiredTiles.push(tile);
@@ -303,24 +309,9 @@ define([
}
++tileset._statistics.visited;
- tile._finalResolution = false;
- tile._ancestorWithContent = undefined;
- tile._ancestorWithContentAvailable = undefined;
-
- tile.updateExpiration();
-
- var parent = tile.parent;
- if (defined(parent)) {
- // ancestorWithContent is an ancestor that has content or has the potential to have
- // content. Used in conjunction with tileset.skipLevels to know when to skip a tile.
- // ancestorWithContentAvailable is an ancestor that we can render if a desired tile is not loaded.
- var hasContent = !hasUnloadedContent(parent) || (parent._requestedFrame === frameState.frameNumber);
- tile._ancestorWithContent = hasContent ? parent : parent._ancestorWithContent;
- tile._ancestorWithContentAvailable = parent.contentAvailable ? parent : parent._ancestorWithContentAvailable;
- }
}
- function touch(tileset, tile, frameState) {
+ function touchTile(tileset, tile, frameState) {
if (tile._touchedFrame === frameState.frameNumber) {
return;
}
@@ -427,7 +418,7 @@ define([
return anyVisible;
}
- function getVisibility(tileset, tile, maximumScreenSpaceError, frameState) {
+ function getVisibility(tileset, tile, frameState) {
updateVisibility(tileset, tile, frameState);
// Not visible
@@ -442,7 +433,7 @@ define([
// Use parent's geometric error with child's box to see if we already meet the SSE
var parent = tile.parent;
- if (defined(parent) && (parent.refine === Cesium3DTileRefine.ADD) && getScreenSpaceError(tileset, parent.geometricError, tile, frameState) <= maximumScreenSpaceError) {
+ if (defined(parent) && (parent.refine === Cesium3DTileRefine.ADD) && getScreenSpaceError(tileset, parent.geometricError, tile, frameState) <= tileset._maximumScreenSpaceError) {
return false;
}
@@ -461,14 +452,31 @@ define([
return true;
}
- function updateTile(tileset, tile, maximumScreenSpaceError, frameState) {
+ function updateTile(tileset, tile, frameState) {
if (tile._touchedFrame === frameState.frameNumber) {
return tile._visibilityPlaneMask !== CullingVolume.MASK_OUTSIDE;
}
- var visible = getVisibility(tileset, tile, maximumScreenSpaceError, frameState);
+ var visible = getVisibility(tileset, tile, frameState);
tile._visibilityPlaneMask = visible ? tile._visibilityPlaneMask : CullingVolume.MASK_OUTSIDE;
- return tile._visibilityPlaneMask !== CullingVolume.MASK_OUTSIDE;
+
+ tile._finalResolution = false;
+ tile._ancestorWithContent = undefined;
+ tile._ancestorWithContentAvailable = undefined;
+
+ tile.updateExpiration();
+
+ var parent = tile.parent;
+ if (defined(parent)) {
+ // ancestorWithContent is an ancestor that has content or has the potential to have
+ // content. Used in conjunction with tileset.skipLevels to know when to skip a tile.
+ // ancestorWithContentAvailable is an ancestor that we can render if a desired tile is not loaded.
+ var hasContent = !hasUnloadedContent(parent) || (parent._requestedFrame === frameState.frameNumber);
+ tile._ancestorWithContent = hasContent ? parent : parent._ancestorWithContent;
+ tile._ancestorWithContentAvailable = parent.contentAvailable ? parent : parent._ancestorWithContentAvailable;
+ }
+
+ return visible;
}
function hasEmptyContent(tile) {
@@ -479,90 +487,116 @@ define([
return !hasEmptyContent(tile) && tile.contentUnloaded;
}
- function executeBaseTraversal(tileset, root, baseScreenSpaceError, maximumScreenSpaceError, leaves, frameState) {
+ function inBaseTraversal(tile, baseScreenSpaceError, maximumScreenSpaceError) {
+ // TODO : what sse would be passed if only base traversal is used?
+ // TODO : what is maximumScreenSpaceError is 0. Que paso?
+ if (baseScreenSpaceError === maximumScreenSpaceError) {
+ return true;
+ }
+
+ if (tile._screenSpaceError === 0) {
+ var parent = tile.parent;
+ if (defined(parent)) {
+ return inBaseTraversal(parent, baseScreenSpaceError, maximumScreenSpaceError);
+ }
+ return true;
+ }
+
+ return tile._screenSpaceError > baseScreenSpaceError;
+ }
+
+ function executeTraversal(tileset, root, baseScreenSpaceError, maximumScreenSpaceError, baseTraversalOnly, frameState) {
// TODO wording
// Depth-first traversal that loads all tiles that are visible and don't meet the screen space error.
// For replacement refinement: selects tiles to render that can't refine further - either because
// their children aren't loaded yet or they meet the screen space error.
// For additive refinement: select all tiles to render
- var stack = baseTraversal.stack;
+ var stack = traversal.stack;
stack.push(root);
while (stack.length > 0) {
- baseTraversal.stackMaximumLength = Math.max(baseTraversal.stackMaximumLength, stack.length);
+ traversal.stackMaximumLength = Math.max(traversal.stackMaximumLength, stack.length);
var tile = stack.pop();
+ var baseTraversal = inBaseTraversal(tile, baseScreenSpaceError, maximumScreenSpaceError);
var add = tile.refine === Cesium3DTileRefine.ADD;
var replace = tile.refine === Cesium3DTileRefine.REPLACE;
var children = tile.children;
var childrenLength = children.length;
- var contentAvailable = tile.contentAvailable;
- var traverse = (childrenLength > 0) && (tile._screenSpaceError > baseScreenSpaceError);
var parent = tile.parent;
- var parentRefines = !defined(parent) || parent._refines;
+ var parentRefines = !baseTraversalOnly || !defined(parent) || parent._refines;
+ var traverse = (childrenLength > 0) && (tile._screenSpaceError > maximumScreenSpaceError);
var refines = traverse && parentRefines;
+ visitTile(tileset, tile, frameState);
+
if (traverse) {
for (var i = 0; i < childrenLength; ++i) {
var child = children[i];
- var visible = updateTile(tileset, child, maximumScreenSpaceError, frameState);
+ var visible = updateTile(tileset, child, frameState);
if (visible) {
stack.push(child);
}
- if (replace && !hasEmptyContent(tile)) {
+ if (replace && baseTraversalOnly && baseTraversal && !hasEmptyContent(tile)) {
// Check if the parent can refine to this child. If the child is empty we need to traverse further to
// load all descendants with content. Keep non-visible children loaded since they are still needed before the parent can refine.
// We don't do this for empty tiles because it looks better if children stream in as they are loaded to fill the empty space.
if (!visible) {
loadTile(tileset, child, true, frameState);
- touch(tileset, child, frameState);
+ touchTile(tileset, child, frameState);
child._touchedFrame = frameState.frameNumber;
}
-
// Always run the internal base traversal even if we already know we can't refine. This keeps the tiles loaded while we wait to refine.
- var refinesToChild = hasEmptyContent(child) ? executeInternalBaseTraversal(tileset, child, baseScreenSpaceError, maximumScreenSpaceError, frameState) : child.contentAvailable;
+ var refinesToChild = hasEmptyContent(child) ? executeEmptyTraversal(tileset, child, baseScreenSpaceError, maximumScreenSpaceError, frameState) : child.contentAvailable;
refines = refines && refinesToChild;
}
}
}
- // TODO : optimization of rendering visible children even if the parent hasn't refined
-
- if (!defined(leaves) && replace && contentAvailable && !refines && parentRefines) {
- // Replacement tiles that have loaded content but can't refine further are desired
- // leaves is undefined if we are just running the base traversal
- addDesiredTile(tileset, tile, frameState);
+ if (replace) {
+ if (baseTraversal) {
+ // Always load tiles in the base traversal
+ // Select tiles that can't refine further
+ loadTile(tileset, tile, true, frameState);
+ if (!refines && parentRefines && tile.contentAvailable) {
+ addDesiredTile(tileset, tile, frameState);
+ }
+ } else {
+ // Load tiles that are not skipped or can't refine further. In practice roughly half the tiles are unloaded.
+ // Select tiles that can't refine further. If the tile doesn't have loaded content it will try to select an ancestor with loaded content instead.
+ if (!refines) {
+ addDesiredTile(tileset, tile, frameState);
+ loadTile(tileset, tile, false, frameState);
+ } else if (reachedSkippingThreshold(tileset, tile)) {
+ loadTile(tileset, tile, false, frameState);
+ }
+ }
}
- if (add && contentAvailable) {
- // Additive tiles that have loaded content are always desired
- addDesiredTile(tileset, tile, frameState);
+ if (add) {
+ // Additive tiles are always loaded and selected
+ if (tile.contentAvailable) {
+ addDesiredTile(tileset, tile, frameState);
+ }
+ loadTile(tileset, tile, false, frameState);
}
- // if (defined(leaves) && !traverse && (childrenLength > 0) && (tile._screenSpaceError > maximumScreenSpaceError)) {
- // // If the tile cannot traverse further in the base traversal but can traverse further in the skip traversal, add it to leaves
- // // leaves is undefined if we are just running the base traversal
- // leaves.push(tile);
- // }
-
- visitTile(tileset, tile, frameState);
- loadTile(tileset, tile, true, frameState);
- touch(tileset, tile, frameState);
+ touchTile(tileset, tile, frameState);
tile._touchedFrame = frameState.frameNumber;
tile._refines = refines;
}
}
- function executeInternalBaseTraversal(tileset, root, baseScreenSpaceError, maximumScreenSpaceError, frameState) {
+ function executeEmptyTraversal(tileset, root, baseScreenSpaceError, maximumScreenSpaceError, frameState) {
// Depth-first traversal that checks if all nearest descendants with content are loaded. Ignores visibility.
// The reason we need to implement a full traversal is to handle chains of empty tiles.
var allDescendantsLoaded = true;
- var stack = internalBaseTraversal.stack;
+ var stack = emptyTraversal.stack;
stack.push(root);
while (stack.length > 0) {
- internalBaseTraversal.stackMaximumLength = Math.max(internalBaseTraversal.stackMaximumLength, stack.length);
+ emptyTraversal.stackMaximumLength = Math.max(emptyTraversal.stackMaximumLength, stack.length);
var tile = stack.pop();
var children = tile.children;
@@ -577,12 +611,12 @@ define([
allDescendantsLoaded = false;
}
- var visible = updateTile(tileset, tile, maximumScreenSpaceError, frameState);
+ var visible = updateTile(tileset, tile, frameState);
if (!visible) {
// Load tiles that aren't visible since they are still needed for the parent to refine
// Tiles that are visible will get loaded from within executeBaseTraversal
loadTile(tileset, tile, true, frameState);
- touch(tileset, tile, frameState);
+ touchTile(tileset, tile, frameState);
tile._touchedFrame = frameState.frameNumber;
}
@@ -608,62 +642,5 @@ define([
(tile._depth > (ancestor._depth + skipLevels));
}
- function executeSkipTraversal(tileset, maximumScreenSpaceError, frameState) {
- // Depth-first traversal that skips tiles until in reaches the skipping threshold.
- var i;
-
- var stack = skipTraversal.stack;
-
- // TODO : possibly remove this
- stack.length = 0;
- stack.push(tileset._root);
-
- while (stack.length > 0) {
- skipTraversal.stackMaximumLength = Math.max(skipTraversal.stackMaximumLength, stack.length);
-
- var tile = stack.pop();
- var add = tile.refine === Cesium3DTileRefine.ADD;
- var replace = tile.refine === Cesium3DTileRefine.REPLACE;
- var children = tile.children;
- var childrenLength = children.length;
- var traverse = (childrenLength > 0) && (tile._screenSpaceError > maximumScreenSpaceError);
-
- visitTile(tileset, tile, frameState);
-
- if (replace) {
- if (!traverse) {
- addDesiredTile(tileset, tile, frameState);
- loadTile(tileset, tile, false, frameState);
- } else if (reachedSkippingThreshold(tileset, tile)) {
- loadTile(tileset, tile, false, frameState);
- }
- // Always touch tiles. Even tiles that are skipped should stay loaded.
- touch(tileset, tile, frameState);
- tile._touchedFrame = frameState.frameNumber;
- }
-
- if (add) {
- // Additive tiles are always desired
- if (tile.contentAvailable) {
- addDesiredTile(tileset, tile, frameState);
- }
- loadTile(tileset, tile, false, frameState);
- touch(tileset, tile, frameState);
- tile._touchedFrame = frameState.frameNumber;
- }
-
- if (traverse) {
- for (i = 0; i < childrenLength; ++i) {
- var child = children[i];
- var visible = updateTile(tileset, child, maximumScreenSpaceError, frameState);
- if (visible) {
- stack.push(child);
- }
- }
- }
- }
- // TODO : ignoring loadSiblings right now
- }
-
return Cesium3DTilesetTraversal;
});
From 5a36e0b13ca1e06659880481b0d2ccdbff6f12a1 Mon Sep 17 00:00:00 2001
From: Sean Lilley
Date: Thu, 22 Mar 2018 16:22:01 -0400
Subject: [PATCH 09/53] Remove touched frame checks
---
Source/Scene/Cesium3DTilesetTraversal.js | 43 ++++++------------------
1 file changed, 11 insertions(+), 32 deletions(-)
diff --git a/Source/Scene/Cesium3DTilesetTraversal.js b/Source/Scene/Cesium3DTilesetTraversal.js
index c11a8a5b8592..3e2bf3662a81 100644
--- a/Source/Scene/Cesium3DTilesetTraversal.js
+++ b/Source/Scene/Cesium3DTilesetTraversal.js
@@ -158,7 +158,6 @@ define([
updateTile(tileset, child, frameState);
touchTile(tileset, child, frameState);
markForSelection(tileset, child, frameState);
- tile._touchedFrame = frameState.frameNumber;
} else if (child._depth - root._depth < 2) {
// Continue traversing, but not too far
stack.push(child);
@@ -296,27 +295,16 @@ define([
}
function addDesiredTile(tileset, tile, frameState) {
- if (tile._touchedFrame === frameState.frameNumber) {
- return;
- }
-
tileset._desiredTiles.push(tile);
}
- function visitTile(tileset, tile, frameState) {
- if (tile._touchedFrame === frameState.frameNumber) {
- return;
- }
-
+ function visitTile(tileset) {
++tileset._statistics.visited;
}
function touchTile(tileset, tile, frameState) {
- if (tile._touchedFrame === frameState.frameNumber) {
- return;
- }
-
tileset._cache.touch(tile);
+ tile._touchedFrame = frameState.frameNumber;
}
function getPriority(tile, useParentPriority) {
@@ -329,19 +317,15 @@ define([
var parent = tile.parent;
var replace = tile.refine === Cesium3DTileRefine.REPLACE;
var add = tile.refine === Cesium3DTileRefine.ADD;
- //if (add) {
+ if (add) {
return tile._distanceToCamera;
- //} else if (replace) {
- // var priority = (defined(parent) && (useParentPriority || tile._screenSpaceError === 0.0)) ? parent._screenSpaceError : tile._screenSpaceError;
- // return 100000.0 - priority; // TODO : doing this just because RequestScheduler wants lower priority
- //}
+ } else if (replace) {
+ var priority = (defined(parent) && (useParentPriority || tile._screenSpaceError === 0.0)) ? parent._screenSpaceError : tile._screenSpaceError;
+ return 100000.0 - priority; // TODO : doing this just because RequestScheduler wants lower priority
+ }
}
function loadTile(tileset, tile, useParentPriority, frameState) {
- if (tile._touchedFrame === frameState.frameNumber) {
- return;
- }
-
if (hasUnloadedContent(tile) || tile.contentExpired) {
tile._requestedFrame = frameState.frameNumber;
tile._priority = getPriority(tile, useParentPriority);
@@ -453,10 +437,6 @@ define([
}
function updateTile(tileset, tile, frameState) {
- if (tile._touchedFrame === frameState.frameNumber) {
- return tile._visibilityPlaneMask !== CullingVolume.MASK_OUTSIDE;
- }
-
var visible = getVisibility(tileset, tile, frameState);
tile._visibilityPlaneMask = visible ? tile._visibilityPlaneMask : CullingVolume.MASK_OUTSIDE;
@@ -490,6 +470,8 @@ define([
function inBaseTraversal(tile, baseScreenSpaceError, maximumScreenSpaceError) {
// TODO : what sse would be passed if only base traversal is used?
// TODO : what is maximumScreenSpaceError is 0. Que paso?
+ // TODO : problem with yellow boxes when we don't want them to be yellow
+ // TODO : if skip traversal always look root
if (baseScreenSpaceError === maximumScreenSpaceError) {
return true;
}
@@ -528,7 +510,7 @@ define([
var traverse = (childrenLength > 0) && (tile._screenSpaceError > maximumScreenSpaceError);
var refines = traverse && parentRefines;
- visitTile(tileset, tile, frameState);
+ visitTile(tileset);
if (traverse) {
for (var i = 0; i < childrenLength; ++i) {
@@ -537,14 +519,13 @@ define([
if (visible) {
stack.push(child);
}
- if (replace && baseTraversalOnly && baseTraversal && !hasEmptyContent(tile)) {
+ if (replace && baseTraversalOnly && !hasEmptyContent(tile)) {
// Check if the parent can refine to this child. If the child is empty we need to traverse further to
// load all descendants with content. Keep non-visible children loaded since they are still needed before the parent can refine.
// We don't do this for empty tiles because it looks better if children stream in as they are loaded to fill the empty space.
if (!visible) {
loadTile(tileset, child, true, frameState);
touchTile(tileset, child, frameState);
- child._touchedFrame = frameState.frameNumber;
}
// Always run the internal base traversal even if we already know we can't refine. This keeps the tiles loaded while we wait to refine.
var refinesToChild = hasEmptyContent(child) ? executeEmptyTraversal(tileset, child, baseScreenSpaceError, maximumScreenSpaceError, frameState) : child.contentAvailable;
@@ -582,7 +563,6 @@ define([
}
touchTile(tileset, tile, frameState);
- tile._touchedFrame = frameState.frameNumber;
tile._refines = refines;
}
}
@@ -617,7 +597,6 @@ define([
// Tiles that are visible will get loaded from within executeBaseTraversal
loadTile(tileset, tile, true, frameState);
touchTile(tileset, tile, frameState);
- tile._touchedFrame = frameState.frameNumber;
}
if (traverse) {
From 66f977de871129fce94e72394a0054ede6318b81 Mon Sep 17 00:00:00 2001
From: Sean Lilley
Date: Thu, 22 Mar 2018 18:07:13 -0400
Subject: [PATCH 10/53] Remove desiredTiles
---
Source/Scene/Cesium3DTileset.js | 1 -
Source/Scene/Cesium3DTilesetTraversal.js | 87 +++++++++---------------
2 files changed, 32 insertions(+), 56 deletions(-)
diff --git a/Source/Scene/Cesium3DTileset.js b/Source/Scene/Cesium3DTileset.js
index 0d49bcda5df9..03f4e1a2004f 100644
--- a/Source/Scene/Cesium3DTileset.js
+++ b/Source/Scene/Cesium3DTileset.js
@@ -183,7 +183,6 @@ define([
this._processingQueue = [];
this._selectedTiles = [];
this._requestedTiles = [];
- this._desiredTiles = new ManagedArray();
this._selectedTilesToStyle = [];
this._loadTimestamp = undefined;
this._timeSinceLoad = 0.0;
diff --git a/Source/Scene/Cesium3DTilesetTraversal.js b/Source/Scene/Cesium3DTilesetTraversal.js
index 3e2bf3662a81..5c2e18b15baf 100644
--- a/Source/Scene/Cesium3DTilesetTraversal.js
+++ b/Source/Scene/Cesium3DTilesetTraversal.js
@@ -59,7 +59,6 @@ define([
return;
}
- tileset._desiredTiles.length = 0;
tileset._selectedTiles.length = 0;
tileset._selectedTilesToStyle.length = 0;
tileset._hasMixedContent = false;
@@ -87,7 +86,6 @@ define([
executeBaseAndSkipTraversal(tileset, root, frameState);
}
- tileset._desiredTiles.trim();
traversal.stack.trim(traversal.stackMaximumLength);
emptyTraversal.stack.trim(emptyTraversal.stackMaximumLength);
descendantTraversal.stack.trim(descendantTraversal.stackMaximumLength);
@@ -99,14 +97,12 @@ define([
var baseScreenSpaceError = tileset._maximumScreenSpaceError;
var maximumScreenSpaceError = tileset._maximumScreenSpaceError;
executeTraversal(tileset, root, baseScreenSpaceError, maximumScreenSpaceError, true, frameState);
- selectDesiredTiles(tileset, frameState);
}
function executeSkipTraversal(tileset, root, frameState) {
var baseScreenSpaceError = Number.MAX_VALUE;
var maximumScreenSpaceError = tileset._maximumScreenSpaceError;
executeTraversal(tileset, root, baseScreenSpaceError, maximumScreenSpaceError, false, frameState);
- markDesiredTilesForSelection(tileset, frameState);
traverseAndSelect(tileset, root, frameState);
}
@@ -114,36 +110,9 @@ define([
var baseScreenSpaceError = Math.max(tileset.baseScreenSpaceError, tileset.maximumScreenSpaceError);
var maximumScreenSpaceError = tileset.maximumScreenSpaceError;
executeTraversal(tileset, root, baseScreenSpaceError, maximumScreenSpaceError, false, frameState);
- markDesiredTilesForSelection(tileset, frameState);
traverseAndSelect(tileset, root, frameState);
}
- function selectDesiredTiles(tileset, frameState) {
- var desiredTiles = tileset._desiredTiles;
- var length = desiredTiles.length;
- for (var i = 0; i < length; ++i) {
- var tile = desiredTiles.get(i);
- markForSelection(tileset, tile, frameState);
- selectTile(tileset, tile, frameState);
- }
- }
-
- function markDesiredTilesForSelection(tileset, frameState) {
- var tiles = tileset._desiredTiles;
- var length = tiles.length;
- for (var i = 0; i < length; ++i) {
- var tile = tiles.get(i);
- var loadedTile = tile.contentAvailable ? tile : tile._ancestorWithContentAvailable;
- if (defined(loadedTile)) {
- markForSelection(tileset, loadedTile, frameState);
- } else if (tileset.immediatelyLoadDesiredLevelOfDetail) {
- // If no ancestors are ready traverse down and select tiles to minimize empty regions.
- // This happens often in cases where the camera zooms out and we're waiting for parent tiles to load.
- markDescendantsForSelection(tileset, tile, frameState);
- }
- }
- }
-
function markDescendantsForSelection(tileset, root, frameState) {
var stack = descendantTraversal.stack;
stack.push(root);
@@ -189,15 +158,12 @@ define([
}
}
- function selectTile(tileset, tile, frameState) {
- if (tile._selectedFrame === frameState.frameNumber) {
- tileset._selectedTiles.push(tile);
- } else {
- console.log('TODO : why am i here');
- }
+ function selectTile(tileset, tile) {
+ tileset._selectedTiles.push(tile);
}
function sortChildrenByDistanceToCamera(a, b) {
+ // TODO : sort during normal traversal
// Sort by farthest child first since this is going on a stack
if (b._distanceToCamera === 0 && a._distanceToCamera === 0) {
return b._centerZDepth - a._centerZDepth;
@@ -245,7 +211,7 @@ define([
if (waitingTile === lastAncestor) {
waitingTile._finalResolution = true;
}
- selectTile(tileset, waitingTile, frameState);
+ selectTile(tileset, waitingTile);
continue;
}
}
@@ -265,7 +231,7 @@ define([
if (markedForSelection) {
if (add) {
tile._finalResolution = true;
- selectTile(tileset, tile, frameState);
+ selectTile(tileset, tile);
} else {
tile._selectionDepth = ancestorStack.length;
if (tile._selectionDepth > 0) {
@@ -274,7 +240,7 @@ define([
lastAncestor = tile;
if (childrenLength === 0) {
tile._finalResolution = true;
- selectTile(tileset, tile, frameState);
+ selectTile(tileset, tile);
continue;
}
ancestorStack.push(tile);
@@ -294,8 +260,20 @@ define([
(tile.contentVisibility(frameState) !== Intersect.OUTSIDE);
}
- function addDesiredTile(tileset, tile, frameState) {
- tileset._desiredTiles.push(tile);
+ function addDesiredTile(tileset, tile, baseTraversalOnly, frameState) {
+ if (baseTraversalOnly) {
+ markForSelection(tileset, tile, frameState);
+ selectTile(tileset, tile);
+ } else {
+ var loadedTile = tile.contentAvailable ? tile : tile._ancestorWithContentAvailable;
+ if (defined(loadedTile)) {
+ markForSelection(tileset, loadedTile, frameState);
+ } else if (tileset.immediatelyLoadDesiredLevelOfDetail) {
+ // If no ancestors are ready traverse down and select tiles to minimize empty regions.
+ // This happens often in cases where the camera zooms out and we're waiting for parent tiles to load.
+ markDescendantsForSelection(tileset, tile, frameState);
+ }
+ }
}
function visitTile(tileset) {
@@ -307,28 +285,27 @@ define([
tile._touchedFrame = frameState.frameNumber;
}
- function getPriority(tile, useParentPriority) {
- // TODO : we want priority of base traversal to be SSE based so that it refines fast...
- // TODO : doesn't really matter though if we have skiplods on, though it does help a bit
- // TODO : somehow that means we need some comparison of distance and sse
+ function getPriority(tileset, tile, useParentPriority) {
// The base traversal sets useParentPriority to true so that child tiles can load as soon as possible so that their parent can refine sooner.
- // Additive tiles always load based on distance because it subjectively looks better
- // TODO : what to do about tileset with heavy mix of replace and add. The priorities will differ a lot.
+ // Additive tiles always load based on distance because it subjectively looks better.
+ // There may be issues with mixing additive and replacement tileset since SSE and distance are not using the same overall scale.
+ // Maybe all priorities need to be normalized to 0-1 range.
var parent = tile.parent;
var replace = tile.refine === Cesium3DTileRefine.REPLACE;
var add = tile.refine === Cesium3DTileRefine.ADD;
if (add) {
return tile._distanceToCamera;
} else if (replace) {
- var priority = (defined(parent) && (useParentPriority || tile._screenSpaceError === 0.0)) ? parent._screenSpaceError : tile._screenSpaceError;
- return 100000.0 - priority; // TODO : doing this just because RequestScheduler wants lower priority
+ var screenSpaceError = (defined(parent) && (useParentPriority || (tile._screenSpaceError === 0.0))) ? parent._screenSpaceError : tile._screenSpaceError;
+ var rootScreenSpaceError = tileset._root._screenSpaceError;
+ return rootScreenSpaceError - screenSpaceError; // Map higher SSE to lower priority values (higher priority)
}
}
function loadTile(tileset, tile, useParentPriority, frameState) {
if (hasUnloadedContent(tile) || tile.contentExpired) {
tile._requestedFrame = frameState.frameNumber;
- tile._priority = getPriority(tile, useParentPriority);
+ tile._priority = getPriority(tileset, tile, useParentPriority);
tileset._requestedTiles.push(tile);
}
}
@@ -540,13 +517,13 @@ define([
// Select tiles that can't refine further
loadTile(tileset, tile, true, frameState);
if (!refines && parentRefines && tile.contentAvailable) {
- addDesiredTile(tileset, tile, frameState);
+ addDesiredTile(tileset, tile, baseTraversalOnly, frameState);
}
} else {
- // Load tiles that are not skipped or can't refine further. In practice roughly half the tiles are unloaded.
+ // Load tiles that are not skipped or can't refine further. In practice roughly half the tiles stay unloaded.
// Select tiles that can't refine further. If the tile doesn't have loaded content it will try to select an ancestor with loaded content instead.
if (!refines) {
- addDesiredTile(tileset, tile, frameState);
+ addDesiredTile(tileset, tile, baseTraversalOnly, frameState);
loadTile(tileset, tile, false, frameState);
} else if (reachedSkippingThreshold(tileset, tile)) {
loadTile(tileset, tile, false, frameState);
@@ -557,7 +534,7 @@ define([
if (add) {
// Additive tiles are always loaded and selected
if (tile.contentAvailable) {
- addDesiredTile(tileset, tile, frameState);
+ addDesiredTile(tileset, tile, baseTraversalOnly, frameState);
}
loadTile(tileset, tile, false, frameState);
}
From e0a2a86853d592cd9ac71169daba1fce5c7a080e Mon Sep 17 00:00:00 2001
From: Sean Lilley
Date: Fri, 23 Mar 2018 11:17:54 -0400
Subject: [PATCH 11/53] Better handling of processing tiles
---
Source/Scene/Cesium3DTileset.js | 30 ++++++++++++++++++++++++------
1 file changed, 24 insertions(+), 6 deletions(-)
diff --git a/Source/Scene/Cesium3DTileset.js b/Source/Scene/Cesium3DTileset.js
index 03f4e1a2004f..1e0eedff1934 100644
--- a/Source/Scene/Cesium3DTileset.js
+++ b/Source/Scene/Cesium3DTileset.js
@@ -28,6 +28,7 @@ define([
'./Axis',
'./Cesium3DTile',
'./Cesium3DTileColorBlendMode',
+ './Cesium3DTileContentState',
'./Cesium3DTileOptimizations',
'./Cesium3DTileRefine',
'./Cesium3DTilesetCache',
@@ -74,6 +75,7 @@ define([
Axis,
Cesium3DTile,
Cesium3DTileColorBlendMode,
+ Cesium3DTileContentState,
Cesium3DTileOptimizations,
Cesium3DTileRefine,
Cesium3DTilesetCache,
@@ -1461,16 +1463,13 @@ define([
function removeFromProcessingQueue(tileset, tile) {
return function() {
- var index = tileset._processingQueue.indexOf(tile);
- if (index === -1) {
+ if (tile._contentState === Cesium3DTileContentState.FAILED) {
// Not in processing queue
// For example, when a url request fails and the ready promise is rejected
--tileset._statistics.numberOfPendingRequests;
return;
}
- // Remove from processing queue
- tileset._processingQueue.splice(index, 1);
--tileset._statistics.numberOfTilesProcessing;
if (tile.hasRenderableContent) {
@@ -1484,15 +1483,34 @@ define([
};
}
+ function filterProcessingQueue(tileset) {
+ var tiles = tileset._processingQueue;
+ var length = tiles.length;
+
+ var removeCount = 0;
+ for (var i = 0; i < length; ++i) {
+ var tile = tiles[i];
+ if (tile._contentState !== Cesium3DTileContentState.PROCESSING) {
+ ++removeCount;
+ continue;
+ }
+ if (removeCount > 0) {
+ tiles[i - removeCount] = tile;
+ }
+ }
+ tiles.length -= removeCount;
+ }
+
function processTiles(tileset, frameState) {
var tiles = tileset._processingQueue;
var length = tiles.length;
// Process tiles in the PROCESSING state so they will eventually move to the READY state.
- // Traverse backwards in case a tile is removed as a result of calling process()
- for (var i = length - 1; i >= 0; --i) {
+ for (var i = 0; i < length; ++i) {
tiles[i].process(tileset, frameState);
}
+
+ filterProcessingQueue(tileset);
}
///////////////////////////////////////////////////////////////////////////
From dbdd7ef6a245899558e57a2b5db16792c6346451 Mon Sep 17 00:00:00 2001
From: Sean Lilley
Date: Mon, 26 Mar 2018 11:51:55 -0400
Subject: [PATCH 12/53] Sort children by distance in main traversal
---
Source/Scene/Cesium3DTilesetTraversal.js | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/Source/Scene/Cesium3DTilesetTraversal.js b/Source/Scene/Cesium3DTilesetTraversal.js
index 5c2e18b15baf..84b903da11a8 100644
--- a/Source/Scene/Cesium3DTilesetTraversal.js
+++ b/Source/Scene/Cesium3DTilesetTraversal.js
@@ -163,7 +163,6 @@ define([
}
function sortChildrenByDistanceToCamera(a, b) {
- // TODO : sort during normal traversal
// Sort by farthest child first since this is going on a stack
if (b._distanceToCamera === 0 && a._distanceToCamera === 0) {
return b._centerZDepth - a._centerZDepth;
@@ -226,7 +225,6 @@ define([
var markedForSelection = tile._selectedFrame === frameState.frameNumber;
var children = tile.children;
var childrenLength = children.length;
- children.sort(sortChildrenByDistanceToCamera);
if (markedForSelection) {
if (add) {
@@ -264,6 +262,8 @@ define([
if (baseTraversalOnly) {
markForSelection(tileset, tile, frameState);
selectTile(tileset, tile);
+ tile._finalResolution = true;
+ console.log(tile._priority);
} else {
var loadedTile = tile.contentAvailable ? tile : tile._ancestorWithContentAvailable;
if (defined(loadedTile)) {
@@ -490,6 +490,7 @@ define([
visitTile(tileset);
if (traverse) {
+ children.sort(sortChildrenByDistanceToCamera);
for (var i = 0; i < childrenLength; ++i) {
var child = children[i];
var visible = updateTile(tileset, child, frameState);
From 2894551a041f96f8630a05a2eacdb4308e13a5d7 Mon Sep 17 00:00:00 2001
From: Sean Lilley
Date: Mon, 26 Mar 2018 13:35:24 -0400
Subject: [PATCH 13/53] Select empty tiles so their debug bounding volumes get
drawn
---
Source/Scene/Cesium3DTile.js | 3 +-
Source/Scene/Cesium3DTileset.js | 2 +-
Source/Scene/Cesium3DTilesetTraversal.js | 79 ++++++++++++------------
3 files changed, 42 insertions(+), 42 deletions(-)
diff --git a/Source/Scene/Cesium3DTile.js b/Source/Scene/Cesium3DTile.js
index ffb89050c24c..fada5afe598c 100644
--- a/Source/Scene/Cesium3DTile.js
+++ b/Source/Scene/Cesium3DTile.js
@@ -979,11 +979,12 @@ define([
function applyDebugSettings(tile, tileset, frameState) {
var hasContentBoundingVolume = defined(tile._header.content) && defined(tile._header.content.boundingVolume);
+ var empty = tile.hasEmptyContent || tile.hasTilesetContent;
var showVolume = tileset.debugShowBoundingVolume || (tileset.debugShowContentBoundingVolume && !hasContentBoundingVolume);
if (showVolume) {
if (!defined(tile._debugBoundingVolume)) {
- var color = tile._finalResolution ? (hasContentBoundingVolume ? Color.WHITE : Color.RED) : Color.YELLOW;
+ var color = tile._finalResolution ? ((hasContentBoundingVolume || empty) ? Color.WHITE : Color.RED) : Color.YELLOW;
tile._debugBoundingVolume = tile._boundingVolume.createDebugVolume(color);
}
tile._debugBoundingVolume.update(frameState);
diff --git a/Source/Scene/Cesium3DTileset.js b/Source/Scene/Cesium3DTileset.js
index 1e0eedff1934..24dbd7a909de 100644
--- a/Source/Scene/Cesium3DTileset.js
+++ b/Source/Scene/Cesium3DTileset.js
@@ -587,7 +587,7 @@ define([
* This property is for debugging only; it is not optimized for production use.
*
* When true, renders the bounding volume for each visible tile. The bounding volume is
- * white if the tile has a content bounding volume; otherwise, it is red. Tiles that don't meet the
+ * white if the tile has a content bounding volume or is empty; otherwise, it is red. Tiles that don't meet the
* screen space error and are still refining to their descendants are yellow.
*
*
diff --git a/Source/Scene/Cesium3DTilesetTraversal.js b/Source/Scene/Cesium3DTilesetTraversal.js
index 84b903da11a8..83f8fbca6262 100644
--- a/Source/Scene/Cesium3DTilesetTraversal.js
+++ b/Source/Scene/Cesium3DTilesetTraversal.js
@@ -113,7 +113,7 @@ define([
traverseAndSelect(tileset, root, frameState);
}
- function markDescendantsForSelection(tileset, root, frameState) {
+ function selectDescendants(tileset, root, frameState) {
var stack = descendantTraversal.stack;
stack.push(root);
while (stack.length > 0) {
@@ -126,7 +126,7 @@ define([
if (child.contentAvailable) {
updateTile(tileset, child, frameState);
touchTile(tileset, child, frameState);
- markForSelection(tileset, child, frameState);
+ selectTile(tileset, child, frameState);
} else if (child._depth - root._depth < 2) {
// Continue traversing, but not too far
stack.push(child);
@@ -143,22 +143,23 @@ define([
// There may also be a tight box around just the tile's contents, e.g., for a city, we may be
// zoomed into a neighborhood and can cull the skyscrapers in the root tile.
if (tile.contentAvailable && contentVisible(tile, frameState)) {
- var tileContent = tile.content;
- if (tileContent.featurePropertiesDirty) {
- // A feature's property in this tile changed, the tile needs to be re-styled.
- tileContent.featurePropertiesDirty = false;
- tile.lastStyleTime = 0; // Force applying the style to this tile
- tileset._selectedTilesToStyle.push(tile);
- } else if ((tile._selectedFrame !== frameState.frameNumber - 1)) {
- // Tile is newly selected; it is selected this frame, but was not selected last frame.
- tile.lastStyleTime = 0; // Force applying the style to this tile
- tileset._selectedTilesToStyle.push(tile);
- }
tile._selectedFrame = frameState.frameNumber;
}
}
- function selectTile(tileset, tile) {
+ function selectTile(tileset, tile, frameState) {
+ var tileContent = tile.content;
+ if (tileContent.featurePropertiesDirty) {
+ // A feature's property in this tile changed, the tile needs to be re-styled.
+ tileContent.featurePropertiesDirty = false;
+ tile.lastStyleTime = 0; // Force applying the style to this tile
+ tileset._selectedTilesToStyle.push(tile);
+ } else if ((tile._selectedFrame !== frameState.frameNumber - 1)) {
+ // Tile is newly selected; it is selected this frame, but was not selected last frame.
+ tile.lastStyleTime = 0; // Force applying the style to this tile
+ tileset._selectedTilesToStyle.push(tile);
+ }
+ tile._selectedFrame = frameState.frameNumber;
tileset._selectedTiles.push(tile);
}
@@ -210,7 +211,7 @@ define([
if (waitingTile === lastAncestor) {
waitingTile._finalResolution = true;
}
- selectTile(tileset, waitingTile);
+ selectTile(tileset, waitingTile, frameState);
continue;
}
}
@@ -221,29 +222,24 @@ define([
continue;
}
- var add = tile.refine === Cesium3DTileRefine.ADD;
- var markedForSelection = tile._selectedFrame === frameState.frameNumber;
+ var replace = tile.refine === Cesium3DTileRefine.REPLACE;
+ var markedForSelection = replace && tile.contentAvailable && (tile._selectedFrame === frameState.frameNumber);
var children = tile.children;
var childrenLength = children.length;
if (markedForSelection) {
- if (add) {
+ tile._selectionDepth = ancestorStack.length;
+ if (tile._selectionDepth > 0) {
+ tileset._hasMixedContent = true;
+ }
+ lastAncestor = tile;
+ if (childrenLength === 0) {
tile._finalResolution = true;
- selectTile(tileset, tile);
- } else {
- tile._selectionDepth = ancestorStack.length;
- if (tile._selectionDepth > 0) {
- tileset._hasMixedContent = true;
- }
- lastAncestor = tile;
- if (childrenLength === 0) {
- tile._finalResolution = true;
- selectTile(tileset, tile);
- continue;
- }
- ancestorStack.push(tile);
- tile._stackLength = stack.length;
+ selectTile(tileset, tile, frameState);
+ continue;
}
+ ancestorStack.push(tile);
+ tile._stackLength = stack.length;
}
for (var i = 0; i < childrenLength; ++i) {
@@ -258,12 +254,10 @@ define([
(tile.contentVisibility(frameState) !== Intersect.OUTSIDE);
}
- function addDesiredTile(tileset, tile, baseTraversalOnly, frameState) {
- if (baseTraversalOnly) {
- markForSelection(tileset, tile, frameState);
- selectTile(tileset, tile);
+ function addDesiredTile(tileset, tile, final, frameState) {
+ if (final) {
+ selectTile(tileset, tile, frameState);
tile._finalResolution = true;
- console.log(tile._priority);
} else {
var loadedTile = tile.contentAvailable ? tile : tile._ancestorWithContentAvailable;
if (defined(loadedTile)) {
@@ -271,7 +265,7 @@ define([
} else if (tileset.immediatelyLoadDesiredLevelOfDetail) {
// If no ancestors are ready traverse down and select tiles to minimize empty regions.
// This happens often in cases where the camera zooms out and we're waiting for parent tiles to load.
- markDescendantsForSelection(tileset, tile, frameState);
+ selectDescendants(tileset, tile, frameState);
}
}
}
@@ -524,7 +518,7 @@ define([
// Load tiles that are not skipped or can't refine further. In practice roughly half the tiles stay unloaded.
// Select tiles that can't refine further. If the tile doesn't have loaded content it will try to select an ancestor with loaded content instead.
if (!refines) {
- addDesiredTile(tileset, tile, baseTraversalOnly, frameState);
+ addDesiredTile(tileset, tile, false, frameState);
loadTile(tileset, tile, false, frameState);
} else if (reachedSkippingThreshold(tileset, tile)) {
loadTile(tileset, tile, false, frameState);
@@ -535,11 +529,16 @@ define([
if (add) {
// Additive tiles are always loaded and selected
if (tile.contentAvailable) {
- addDesiredTile(tileset, tile, baseTraversalOnly, frameState);
+ addDesiredTile(tileset, tile, true, frameState);
}
loadTile(tileset, tile, false, frameState);
}
+ if (hasEmptyContent(tile)) {
+ // Select empty tiles so that we can see their debug bounding volumes
+ addDesiredTile(tileset, tile, true, frameState);
+ }
+
touchTile(tileset, tile, frameState);
tile._refines = refines;
}
From b9219db9acc422374baf9c166f732e9d12665808 Mon Sep 17 00:00:00 2001
From: Sean Lilley
Date: Mon, 26 Mar 2018 14:24:01 -0400
Subject: [PATCH 14/53] Limit selection traversal by SSE and visibility
---
Source/Scene/Cesium3DTilesetTraversal.js | 16 +++++++++++++---
1 file changed, 13 insertions(+), 3 deletions(-)
diff --git a/Source/Scene/Cesium3DTilesetTraversal.js b/Source/Scene/Cesium3DTilesetTraversal.js
index 83f8fbca6262..eeaa97a191d0 100644
--- a/Source/Scene/Cesium3DTilesetTraversal.js
+++ b/Source/Scene/Cesium3DTilesetTraversal.js
@@ -194,6 +194,7 @@ define([
* selected tiles must be no deeper than 15. This is still very unlikely.
*/
function traverseAndSelect(tileset, root, frameState) {
+ var maximumScreenSpaceError = tileset._maximumScreenSpaceError;
var stack = selectionTraversal.stack;
var ancestorStack = selectionTraversal.ancestorStack;
var lastAncestor;
@@ -226,6 +227,7 @@ define([
var markedForSelection = replace && tile.contentAvailable && (tile._selectedFrame === frameState.frameNumber);
var children = tile.children;
var childrenLength = children.length;
+ var traverse = (childrenLength > 0) && (tile._screenSpaceError > maximumScreenSpaceError);
if (markedForSelection) {
tile._selectionDepth = ancestorStack.length;
@@ -242,9 +244,13 @@ define([
tile._stackLength = stack.length;
}
- for (var i = 0; i < childrenLength; ++i) {
- var child = children[i];
- stack.push(child);
+ if (traverse) {
+ for (var i = 0; i < childrenLength; ++i) {
+ var child = children[i];
+ if (isVisible(child)) {
+ stack.push(child);
+ }
+ }
}
}
}
@@ -430,6 +436,10 @@ define([
return visible;
}
+ function isVisible(tile) {
+ return tile._visibilityPlaneMask !== CullingVolume.MASK_OUTSIDE;
+ }
+
function hasEmptyContent(tile) {
return tile.hasEmptyContent || tile.hasTilesetContent;
}
From 5e7e764a08b403d1135246cb42f2eedae157da2c Mon Sep 17 00:00:00 2001
From: Sean Lilley
Date: Mon, 26 Mar 2018 16:26:41 -0400
Subject: [PATCH 15/53] Update tile outline properly
---
Source/Scene/Cesium3DTile.js | 6 +++++-
Source/Scene/Cesium3DTilesetTraversal.js | 2 +-
Source/Scene/TileBoundingRegion.js | 1 +
Source/Scene/TileBoundingSphere.js | 1 +
Source/Scene/TileOrientedBoundingBox.js | 1 +
5 files changed, 9 insertions(+), 2 deletions(-)
diff --git a/Source/Scene/Cesium3DTile.js b/Source/Scene/Cesium3DTile.js
index fada5afe598c..3f203616c1db 100644
--- a/Source/Scene/Cesium3DTile.js
+++ b/Source/Scene/Cesium3DTile.js
@@ -2,6 +2,7 @@ define([
'../Core/BoundingSphere',
'../Core/Cartesian3',
'../Core/Color',
+ '../Core/ColorGeometryInstanceAttribute',
'../Core/CullingVolume',
'../Core/defaultValue',
'../Core/defined',
@@ -36,6 +37,7 @@ define([
BoundingSphere,
Cartesian3,
Color,
+ ColorGeometryInstanceAttribute,
CullingVolume,
defaultValue,
defined,
@@ -983,11 +985,13 @@ define([
var showVolume = tileset.debugShowBoundingVolume || (tileset.debugShowContentBoundingVolume && !hasContentBoundingVolume);
if (showVolume) {
+ var color = tile._finalResolution ? ((hasContentBoundingVolume || empty) ? Color.WHITE : Color.RED) : Color.YELLOW;
if (!defined(tile._debugBoundingVolume)) {
- var color = tile._finalResolution ? ((hasContentBoundingVolume || empty) ? Color.WHITE : Color.RED) : Color.YELLOW;
tile._debugBoundingVolume = tile._boundingVolume.createDebugVolume(color);
}
tile._debugBoundingVolume.update(frameState);
+ var attributes = tile._debugBoundingVolume.getGeometryInstanceAttributes('outline');
+ attributes.color = ColorGeometryInstanceAttribute.toValue(color, attributes.color);
} else if (!showVolume && defined(tile._debugBoundingVolume)) {
tile._debugBoundingVolume = tile._debugBoundingVolume.destroy();
}
diff --git a/Source/Scene/Cesium3DTilesetTraversal.js b/Source/Scene/Cesium3DTilesetTraversal.js
index eeaa97a191d0..5a0d987a4b0e 100644
--- a/Source/Scene/Cesium3DTilesetTraversal.js
+++ b/Source/Scene/Cesium3DTilesetTraversal.js
@@ -235,7 +235,7 @@ define([
tileset._hasMixedContent = true;
}
lastAncestor = tile;
- if (childrenLength === 0) {
+ if (!traverse) {
tile._finalResolution = true;
selectTile(tileset, tile, frameState);
continue;
diff --git a/Source/Scene/TileBoundingRegion.js b/Source/Scene/TileBoundingRegion.js
index 48ac2003f723..52686d5a3d88 100644
--- a/Source/Scene/TileBoundingRegion.js
+++ b/Source/Scene/TileBoundingRegion.js
@@ -352,6 +352,7 @@ define([
});
var instance = new GeometryInstance({
geometry : geometry,
+ id : 'outline',
modelMatrix : modelMatrix,
attributes : {
color : ColorGeometryInstanceAttribute.fromColor(color)
diff --git a/Source/Scene/TileBoundingSphere.js b/Source/Scene/TileBoundingSphere.js
index ffc98d1245ad..278e11ad8a51 100644
--- a/Source/Scene/TileBoundingSphere.js
+++ b/Source/Scene/TileBoundingSphere.js
@@ -151,6 +151,7 @@ define([
var modelMatrix = Matrix4.fromTranslation(this.center, new Matrix4.clone(Matrix4.IDENTITY));
var instance = new GeometryInstance({
geometry : geometry,
+ id : 'outline',
modelMatrix : modelMatrix,
attributes : {
color : ColorGeometryInstanceAttribute.fromColor(color)
diff --git a/Source/Scene/TileOrientedBoundingBox.js b/Source/Scene/TileOrientedBoundingBox.js
index bf09771654a6..c1f0ef277694 100644
--- a/Source/Scene/TileOrientedBoundingBox.js
+++ b/Source/Scene/TileOrientedBoundingBox.js
@@ -134,6 +134,7 @@ define([
var modelMatrix = Matrix4.fromRotationTranslation(this.boundingVolume.halfAxes, this.boundingVolume.center);
var instance = new GeometryInstance({
geometry : geometry,
+ id : 'outline',
modelMatrix : modelMatrix,
attributes : {
color : ColorGeometryInstanceAttribute.fromColor(color)
From 63ad7c3ae611d83ecb1b527fdace137df8e43829 Mon Sep 17 00:00:00 2001
From: Sean Lilley
Date: Mon, 26 Mar 2018 17:16:48 -0400
Subject: [PATCH 16/53] Cleanup
---
Source/Scene/Cesium3DTile.js | 1 +
Source/Scene/Cesium3DTilesetTraversal.js | 303 +++++++++++------------
2 files changed, 144 insertions(+), 160 deletions(-)
diff --git a/Source/Scene/Cesium3DTile.js b/Source/Scene/Cesium3DTile.js
index 3f203616c1db..3c4d61a6ecba 100644
--- a/Source/Scene/Cesium3DTile.js
+++ b/Source/Scene/Cesium3DTile.js
@@ -331,6 +331,7 @@ define([
this._ancestorWithContent = undefined;
this._ancestorWithContentAvailable = undefined;
this._refines = false;
+ this._selected = false;
this._priority = 0.0;
this._isClipped = true;
this._clippingPlanesState = 0; // encapsulates (_isClipped, clippingPlanes.enabled) and number/function
diff --git a/Source/Scene/Cesium3DTilesetTraversal.js b/Source/Scene/Cesium3DTilesetTraversal.js
index 5a0d987a4b0e..41e18add0270 100644
--- a/Source/Scene/Cesium3DTilesetTraversal.js
+++ b/Source/Scene/Cesium3DTilesetTraversal.js
@@ -78,7 +78,7 @@ define([
return;
}
- if (!tileset._skipLevelOfDetail) {//} || tileset._allTilesAdditive) { TODO
+ if (!tileset._skipLevelOfDetail || tileset._allTilesAdditive) {
executeBaseTraversal(tileset, root, frameState);
} else if (tileset.immediatelyLoadDesiredLevelOfDetail) {
executeSkipTraversal(tileset, root, frameState);
@@ -96,73 +96,41 @@ define([
function executeBaseTraversal(tileset, root, frameState) {
var baseScreenSpaceError = tileset._maximumScreenSpaceError;
var maximumScreenSpaceError = tileset._maximumScreenSpaceError;
- executeTraversal(tileset, root, baseScreenSpaceError, maximumScreenSpaceError, true, frameState);
+ executeTraversal(tileset, root, baseScreenSpaceError, maximumScreenSpaceError, frameState);
}
function executeSkipTraversal(tileset, root, frameState) {
var baseScreenSpaceError = Number.MAX_VALUE;
var maximumScreenSpaceError = tileset._maximumScreenSpaceError;
- executeTraversal(tileset, root, baseScreenSpaceError, maximumScreenSpaceError, false, frameState);
+ executeTraversal(tileset, root, baseScreenSpaceError, maximumScreenSpaceError, frameState);
traverseAndSelect(tileset, root, frameState);
}
function executeBaseAndSkipTraversal(tileset, root, frameState) {
var baseScreenSpaceError = Math.max(tileset.baseScreenSpaceError, tileset.maximumScreenSpaceError);
var maximumScreenSpaceError = tileset.maximumScreenSpaceError;
- executeTraversal(tileset, root, baseScreenSpaceError, maximumScreenSpaceError, false, frameState);
+ executeTraversal(tileset, root, baseScreenSpaceError, maximumScreenSpaceError, frameState);
traverseAndSelect(tileset, root, frameState);
}
- function selectDescendants(tileset, root, frameState) {
- var stack = descendantTraversal.stack;
- stack.push(root);
- while (stack.length > 0) {
- descendantTraversal.stackMaximumLength = Math.max(descendantTraversal.stackMaximumLength, stack.length);
- var tile = stack.pop();
- var children = tile.children;
- var childrenLength = children.length;
- for (var i = 0; i < childrenLength; ++i) {
- var child = children[i];
- if (child.contentAvailable) {
- updateTile(tileset, child, frameState);
- touchTile(tileset, child, frameState);
- selectTile(tileset, child, frameState);
- } else if (child._depth - root._depth < 2) {
- // Continue traversing, but not too far
- stack.push(child);
- }
- }
- }
- }
-
- function markForSelection(tileset, tile, frameState) {
- if (tile._selectedFrame === frameState.frameNumber) {
- return;
- }
-
- // There may also be a tight box around just the tile's contents, e.g., for a city, we may be
- // zoomed into a neighborhood and can cull the skyscrapers in the root tile.
+ function selectTile(tileset, tile, frameState) {
if (tile.contentAvailable && contentVisible(tile, frameState)) {
+ var tileContent = tile.content;
+ if (tileContent.featurePropertiesDirty) {
+ // A feature's property in this tile changed, the tile needs to be re-styled.
+ tileContent.featurePropertiesDirty = false;
+ tile.lastStyleTime = 0; // Force applying the style to this tile
+ tileset._selectedTilesToStyle.push(tile);
+ } else if ((tile._selectedFrame !== frameState.frameNumber - 1)) {
+ // Tile is newly selected; it is selected this frame, but was not selected last frame.
+ tile.lastStyleTime = 0; // Force applying the style to this tile
+ tileset._selectedTilesToStyle.push(tile);
+ }
tile._selectedFrame = frameState.frameNumber;
+ tileset._selectedTiles.push(tile);
}
}
- function selectTile(tileset, tile, frameState) {
- var tileContent = tile.content;
- if (tileContent.featurePropertiesDirty) {
- // A feature's property in this tile changed, the tile needs to be re-styled.
- tileContent.featurePropertiesDirty = false;
- tile.lastStyleTime = 0; // Force applying the style to this tile
- tileset._selectedTilesToStyle.push(tile);
- } else if ((tile._selectedFrame !== frameState.frameNumber - 1)) {
- // Tile is newly selected; it is selected this frame, but was not selected last frame.
- tile.lastStyleTime = 0; // Force applying the style to this tile
- tileset._selectedTilesToStyle.push(tile);
- }
- tile._selectedFrame = frameState.frameNumber;
- tileset._selectedTiles.push(tile);
- }
-
function sortChildrenByDistanceToCamera(a, b) {
// Sort by farthest child first since this is going on a stack
if (b._distanceToCamera === 0 && a._distanceToCamera === 0) {
@@ -172,102 +140,43 @@ define([
return b._distanceToCamera - a._distanceToCamera;
}
- /**
- * Traverse the tree and check if their selected frame is the current frame. If so, add it to a selection queue.
- * Tiles are sorted near to far so we can take advantage of early Z.
- * Furthermore, this is a preorder traversal so children tiles are selected before ancestor tiles.
- *
- * The reason for the preorder traversal is so that tiles can easily be marked with their
- * selection depth. A tile's _selectionDepth is its depth in the tree where all non-selected tiles are removed.
- * This property is important for use in the stencil test because we want to render deeper tiles on top of their
- * ancestors. If a tileset is very deep, the depth is unlikely to fit into the stencil buffer.
- *
- * We want to select children before their ancestors because there is no guarantee on the relationship between
- * the children's z-depth and the ancestor's z-depth. We cannot rely on Z because we want the child to appear on top
- * of ancestor regardless of true depth. The stencil tests used require children to be drawn first.
- *
- * NOTE: this will no longer work when there is a chain of selected tiles that is longer than the size of the
- * stencil buffer (usually 8 bits). In other words, the subset of the tree containing only selected tiles must be
- * no deeper than 255. It is very, very unlikely this will cause a problem.
- *
- * NOTE: when the scene has inverted classification enabled, the stencil buffer will be masked to 4 bits. So, the
- * selected tiles must be no deeper than 15. This is still very unlikely.
- */
- function traverseAndSelect(tileset, root, frameState) {
- var maximumScreenSpaceError = tileset._maximumScreenSpaceError;
- var stack = selectionTraversal.stack;
- var ancestorStack = selectionTraversal.ancestorStack;
- var lastAncestor;
+ function contentVisible(tile, frameState) {
+ return (tile._visibilityPlaneMask === CullingVolume.MASK_INSIDE) ||
+ (tile.contentVisibility(frameState) !== Intersect.OUTSIDE);
+ }
+ function selectDescendants(tileset, root, frameState) {
+ var stack = descendantTraversal.stack;
stack.push(root);
-
- while (stack.length > 0 || ancestorStack.length > 0) {
- selectionTraversal.stackMaximumLength = Math.max(selectionTraversal.stackMaximumLength, stack.length);
- selectionTraversal.ancestorStackMaximumLength = Math.max(selectionTraversal.ancestorStackMaximumLength, ancestorStack.length);
-
- if (ancestorStack.length > 0) {
- var waitingTile = ancestorStack.get(ancestorStack.length - 1);
- if (waitingTile._stackLength === stack.length) {
- ancestorStack.pop();
- if (waitingTile === lastAncestor) {
- waitingTile._finalResolution = true;
- }
- selectTile(tileset, waitingTile, frameState);
- continue;
- }
- }
-
+ while (stack.length > 0) {
+ descendantTraversal.stackMaximumLength = Math.max(descendantTraversal.stackMaximumLength, stack.length);
var tile = stack.pop();
- if (!defined(tile)) {
- // stack is empty but ancestorStack isn't
- continue;
- }
-
- var replace = tile.refine === Cesium3DTileRefine.REPLACE;
- var markedForSelection = replace && tile.contentAvailable && (tile._selectedFrame === frameState.frameNumber);
var children = tile.children;
var childrenLength = children.length;
- var traverse = (childrenLength > 0) && (tile._screenSpaceError > maximumScreenSpaceError);
-
- if (markedForSelection) {
- tile._selectionDepth = ancestorStack.length;
- if (tile._selectionDepth > 0) {
- tileset._hasMixedContent = true;
- }
- lastAncestor = tile;
- if (!traverse) {
- tile._finalResolution = true;
- selectTile(tileset, tile, frameState);
- continue;
- }
- ancestorStack.push(tile);
- tile._stackLength = stack.length;
- }
-
- if (traverse) {
- for (var i = 0; i < childrenLength; ++i) {
- var child = children[i];
- if (isVisible(child)) {
- stack.push(child);
- }
+ for (var i = 0; i < childrenLength; ++i) {
+ var child = children[i];
+ if (child.contentAvailable) {
+ updateTile(tileset, child, frameState);
+ touchTile(tileset, child, frameState);
+ selectTile(tileset, child, frameState);
+ } else if (child._depth - root._depth < 2) {
+ // Continue traversing, but not too far
+ stack.push(child);
}
}
}
}
- function contentVisible(tile, frameState) {
- return (tile._visibilityPlaneMask === CullingVolume.MASK_INSIDE) ||
- (tile.contentVisibility(frameState) !== Intersect.OUTSIDE);
- }
-
- function addDesiredTile(tileset, tile, final, frameState) {
+ function selectDesiredTile(tileset, tile, final, frameState) {
if (final) {
+ // The tile can be selected right away and does not require traverseAndSelect. E.g. additive or empty tiles
selectTile(tileset, tile, frameState);
tile._finalResolution = true;
} else {
var loadedTile = tile.contentAvailable ? tile : tile._ancestorWithContentAvailable;
if (defined(loadedTile)) {
- markForSelection(tileset, loadedTile, frameState);
+ // Tiles marked for selection will be selected in traverseAndSelect
+ loadedTile._selected = true;
} else if (tileset.immediatelyLoadDesiredLevelOfDetail) {
// If no ancestors are ready traverse down and select tiles to minimize empty regions.
// This happens often in cases where the camera zooms out and we're waiting for parent tiles to load.
@@ -417,6 +326,7 @@ define([
var visible = getVisibility(tileset, tile, frameState);
tile._visibilityPlaneMask = visible ? tile._visibilityPlaneMask : CullingVolume.MASK_OUTSIDE;
+ tile._selected = false;
tile._finalResolution = false;
tile._ancestorWithContent = undefined;
tile._ancestorWithContentAvailable = undefined;
@@ -448,32 +358,34 @@ define([
return !hasEmptyContent(tile) && tile.contentUnloaded;
}
- function inBaseTraversal(tile, baseScreenSpaceError, maximumScreenSpaceError) {
- // TODO : what sse would be passed if only base traversal is used?
- // TODO : what is maximumScreenSpaceError is 0. Que paso?
- // TODO : problem with yellow boxes when we don't want them to be yellow
- // TODO : if skip traversal always look root
- if (baseScreenSpaceError === maximumScreenSpaceError) {
- return true;
- }
+ function reachedSkippingThreshold(tileset, tile) {
+ var ancestor = tile._ancestorWithContent;
+ var skipLevels = tileset.skipLevelOfDetail ? tileset.skipLevels : 0;
+ var skipScreenSpaceErrorFactor = tileset.skipLevelOfDetail ? tileset.skipScreenSpaceErrorFactor : 1.0;
+
+ return !tileset.immediatelyLoadDesiredLevelOfDetail &&
+ defined(ancestor) &&
+ (tile._screenSpaceError < (ancestor._screenSpaceError / skipScreenSpaceErrorFactor)) &&
+ (tile._depth > (ancestor._depth + skipLevels));
+ }
+ function inBaseTraversal(tile, baseScreenSpaceError) {
if (tile._screenSpaceError === 0) {
var parent = tile.parent;
if (defined(parent)) {
- return inBaseTraversal(parent, baseScreenSpaceError, maximumScreenSpaceError);
+ return parent._screenSpaceError > baseScreenSpaceError;
}
return true;
}
-
return tile._screenSpaceError > baseScreenSpaceError;
}
- function executeTraversal(tileset, root, baseScreenSpaceError, maximumScreenSpaceError, baseTraversalOnly, frameState) {
- // TODO wording
- // Depth-first traversal that loads all tiles that are visible and don't meet the screen space error.
- // For replacement refinement: selects tiles to render that can't refine further - either because
- // their children aren't loaded yet or they meet the screen space error.
- // For additive refinement: select all tiles to render
+ function executeTraversal(tileset, root, baseScreenSpaceError, maximumScreenSpaceError, frameState) {
+ // Depth-first traversal that traverses all visible tiles and marks tiles for selection.
+ // If the traversal is base traversal ONLY then a tile does not refine until all children are loaded. This is the
+ // traditional replacement refinement approach.
+ // Otherwise we allow for skipping levels of the tree and rendering children and parent tiles simultaneously.
+ var baseTraversalOnly = baseScreenSpaceError === maximumScreenSpaceError;
var stack = traversal.stack;
stack.push(root);
@@ -481,7 +393,7 @@ define([
traversal.stackMaximumLength = Math.max(traversal.stackMaximumLength, stack.length);
var tile = stack.pop();
- var baseTraversal = inBaseTraversal(tile, baseScreenSpaceError, maximumScreenSpaceError);
+ var baseTraversal = baseTraversalOnly || inBaseTraversal(tile, baseScreenSpaceError);
var add = tile.refine === Cesium3DTileRefine.ADD;
var replace = tile.refine === Cesium3DTileRefine.REPLACE;
var children = tile.children;
@@ -501,7 +413,7 @@ define([
if (visible) {
stack.push(child);
}
- if (replace && baseTraversalOnly && !hasEmptyContent(tile)) {
+ if (baseTraversalOnly && replace && !hasEmptyContent(tile)) {
// Check if the parent can refine to this child. If the child is empty we need to traverse further to
// load all descendants with content. Keep non-visible children loaded since they are still needed before the parent can refine.
// We don't do this for empty tiles because it looks better if children stream in as they are loaded to fill the empty space.
@@ -522,13 +434,13 @@ define([
// Select tiles that can't refine further
loadTile(tileset, tile, true, frameState);
if (!refines && parentRefines && tile.contentAvailable) {
- addDesiredTile(tileset, tile, baseTraversalOnly, frameState);
+ selectDesiredTile(tileset, tile, baseTraversalOnly, frameState);
}
} else {
// Load tiles that are not skipped or can't refine further. In practice roughly half the tiles stay unloaded.
// Select tiles that can't refine further. If the tile doesn't have loaded content it will try to select an ancestor with loaded content instead.
- if (!refines) {
- addDesiredTile(tileset, tile, false, frameState);
+ if (!refines) { // eslint-disable-line
+ selectDesiredTile(tileset, tile, false, frameState);
loadTile(tileset, tile, false, frameState);
} else if (reachedSkippingThreshold(tileset, tile)) {
loadTile(tileset, tile, false, frameState);
@@ -539,14 +451,14 @@ define([
if (add) {
// Additive tiles are always loaded and selected
if (tile.contentAvailable) {
- addDesiredTile(tileset, tile, true, frameState);
+ selectDesiredTile(tileset, tile, true, frameState);
}
loadTile(tileset, tile, false, frameState);
}
if (hasEmptyContent(tile)) {
// Select empty tiles so that we can see their debug bounding volumes
- addDesiredTile(tileset, tile, true, frameState);
+ selectDesiredTile(tileset, tile, true, frameState);
}
touchTile(tileset, tile, frameState);
@@ -597,15 +509,86 @@ define([
return allDescendantsLoaded;
}
- function reachedSkippingThreshold(tileset, tile) {
- var ancestor = tile._ancestorWithContent;
- var skipLevels = tileset.skipLevelOfDetail ? tileset.skipLevels : 0;
- var skipScreenSpaceErrorFactor = tileset.skipLevelOfDetail ? tileset.skipScreenSpaceErrorFactor : 1.0;
+ /**
+ * Traverse the tree and check if their selected frame is the current frame. If so, add it to a selection queue.
+ * Tiles are sorted near to far so we can take advantage of early Z.
+ * Furthermore, this is a preorder traversal so children tiles are selected before ancestor tiles.
+ *
+ * The reason for the preorder traversal is so that tiles can easily be marked with their
+ * selection depth. A tile's _selectionDepth is its depth in the tree where all non-selected tiles are removed.
+ * This property is important for use in the stencil test because we want to render deeper tiles on top of their
+ * ancestors. If a tileset is very deep, the depth is unlikely to fit into the stencil buffer.
+ *
+ * We want to select children before their ancestors because there is no guarantee on the relationship between
+ * the children's z-depth and the ancestor's z-depth. We cannot rely on Z because we want the child to appear on top
+ * of ancestor regardless of true depth. The stencil tests used require children to be drawn first.
+ *
+ * NOTE: this will no longer work when there is a chain of selected tiles that is longer than the size of the
+ * stencil buffer (usually 8 bits). In other words, the subset of the tree containing only selected tiles must be
+ * no deeper than 255. It is very, very unlikely this will cause a problem.
+ *
+ * NOTE: when the scene has inverted classification enabled, the stencil buffer will be masked to 4 bits. So, the
+ * selected tiles must be no deeper than 15. This is still very unlikely.
+ */
+ function traverseAndSelect(tileset, root, frameState) {
+ var maximumScreenSpaceError = tileset._maximumScreenSpaceError;
+ var stack = selectionTraversal.stack;
+ var ancestorStack = selectionTraversal.ancestorStack;
+ var lastAncestor;
- return !tileset.immediatelyLoadDesiredLevelOfDetail &&
- defined(ancestor) &&
- (tile._screenSpaceError < (ancestor._screenSpaceError / skipScreenSpaceErrorFactor)) &&
- (tile._depth > (ancestor._depth + skipLevels));
+ stack.push(root);
+
+ while (stack.length > 0 || ancestorStack.length > 0) {
+ selectionTraversal.stackMaximumLength = Math.max(selectionTraversal.stackMaximumLength, stack.length);
+ selectionTraversal.ancestorStackMaximumLength = Math.max(selectionTraversal.ancestorStackMaximumLength, ancestorStack.length);
+
+ if (ancestorStack.length > 0) {
+ var waitingTile = ancestorStack.get(ancestorStack.length - 1);
+ if (waitingTile._stackLength === stack.length) {
+ ancestorStack.pop();
+ if (waitingTile === lastAncestor) {
+ waitingTile._finalResolution = true;
+ }
+ selectTile(tileset, waitingTile, frameState);
+ continue;
+ }
+ }
+
+ var tile = stack.pop();
+ if (!defined(tile)) {
+ // stack is empty but ancestorStack isn't
+ continue;
+ }
+
+ var markedForSelection = tile._selected;
+ var children = tile.children;
+ var childrenLength = children.length;
+ var traverse = (childrenLength > 0) && (tile._screenSpaceError > maximumScreenSpaceError);
+
+ if (markedForSelection) {
+ tile._selectionDepth = ancestorStack.length;
+ if (tile._selectionDepth > 0) {
+ tileset._hasMixedContent = true;
+ }
+ lastAncestor = tile;
+ if (!traverse) {
+ tile._finalResolution = true;
+ selectTile(tileset, tile, frameState);
+ continue;
+ }
+ ancestorStack.push(tile);
+ tile._stackLength = stack.length;
+ }
+
+ if (traverse) {
+ for (var i = 0; i < childrenLength; ++i) {
+ var child = children[i];
+ if (isVisible(child)) {
+ stack.push(child);
+ }
+ }
+ }
+ }
}
return Cesium3DTilesetTraversal;
From c804dd3c1d20d242ad58ec9487d3853c86a4c4b5 Mon Sep 17 00:00:00 2001
From: Sean Lilley
Date: Mon, 26 Mar 2018 17:43:42 -0400
Subject: [PATCH 17/53] Fix for outlines
---
Source/Scene/Cesium3DTilesetTraversal.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Source/Scene/Cesium3DTilesetTraversal.js b/Source/Scene/Cesium3DTilesetTraversal.js
index 41e18add0270..00d682ef04b9 100644
--- a/Source/Scene/Cesium3DTilesetTraversal.js
+++ b/Source/Scene/Cesium3DTilesetTraversal.js
@@ -114,7 +114,7 @@ define([
}
function selectTile(tileset, tile, frameState) {
- if (tile.contentAvailable && contentVisible(tile, frameState)) {
+ if (contentVisible(tile, frameState)) {
var tileContent = tile.content;
if (tileContent.featurePropertiesDirty) {
// A feature's property in this tile changed, the tile needs to be re-styled.
From a6aad571e2e79bc1948269c5ed3a96769ad50ea8 Mon Sep 17 00:00:00 2001
From: Sean Lilley
Date: Tue, 27 Mar 2018 18:04:34 -0400
Subject: [PATCH 18/53] Fixing tests
---
Source/Scene/Cesium3DTile.js | 2 +-
Source/Scene/Cesium3DTileset.js | 3 +-
Source/Scene/Cesium3DTilesetTraversal.js | 17 +++++--
Specs/Cesium3DTilesTester.js | 1 +
.../Tilesets/TilesetOfTilesets/tileset2.json | 2 +-
.../TilesetOfTilesets/tileset3/tileset3.json | 2 +-
Specs/Scene/Cesium3DTilesetSpec.js | 50 +++++++++++--------
7 files changed, 46 insertions(+), 31 deletions(-)
diff --git a/Source/Scene/Cesium3DTile.js b/Source/Scene/Cesium3DTile.js
index 3c4d61a6ecba..73da68df57c4 100644
--- a/Source/Scene/Cesium3DTile.js
+++ b/Source/Scene/Cesium3DTile.js
@@ -838,7 +838,7 @@ define([
* Computes the distance from the center of the tile's bounding volume to the camera.
*
* @param {FrameState} frameState The frame state.
- * @returns {Number} The distance, in meters, or zero if the camera is inside the bounding volume.
+ * @returns {Number} The distance, in meters.
*
* @private
*/
diff --git a/Source/Scene/Cesium3DTileset.js b/Source/Scene/Cesium3DTileset.js
index 24dbd7a909de..e0c947ca6958 100644
--- a/Source/Scene/Cesium3DTileset.js
+++ b/Source/Scene/Cesium3DTileset.js
@@ -1502,6 +1502,7 @@ define([
}
function processTiles(tileset, frameState) {
+ filterProcessingQueue(tileset);
var tiles = tileset._processingQueue;
var length = tiles.length;
@@ -1509,8 +1510,6 @@ define([
for (var i = 0; i < length; ++i) {
tiles[i].process(tileset, frameState);
}
-
- filterProcessingQueue(tileset);
}
///////////////////////////////////////////////////////////////////////////
diff --git a/Source/Scene/Cesium3DTilesetTraversal.js b/Source/Scene/Cesium3DTilesetTraversal.js
index 00d682ef04b9..7be94d1d604e 100644
--- a/Source/Scene/Cesium3DTilesetTraversal.js
+++ b/Source/Scene/Cesium3DTilesetTraversal.js
@@ -261,10 +261,10 @@ define([
}
var parent = tile.parent;
- var parentTransorm = defined(parent) ? parent.computedTransform : tileset._modelMatrix;
+ var parentTransform = defined(parent) ? parent.computedTransform : tileset._modelMatrix;
var parentVisibilityPlaneMask = defined(parent) ? parent._visibilityPlaneMask : CullingVolume.MASK_INDETERMINATE;
- tile.updateTransform(parentTransorm);
+ tile.updateTransform(parentTransform);
tile._distanceToCamera = tile.distanceToTile(frameState);
tile._centerZDepth = tile.distanceToTileCenter(frameState);
tile._screenSpaceError = getScreenSpaceError(tileset, tile.geometricError, tile, frameState);
@@ -346,6 +346,15 @@ define([
return visible;
}
+ function updateChildren(tileset, tile, frameState) {
+ var children = tile.children;
+ var length = children.length;
+ for (var i = 0; i < length; ++i) {
+ updateTile(tileset, children[i], frameState);
+ }
+ children.sort(sortChildrenByDistanceToCamera);
+ }
+
function isVisible(tile) {
return tile._visibilityPlaneMask !== CullingVolume.MASK_OUTSIDE;
}
@@ -406,10 +415,10 @@ define([
visitTile(tileset);
if (traverse) {
- children.sort(sortChildrenByDistanceToCamera);
+ updateChildren(tileset, tile, frameState);
for (var i = 0; i < childrenLength; ++i) {
var child = children[i];
- var visible = updateTile(tileset, child, frameState);
+ var visible = isVisible(child);
if (visible) {
stack.push(child);
}
diff --git a/Specs/Cesium3DTilesTester.js b/Specs/Cesium3DTilesTester.js
index 9c6e6476593b..1ba742bc8ae1 100644
--- a/Specs/Cesium3DTilesTester.js
+++ b/Specs/Cesium3DTilesTester.js
@@ -97,6 +97,7 @@ define([
scene.renderForSpecs();
return tileset.tilesLoaded;
}).then(function() {
+ scene.renderForSpecs();
return tileset;
});
};
diff --git a/Specs/Data/Cesium3DTiles/Tilesets/TilesetOfTilesets/tileset2.json b/Specs/Data/Cesium3DTiles/Tilesets/TilesetOfTilesets/tileset2.json
index e2e4b8f7a4c4..855d5fcad7cb 100644
--- a/Specs/Data/Cesium3DTiles/Tilesets/TilesetOfTilesets/tileset2.json
+++ b/Specs/Data/Cesium3DTiles/Tilesets/TilesetOfTilesets/tileset2.json
@@ -31,7 +31,7 @@
20
]
},
- "geometricError": 0,
+ "geometricError": 70,
"content": {
"url": "tileset3/tileset3.json"
}
diff --git a/Specs/Data/Cesium3DTiles/Tilesets/TilesetOfTilesets/tileset3/tileset3.json b/Specs/Data/Cesium3DTiles/Tilesets/TilesetOfTilesets/tileset3/tileset3.json
index 76266f647b3c..9ac1abf95d6b 100644
--- a/Specs/Data/Cesium3DTiles/Tilesets/TilesetOfTilesets/tileset3/tileset3.json
+++ b/Specs/Data/Cesium3DTiles/Tilesets/TilesetOfTilesets/tileset3/tileset3.json
@@ -2,7 +2,7 @@
"asset": {
"version": "1.0"
},
- "geometricError": 0,
+ "geometricError": 70,
"root": {
"boundingVolume": {
"region": [
diff --git a/Specs/Scene/Cesium3DTilesetSpec.js b/Specs/Scene/Cesium3DTilesetSpec.js
index f24d67e4da0a..5b0ad50cd672 100644
--- a/Specs/Scene/Cesium3DTilesetSpec.js
+++ b/Specs/Scene/Cesium3DTilesetSpec.js
@@ -197,6 +197,10 @@ defineSuite([
setZoom(5.0);
}
+ function isSelected(tileset, tile) {
+ return tileset._selectedTiles.indexOf(tile) > -1;
+ }
+
it('throws with undefined url', function() {
expect(function() {
return new Cesium3DTileset();
@@ -1005,26 +1009,28 @@ defineSuite([
});
it('replacement refinement - selects tile when inside viewer request volume', function() {
- return Cesium3DTilesTester.loadTileset(scene, tilesetWithViewerRequestVolumeUrl).then(function(tileset) {
+ var options = {
+ skipLevelOfDetail : false
+ }
+ return Cesium3DTilesTester.loadTileset(scene, tilesetWithViewerRequestVolumeUrl, options).then(function(tileset) {
var statistics = tileset._statistics;
var root = tileset._root;
root.refine = Cesium3DTileRefine.REPLACE;
- // Force root tile to always not meet SSE since this is just checking the request volume
- tileset.maximumScreenSpaceError = 0.0;
+ root.hasRenderableContent = true; // mock content
+ tileset.maximumScreenSpaceError = 0.0; // Force root tile to always not meet SSE since this is just checking the request volume
// Renders all 5 tiles
setZoom(20.0);
scene.renderForSpecs();
expect(statistics.numberOfCommands).toEqual(5);
- expect(root.selected).toBe(false);
+ expect(isSelected(tileset, root)).toBe(false);
// No longer renders the tile with a request volume
setZoom(1500.0);
- root.contentAvailable = true; // mock content
scene.renderForSpecs();
expect(statistics.numberOfCommands).toEqual(4);
- expect(root.selected).toBe(true); // one child is no longer selected. root is chosen instead
+ expect(isSelected(tileset, root)).toBe(true); // one child is no longer selected. root is chosen instead
});
});
@@ -1106,7 +1112,7 @@ defineSuite([
expect(statistics.numberOfPendingRequests).toEqual(4); // Loading child content tiles
return Cesium3DTilesTester.waitForTilesLoaded(scene, tileset).then(function() {
- expect(root.selected).toEqual(false);
+ expect(isSelected(tileset, root)).toEqual(false);
expect(statistics.numberOfCommands).toEqual(4); // Render child content tiles
});
});
@@ -1144,7 +1150,7 @@ defineSuite([
expect(childRoot.children[3].visibility(scene.frameState, CullingVolume.MASK_INDETERMINATE)).toEqual(CullingVolume.MASK_OUTSIDE);
expect(tileset._selectedTiles.length).toEqual(0);
- expect(childRoot.selected).toBe(false);
+ expect(isSelected(tileset, childRoot)).toBe(false);
});
});
@@ -1163,7 +1169,7 @@ defineSuite([
expect(childRoot.children[2].visibility(scene.frameState, CullingVolume.MASK_INDETERMINATE)).not.toEqual(CullingVolume.MASK_OUTSIDE);
expect(childRoot.children[3].visibility(scene.frameState, CullingVolume.MASK_INDETERMINATE)).not.toEqual(CullingVolume.MASK_OUTSIDE);
- expect(childRoot.selected).toBe(false);
+ expect(isSelected(tileset, childRoot)).toBe(false);
});
});
@@ -1183,7 +1189,7 @@ defineSuite([
expect(childRoot.children[2].visibility(scene.frameState, CullingVolume.MASK_INDETERMINATE)).not.toEqual(CullingVolume.MASK_OUTSIDE);
expect(childRoot.children[3].visibility(scene.frameState, CullingVolume.MASK_INDETERMINATE)).not.toEqual(CullingVolume.MASK_OUTSIDE);
- expect(childRoot.selected).toBe(true);
+ expect(isSelected(tileset, childRoot)).toBe(true);
});
});
});
@@ -1202,7 +1208,7 @@ defineSuite([
expect(childRoot.children[3].visibility(scene.frameState, CullingVolume.MASK_INDETERMINATE)).not.toEqual(CullingVolume.MASK_OUTSIDE);
expect(tileset._selectedTiles.length).toEqual(1);
- expect(childRoot.selected).toBe(true);
+ expect(isSelected(tileset, childRoot)).toBe(true);
});
});
@@ -1222,12 +1228,12 @@ defineSuite([
expect(childRoot.children[3].visibility(scene.frameState, CullingVolume.MASK_INDETERMINATE)).not.toEqual(CullingVolume.MASK_OUTSIDE);
expect(tileset._selectedTiles.length).toEqual(1);
- expect(childRoot.selected).toBe(true);
+ expect(isSelected(tileset, childRoot)).toBe(true);
childRoot.geometricError = 200;
scene.renderForSpecs();
expect(tileset._selectedTiles.length).toEqual(4);
- expect(childRoot.selected).toBe(false);
+ expect(isSelected(tileset, childRoot)).toBe(false);
});
});
});
@@ -2579,9 +2585,9 @@ defineSuite([
// 2 for root tile, 1 for child, 1 for stencil clear
// Tiles that are marked as finalResolution, including leaves, do not create back face commands
expect(statistics.numberOfCommands).toEqual(4);
- expect(root.selected).toBe(true);
+ expect(isSelected(tileset, root)).toBe(true);
expect(root._finalResolution).toBe(false);
- expect(root.children[0].children[0].children[3].selected).toBe(true);
+ expect(isSelected(tileset, root.children[0].children[0].children[3])).toBe(true);
expect(root.children[0].children[0].children[3]._finalResolution).toBe(true);
expect(tileset._hasMixedContent).toBe(true);
@@ -2607,12 +2613,12 @@ defineSuite([
// 2 for root tile, 1 for child, 1 for stencil clear
expect(statistics.numberOfCommands).toEqual(1);
- expect(root.selected).toBe(true);
+ expect(isSelected(tileset, root)).toBe(true);
expect(root._finalResolution).toBe(true);
- expect(root.children[0].children[0].children[0].selected).toBe(false);
- expect(root.children[0].children[0].children[1].selected).toBe(false);
- expect(root.children[0].children[0].children[2].selected).toBe(false);
- expect(root.children[0].children[0].children[3].selected).toBe(false);
+ expect(isSelected(tileset, root.children[0].children[0].children[0])).toBe(false);
+ expect(isSelected(tileset, root.children[0].children[0].children[1])).toBe(false);
+ expect(isSelected(tileset, root.children[0].children[0].children[2])).toBe(false);
+ expect(isSelected(tileset, root.children[0].children[0].children[3])).toBe(false);
expect(tileset._hasMixedContent).toBe(false);
});
});
@@ -2691,8 +2697,8 @@ defineSuite([
expect(child.contentReady).toBe(true);
expect(parent.contentReady).toBe(false);
- expect(child.selected).toBe(true);
- expect(parent.selected).toBe(false);
+ expect(isSelected(tileset, child)).toBe(true);
+ expect(isSelected(tileset, parent)).toBe(false);
expect(statistics.numberOfCommands).toEqual(1);
});
});
From 4bbacf886d00412125cb1fbe911b9fa2c0e3520c Mon Sep 17 00:00:00 2001
From: Sean Lilley
Date: Wed, 28 Mar 2018 09:39:52 -0400
Subject: [PATCH 19/53] Cleanup
---
.../Scene/Cesium3DTileChildrenVisibility.js | 19 -------------------
Source/Scene/Cesium3DTilesetTraversal.js | 1 -
Specs/Scene/Cesium3DTilesetSpec.js | 3 ++-
3 files changed, 2 insertions(+), 21 deletions(-)
delete mode 100644 Source/Scene/Cesium3DTileChildrenVisibility.js
diff --git a/Source/Scene/Cesium3DTileChildrenVisibility.js b/Source/Scene/Cesium3DTileChildrenVisibility.js
deleted file mode 100644
index ddfd279b147d..000000000000
--- a/Source/Scene/Cesium3DTileChildrenVisibility.js
+++ /dev/null
@@ -1,19 +0,0 @@
-define([
- '../Core/freezeObject'
- ], function(
- freezeObject) {
- 'use strict';
-
- /**
- * @private
- */
- var Cesium3DTileChildrenVisibility = {
- NONE : 0, // No children visible
- VISIBLE : 1, // At least one child visible
- IN_REQUEST_VOLUME : 2, // At least one child in viewer request volume
- VISIBLE_IN_REQUEST_VOLUME : 4, // At least one child both visible and in viewer request volume
- VISIBLE_NOT_IN_REQUEST_VOLUME : 8 // At least one child visible but not in viewer request volume
- };
-
- return freezeObject(Cesium3DTileChildrenVisibility);
-});
diff --git a/Source/Scene/Cesium3DTilesetTraversal.js b/Source/Scene/Cesium3DTilesetTraversal.js
index 7be94d1d604e..e217c69b4dbe 100644
--- a/Source/Scene/Cesium3DTilesetTraversal.js
+++ b/Source/Scene/Cesium3DTilesetTraversal.js
@@ -273,7 +273,6 @@ define([
var visible = visibilityPlaneMask !== CullingVolume.MASK_OUTSIDE;
visible = visible && tile.insideViewerRequestVolume(frameState);
tile._visibilityPlaneMask = visible ? visibilityPlaneMask : CullingVolume.MASK_OUTSIDE;
- tile._updatedFrame = frameState.frameNumber;
return visible;
}
diff --git a/Specs/Scene/Cesium3DTilesetSpec.js b/Specs/Scene/Cesium3DTilesetSpec.js
index 5b0ad50cd672..df258b19176a 100644
--- a/Specs/Scene/Cesium3DTilesetSpec.js
+++ b/Specs/Scene/Cesium3DTilesetSpec.js
@@ -1011,12 +1011,13 @@ defineSuite([
it('replacement refinement - selects tile when inside viewer request volume', function() {
var options = {
skipLevelOfDetail : false
- }
+ };
return Cesium3DTilesTester.loadTileset(scene, tilesetWithViewerRequestVolumeUrl, options).then(function(tileset) {
var statistics = tileset._statistics;
var root = tileset._root;
root.refine = Cesium3DTileRefine.REPLACE;
+ root.hasEmptyContent = false;
root.hasRenderableContent = true; // mock content
tileset.maximumScreenSpaceError = 0.0; // Force root tile to always not meet SSE since this is just checking the request volume
From 248c44342578b02a21d1aa9ec256fd80fed42694 Mon Sep 17 00:00:00 2001
From: Sean Lilley
Date: Wed, 28 Mar 2018 10:55:59 -0400
Subject: [PATCH 20/53] Better handling of request volume
---
Source/Scene/Cesium3DTile.js | 3 +-
Source/Scene/Cesium3DTilesetTraversal.js | 107 +++++++++++++++--------
Specs/Scene/Cesium3DTilesetSpec.js | 2 +-
3 files changed, 73 insertions(+), 39 deletions(-)
diff --git a/Source/Scene/Cesium3DTile.js b/Source/Scene/Cesium3DTile.js
index 73da68df57c4..cf2222031145 100644
--- a/Source/Scene/Cesium3DTile.js
+++ b/Source/Scene/Cesium3DTile.js
@@ -23,7 +23,6 @@ define([
'../Core/Resource',
'../Core/RuntimeError',
'../ThirdParty/when',
- './Cesium3DTileChildrenVisibility',
'./Cesium3DTileContentFactory',
'./Cesium3DTileContentState',
'./Cesium3DTileOptimizationHint',
@@ -58,7 +57,6 @@ define([
Resource,
RuntimeError,
when,
- Cesium3DTileChildrenVisibility,
Cesium3DTileContentFactory,
Cesium3DTileContentState,
Cesium3DTileOptimizationHint,
@@ -318,6 +316,7 @@ define([
this._centerZDepth = 0; // TODO : remove?
this._screenSpaceError = 0;
this._visibilityPlaneMask = 0;
+ this._visibilityFlag = 0;
this._finalResolution = true;
this._depth = 0;
diff --git a/Source/Scene/Cesium3DTilesetTraversal.js b/Source/Scene/Cesium3DTilesetTraversal.js
index e217c69b4dbe..a320a509443c 100644
--- a/Source/Scene/Cesium3DTilesetTraversal.js
+++ b/Source/Scene/Cesium3DTilesetTraversal.js
@@ -7,7 +7,6 @@ define([
'../Core/ManagedArray',
'../Core/Math',
'../Core/OrthographicFrustum',
- './Cesium3DTileChildrenVisibility',
'./Cesium3DTileOptimizationHint',
'./Cesium3DTileRefine',
'./SceneMode'
@@ -20,7 +19,6 @@ define([
ManagedArray,
CesiumMath,
OrthographicFrustum,
- Cesium3DTileChildrenVisibility,
Cesium3DTileOptimizationHint,
Cesium3DTileRefine,
SceneMode) {
@@ -32,6 +30,34 @@ define([
function Cesium3DTilesetTraversal() {
}
+ var VisibilityFlag = {
+ NONE : 0,
+ VISIBLE : 1,
+ IN_REQUEST_VOLUME : 2
+ };
+
+ function isVisibleBit(flag) {
+ return (flag & VisibilityFlag.VISIBLE) > 0;
+ }
+
+ function inRequestVolumeBit(flag) {
+ return (flag & VisibilityFlag.IN_REQUEST_VOLUME) > 0;
+ }
+
+ function clearVisibility(tile) {
+ tile._visibilityFlag = tile._visibilityFlag & ~VisibilityFlag.VISIBLE;
+ }
+
+ function isVisible(tile) {
+ var flag = tile._visibilityFlag;
+ return isVisibleBit(flag) && inRequestVolumeBit(flag);
+ }
+
+ function isVisibleButNotInRequestVolume(tile) {
+ var flag = tile._visibilityFlag;
+ return isVisibleBit(flag) && !inRequestVolumeBit(flag);
+ }
+
var traversal = {
stack : new ManagedArray(),
stackMaximumLength : 0
@@ -68,8 +94,8 @@ define([
var maximumScreenSpaceError = tileset._maximumScreenSpaceError;
var root = tileset._root;
- var rootVisible = updateTile(tileset, root, frameState);
- if (!rootVisible) {
+ updateTile(tileset, root, frameState);
+ if (!isVisible(root)) {
return;
}
@@ -257,9 +283,11 @@ define([
function updateVisibility(tileset, tile, frameState) {
if (tile._updatedVisibilityFrame === frameState.frameNumber) {
- return tile._visibilityPlaneMask !== CullingVolume.MASK_OUTSIDE;
+ return;
}
+ var visibilityFlag = VisibilityFlag.NONE;
+
var parent = tile.parent;
var parentTransform = defined(parent) ? parent.computedTransform : tileset._modelMatrix;
var parentVisibilityPlaneMask = defined(parent) ? parent._visibilityPlaneMask : CullingVolume.MASK_INDETERMINATE;
@@ -268,42 +296,49 @@ define([
tile._distanceToCamera = tile.distanceToTile(frameState);
tile._centerZDepth = tile.distanceToTileCenter(frameState);
tile._screenSpaceError = getScreenSpaceError(tileset, tile.geometricError, tile, frameState);
+ tile._visibilityPlaneMask = tile.visibility(frameState, parentVisibilityPlaneMask); // Use parent's plane mask to speed up visibility test
- var visibilityPlaneMask = tile.visibility(frameState, parentVisibilityPlaneMask); // Use parent's plane mask to speed up visibility test
- var visible = visibilityPlaneMask !== CullingVolume.MASK_OUTSIDE;
- visible = visible && tile.insideViewerRequestVolume(frameState);
- tile._visibilityPlaneMask = visible ? visibilityPlaneMask : CullingVolume.MASK_OUTSIDE;
- return visible;
+ if (tile._visibilityPlaneMask !== CullingVolume.MASK_OUTSIDE) {
+ visibilityFlag |= VisibilityFlag.VISIBLE;
+ }
+
+ if (tile.insideViewerRequestVolume(frameState)) {
+ visibilityFlag |= VisibilityFlag.IN_REQUEST_VOLUME;
+ }
+
+ tile._visibilityFlag = visibilityFlag;
}
- function updateChildrenVisibility(tileset, tile, frameState) {
+ function anyChildrenVisible(tileset, tile, frameState) {
var anyVisible = false;
var children = tile.children;
var length = children.length;
for (var i = 0; i < length; ++i) {
- var childVisible = updateVisibility(tileset, children[i], frameState);
- anyVisible = anyVisible || childVisible;
+ var child = children[i];
+ updateVisibility(tileset, child, frameState);
+ anyVisible = anyVisible || isVisible(child);
}
return anyVisible;
}
- function getVisibility(tileset, tile, frameState) {
+ function updateTileVisibility(tileset, tile, frameState) {
updateVisibility(tileset, tile, frameState);
- // Not visible
- if (tile._visibilityPlaneMask === CullingVolume.MASK_OUTSIDE) {
- return false;
+ if (!isVisible(tile)) {
+ return;
}
// Don't visit an expired subtree because it will be destroyed
if (tile.hasTilesetContent && tile.contentExpired) {
- return false;
+ clearVisibility(tile);
+ return;
}
// Use parent's geometric error with child's box to see if we already meet the SSE
var parent = tile.parent;
if (defined(parent) && (parent.refine === Cesium3DTileRefine.ADD) && getScreenSpaceError(tileset, parent.geometricError, tile, frameState) <= tileset._maximumScreenSpaceError) {
- return false;
+ clearVisibility(tile);
+ return;
}
// Optimization - if none of the tile's children are visible then this tile isn't visible
@@ -311,19 +346,16 @@ define([
var useOptimization = tile._optimChildrenWithinParent === Cesium3DTileOptimizationHint.USE_OPTIMIZATION;
var hasChildren = tile.children.length > 0;
if (replace && useOptimization && hasChildren) {
- var anyChildrenVisible = updateChildrenVisibility(tileset, tile, frameState);
- if (!anyChildrenVisible) {
+ if (!anyChildrenVisible(tileset, tile, frameState)) {
++tileset._statistics.numberOfTilesCulledWithChildrenUnion;
- return false;
+ clearVisibility(tile);
+ return;
}
}
-
- return true;
}
function updateTile(tileset, tile, frameState) {
- var visible = getVisibility(tileset, tile, frameState);
- tile._visibilityPlaneMask = visible ? tile._visibilityPlaneMask : CullingVolume.MASK_OUTSIDE;
+ updateTileVisibility(tileset, tile, frameState);
tile._selected = false;
tile._finalResolution = false;
@@ -341,8 +373,6 @@ define([
tile._ancestorWithContent = hasContent ? parent : parent._ancestorWithContent;
tile._ancestorWithContentAvailable = parent.contentAvailable ? parent : parent._ancestorWithContentAvailable;
}
-
- return visible;
}
function updateChildren(tileset, tile, frameState) {
@@ -354,10 +384,6 @@ define([
children.sort(sortChildrenByDistanceToCamera);
}
- function isVisible(tile) {
- return tile._visibilityPlaneMask !== CullingVolume.MASK_OUTSIDE;
- }
-
function hasEmptyContent(tile) {
return tile.hasEmptyContent || tile.hasTilesetContent;
}
@@ -429,8 +455,17 @@ define([
loadTile(tileset, child, true, frameState);
touchTile(tileset, child, frameState);
}
- // Always run the internal base traversal even if we already know we can't refine. This keeps the tiles loaded while we wait to refine.
- var refinesToChild = hasEmptyContent(child) ? executeEmptyTraversal(tileset, child, baseScreenSpaceError, maximumScreenSpaceError, frameState) : child.contentAvailable;
+
+ var refinesToChild;
+ if (isVisibleButNotInRequestVolume(child)) {
+ refinesToChild = false;
+ } else if (hasEmptyContent(child)) {
+ // Always run the internal base traversal even if we already know we can't refine. This keeps the tiles loaded while we wait to refine.
+ refinesToChild = executeEmptyTraversal(tileset, child, baseScreenSpaceError, maximumScreenSpaceError, frameState);
+ } else {
+ refinesToChild = child.contentAvailable;
+ }
+
refines = refines && refinesToChild;
}
}
@@ -498,8 +533,8 @@ define([
allDescendantsLoaded = false;
}
- var visible = updateTile(tileset, tile, frameState);
- if (!visible) {
+ updateTile(tileset, tile, frameState);
+ if (!isVisible(tile)) {
// Load tiles that aren't visible since they are still needed for the parent to refine
// Tiles that are visible will get loaded from within executeBaseTraversal
loadTile(tileset, tile, true, frameState);
diff --git a/Specs/Scene/Cesium3DTilesetSpec.js b/Specs/Scene/Cesium3DTilesetSpec.js
index df258b19176a..379327d4706c 100644
--- a/Specs/Scene/Cesium3DTilesetSpec.js
+++ b/Specs/Scene/Cesium3DTilesetSpec.js
@@ -1017,7 +1017,7 @@ defineSuite([
var root = tileset._root;
root.refine = Cesium3DTileRefine.REPLACE;
- root.hasEmptyContent = false;
+ root.hasEmptyContent = false; // mock content
root.hasRenderableContent = true; // mock content
tileset.maximumScreenSpaceError = 0.0; // Force root tile to always not meet SSE since this is just checking the request volume
From db216c1d731f50d496862800aafb55cc6341e7ab Mon Sep 17 00:00:00 2001
From: Sean Lilley
Date: Wed, 28 Mar 2018 13:06:31 -0400
Subject: [PATCH 21/53] Include additive tiles in final selection
---
Source/Scene/Cesium3DTile.js | 2 +-
Source/Scene/Cesium3DTilesetTraversal.js | 91 ++++++++++++------------
Specs/Scene/Cesium3DTilesetSpec.js | 18 ++---
3 files changed, 55 insertions(+), 56 deletions(-)
diff --git a/Source/Scene/Cesium3DTile.js b/Source/Scene/Cesium3DTile.js
index cf2222031145..d42385cc022d 100644
--- a/Source/Scene/Cesium3DTile.js
+++ b/Source/Scene/Cesium3DTile.js
@@ -330,7 +330,7 @@ define([
this._ancestorWithContent = undefined;
this._ancestorWithContentAvailable = undefined;
this._refines = false;
- this._selected = false;
+ this._shouldSelect = false;
this._priority = 0.0;
this._isClipped = true;
this._clippingPlanesState = 0; // encapsulates (_isClipped, clippingPlanes.enabled) and number/function
diff --git a/Source/Scene/Cesium3DTilesetTraversal.js b/Source/Scene/Cesium3DTilesetTraversal.js
index a320a509443c..1d63a88843b8 100644
--- a/Source/Scene/Cesium3DTilesetTraversal.js
+++ b/Source/Scene/Cesium3DTilesetTraversal.js
@@ -195,19 +195,22 @@ define([
function selectDesiredTile(tileset, tile, final, frameState) {
if (final) {
- // The tile can be selected right away and does not require traverseAndSelect. E.g. additive or empty tiles
- selectTile(tileset, tile, frameState);
- tile._finalResolution = true;
- } else {
- var loadedTile = tile.contentAvailable ? tile : tile._ancestorWithContentAvailable;
- if (defined(loadedTile)) {
- // Tiles marked for selection will be selected in traverseAndSelect
- loadedTile._selected = true;
- } else if (tileset.immediatelyLoadDesiredLevelOfDetail) {
- // If no ancestors are ready traverse down and select tiles to minimize empty regions.
- // This happens often in cases where the camera zooms out and we're waiting for parent tiles to load.
- selectDescendants(tileset, tile, frameState);
+ if (tile.contentAvailable) {
+ // The tile can be selected right away and does not require traverseAndSelect
+ tile._finalResolution = true;
+ selectTile(tileset, tile, frameState);
}
+ return;
+ }
+
+ var loadedTile = tile.contentAvailable ? tile : tile._ancestorWithContentAvailable;
+ if (defined(loadedTile)) {
+ // Tiles will be selected in traverseAndSelect
+ loadedTile._shouldSelect = true;
+ } else if (tileset.immediatelyLoadDesiredLevelOfDetail) {
+ // If no ancestors are ready traverse down and select tiles to minimize empty regions.
+ // This happens often in cases where the camera zooms out and we're waiting for parent tiles to load.
+ selectDescendants(tileset, tile, frameState);
}
}
@@ -357,7 +360,7 @@ define([
function updateTile(tileset, tile, frameState) {
updateTileVisibility(tileset, tile, frameState);
- tile._selected = false;
+ tile._shouldSelect = false;
tile._finalResolution = false;
tile._ancestorWithContent = undefined;
tile._ancestorWithContentAvailable = undefined;
@@ -452,7 +455,7 @@ define([
// load all descendants with content. Keep non-visible children loaded since they are still needed before the parent can refine.
// We don't do this for empty tiles because it looks better if children stream in as they are loaded to fill the empty space.
if (!visible) {
- loadTile(tileset, child, true, frameState);
+ loadTile(tileset, child, baseTraversalOnly, frameState);
touchTile(tileset, child, frameState);
}
@@ -471,39 +474,33 @@ define([
}
}
- if (replace) {
+ if (hasEmptyContent(tile)) {
+ // Select empty tiles so that we can see their debug bounding volumes
+ selectTile(tileset, tile, frameState);
+ } else if (add) {
+ // Additive tiles are always loaded and selected
+ selectDesiredTile(tileset, tile, baseTraversalOnly, frameState);
+ loadTile(tileset, tile, baseTraversalOnly, frameState);
+ } else if (replace) {
if (baseTraversal) {
// Always load tiles in the base traversal
// Select tiles that can't refine further
- loadTile(tileset, tile, true, frameState);
- if (!refines && parentRefines && tile.contentAvailable) {
+ loadTile(tileset, tile, baseTraversalOnly, frameState);
+ if (!refines && parentRefines) {
selectDesiredTile(tileset, tile, baseTraversalOnly, frameState);
}
} else {
// Load tiles that are not skipped or can't refine further. In practice roughly half the tiles stay unloaded.
// Select tiles that can't refine further. If the tile doesn't have loaded content it will try to select an ancestor with loaded content instead.
if (!refines) { // eslint-disable-line
- selectDesiredTile(tileset, tile, false, frameState);
- loadTile(tileset, tile, false, frameState);
+ selectDesiredTile(tileset, tile, baseTraversalOnly, frameState);
+ loadTile(tileset, tile, baseTraversalOnly, frameState);
} else if (reachedSkippingThreshold(tileset, tile)) {
- loadTile(tileset, tile, false, frameState);
+ loadTile(tileset, tile, baseTraversalOnly, frameState);
}
}
}
- if (add) {
- // Additive tiles are always loaded and selected
- if (tile.contentAvailable) {
- selectDesiredTile(tileset, tile, true, frameState);
- }
- loadTile(tileset, tile, false, frameState);
- }
-
- if (hasEmptyContent(tile)) {
- // Select empty tiles so that we can see their debug bounding volumes
- selectDesiredTile(tileset, tile, true, frameState);
- }
-
touchTile(tileset, tile, frameState);
tile._refines = refines;
}
@@ -603,24 +600,30 @@ define([
continue;
}
- var markedForSelection = tile._selected;
+ var add = tile.refine === Cesium3DTileRefine.ADD;
+ var shouldSelect = tile._shouldSelect;
var children = tile.children;
var childrenLength = children.length;
var traverse = (childrenLength > 0) && (tile._screenSpaceError > maximumScreenSpaceError);
- if (markedForSelection) {
- tile._selectionDepth = ancestorStack.length;
- if (tile._selectionDepth > 0) {
- tileset._hasMixedContent = true;
- }
- lastAncestor = tile;
- if (!traverse) {
+ if (shouldSelect) {
+ if (add) {
tile._finalResolution = true;
selectTile(tileset, tile, frameState);
- continue;
+ } else {
+ tile._selectionDepth = ancestorStack.length;
+ if (tile._selectionDepth > 0) {
+ tileset._hasMixedContent = true;
+ }
+ lastAncestor = tile;
+ if (!traverse) {
+ tile._finalResolution = true;
+ selectTile(tileset, tile, frameState);
+ continue;
+ }
+ ancestorStack.push(tile);
+ tile._stackLength = stack.length;
}
- ancestorStack.push(tile);
- tile._stackLength = stack.length;
}
if (traverse) {
diff --git a/Specs/Scene/Cesium3DTilesetSpec.js b/Specs/Scene/Cesium3DTilesetSpec.js
index 379327d4706c..7594d6b241dc 100644
--- a/Specs/Scene/Cesium3DTilesetSpec.js
+++ b/Specs/Scene/Cesium3DTilesetSpec.js
@@ -987,9 +987,7 @@ defineSuite([
});
it('replacement refinement - selects root when sse is not met and children are not ready', function() {
- // Set view so that only root tile is loaded initially
viewRootOnly();
-
return Cesium3DTilesTester.loadTileset(scene, tilesetUrl).then(function(tileset) {
var root = tileset._root;
root.refine = Cesium3DTileRefine.REPLACE;
@@ -1042,7 +1040,6 @@ defineSuite([
// E E
// C C C C
//
-
viewRootOnly();
return Cesium3DTilesTester.loadTileset(scene, tilesetReplacement1Url).then(function(tileset) {
viewAllTiles();
@@ -1051,15 +1048,14 @@ defineSuite([
var statistics = tileset._statistics;
var root = tileset._root;
- return when.join(root.children[0].contentReadyPromise, root.children[1].contentReadyPromise).then(function() {
- // Even though root's children are loaded, the grandchildren need to be loaded before it becomes refinable
- expect(numberOfChildrenWithoutContent(root)).toEqual(0); // Children are loaded
- expect(statistics.numberOfCommands).toEqual(1); // No stencil or backface commands; no mixed content
- expect(statistics.numberOfPendingRequests).toEqual(4); // Loading grandchildren
+ // Even though root's children are loaded, the grandchildren need to be loaded before it becomes refinable
+ expect(numberOfChildrenWithoutContent(root)).toEqual(0); // Children are loaded
+ expect(statistics.numberOfCommands).toEqual(1); // No stencil or backface commands; no mixed content
+ expect(statistics.numberOfPendingRequests).toEqual(4); // Loading grandchildren
- return Cesium3DTilesTester.waitForTilesLoaded(scene, tileset).then(function() {
- expect(statistics.numberOfCommands).toEqual(4); // Render children
- });
+ return Cesium3DTilesTester.waitForTilesLoaded(scene, tileset).then(function() {
+ scene.renderForSpecs();
+ expect(statistics.numberOfCommands).toEqual(4); // Render children
});
});
});
From 1f51e9b6d6786c90022338a8c7e504a6eed70403 Mon Sep 17 00:00:00 2001
From: Sean Lilley
Date: Wed, 28 Mar 2018 14:04:17 -0400
Subject: [PATCH 22/53] Add empty tiles to a different selection list
---
Source/Scene/Cesium3DTileset.js | 31 +++++++++++++++++++-----
Source/Scene/Cesium3DTilesetTraversal.js | 9 +++++--
2 files changed, 32 insertions(+), 8 deletions(-)
diff --git a/Source/Scene/Cesium3DTileset.js b/Source/Scene/Cesium3DTileset.js
index e0c947ca6958..5684d2e41f24 100644
--- a/Source/Scene/Cesium3DTileset.js
+++ b/Source/Scene/Cesium3DTileset.js
@@ -184,6 +184,7 @@ define([
this._cache = new Cesium3DTilesetCache();
this._processingQueue = [];
this._selectedTiles = [];
+ this._emptyTiles = [];
this._requestedTiles = [];
this._selectedTilesToStyle = [];
this._loadTimestamp = undefined;
@@ -1599,8 +1600,12 @@ define([
}
function updateTileDebugLabels(tileset, frameState) {
+ var i;
+ var tile;
var selectedTiles = tileset._selectedTiles;
- var length = selectedTiles.length;
+ var selectedLength = selectedTiles.length;
+ var emptyTiles = tileset._emptyTiles;
+ var emptyLength = emptyTiles.length;
tileset._tileDebugLabels.removeAll();
if (tileset.debugPickedTileLabelOnly) {
@@ -1610,10 +1615,16 @@ define([
label.pixelOffset = new Cartesian2(15, -15); // Offset to avoid picking the label.
}
} else {
- for (var i = 0; i < length; ++i) {
- var tile = selectedTiles[i];
+ for (i = 0; i < selectedLength; ++i) {
+ tile = selectedTiles[i];
addTileDebugLabel(tile, tileset, computeTileLabelPosition(tile));
}
+ for (i = 0; i < emptyLength; ++i) {
+ tile = emptyTiles[i];
+ if (tile.hasTilesetContent) {
+ addTileDebugLabel(tile, tileset, computeTileLabelPosition(tile));
+ }
+ }
}
tileset._tileDebugLabels.update(frameState);
}
@@ -1630,9 +1641,12 @@ define([
var commandList = frameState.commandList;
var numberOfInitialCommands = commandList.length;
var selectedTiles = tileset._selectedTiles;
- var length = selectedTiles.length;
+ var selectedLength = selectedTiles.length;
+ var emptyTiles = tileset._emptyTiles;
+ var emptyLength = emptyTiles.length;
var tileVisible = tileset.tileVisible;
var i;
+ var tile;
var bivariateVisibilityTest = tileset._skipLevelOfDetail && tileset._hasMixedContent && frameState.context.stencilBuffer && length > 0;
@@ -1643,8 +1657,8 @@ define([
}
var lengthBeforeUpdate = commandList.length;
- for (i = 0; i < length; ++i) {
- var tile = selectedTiles[i];
+ for (i = 0; i < selectedLength; ++i) {
+ tile = selectedTiles[i];
// Raise the tileVisible event before update in case the tileVisible event
// handler makes changes that update needs to apply to WebGL resources
tileVisible.raiseEvent(tile);
@@ -1652,6 +1666,11 @@ define([
statistics.incrementSelectionCounts(tile.content);
++statistics.selected;
}
+ for (i = 0; i < emptyLength; ++i) {
+ tile = emptyTiles[i];
+ tile.update(tileset, frameState);
+ }
+
var lengthAfterUpdate = commandList.length;
var addedCommandsLength = lengthAfterUpdate - lengthBeforeUpdate;
diff --git a/Source/Scene/Cesium3DTilesetTraversal.js b/Source/Scene/Cesium3DTilesetTraversal.js
index 1d63a88843b8..867d94dde752 100644
--- a/Source/Scene/Cesium3DTilesetTraversal.js
+++ b/Source/Scene/Cesium3DTilesetTraversal.js
@@ -87,6 +87,7 @@ define([
tileset._selectedTiles.length = 0;
tileset._selectedTilesToStyle.length = 0;
+ tileset._emptyTiles.length = 0;
tileset._hasMixedContent = false;
tileset._cache.reset();
@@ -139,6 +140,10 @@ define([
traverseAndSelect(tileset, root, frameState);
}
+ function addEmptyTile(tileset, tile) {
+ tileset._emptyTiles.push(tile);
+ }
+
function selectTile(tileset, tile, frameState) {
if (contentVisible(tile, frameState)) {
var tileContent = tile.content;
@@ -475,8 +480,8 @@ define([
}
if (hasEmptyContent(tile)) {
- // Select empty tiles so that we can see their debug bounding volumes
- selectTile(tileset, tile, frameState);
+ // Add empty tiles so that we can see its debug bounding volumes
+ addEmptyTile(tileset, tile, frameState);
} else if (add) {
// Additive tiles are always loaded and selected
selectDesiredTile(tileset, tile, baseTraversalOnly, frameState);
From 978f9f66209d7fa0446a79fe35bb6472d1037a10 Mon Sep 17 00:00:00 2001
From: Sean Lilley
Date: Wed, 28 Mar 2018 16:14:30 -0400
Subject: [PATCH 23/53] Reorganization of checking refinement
---
Source/Scene/Cesium3DTilesetTraversal.js | 196 +++++++++++++----------
Specs/Scene/Cesium3DTilesetSpec.js | 5 +-
2 files changed, 111 insertions(+), 90 deletions(-)
diff --git a/Source/Scene/Cesium3DTilesetTraversal.js b/Source/Scene/Cesium3DTilesetTraversal.js
index 867d94dde752..fd5656551c9b 100644
--- a/Source/Scene/Cesium3DTilesetTraversal.js
+++ b/Source/Scene/Cesium3DTilesetTraversal.js
@@ -105,7 +105,7 @@ define([
return;
}
- if (!tileset._skipLevelOfDetail || tileset._allTilesAdditive) {
+ if (!skipLevelOfDetail(tileset)) {
executeBaseTraversal(tileset, root, frameState);
} else if (tileset.immediatelyLoadDesiredLevelOfDetail) {
executeSkipTraversal(tileset, root, frameState);
@@ -140,10 +140,21 @@ define([
traverseAndSelect(tileset, root, frameState);
}
+ function skipLevelOfDetail(tileset) {
+ // Optimization: if all tiles are additive we can turn skipLevelOfDetail off and save some processing
+ return tileset._skipLevelOfDetail && !tileset._allTilesAdditive;
+ }
+
function addEmptyTile(tileset, tile) {
+ tile._finalResolution = true;
tileset._emptyTiles.push(tile);
}
+ function contentVisible(tile, frameState) {
+ return (tile._visibilityPlaneMask === CullingVolume.MASK_INSIDE) ||
+ (tile.contentVisibility(frameState) !== Intersect.OUTSIDE);
+ }
+
function selectTile(tileset, tile, frameState) {
if (contentVisible(tile, frameState)) {
var tileContent = tile.content;
@@ -162,20 +173,6 @@ define([
}
}
- function sortChildrenByDistanceToCamera(a, b) {
- // Sort by farthest child first since this is going on a stack
- if (b._distanceToCamera === 0 && a._distanceToCamera === 0) {
- return b._centerZDepth - a._centerZDepth;
- }
-
- return b._distanceToCamera - a._distanceToCamera;
- }
-
- function contentVisible(tile, frameState) {
- return (tile._visibilityPlaneMask === CullingVolume.MASK_INSIDE) ||
- (tile.contentVisibility(frameState) !== Intersect.OUTSIDE);
- }
-
function selectDescendants(tileset, root, frameState) {
var stack = descendantTraversal.stack;
stack.push(root);
@@ -198,8 +195,8 @@ define([
}
}
- function selectDesiredTile(tileset, tile, final, frameState) {
- if (final) {
+ function selectDesiredTile(tileset, tile, frameState) {
+ if (!skipLevelOfDetail(tileset)) {
if (tile.contentAvailable) {
// The tile can be selected right away and does not require traverseAndSelect
tile._finalResolution = true;
@@ -208,9 +205,10 @@ define([
return;
}
+ // If this tile is not loaded attempt to select its ancestor instead
var loadedTile = tile.contentAvailable ? tile : tile._ancestorWithContentAvailable;
if (defined(loadedTile)) {
- // Tiles will be selected in traverseAndSelect
+ // Tiles will actually be selected in traverseAndSelect
loadedTile._shouldSelect = true;
} else if (tileset.immediatelyLoadDesiredLevelOfDetail) {
// If no ancestors are ready traverse down and select tiles to minimize empty regions.
@@ -228,10 +226,10 @@ define([
tile._touchedFrame = frameState.frameNumber;
}
- function getPriority(tileset, tile, useParentPriority) {
- // The base traversal sets useParentPriority to true so that child tiles can load as soon as possible so that their parent can refine sooner.
+ function getPriority(tileset, tile) {
+ // If skipLevelOfDetail is off we try to load child tiles as soon as possible so that their parent can refine sooner.
// Additive tiles always load based on distance because it subjectively looks better.
- // There may be issues with mixing additive and replacement tileset since SSE and distance are not using the same overall scale.
+ // There may be issues with mixing additive and replacement tiles since SSE and distance are different types of values.
// Maybe all priorities need to be normalized to 0-1 range.
var parent = tile.parent;
var replace = tile.refine === Cesium3DTileRefine.REPLACE;
@@ -239,16 +237,17 @@ define([
if (add) {
return tile._distanceToCamera;
} else if (replace) {
- var screenSpaceError = (defined(parent) && (useParentPriority || (tile._screenSpaceError === 0.0))) ? parent._screenSpaceError : tile._screenSpaceError;
+ var useParentScreenSpaceError = defined(parent) && (!skipLevelOfDetail(tileset) || (tile._screenSpaceError === 0.0));
+ var screenSpaceError = useParentScreenSpaceError ? parent._screenSpaceError : tile._screenSpaceError;
var rootScreenSpaceError = tileset._root._screenSpaceError;
return rootScreenSpaceError - screenSpaceError; // Map higher SSE to lower priority values (higher priority)
}
}
- function loadTile(tileset, tile, useParentPriority, frameState) {
+ function loadTile(tileset, tile, frameState) {
if (hasUnloadedContent(tile) || tile.contentExpired) {
tile._requestedFrame = frameState.frameNumber;
- tile._priority = getPriority(tileset, tile, useParentPriority);
+ tile._priority = getPriority(tileset, tile);
tileset._requestedTiles.push(tile);
}
}
@@ -383,15 +382,6 @@ define([
}
}
- function updateChildren(tileset, tile, frameState) {
- var children = tile.children;
- var length = children.length;
- for (var i = 0; i < length; ++i) {
- updateTile(tileset, children[i], frameState);
- }
- children.sort(sortChildrenByDistanceToCamera);
- }
-
function hasEmptyContent(tile) {
return tile.hasEmptyContent || tile.hasTilesetContent;
}
@@ -402,16 +392,77 @@ define([
function reachedSkippingThreshold(tileset, tile) {
var ancestor = tile._ancestorWithContent;
- var skipLevels = tileset.skipLevelOfDetail ? tileset.skipLevels : 0;
- var skipScreenSpaceErrorFactor = tileset.skipLevelOfDetail ? tileset.skipScreenSpaceErrorFactor : 1.0;
-
return !tileset.immediatelyLoadDesiredLevelOfDetail &&
defined(ancestor) &&
- (tile._screenSpaceError < (ancestor._screenSpaceError / skipScreenSpaceErrorFactor)) &&
- (tile._depth > (ancestor._depth + skipLevels));
+ (tile._screenSpaceError < (ancestor._screenSpaceError / tileset.skipScreenSpaceErrorFactor)) &&
+ (tile._depth > (ancestor._depth + tileset.skipLevels));
}
- function inBaseTraversal(tile, baseScreenSpaceError) {
+ function sortChildrenByDistanceToCamera(a, b) {
+ // Sort by farthest child first since this is going on a stack
+ if (b._distanceToCamera === 0 && a._distanceToCamera === 0) {
+ return b._centerZDepth - a._centerZDepth;
+ }
+
+ return b._distanceToCamera - a._distanceToCamera;
+ }
+
+ function updateAndPushChildren(tileset, tile, stack, frameState) {
+ var i;
+ var replace = tile.refine === Cesium3DTileRefine.REPLACE;
+ var children = tile.children;
+ var length = children.length;
+
+ for (i = 0; i < length; ++i) {
+ updateTile(tileset, children[i], frameState);
+ }
+
+ // Sort by distance to take advantage of early Z and reduce artifacts for skipLevelOfDetail
+ children.sort(sortChildrenByDistanceToCamera);
+
+ // For traditional replacement refinement we need to check if all children are loaded before we can refine
+ // The reason we always refine empty tiles is it looks better if children stream in as they are loaded to fill the empty space
+ var checkRefines = !skipLevelOfDetail(tileset) && replace && !hasEmptyContent(parent);
+ var refines = true;
+
+ var anyChildrenVisible = false;
+ for (i = 0; i < length; ++i) {
+ var child = children[i];
+ var visible = isVisible(child);
+ if (visible) {
+ stack.push(child);
+ anyChildrenVisible = true;
+ }
+ if (checkRefines) {
+ if (!visible) {
+ // Keep non-visible children loaded since they are still needed before the parent can refine.
+ loadTile(tileset, child, frameState);
+ touchTile(tileset, child, frameState);
+ }
+ var childRefines;
+ if (isVisibleButNotInRequestVolume(child)) {
+ childRefines = false;
+ } else if (hasEmptyContent(child)) {
+ // We need to traverse past any empty tiles to know if we can refine
+ childRefines = executeEmptyTraversal(tileset, child, frameState);
+ } else {
+ childRefines = child.contentAvailable;
+ }
+ refines = refines && childRefines;
+ }
+ }
+
+ if (!anyChildrenVisible) {
+ refines = false;
+ }
+
+ return refines;
+ }
+
+ function inBaseTraversal(tileset, tile, baseScreenSpaceError) {
+ if (!skipLevelOfDetail(tileset)) {
+ return true;
+ }
if (tile._screenSpaceError === 0) {
var parent = tile.parent;
if (defined(parent)) {
@@ -424,10 +475,9 @@ define([
function executeTraversal(tileset, root, baseScreenSpaceError, maximumScreenSpaceError, frameState) {
// Depth-first traversal that traverses all visible tiles and marks tiles for selection.
- // If the traversal is base traversal ONLY then a tile does not refine until all children are loaded. This is the
+ // If skipLevelOfDetail is off then a tile does not refine until all children are loaded. This is the
// traditional replacement refinement approach.
// Otherwise we allow for skipping levels of the tree and rendering children and parent tiles simultaneously.
- var baseTraversalOnly = baseScreenSpaceError === maximumScreenSpaceError;
var stack = traversal.stack;
stack.push(root);
@@ -435,87 +485,58 @@ define([
traversal.stackMaximumLength = Math.max(traversal.stackMaximumLength, stack.length);
var tile = stack.pop();
- var baseTraversal = baseTraversalOnly || inBaseTraversal(tile, baseScreenSpaceError);
+ var baseTraversal = inBaseTraversal(tile, baseScreenSpaceError);
var add = tile.refine === Cesium3DTileRefine.ADD;
var replace = tile.refine === Cesium3DTileRefine.REPLACE;
var children = tile.children;
var childrenLength = children.length;
var parent = tile.parent;
- var parentRefines = !baseTraversalOnly || !defined(parent) || parent._refines;
+ var parentRefines = !defined(parent) || parent._refines;
var traverse = (childrenLength > 0) && (tile._screenSpaceError > maximumScreenSpaceError);
- var refines = traverse && parentRefines;
-
- visitTile(tileset);
+ var refines = false;
if (traverse) {
- updateChildren(tileset, tile, frameState);
- for (var i = 0; i < childrenLength; ++i) {
- var child = children[i];
- var visible = isVisible(child);
- if (visible) {
- stack.push(child);
- }
- if (baseTraversalOnly && replace && !hasEmptyContent(tile)) {
- // Check if the parent can refine to this child. If the child is empty we need to traverse further to
- // load all descendants with content. Keep non-visible children loaded since they are still needed before the parent can refine.
- // We don't do this for empty tiles because it looks better if children stream in as they are loaded to fill the empty space.
- if (!visible) {
- loadTile(tileset, child, baseTraversalOnly, frameState);
- touchTile(tileset, child, frameState);
- }
-
- var refinesToChild;
- if (isVisibleButNotInRequestVolume(child)) {
- refinesToChild = false;
- } else if (hasEmptyContent(child)) {
- // Always run the internal base traversal even if we already know we can't refine. This keeps the tiles loaded while we wait to refine.
- refinesToChild = executeEmptyTraversal(tileset, child, baseScreenSpaceError, maximumScreenSpaceError, frameState);
- } else {
- refinesToChild = child.contentAvailable;
- }
-
- refines = refines && refinesToChild;
- }
- }
+ refines = updateAndPushChildren(tileset, tile, stack, frameState);
}
if (hasEmptyContent(tile)) {
- // Add empty tiles so that we can see its debug bounding volumes
+ // Add empty tile so we can see its debug bounding volumes
addEmptyTile(tileset, tile, frameState);
} else if (add) {
// Additive tiles are always loaded and selected
- selectDesiredTile(tileset, tile, baseTraversalOnly, frameState);
- loadTile(tileset, tile, baseTraversalOnly, frameState);
+ selectDesiredTile(tileset, tile, frameState);
+ loadTile(tileset, tile, frameState);
} else if (replace) {
if (baseTraversal) {
// Always load tiles in the base traversal
// Select tiles that can't refine further
- loadTile(tileset, tile, baseTraversalOnly, frameState);
+ loadTile(tileset, tile, frameState);
if (!refines && parentRefines) {
- selectDesiredTile(tileset, tile, baseTraversalOnly, frameState);
+ selectDesiredTile(tileset, tile, frameState);
}
} else {
// Load tiles that are not skipped or can't refine further. In practice roughly half the tiles stay unloaded.
// Select tiles that can't refine further. If the tile doesn't have loaded content it will try to select an ancestor with loaded content instead.
if (!refines) { // eslint-disable-line
- selectDesiredTile(tileset, tile, baseTraversalOnly, frameState);
- loadTile(tileset, tile, baseTraversalOnly, frameState);
+ selectDesiredTile(tileset, tile, frameState);
+ loadTile(tileset, tile, frameState);
} else if (reachedSkippingThreshold(tileset, tile)) {
- loadTile(tileset, tile, baseTraversalOnly, frameState);
+ loadTile(tileset, tile, frameState);
}
}
}
+ visitTile(tileset);
touchTile(tileset, tile, frameState);
tile._refines = refines;
}
}
- function executeEmptyTraversal(tileset, root, baseScreenSpaceError, maximumScreenSpaceError, frameState) {
+ function executeEmptyTraversal(tileset, root, frameState) {
// Depth-first traversal that checks if all nearest descendants with content are loaded. Ignores visibility.
// The reason we need to implement a full traversal is to handle chains of empty tiles.
var allDescendantsLoaded = true;
-
+ var maximumScreenSpaceError = tileset._maximumScreenSpaceError;
var stack = emptyTraversal.stack;
stack.push(root);
@@ -527,7 +548,7 @@ define([
var childrenLength = children.length;
// Only traverse if the tile is empty - we are trying to find descendants with content
- var traverse = hasEmptyContent(tile) && (childrenLength > 0) && (tile._screenSpaceError > baseScreenSpaceError);
+ var traverse = hasEmptyContent(tile) && (childrenLength > 0) && (tile._screenSpaceError > maximumScreenSpaceError);
// When we reach a "leaf" that does not have content available we know that not all descendants are loaded
// i.e. there will be holes if the parent tries to refine to its children, so don't refine
@@ -538,8 +559,7 @@ define([
updateTile(tileset, tile, frameState);
if (!isVisible(tile)) {
// Load tiles that aren't visible since they are still needed for the parent to refine
- // Tiles that are visible will get loaded from within executeBaseTraversal
- loadTile(tileset, tile, true, frameState);
+ loadTile(tileset, tile, frameState);
touchTile(tileset, tile, frameState);
}
diff --git a/Specs/Scene/Cesium3DTilesetSpec.js b/Specs/Scene/Cesium3DTilesetSpec.js
index 7594d6b241dc..b9f0dd2dd457 100644
--- a/Specs/Scene/Cesium3DTilesetSpec.js
+++ b/Specs/Scene/Cesium3DTilesetSpec.js
@@ -1191,9 +1191,10 @@ defineSuite([
});
});
- it('does select visibile tiles with visible children failing request volumes', function() {
+ it('does select visible tiles with visible children failing request volumes', function() {
viewRootOnly();
return Cesium3DTilesTester.loadTileset(scene, tilesetReplacementWithViewerRequestVolumeUrl).then(function(tileset) {
+ scene.renderForSpecs();
var root = tileset._root;
var childRoot = root.children[0];
@@ -1209,7 +1210,7 @@ defineSuite([
});
});
- it('does select visibile tiles with visible children passing request volumes', function() {
+ it('does select visible tiles with visible children passing request volumes', function() {
return Cesium3DTilesTester.loadTileset(scene, tilesetReplacementWithViewerRequestVolumeUrl).then(function(tileset) {
var root = tileset._root;
var childRoot = root.children[0];
From 582a51fc71690551db7cf28e85fc686714bc7a57 Mon Sep 17 00:00:00 2001
From: Sean Lilley
Date: Wed, 28 Mar 2018 16:37:15 -0400
Subject: [PATCH 24/53] Fix geometric errors on TilesetOfTilesets
---
.../Data/Cesium3DTiles/Tilesets/TilesetOfTilesets/tileset.json | 2 +-
.../Data/Cesium3DTiles/Tilesets/TilesetOfTilesets/tileset2.json | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/Specs/Data/Cesium3DTiles/Tilesets/TilesetOfTilesets/tileset.json b/Specs/Data/Cesium3DTiles/Tilesets/TilesetOfTilesets/tileset.json
index 4b1aba30142c..98a3089d2cf5 100644
--- a/Specs/Data/Cesium3DTiles/Tilesets/TilesetOfTilesets/tileset.json
+++ b/Specs/Data/Cesium3DTiles/Tilesets/TilesetOfTilesets/tileset.json
@@ -32,7 +32,7 @@
88
]
},
- "geometricError": 70,
+ "geometricError": 240,
"refine": "ADD",
"content": {
"url": "tileset2.json"
diff --git a/Specs/Data/Cesium3DTiles/Tilesets/TilesetOfTilesets/tileset2.json b/Specs/Data/Cesium3DTiles/Tilesets/TilesetOfTilesets/tileset2.json
index 855d5fcad7cb..2b7a8d55fe89 100644
--- a/Specs/Data/Cesium3DTiles/Tilesets/TilesetOfTilesets/tileset2.json
+++ b/Specs/Data/Cesium3DTiles/Tilesets/TilesetOfTilesets/tileset2.json
@@ -2,7 +2,7 @@
"asset": {
"version": "1.0"
},
- "geometricError": 70,
+ "geometricError": 240,
"root": {
"boundingVolume": {
"region": [
From 49c005b806aff34ef3dfe9a38bfa61205b18b0bf Mon Sep 17 00:00:00 2001
From: Sean Lilley
Date: Wed, 28 Mar 2018 17:59:10 -0400
Subject: [PATCH 25/53] Add back loadSiblings and fix more tests
---
Source/Scene/Cesium3DTileset.js | 2 +-
Source/Scene/Cesium3DTilesetTraversal.js | 17 ++++++++---------
Specs/Scene/Cesium3DTilesetSpec.js | 14 +++-----------
3 files changed, 12 insertions(+), 21 deletions(-)
diff --git a/Source/Scene/Cesium3DTileset.js b/Source/Scene/Cesium3DTileset.js
index 5684d2e41f24..4222d3196ab2 100644
--- a/Source/Scene/Cesium3DTileset.js
+++ b/Source/Scene/Cesium3DTileset.js
@@ -1648,7 +1648,7 @@ define([
var i;
var tile;
- var bivariateVisibilityTest = tileset._skipLevelOfDetail && tileset._hasMixedContent && frameState.context.stencilBuffer && length > 0;
+ var bivariateVisibilityTest = tileset._skipLevelOfDetail && tileset._hasMixedContent && frameState.context.stencilBuffer && selectedLength > 0;
tileset._backfaceCommands.length = 0;
diff --git a/Source/Scene/Cesium3DTilesetTraversal.js b/Source/Scene/Cesium3DTilesetTraversal.js
index fd5656551c9b..170f2f1c3083 100644
--- a/Source/Scene/Cesium3DTilesetTraversal.js
+++ b/Source/Scene/Cesium3DTilesetTraversal.js
@@ -422,23 +422,22 @@ define([
// For traditional replacement refinement we need to check if all children are loaded before we can refine
// The reason we always refine empty tiles is it looks better if children stream in as they are loaded to fill the empty space
- var checkRefines = !skipLevelOfDetail(tileset) && replace && !hasEmptyContent(parent);
+ var checkRefines = !skipLevelOfDetail(tileset) && replace && !hasEmptyContent(tile);
var refines = true;
var anyChildrenVisible = false;
for (i = 0; i < length; ++i) {
var child = children[i];
- var visible = isVisible(child);
- if (visible) {
+ if (isVisible(child)) {
stack.push(child);
anyChildrenVisible = true;
+ } else if (checkRefines || tileset.loadSiblings) {
+ // Keep non-visible children loaded since they are still needed before the parent can refine.
+ // Or loadSiblings is true so we should always load tiles regardless of visibility.
+ loadTile(tileset, child, frameState);
+ touchTile(tileset, child, frameState);
}
if (checkRefines) {
- if (!visible) {
- // Keep non-visible children loaded since they are still needed before the parent can refine.
- loadTile(tileset, child, frameState);
- touchTile(tileset, child, frameState);
- }
var childRefines;
if (isVisibleButNotInRequestVolume(child)) {
childRefines = false;
@@ -485,7 +484,7 @@ define([
traversal.stackMaximumLength = Math.max(traversal.stackMaximumLength, stack.length);
var tile = stack.pop();
- var baseTraversal = inBaseTraversal(tile, baseScreenSpaceError);
+ var baseTraversal = inBaseTraversal(tileset, tile, baseScreenSpaceError);
var add = tile.refine === Cesium3DTileRefine.ADD;
var replace = tile.refine === Cesium3DTileRefine.REPLACE;
var children = tile.children;
diff --git a/Specs/Scene/Cesium3DTilesetSpec.js b/Specs/Scene/Cesium3DTilesetSpec.js
index b9f0dd2dd457..d152aaa1bc6d 100644
--- a/Specs/Scene/Cesium3DTilesetSpec.js
+++ b/Specs/Scene/Cesium3DTilesetSpec.js
@@ -183,12 +183,6 @@ defineSuite([
scene.camera.moveDown(200.0);
}
- function viewBottomRight() {
- viewAllTiles();
- scene.camera.moveRight(200.0);
- scene.camera.moveDown(200.0);
- }
-
function viewInstances() {
setZoom(30.0);
}
@@ -1257,7 +1251,6 @@ defineSuite([
// Check that headers are equal
var subtreeRoot = root.children[0];
- expect(root.geometricError).toEqual(subtreeRoot.geometricError);
expect(root.refine).toEqual(subtreeRoot.refine);
expect(root.contentBoundingVolume.boundingVolume).toEqual(subtreeRoot.contentBoundingVolume.boundingVolume);
@@ -2556,7 +2549,6 @@ defineSuite([
it('adds stencil clear command first when unresolved', function() {
return Cesium3DTilesTester.loadTileset(scene, tilesetReplacement3Url).then(function(tileset) {
-
tileset._root.children[0].children[0].children[0].unloadContent();
tileset._root.children[0].children[0].children[1].unloadContent();
tileset._root.children[0].children[0].children[2].unloadContent();
@@ -2570,7 +2562,6 @@ defineSuite([
it('creates duplicate backface commands', function() {
return Cesium3DTilesTester.loadTileset(scene, tilesetReplacement3Url).then(function(tileset) {
-
var statistics = tileset._statistics;
var root = tileset._root;
@@ -2663,8 +2654,8 @@ defineSuite([
});
});
- xit('immediatelyLoadDesiredLevelOfDetail', function() {
- viewBottomRight();
+ it('immediatelyLoadDesiredLevelOfDetail', function() {
+ viewBottomLeft();
var tileset = scene.primitives.add(new Cesium3DTileset({
url : tilesetOfTilesetsUrl,
immediatelyLoadDesiredLevelOfDetail : true
@@ -2674,6 +2665,7 @@ defineSuite([
return tileset._root.contentReadyPromise.then(function() {
tileset._root.refine = Cesium3DTileRefine.REPLACE;
tileset._root.children[0].refine = Cesium3DTileRefine.REPLACE;
+ tileset._allTilesAdditive = false;
return Cesium3DTilesTester.waitForTilesLoaded(scene, tileset).then(function(tileset) {
var statistics = tileset._statistics;
expect(statistics.numberOfTilesWithContentReady).toBe(1);
From 45090175328e110e5206d8804fa0dca52476da09 Mon Sep 17 00:00:00 2001
From: Sean Lilley
Date: Wed, 28 Mar 2018 22:17:42 -0400
Subject: [PATCH 26/53] Fix 2 tests
---
Specs/Scene/Cesium3DTilesetSpec.js | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/Specs/Scene/Cesium3DTilesetSpec.js b/Specs/Scene/Cesium3DTilesetSpec.js
index d152aaa1bc6d..a7f7b412ca42 100644
--- a/Specs/Scene/Cesium3DTilesetSpec.js
+++ b/Specs/Scene/Cesium3DTilesetSpec.js
@@ -1186,8 +1186,11 @@ defineSuite([
});
it('does select visible tiles with visible children failing request volumes', function() {
+ var options = {
+ cullWithChildrenBounds : false
+ };
viewRootOnly();
- return Cesium3DTilesTester.loadTileset(scene, tilesetReplacementWithViewerRequestVolumeUrl).then(function(tileset) {
+ return Cesium3DTilesTester.loadTileset(scene, tilesetReplacementWithViewerRequestVolumeUrl, options).then(function(tileset) {
scene.renderForSpecs();
var root = tileset._root;
var childRoot = root.children[0];
@@ -2641,8 +2644,7 @@ defineSuite([
it('loadSiblings', function() {
viewBottomLeft();
return Cesium3DTilesTester.loadTileset(scene, tilesetReplacement3Url, {
- loadSiblings : false,
- baseScreenSpaceError: 1000000000
+ loadSiblings : false
}).then(function(tileset) {
var statistics = tileset._statistics;
expect(statistics.numberOfTilesWithContentReady).toBe(2);
From 7d2128191e7f72835a6f6c3d12002f3ade4cb4d8 Mon Sep 17 00:00:00 2001
From: Sean Lilley
Date: Thu, 29 Mar 2018 10:29:48 -0400
Subject: [PATCH 27/53] Fix expiration
---
Source/Scene/Cesium3DTile.js | 2 +-
Source/Scene/Cesium3DTilesetTraversal.js | 19 ++++++++++---------
Specs/Scene/Cesium3DTilesetSpec.js | 4 ++++
3 files changed, 15 insertions(+), 10 deletions(-)
diff --git a/Source/Scene/Cesium3DTile.js b/Source/Scene/Cesium3DTile.js
index d42385cc022d..f137ba9c01fa 100644
--- a/Source/Scene/Cesium3DTile.js
+++ b/Source/Scene/Cesium3DTile.js
@@ -692,7 +692,7 @@ define([
updateExpireDate(that);
// Refresh style for expired content
- that.lastStyleTime = 0;
+ that._selectedFrame = 0;
that._contentState = Cesium3DTileContentState.READY;
that._contentReadyPromise.resolve(content);
diff --git a/Source/Scene/Cesium3DTilesetTraversal.js b/Source/Scene/Cesium3DTilesetTraversal.js
index 170f2f1c3083..afa26439158f 100644
--- a/Source/Scene/Cesium3DTilesetTraversal.js
+++ b/Source/Scene/Cesium3DTilesetTraversal.js
@@ -234,7 +234,9 @@ define([
var parent = tile.parent;
var replace = tile.refine === Cesium3DTileRefine.REPLACE;
var add = tile.refine === Cesium3DTileRefine.ADD;
- if (add) {
+ if (tile.hasTilesetContent) {
+ return 0.0; // Load external tileset as soon as possible
+ } else if (add) {
return tile._distanceToCamera;
} else if (replace) {
var useParentScreenSpaceError = defined(parent) && (!skipLevelOfDetail(tileset) || (tile._screenSpaceError === 0.0));
@@ -335,12 +337,6 @@ define([
return;
}
- // Don't visit an expired subtree because it will be destroyed
- if (tile.hasTilesetContent && tile.contentExpired) {
- clearVisibility(tile);
- return;
- }
-
// Use parent's geometric error with child's box to see if we already meet the SSE
var parent = tile.parent;
if (defined(parent) && (parent.refine === Cesium3DTileRefine.ADD) && getScreenSpaceError(tileset, parent.geometricError, tile, frameState) <= tileset._maximumScreenSpaceError) {
@@ -363,14 +359,13 @@ define([
function updateTile(tileset, tile, frameState) {
updateTileVisibility(tileset, tile, frameState);
+ tile.updateExpiration();
tile._shouldSelect = false;
tile._finalResolution = false;
tile._ancestorWithContent = undefined;
tile._ancestorWithContentAvailable = undefined;
- tile.updateExpiration();
-
var parent = tile.parent;
if (defined(parent)) {
// ancestorWithContent is an ancestor that has content or has the potential to have
@@ -494,6 +489,11 @@ define([
var traverse = (childrenLength > 0) && (tile._screenSpaceError > maximumScreenSpaceError);
var refines = false;
+ if (tile.hasTilesetContent && tile.contentExpired) {
+ // Don't traverse expired subtree because it will be destroyed
+ traverse = false;
+ }
+
if (traverse) {
refines = updateAndPushChildren(tileset, tile, stack, frameState);
}
@@ -501,6 +501,7 @@ define([
if (hasEmptyContent(tile)) {
// Add empty tile so we can see its debug bounding volumes
addEmptyTile(tileset, tile, frameState);
+ loadTile(tileset, tile, frameState);
} else if (add) {
// Additive tiles are always loaded and selected
selectDesiredTile(tileset, tile, frameState);
diff --git a/Specs/Scene/Cesium3DTilesetSpec.js b/Specs/Scene/Cesium3DTilesetSpec.js
index a7f7b412ca42..45a6f917a5c1 100644
--- a/Specs/Scene/Cesium3DTilesetSpec.js
+++ b/Specs/Scene/Cesium3DTilesetSpec.js
@@ -2877,7 +2877,11 @@ defineSuite([
tile.expireDate = JulianDate.addSeconds(JulianDate.now(), -1.0, new JulianDate());
// Stays in the expired state until the request goes through
+ var originalMaxmimumRequests = RequestScheduler.maximumRequests;
+ RequestScheduler.maximumRequests = 0; // Artificially limit Request Scheduler so the request won't go through
scene.renderForSpecs();
+ RequestScheduler.maximumRequests = originalMaxmimumRequests;
+
expect(tile.contentExpired).toBe(true);
return pollToPromise(function() {
From ca07240dcfeabffbbaeaffc6d5e48fb417c16945 Mon Sep 17 00:00:00 2001
From: Sean Lilley
Date: Thu, 29 Mar 2018 10:59:08 -0400
Subject: [PATCH 28/53] Remove unneeeded scene.renderForSpecs
---
Specs/Scene/Cesium3DTilesetSpec.js | 1 -
1 file changed, 1 deletion(-)
diff --git a/Specs/Scene/Cesium3DTilesetSpec.js b/Specs/Scene/Cesium3DTilesetSpec.js
index 45a6f917a5c1..52565adf898a 100644
--- a/Specs/Scene/Cesium3DTilesetSpec.js
+++ b/Specs/Scene/Cesium3DTilesetSpec.js
@@ -1191,7 +1191,6 @@ defineSuite([
};
viewRootOnly();
return Cesium3DTilesTester.loadTileset(scene, tilesetReplacementWithViewerRequestVolumeUrl, options).then(function(tileset) {
- scene.renderForSpecs();
var root = tileset._root;
var childRoot = root.children[0];
From fae93f7981566a532139a99a6b3b3cd58c61b45f Mon Sep 17 00:00:00 2001
From: Sean Lilley
Date: Thu, 29 Mar 2018 11:55:16 -0400
Subject: [PATCH 29/53] Fix remaining tests and starting to reorg for pick pass
---
Source/Scene/Cesium3DTileset.js | 9 +++++++--
Source/Scene/Cesium3DTilesetTraversal.js | 8 ++++++--
Specs/Scene/Cesium3DTileSpec.js | 1 +
3 files changed, 14 insertions(+), 4 deletions(-)
diff --git a/Source/Scene/Cesium3DTileset.js b/Source/Scene/Cesium3DTileset.js
index 4222d3196ab2..5571b30630b1 100644
--- a/Source/Scene/Cesium3DTileset.js
+++ b/Source/Scene/Cesium3DTileset.js
@@ -1862,8 +1862,13 @@ define([
}
if (outOfCore) {
- this._requestedTiles.length = 0;
- Cesium3DTilesetTraversal.selectTiles(this, frameState);
+ this._cache.reset();
+ }
+
+ this._requestedTiles.length = 0;
+ Cesium3DTilesetTraversal.selectTiles(this, frameState);
+
+ if (outOfCore) {
requestTiles(this);
processTiles(this, frameState);
}
diff --git a/Source/Scene/Cesium3DTilesetTraversal.js b/Source/Scene/Cesium3DTilesetTraversal.js
index afa26439158f..e895cac8d4bb 100644
--- a/Source/Scene/Cesium3DTilesetTraversal.js
+++ b/Source/Scene/Cesium3DTilesetTraversal.js
@@ -85,13 +85,15 @@ define([
return;
}
+ if (frameState.passes.pick) {
+ console.log('pick');
+ }
+
tileset._selectedTiles.length = 0;
tileset._selectedTilesToStyle.length = 0;
tileset._emptyTiles.length = 0;
tileset._hasMixedContent = false;
- tileset._cache.reset();
-
var maximumScreenSpaceError = tileset._maximumScreenSpaceError;
var root = tileset._root;
@@ -291,6 +293,7 @@ define([
}
function updateVisibility(tileset, tile, frameState) {
+ // TODO : pick pass might be different
if (tile._updatedVisibilityFrame === frameState.frameNumber) {
return;
}
@@ -316,6 +319,7 @@ define([
}
tile._visibilityFlag = visibilityFlag;
+ tile._updatedVisibilityFrame = frameState.frameNumber;
}
function anyChildrenVisible(tileset, tile, frameState) {
diff --git a/Specs/Scene/Cesium3DTileSpec.js b/Specs/Scene/Cesium3DTileSpec.js
index a0699cedf8fd..590031c45c21 100644
--- a/Specs/Scene/Cesium3DTileSpec.js
+++ b/Specs/Scene/Cesium3DTileSpec.js
@@ -317,6 +317,7 @@ defineSuite([
var scene;
beforeEach(function() {
scene = createScene();
+ scene.frameState.passes.render = true;
});
afterEach(function() {
From 72ec719bac39c9f0c45cc931d3fa178b89f9c0aa Mon Sep 17 00:00:00 2001
From: Sean Lilley
Date: Thu, 29 Mar 2018 13:45:40 -0400
Subject: [PATCH 30/53] Fix picking statistics
---
Source/Scene/Cesium3DTilesetTraversal.js | 10 ++++------
.../Cesium3DTilesInspectorViewModel.js | 4 ++--
2 files changed, 6 insertions(+), 8 deletions(-)
diff --git a/Source/Scene/Cesium3DTilesetTraversal.js b/Source/Scene/Cesium3DTilesetTraversal.js
index e895cac8d4bb..5f104421a515 100644
--- a/Source/Scene/Cesium3DTilesetTraversal.js
+++ b/Source/Scene/Cesium3DTilesetTraversal.js
@@ -85,10 +85,6 @@ define([
return;
}
- if (frameState.passes.pick) {
- console.log('pick');
- }
-
tileset._selectedTiles.length = 0;
tileset._selectedTilesToStyle.length = 0;
tileset._emptyTiles.length = 0;
@@ -224,6 +220,9 @@ define([
}
function touchTile(tileset, tile, frameState) {
+ if (frameState.passes.pick) {
+ return;
+ }
tileset._cache.touch(tile);
tile._touchedFrame = frameState.frameNumber;
}
@@ -293,8 +292,7 @@ define([
}
function updateVisibility(tileset, tile, frameState) {
- // TODO : pick pass might be different
- if (tile._updatedVisibilityFrame === frameState.frameNumber) {
+ if (tile._updatedVisibilityFrame === frameState.frameNumber && !frameState.passes.pick) {
return;
}
diff --git a/Source/Widgets/Cesium3DTilesInspector/Cesium3DTilesInspectorViewModel.js b/Source/Widgets/Cesium3DTilesInspector/Cesium3DTilesInspectorViewModel.js
index b7a6153f547d..0641d2ff10b6 100644
--- a/Source/Widgets/Cesium3DTilesInspector/Cesium3DTilesInspectorViewModel.js
+++ b/Source/Widgets/Cesium3DTilesInspector/Cesium3DTilesInspectorViewModel.js
@@ -71,7 +71,7 @@ define([
return '';
}
- var statistics = tileset.statistics;
+ var statistics = isPick ? tileset._statisticsLastPick : tileset._statisticsLastColor;
// Since the pick pass uses a smaller frustum around the pixel of interest,
// the statistics will be different than the normal render pass.
@@ -81,7 +81,7 @@ define([
'Visited: ' + statistics.visited.toLocaleString() + '' +
// Number of commands returned is likely to be higher than the number of tiles selected
// because of tiles that create multiple commands.
- 'Selected: ' + tileset._selectedTiles.length.toLocaleString() + '' +
+ 'Selected: ' + statistics.selected.toLocaleString() + '' +
// Number of commands executed is likely to be higher because of commands overlapping
// multiple frustums.
'Commands: ' + statistics.numberOfCommands.toLocaleString() + '';
From e558f2f39cf62a8b91b4c5dc3156be5ce9e06bff Mon Sep 17 00:00:00 2001
From: Sean Lilley
Date: Thu, 29 Mar 2018 14:14:27 -0400
Subject: [PATCH 31/53] Fix refine for skip-lods off
---
Source/Scene/Cesium3DTilesetTraversal.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Source/Scene/Cesium3DTilesetTraversal.js b/Source/Scene/Cesium3DTilesetTraversal.js
index 5f104421a515..5baf031b793a 100644
--- a/Source/Scene/Cesium3DTilesetTraversal.js
+++ b/Source/Scene/Cesium3DTilesetTraversal.js
@@ -497,7 +497,7 @@ define([
}
if (traverse) {
- refines = updateAndPushChildren(tileset, tile, stack, frameState);
+ refines = updateAndPushChildren(tileset, tile, stack, frameState) && parentRefines;
}
if (hasEmptyContent(tile)) {
From 0cb8dcd71bcf751169fdcdc13c84139250c66e26 Mon Sep 17 00:00:00 2001
From: Sean Lilley
Date: Thu, 29 Mar 2018 15:10:39 -0400
Subject: [PATCH 32/53] Always include root tile in base traversal
---
Source/Scene/Cesium3DTilesetTraversal.js | 25 ++++++++++++++----------
1 file changed, 15 insertions(+), 10 deletions(-)
diff --git a/Source/Scene/Cesium3DTilesetTraversal.js b/Source/Scene/Cesium3DTilesetTraversal.js
index 5baf031b793a..60d334b2b7b3 100644
--- a/Source/Scene/Cesium3DTilesetTraversal.js
+++ b/Source/Scene/Cesium3DTilesetTraversal.js
@@ -185,6 +185,7 @@ define([
updateTile(tileset, child, frameState);
touchTile(tileset, child, frameState);
selectTile(tileset, child, frameState);
+ tile._finalResolution = true;
} else if (child._depth - root._depth < 2) {
// Continue traversing, but not too far
stack.push(child);
@@ -197,8 +198,8 @@ define([
if (!skipLevelOfDetail(tileset)) {
if (tile.contentAvailable) {
// The tile can be selected right away and does not require traverseAndSelect
- tile._finalResolution = true;
selectTile(tileset, tile, frameState);
+ tile._finalResolution = true;
}
return;
}
@@ -208,9 +209,9 @@ define([
if (defined(loadedTile)) {
// Tiles will actually be selected in traverseAndSelect
loadedTile._shouldSelect = true;
- } else if (tileset.immediatelyLoadDesiredLevelOfDetail) {
+ } else {
// If no ancestors are ready traverse down and select tiles to minimize empty regions.
- // This happens often in cases where the camera zooms out and we're waiting for parent tiles to load.
+ // This happens often for immediatelyLoadDesiredLevelOfDetail where parent tiles are not necessarily loaded before zooming out.
selectDescendants(tileset, tile, frameState);
}
}
@@ -459,13 +460,17 @@ define([
if (!skipLevelOfDetail(tileset)) {
return true;
}
- if (tile._screenSpaceError === 0) {
- var parent = tile.parent;
- if (defined(parent)) {
- return parent._screenSpaceError > baseScreenSpaceError;
- }
+ if (tileset.immediatelyLoadDesiredLevelOfDetail) {
+ return false;
+ }
+ var parent = tile.parent;
+ if (!defined(tile._ancestorWithContent)) {
+ // Include root tiles in the base traversal always so we will have something to select up to
return true;
}
+ if (tile._screenSpaceError === 0) {
+ return parent._screenSpaceError > baseScreenSpaceError;
+ }
return tile._screenSpaceError > baseScreenSpaceError;
}
@@ -635,8 +640,8 @@ define([
if (shouldSelect) {
if (add) {
- tile._finalResolution = true;
selectTile(tileset, tile, frameState);
+ tile._finalResolution = true;
} else {
tile._selectionDepth = ancestorStack.length;
if (tile._selectionDepth > 0) {
@@ -644,8 +649,8 @@ define([
}
lastAncestor = tile;
if (!traverse) {
- tile._finalResolution = true;
selectTile(tileset, tile, frameState);
+ tile._finalResolution = true;
continue;
}
ancestorStack.push(tile);
From 6d29c37a96b60f7933559f30c4894c8699cb334a Mon Sep 17 00:00:00 2001
From: Sean Lilley
Date: Thu, 22 Feb 2018 14:20:37 -0500
Subject: [PATCH 33/53] Move tileset cache to its own class
---
Source/Scene/Cesium3DTile.js | 8 +--
Source/Scene/Cesium3DTileset.js | 66 +++++---------------
Source/Scene/Cesium3DTilesetCache.js | 79 ++++++++++++++++++++++++
Source/Scene/Cesium3DTilesetTraversal.js | 15 ++---
Specs/Scene/Cesium3DTilesetSpec.js | 8 +--
5 files changed, 108 insertions(+), 68 deletions(-)
create mode 100644 Source/Scene/Cesium3DTilesetCache.js
diff --git a/Source/Scene/Cesium3DTile.js b/Source/Scene/Cesium3DTile.js
index 18101211d979..57e588774403 100644
--- a/Source/Scene/Cesium3DTile.js
+++ b/Source/Scene/Cesium3DTile.js
@@ -250,14 +250,16 @@ define([
this.hasTilesetContent = false;
/**
- * The corresponding node in the cache replacement list.
+ * The corresponding node in the cache.
+ *
+ * See {@link Cesium3DTilesetCache}
*
* @type {DoublyLinkedListNode}
* @readonly
*
* @private
*/
- this.replacementNode = undefined;
+ this.cacheNode = undefined;
var expire = header.expire;
var expireDuration;
@@ -732,8 +734,6 @@ define([
this._contentReadyToProcessPromise = undefined;
this._contentReadyPromise = undefined;
- this.replacementNode = undefined;
-
this.lastStyleTime = 0;
this._debugColorizeTiles = false;
diff --git a/Source/Scene/Cesium3DTileset.js b/Source/Scene/Cesium3DTileset.js
index e4590a8d60e1..25ef90c9dff2 100644
--- a/Source/Scene/Cesium3DTileset.js
+++ b/Source/Scene/Cesium3DTileset.js
@@ -29,6 +29,7 @@ define([
'./Cesium3DTile',
'./Cesium3DTileColorBlendMode',
'./Cesium3DTileOptimizations',
+ './Cesium3DTilesetCache',
'./Cesium3DTilesetStatistics',
'./Cesium3DTilesetTraversal',
'./Cesium3DTileStyleEngine',
@@ -73,6 +74,7 @@ define([
Cesium3DTile,
Cesium3DTileColorBlendMode,
Cesium3DTileOptimizations,
+ Cesium3DTilesetCache,
Cesium3DTilesetStatistics,
Cesium3DTilesetTraversal,
Cesium3DTileStyleEngine,
@@ -175,6 +177,7 @@ define([
this._properties = undefined; // Metadata for per-model/point/etc properties
this._geometricError = undefined; // Geometric error when the tree is not rendered at all
this._gltfUpAxis = undefined;
+ this._cache = new Cesium3DTilesetCache();
this._processingQueue = [];
this._selectedTiles = [];
this._requestedTiles = [];
@@ -183,14 +186,6 @@ define([
this._loadTimestamp = undefined;
this._timeSinceLoad = 0.0;
- var replacementList = new DoublyLinkedList();
-
- // [head, sentinel) -> tiles that weren't selected this frame and may be replaced
- // (sentinel, tail] -> tiles that were selected this frame
- this._replacementList = replacementList; // Tiles with content loaded. For cache management.
- this._replacementSentinel = replacementList.add();
- this._trimTiles = false;
-
this._cullWithChildrenBounds = defaultValue(options.cullWithChildrenBounds, true);
this._hasMixedContent = false;
@@ -1502,10 +1497,7 @@ define([
tileset._statistics.incrementLoadCounts(tile.content);
++tileset._statistics.numberOfTilesWithContentReady;
- // Add to the tile cache. Previously expired tiles are already in the cache.
- if (!defined(tile.replacementNode)) {
- tile.replacementNode = tileset._replacementList.add(tile);
- }
+ tileset._cache.add(tile);
}
};
}
@@ -1745,52 +1737,27 @@ define([
stack.push(children[i]);
}
if (tile !== root) {
- unloadTileFromCache(tileset, tile);
- tile.destroy();
+ destroyTile(tileset, tile);
--statistics.numberOfTilesTotal;
}
}
root.children = [];
}
- function unloadTileFromCache(tileset, tile) {
- var node = tile.replacementNode;
- if (!defined(node)) {
- return;
- }
-
- var statistics = tileset._statistics;
- var replacementList = tileset._replacementList;
- var tileUnload = tileset.tileUnload;
+ function unloadTile(tileset, tile) {
+ tileset.tileUnload.raiseEvent(tile);
+ tileset._statistics.decrementLoadCounts(tile.content);
+ --tileset._statistics.numberOfTilesWithContentReady;
+ tile.unloadContent();
+ }
- tileUnload.raiseEvent(tile);
- replacementList.remove(node);
- statistics.decrementLoadCounts(tile.content);
- --statistics.numberOfTilesWithContentReady;
+ function destroyTile(tileset, tile) {
+ tileset._cache.unloadTile(tileset, tile, unloadTile);
+ tile.destroy();
}
function unloadTiles(tileset) {
- var trimTiles = tileset._trimTiles;
- tileset._trimTiles = false;
-
- var replacementList = tileset._replacementList;
-
- var totalMemoryUsageInBytes = tileset.totalMemoryUsageInBytes;
- var maximumMemoryUsageInBytes = tileset._maximumMemoryUsage * 1024 * 1024;
-
- // Traverse the list only to the sentinel since tiles/nodes to the
- // right of the sentinel were used this frame.
- //
- // The sub-list to the left of the sentinel is ordered from LRU to MRU.
- var sentinel = tileset._replacementSentinel;
- var node = replacementList.head;
- while ((node !== sentinel) && ((totalMemoryUsageInBytes > maximumMemoryUsageInBytes) || trimTiles)) {
- var tile = node.item;
- node = node.next;
- unloadTileFromCache(tileset, tile);
- tile.unloadContent();
- totalMemoryUsageInBytes = tileset.totalMemoryUsageInBytes;
- }
+ tileset._cache.unloadTiles(tileset, unloadTile);
}
/**
@@ -1803,8 +1770,7 @@ define([
*
*/
Cesium3DTileset.prototype.trimLoadedTiles = function() {
- // Defer to next frame so WebGL delete calls happen inside the render loop
- this._trimTiles = true;
+ this._cache.trim();
};
///////////////////////////////////////////////////////////////////////////
diff --git a/Source/Scene/Cesium3DTilesetCache.js b/Source/Scene/Cesium3DTilesetCache.js
new file mode 100644
index 000000000000..848a41da2529
--- /dev/null
+++ b/Source/Scene/Cesium3DTilesetCache.js
@@ -0,0 +1,79 @@
+define([
+ '../Core/defined',
+ '../Core/DoublyLinkedList'
+ ], function(
+ defined,
+ DoublyLinkedList) {
+ 'use strict';
+
+ /**
+ * Stores tiles with content loaded.
+ *
+ * @private
+ */
+ function Cesium3DTilesetCache() {
+ // [head, sentinel) -> tiles that weren't selected this frame and may be removed from the cache
+ // (sentinel, tail] -> tiles that were selected this frame
+ this._list = new DoublyLinkedList();
+ this._sentinel = this._list.add();
+ this._trimTiles = false;
+ }
+
+ Cesium3DTilesetCache.prototype.reset = function() {
+ // Move sentinel node to the tail so, at the start of the frame, all tiles
+ // may be potentially replaced. Tiles are moved to the right of the sentinel
+ // when they are selected so they will not be replaced.
+ this._list.splice(this._list.tail, this._sentinel);
+ };
+
+ Cesium3DTilesetCache.prototype.touch = function(tile) {
+ var node = tile.cacheNode;
+ if (defined(node)) {
+ this._list.splice(this._sentinel, node);
+ }
+ };
+
+ Cesium3DTilesetCache.prototype.add = function(tile) {
+ if (!defined(tile.cacheNode)) {
+ tile.cacheNode = this._list.add(tile);
+ }
+ };
+
+ Cesium3DTilesetCache.prototype.unloadTile = function(tileset, tile, unloadCallback) {
+ var node = tile.cacheNode;
+ if (!defined(node)) {
+ return;
+ }
+
+ this._list.remove(node);
+ tile.cacheNode = undefined;
+ unloadCallback(tileset, tile);
+ };
+
+ Cesium3DTilesetCache.prototype.unloadTiles = function(tileset, unloadCallback) {
+ var trimTiles = this._trimTiles;
+ this._trimTiles = false;
+
+ var list = this._list;
+
+ var maximumMemoryUsageInBytes = tileset.maximumMemoryUsage * 1024 * 1024;
+
+ // Traverse the list only to the sentinel since tiles/nodes to the
+ // right of the sentinel were used this frame.
+ //
+ // The sub-list to the left of the sentinel is ordered from LRU to MRU.
+ var sentinel = this._sentinel;
+ var node = list.head;
+ while ((node !== sentinel) && ((tileset.totalMemoryUsageInBytes > maximumMemoryUsageInBytes) || trimTiles)) {
+ var tile = node.item;
+ node = node.next;
+ this.unloadTile(tileset, tile, unloadCallback);
+ }
+ };
+
+ Cesium3DTilesetCache.prototype.trim = function() {
+ this._trimTiles = true;
+ };
+
+ return Cesium3DTilesetCache;
+});
diff --git a/Source/Scene/Cesium3DTilesetTraversal.js b/Source/Scene/Cesium3DTilesetTraversal.js
index 3052b00ddb79..3acb1a2a66a3 100644
--- a/Source/Scene/Cesium3DTilesetTraversal.js
+++ b/Source/Scene/Cesium3DTilesetTraversal.js
@@ -25,7 +25,9 @@ define([
/**
* @private
*/
- var Cesium3DTilesetTraversal = {};
+ function Cesium3DTilesetTraversal() {
+ }
+
function selectTiles(tileset, frameState, outOfCore) {
if (tileset.debugFreezeFrame) {
@@ -40,11 +42,7 @@ define([
tileset._selectedTilesToStyle.length = 0;
tileset._hasMixedContent = false;
- // Move sentinel node to the tail so, at the start of the frame, all tiles
- // may be potentially replaced. Tiles are moved to the right of the sentinel
- // when they are selected so they will not be replaced.
- var replacementList = tileset._replacementList;
- replacementList.splice(replacementList.tail, tileset._replacementSentinel);
+ tileset._cache.reset();
var root = tileset._root;
root.updateTransform(tileset._modelMatrix);
@@ -625,10 +623,7 @@ define([
if (!outOfCore) {
return;
}
- var node = tile.replacementNode;
- if (defined(node)) {
- tileset._replacementList.splice(tileset._replacementSentinel, node);
- }
+ tileset._cache.touch(tile);
}
function computeSSE(tile, frameState) {
diff --git a/Specs/Scene/Cesium3DTilesetSpec.js b/Specs/Scene/Cesium3DTilesetSpec.js
index ce9f61a122cb..1e0330376b61 100644
--- a/Specs/Scene/Cesium3DTilesetSpec.js
+++ b/Specs/Scene/Cesium3DTilesetSpec.js
@@ -2299,14 +2299,14 @@ defineSuite([
it('Unloads cached tiles in a tileset with external tileset.json using maximumMemoryUsage', function() {
return Cesium3DTilesTester.loadTileset(scene, tilesetOfTilesetsUrl).then(function(tileset) {
var statistics = tileset._statistics;
- var replacementList = tileset._replacementList;
+ var cacheList = tileset._cache._list;
tileset.maximumMemoryUsage = 0.025;
scene.renderForSpecs();
expect(statistics.numberOfCommands).toEqual(5);
expect(statistics.numberOfTilesWithContentReady).toEqual(5);
- expect(replacementList.length - 1).toEqual(5); // Only tiles with content are on the replacement list. -1 for sentinel.
+ expect(cacheList.length - 1).toEqual(5); // Only tiles with content are on the replacement list. -1 for sentinel.
// Zoom out so only root tile is needed to meet SSE. This unloads
// all tiles except the root and one of the b3dm children
@@ -2315,7 +2315,7 @@ defineSuite([
expect(statistics.numberOfCommands).toEqual(1);
expect(statistics.numberOfTilesWithContentReady).toEqual(2);
- expect(replacementList.length - 1).toEqual(2);
+ expect(cacheList.length - 1).toEqual(2);
// Reset camera so all tiles are reloaded
viewAllTiles();
@@ -2324,7 +2324,7 @@ defineSuite([
expect(statistics.numberOfCommands).toEqual(5);
expect(statistics.numberOfTilesWithContentReady).toEqual(5);
- expect(replacementList.length - 1).toEqual(5);
+ expect(cacheList.length - 1).toEqual(5);
});
});
});
From 5c97c9b7ea9fd4c413ace25daccb42984837dd16 Mon Sep 17 00:00:00 2001
From: Sean Lilley
Date: Tue, 27 Mar 2018 14:24:02 -0400
Subject: [PATCH 34/53] Rename file
---
.../InstancedGltfExternal/{Box.glb => boxtemp.glb} | Bin
1 file changed, 0 insertions(+), 0 deletions(-)
rename Specs/Data/Cesium3DTiles/Instanced/InstancedGltfExternal/{Box.glb => boxtemp.glb} (100%)
diff --git a/Specs/Data/Cesium3DTiles/Instanced/InstancedGltfExternal/Box.glb b/Specs/Data/Cesium3DTiles/Instanced/InstancedGltfExternal/boxtemp.glb
similarity index 100%
rename from Specs/Data/Cesium3DTiles/Instanced/InstancedGltfExternal/Box.glb
rename to Specs/Data/Cesium3DTiles/Instanced/InstancedGltfExternal/boxtemp.glb
From 15921c22800f7cb77ad4b33834c3cae16780165d Mon Sep 17 00:00:00 2001
From: Sean Lilley
Date: Tue, 27 Mar 2018 14:26:08 -0400
Subject: [PATCH 35/53] Rename file back to lowercase
---
.../InstancedGltfExternal/{boxtemp.glb => box.glb} | Bin
1 file changed, 0 insertions(+), 0 deletions(-)
rename Specs/Data/Cesium3DTiles/Instanced/InstancedGltfExternal/{boxtemp.glb => box.glb} (100%)
diff --git a/Specs/Data/Cesium3DTiles/Instanced/InstancedGltfExternal/boxtemp.glb b/Specs/Data/Cesium3DTiles/Instanced/InstancedGltfExternal/box.glb
similarity index 100%
rename from Specs/Data/Cesium3DTiles/Instanced/InstancedGltfExternal/boxtemp.glb
rename to Specs/Data/Cesium3DTiles/Instanced/InstancedGltfExternal/box.glb
From bc9c55a4570cb614833fc945b6381ed1180ec950 Mon Sep 17 00:00:00 2001
From: Sean Lilley
Date: Thu, 29 Mar 2018 16:33:45 -0400
Subject: [PATCH 36/53] Fix eslint error
---
Source/Scene/Cesium3DTilesetTraversal.js | 1 -
1 file changed, 1 deletion(-)
diff --git a/Source/Scene/Cesium3DTilesetTraversal.js b/Source/Scene/Cesium3DTilesetTraversal.js
index 3acb1a2a66a3..2fd1038170ff 100644
--- a/Source/Scene/Cesium3DTilesetTraversal.js
+++ b/Source/Scene/Cesium3DTilesetTraversal.js
@@ -28,7 +28,6 @@ define([
function Cesium3DTilesetTraversal() {
}
-
function selectTiles(tileset, frameState, outOfCore) {
if (tileset.debugFreezeFrame) {
return;
From ee36a460b83969229cae0b70c6496decff078923 Mon Sep 17 00:00:00 2001
From: Sean Lilley
Date: Thu, 29 Mar 2018 16:37:04 -0400
Subject: [PATCH 37/53] Temp CHANGES.md
---
CHANGES.md | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/CHANGES.md b/CHANGES.md
index 14eaaaef168b..8fa83ccdcd2a 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,6 +1,10 @@
Change Log
==========
+### 1.45 - 2018-05-01
+##### Additions :tada:
+* 3D Tiles changes - TODO
+
### 1.44 - 2018-04-02
##### Breaking Changes :mega:
From 7254492c5841ba9306d646fffc6b6ff8978ae150 Mon Sep 17 00:00:00 2001
From: Sean Lilley
Date: Tue, 3 Apr 2018 13:17:14 -0400
Subject: [PATCH 38/53] Removed hasTilesetContent check when loading because it
will always be false until the tile is loaded
---
Source/Scene/Cesium3DTilesetTraversal.js | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/Source/Scene/Cesium3DTilesetTraversal.js b/Source/Scene/Cesium3DTilesetTraversal.js
index 60d334b2b7b3..5a46192d9972 100644
--- a/Source/Scene/Cesium3DTilesetTraversal.js
+++ b/Source/Scene/Cesium3DTilesetTraversal.js
@@ -236,9 +236,7 @@ define([
var parent = tile.parent;
var replace = tile.refine === Cesium3DTileRefine.REPLACE;
var add = tile.refine === Cesium3DTileRefine.ADD;
- if (tile.hasTilesetContent) {
- return 0.0; // Load external tileset as soon as possible
- } else if (add) {
+ if (add) {
return tile._distanceToCamera;
} else if (replace) {
var useParentScreenSpaceError = defined(parent) && (!skipLevelOfDetail(tileset) || (tile._screenSpaceError === 0.0));
From 8e5883514d8614d0c42a9a3d5694497707843e88 Mon Sep 17 00:00:00 2001
From: Sean Lilley
Date: Wed, 4 Apr 2018 14:35:06 -0400
Subject: [PATCH 39/53] Add back comment about expired tiles
---
Source/Scene/Cesium3DTileset.js | 1 +
1 file changed, 1 insertion(+)
diff --git a/Source/Scene/Cesium3DTileset.js b/Source/Scene/Cesium3DTileset.js
index b8b91ca3fd23..aab8c326ca54 100644
--- a/Source/Scene/Cesium3DTileset.js
+++ b/Source/Scene/Cesium3DTileset.js
@@ -1496,6 +1496,7 @@ define([
tileset._statistics.incrementLoadCounts(tile.content);
++tileset._statistics.numberOfTilesWithContentReady;
+ // Add to the tile cache. Previously expired tiles are already in the cache and won't get re-added.
tileset._cache.add(tile);
}
};
From 3f6714b9e2621b0aa31d148611c3fd14b7a086d4 Mon Sep 17 00:00:00 2001
From: Sean Lilley
Date: Tue, 12 Jun 2018 21:43:04 -0400
Subject: [PATCH 40/53] Add and update comments
---
Source/Scene/Cesium3DTile.js | 3 ++-
Source/Scene/Cesium3DTilesetTraversal.js | 30 +++++++++++++++++-------
2 files changed, 24 insertions(+), 9 deletions(-)
diff --git a/Source/Scene/Cesium3DTile.js b/Source/Scene/Cesium3DTile.js
index 0ce42fc85d5b..83185a17ab0c 100644
--- a/Source/Scene/Cesium3DTile.js
+++ b/Source/Scene/Cesium3DTile.js
@@ -321,7 +321,8 @@ define([
this._stackLength = 0;
this._selectionDepth = 0;
- this._updatedVisibilityFrame = 0;
+ this._updatedVisibilityFrameRender = 0;
+ this._updatedVisibilityFramePick = 0;
this._touchedFrame = 0;
this._selectedFrame = 0;
this._requestedFrame = 0;
diff --git a/Source/Scene/Cesium3DTilesetTraversal.js b/Source/Scene/Cesium3DTilesetTraversal.js
index 5a46192d9972..3d3a44123b74 100644
--- a/Source/Scene/Cesium3DTilesetTraversal.js
+++ b/Source/Scene/Cesium3DTilesetTraversal.js
@@ -291,8 +291,21 @@ define([
}
function updateVisibility(tileset, tile, frameState) {
- if (tile._updatedVisibilityFrame === frameState.frameNumber && !frameState.passes.pick) {
- return;
+ // These checks are here to prevent updating tile visibility multiple times - once when checking children
+ // visibility with the cullWithChildrenBounds optimization, and again when checking the tile itself.
+ // This is also the only reason updateVisibility and updateTileVisibility are separate functions.
+ // Render and pick passes require different visibility checks since they use different frustums.
+ if (frameState.passes.pick) {
+ if (tile._updatedVisibilityFramePick === frameState.frameNumber) {
+ return;
+ }
+ tile._updatedVisibilityFramePick = frameState.frameNumber;
+ }
+ if (frameState.passes.render) {
+ if (tile._updatedVisibilityFrameRender === frameState.frameNumber) {
+ return;
+ }
+ tile._updatedVisibilityFrameRender = frameState.frameNumber;
}
var visibilityFlag = VisibilityFlag.NONE;
@@ -316,7 +329,6 @@ define([
}
tile._visibilityFlag = visibilityFlag;
- tile._updatedVisibilityFrame = frameState.frameNumber;
}
function anyChildrenVisible(tileset, tile, frameState) {
@@ -461,13 +473,13 @@ define([
if (tileset.immediatelyLoadDesiredLevelOfDetail) {
return false;
}
- var parent = tile.parent;
if (!defined(tile._ancestorWithContent)) {
- // Include root tiles in the base traversal always so we will have something to select up to
+ // Include the highest up tiles with content in the base traversal so we have something to select up to
return true;
}
if (tile._screenSpaceError === 0) {
- return parent._screenSpaceError > baseScreenSpaceError;
+ // If a leaf, use parent's SSE
+ return tile.parent._screenSpaceError > baseScreenSpaceError;
}
return tile._screenSpaceError > baseScreenSpaceError;
}
@@ -475,8 +487,10 @@ define([
function executeTraversal(tileset, root, baseScreenSpaceError, maximumScreenSpaceError, frameState) {
// Depth-first traversal that traverses all visible tiles and marks tiles for selection.
// If skipLevelOfDetail is off then a tile does not refine until all children are loaded. This is the
- // traditional replacement refinement approach.
- // Otherwise we allow for skipping levels of the tree and rendering children and parent tiles simultaneously.
+ // traditional replacement refinement approach and is called the base traversal.
+ // Tiles that have a greater screen space error than the base screen space error are part of the base traversal,
+ // all other tiles are part of the skip traversal. The skip traversal allows for skipping levels of the tree
+ // and rendering children and parent tiles simultaneously.
var stack = traversal.stack;
stack.push(root);
From 5856714239fe92d46177bb30bf78465e1df69faf Mon Sep 17 00:00:00 2001
From: Sean Lilley
Date: Wed, 4 Jul 2018 12:34:16 -0400
Subject: [PATCH 41/53] Simplify update visibility checking
---
Source/Scene/Cesium3DTile.js | 3 +--
Source/Scene/Cesium3DTileset.js | 18 ++++++++++++++++++
Source/Scene/Cesium3DTilesetTraversal.js | 19 ++++++-------------
3 files changed, 25 insertions(+), 15 deletions(-)
diff --git a/Source/Scene/Cesium3DTile.js b/Source/Scene/Cesium3DTile.js
index 5d7c8b551851..86563d9018d9 100644
--- a/Source/Scene/Cesium3DTile.js
+++ b/Source/Scene/Cesium3DTile.js
@@ -323,8 +323,7 @@ define([
this._stackLength = 0;
this._selectionDepth = 0;
- this._updatedVisibilityFrameRender = 0;
- this._updatedVisibilityFramePick = 0;
+ this._updatedVisibilityFrame = 0;
this._touchedFrame = 0;
this._selectedFrame = 0;
this._requestedFrame = 0;
diff --git a/Source/Scene/Cesium3DTileset.js b/Source/Scene/Cesium3DTileset.js
index 6d412f94cd5b..1dfde692f84b 100644
--- a/Source/Scene/Cesium3DTileset.js
+++ b/Source/Scene/Cesium3DTileset.js
@@ -180,6 +180,8 @@ define([
this._selectedTilesToStyle = [];
this._loadTimestamp = undefined;
this._timeSinceLoad = 0.0;
+ this._pass = 0;
+ this._passDirty = true;
this._cullWithChildrenBounds = defaultValue(options.cullWithChildrenBounds, true);
this._allTilesAdditive = true;
@@ -1806,6 +1808,18 @@ define([
///////////////////////////////////////////////////////////////////////////
+ function getPass(frameState) {
+ // Convert the passes object to a bitfield to determine when the pass has changed.
+ var passState = 0;
+ var passes = frameState.passes;
+ for (var pass in passes) {
+ if (passes.hasOwnProperty(pass)) {
+ passState = (passState << 1) | (passes[pass] ? 1 : 0);
+ }
+ }
+ return passState;
+ }
+
/**
* Called when {@link Viewer} or {@link CesiumWidget} render the scene to
* get the draw commands needed to render this primitive.
@@ -1854,6 +1868,10 @@ define([
this._cache.reset();
}
+ var pass = getPass(frameState);
+ this._passDirty = this._pass !== pass;
+ this._pass = pass;
+
this._requestedTiles.length = 0;
Cesium3DTilesetTraversal.selectTiles(this, frameState);
diff --git a/Source/Scene/Cesium3DTilesetTraversal.js b/Source/Scene/Cesium3DTilesetTraversal.js
index 3d3a44123b74..b6887b8c051f 100644
--- a/Source/Scene/Cesium3DTilesetTraversal.js
+++ b/Source/Scene/Cesium3DTilesetTraversal.js
@@ -222,6 +222,7 @@ define([
function touchTile(tileset, tile, frameState) {
if (frameState.passes.pick) {
+ // Tiles already touched in render pass.
return;
}
tileset._cache.touch(tile);
@@ -291,23 +292,15 @@ define([
}
function updateVisibility(tileset, tile, frameState) {
- // These checks are here to prevent updating tile visibility multiple times - once when checking children
+ // Prevent updating tile visibility multiple times - once when checking children
// visibility with the cullWithChildrenBounds optimization, and again when checking the tile itself.
// This is also the only reason updateVisibility and updateTileVisibility are separate functions.
- // Render and pick passes require different visibility checks since they use different frustums.
- if (frameState.passes.pick) {
- if (tile._updatedVisibilityFramePick === frameState.frameNumber) {
- return;
- }
- tile._updatedVisibilityFramePick = frameState.frameNumber;
- }
- if (frameState.passes.render) {
- if (tile._updatedVisibilityFrameRender === frameState.frameNumber) {
- return;
- }
- tile._updatedVisibilityFrameRender = frameState.frameNumber;
+ if (!tileset._passDirty && (tile._updatedVisibilityFrame === frameState.frameNumber)) {
+ return;
}
+ tile._updatedVisibilityFrame = frameState.frameNumber;
+
var visibilityFlag = VisibilityFlag.NONE;
var parent = tile.parent;
From 6bda10efb9f8b6088e6a74aa99937ad9184ce6f8 Mon Sep 17 00:00:00 2001
From: Sean Lilley
Date: Wed, 4 Jul 2018 15:49:21 -0400
Subject: [PATCH 42/53] Simplify visibility code and tweak comments
---
Source/Scene/Cesium3DTile.js | 3 +-
Source/Scene/Cesium3DTilesetTraversal.js | 109 ++++++++---------------
2 files changed, 40 insertions(+), 72 deletions(-)
diff --git a/Source/Scene/Cesium3DTile.js b/Source/Scene/Cesium3DTile.js
index 86563d9018d9..10d13534cdf1 100644
--- a/Source/Scene/Cesium3DTile.js
+++ b/Source/Scene/Cesium3DTile.js
@@ -316,7 +316,8 @@ define([
this._centerZDepth = 0; // TODO : remove?
this._screenSpaceError = 0;
this._visibilityPlaneMask = 0;
- this._visibilityFlag = 0;
+ this._visible = false;
+ this._inRequestVolume = false;
this._finalResolution = true;
this._depth = 0;
diff --git a/Source/Scene/Cesium3DTilesetTraversal.js b/Source/Scene/Cesium3DTilesetTraversal.js
index b6887b8c051f..6888ccc8ee48 100644
--- a/Source/Scene/Cesium3DTilesetTraversal.js
+++ b/Source/Scene/Cesium3DTilesetTraversal.js
@@ -30,32 +30,8 @@ define([
function Cesium3DTilesetTraversal() {
}
- var VisibilityFlag = {
- NONE : 0,
- VISIBLE : 1,
- IN_REQUEST_VOLUME : 2
- };
-
- function isVisibleBit(flag) {
- return (flag & VisibilityFlag.VISIBLE) > 0;
- }
-
- function inRequestVolumeBit(flag) {
- return (flag & VisibilityFlag.IN_REQUEST_VOLUME) > 0;
- }
-
- function clearVisibility(tile) {
- tile._visibilityFlag = tile._visibilityFlag & ~VisibilityFlag.VISIBLE;
- }
-
function isVisible(tile) {
- var flag = tile._visibilityFlag;
- return isVisibleBit(flag) && inRequestVolumeBit(flag);
- }
-
- function isVisibleButNotInRequestVolume(tile) {
- var flag = tile._visibilityFlag;
- return isVisibleBit(flag) && !inRequestVolumeBit(flag);
+ return tile._visible && tile._inRequestVolume;
}
var traversal = {
@@ -80,6 +56,8 @@ define([
ancestorStackMaximumLength : 0
};
+ var descendantSelectionDepth = 2;
+
Cesium3DTilesetTraversal.selectTiles = function(tileset, frameState) {
if (tileset.debugFreezeFrame) {
return;
@@ -90,16 +68,16 @@ define([
tileset._emptyTiles.length = 0;
tileset._hasMixedContent = false;
- var maximumScreenSpaceError = tileset._maximumScreenSpaceError;
-
var root = tileset._root;
updateTile(tileset, root, frameState);
+
+ // The root tile is not visible
if (!isVisible(root)) {
return;
}
// The SSE of not rendering the tree is small enough that the tree does not need to be rendered
- if (getScreenSpaceError(tileset, tileset._geometricError, root, frameState) <= maximumScreenSpaceError) {
+ if (getScreenSpaceError(tileset, tileset._geometricError, root, frameState) <= tileset._maximumScreenSpaceError) {
return;
}
@@ -139,7 +117,7 @@ define([
}
function skipLevelOfDetail(tileset) {
- // Optimization: if all tiles are additive we can turn skipLevelOfDetail off and save some processing
+ // Optimization: if all tiles are additive turn off skipLevelOfDetail to save some processing
return tileset._skipLevelOfDetail && !tileset._allTilesAdditive;
}
@@ -186,7 +164,7 @@ define([
touchTile(tileset, child, frameState);
selectTile(tileset, child, frameState);
tile._finalResolution = true;
- } else if (child._depth - root._depth < 2) {
+ } else if (child._depth - root._depth < descendantSelectionDepth) {
// Continue traversing, but not too far
stack.push(child);
}
@@ -221,8 +199,8 @@ define([
}
function touchTile(tileset, tile, frameState) {
- if (frameState.passes.pick) {
- // Tiles already touched in render pass.
+ if (tile._touchedFrame === frameState.frameNumber) {
+ // Prevents another pass from touching the frame again
return;
}
tileset._cache.touch(tile);
@@ -230,10 +208,11 @@ define([
}
function getPriority(tileset, tile) {
- // If skipLevelOfDetail is off we try to load child tiles as soon as possible so that their parent can refine sooner.
- // Additive tiles always load based on distance because it subjectively looks better.
- // There may be issues with mixing additive and replacement tiles since SSE and distance are different types of values.
- // Maybe all priorities need to be normalized to 0-1 range.
+ // If skipLevelOfDetail is off try to load child tiles as soon as possible so that their parent can refine sooner.
+ // Additive tiles are prioritized by distance because it subjectively looks better.
+ // Replacement tiles are prioritized by screen space error.
+ // A tileset that has both additive and replacement tiles may not prioritize tiles as effectively since SSE and distance
+ // are different types of values. Maybe all priorities need to be normalized to 0-1 range.
var parent = tile.parent;
var replace = tile.refine === Cesium3DTileRefine.REPLACE;
var add = tile.refine === Cesium3DTileRefine.ADD;
@@ -243,7 +222,7 @@ define([
var useParentScreenSpaceError = defined(parent) && (!skipLevelOfDetail(tileset) || (tile._screenSpaceError === 0.0));
var screenSpaceError = useParentScreenSpaceError ? parent._screenSpaceError : tile._screenSpaceError;
var rootScreenSpaceError = tileset._root._screenSpaceError;
- return rootScreenSpaceError - screenSpaceError; // Map higher SSE to lower priority values (higher priority)
+ return rootScreenSpaceError - screenSpaceError; // Map higher SSE to lower values (e.g. 0.0 is highest priority)
}
}
@@ -292,17 +271,16 @@ define([
}
function updateVisibility(tileset, tile, frameState) {
- // Prevent updating tile visibility multiple times - once when checking children
- // visibility with the cullWithChildrenBounds optimization, and again when checking the tile itself.
- // This is also the only reason updateVisibility and updateTileVisibility are separate functions.
if (!tileset._passDirty && (tile._updatedVisibilityFrame === frameState.frameNumber)) {
+ // Return early if visibility has already been checked during this pass and this frame.
+ // The visibility may have already been checked if the cullWithChildrenBounds optimization is used.
+ // Visibility must be checked again if the pass changes since the pick pass uses
+ // a smaller frustum than the render pass.
return;
}
tile._updatedVisibilityFrame = frameState.frameNumber;
- var visibilityFlag = VisibilityFlag.NONE;
-
var parent = tile.parent;
var parentTransform = defined(parent) ? parent.computedTransform : tileset._modelMatrix;
var parentVisibilityPlaneMask = defined(parent) ? parent._visibilityPlaneMask : CullingVolume.MASK_INDETERMINATE;
@@ -312,16 +290,8 @@ define([
tile._centerZDepth = tile.distanceToTileCenter(frameState);
tile._screenSpaceError = getScreenSpaceError(tileset, tile.geometricError, tile, frameState);
tile._visibilityPlaneMask = tile.visibility(frameState, parentVisibilityPlaneMask); // Use parent's plane mask to speed up visibility test
-
- if (tile._visibilityPlaneMask !== CullingVolume.MASK_OUTSIDE) {
- visibilityFlag |= VisibilityFlag.VISIBLE;
- }
-
- if (tile.insideViewerRequestVolume(frameState)) {
- visibilityFlag |= VisibilityFlag.IN_REQUEST_VOLUME;
- }
-
- tile._visibilityFlag = visibilityFlag;
+ tile._visible = tile._visibilityPlaneMask !== CullingVolume.MASK_OUTSIDE;
+ tile._inRequestVolume = tile.insideViewerRequestVolume(frameState);
}
function anyChildrenVisible(tileset, tile, frameState) {
@@ -343,10 +313,10 @@ define([
return;
}
- // Use parent's geometric error with child's box to see if we already meet the SSE
+ // Use parent's geometric error with child's box to see if the tile already meet the SSE
var parent = tile.parent;
if (defined(parent) && (parent.refine === Cesium3DTileRefine.ADD) && getScreenSpaceError(tileset, parent.geometricError, tile, frameState) <= tileset._maximumScreenSpaceError) {
- clearVisibility(tile);
+ tile._visible = false;
return;
}
@@ -357,7 +327,7 @@ define([
if (replace && useOptimization && hasChildren) {
if (!anyChildrenVisible(tileset, tile, frameState)) {
++tileset._statistics.numberOfTilesCulledWithChildrenUnion;
- clearVisibility(tile);
+ tile._visible = false;
return;
}
}
@@ -376,7 +346,7 @@ define([
if (defined(parent)) {
// ancestorWithContent is an ancestor that has content or has the potential to have
// content. Used in conjunction with tileset.skipLevels to know when to skip a tile.
- // ancestorWithContentAvailable is an ancestor that we can render if a desired tile is not loaded.
+ // ancestorWithContentAvailable is an ancestor that is rendered if a desired tile is not loaded.
var hasContent = !hasUnloadedContent(parent) || (parent._requestedFrame === frameState.frameNumber);
tile._ancestorWithContent = hasContent ? parent : parent._ancestorWithContent;
tile._ancestorWithContentAvailable = parent.contentAvailable ? parent : parent._ancestorWithContentAvailable;
@@ -421,8 +391,8 @@ define([
// Sort by distance to take advantage of early Z and reduce artifacts for skipLevelOfDetail
children.sort(sortChildrenByDistanceToCamera);
- // For traditional replacement refinement we need to check if all children are loaded before we can refine
- // The reason we always refine empty tiles is it looks better if children stream in as they are loaded to fill the empty space
+ // For traditional replacement refinement only refine if all children are loaded.
+ // Empty tiles are exempt since it looks better if children stream in as they are loaded to fill the empty space.
var checkRefines = !skipLevelOfDetail(tileset) && replace && !hasEmptyContent(tile);
var refines = true;
@@ -434,16 +404,15 @@ define([
anyChildrenVisible = true;
} else if (checkRefines || tileset.loadSiblings) {
// Keep non-visible children loaded since they are still needed before the parent can refine.
- // Or loadSiblings is true so we should always load tiles regardless of visibility.
+ // Or loadSiblings is true so always load tiles regardless of visibility.
loadTile(tileset, child, frameState);
touchTile(tileset, child, frameState);
}
if (checkRefines) {
var childRefines;
- if (isVisibleButNotInRequestVolume(child)) {
+ if (!child._inRequestVolume) {
childRefines = false;
} else if (hasEmptyContent(child)) {
- // We need to traverse past any empty tiles to know if we can refine
childRefines = executeEmptyTraversal(tileset, child, frameState);
} else {
childRefines = child.contentAvailable;
@@ -467,10 +436,10 @@ define([
return false;
}
if (!defined(tile._ancestorWithContent)) {
- // Include the highest up tiles with content in the base traversal so we have something to select up to
+ // Include root or near-root tiles in the base traversal so there is something to select up to
return true;
}
- if (tile._screenSpaceError === 0) {
+ if (tile._screenSpaceError === 0.0) {
// If a leaf, use parent's SSE
return tile.parent._screenSpaceError > baseScreenSpaceError;
}
@@ -479,8 +448,8 @@ define([
function executeTraversal(tileset, root, baseScreenSpaceError, maximumScreenSpaceError, frameState) {
// Depth-first traversal that traverses all visible tiles and marks tiles for selection.
- // If skipLevelOfDetail is off then a tile does not refine until all children are loaded. This is the
- // traditional replacement refinement approach and is called the base traversal.
+ // If skipLevelOfDetail is off then a tile does not refine until all children are loaded.
+ // This is the traditional replacement refinement approach and is called the base traversal.
// Tiles that have a greater screen space error than the base screen space error are part of the base traversal,
// all other tiles are part of the skip traversal. The skip traversal allows for skipping levels of the tree
// and rendering children and parent tiles simultaneously.
@@ -511,7 +480,7 @@ define([
}
if (hasEmptyContent(tile)) {
- // Add empty tile so we can see its debug bounding volumes
+ // Add empty tile just to show its debug bounding volume
addEmptyTile(tileset, tile, frameState);
loadTile(tileset, tile, frameState);
} else if (add) {
@@ -546,7 +515,6 @@ define([
function executeEmptyTraversal(tileset, root, frameState) {
// Depth-first traversal that checks if all nearest descendants with content are loaded. Ignores visibility.
- // The reason we need to implement a full traversal is to handle chains of empty tiles.
var allDescendantsLoaded = true;
var maximumScreenSpaceError = tileset._maximumScreenSpaceError;
var stack = emptyTraversal.stack;
@@ -559,11 +527,11 @@ define([
var children = tile.children;
var childrenLength = children.length;
- // Only traverse if the tile is empty - we are trying to find descendants with content
+ // Only traverse if the tile is empty - traversal stop at descendants with content
var traverse = hasEmptyContent(tile) && (childrenLength > 0) && (tile._screenSpaceError > maximumScreenSpaceError);
- // When we reach a "leaf" that does not have content available we know that not all descendants are loaded
- // i.e. there will be holes if the parent tries to refine to its children, so don't refine
+ // Traversal stops but the tile does not have content yet.
+ // There will be holes if the parent tries to refine to its children, so don't refine.
if (!traverse && !tile.contentAvailable) {
allDescendantsLoaded = false;
}
@@ -588,7 +556,6 @@ define([
/**
* Traverse the tree and check if their selected frame is the current frame. If so, add it to a selection queue.
- * Tiles are sorted near to far so we can take advantage of early Z.
* Furthermore, this is a preorder traversal so children tiles are selected before ancestor tiles.
*
* The reason for the preorder traversal is so that tiles can easily be marked with their
From 79e77c7ff11ab25deef080738ded789bca2c1f64 Mon Sep 17 00:00:00 2001
From: Sean Lilley
Date: Wed, 4 Jul 2018 16:12:35 -0400
Subject: [PATCH 43/53] Tweak and fix tests to get better code coverage
---
Specs/Scene/Cesium3DTilesetSpec.js | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/Specs/Scene/Cesium3DTilesetSpec.js b/Specs/Scene/Cesium3DTilesetSpec.js
index a3222b836512..88df58ee7a97 100644
--- a/Specs/Scene/Cesium3DTilesetSpec.js
+++ b/Specs/Scene/Cesium3DTilesetSpec.js
@@ -1031,6 +1031,7 @@ defineSuite([
//
viewRootOnly();
return Cesium3DTilesTester.loadTileset(scene, tilesetReplacement1Url).then(function(tileset) {
+ tileset.skipLevelOfDetail = false;
viewAllTiles();
scene.renderForSpecs();
@@ -1060,6 +1061,7 @@ defineSuite([
viewRootOnly();
return Cesium3DTilesTester.loadTileset(scene, tilesetReplacement2Url).then(function(tileset) {
+ tileset.skipLevelOfDetail = false;
var statistics = tileset._statistics;
return Cesium3DTilesTester.waitForTilesLoaded(scene, tileset).then(function() {
expect(statistics.numberOfCommands).toEqual(1);
@@ -1085,6 +1087,7 @@ defineSuite([
viewRootOnly();
return Cesium3DTilesTester.loadTileset(scene, tilesetReplacement3Url).then(function(tileset) {
+ tileset.skipLevelOfDetail = false;
var statistics = tileset._statistics;
var root = tileset._root;
expect(statistics.numberOfCommands).toEqual(1);
@@ -2665,6 +2668,15 @@ defineSuite([
return Cesium3DTilesTester.waitForTilesLoaded(scene, tileset).then(function(tileset) {
var statistics = tileset._statistics;
expect(statistics.numberOfTilesWithContentReady).toBe(1);
+ // Renders child while parent loads
+ viewRootOnly();
+ scene.renderForSpecs();
+ expect(isSelected(tileset, tileset._root.children[0]));
+ expect(!isSelected(tileset, tileset._root));
+ return Cesium3DTilesTester.waitForTilesLoaded(scene, tileset).then(function(tileset) {
+ expect(!isSelected(tileset, tileset._root.children[0]));
+ expect(isSelected(tileset, tileset._root));
+ });
});
});
});
From 716c6a4b7f5598f6fc17e2ec2458021ee9eaa2bf Mon Sep 17 00:00:00 2001
From: Sean Lilley
Date: Wed, 4 Jul 2018 16:30:32 -0400
Subject: [PATCH 44/53] Draw empty bounding volumes as a different color
(green)
---
Source/Scene/Cesium3DTile.js | 11 ++++++++++-
1 file changed, 10 insertions(+), 1 deletion(-)
diff --git a/Source/Scene/Cesium3DTile.js b/Source/Scene/Cesium3DTile.js
index 10d13534cdf1..67fdfcb89c32 100644
--- a/Source/Scene/Cesium3DTile.js
+++ b/Source/Scene/Cesium3DTile.js
@@ -988,7 +988,16 @@ define([
var showVolume = tileset.debugShowBoundingVolume || (tileset.debugShowContentBoundingVolume && !hasContentBoundingVolume);
if (showVolume) {
- var color = tile._finalResolution ? ((hasContentBoundingVolume || empty) ? Color.WHITE : Color.RED) : Color.YELLOW;
+ var color;
+ if (!tile._finalResolution) {
+ color = Color.YELLOW;
+ } else if (hasContentBoundingVolume) {
+ color = Color.WHITE;
+ } else if (empty) {
+ color = Color.GREEN;
+ } else {
+ color = Color.RED;
+ }
if (!defined(tile._debugBoundingVolume)) {
tile._debugBoundingVolume = tile._boundingVolume.createDebugVolume(color);
}
From a0d5c5c4116031e04cb028e02bf40dd5b9d64398 Mon Sep 17 00:00:00 2001
From: Sean Lilley
Date: Wed, 4 Jul 2018 20:08:03 -0400
Subject: [PATCH 45/53] Comments and .peek
---
Source/Core/ManagedArray.js | 17 ++++++++++++++---
Source/Scene/Cesium3DTilesetTraversal.js | 6 +++---
Specs/Core/ManagedArraySpec.js | 10 ++++++++++
3 files changed, 27 insertions(+), 6 deletions(-)
diff --git a/Source/Core/ManagedArray.js b/Source/Core/ManagedArray.js
index fed116228452..8c63178ed689 100644
--- a/Source/Core/ManagedArray.js
+++ b/Source/Core/ManagedArray.js
@@ -74,9 +74,9 @@ define([
* Sets the element at an index. Resizes the array if index is greater than the length of the array.
*
* @param {Number} index The index to set.
- * @param {*} value The value to set at index.
+ * @param {*} element The element to set at index.
*/
- ManagedArray.prototype.set = function(index, value) {
+ ManagedArray.prototype.set = function(index, element) {
//>>includeStart('debug', pragmas.debug);
Check.typeOf.number('index', index);
//>>includeEnd('debug');
@@ -84,11 +84,22 @@ define([
if (index >= this.length) {
this.length = index + 1;
}
- this._array[index] = value;
+ this._array[index] = element;
+ };
+
+ /**
+ * Returns the last element in the array without modifying the array.
+ *
+ * @returns {*} The last element in the array.
+ */
+ ManagedArray.prototype.peek = function() {
+ return this._array[this._length - 1];
};
/**
* Push an element into the array.
+ *
+ * @param {*} element The element to push.
*/
ManagedArray.prototype.push = function(element) {
var index = this.length++;
diff --git a/Source/Scene/Cesium3DTilesetTraversal.js b/Source/Scene/Cesium3DTilesetTraversal.js
index 6888ccc8ee48..ae8e6a09525a 100644
--- a/Source/Scene/Cesium3DTilesetTraversal.js
+++ b/Source/Scene/Cesium3DTilesetTraversal.js
@@ -222,7 +222,7 @@ define([
var useParentScreenSpaceError = defined(parent) && (!skipLevelOfDetail(tileset) || (tile._screenSpaceError === 0.0));
var screenSpaceError = useParentScreenSpaceError ? parent._screenSpaceError : tile._screenSpaceError;
var rootScreenSpaceError = tileset._root._screenSpaceError;
- return rootScreenSpaceError - screenSpaceError; // Map higher SSE to lower values (e.g. 0.0 is highest priority)
+ return rootScreenSpaceError - screenSpaceError; // Map higher SSE to lower values (e.g. root tile is highest priority)
}
}
@@ -240,7 +240,6 @@ define([
return 0.0;
}
- // Avoid divide by zero when viewer is inside the tile
var camera = frameState.camera;
var frustum = camera.frustum;
var context = frameState.context;
@@ -255,6 +254,7 @@ define([
var pixelSize = Math.max(frustum.top - frustum.bottom, frustum.right - frustum.left) / Math.max(width, height);
error = geometricError / pixelSize;
} else {
+ // Avoid divide by zero when viewer is inside the tile
var distance = Math.max(tile._distanceToCamera, CesiumMath.EPSILON7);
var sseDenominator = camera.frustum.sseDenominator;
error = (geometricError * height) / (distance * sseDenominator);
@@ -587,7 +587,7 @@ define([
selectionTraversal.ancestorStackMaximumLength = Math.max(selectionTraversal.ancestorStackMaximumLength, ancestorStack.length);
if (ancestorStack.length > 0) {
- var waitingTile = ancestorStack.get(ancestorStack.length - 1);
+ var waitingTile = ancestorStack.peek();
if (waitingTile._stackLength === stack.length) {
ancestorStack.pop();
if (waitingTile === lastAncestor) {
diff --git a/Specs/Core/ManagedArraySpec.js b/Specs/Core/ManagedArraySpec.js
index 94c1c5b32a38..c050bb8f6013 100644
--- a/Specs/Core/ManagedArraySpec.js
+++ b/Specs/Core/ManagedArraySpec.js
@@ -55,6 +55,16 @@ defineSuite([
expect(array.length).toEqual(6);
});
+ it('peeks at the last element of the array', function() {
+ var array = new ManagedArray();
+ expect(array.peek()).toBeUndefined();
+ array.push(0);
+ expect(array.peek()).toBe(0);
+ array.push(1);
+ array.push(2);
+ expect(array.peek()).toBe(2);
+ });
+
it('can push values', function() {
var array = new ManagedArray();
var length = 10;
From 6a8fcfab3c2469deec7f54dc680094eaf43285b0 Mon Sep 17 00:00:00 2001
From: Sean Lilley
Date: Wed, 4 Jul 2018 20:24:37 -0400
Subject: [PATCH 46/53] Updated CHANGES.md
---
CHANGES.md | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/CHANGES.md b/CHANGES.md
index 04cd837bf343..3c329a4fbe5c 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,6 +1,16 @@
Change Log
==========
+### 1.48 2018-08-01
+
+##### Fixes :wrench:
+* Several performance improvements and fixes to the 3D Tiles traversal code. [#6390](https://github.com/AnalyticalGraphicsInc/cesium/pull/6390)
+ * Improved load performance when `skipLevelOfDetail` is false.
+ * Fixed a bug that caused some skipped tiles to load when `skipLevelOfDetail` is true.
+ * Fixed pick statistics in the 3D Tiles Inspector.
+ * Fixed drawing of debug labels for external tilesets.
+ * Fixed drawing of debug outlines for empty tiles.
+
### 1.47 - 2018-07-02
##### Breaking Changes :mega:
From 270599616b7a24f7b0b4e0ecc83fabf681264871 Mon Sep 17 00:00:00 2001
From: Sean Lilley
Date: Wed, 4 Jul 2018 20:56:12 -0400
Subject: [PATCH 47/53] Remove RequestQueue code
---
Source/Core/RequestQueue.js | 186 ----------------------------
Source/Core/RequestScheduler.js | 82 +++++++------
Source/Scene/GlobeSurfaceTile.js | 6 -
Specs/Core/HeapSpec.js | 174 ++++++++++++++++++++++++++
Specs/Core/RequestQueueSpec.js | 189 -----------------------------
Specs/Core/RequestSchedulerSpec.js | 38 +++---
6 files changed, 234 insertions(+), 441 deletions(-)
delete mode 100644 Source/Core/RequestQueue.js
create mode 100644 Specs/Core/HeapSpec.js
delete mode 100644 Specs/Core/RequestQueueSpec.js
diff --git a/Source/Core/RequestQueue.js b/Source/Core/RequestQueue.js
deleted file mode 100644
index 169483d3881c..000000000000
--- a/Source/Core/RequestQueue.js
+++ /dev/null
@@ -1,186 +0,0 @@
-define([
- './Check',
- './defineProperties'
- ], function(
- Check,
- defineProperties) {
- 'use strict';
-
- /**
- * Priority queue for the {@link RequestScheduler} implemented as a sorted array.
- * The request with the highest priority is placed at index 0 and the request
- * with lowest priority is placed at index length - 1
.
- *
- * A lower request.priority
value indicates that the request has higher priority. See {@link Request#priority}.
- *
- *
- * @alias RequestQueue
- * @constructor
- * @private
- *
- * @param {Number} maximumLength The maximum length of the queue.
- */
- function RequestQueue(maximumLength) {
- //>>includeStart('debug', pragmas.debug);
- Check.typeOf.number('maximumLength', maximumLength);
- //>>includeEnd('debug');
-
- this._array = new Array(maximumLength);
- this._length = 0;
- this._maximumLength = maximumLength;
- }
-
- defineProperties(RequestQueue.prototype, {
- /**
- * Gets the length of the queue.
- *
- * @memberof RequestQueue.prototype
- *
- * @type {Number}
- * @readonly
- */
- length : {
- get : function() {
- return this._length;
- }
- }
- });
-
- /**
- * Get the request at the given index.
- *
- * @param {Number} index The index of the request.
- *
- * @return {Request} The request at the given index.
- */
- RequestQueue.prototype.get = function(index) {
- //>>includeStart('debug', pragmas.debug);
- Check.typeOf.number.greaterThanOrEquals('index', index, 0);
- Check.typeOf.number.lessThan('index', index, this._length);
- //>>includeEnd('debug');
- return this._array[index];
- };
-
- /**
- * Insert a request into the queue. If the length would grow greater than the maximum length
- * of the queue, the lowest priority request is removed and returned.
- *
- * @param {Request} request The request to insert.
- *
- * @return {Request|undefined} The request that was removed from the queue if the queue is at full capacity.
- */
- RequestQueue.prototype.insert = function(request) {
- //>>includeStart('debug', pragmas.debug);
- Check.defined('request', request);
- //>>includeEnd('debug');
-
- var array = this._array;
- var previousLength = this._length;
- var length = this._length;
- var maximumLength = this._maximumLength;
-
- if (length < maximumLength)
- {
- ++this._length;
- }
-
- if (previousLength === 0)
- {
- array[0] = request;
- return;
- }
-
- var removedRequest;
- var lastIndex = previousLength - 1;
-
- if (previousLength === maximumLength) {
- var lastRequest = array[lastIndex];
- if (request.priority >= lastRequest.priority) {
- // The array is full and the priority value of this request is too high to be inserted.
- return request;
- }
- // The array is full and the inserted request pushes off the last request
- removedRequest = lastRequest;
- --lastIndex;
- }
-
- while (lastIndex >= 0 && request.priority < array[lastIndex].priority) {
- array[lastIndex + 1] = array[lastIndex]; // Shift element to the right
- --lastIndex;
- }
- array[lastIndex + 1] = request;
-
- return removedRequest;
- };
-
- /**
- * Call the given function for each request in the queue.
- *
- * @type {RequestQueue~ForEachCallback} The function to call.
- */
- RequestQueue.prototype.forEach = function(callback) {
- //>>includeStart('debug', pragmas.debug);
- Check.typeOf.func('callback', callback);
- //>>includeEnd('debug');
-
- var array = this._array;
- var length = this._length;
- for (var i = 0; i < length; ++i) {
- callback(array[i]);
- }
- };
-
- /**
- * Sorts the queue.
- */
- RequestQueue.prototype.sort = function() {
- var array = this._array;
- var length = this._length;
-
- // Use insertion sort since our array is small and likely to be mostly sorted already.
- // Additionally length may be smaller than the array's actual length, so calling array.sort will lead to incorrect results for uninitialized values.
- for (var i = 1; i < length; ++i) {
- var j = i;
- while ((j > 0) && (array[j - 1].priority > array[j].priority)) {
- var temp = array[j - 1];
- array[j - 1] = array[j];
- array[j] = temp;
- --j;
- }
- }
- };
-
- /**
- * Remove length
number of requests from the top of the queue.
- *
- * @param {Number} length The number of requests to remove.
- */
- RequestQueue.prototype.remove = function(length) {
- //>>includeStart('debug', pragmas.debug);
- Check.typeOf.number.greaterThanOrEquals('length', length, 0);
- Check.typeOf.number.lessThanOrEquals('length', length, this._length);
- //>>includeEnd('debug');
- if (length === 0) {
- return;
- }
- if (length === this._length) {
- this._length = 0;
- return;
- }
-
- // Shift remaining requests back to the left
- var array = this._array;
- for (var i = length; i < this._length; ++i) {
- array[i - length] = array[i];
- }
- this._length -= length;
- };
-
- /**
- * The callback to use in forEach.
- * @callback RequestQueue~ForEachCallback
- * @param {Request} request The request.
- */
-
- return RequestQueue;
-});
diff --git a/Source/Core/RequestScheduler.js b/Source/Core/RequestScheduler.js
index 8c307663d3a4..6fd455b58007 100644
--- a/Source/Core/RequestScheduler.js
+++ b/Source/Core/RequestScheduler.js
@@ -6,9 +6,9 @@ define([
'./defined',
'./defineProperties',
'./Event',
+ './Heap',
'./isBlobUri',
'./isDataUri',
- './RequestQueue',
'./RequestState'
], function(
Uri,
@@ -18,12 +18,16 @@ define([
defined,
defineProperties,
Event,
+ Heap,
isBlobUri,
isDataUri,
- RequestQueue,
RequestState) {
'use strict';
+ function sortRequests(a, b) {
+ return a.priority - b.priority;
+ }
+
var statistics = {
numberOfAttemptedRequests : 0,
numberOfActiveRequests : 0,
@@ -33,8 +37,12 @@ define([
numberOfActiveRequestsEver : 0
};
- var requestQueueLength = 20;
- var requestQueue = new RequestQueue(requestQueueLength);
+ var priorityHeapLength = 20;
+ var requestHeap = new Heap({
+ comparator : sortRequests
+ });
+ requestHeap.maximumLength = priorityHeapLength;
+ requestHeap.reserve(priorityHeapLength);
var activeRequests = [];
var numberOfActiveRequestsByServer = {};
@@ -115,28 +123,29 @@ define([
},
/**
- * The maximum length of the request queue. This limits the number of requests that are sorted by priority. Only applies to requests that are not yet active.
+ * The maximum size of the priority heap. This limits the number of requests that are sorted by priority. Only applies to requests that are not yet active.
*
* @memberof RequestScheduler
*
* @type {Number}
* @default 20
- *
- * @private
*/
- requestQueueLength : {
+ priorityHeapLength : {
get : function() {
- return requestQueueLength;
+ return priorityHeapLength;
},
set : function(value) {
- // Cancel all requests and resize the queue
- var length = requestQueue.length;
- for (var i = 0; i < length; ++i) {
- var request = requestQueue.get(i);
- cancelRequest(request);
+ // If the new length shrinks the heap, need to cancel some of the requests.
+ // Since this value is not intended to be tweaked regularly it is fine to just cancel the high priority requests.
+ if (value < priorityHeapLength) {
+ while (requestHeap.length > value) {
+ var request = requestHeap.pop();
+ cancelRequest(request);
+ }
}
- requestQueue = new RequestQueue(value);
- RequestScheduler.requestQueue = requestQueue;
+ priorityHeapLength = value;
+ requestHeap.maximumLength = value;
+ requestHeap.reserve(value);
}
}
});
@@ -245,19 +254,21 @@ define([
}
activeRequests.length -= removeCount;
- // Update priority of issued requests and resort the queue
- requestQueue.forEach(updatePriority);
- requestQueue.sort();
+ // Update priority of issued requests and resort the heap
+ var issuedRequests = requestHeap.internalArray;
+ var issuedLength = requestHeap.length;
+ for (i = 0; i < issuedLength; ++i) {
+ updatePriority(issuedRequests[i]);
+ }
+ requestHeap.resort();
// Get the number of open slots and fill with the highest priority requests.
// Un-throttled requests are automatically added to activeRequests, so activeRequests.length may exceed maximumRequests
var openSlots = Math.max(RequestScheduler.maximumRequests - activeRequests.length, 0);
var filledSlots = 0;
- var processedRequests = 0;
- var totalRequests = requestQueue.length;
- while (filledSlots < openSlots && processedRequests < totalRequests) {
- // Loop until all open slots are filled or the queue becomes empty
- request = requestQueue.get(processedRequests++);
+ while (filledSlots < openSlots && requestHeap.length > 0) {
+ // Loop until all open slots are filled or the heap becomes empty
+ request = requestHeap.pop();
if (request.cancelled) {
// Request was explicitly cancelled
cancelRequest(request);
@@ -273,7 +284,6 @@ define([
startRequest(request);
++filledSlots;
}
- requestQueue.remove(processedRequests);
updateStatistics();
};
@@ -346,9 +356,10 @@ define([
return undefined;
}
- // Insert into the priority queue and see if a request was bumped off. If this request is the lowest priority it will be returned.
+ // Insert into the priority heap and see if a request was bumped off. If this request is the lowest
+ // priority it will be returned.
updatePriority(request);
- var removedRequest = requestQueue.insert(request);
+ var removedRequest = requestHeap.insert(request);
if (defined(removedRequest)) {
if (removedRequest === request) {
@@ -398,19 +409,12 @@ define([
* @private
*/
RequestScheduler.clearForSpecs = function() {
- var request;
- var length;
- var i;
-
- length = requestQueue.length;
- for (i = 0; i < length; ++i) {
- request = requestQueue.get(i);
+ while (requestHeap.length > 0) {
+ var request = requestHeap.pop();
cancelRequest(request);
}
- requestQueue.remove(length);
-
- length = activeRequests.length;
- for (i = 0; i < length; ++i) {
+ var length = activeRequests.length;
+ for (var i = 0; i < length; ++i) {
cancelRequest(activeRequests[i]);
}
activeRequests.length = 0;
@@ -439,7 +443,7 @@ define([
*
* @private
*/
- RequestScheduler.requestQueue = requestQueue;
+ RequestScheduler.requestHeap = requestHeap;
return RequestScheduler;
});
diff --git a/Source/Scene/GlobeSurfaceTile.js b/Source/Scene/GlobeSurfaceTile.js
index efa1ab9f40bd..46bba1be5100 100644
--- a/Source/Scene/GlobeSurfaceTile.js
+++ b/Source/Scene/GlobeSurfaceTile.js
@@ -7,7 +7,6 @@ define([
'../Core/defineProperties',
'../Core/IntersectionTests',
'../Core/PixelFormat',
- '../Core/Rectangle',
'../Renderer/PixelDatatype',
'../Renderer/Sampler',
'../Renderer/Texture',
@@ -29,7 +28,6 @@ define([
defineProperties,
IntersectionTests,
PixelFormat,
- Rectangle,
PixelDatatype,
Sampler,
Texture,
@@ -256,10 +254,6 @@ define([
function createPriorityFunction(surfaceTile, frameState) {
return function() {
- if (Rectangle.contains(surfaceTile.tileBoundingRegion.rectangle, frameState.camera.positionCartographic)) {
- // If the camera is inside this tile's bounding region treat it as highest priority
- return 0.0;
- }
return surfaceTile.tileBoundingRegion.distanceToCamera(frameState);
};
}
diff --git a/Specs/Core/HeapSpec.js b/Specs/Core/HeapSpec.js
new file mode 100644
index 000000000000..1b77e269652e
--- /dev/null
+++ b/Specs/Core/HeapSpec.js
@@ -0,0 +1,174 @@
+defineSuite([
+ 'Core/Heap'
+ ], function(
+ Heap) {
+ 'use strict';
+
+ var length = 100;
+
+ function checkHeap(heap, comparator) {
+ var array = heap.internalArray;
+ var pass = true;
+ var length = heap.length;
+ for (var i = 0; i < length; ++i) {
+ var left = 2 * (i + 1) - 1;
+ var right = 2 * (i + 1);
+ if (left < heap.length) {
+ pass = pass && (comparator(array[i], array[left]) <= 0);
+ }
+ if (right < heap.length) {
+ pass = pass && (comparator(array[i], array[right]) <= 0);
+ }
+ }
+ return pass;
+ }
+
+ // min heap
+ function comparator(a, b) {
+ return a - b;
+ }
+
+ it('maintains heap property on insert', function() {
+ var heap = new Heap({
+ comparator : comparator
+ });
+ var pass = true;
+ for (var i = 0; i < length; ++i) {
+ heap.insert(Math.random());
+ pass = pass && checkHeap(heap, comparator);
+ }
+
+ expect(pass).toBe(true);
+ });
+
+ it('maintains heap property on pop', function() {
+ var heap = new Heap({
+ comparator : comparator
+ });
+ var i;
+ for (i = 0; i < length; ++i) {
+ heap.insert(Math.random());
+ }
+ var pass = true;
+ for (i = 0; i < length; ++i) {
+ heap.pop();
+ pass = pass && checkHeap(heap, comparator);
+ }
+ expect(pass).toBe(true);
+ });
+
+ it('limited by maximum length', function() {
+ var heap = new Heap({
+ comparator : comparator
+ });
+ heap.maximumLength = length / 2;
+ var pass = true;
+ for (var i = 0; i < length; ++i) {
+ heap.insert(Math.random());
+ pass = pass && checkHeap(heap, comparator);
+ }
+ expect(pass).toBe(true);
+ expect(heap.length <= heap.maximumLength).toBe(true);
+ // allowed one extra slot for swapping
+ expect(heap.internalArray.length).toBeLessThanOrEqualTo(heap.maximumLength + 1);
+ });
+
+ it('pops in sorted order', function() {
+ var heap = new Heap({
+ comparator : comparator
+ });
+ var i;
+ for (i = 0; i < length; ++i) {
+ heap.insert(Math.random());
+ }
+ var curr = heap.pop();
+ var pass = true;
+ for (i = 0; i < length - 1; ++i) {
+ var next = heap.pop();
+ pass = pass && (comparator(curr, next) <= 0);
+ curr = next;
+ }
+ expect(pass).toBe(true);
+ });
+
+ it('insert returns the removed element when maximumLength is set', function() {
+ var heap = new Heap({
+ comparator : comparator
+ });
+ heap.maximumLength = length;
+
+ var i;
+ var max = 0.0;
+ var min = 1.0;
+ var values = new Array(length);
+ for (i = 0; i < length; ++i) {
+ var value = Math.random();
+ max = Math.max(max, value);
+ min = Math.min(min, value);
+ values[i] = value;
+ }
+
+ // Push 99 values
+ for (i = 0; i < length - 1; ++i) {
+ heap.insert(values[i]);
+ }
+
+ // Push 100th, nothing is removed so it returns undefined
+ var removed = heap.insert(values[length - 1]);
+ expect(removed).toBeUndefined();
+
+ // Insert value, an element is removed
+ removed = heap.insert(max - 0.1);
+ expect(removed).toBeDefined();
+
+ // If this value is the least priority it will be returned
+ removed = heap.insert(max + 0.1);
+ expect(removed).toBe(max + 0.1);
+ });
+
+ it('resort', function() {
+ function comparator(a, b) {
+ return a.distance - b.distance;
+ }
+
+ var i;
+ var heap = new Heap({
+ comparator : comparator
+ });
+ for (i = 0; i < length; ++i) {
+ heap.insert({
+ distance : i / (length - 1),
+ id : i
+ });
+ }
+
+ // Check that elements are initially sorted
+ var element;
+ var elements = [];
+ var currentId = 0;
+ while (heap.length > 0) {
+ element = heap.pop();
+ elements.push(element);
+ expect(element.id).toBeGreaterThanOrEqualTo(currentId);
+ currentId = element.id;
+ }
+
+ // Add back into heap
+ for (i = 0; i < length; ++i) {
+ heap.insert(elements[i]);
+ }
+
+ // Invert priority
+ for (i = 0; i < length; ++i) {
+ elements[i].distance = 1.0 - elements[i].distance;
+ }
+
+ // Resort and check the the elements are popped in the opposite order now
+ heap.resort();
+ while (heap.length > 0) {
+ element = heap.pop();
+ expect(element.id).toBeLessThanOrEqualTo(currentId);
+ currentId = element.id;
+ }
+ });
+});
diff --git a/Specs/Core/RequestQueueSpec.js b/Specs/Core/RequestQueueSpec.js
deleted file mode 100644
index a7bb51107e7e..000000000000
--- a/Specs/Core/RequestQueueSpec.js
+++ /dev/null
@@ -1,189 +0,0 @@
-defineSuite([
- 'Core/RequestQueue',
- 'Core/Math',
- 'Core/Request'
- ], function(
- RequestQueue,
- CesiumMath,
- Request) {
- 'use strict';
-
- var length = 20;
-
- function createRequest(distance) {
- return new Request({
- priority : distance
- });
- }
-
- function isSorted(queue) {
- var distance = Number.NEGATIVE_INFINITY;
- for (var i = 0; i < queue.length; ++i) {
- var requestDistance = queue.get(i).priority;
- if (requestDistance < distance) {
- return false;
- }
-
- distance = requestDistance;
- }
- return true;
- }
-
- it('sets initial values', function() {
- var queue = new RequestQueue(length);
- expect(queue._array.length).toBe(length);
- expect(queue._length).toBe(0);
- expect(queue._maximumLength).toBe(length);
- });
-
- it('gets length', function() {
- var queue = new RequestQueue(length);
- expect(queue.length).toBe(0);
- queue.insert(createRequest(1.0));
- expect(queue.length).toBe(1);
- });
-
- it('get', function() {
- var queue = new RequestQueue(length);
- queue.insert(createRequest(1));
- queue.insert(createRequest(0));
- expect(queue.get(0).priority).toBe(0);
- expect(queue.get(1).priority).toBe(1);
- });
-
- it('insert', function() {
- var removedRequest;
- var request;
- var i;
-
- CesiumMath.setRandomNumberSeed(0.0);
- var distances = new Array(length);
- for (i = 0; i < length; ++i) {
- distances[i] = CesiumMath.nextRandomNumber();
- }
- distances.sort();
- var lowestDistance = distances[0];
- var highestDistance = distances[distances.length - 1];
-
- var queue = new RequestQueue(length);
- for (i = 0; i < length; ++i) {
- removedRequest = queue.insert(createRequest(distances[i]));
- expect(removedRequest).toBeUndefined(); // Requests are not removed until the queue is full
- }
-
- expect(isSorted(queue)).toBe(true);
-
- request = createRequest(highestDistance);
- expect(queue.insert(request)).toBe(request);
-
- request = createRequest(highestDistance + 1.0);
- expect(queue.insert(request)).toBe(request);
-
- request = createRequest(lowestDistance);
- expect(queue.insert(request).priority).toBe(highestDistance);
- expect(queue.get(0).priority).toBe(lowestDistance);
- expect(queue.get(1).priority).toBe(lowestDistance);
- expect(queue.get(2).priority).toBeGreaterThan(lowestDistance);
-
- expect(isSorted(queue)).toBe(true);
- });
-
- it('forEach', function() {
- var total = 0;
- var queue = new RequestQueue(length);
- for (var i = 0; i < length; ++i) {
- queue.insert(createRequest(1));
- }
- queue.forEach(function(request) {
- total += request.priority;
- });
- expect(total).toBe(length);
- });
-
- it('sort', function() {
- var i;
- CesiumMath.setRandomNumberSeed(0.0);
- var queue = new RequestQueue(length);
- for (i = 0; i < length / 2; ++i) {
- queue.insert(createRequest(CesiumMath.nextRandomNumber()));
- }
- queue.forEach(function(request) {
- request.priority = CesiumMath.nextRandomNumber();
- });
- expect(isSorted(queue)).toBe(false);
- queue.sort();
- expect(isSorted(queue)).toBe(true);
- });
-
- it('remove', function() {
- var queue = new RequestQueue(length);
- for (var i = 0; i < length; ++i) {
- queue.insert(createRequest(i));
- }
- queue.remove(0);
- expect(queue.get(0).priority).toBe(0);
- expect(queue.get(1).priority).toBe(1);
- expect(queue.length).toBe(length);
-
- queue.remove(1);
- expect(queue.get(0).priority).toBe(1);
- expect(queue.get(1).priority).toBe(2);
- expect(queue.length).toBe(length - 1);
-
- queue.remove(2);
- expect(queue.get(0).priority).toBe(3);
- expect(queue.get(1).priority).toBe(4);
- expect(queue.length).toBe(length - 3);
-
- queue.remove(17);
- expect(queue.length).toBe(0);
- });
-
- it('throws if maximumLength is undefined', function() {
- expect(function() {
- return new RequestQueue();
- }).toThrowDeveloperError();
- });
-
- it('throws if get index is out of range', function() {
- expect(function() {
- var queue = new RequestQueue(length);
- queue.get(0);
- }).toThrowDeveloperError();
-
- expect(function() {
- var queue = new RequestQueue(length);
- queue.insert(createRequest(0.0));
- queue.get(1);
- }).toThrowDeveloperError();
-
- expect(function() {
- var queue = new RequestQueue(length);
- queue.insert(createRequest(0.0));
- queue.get(-1);
- }).toThrowDeveloperError();
- });
-
- it('throws if forEach callback is not a function', function() {
- expect(function() {
- var queue = new RequestQueue(length);
- queue.forEach();
- }).toThrowDeveloperError();
- expect(function() {
- var queue = new RequestQueue(length);
- queue.forEach(5);
- }).toThrowDeveloperError();
- });
-
- it('throws if remove length is out of range', function() {
- expect(function() {
- var queue = new RequestQueue(length);
- queue.remove(1);
- }).toThrowDeveloperError();
-
- expect(function() {
- var queue = new RequestQueue(length);
- queue.remove(-1);
- }).toThrowDeveloperError();
- });
-});
diff --git a/Specs/Core/RequestSchedulerSpec.js b/Specs/Core/RequestSchedulerSpec.js
index b5b3b965cc16..55f49fc1492e 100644
--- a/Specs/Core/RequestSchedulerSpec.js
+++ b/Specs/Core/RequestSchedulerSpec.js
@@ -12,13 +12,13 @@ defineSuite([
var originalMaximumRequests;
var originalMaximumRequestsPerServer;
- var originalRequestQueueLength;
+ var originalPriorityHeapLength;
var originalRequestsByServer;
beforeAll(function() {
originalMaximumRequests = RequestScheduler.maximumRequests;
originalMaximumRequestsPerServer = RequestScheduler.maximumRequestsPerServer;
- originalRequestQueueLength = RequestScheduler.requestQueueLength;
+ originalPriorityHeapLength = RequestScheduler.priorityHeapLength;
originalRequestsByServer = RequestScheduler.requestsByServer;
});
@@ -30,7 +30,7 @@ defineSuite([
afterEach(function() {
RequestScheduler.maximumRequests = originalMaximumRequests;
RequestScheduler.maximumRequestsPerServer = originalMaximumRequestsPerServer;
- RequestScheduler.requestQueueLength = originalRequestQueueLength;
+ RequestScheduler.priorityHeapLength = originalPriorityHeapLength;
RequestScheduler.requestsByServer = originalRequestsByServer;
});
@@ -208,7 +208,7 @@ defineSuite([
}
});
- it('honors requestQueue length', function() {
+ it('honors priorityHeapLength', function() {
var deferreds = [];
var requests = [];
@@ -229,23 +229,22 @@ defineSuite([
return request;
}
- RequestScheduler.requestQueueLength = 1;
+ RequestScheduler.priorityHeapLength = 1;
var firstRequest = createRequest(0.0);
var promise = RequestScheduler.request(firstRequest);
expect(promise).toBeDefined();
promise = RequestScheduler.request(createRequest(1.0));
expect(promise).toBeUndefined();
- RequestScheduler.requestQueueLength = 3;
- promise = RequestScheduler.request(createRequest(1.0));
+ RequestScheduler.priorityHeapLength = 3;
promise = RequestScheduler.request(createRequest(2.0));
promise = RequestScheduler.request(createRequest(3.0));
expect(promise).toBeDefined();
promise = RequestScheduler.request(createRequest(4.0));
expect(promise).toBeUndefined();
- // The requests are cancelled when the queue length changes
- RequestScheduler.requestQueueLength = 2;
+ // A request is cancelled to accommodate the new heap length
+ RequestScheduler.priorityHeapLength = 2;
expect(firstRequest.state).toBe(RequestState.CANCELLED);
var length = deferreds.length;
@@ -453,7 +452,7 @@ defineSuite([
});
}
- var length = RequestScheduler.requestQueueLength;
+ var length = RequestScheduler.priorityHeapLength;
for (var i = 0; i < length; ++i) {
var priority = Math.random();
RequestScheduler.request(createRequest(priority));
@@ -490,7 +489,7 @@ defineSuite([
var i;
var request;
- var length = RequestScheduler.requestQueueLength;
+ var length = RequestScheduler.priorityHeapLength;
for (i = 0; i < length; ++i) {
var priority = i / (length - 1);
request = createRequest(priority);
@@ -502,31 +501,28 @@ defineSuite([
RequestScheduler.maximumRequests = 0;
RequestScheduler.update();
- var requestQueue = RequestScheduler.requestQueue;
+ var requestHeap = RequestScheduler.requestHeap;
var requests = [];
var currentTestId = 0;
-
- for (i = 0; i < length; ++i) {
- request = requestQueue.get(i);
+ while (requestHeap.length > 0) {
+ request = requestHeap.pop();
requests.push(request);
expect(request.testId).toBeGreaterThanOrEqualTo(currentTestId);
currentTestId = request.testId;
}
- requestQueue.remove(length);
for (i = 0; i < length; ++i) {
- requestQueue.insert(requests[i]);
+ requestHeap.insert(requests[i]);
}
invertPriority = true;
RequestScheduler.update();
- for (i = 0; i < length; ++i) {
- request = requestQueue.get(i);
+ while (requestHeap.length > 0) {
+ request = requestHeap.pop();
expect(request.testId).toBeLessThanOrEqualTo(currentTestId);
currentTestId = request.testId;
}
- requestQueue.remove(length);
});
it('handles low priority requests', function() {
@@ -547,7 +543,7 @@ defineSuite([
var mediumPriority = 0.5;
var lowPriority = 1.0;
- var length = RequestScheduler.requestQueueLength;
+ var length = RequestScheduler.priorityHeapLength;
for (var i = 0; i < length; ++i) {
RequestScheduler.request(createRequest(mediumPriority));
}
From c8c981eded706a60b0e897bcce231c215ec8459e Mon Sep 17 00:00:00 2001
From: Sean Lilley
Date: Fri, 6 Jul 2018 14:10:40 -0400
Subject: [PATCH 48/53] Added visited frame to tile
---
Source/Scene/Cesium3DTile.js | 1 +
Source/Scene/Cesium3DTilesetTraversal.js | 8 ++++++--
2 files changed, 7 insertions(+), 2 deletions(-)
diff --git a/Source/Scene/Cesium3DTile.js b/Source/Scene/Cesium3DTile.js
index 67fdfcb89c32..12eba5f028b1 100644
--- a/Source/Scene/Cesium3DTile.js
+++ b/Source/Scene/Cesium3DTile.js
@@ -326,6 +326,7 @@ define([
this._updatedVisibilityFrame = 0;
this._touchedFrame = 0;
+ this._visitedFrame = 0;
this._selectedFrame = 0;
this._requestedFrame = 0;
this._ancestorWithContent = undefined;
diff --git a/Source/Scene/Cesium3DTilesetTraversal.js b/Source/Scene/Cesium3DTilesetTraversal.js
index ae8e6a09525a..71bf7bf3e8bd 100644
--- a/Source/Scene/Cesium3DTilesetTraversal.js
+++ b/Source/Scene/Cesium3DTilesetTraversal.js
@@ -194,8 +194,12 @@ define([
}
}
- function visitTile(tileset) {
+ function visitTile(tileset, tile, frameState) {
+ if (!tileset._passDirty && (tile._visitedFrame === frameState.frameNumber)) {
+ return;
+ }
++tileset._statistics.visited;
+ tile._visitedFrame = frameState.frameNumber;
}
function touchTile(tileset, tile, frameState) {
@@ -507,7 +511,7 @@ define([
}
}
- visitTile(tileset);
+ visitTile(tileset, tile, frameState);
touchTile(tileset, tile, frameState);
tile._refines = refines;
}
From 1dd5e6828ccd3d5be0fcd5e1a68c4c4983408f58 Mon Sep 17 00:00:00 2001
From: Sean Lilley
Date: Fri, 6 Jul 2018 15:56:22 -0400
Subject: [PATCH 49/53] Fix update visibility code again
---
Source/Scene/Cesium3DTileset.js | 25 ++++--------------------
Source/Scene/Cesium3DTilesetTraversal.js | 16 +++++----------
2 files changed, 9 insertions(+), 32 deletions(-)
diff --git a/Source/Scene/Cesium3DTileset.js b/Source/Scene/Cesium3DTileset.js
index 1dfde692f84b..258eb8a41158 100644
--- a/Source/Scene/Cesium3DTileset.js
+++ b/Source/Scene/Cesium3DTileset.js
@@ -180,8 +180,6 @@ define([
this._selectedTilesToStyle = [];
this._loadTimestamp = undefined;
this._timeSinceLoad = 0.0;
- this._pass = 0;
- this._passDirty = true;
this._cullWithChildrenBounds = defaultValue(options.cullWithChildrenBounds, true);
this._allTilesAdditive = true;
@@ -1808,18 +1806,6 @@ define([
///////////////////////////////////////////////////////////////////////////
- function getPass(frameState) {
- // Convert the passes object to a bitfield to determine when the pass has changed.
- var passState = 0;
- var passes = frameState.passes;
- for (var pass in passes) {
- if (passes.hasOwnProperty(pass)) {
- passState = (passState << 1) | (passes[pass] ? 1 : 0);
- }
- }
- return passState;
- }
-
/**
* Called when {@link Viewer} or {@link CesiumWidget} render the scene to
* get the draw commands needed to render this primitive.
@@ -1849,13 +1835,14 @@ define([
this._timeSinceLoad = Math.max(JulianDate.secondsDifference(frameState.time, this._loadTimestamp) * 1000, 0.0);
- this._skipLevelOfDetail = this.skipLevelOfDetail && !defined(this._classificationType) && !this._disableSkipLevelOfDetail;
+ this._skipLevelOfDetail = this.skipLevelOfDetail && !defined(this._classificationType) && !this._disableSkipLevelOfDetail && !this._allTilesAdditive;
// Do not do out-of-core operations (new content requests, cache removal,
// process new tiles) during the pick pass.
var passes = frameState.passes;
- var isPick = (passes.pick && !passes.render);
- var outOfCore = !isPick;
+ var isRender = passes.render;
+ var isPick = passes.pick;
+ var outOfCore = isRender;
var statistics = this._statistics;
statistics.clear();
@@ -1868,10 +1855,6 @@ define([
this._cache.reset();
}
- var pass = getPass(frameState);
- this._passDirty = this._pass !== pass;
- this._pass = pass;
-
this._requestedTiles.length = 0;
Cesium3DTilesetTraversal.selectTiles(this, frameState);
diff --git a/Source/Scene/Cesium3DTilesetTraversal.js b/Source/Scene/Cesium3DTilesetTraversal.js
index 71bf7bf3e8bd..1131a87955ca 100644
--- a/Source/Scene/Cesium3DTilesetTraversal.js
+++ b/Source/Scene/Cesium3DTilesetTraversal.js
@@ -117,8 +117,7 @@ define([
}
function skipLevelOfDetail(tileset) {
- // Optimization: if all tiles are additive turn off skipLevelOfDetail to save some processing
- return tileset._skipLevelOfDetail && !tileset._allTilesAdditive;
+ return tileset._skipLevelOfDetail;
}
function addEmptyTile(tileset, tile) {
@@ -195,9 +194,6 @@ define([
}
function visitTile(tileset, tile, frameState) {
- if (!tileset._passDirty && (tile._visitedFrame === frameState.frameNumber)) {
- return;
- }
++tileset._statistics.visited;
tile._visitedFrame = frameState.frameNumber;
}
@@ -275,16 +271,12 @@ define([
}
function updateVisibility(tileset, tile, frameState) {
- if (!tileset._passDirty && (tile._updatedVisibilityFrame === frameState.frameNumber)) {
- // Return early if visibility has already been checked during this pass and this frame.
+ if (tile._updatedVisibilityFrame === frameState.frameNumber) {
+ // Return early if visibility has already been checked during the traversal.
// The visibility may have already been checked if the cullWithChildrenBounds optimization is used.
- // Visibility must be checked again if the pass changes since the pick pass uses
- // a smaller frustum than the render pass.
return;
}
- tile._updatedVisibilityFrame = frameState.frameNumber;
-
var parent = tile.parent;
var parentTransform = defined(parent) ? parent.computedTransform : tileset._modelMatrix;
var parentVisibilityPlaneMask = defined(parent) ? parent._visibilityPlaneMask : CullingVolume.MASK_INDETERMINATE;
@@ -296,6 +288,7 @@ define([
tile._visibilityPlaneMask = tile.visibility(frameState, parentVisibilityPlaneMask); // Use parent's plane mask to speed up visibility test
tile._visible = tile._visibilityPlaneMask !== CullingVolume.MASK_OUTSIDE;
tile._inRequestVolume = tile.insideViewerRequestVolume(frameState);
+ tile._updatedVisibilityFrame = frameState.frameNumber;
}
function anyChildrenVisible(tileset, tile, frameState) {
@@ -514,6 +507,7 @@ define([
visitTile(tileset, tile, frameState);
touchTile(tileset, tile, frameState);
tile._refines = refines;
+ tile._updatedVisibilityFrame = 0; // Reset so visibility is checked during the next pass
}
}
From 6e8439019b1a0b74b1161b74f2d00ee33157f2b5 Mon Sep 17 00:00:00 2001
From: Sean Lilley
Date: Fri, 6 Jul 2018 17:04:48 -0400
Subject: [PATCH 50/53] Remove TODO
---
Source/Scene/Cesium3DTile.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Source/Scene/Cesium3DTile.js b/Source/Scene/Cesium3DTile.js
index da9b5673bcb5..4470ea132699 100644
--- a/Source/Scene/Cesium3DTile.js
+++ b/Source/Scene/Cesium3DTile.js
@@ -318,7 +318,7 @@ define([
// Members that are updated every frame for tree traversal and rendering optimizations:
this._distanceToCamera = 0;
- this._centerZDepth = 0; // TODO : remove?
+ this._centerZDepth = 0;
this._screenSpaceError = 0;
this._visibilityPlaneMask = 0;
this._visible = false;
From 8d85e60ef97e9f04d4a531a20192dbc1c5fdbf1c Mon Sep 17 00:00:00 2001
From: Sean Lilley
Date: Fri, 27 Jul 2018 21:21:48 -0400
Subject: [PATCH 51/53] Updates
---
Source/Scene/Cesium3DTile.js | 27 ++++++++++++++++++------
Source/Scene/Cesium3DTilesetTraversal.js | 20 ++++++++----------
2 files changed, 30 insertions(+), 17 deletions(-)
diff --git a/Source/Scene/Cesium3DTile.js b/Source/Scene/Cesium3DTile.js
index fdae1766118b..b1bea8a2c1a1 100644
--- a/Source/Scene/Cesium3DTile.js
+++ b/Source/Scene/Cesium3DTile.js
@@ -262,7 +262,7 @@ define([
this.hasTilesetContent = false;
/**
- * The corresponding node in the cache.
+ * The node in the tileset's LRU cache, used to determine when to unload a tile's content.
*
* See {@link Cesium3DTilesetCache}
*
@@ -465,7 +465,7 @@ define([
*/
contentAvailable : {
get : function() {
- return (this.contentReady && this.hasRenderableContent) || (defined(this._expiredContent) && this._contentState !== Cesium3DTileContentState.FAILED);
+ return (this.contentReady && this.hasRenderableContent) || (defined(this._expiredContent) && !this.contentFailed);
}
},
@@ -520,6 +520,23 @@ define([
}
},
+ /**
+ * Determines if the tile's content failed to load. true
if the tile's
+ * content failed to load; otherwise, false
.
+ *
+ * @memberof Cesium3DTile.prototype
+ *
+ * @type {Boolean}
+ * @readonly
+ *
+ * @private
+ */
+ contentFailed : {
+ get : function() {
+ return this._contentState === Cesium3DTileContentState.FAILED;
+ }
+ },
+
/**
* Gets the promise that will be resolved when the tile's content is ready to process.
* This happens after the content is downloaded but before the content is ready
@@ -1035,12 +1052,10 @@ define([
var color;
if (!tile._finalResolution) {
color = Color.YELLOW;
- } else if (hasContentBoundingVolume) {
- color = Color.WHITE;
} else if (empty) {
- color = Color.GREEN;
+ color = Color.DARKGRAY;
} else {
- color = Color.RED;
+ color = Color.WHITE;
}
if (!defined(tile._debugBoundingVolume)) {
tile._debugBoundingVolume = tile._boundingVolume.createDebugVolume(color);
diff --git a/Source/Scene/Cesium3DTilesetTraversal.js b/Source/Scene/Cesium3DTilesetTraversal.js
index 1131a87955ca..3a27113a282f 100644
--- a/Source/Scene/Cesium3DTilesetTraversal.js
+++ b/Source/Scene/Cesium3DTilesetTraversal.js
@@ -76,7 +76,7 @@ define([
return;
}
- // The SSE of not rendering the tree is small enough that the tree does not need to be rendered
+ // The tileset doesn't meet the SSE requirement, therefore the tree does not need to be rendered
if (getScreenSpaceError(tileset, tileset._geometricError, root, frameState) <= tileset._maximumScreenSpaceError) {
return;
}
@@ -213,17 +213,14 @@ define([
// Replacement tiles are prioritized by screen space error.
// A tileset that has both additive and replacement tiles may not prioritize tiles as effectively since SSE and distance
// are different types of values. Maybe all priorities need to be normalized to 0-1 range.
- var parent = tile.parent;
- var replace = tile.refine === Cesium3DTileRefine.REPLACE;
- var add = tile.refine === Cesium3DTileRefine.ADD;
- if (add) {
+ if (tile.refine === Cesium3DTileRefine.ADD) {
return tile._distanceToCamera;
- } else if (replace) {
- var useParentScreenSpaceError = defined(parent) && (!skipLevelOfDetail(tileset) || (tile._screenSpaceError === 0.0));
- var screenSpaceError = useParentScreenSpaceError ? parent._screenSpaceError : tile._screenSpaceError;
- var rootScreenSpaceError = tileset._root._screenSpaceError;
- return rootScreenSpaceError - screenSpaceError; // Map higher SSE to lower values (e.g. root tile is highest priority)
}
+ var parent = tile.parent;
+ var useParentScreenSpaceError = defined(parent) && (!skipLevelOfDetail(tileset) || (tile._screenSpaceError === 0.0));
+ var screenSpaceError = useParentScreenSpaceError ? parent._screenSpaceError : tile._screenSpaceError;
+ var rootScreenSpaceError = tileset._root._screenSpaceError;
+ return rootScreenSpaceError - screenSpaceError; // Map higher SSE to lower values (e.g. root tile is highest priority)
}
function loadTile(tileset, tile, frameState) {
@@ -478,6 +475,7 @@ define([
if (hasEmptyContent(tile)) {
// Add empty tile just to show its debug bounding volume
+ // If the tile has tileset content load the external tileset
addEmptyTile(tileset, tile, frameState);
loadTile(tileset, tile, frameState);
} else if (add) {
@@ -554,7 +552,7 @@ define([
/**
* Traverse the tree and check if their selected frame is the current frame. If so, add it to a selection queue.
- * Furthermore, this is a preorder traversal so children tiles are selected before ancestor tiles.
+ * This is a preorder traversal so children tiles are selected before ancestor tiles.
*
* The reason for the preorder traversal is so that tiles can easily be marked with their
* selection depth. A tile's _selectionDepth is its depth in the tree where all non-selected tiles are removed.
From 4fc6f7e9652a1535cd37ca4ef1e5014da2495bcc Mon Sep 17 00:00:00 2001
From: Sean Lilley
Date: Fri, 27 Jul 2018 21:41:28 -0400
Subject: [PATCH 52/53] More concise handling of finalResolution
---
Source/Scene/Batched3DModel3DTileContent.js | 3 +--
Source/Scene/Cesium3DTileBatchTable.js | 3 ++-
Source/Scene/Cesium3DTilesetTraversal.js | 11 +++--------
3 files changed, 6 insertions(+), 11 deletions(-)
diff --git a/Source/Scene/Batched3DModel3DTileContent.js b/Source/Scene/Batched3DModel3DTileContent.js
index b89874f33998..02303da0422a 100644
--- a/Source/Scene/Batched3DModel3DTileContent.js
+++ b/Source/Scene/Batched3DModel3DTileContent.js
@@ -482,8 +482,7 @@ define([
// If any commands were pushed, add derived commands
var commandEnd = frameState.commandList.length;
if ((commandStart < commandEnd) && (frameState.passes.render || frameState.passes.pick) && !defined(tileset.classificationType)) {
- var finalResolution = this._tile._finalResolution;
- this._batchTable.addDerivedCommands(frameState, commandStart, finalResolution);
+ this._batchTable.addDerivedCommands(frameState, commandStart);
}
};
diff --git a/Source/Scene/Cesium3DTileBatchTable.js b/Source/Scene/Cesium3DTileBatchTable.js
index f8276570858f..9ad79c24ead0 100644
--- a/Source/Scene/Cesium3DTileBatchTable.js
+++ b/Source/Scene/Cesium3DTileBatchTable.js
@@ -1210,10 +1210,11 @@ define([
OPAQUE_AND_TRANSLUCENT : 2
};
- Cesium3DTileBatchTable.prototype.addDerivedCommands = function(frameState, commandStart, finalResolution) {
+ Cesium3DTileBatchTable.prototype.addDerivedCommands = function(frameState, commandStart) {
var commandList = frameState.commandList;
var commandEnd = commandList.length;
var tile = this._content._tile;
+ var finalResolution = tile._finalResolution;
var tileset = tile._tileset;
var bivariateVisibilityTest = tileset._skipLevelOfDetail && tileset._hasMixedContent && frameState.context.stencilBuffer;
var styleCommandsNeeded = getStyleCommandsNeeded(this);
diff --git a/Source/Scene/Cesium3DTilesetTraversal.js b/Source/Scene/Cesium3DTilesetTraversal.js
index 3a27113a282f..72447fbf8cda 100644
--- a/Source/Scene/Cesium3DTilesetTraversal.js
+++ b/Source/Scene/Cesium3DTilesetTraversal.js
@@ -121,7 +121,6 @@ define([
}
function addEmptyTile(tileset, tile) {
- tile._finalResolution = true;
tileset._emptyTiles.push(tile);
}
@@ -162,7 +161,6 @@ define([
updateTile(tileset, child, frameState);
touchTile(tileset, child, frameState);
selectTile(tileset, child, frameState);
- tile._finalResolution = true;
} else if (child._depth - root._depth < descendantSelectionDepth) {
// Continue traversing, but not too far
stack.push(child);
@@ -176,7 +174,6 @@ define([
if (tile.contentAvailable) {
// The tile can be selected right away and does not require traverseAndSelect
selectTile(tileset, tile, frameState);
- tile._finalResolution = true;
}
return;
}
@@ -332,7 +329,7 @@ define([
tile.updateExpiration();
tile._shouldSelect = false;
- tile._finalResolution = false;
+ tile._finalResolution = true;
tile._ancestorWithContent = undefined;
tile._ancestorWithContentAvailable = undefined;
@@ -586,8 +583,8 @@ define([
var waitingTile = ancestorStack.peek();
if (waitingTile._stackLength === stack.length) {
ancestorStack.pop();
- if (waitingTile === lastAncestor) {
- waitingTile._finalResolution = true;
+ if (waitingTile !== lastAncestor) {
+ waitingTile._finalResolution = false;
}
selectTile(tileset, waitingTile, frameState);
continue;
@@ -609,7 +606,6 @@ define([
if (shouldSelect) {
if (add) {
selectTile(tileset, tile, frameState);
- tile._finalResolution = true;
} else {
tile._selectionDepth = ancestorStack.length;
if (tile._selectionDepth > 0) {
@@ -618,7 +614,6 @@ define([
lastAncestor = tile;
if (!traverse) {
selectTile(tileset, tile, frameState);
- tile._finalResolution = true;
continue;
}
ancestorStack.push(tile);
From 69911031ab97142a41f845be081b86175c38e5ba Mon Sep 17 00:00:00 2001
From: Sean Lilley
Date: Fri, 27 Jul 2018 21:58:14 -0400
Subject: [PATCH 53/53] Remove tile.renderableContent
---
Source/Scene/Cesium3DTile.js | 18 ++----------------
Source/Scene/Cesium3DTileset.js | 9 ++++-----
Specs/Scene/Cesium3DTilesetSpec.js | 1 -
3 files changed, 6 insertions(+), 22 deletions(-)
diff --git a/Source/Scene/Cesium3DTile.js b/Source/Scene/Cesium3DTile.js
index b1bea8a2c1a1..95aafb56ae90 100644
--- a/Source/Scene/Cesium3DTile.js
+++ b/Source/Scene/Cesium3DTile.js
@@ -235,19 +235,6 @@ define([
*/
this.hasEmptyContent = hasEmptyContent;
- /**
- * When true
, the tile's content is renderable.
- *
- * This is false
until the tile's content is loaded.
- *
- *
- * @type {Boolean}
- * @readonly
- *
- * @private
- */
- this.hasRenderableContent = false;
-
/**
* When true
, the tile's content points to an external tileset.
*
@@ -465,7 +452,7 @@ define([
*/
contentAvailable : {
get : function() {
- return (this.contentReady && this.hasRenderableContent) || (defined(this._expiredContent) && !this.contentFailed);
+ return (this.contentReady && !this.hasEmptyContent && !this.hasTilesetContent) || (defined(this._expiredContent) && !this.contentFailed);
}
},
@@ -705,7 +692,6 @@ define([
if (defined(contentFactory)) {
content = contentFactory(tileset, that, that._contentResource, arrayBuffer, 0);
- that.hasRenderableContent = true;
} else {
// The content may be json instead
content = Cesium3DTileContentFactory.json(tileset, that, that._contentResource, arrayBuffer, 0);
@@ -750,7 +736,7 @@ define([
* @private
*/
Cesium3DTile.prototype.unloadContent = function() {
- if (!this.hasRenderableContent) {
+ if (this.hasEmptyContent || this.hasTilesetContent) {
return;
}
diff --git a/Source/Scene/Cesium3DTileset.js b/Source/Scene/Cesium3DTileset.js
index 3c69c64e85ee..4b464bd27e21 100644
--- a/Source/Scene/Cesium3DTileset.js
+++ b/Source/Scene/Cesium3DTileset.js
@@ -693,7 +693,6 @@ define([
that._url = resource.url;
that._basePath = basePath;
- // We don't know the distance of the tileset until tileset JSON file is loaded, so use the default distance for now
return Cesium3DTileset.loadJson(resource);
})
.then(function(tilesetJson) {
@@ -1401,11 +1400,11 @@ define([
}
if (expired) {
- if (tile.hasRenderableContent) {
+ if (tile.hasTilesetContent) {
+ destroySubtree(tileset, tile);
+ } else {
statistics.decrementLoadCounts(tile.content);
--tileset._statistics.numberOfTilesWithContentReady;
- } else if (tile.hasTilesetContent) {
- destroySubtree(tileset, tile);
}
}
@@ -1467,7 +1466,7 @@ define([
return function() {
--tileset._statistics.numberOfTilesProcessing;
- if (tile.hasRenderableContent) {
+ if (!tile.hasTilesetContent) {
// RESEARCH_IDEA: ability to unload tiles (without content) for an
// external tileset when all the tiles are unloaded.
tileset._statistics.incrementLoadCounts(tile.content);
diff --git a/Specs/Scene/Cesium3DTilesetSpec.js b/Specs/Scene/Cesium3DTilesetSpec.js
index e6f76c631fb0..a0cc8e33b911 100644
--- a/Specs/Scene/Cesium3DTilesetSpec.js
+++ b/Specs/Scene/Cesium3DTilesetSpec.js
@@ -1015,7 +1015,6 @@ defineSuite([
var root = tileset._root;
root.refine = Cesium3DTileRefine.REPLACE;
root.hasEmptyContent = false; // mock content
- root.hasRenderableContent = true; // mock content
tileset.maximumScreenSpaceError = 0.0; // Force root tile to always not meet SSE since this is just checking the request volume
// Renders all 5 tiles