From 936944696b52e679574a7776d8a9953a352becad Mon Sep 17 00:00:00 2001 From: Ian Lilley Date: Thu, 17 Dec 2020 16:18:41 -0500 Subject: [PATCH 01/76] double ended priority queue --- Source/Core/DoubleEndedPriorityQueue.js | 418 +++++++++++++++++++++ Specs/Core/DoubleEndedPriorityQueueSpec.js | 417 ++++++++++++++++++++ 2 files changed, 835 insertions(+) create mode 100644 Source/Core/DoubleEndedPriorityQueue.js create mode 100644 Specs/Core/DoubleEndedPriorityQueueSpec.js diff --git a/Source/Core/DoubleEndedPriorityQueue.js b/Source/Core/DoubleEndedPriorityQueue.js new file mode 100644 index 0000000000..76538dd463 --- /dev/null +++ b/Source/Core/DoubleEndedPriorityQueue.js @@ -0,0 +1,418 @@ +import Check from "./Check.js"; +import defined from "./defined.js"; + +/** + * Array-backed min-max heap implementation of a double-ended priority queue. + * This data structure allows for efficient removal of minimum and maximum elements. + * + * @alias DoubleEndedPriorityQueue + * @constructor + * @private + * + * @param {Object} options Object with the following properties: + * @param {DoubleEndedPriorityQueue.ComparatorCallback} options.comparator The comparator to use for the queue. If comparator(a, b) is less than 0, a is lower priority than b. + * @param {Number} [options.maximumLength] The maximum length of the queue. If an element is inserted when the queue is full, the minimum element is removed. By default, the size of the queue is unlimited. + */ +function DoubleEndedPriorityQueue(options) { + //>>includeStart('debug', pragmas.debug); + Check.typeOf.object("options", options); + Check.defined("options.comparator", options.comparator); + if (defined(options.maximumLength)) { + Check.typeOf.number.greaterThanOrEquals( + "options.maximumLength", + options.maximumLength, + 0 + ); + } + //>>includeEnd('debug'); + + this._comparator = options.comparator; + this._maximumLength = options.maximumLength; + this._array = defined(options.maximumLength) + ? new Array(options.maximumLength) + : []; + this._length = 0; +} + +Object.defineProperties(DoubleEndedPriorityQueue.prototype, { + /** + * Gets the number of elements in the queue. + * + * @memberof DoubleEndedPriorityQueue.prototype + * + * @type {Number} + * @readonly + */ + length: { + get: function () { + return this._length; + }, + }, + + /** + * Gets or sets the maximum number of elements in the queue. + * If set to a smaller value than the current length of the queue, the lowest priority elements are removed. + * If an element is inserted when the queue's length equals the maximum length, the minimum element is removed. + * If set to undefined, the size of the queue is unlimited. + * + * @memberof DoubleEndedPriorityQueue.prototype + * + * @type {Number} + * @readonly + */ + maximumLength: { + get: function () { + return this._maximumLength; + }, + set: function (value) { + if (defined(value)) { + //>>includeStart('debug', pragmas.debug); + Check.typeOf.number.greaterThanOrEquals("maximumLength", value, 0); + //>>includeEnd('debug'); + + // Remove elements until the maximum length is met. + while (this._length > value) { + this.removeMinimum(); + } + + // The array size is fixed to the maximum length + this._array.length = value; + } + this._maximumLength = value; + }, + }, + + /** + * Gets the minimum element in the queue. + * If the queue is empty, the result is undefined. + * + * @param {*|undefined} element + */ + minimum: { + get: function () { + var length = this._length; + if (length === 0) { + return undefined; + } + + // The minimum element is always the root + return this._array[0]; + }, + }, + + /** + * Gets the maximum element in the queue. + * If the queue is empty, the result is undefined. + * + * @param {*|undefined} element + */ + maximum: { + get: function () { + var length = this._length; + if (length === 0) { + return undefined; + } + + // If the root has no children, the maximum is the root. + // If the root has one child, the maximum is the child. + if (length <= 2) { + return this._array[length - 1]; + } + + // Otherwise, the maximum is the larger of the root's two children. + var that = this; + return this._array[greaterThan(that, 1, 2) ? 1 : 2]; + }, + }, + + /** + * Gets the internal array. + * + * @memberof DoubleEndedPriorityQueue.prototype + * + * @type {Array} + * @readonly + */ + internalArray: { + get: function () { + return this._array; + }, + }, + + /** + * The comparator used by the queue. + * If comparator(a, b) is less than 0, a is lower priority than b. + * + * @memberof DoubleEndedPriorityQueue.prototype + * + * @type {DoubleEndedPriorityQueue.ComparatorCallback} + * @readonly + */ + comparator: { + get: function () { + return this._comparator; + }, + }, +}); + +/** + * @param {DoubleEndedPriorityQueue} that + * @param {Number} index0 + * @param {Number} index1 + */ +function swap(that, index0, index1) { + var array = that._array; + var temp = array[index0]; + array[index0] = array[index1]; + array[index1] = temp; +} + +/** + * @param {DoubleEndedPriorityQueue} that + * @param {Number} indexA + * @param {Number} indexB + */ +function lessThan(that, indexA, indexB) { + return that._comparator(that._array[indexA], that._array[indexB]) < 0.0; +} + +/** + * @param {DoubleEndedPriorityQueue} that + * @param {Number} indexA + * @param {Number} indexB + */ +function greaterThan(that, indexA, indexB) { + return that._comparator(that._array[indexA], that._array[indexB]) > 0.0; +} + +/** + * @param {DoubleEndedPriorityQueue} that + * @param {Number} index + */ +function pushUp(that, index) { + if (index === 0) { + return; + } + var onMinLevel = Math.floor(Math.log2(index + 1)) % 2 === 0; + var parentIndex = Math.floor((index - 1) / 2); + var lessThanParent = lessThan(that, index, parentIndex); + + // Get the element onto the correct level if it's not already + if (lessThanParent !== onMinLevel) { + swap(that, index, parentIndex); + index = parentIndex; + } + + // Swap element with grandparent as long as it: + // 1) has a grandparent + // 2A) is less than the grandparent when on a min level + // 2B) is greater than the grandparent when on a max level + while (index >= 3) { + var grandparentIndex = Math.floor((Math.floor((index - 1) / 2) - 1) / 2); + if (lessThan(that, index, grandparentIndex) !== lessThanParent) { + break; + } + swap(that, index, grandparentIndex); + index = grandparentIndex; + } +} + +/** + * @param {DoubleEndedPriorityQueue} that + * @param {Number} index + */ +function pushDown(that, index) { + var length = that._length; + var onMinLevel = Math.floor(Math.log2(index + 1)) % 2 === 0; + + // Loop as long as there is a left child. + var leftChildIndex; + while ((leftChildIndex = 2 * index + 1) < length) { + // Find the minimum (or maximum) child or grandchild + var target = leftChildIndex; + + var rightChildIndex = leftChildIndex + 1; + if (rightChildIndex < length) { + if (lessThan(that, rightChildIndex, target) === onMinLevel) { + target = rightChildIndex; + } + var grandChildStart = 2 * leftChildIndex + 1; + var grandChildCount = Math.max(Math.min(length - grandChildStart, 4), 0); + for (var i = 0; i < grandChildCount; i++) { + var grandChildIndex = grandChildStart + i; + if (lessThan(that, grandChildIndex, target) === onMinLevel) { + target = grandChildIndex; + } + } + } + + if (lessThan(that, target, index) === onMinLevel) { + swap(that, target, index); + if (target !== leftChildIndex && target !== rightChildIndex) { + var parentOfGrandchildIndex = Math.floor((target - 1) / 2); + if (greaterThan(that, target, parentOfGrandchildIndex) === onMinLevel) { + swap(that, target, parentOfGrandchildIndex); + } + } + } + + index = target; + } +} + +/** + * Clones the double ended priority queue. + * + * @returns {DoubleEndedPriorityQueue} The cloned double ended priority queue. + */ +DoubleEndedPriorityQueue.prototype.clone = function () { + var that = this; + var maximumLength = that._maximumLength; + var comparator = that._comparator; + var array = that._array; + var length = that._length; + + var result = new DoubleEndedPriorityQueue({ + comparator: comparator, + maximumLength: maximumLength, + }); + + result._length = length; + for (var i = 0; i < length; i++) { + result._array[i] = array[i]; + } + + return result; +}; + +/** + * Removes all elements from the queue. + */ +DoubleEndedPriorityQueue.prototype.reset = function () { + this._length = 0; + + // Dereference elements + var maximumLength = this._maximumLength; + if (defined(maximumLength)) { + // Dereference all elements but keep the array the same size + for (var i = 0; i < maximumLength; i++) { + this._array[i] = undefined; + } + } else { + // Dereference all elements by clearing the array + this._array.length = 0; + } +}; + +/** + * Inserts an element into the queue. + * If the queue is full, the minimum element is removed before the new element is added. + * If the new element is lower or equal priority to the minimum element, it is not added. + * + * @param {*} element + * @returns {*|undefined} The element that was removed to make room for the new element. Only applicable if maximumLength is defined. + */ +DoubleEndedPriorityQueue.prototype.insert = function (element) { + var removedElement; + + var that = this; + var maximumLength = that._maximumLength; + if (defined(maximumLength)) { + if (maximumLength === 0) { + return undefined; + } else if (that._length === maximumLength) { + // It's faster to access the minimum directly instead of calling the getter + // because it avoids the length === 0 check. + var minimumElement = that._array[0]; + if (that._comparator(element, minimumElement) <= 0.0) { + // The element that is being inserted is less than or equal to + // the minimum element, so don't insert anything and exit early. + return undefined; + } + removedElement = that.removeMinimum(); + } + } + + var index = that._length; + that._array[index] = element; + that._length++; + pushUp(that, index); + + return removedElement; +}; + +/** + * Removes the minimum element from the queue and returns it. + * If the queue is empty, the return value is undefined. + * + * @returns {*|undefined} The minimum element, or undefined if the queue is empty. + */ +DoubleEndedPriorityQueue.prototype.removeMinimum = function () { + var that = this; + var length = that._length; + if (length === 0) { + return undefined; + } + + that._length--; + + // The minimum element is always the root + var minimumElement = that._array[0]; + + if (length >= 2) { + that._array[0] = that._array[length - 1]; + pushDown(that, 0); + } + + // Dereference removed element + that._array[length - 1] = undefined; + + return minimumElement; +}; + +/** + * Removes the maximum element from the queue and returns it. + * If the queue is empty, the return value is undefined. + * + * @returns {*|undefined} The maximum element, or undefined if the queue is empty. + */ +DoubleEndedPriorityQueue.prototype.removeMaximum = function () { + var that = this; + var length = that._length; + if (length === 0) { + return undefined; + } + + that._length--; + var maximumElement; + + // If the root has no children, the maximum is the root. + // If the root has one child, the maximum is the child. + if (length <= 2) { + maximumElement = that._array[length - 1]; + } else { + // Otherwise, the maximum is the larger of the root's two children. + var maximumElementIndex = greaterThan(that, 1, 2) ? 1 : 2; + maximumElement = that._array[maximumElementIndex]; + + // Re-balance the heap + that._array[maximumElementIndex] = that._array[length - 1]; + if (length >= 4) { + pushDown(that, maximumElementIndex); + } + } + + // Dereference removed element + that._array[length - 1] = undefined; + + return maximumElement; +}; + +/** + * The comparator to use for the queue. + * @callback DoubleEndedPriorityQueue.ComparatorCallback + * @param {*} a An element in the queue. + * @param {*} b An element in the queue. + * @returns {Number} If the result of the comparison is less than 0, a is lower priority than b. + */ +export default DoubleEndedPriorityQueue; diff --git a/Specs/Core/DoubleEndedPriorityQueueSpec.js b/Specs/Core/DoubleEndedPriorityQueueSpec.js new file mode 100644 index 0000000000..d9d2d12ad0 --- /dev/null +++ b/Specs/Core/DoubleEndedPriorityQueueSpec.js @@ -0,0 +1,417 @@ +import { DoubleEndedPriorityQueue } from "../../Source/Cesium.js"; + +describe("Core/DoubleEndedPriorityQueue", function () { + function comparator(a, b) { + return a - b; + } + + it("constructor throws without options", function () { + expect(function () { + return new DoubleEndedPriorityQueue(); + }).toThrowDeveloperError(); + }); + + it("constructor throws if maximum length is less than zero", function () { + expect(function () { + return new DoubleEndedPriorityQueue({ + comparator: comparator, + maximumLength: -1, + }); + }).toThrowDeveloperError(); + }); + + it("constructor throws without comparator", function () { + expect(function () { + return new DoubleEndedPriorityQueue({ + comparator: undefined, + }); + }).toThrowDeveloperError(); + }); + + it("gets comparator", function () { + var queue = new DoubleEndedPriorityQueue({ + comparator: comparator, + }); + var returnedComparator = queue.comparator; + expect(returnedComparator).toEqual(comparator); + }); + + it("uses different comparator", function () { + var queue = new DoubleEndedPriorityQueue({ + comparator: function (a, b) { + return b - a; + }, + }); + queue.insert(1); + queue.insert(2); + + // The comparator is flipped, so 2 is considered the minimum and 1 is considered the maximum + expect(queue.length).toEqual(2); + expect(queue.minimum).toEqual(2); + expect(queue.maximum).toEqual(1); + }); + + it("checks state of default empty queue", function () { + var queue = new DoubleEndedPriorityQueue({ + comparator: comparator, + }); + + expect(queue.length).toEqual(0); + expect(queue.maximumLength).toBeUndefined(); + expect(queue.internalArray.length).toEqual(0); + expect(queue.minimum).toBeUndefined(); + expect(queue.maximum).toBeUndefined(); + }); + + it("inserts one element into queue", function () { + var queue = new DoubleEndedPriorityQueue({ + comparator: comparator, + }); + + queue.insert(1); + + expect(queue.length).toEqual(1); + expect(queue.internalArray.length).toEqual(1); + expect(queue.minimum).toEqual(1); + expect(queue.maximum).toEqual(1); + }); + + it("inserts two elements into queue", function () { + var queue = new DoubleEndedPriorityQueue({ + comparator: comparator, + }); + + queue.insert(1); + queue.insert(2); + + expect(queue.length).toEqual(2); + expect(queue.internalArray.length).toEqual(2); + expect(queue.minimum).toEqual(1); + expect(queue.maximum).toEqual(2); + }); + + it("inserts three elements into queue", function () { + var queue = new DoubleEndedPriorityQueue({ + comparator: comparator, + }); + + queue.insert(1); + queue.insert(2); + queue.insert(3); + + expect(queue.length).toEqual(3); + expect(queue.internalArray.length).toEqual(3); + expect(queue.minimum).toEqual(1); + expect(queue.maximum).toEqual(3); + }); + + it("inserts four elements into queue", function () { + var queue = new DoubleEndedPriorityQueue({ + comparator: comparator, + }); + + queue.insert(1); + queue.insert(2); + queue.insert(3); + queue.insert(4); + + expect(queue.length).toEqual(4); + expect(queue.internalArray.length).toEqual(4); + expect(queue.minimum).toEqual(1); + expect(queue.maximum).toEqual(4); + }); + + it("insert removes and returns minimum element when the queue is full", function () { + var queue = new DoubleEndedPriorityQueue({ + comparator: comparator, + maximumLength: 1, + }); + + var nothing = queue.insert(1); + var removed = queue.insert(2); + + expect(queue.length).toEqual(1); + expect(queue.maximumLength).toEqual(1); + expect(queue.internalArray.length).toEqual(1); + expect(queue.minimum).toEqual(2); + expect(queue.maximum).toEqual(2); + expect(nothing).toBeUndefined(); + expect(removed).toEqual(1); + }); + + it("insert returns undefined when new element is less than or equal priority to the minimum element and the queue is full", function () { + var queue = new DoubleEndedPriorityQueue({ + comparator: function (a, b) { + return a.value - b.value; + }, + maximumLength: 2, + }); + + queue.insert({ value: 1, id: 0 }); + queue.insert({ value: 2, id: 0 }); + var result1 = queue.insert({ value: 1, id: 1 }); // ignored because equal priority to minimum + var result2 = queue.insert({ value: 0, id: 1 }); // ignored because lower priority than minimum + + expect(queue.length).toEqual(2); + expect(queue.maximumLength).toEqual(2); + expect(queue.internalArray.length).toEqual(2); + expect(queue.minimum.id).toEqual(0); + expect(result1).toBeUndefined(); + expect(result2).toBeUndefined(); + }); + + it("remove and return minimum element", function () { + var queue = new DoubleEndedPriorityQueue({ + comparator: comparator, + }); + + queue.insert(1); + queue.insert(2); + queue.insert(3); + + var minimumValue = queue.removeMinimum(); + + expect(queue.length).toEqual(2); + expect(minimumValue).toEqual(1); + expect(queue.minimum).toEqual(2); + // check that the element was dereferenced + expect(queue.internalArray[2]).toBeUndefined(); + }); + + it("removeMinimum returns undefined when queue is empty", function () { + var queue = new DoubleEndedPriorityQueue({ + comparator: comparator, + }); + + var minimumValue = queue.removeMinimum(); + expect(minimumValue).toBeUndefined(); + }); + + it("remove and return maximum element", function () { + var queue = new DoubleEndedPriorityQueue({ + comparator: comparator, + }); + + queue.insert(1); + queue.insert(2); + queue.insert(3); + + var maximumValue = queue.removeMaximum(); + + expect(queue.length).toEqual(2); + expect(maximumValue).toEqual(3); + expect(queue.maximum).toEqual(2); + // check that the element was dereferenced + expect(queue.internalArray[2]).toBeUndefined(); + }); + + it("removeMaximum returns undefined when queue is empty", function () { + var queue = new DoubleEndedPriorityQueue({ + comparator: comparator, + }); + + var maximumValue = queue.removeMaximum(); + expect(maximumValue).toBeUndefined(); + }); + + it("resets queue", function () { + var queue = new DoubleEndedPriorityQueue({ + comparator: comparator, + }); + queue.insert(1); + queue.insert(2); + queue.reset(); + + expect(queue.length).toEqual(0); + expect(queue.minimum).toBeUndefined(); + expect(queue.maximum).toBeUndefined(); + // check that the elements were dereferenced + expect(queue.internalArray.length).toEqual(0); + }); + + it("resets queue with maximum length", function () { + var queue = new DoubleEndedPriorityQueue({ + comparator: comparator, + maximumLength: 1, + }); + queue.insert(1); + queue.reset(); + + expect(queue.length).toEqual(0); + expect(queue.minimum).toBeUndefined(); + expect(queue.maximum).toBeUndefined(); + // check that the element was dereferenced but the array stayed the same size + expect(queue.internalArray.length).toEqual(1); + expect(queue.internalArray[0]).toBeUndefined(); + }); + + it("creates queue with maximum length of zero", function () { + var queue = new DoubleEndedPriorityQueue({ + comparator: comparator, + maximumLength: 0, + }); + + queue.insert(1); + + expect(queue.length).toEqual(0); + expect(queue.maximumLength).toEqual(0); + expect(queue.internalArray.length).toEqual(0); + expect(queue.minimum).toBeUndefined(); + expect(queue.maximum).toBeUndefined(); + }); + + it("creates queue with maximum length of one", function () { + var queue = new DoubleEndedPriorityQueue({ + comparator: comparator, + maximumLength: 1, + }); + + queue.insert(1); + queue.insert(2); + + expect(queue.length).toEqual(1); + expect(queue.maximumLength).toEqual(1); + expect(queue.internalArray.length).toEqual(1); + expect(queue.minimum).toEqual(2); + expect(queue.maximum).toEqual(2); + }); + + it("throws when maximum length is set to less than zero", function () { + var queue = new DoubleEndedPriorityQueue({ + comparator: comparator, + }); + + expect(function () { + queue.maximumLength = -1; + }).toThrowDeveloperError(); + }); + + it("sets maximum length to undefined", function () { + var queue = new DoubleEndedPriorityQueue({ + comparator: comparator, + }); + + queue.maximumLength = 2; + queue.insert(1); + queue.insert(2); + + queue.maximumLength = undefined; + queue.insert(3); + + expect(queue.length).toEqual(3); + expect(queue.maximumLength).toBeUndefined(); + expect(queue.minimum).toEqual(1); + expect(queue.maximum).toEqual(3); + }); + + it("sets maximum length to less than current length", function () { + var queue = new DoubleEndedPriorityQueue({ + comparator: comparator, + }); + + var maximumLength = 5; + for (var i = 0; i < maximumLength * 2; i++) { + var value = i; + queue.insert(value); + } + queue.maximumLength = maximumLength; + + expect(queue.length).toEqual(maximumLength); + expect(queue.maximumLength).toEqual(maximumLength); + expect(queue.internalArray.length).toEqual(maximumLength); + expect(queue.minimum).toEqual(maximumLength); + expect(queue.maximum).toEqual(maximumLength * 2 - 1); + }); + + function isValidQueue(queue) { + // 1) Remove successive minimum elements from the queue and check if they are sorted correctly + // 2) Remove successive maximum elements from the queue and check if they are sorted correctly + + var minArray = []; + var maxArray = []; + + var minQueue = queue.clone(); + var maxQueue = queue.clone(); + + while (minQueue.length > 0) { + minArray.push(minQueue.removeMinimum()); + } + while (maxQueue.length > 0) { + maxArray.push(maxQueue.removeMaximum()); + } + + if (minQueue.length !== 0 || maxQueue.length !== 0) { + return false; + } + + var i; + for (i = 0; i < minArray.length - 1; i++) { + if (minArray[i] > minArray[i + 1]) { + return false; + } + } + for (i = 0; i < maxArray.length - 1; i++) { + if (maxArray[i] < maxArray[i + 1]) { + return false; + } + } + return true; + } + + it("maintains priority with ascending insertions", function () { + var length = 20; + var maximumLength = 10; + + var queue = new DoubleEndedPriorityQueue({ + comparator: comparator, + }); + queue.maximumLength = maximumLength; + + var pass = true; + for (var i = 0; i < length; ++i) { + var value = i; + queue.insert(value); + pass = pass && isValidQueue(queue); + } + + expect(pass).toBe(true); + }); + + it("maintains priority with descending insertions", function () { + var length = 20; + var maximumLength = 10; + + var queue = new DoubleEndedPriorityQueue({ + comparator: comparator, + }); + queue.maximumLength = maximumLength; + + var pass = true; + for (var i = 0; i < length; ++i) { + var value = length - 1 - i; + queue.insert(value); + pass = pass && isValidQueue(queue); + } + + expect(pass).toBe(true); + }); + + it("maintains priority with random insertions", function () { + var length = 200; + var maximumLength = 100; + + var queue = new DoubleEndedPriorityQueue({ + comparator: comparator, + }); + queue.maximumLength = maximumLength; + + var pass = true; + for (var i = 0; i < length; ++i) { + var value = Math.random(); + queue.insert(value); + pass = pass && isValidQueue(queue); + } + + expect(pass).toBe(true); + }); +}); From 5a1b7bf415dfe71685295f0f38962773647d0308 Mon Sep 17 00:00:00 2001 From: Ian Lilley Date: Thu, 17 Dec 2020 20:17:45 -0500 Subject: [PATCH 02/76] added resort function --- Source/Core/DoubleEndedPriorityQueue.js | 17 ++++++++-- Specs/Core/DoubleEndedPriorityQueueSpec.js | 38 ++++++++++++++++++---- 2 files changed, 46 insertions(+), 9 deletions(-) diff --git a/Source/Core/DoubleEndedPriorityQueue.js b/Source/Core/DoubleEndedPriorityQueue.js index 76538dd463..4d25cd6a03 100644 --- a/Source/Core/DoubleEndedPriorityQueue.js +++ b/Source/Core/DoubleEndedPriorityQueue.js @@ -208,7 +208,7 @@ function pushUp(that, index) { // 2A) is less than the grandparent when on a min level // 2B) is greater than the grandparent when on a max level while (index >= 3) { - var grandparentIndex = Math.floor((Math.floor((index - 1) / 2) - 1) / 2); + var grandparentIndex = Math.floor((index - 3) / 4); if (lessThan(that, index, grandparentIndex) !== lessThanParent) { break; } @@ -230,7 +230,6 @@ function pushDown(that, index) { while ((leftChildIndex = 2 * index + 1) < length) { // Find the minimum (or maximum) child or grandchild var target = leftChildIndex; - var rightChildIndex = leftChildIndex + 1; if (rightChildIndex < length) { if (lessThan(that, rightChildIndex, target) === onMinLevel) { @@ -246,6 +245,7 @@ function pushDown(that, index) { } } + // Swap the element into the correct spot if (lessThan(that, target, index) === onMinLevel) { swap(that, target, index); if (target !== leftChildIndex && target !== rightChildIndex) { @@ -304,6 +304,19 @@ DoubleEndedPriorityQueue.prototype.reset = function () { } }; +/** + * Resort the queue. + */ +DoubleEndedPriorityQueue.prototype.resort = function () { + var that = this; + var length = that._length; + + // Fix the queue from the top-down + for (var i = 0; i < length; i++) { + pushUp(that, i); + } +}; + /** * Inserts an element into the queue. * If the queue is full, the minimum element is removed before the new element is added. diff --git a/Specs/Core/DoubleEndedPriorityQueueSpec.js b/Specs/Core/DoubleEndedPriorityQueueSpec.js index d9d2d12ad0..40d47c35e4 100644 --- a/Specs/Core/DoubleEndedPriorityQueueSpec.js +++ b/Specs/Core/DoubleEndedPriorityQueueSpec.js @@ -359,13 +359,13 @@ describe("Core/DoubleEndedPriorityQueue", function () { } it("maintains priority with ascending insertions", function () { - var length = 20; - var maximumLength = 10; + var length = 200; + var maximumLength = 100; var queue = new DoubleEndedPriorityQueue({ comparator: comparator, + maximumLength: maximumLength, }); - queue.maximumLength = maximumLength; var pass = true; for (var i = 0; i < length; ++i) { @@ -378,13 +378,13 @@ describe("Core/DoubleEndedPriorityQueue", function () { }); it("maintains priority with descending insertions", function () { - var length = 20; - var maximumLength = 10; + var length = 200; + var maximumLength = 100; var queue = new DoubleEndedPriorityQueue({ comparator: comparator, + maximumLength: maximumLength, }); - queue.maximumLength = maximumLength; var pass = true; for (var i = 0; i < length; ++i) { @@ -402,8 +402,8 @@ describe("Core/DoubleEndedPriorityQueue", function () { var queue = new DoubleEndedPriorityQueue({ comparator: comparator, + maximumLength: maximumLength, }); - queue.maximumLength = maximumLength; var pass = true; for (var i = 0; i < length; ++i) { @@ -414,4 +414,28 @@ describe("Core/DoubleEndedPriorityQueue", function () { expect(pass).toBe(true); }); + + it("resorts queue", function () { + var queue = new DoubleEndedPriorityQueue({ + comparator: comparator, + }); + + var i; + var length = 200; + + for (i = 0; i < length; ++i) { + queue.insert(0); + } + + // Change all of the queue values to random values to make it unsorted + var array = queue.internalArray; + for (i = 0; i < length; i++) { + array[i] = Math.random(); + } + + queue.resort(); + + var pass = isValidQueue(queue); + expect(pass).toBe(true); + }); }); From 1215130ff35909bee6b141b1a3daf0fd2de27eab Mon Sep 17 00:00:00 2001 From: Ian Lilley Date: Thu, 17 Dec 2020 23:58:06 -0500 Subject: [PATCH 03/76] added double ended priority queue to request scheduler --- Source/Core/DoubleEndedPriorityQueue.js | 12 ++--- Source/Core/RequestScheduler.js | 58 +++++++++++----------- Specs/Core/DoubleEndedPriorityQueueSpec.js | 14 ++++-- Specs/Core/RequestSchedulerSpec.js | 43 ++++++++-------- 4 files changed, 66 insertions(+), 61 deletions(-) diff --git a/Source/Core/DoubleEndedPriorityQueue.js b/Source/Core/DoubleEndedPriorityQueue.js index 4d25cd6a03..e7ac2794a5 100644 --- a/Source/Core/DoubleEndedPriorityQueue.js +++ b/Source/Core/DoubleEndedPriorityQueue.js @@ -11,7 +11,7 @@ import defined from "./defined.js"; * * @param {Object} options Object with the following properties: * @param {DoubleEndedPriorityQueue.ComparatorCallback} options.comparator The comparator to use for the queue. If comparator(a, b) is less than 0, a is lower priority than b. - * @param {Number} [options.maximumLength] The maximum length of the queue. If an element is inserted when the queue is full, the minimum element is removed. By default, the size of the queue is unlimited. + * @param {Number} [options.maximumLength] The maximum length of the queue. If an element is inserted when the queue is at full capacity, the minimum element is removed. By default, the size of the queue is unlimited. */ function DoubleEndedPriorityQueue(options) { //>>includeStart('debug', pragmas.debug); @@ -52,7 +52,7 @@ Object.defineProperties(DoubleEndedPriorityQueue.prototype, { /** * Gets or sets the maximum number of elements in the queue. * If set to a smaller value than the current length of the queue, the lowest priority elements are removed. - * If an element is inserted when the queue's length equals the maximum length, the minimum element is removed. + * If an element is inserted when the queue is at full capacity, the minimum element is removed. * If set to undefined, the size of the queue is unlimited. * * @memberof DoubleEndedPriorityQueue.prototype @@ -319,11 +319,11 @@ DoubleEndedPriorityQueue.prototype.resort = function () { /** * Inserts an element into the queue. - * If the queue is full, the minimum element is removed before the new element is added. - * If the new element is lower or equal priority to the minimum element, it is not added. + * If the queue is at full capacity, the minimum element is removed. + * The new element is returned (and not added) if it is less than or equal priority to the minimum element. * * @param {*} element - * @returns {*|undefined} The element that was removed to make room for the new element. Only applicable if maximumLength is defined. + * @returns {*|undefined} The minimum element if the queue is at full capacity. Returns undefined if there is no maximum length. */ DoubleEndedPriorityQueue.prototype.insert = function (element) { var removedElement; @@ -340,7 +340,7 @@ DoubleEndedPriorityQueue.prototype.insert = function (element) { if (that._comparator(element, minimumElement) <= 0.0) { // The element that is being inserted is less than or equal to // the minimum element, so don't insert anything and exit early. - return undefined; + return element; } removedElement = that.removeMinimum(); } diff --git a/Source/Core/RequestScheduler.js b/Source/Core/RequestScheduler.js index cc364908d8..33dd71ae0f 100644 --- a/Source/Core/RequestScheduler.js +++ b/Source/Core/RequestScheduler.js @@ -3,14 +3,15 @@ import when from "../ThirdParty/when.js"; import Check from "./Check.js"; import defaultValue from "./defaultValue.js"; import defined from "./defined.js"; +import DoubleEndedPriorityQueue from "./DoubleEndedPriorityQueue.js"; import Event from "./Event.js"; -import Heap from "./Heap.js"; import isBlobUri from "./isBlobUri.js"; import isDataUri from "./isDataUri.js"; import RequestState from "./RequestState.js"; function sortRequests(a, b) { - return a.priority - b.priority; + // lower number = higher priority, so flip the order + return b.priority - a.priority; } var statistics = { @@ -23,12 +24,11 @@ var statistics = { lastNumberOfActiveRequests: 0, }; -var priorityHeapLength = 20; -var requestHeap = new Heap({ +var priorityQueueLength = 20; +var requestQueue = new DoubleEndedPriorityQueue({ comparator: sortRequests, + maximumLength: priorityQueueLength, }); -requestHeap.maximumLength = priorityHeapLength; -requestHeap.reserve(priorityHeapLength); var activeRequests = []; var numberOfActiveRequestsByServer = {}; @@ -121,7 +121,7 @@ Object.defineProperties(RequestScheduler, { }, /** - * 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 size of the priority queue. This limits the number of requests that are sorted by priority. Only applies to requests that are not yet active. * * @memberof RequestScheduler * @@ -129,22 +129,20 @@ Object.defineProperties(RequestScheduler, { * @default 20 * @private */ - priorityHeapLength: { + priorityQueueLength: { get: function () { - return priorityHeapLength; + return priorityQueueLength; }, 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(); + // If the new length shrinks the queue, need to cancel some of the requests. + if (value < priorityQueueLength) { + while (requestQueue.length > value) { + var request = requestQueue.removeMinimum(); cancelRequest(request); } } - priorityHeapLength = value; - requestHeap.maximumLength = value; - requestHeap.reserve(value); + priorityQueueLength = value; + requestQueue.maximumLength = value; }, }, }); @@ -271,13 +269,13 @@ RequestScheduler.update = function () { } activeRequests.length -= removeCount; - // Update priority of issued requests and resort the heap - var issuedRequests = requestHeap.internalArray; - var issuedLength = requestHeap.length; + // Update priority of issued requests and resort the queue + var issuedRequests = requestQueue.internalArray; + var issuedLength = requestQueue.length; for (i = 0; i < issuedLength; ++i) { updatePriority(issuedRequests[i]); } - requestHeap.resort(); + requestQueue.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 @@ -286,9 +284,9 @@ RequestScheduler.update = function () { 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(); + while (filledSlots < openSlots && requestQueue.length > 0) { + // Loop until all open slots are filled or the queue becomes empty + request = requestQueue.removeMaximum(); if (request.cancelled) { // Request was explicitly cancelled cancelRequest(request); @@ -383,17 +381,17 @@ RequestScheduler.request = function (request) { return undefined; } - // Insert into the priority heap and see if a request was bumped off. If this request is the lowest + // 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) { // Request does not have high enough priority to be issued return undefined; } - // A previously issued request has been bumped off the priority heap, so cancel it + // A previously issued request has been bumped off the priority queue, so cancel it cancelRequest(removedRequest); } @@ -448,8 +446,8 @@ function updateStatistics() { * @private */ RequestScheduler.clearForSpecs = function () { - while (requestHeap.length > 0) { - var request = requestHeap.pop(); + while (requestQueue.length > 0) { + var request = requestQueue.removeMinimum(); cancelRequest(request); } var length = activeRequests.length; @@ -483,5 +481,5 @@ RequestScheduler.numberOfActiveRequestsByServer = function (serverKey) { * * @private */ -RequestScheduler.requestHeap = requestHeap; +RequestScheduler.requestQueue = requestQueue; export default RequestScheduler; diff --git a/Specs/Core/DoubleEndedPriorityQueueSpec.js b/Specs/Core/DoubleEndedPriorityQueueSpec.js index 40d47c35e4..6f36821471 100644 --- a/Specs/Core/DoubleEndedPriorityQueueSpec.js +++ b/Specs/Core/DoubleEndedPriorityQueueSpec.js @@ -147,10 +147,14 @@ describe("Core/DoubleEndedPriorityQueue", function () { maximumLength: 2, }); - queue.insert({ value: 1, id: 0 }); - queue.insert({ value: 2, id: 0 }); - var result1 = queue.insert({ value: 1, id: 1 }); // ignored because equal priority to minimum - var result2 = queue.insert({ value: 0, id: 1 }); // ignored because lower priority than minimum + var obj1 = { value: 1, id: 0 }; + var obj2 = { value: 2, id: 0 }; + var obj3 = { value: 1, id: 1 }; + var obj4 = { value: 0, id: 1 }; + var result1 = queue.insert(obj1); + var result2 = queue.insert(obj2); + var result3 = queue.insert(obj3); // ignored because equal priority to minimum + var result4 = queue.insert(obj4); // ignored because lower priority than minimum expect(queue.length).toEqual(2); expect(queue.maximumLength).toEqual(2); @@ -158,6 +162,8 @@ describe("Core/DoubleEndedPriorityQueue", function () { expect(queue.minimum.id).toEqual(0); expect(result1).toBeUndefined(); expect(result2).toBeUndefined(); + expect(result3).toEqual(obj3); + expect(result4).toEqual(obj4); }); it("remove and return minimum element", function () { diff --git a/Specs/Core/RequestSchedulerSpec.js b/Specs/Core/RequestSchedulerSpec.js index ab3cb72722..d91eb3dc5a 100644 --- a/Specs/Core/RequestSchedulerSpec.js +++ b/Specs/Core/RequestSchedulerSpec.js @@ -6,14 +6,14 @@ import { when } from "../../Source/Cesium.js"; describe("Core/RequestScheduler", function () { var originalMaximumRequests; var originalMaximumRequestsPerServer; - var originalPriorityHeapLength; + var originalPriorityQueueLength; var originalRequestsByServer; beforeAll(function () { originalMaximumRequests = RequestScheduler.maximumRequests; originalMaximumRequestsPerServer = RequestScheduler.maximumRequestsPerServer; - originalPriorityHeapLength = RequestScheduler.priorityHeapLength; + originalPriorityQueueLength = RequestScheduler.priorityQueueLength; originalRequestsByServer = RequestScheduler.requestsByServer; }); @@ -25,7 +25,7 @@ describe("Core/RequestScheduler", function () { afterEach(function () { RequestScheduler.maximumRequests = originalMaximumRequests; RequestScheduler.maximumRequestsPerServer = originalMaximumRequestsPerServer; - RequestScheduler.priorityHeapLength = originalPriorityHeapLength; + RequestScheduler.priorityQueueLength = originalPriorityQueueLength; RequestScheduler.requestsByServer = originalRequestsByServer; }); @@ -207,7 +207,7 @@ describe("Core/RequestScheduler", function () { } }); - it("honors priorityHeapLength", function () { + it("honors priorityQueueLength", function () { var deferreds = []; var requests = []; @@ -228,23 +228,24 @@ describe("Core/RequestScheduler", function () { return request; } - RequestScheduler.priorityHeapLength = 1; + RequestScheduler.priorityQueueLength = 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.priorityQueueLength = 3; promise = RequestScheduler.request(createRequest(2.0)); - promise = RequestScheduler.request(createRequest(3.0)); + var lastRequest = createRequest(3.0); + promise = RequestScheduler.request(lastRequest); 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; - expect(firstRequest.state).toBe(RequestState.CANCELLED); + // A request is cancelled to accommodate the new queue length + RequestScheduler.priorityQueueLength = 2; + expect(lastRequest.state).toBe(RequestState.CANCELLED); var length = deferreds.length; for (var i = 0; i < length; ++i) { @@ -463,7 +464,7 @@ describe("Core/RequestScheduler", function () { }); } - var length = RequestScheduler.priorityHeapLength; + var length = RequestScheduler.priorityQueueLength; for (var i = 0; i < length; ++i) { var priority = Math.random(); RequestScheduler.request(createRequest(priority)); @@ -500,7 +501,7 @@ describe("Core/RequestScheduler", function () { var i; var request; - var length = RequestScheduler.priorityHeapLength; + var length = RequestScheduler.priorityQueueLength; for (i = 0; i < length; ++i) { var priority = i / (length - 1); request = createRequest(priority); @@ -512,25 +513,25 @@ describe("Core/RequestScheduler", function () { 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(); + while (requestQueue.length > 0) { + request = requestQueue.removeMaximum(); requests.push(request); expect(request.testId).toBeGreaterThanOrEqualTo(currentTestId); currentTestId = request.testId; } 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(); + while (requestQueue.length > 0) { + request = requestQueue.removeMaximum(); expect(request.testId).toBeLessThanOrEqualTo(currentTestId); currentTestId = request.testId; } @@ -554,17 +555,17 @@ describe("Core/RequestScheduler", function () { var mediumPriority = 0.5; var lowPriority = 1.0; - var length = RequestScheduler.priorityHeapLength; + var length = RequestScheduler.priorityQueueLength; for (var i = 0; i < length; ++i) { RequestScheduler.request(createRequest(mediumPriority)); } - // Heap is full so low priority request is not even issued + // Queue is full so low priority request is not even issued var promise = RequestScheduler.request(createRequest(lowPriority)); expect(promise).toBeUndefined(); expect(RequestScheduler.statistics.numberOfCancelledRequests).toBe(0); - // Heap is full so high priority request bumps off lower priority request + // Queue is full so high priority request bumps off lower priority request promise = RequestScheduler.request(createRequest(highPriority)); expect(promise).toBeDefined(); expect(RequestScheduler.statistics.numberOfCancelledRequests).toBe(1); From 05ce97dcd16b753c7d139d6f369cbd0dbc4f0fbd Mon Sep 17 00:00:00 2001 From: Ian Lilley Date: Fri, 18 Dec 2020 00:25:56 -0500 Subject: [PATCH 04/76] removed bad tsdoc and added a spec for clone --- Source/Core/DoubleEndedPriorityQueue.js | 23 ---------------------- Specs/Core/DoubleEndedPriorityQueueSpec.js | 17 ++++++++++++++++ 2 files changed, 17 insertions(+), 23 deletions(-) diff --git a/Source/Core/DoubleEndedPriorityQueue.js b/Source/Core/DoubleEndedPriorityQueue.js index e7ac2794a5..bb33e32dea 100644 --- a/Source/Core/DoubleEndedPriorityQueue.js +++ b/Source/Core/DoubleEndedPriorityQueue.js @@ -155,11 +155,6 @@ Object.defineProperties(DoubleEndedPriorityQueue.prototype, { }, }); -/** - * @param {DoubleEndedPriorityQueue} that - * @param {Number} index0 - * @param {Number} index1 - */ function swap(that, index0, index1) { var array = that._array; var temp = array[index0]; @@ -167,28 +162,14 @@ function swap(that, index0, index1) { array[index1] = temp; } -/** - * @param {DoubleEndedPriorityQueue} that - * @param {Number} indexA - * @param {Number} indexB - */ function lessThan(that, indexA, indexB) { return that._comparator(that._array[indexA], that._array[indexB]) < 0.0; } -/** - * @param {DoubleEndedPriorityQueue} that - * @param {Number} indexA - * @param {Number} indexB - */ function greaterThan(that, indexA, indexB) { return that._comparator(that._array[indexA], that._array[indexB]) > 0.0; } -/** - * @param {DoubleEndedPriorityQueue} that - * @param {Number} index - */ function pushUp(that, index) { if (index === 0) { return; @@ -217,10 +198,6 @@ function pushUp(that, index) { } } -/** - * @param {DoubleEndedPriorityQueue} that - * @param {Number} index - */ function pushDown(that, index) { var length = that._length; var onMinLevel = Math.floor(Math.log2(index + 1)) % 2 === 0; diff --git a/Specs/Core/DoubleEndedPriorityQueueSpec.js b/Specs/Core/DoubleEndedPriorityQueueSpec.js index 6f36821471..137c57a9f2 100644 --- a/Specs/Core/DoubleEndedPriorityQueueSpec.js +++ b/Specs/Core/DoubleEndedPriorityQueueSpec.js @@ -220,6 +220,23 @@ describe("Core/DoubleEndedPriorityQueue", function () { expect(maximumValue).toBeUndefined(); }); + it("clones queue", function () { + var queue = new DoubleEndedPriorityQueue({ + comparator: comparator, + maximumLength: 4, + }); + + queue.insert(1); + queue.insert(2); + + var clone = queue.clone(); + expect(clone.length).toEqual(queue.length); + expect(clone.maximumLength).toEqual(queue.maximumLength); + expect(clone.comparator).toEqual(queue.comparator); + expect(clone.maximum).toEqual(queue.maximum); + expect(clone.minimum).toEqual(queue.minimum); + }); + it("resets queue", function () { var queue = new DoubleEndedPriorityQueue({ comparator: comparator, From 42eb9315afbf9fcc5728f7b59b3e9bdfbd6614c0 Mon Sep 17 00:00:00 2001 From: Ian Lilley Date: Fri, 29 Jan 2021 15:35:17 -0500 Subject: [PATCH 05/76] CesiumMath.log2 instead of Math.log2 --- Source/Core/DoubleEndedPriorityQueue.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Source/Core/DoubleEndedPriorityQueue.js b/Source/Core/DoubleEndedPriorityQueue.js index bb33e32dea..e8e6dde705 100644 --- a/Source/Core/DoubleEndedPriorityQueue.js +++ b/Source/Core/DoubleEndedPriorityQueue.js @@ -1,3 +1,4 @@ +import CesiumMath from "./Math.js"; import Check from "./Check.js"; import defined from "./defined.js"; @@ -174,7 +175,7 @@ function pushUp(that, index) { if (index === 0) { return; } - var onMinLevel = Math.floor(Math.log2(index + 1)) % 2 === 0; + var onMinLevel = Math.floor(CesiumMath.log2(index + 1)) % 2 === 0; var parentIndex = Math.floor((index - 1) / 2); var lessThanParent = lessThan(that, index, parentIndex); @@ -200,7 +201,7 @@ function pushUp(that, index) { function pushDown(that, index) { var length = that._length; - var onMinLevel = Math.floor(Math.log2(index + 1)) % 2 === 0; + var onMinLevel = Math.floor(CesiumMath.log2(index + 1)) % 2 === 0; // Loop as long as there is a left child. var leftChildIndex; From ce33b1c546ec8d9b62710e4cf0caa96d2cd93d16 Mon Sep 17 00:00:00 2001 From: Ian Lilley Date: Fri, 29 Jan 2021 16:25:39 -0500 Subject: [PATCH 06/76] changed minimum and maximum getters to functions --- Source/Core/DoubleEndedPriorityQueue.js | 83 +++++++++++----------- Specs/Core/DoubleEndedPriorityQueueSpec.js | 62 ++++++++-------- 2 files changed, 71 insertions(+), 74 deletions(-) diff --git a/Source/Core/DoubleEndedPriorityQueue.js b/Source/Core/DoubleEndedPriorityQueue.js index e8e6dde705..220e0a6cf7 100644 --- a/Source/Core/DoubleEndedPriorityQueue.js +++ b/Source/Core/DoubleEndedPriorityQueue.js @@ -83,49 +83,6 @@ Object.defineProperties(DoubleEndedPriorityQueue.prototype, { }, }, - /** - * Gets the minimum element in the queue. - * If the queue is empty, the result is undefined. - * - * @param {*|undefined} element - */ - minimum: { - get: function () { - var length = this._length; - if (length === 0) { - return undefined; - } - - // The minimum element is always the root - return this._array[0]; - }, - }, - - /** - * Gets the maximum element in the queue. - * If the queue is empty, the result is undefined. - * - * @param {*|undefined} element - */ - maximum: { - get: function () { - var length = this._length; - if (length === 0) { - return undefined; - } - - // If the root has no children, the maximum is the root. - // If the root has one child, the maximum is the child. - if (length <= 2) { - return this._array[length - 1]; - } - - // Otherwise, the maximum is the larger of the root's two children. - var that = this; - return this._array[greaterThan(that, 1, 2) ? 1 : 2]; - }, - }, - /** * Gets the internal array. * @@ -399,6 +356,46 @@ DoubleEndedPriorityQueue.prototype.removeMaximum = function () { return maximumElement; }; +/** + * Gets the minimum element in the queue. + * If the queue is empty, the result is undefined. + * + * @param {*|undefined} element + */ + +DoubleEndedPriorityQueue.prototype.getMinimum = function () { + var length = this._length; + if (length === 0) { + return undefined; + } + + // The minimum element is always the root + return this._array[0]; +}; + +/** + * Gets the maximum element in the queue. + * If the queue is empty, the result is undefined. + * + * @param {*|undefined} element + */ +DoubleEndedPriorityQueue.prototype.getMaximum = function () { + var length = this._length; + if (length === 0) { + return undefined; + } + + // If the root has no children, the maximum is the root. + // If the root has one child, the maximum is the child. + if (length <= 2) { + return this._array[length - 1]; + } + + // Otherwise, the maximum is the larger of the root's two children. + var that = this; + return this._array[greaterThan(that, 1, 2) ? 1 : 2]; +}; + /** * The comparator to use for the queue. * @callback DoubleEndedPriorityQueue.ComparatorCallback diff --git a/Specs/Core/DoubleEndedPriorityQueueSpec.js b/Specs/Core/DoubleEndedPriorityQueueSpec.js index 137c57a9f2..d9583e0986 100644 --- a/Specs/Core/DoubleEndedPriorityQueueSpec.js +++ b/Specs/Core/DoubleEndedPriorityQueueSpec.js @@ -47,8 +47,8 @@ describe("Core/DoubleEndedPriorityQueue", function () { // The comparator is flipped, so 2 is considered the minimum and 1 is considered the maximum expect(queue.length).toEqual(2); - expect(queue.minimum).toEqual(2); - expect(queue.maximum).toEqual(1); + expect(queue.getMinimum()).toEqual(2); + expect(queue.getMaximum()).toEqual(1); }); it("checks state of default empty queue", function () { @@ -59,8 +59,8 @@ describe("Core/DoubleEndedPriorityQueue", function () { expect(queue.length).toEqual(0); expect(queue.maximumLength).toBeUndefined(); expect(queue.internalArray.length).toEqual(0); - expect(queue.minimum).toBeUndefined(); - expect(queue.maximum).toBeUndefined(); + expect(queue.getMinimum()).toBeUndefined(); + expect(queue.getMaximum()).toBeUndefined(); }); it("inserts one element into queue", function () { @@ -72,8 +72,8 @@ describe("Core/DoubleEndedPriorityQueue", function () { expect(queue.length).toEqual(1); expect(queue.internalArray.length).toEqual(1); - expect(queue.minimum).toEqual(1); - expect(queue.maximum).toEqual(1); + expect(queue.getMinimum()).toEqual(1); + expect(queue.getMaximum()).toEqual(1); }); it("inserts two elements into queue", function () { @@ -86,8 +86,8 @@ describe("Core/DoubleEndedPriorityQueue", function () { expect(queue.length).toEqual(2); expect(queue.internalArray.length).toEqual(2); - expect(queue.minimum).toEqual(1); - expect(queue.maximum).toEqual(2); + expect(queue.getMinimum()).toEqual(1); + expect(queue.getMaximum()).toEqual(2); }); it("inserts three elements into queue", function () { @@ -101,8 +101,8 @@ describe("Core/DoubleEndedPriorityQueue", function () { expect(queue.length).toEqual(3); expect(queue.internalArray.length).toEqual(3); - expect(queue.minimum).toEqual(1); - expect(queue.maximum).toEqual(3); + expect(queue.getMinimum()).toEqual(1); + expect(queue.getMaximum()).toEqual(3); }); it("inserts four elements into queue", function () { @@ -117,8 +117,8 @@ describe("Core/DoubleEndedPriorityQueue", function () { expect(queue.length).toEqual(4); expect(queue.internalArray.length).toEqual(4); - expect(queue.minimum).toEqual(1); - expect(queue.maximum).toEqual(4); + expect(queue.getMinimum()).toEqual(1); + expect(queue.getMaximum()).toEqual(4); }); it("insert removes and returns minimum element when the queue is full", function () { @@ -133,8 +133,8 @@ describe("Core/DoubleEndedPriorityQueue", function () { expect(queue.length).toEqual(1); expect(queue.maximumLength).toEqual(1); expect(queue.internalArray.length).toEqual(1); - expect(queue.minimum).toEqual(2); - expect(queue.maximum).toEqual(2); + expect(queue.getMinimum()).toEqual(2); + expect(queue.getMaximum()).toEqual(2); expect(nothing).toBeUndefined(); expect(removed).toEqual(1); }); @@ -159,7 +159,7 @@ describe("Core/DoubleEndedPriorityQueue", function () { expect(queue.length).toEqual(2); expect(queue.maximumLength).toEqual(2); expect(queue.internalArray.length).toEqual(2); - expect(queue.minimum.id).toEqual(0); + expect(queue.getMinimum().id).toEqual(0); expect(result1).toBeUndefined(); expect(result2).toBeUndefined(); expect(result3).toEqual(obj3); @@ -179,7 +179,7 @@ describe("Core/DoubleEndedPriorityQueue", function () { expect(queue.length).toEqual(2); expect(minimumValue).toEqual(1); - expect(queue.minimum).toEqual(2); + expect(queue.getMinimum()).toEqual(2); // check that the element was dereferenced expect(queue.internalArray[2]).toBeUndefined(); }); @@ -206,7 +206,7 @@ describe("Core/DoubleEndedPriorityQueue", function () { expect(queue.length).toEqual(2); expect(maximumValue).toEqual(3); - expect(queue.maximum).toEqual(2); + expect(queue.getMaximum()).toEqual(2); // check that the element was dereferenced expect(queue.internalArray[2]).toBeUndefined(); }); @@ -233,8 +233,8 @@ describe("Core/DoubleEndedPriorityQueue", function () { expect(clone.length).toEqual(queue.length); expect(clone.maximumLength).toEqual(queue.maximumLength); expect(clone.comparator).toEqual(queue.comparator); - expect(clone.maximum).toEqual(queue.maximum); - expect(clone.minimum).toEqual(queue.minimum); + expect(clone.getMaximum()).toEqual(queue.getMaximum()); + expect(clone.getMinimum()).toEqual(queue.getMinimum()); }); it("resets queue", function () { @@ -246,8 +246,8 @@ describe("Core/DoubleEndedPriorityQueue", function () { queue.reset(); expect(queue.length).toEqual(0); - expect(queue.minimum).toBeUndefined(); - expect(queue.maximum).toBeUndefined(); + expect(queue.getMinimum()).toBeUndefined(); + expect(queue.getMaximum()).toBeUndefined(); // check that the elements were dereferenced expect(queue.internalArray.length).toEqual(0); }); @@ -261,8 +261,8 @@ describe("Core/DoubleEndedPriorityQueue", function () { queue.reset(); expect(queue.length).toEqual(0); - expect(queue.minimum).toBeUndefined(); - expect(queue.maximum).toBeUndefined(); + expect(queue.getMinimum()).toBeUndefined(); + expect(queue.getMaximum()).toBeUndefined(); // check that the element was dereferenced but the array stayed the same size expect(queue.internalArray.length).toEqual(1); expect(queue.internalArray[0]).toBeUndefined(); @@ -279,8 +279,8 @@ describe("Core/DoubleEndedPriorityQueue", function () { expect(queue.length).toEqual(0); expect(queue.maximumLength).toEqual(0); expect(queue.internalArray.length).toEqual(0); - expect(queue.minimum).toBeUndefined(); - expect(queue.maximum).toBeUndefined(); + expect(queue.getMinimum()).toBeUndefined(); + expect(queue.getMaximum()).toBeUndefined(); }); it("creates queue with maximum length of one", function () { @@ -295,8 +295,8 @@ describe("Core/DoubleEndedPriorityQueue", function () { expect(queue.length).toEqual(1); expect(queue.maximumLength).toEqual(1); expect(queue.internalArray.length).toEqual(1); - expect(queue.minimum).toEqual(2); - expect(queue.maximum).toEqual(2); + expect(queue.getMinimum()).toEqual(2); + expect(queue.getMaximum()).toEqual(2); }); it("throws when maximum length is set to less than zero", function () { @@ -323,8 +323,8 @@ describe("Core/DoubleEndedPriorityQueue", function () { expect(queue.length).toEqual(3); expect(queue.maximumLength).toBeUndefined(); - expect(queue.minimum).toEqual(1); - expect(queue.maximum).toEqual(3); + expect(queue.getMinimum()).toEqual(1); + expect(queue.getMaximum()).toEqual(3); }); it("sets maximum length to less than current length", function () { @@ -342,8 +342,8 @@ describe("Core/DoubleEndedPriorityQueue", function () { expect(queue.length).toEqual(maximumLength); expect(queue.maximumLength).toEqual(maximumLength); expect(queue.internalArray.length).toEqual(maximumLength); - expect(queue.minimum).toEqual(maximumLength); - expect(queue.maximum).toEqual(maximumLength * 2 - 1); + expect(queue.getMinimum()).toEqual(maximumLength); + expect(queue.getMaximum()).toEqual(maximumLength * 2 - 1); }); function isValidQueue(queue) { From 071a6a8bb2325ba4b014641ba6e5333f2891a00e Mon Sep 17 00:00:00 2001 From: ebogo1 Date: Mon, 1 Mar 2021 17:12:09 -0500 Subject: [PATCH 07/76] add diff files --- Source/Core/decodeVectorPolylinePositions.js | 51 ++ Source/Scene/Cesium3DTileset.js | 21 + Source/Scene/Vector3DTileClampedPolylines.js | 718 ++++++++++++++++++ Source/Scene/Vector3DTileContent.js | 61 +- .../Vector3DTileClampedPolylinesFS.glsl | 52 ++ .../Vector3DTileClampedPolylinesVS.glsl | 86 +++ .../createVectorTileClampedPolylines.js | 507 +++++++++++++ .../WorkersES6/createVectorTilePolylines.js | 52 +- 8 files changed, 1497 insertions(+), 51 deletions(-) create mode 100644 Source/Core/decodeVectorPolylinePositions.js create mode 100644 Source/Scene/Vector3DTileClampedPolylines.js create mode 100644 Source/Shaders/Vector3DTileClampedPolylinesFS.glsl create mode 100644 Source/Shaders/Vector3DTileClampedPolylinesVS.glsl create mode 100644 Source/WorkersES6/createVectorTileClampedPolylines.js diff --git a/Source/Core/decodeVectorPolylinePositions.js b/Source/Core/decodeVectorPolylinePositions.js new file mode 100644 index 0000000000..f0df3a0557 --- /dev/null +++ b/Source/Core/decodeVectorPolylinePositions.js @@ -0,0 +1,51 @@ +import AttributeCompression from "./AttributeCompression.js"; +import Cartesian3 from "./Cartesian3.js"; +import Cartographic from "./Cartographic.js"; +import CesiumMath from "./Math.js"; + +var maxShort = 32767; + +var scratchBVCartographic = new Cartographic(); +var scratchEncodedPosition = new Cartesian3(); + +function decodeVectorPolylinePositions( + positions, + rectangle, + minimumHeight, + maximumHeight, + ellipsoid +) { + var positionsLength = positions.length / 3; + var uBuffer = positions.subarray(0, positionsLength); + var vBuffer = positions.subarray(positionsLength, 2 * positionsLength); + var heightBuffer = positions.subarray( + 2 * positionsLength, + 3 * positionsLength + ); + AttributeCompression.zigZagDeltaDecode(uBuffer, vBuffer, heightBuffer); + + var decoded = new Float64Array(positions.length); + for (var i = 0; i < positionsLength; ++i) { + var u = uBuffer[i]; + var v = vBuffer[i]; + var h = heightBuffer[i]; + + var lon = CesiumMath.lerp(rectangle.west, rectangle.east, u / maxShort); + var lat = CesiumMath.lerp(rectangle.south, rectangle.north, v / maxShort); + var alt = CesiumMath.lerp(minimumHeight, maximumHeight, h / maxShort); + + var cartographic = Cartographic.fromRadians( + lon, + lat, + alt, + scratchBVCartographic + ); + var decodedPosition = ellipsoid.cartographicToCartesian( + cartographic, + scratchEncodedPosition + ); + Cartesian3.pack(decodedPosition, decoded, i * 3); + } + return decoded; +} +export default decodeVectorPolylinePositions; diff --git a/Source/Scene/Cesium3DTileset.js b/Source/Scene/Cesium3DTileset.js index 19094051d8..f4e88edd41 100644 --- a/Source/Scene/Cesium3DTileset.js +++ b/Source/Scene/Cesium3DTileset.js @@ -898,6 +898,13 @@ function Cesium3DTileset(options) { */ this.debugShowUrl = defaultValue(options.debugShowUrl, false); + /** + * Function for examining vector lines as they are being streamed. + * + * @private + */ + this.examineVectorLinesFunction = undefined; + var that = this; var resource; when(options.url) @@ -1654,6 +1661,20 @@ Object.defineProperties(Cesium3DTileset.prototype, { Cartesian2.clone(value, this._imageBasedLightingFactor); }, }, + + /** + * Minimum and maximum heights that vector tiles clamped to surfaces will clamp to. + * + * @memberof Cesium3DTileset.prototype + * + * @type {Cartesian2} + * @default undefined + */ + minimumMaximumVectorHeights: { + get: function () { + return this._minimumMaximumVectorHeights; + }, + }, }); /** diff --git a/Source/Scene/Vector3DTileClampedPolylines.js b/Source/Scene/Vector3DTileClampedPolylines.js new file mode 100644 index 0000000000..e984ef5777 --- /dev/null +++ b/Source/Scene/Vector3DTileClampedPolylines.js @@ -0,0 +1,718 @@ +import ApproximateTerrainHeights from "../Core/ApproximateTerrainHeights.js"; +import arraySlice from "../Core/arraySlice.js"; +import Cartesian2 from "../Core/Cartesian2.js"; +import Cartesian3 from "../Core/Cartesian3.js"; +import Color from "../Core/Color.js"; +import ComponentDatatype from "../Core/ComponentDatatype.js"; +import defaultValue from "../Core/defaultValue.js"; +import defined from "../Core/defined.js"; +import destroyObject from "../Core/destroyObject.js"; +import Ellipsoid from "../Core/Ellipsoid.js"; +import FeatureDetection from "../Core/FeatureDetection.js"; +import IndexDatatype from "../Core/IndexDatatype.js"; +import Matrix4 from "../Core/Matrix4.js"; +import Rectangle from "../Core/Rectangle.js"; +import TaskProcessor from "../Core/TaskProcessor.js"; +import Buffer from "../Renderer/Buffer.js"; +import BufferUsage from "../Renderer/BufferUsage.js"; +import DrawCommand from "../Renderer/DrawCommand.js"; +import Pass from "../Renderer/Pass.js"; +import RenderState from "../Renderer/RenderState.js"; +import ShaderProgram from "../Renderer/ShaderProgram.js"; +import ShaderSource from "../Renderer/ShaderSource.js"; +import VertexArray from "../Renderer/VertexArray.js"; +import PolylineCommon from "../Shaders/PolylineCommon.js"; +import Vector3DTileClampedPolylinesVS from "../Shaders/Vector3DTileClampedPolylinesVS.js"; +import Vector3DTileClampedPolylinesFS from "../Shaders/Vector3DTileClampedPolylinesFS.js"; +import when from "../ThirdParty/when.js"; +import BlendingState from "./BlendingState.js"; +import Cesium3DTileFeature from "./Cesium3DTileFeature.js"; +import ClassificationType from "./ClassificationType.js"; +import StencilConstants from "./StencilConstants.js"; +import StencilFunction from "./StencilFunction.js"; +import StencilOperation from "./StencilOperation.js"; + +/** + * Creates a batch of polylines as volumes with shader-adjustable width. + * + * @alias Vector3DTileClampedPolylines + * @constructor + * + * @param {Object} options An object with following properties: + * @param {Uint16Array} options.positions The positions of the polylines + * @param {Uint32Array} options.counts The number or positions in the each polyline. + * @param {Uint16Array} options.widths The width of each polyline. + * @param {Number} options.minimumHeight The minimum height of the tile's region. + * @param {Number} options.maximumHeight The maximum height of the tile's region. + * @param {Rectangle} options.rectangle The rectangle containing the tile. + * @param {Cartesian3} [options.center=Cartesian3.ZERO] The RTC center. + * @param {Cesium3DTileBatchTable} options.batchTable The batch table for the tile containing the batched polylines. + * @param {Uint16Array} options.batchIds The batch ids for each polyline. + * @param {BoundingSphere} options.boundingVolume The bounding volume for the entire batch of polylines. + * @param {Cesium3DTileset} options.tileset Tileset carrying minimum and maximum clamping heights. + * + * @private + */ +function Vector3DTileClampedPolylines(options) { + // these arrays hold data from the tile payload + // and are all released after the first update. + this._positions = options.positions; + this._widths = options.widths; + this._counts = options.counts; + this._batchIds = options.batchIds; + + this._ellipsoid = defaultValue(options.ellipsoid, Ellipsoid.WGS84); + this._minimumHeight = options.minimumHeight; + this._maximumHeight = options.maximumHeight; + this._center = options.center; + this._rectangle = options.rectangle; + + this._boundingVolume = options.boundingVolume; + this._batchTable = options.batchTable; + + this._va = undefined; + this._sp = undefined; + this._rs = undefined; + this._uniformMap = undefined; + this._command = undefined; + + this._transferrableBatchIds = undefined; + this._packedBuffer = undefined; + this._tileset = options.tileset; + this._minimumMaximumVectorHeights = new Cartesian2( + ApproximateTerrainHeights._defaultMinTerrainHeight, + ApproximateTerrainHeights._defaultMaxTerrainHeight + ); + + // Fat vertices - all information for each volume packed to a vec3 and 5 vec4s + this._startEllipsoidNormals = undefined; + this._endEllipsoidNormals = undefined; + this._startPositionAndHeights = undefined; + this._startFaceNormalAndVertexCorners = undefined; + this._endPositionAndHeights = undefined; + this._endFaceNormalAndHalfWidths = undefined; + this._vertexBatchIds = undefined; + + this._indices = undefined; + + this._constantColor = Color.clone(Color.WHITE); + this._highlightColor = this._constantColor; + + this._trianglesLength = 0; + this._geometryByteLength = 0; + + this._ready = false; + this._readyPromise = when.defer(); + + this._verticesPromise = undefined; + + var that = this; + ApproximateTerrainHeights.initialize() + .then(function () { + updateMinimumMaximumHeights(that, that._rectangle, that._ellipsoid); + }) + .otherwise(function (error) { + this._readyPromise.reject(error); + }); +} + +Object.defineProperties(Vector3DTileClampedPolylines.prototype, { + /** + * Gets the number of triangles. + * + * @memberof Vector3DTileClampedPolylines.prototype + * + * @type {Number} + * @readonly + */ + trianglesLength: { + get: function () { + return this._trianglesLength; + }, + }, + + /** + * Gets the geometry memory in bytes. + * + * @memberof Vector3DTileClampedPolylines.prototype + * + * @type {Number} + * @readonly + */ + geometryByteLength: { + get: function () { + return this._geometryByteLength; + }, + }, + + /** + * Gets a promise that resolves when the primitive is ready to render. + * @memberof Vector3DTileClampedPolylines.prototype + * @type {Promise} + * @readonly + */ + readyPromise: { + get: function () { + return this._readyPromise.promise; + }, + }, +}); + +function updateMinimumMaximumHeights(polylines, rectangle, ellipsoid) { + var result = ApproximateTerrainHeights.getMinimumMaximumHeights( + rectangle, + ellipsoid + ); + var minimumMaximumVectorHeights = polylines._minimumMaximumVectorHeights; + minimumMaximumVectorHeights.x = result.minimumTerrainHeight; + minimumMaximumVectorHeights.y = result.maximumTerrainHeight; +} + +function packBuffer(polylines) { + var rectangle = polylines._rectangle; + var minimumHeight = polylines._minimumHeight; + var maximumHeight = polylines._maximumHeight; + var ellipsoid = polylines._ellipsoid; + var center = polylines._center; + + var packedLength = + 2 + + Rectangle.packedLength + + Ellipsoid.packedLength + + Cartesian3.packedLength; + var packedBuffer = new Float64Array(packedLength); + + var offset = 0; + packedBuffer[offset++] = minimumHeight; + packedBuffer[offset++] = maximumHeight; + + Rectangle.pack(rectangle, packedBuffer, offset); + offset += Rectangle.packedLength; + + Ellipsoid.pack(ellipsoid, packedBuffer, offset); + offset += Ellipsoid.packedLength; + + Cartesian3.pack(center, packedBuffer, offset); + + return packedBuffer; +} + +var createVerticesTaskProcessor = new TaskProcessor( + "createVectorTileClampedPolylines" +); +var attributeLocations = { + startEllipsoidNormal: 0, + endEllipsoidNormal: 1, + startPositionAndHeight: 2, + endPositionAndHeight: 3, + startFaceNormalAndVertexCorner: 4, + endFaceNormalAndHalfWidth: 5, + a_batchId: 6, +}; + +function createVertexArray(polylines, context) { + if (defined(polylines._va)) { + return; + } + + if (!defined(polylines._verticesPromise)) { + var positions = polylines._positions; + var widths = polylines._widths; + var counts = polylines._counts; + var batchIds = polylines._transferrableBatchIds; + + var packedBuffer = polylines._packedBuffer; + + if (!defined(packedBuffer)) { + // Copy because they may be the views on the same buffer. + positions = polylines._positions = arraySlice(positions); + widths = polylines._widths = arraySlice(widths); + counts = polylines._counts = arraySlice(counts); + + batchIds = polylines._transferrableBatchIds = arraySlice( + polylines._batchIds + ); + + packedBuffer = polylines._packedBuffer = packBuffer(polylines); + } + + var transferrableObjects = [ + positions.buffer, + widths.buffer, + counts.buffer, + batchIds.buffer, + packedBuffer.buffer, + ]; + var parameters = { + positions: positions.buffer, + widths: widths.buffer, + counts: counts.buffer, + batchIds: batchIds.buffer, + packedBuffer: packedBuffer.buffer, + }; + + var verticesPromise = (polylines._verticesPromise = createVerticesTaskProcessor.scheduleTask( + parameters, + transferrableObjects + )); + if (!defined(verticesPromise)) { + // Postponed + return; + } + + when(verticesPromise, function (result) { + polylines._startEllipsoidNormals = new Float32Array( + result.startEllipsoidNormals + ); + polylines._endEllipsoidNormals = new Float32Array( + result.endEllipsoidNormals + ); + polylines._startPositionAndHeights = new Float32Array( + result.startPositionAndHeights + ); + polylines._startFaceNormalAndVertexCorners = new Float32Array( + result.startFaceNormalAndVertexCorners + ); + polylines._endPositionAndHeights = new Float32Array( + result.endPositionAndHeights + ); + polylines._endFaceNormalAndHalfWidths = new Float32Array( + result.endFaceNormalAndHalfWidths + ); + polylines._vertexBatchIds = new Uint16Array(result.vertexBatchIds); + + var indexDatatype = result.indexDatatype; + polylines._indices = + indexDatatype === IndexDatatype.UNSIGNED_SHORT + ? new Uint16Array(result.indices) + : new Uint32Array(result.indices); + + polylines._ready = true; + }).otherwise(function (error) { + polylines._readyPromise.reject(error); + }); + } + + if (polylines._ready && !defined(polylines._va)) { + var startEllipsoidNormals = polylines._startEllipsoidNormals; + var endEllipsoidNormals = polylines._endEllipsoidNormals; + var startPositionAndHeights = polylines._startPositionAndHeights; + var endPositionAndHeights = polylines._endPositionAndHeights; + var startFaceNormalAndVertexCorners = + polylines._startFaceNormalAndVertexCorners; + var endFaceNormalAndHalfWidths = polylines._endFaceNormalAndHalfWidths; + var batchIdAttribute = polylines._vertexBatchIds; + + var indices = polylines._indices; + + var byteLength = + startEllipsoidNormals.byteLength + endEllipsoidNormals.byteLength; + byteLength += + startPositionAndHeights.byteLength + endPositionAndHeights.byteLength; + byteLength += + startFaceNormalAndVertexCorners.byteLength + + endFaceNormalAndHalfWidths.byteLength; + byteLength += batchIdAttribute.byteLength + indices.byteLength; + + polylines._trianglesLength = indices.length / 3; + polylines._geometryByteLength = byteLength; + + var startEllipsoidNormalsBuffer = Buffer.createVertexBuffer({ + context: context, + typedArray: startEllipsoidNormals, + usage: BufferUsage.STATIC_DRAW, + }); + var endEllipsoidNormalsBuffer = Buffer.createVertexBuffer({ + context: context, + typedArray: endEllipsoidNormals, + usage: BufferUsage.STATIC_DRAW, + }); + var startPositionAndHeightsBuffer = Buffer.createVertexBuffer({ + context: context, + typedArray: startPositionAndHeights, + usage: BufferUsage.STATIC_DRAW, + }); + var endPositionAndHeightsBuffer = Buffer.createVertexBuffer({ + context: context, + typedArray: endPositionAndHeights, + usage: BufferUsage.STATIC_DRAW, + }); + var startFaceNormalAndVertexCornersBuffer = Buffer.createVertexBuffer({ + context: context, + typedArray: startFaceNormalAndVertexCorners, + usage: BufferUsage.STATIC_DRAW, + }); + var endFaceNormalAndHalfWidthsBuffer = Buffer.createVertexBuffer({ + context: context, + typedArray: endFaceNormalAndHalfWidths, + usage: BufferUsage.STATIC_DRAW, + }); + var batchIdAttributeBuffer = Buffer.createVertexBuffer({ + context: context, + typedArray: batchIdAttribute, + usage: BufferUsage.STATIC_DRAW, + }); + + var indexBuffer = Buffer.createIndexBuffer({ + context: context, + typedArray: indices, + usage: BufferUsage.STATIC_DRAW, + indexDatatype: + indices.BYTES_PER_ELEMENT === 2 + ? IndexDatatype.UNSIGNED_SHORT + : IndexDatatype.UNSIGNED_INT, + }); + + var vertexAttributes = [ + { + index: attributeLocations.startEllipsoidNormal, + vertexBuffer: startEllipsoidNormalsBuffer, + componentDatatype: ComponentDatatype.FLOAT, + componentsPerAttribute: 3, + }, + { + index: attributeLocations.endEllipsoidNormal, + vertexBuffer: endEllipsoidNormalsBuffer, + componentDatatype: ComponentDatatype.FLOAT, + componentsPerAttribute: 3, + }, + { + index: attributeLocations.startPositionAndHeight, + vertexBuffer: startPositionAndHeightsBuffer, + componentDatatype: ComponentDatatype.FLOAT, + componentsPerAttribute: 4, + }, + { + index: attributeLocations.endPositionAndHeight, + vertexBuffer: endPositionAndHeightsBuffer, + componentDatatype: ComponentDatatype.FLOAT, + componentsPerAttribute: 4, + }, + { + index: attributeLocations.startFaceNormalAndVertexCorner, + vertexBuffer: startFaceNormalAndVertexCornersBuffer, + componentDatatype: ComponentDatatype.FLOAT, + componentsPerAttribute: 4, + }, + { + index: attributeLocations.endFaceNormalAndHalfWidth, + vertexBuffer: endFaceNormalAndHalfWidthsBuffer, + componentDatatype: ComponentDatatype.FLOAT, + componentsPerAttribute: 4, + }, + { + index: attributeLocations.a_batchId, + vertexBuffer: batchIdAttributeBuffer, + componentDatatype: ComponentDatatype.UNSIGNED_SHORT, + componentsPerAttribute: 1, + }, + ]; + + polylines._va = new VertexArray({ + context: context, + attributes: vertexAttributes, + indexBuffer: indexBuffer, + }); + + polylines._positions = undefined; + polylines._widths = undefined; + polylines._counts = undefined; + + polylines._ellipsoid = undefined; + polylines._minimumHeight = undefined; + polylines._maximumHeight = undefined; + polylines._rectangle = undefined; + + polylines._transferrableBatchIds = undefined; + polylines._packedBuffer = undefined; + + polylines._startEllipsoidNormals = undefined; + polylines._endEllipsoidNormals = undefined; + polylines._startPositionAndHeights = undefined; + polylines._startFaceNormalAndVertexCorners = undefined; + polylines._endPositionAndHeights = undefined; + polylines._endFaceNormalAndHalfWidths = undefined; + polylines._vertexBatchIds = undefined; + + polylines._indices = undefined; + + polylines._readyPromise.resolve(); + } +} + +var modifiedModelViewScratch = new Matrix4(); +var rtcScratch = new Cartesian3(); + +function createUniformMap(primitive, context) { + if (defined(primitive._uniformMap)) { + return; + } + + primitive._uniformMap = { + u_modifiedModelView: function () { + var viewMatrix = context.uniformState.view; + Matrix4.clone(viewMatrix, modifiedModelViewScratch); + Matrix4.multiplyByPoint( + modifiedModelViewScratch, + primitive._center, + rtcScratch + ); + Matrix4.setTranslation( + modifiedModelViewScratch, + rtcScratch, + modifiedModelViewScratch + ); + return modifiedModelViewScratch; + }, + u_highlightColor: function () { + return primitive._highlightColor; + }, + u_minimumMaximumVectorHeights: function () { + return primitive._minimumMaximumVectorHeights; + }, + }; +} + +function getRenderState(mask3DTiles) { + return RenderState.fromCache({ + cull: { + enabled: true, // prevent double-draw. Geometry is "inverted" (reversed winding order) so we're drawing backfaces. + }, + blending: BlendingState.ALPHA_BLEND, + depthMask: false, + stencilTest: { + enabled: mask3DTiles, + frontFunction: StencilFunction.EQUAL, + frontOperation: { + fail: StencilOperation.KEEP, + zFail: StencilOperation.KEEP, + zPass: StencilOperation.KEEP, + }, + backFunction: StencilFunction.EQUAL, + backOperation: { + fail: StencilOperation.KEEP, + zFail: StencilOperation.KEEP, + zPass: StencilOperation.KEEP, + }, + reference: StencilConstants.CESIUM_3D_TILE_MASK, + mask: StencilConstants.CESIUM_3D_TILE_MASK, + }, + }); +} + +function createRenderStates(primitive) { + if (defined(primitive._rs)) { + return; + } + + primitive._rs = getRenderState(false); + primitive._rs3DTiles = getRenderState(true); +} + +function createShaders(primitive, context) { + if (defined(primitive._sp)) { + return; + } + + var batchTable = primitive._batchTable; + + var vsSource = batchTable.getVertexShaderCallback( + false, + "a_batchId", + undefined + )(Vector3DTileClampedPolylinesVS); + var fsSource = batchTable.getFragmentShaderCallback()( + Vector3DTileClampedPolylinesFS, + false, + undefined + ); + + var vs = new ShaderSource({ + defines: [ + "VECTOR_TILE", + !FeatureDetection.isInternetExplorer() ? "CLIP_POLYLINE" : "", + ], + sources: [PolylineCommon, vsSource], + }); + var fs = new ShaderSource({ + defines: ["VECTOR_TILE"], + sources: [fsSource], + }); + + primitive._sp = ShaderProgram.fromCache({ + context: context, + vertexShaderSource: vs, + fragmentShaderSource: fs, + attributeLocations: attributeLocations, + }); +} + +function queueCommands(primitive, frameState) { + var command = primitive._command; + if (!defined(primitive._command)) { + var uniformMap = primitive._batchTable.getUniformMapCallback()( + primitive._uniformMap + ); + command = primitive._command = new DrawCommand({ + owner: primitive, + vertexArray: primitive._va, + renderState: primitive._rs, + shaderProgram: primitive._sp, + uniformMap: uniformMap, + boundingVolume: primitive._boundingVolume, + pass: Pass.TERRAIN_CLASSIFICATION, + pickId: primitive._batchTable.getPickId(), + }); + + var derivedTilesetCommand = DrawCommand.shallowClone( + command, + command.derivedCommands.tileset + ); + derivedTilesetCommand.renderState = primitive._rs3DTiles; + derivedTilesetCommand.pass = Pass.CESIUM_3D_TILE_CLASSIFICATION; + command.derivedCommands.tileset = derivedTilesetCommand; + } + + var classificationType = primitive._tileset.classificationType; + if ( + classificationType === ClassificationType.TERRAIN || + classificationType === ClassificationType.BOTH + ) { + frameState.commandList.push(command); + } + if ( + classificationType === ClassificationType.CESIUM_3D_TILE || + classificationType === ClassificationType.BOTH + ) { + frameState.commandList.push(command.derivedCommands.tileset); + } +} + +/** + * Creates features for each polyline and places it at the batch id index of features. + * + * @param {Vector3DTileContent} content The vector tile content. + * @param {Cesium3DTileFeature[]} features An array of features where the polygon features will be placed. + */ +Vector3DTileClampedPolylines.prototype.createFeatures = function ( + content, + features +) { + var batchIds = this._batchIds; + var length = batchIds.length; + for (var i = 0; i < length; ++i) { + var batchId = batchIds[i]; + features[batchId] = new Cesium3DTileFeature(content, batchId); + } +}; + +/** + * Colors the entire tile when enabled is true. The resulting color will be (polyline batch table color * color). + * + * @param {Boolean} enabled Whether to enable debug coloring. + * @param {Color} color The debug color. + */ +Vector3DTileClampedPolylines.prototype.applyDebugSettings = function ( + enabled, + color +) { + this._highlightColor = enabled ? color : this._constantColor; +}; + +function clearStyle(polygons, features) { + var batchIds = polygons._batchIds; + var length = batchIds.length; + for (var i = 0; i < length; ++i) { + var batchId = batchIds[i]; + var feature = features[batchId]; + + feature.show = true; + feature.color = Color.WHITE; + } +} + +var scratchColor = new Color(); + +var DEFAULT_COLOR_VALUE = Color.WHITE; +var DEFAULT_SHOW_VALUE = true; + +/** + * Apply a style to the content. + * + * @param {Cesium3DTileStyle} style The style. + * @param {Cesium3DTileFeature[]} features The dictionary of features. + */ +Vector3DTileClampedPolylines.prototype.applyStyle = function (style, features) { + if (!defined(style)) { + clearStyle(this, features); + return; + } + + var batchIds = this._batchIds; + var length = batchIds.length; + for (var i = 0; i < length; ++i) { + var batchId = batchIds[i]; + var feature = features[batchId]; + + feature.color = defined(style.color) + ? style.color.evaluateColor(feature, scratchColor) + : DEFAULT_COLOR_VALUE; + feature.show = defined(style.show) + ? style.show.evaluate(feature) + : DEFAULT_SHOW_VALUE; + } +}; + +/** + * Updates the batches and queues the commands for rendering. + * + * @param {FrameState} frameState The current frame state. + */ +Vector3DTileClampedPolylines.prototype.update = function (frameState) { + var context = frameState.context; + + createVertexArray(this, context); + createUniformMap(this, context); + createShaders(this, context); + createRenderStates(this); + + if (!this._ready) { + return; + } + + var passes = frameState.passes; + if (passes.render || passes.pick) { + queueCommands(this, frameState); + } +}; + +/** + * Returns true if this object was destroyed; otherwise, false. + *

+ * If this object was destroyed, it should not be used; calling any function other than + * isDestroyed will result in a {@link DeveloperError} exception. + *

+ * + * @returns {Boolean} true if this object was destroyed; otherwise, false. + */ +Vector3DTileClampedPolylines.prototype.isDestroyed = function () { + return false; +}; + +/** + * Destroys the WebGL resources held by this object. Destroying an object allows for deterministic + * release of WebGL resources, instead of relying on the garbage collector to destroy this object. + *

+ * Once an object is destroyed, it should not be used; calling any function other than + * isDestroyed will result in a {@link DeveloperError} exception. Therefore, + * assign the return value (undefined) to the object as done in the example. + *

+ * + * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called. + */ +Vector3DTileClampedPolylines.prototype.destroy = function () { + this._va = this._va && this._va.destroy(); + this._sp = this._sp && this._sp.destroy(); + return destroyObject(this); +}; +export default Vector3DTileClampedPolylines; diff --git a/Source/Scene/Vector3DTileContent.js b/Source/Scene/Vector3DTileContent.js index cffa793b4e..f1d0080704 100644 --- a/Source/Scene/Vector3DTileContent.js +++ b/Source/Scene/Vector3DTileContent.js @@ -16,6 +16,8 @@ import Cesium3DTileFeatureTable from "./Cesium3DTileFeatureTable.js"; import Vector3DTilePoints from "./Vector3DTilePoints.js"; import Vector3DTilePolygons from "./Vector3DTilePolygons.js"; import Vector3DTilePolylines from "./Vector3DTilePolylines.js"; +import Vector3DTileClampedPolylines from "./Vector3DTileClampedPolylines.js"; +import decodeVectorPolylinePositions from "../Core/decodeVectorPolylinePositions.js"; /** * Represents the contents of a @@ -248,6 +250,14 @@ function getBatchIds(featureTableJson, featureTableBinary) { var sizeOfUint32 = Uint32Array.BYTES_PER_ELEMENT; +function createFloatingPolylines(options) { + return new Vector3DTilePolylines(options); +} + +function createClampedPolylines(options) { + return new Vector3DTileClampedPolylines(options); +} + function initialize(content, arrayBuffer, byteOffset) { byteOffset = defaultValue(byteOffset, 0); @@ -533,7 +543,32 @@ function initialize(content, arrayBuffer, byteOffset) { ); byteOffset += polylinePositionByteLength; - content._polylines = new Vector3DTilePolylines({ + var tileset = content._tileset; + var examineVectorLinesFunction = tileset.examineVectorLinesFunction; + if (defined(examineVectorLinesFunction)) { + var decodedPositions = decodeVectorPolylinePositions( + new Uint16Array(polylinePositions), + rectangle, + minHeight, + maxHeight, + Ellipsoid.WGS84 + ); + examineVectorLines( + decodedPositions, + polylineCounts, + batchIds.polylines, + batchTable, + content.url, + examineVectorLinesFunction + ); + } + + var createPolylines = createFloatingPolylines; + if (defined(tileset.classificationType)) { + createPolylines = createClampedPolylines; + } + + content._polylines = createPolylines({ positions: polylinePositions, widths: widths, counts: polylineCounts, @@ -544,6 +579,7 @@ function initialize(content, arrayBuffer, byteOffset) { rectangle: rectangle, boundingVolume: content.tile.boundingVolume.boundingVolume, batchTable: batchTable, + tileset: tileset, }); } @@ -664,6 +700,9 @@ Vector3DTileContent.prototype.update = function (tileset, frameState) { .all([pointsPromise, polygonPromise, polylinePromise]) .then(function () { that._readyPromise.resolve(that); + }) + .otherwise(function (error) { + that._readyPromise.reject(error); }); } }; @@ -679,4 +718,24 @@ Vector3DTileContent.prototype.destroy = function () { this._batchTable = this._batchTable && this._batchTable.destroy(); return destroyObject(this); }; + +function examineVectorLines( + positions, + counts, + batchIds, + batchTable, + url, + callback +) { + var countsLength = counts.length; + var polylineStart = 0; + for (var i = 0; i < countsLength; i++) { + var count = counts[i] * 3; + var linePositions = positions.slice(polylineStart, polylineStart + count); + polylineStart += count; + + callback(linePositions, batchIds[i], url, batchTable); + } +} + export default Vector3DTileContent; diff --git a/Source/Shaders/Vector3DTileClampedPolylinesFS.glsl b/Source/Shaders/Vector3DTileClampedPolylinesFS.glsl new file mode 100644 index 0000000000..444edf3e0c --- /dev/null +++ b/Source/Shaders/Vector3DTileClampedPolylinesFS.glsl @@ -0,0 +1,52 @@ +#ifdef GL_EXT_frag_depth +#extension GL_EXT_frag_depth : enable +#endif + +varying vec4 v_startPlaneEC; +varying vec4 v_endPlaneEC; +varying vec4 v_rightPlaneEC; +varying float v_halfWidth; +varying vec3 v_volumeUpEC; + +uniform vec4 u_highlightColor; +void main() +{ + float logDepthOrDepth = czm_branchFreeTernary(czm_sceneMode == czm_sceneMode2D, gl_FragCoord.z, czm_unpackDepth(texture2D(czm_globeDepthTexture, gl_FragCoord.xy / czm_viewport.zw))); + + // Discard for sky + if (logDepthOrDepth == 0.0) { +#ifdef DEBUG_SHOW_VOLUME + gl_FragColor = vec4(0.0, 0.0, 1.0, 0.5); + return; +#else // DEBUG_SHOW_VOLUME + discard; +#endif // DEBUG_SHOW_VOLUME + } + + vec4 eyeCoordinate = czm_windowToEyeCoordinates(gl_FragCoord.xy, logDepthOrDepth); + eyeCoordinate /= eyeCoordinate.w; + + float halfMaxWidth = v_halfWidth * czm_metersPerPixel(eyeCoordinate); + + // Expand halfMaxWidth if direction to camera is almost perpendicular with the volume's up direction + halfMaxWidth += halfMaxWidth * (1.0 - dot(-normalize(eyeCoordinate.xyz), v_volumeUpEC)); + + // Check distance of the eye coordinate against the right-facing plane + float widthwiseDistance = czm_planeDistance(v_rightPlaneEC, eyeCoordinate.xyz); + + // Check eye coordinate against the mitering planes + float distanceFromStart = czm_planeDistance(v_startPlaneEC, eyeCoordinate.xyz); + float distanceFromEnd = czm_planeDistance(v_endPlaneEC, eyeCoordinate.xyz); + + if (abs(widthwiseDistance) > halfMaxWidth || distanceFromStart < 0.0 || distanceFromEnd < 0.0) { +#ifdef DEBUG_SHOW_VOLUME + gl_FragColor = vec4(logDepthOrDepth, 0.0, 0.0, 0.5); + return; +#else // DEBUG_SHOW_VOLUME + discard; +#endif // DEBUG_SHOW_VOLUME + } + gl_FragColor = u_highlightColor; + + czm_writeDepthClamp(); +} \ No newline at end of file diff --git a/Source/Shaders/Vector3DTileClampedPolylinesVS.glsl b/Source/Shaders/Vector3DTileClampedPolylinesVS.glsl new file mode 100644 index 0000000000..eaa3e6c365 --- /dev/null +++ b/Source/Shaders/Vector3DTileClampedPolylinesVS.glsl @@ -0,0 +1,86 @@ +attribute vec3 startEllipsoidNormal; +attribute vec3 endEllipsoidNormal; +attribute vec4 startPositionAndHeight; +attribute vec4 endPositionAndHeight; +attribute vec4 startFaceNormalAndVertexCorner; +attribute vec4 endFaceNormalAndHalfWidth; +attribute float a_batchId; + +uniform mat4 u_modifiedModelView; +uniform vec2 u_minimumMaximumVectorHeights; + +varying vec4 v_startPlaneEC; +varying vec4 v_endPlaneEC; +varying vec4 v_rightPlaneEC; +varying float v_halfWidth; +varying vec3 v_volumeUpEC; + +void main() +{ + vec3 scratchNormal; + // vertex corner IDs + // 3-----------7 + // /| left /| + // / | 1 / | + // 2-----------6 5 end + // | / | / + // start |/ right |/ + // 0-----------4 + // + float isEnd = floor(startFaceNormalAndVertexCorner.w * 0.251); // 0 for front, 1 for end + float isTop = floor(startFaceNormalAndVertexCorner.w * mix(0.51, 0.19, isEnd)); // 0 for bottom, 1 for top + + scratchNormal = endPositionAndHeight.xyz - startPositionAndHeight.xyz; // scratchNormal = forward + vec3 right = normalize(cross(scratchNormal, startEllipsoidNormal)); + + vec4 position = vec4(startPositionAndHeight.xyz, 1.0); + position.xyz += scratchNormal * isEnd; // scratchNormal = forward + + v_volumeUpEC = czm_normal * normalize(cross(right, scratchNormal)); + + // Push for volume height + float offset; + scratchNormal = mix(startEllipsoidNormal, endEllipsoidNormal, isEnd); // scratchNormal = ellipsoidNormal + + // offset height to create volume + offset = mix(startPositionAndHeight.w, endPositionAndHeight.w, isEnd); + offset = mix(u_minimumMaximumVectorHeights.y, u_minimumMaximumVectorHeights.x, isTop) - offset; + position.xyz += offset * scratchNormal; // scratchNormal = ellipsoidNormal + + // move from RTC to EC + position = u_modifiedModelView * position; + right = czm_normal * right; + + // Push for width in a direction that is in the start or end plane and in a plane with right + // N = normalEC ("right-facing" direction for push) + // R = right + // p = angle between N and R + // w = distance to push along R if R == N + // d = distance to push along N + // + // N R + // { \ p| } * cos(p) = dot(N, R) = w / d + // d\ \ | |w * d = w / dot(N, R) + // { \| } + // o---------- polyline segment ----> + // + scratchNormal = mix(-startFaceNormalAndVertexCorner.xyz, endFaceNormalAndHalfWidth.xyz, isEnd); + scratchNormal = cross(scratchNormal, mix(startEllipsoidNormal, endEllipsoidNormal, isEnd)); + scratchNormal = czm_normal * normalize(scratchNormal); + + offset = 2.0 * endFaceNormalAndHalfWidth.w * max(0.0, czm_metersPerPixel(position)); // offset = widthEC + offset = offset / dot(scratchNormal, right); + position.xyz += scratchNormal * (offset * sign(0.5 - mod(startFaceNormalAndVertexCorner.w, 2.0))); + + gl_Position = czm_depthClamp(czm_projection * position); + + position = u_modifiedModelView * vec4(startPositionAndHeight.xyz, 1.0); + scratchNormal = czm_normal * startFaceNormalAndVertexCorner.xyz; + v_startPlaneEC = vec4(scratchNormal, -dot(scratchNormal, position.xyz)); + v_rightPlaneEC = vec4(right, -dot(right, position.xyz)); + + position = u_modifiedModelView * vec4(endPositionAndHeight.xyz, 1.0); + scratchNormal = czm_normal * endFaceNormalAndHalfWidth.xyz; + v_endPlaneEC = vec4(scratchNormal, -dot(scratchNormal, position.xyz)); + v_halfWidth = endFaceNormalAndHalfWidth.w; +} \ No newline at end of file diff --git a/Source/WorkersES6/createVectorTileClampedPolylines.js b/Source/WorkersES6/createVectorTileClampedPolylines.js new file mode 100644 index 0000000000..3a41d5b197 --- /dev/null +++ b/Source/WorkersES6/createVectorTileClampedPolylines.js @@ -0,0 +1,507 @@ +import AttributeCompression from "../Core/AttributeCompression.js"; +import Cartesian3 from "../Core/Cartesian3.js"; +import Cartographic from "../Core/Cartographic.js"; +import Ellipsoid from "../Core/Ellipsoid.js"; +import IndexDatatype from "../Core/IndexDatatype.js"; +import CesiumMath from "../Core/Math.js"; +import Rectangle from "../Core/Rectangle.js"; +import createTaskProcessorWorker from "./createTaskProcessorWorker.js"; + +var MAX_SHORT = 32767; +var MITER_BREAK = Math.cos(CesiumMath.toRadians(150.0)); + +var scratchBVCartographic = new Cartographic(); +var scratchEncodedPosition = new Cartesian3(); + +function decodePositionsToRtc( + uBuffer, + vBuffer, + heightBuffer, + rectangle, + minimumHeight, + maximumHeight, + ellipsoid, + center +) { + var positionsLength = uBuffer.length; + var decodedPositions = new Float32Array(positionsLength * 3); + for (var i = 0; i < positionsLength; ++i) { + var u = uBuffer[i]; + var v = vBuffer[i]; + var h = heightBuffer[i]; + + var lon = CesiumMath.lerp(rectangle.west, rectangle.east, u / MAX_SHORT); + var lat = CesiumMath.lerp(rectangle.south, rectangle.north, v / MAX_SHORT); + var alt = CesiumMath.lerp(minimumHeight, maximumHeight, h / MAX_SHORT); + + var cartographic = Cartographic.fromRadians( + lon, + lat, + alt, + scratchBVCartographic + ); + var decodedPosition = ellipsoid.cartographicToCartesian( + cartographic, + scratchEncodedPosition + ); + var rtc = Cartesian3.subtract( + decodedPosition, + center, + scratchEncodedPosition + ); + Cartesian3.pack(rtc, decodedPositions, i * 3); + } + return decodedPositions; +} + +var previousCompressedCartographicScratch = new Cartographic(); +var currentCompressedCartographicScratch = new Cartographic(); +function removeDuplicates(uBuffer, vBuffer, heightBuffer, counts) { + var countsLength = counts.length; + var positionsLength = uBuffer.length; + var markRemoval = new Uint8Array(positionsLength); + var previous = previousCompressedCartographicScratch; + var current = currentCompressedCartographicScratch; + var offset = 0; + for (var i = 0; i < countsLength; i++) { + var count = counts[i]; + var updatedCount = count; + for (var j = 1; j < count; j++) { + var index = offset + j; + var previousIndex = index - 1; + current.longitude = uBuffer[index]; + current.latitude = vBuffer[index]; + previous.longitude = uBuffer[previousIndex]; + previous.latitude = vBuffer[previousIndex]; + + if (Cartographic.equals(current, previous)) { + updatedCount--; + markRemoval[previousIndex] = 1; + } + } + counts[i] = updatedCount; + offset += count; + } + + var nextAvailableIndex = 0; + for (var k = 0; k < positionsLength; k++) { + if (markRemoval[k] !== 1) { + uBuffer[nextAvailableIndex] = uBuffer[k]; + vBuffer[nextAvailableIndex] = vBuffer[k]; + heightBuffer[nextAvailableIndex] = heightBuffer[k]; + nextAvailableIndex++; + } + } +} + +function VertexAttributesAndIndices(volumesCount) { + var vertexCount = volumesCount * 8; + var vec3Floats = vertexCount * 3; + var vec4Floats = vertexCount * 4; + this.startEllipsoidNormals = new Float32Array(vec3Floats); + this.endEllipsoidNormals = new Float32Array(vec3Floats); + this.startPositionAndHeights = new Float32Array(vec4Floats); + this.startFaceNormalAndVertexCorners = new Float32Array(vec4Floats); + this.endPositionAndHeights = new Float32Array(vec4Floats); + this.endFaceNormalAndHalfWidths = new Float32Array(vec4Floats); + this.vertexBatchIds = new Uint16Array(vertexCount); + + this.indices = IndexDatatype.createTypedArray(vertexCount, 36 * volumesCount); + + this.vec3Offset = 0; + this.vec4Offset = 0; + this.batchIdOffset = 0; + this.indexOffset = 0; + + this.volumeStartIndex = 0; +} + +var towardCurrScratch = new Cartesian3(); +var towardNextScratch = new Cartesian3(); +function computeMiteredNormal( + previousPosition, + position, + nextPosition, + ellipsoidSurfaceNormal, + result +) { + var towardNext = Cartesian3.subtract( + nextPosition, + position, + towardNextScratch + ); + var towardCurr = Cartesian3.subtract( + position, + previousPosition, + towardCurrScratch + ); + Cartesian3.normalize(towardNext, towardNext); + Cartesian3.normalize(towardCurr, towardCurr); + + if (Cartesian3.dot(towardNext, towardCurr) < MITER_BREAK) { + towardCurr = Cartesian3.multiplyByScalar( + towardCurr, + -1.0, + towardCurrScratch + ); + } + + Cartesian3.add(towardNext, towardCurr, result); + if (Cartesian3.equals(result, Cartesian3.ZERO)) { + result = Cartesian3.subtract(previousPosition, position); + } + + // Make sure the normal is orthogonal to the ellipsoid surface normal + Cartesian3.cross(result, ellipsoidSurfaceNormal, result); + Cartesian3.cross(ellipsoidSurfaceNormal, result, result); + Cartesian3.normalize(result, result); + return result; +} + +// Winding order is reversed so each segment's volume is inside-out +// 3-----------7 +// /| left /| +// / | 1 / | +// 2-----------6 5 end +// | / | / +// start |/ right |/ +// 0-----------4 +// +var REFERENCE_INDICES = [ + 0, + 2, + 6, + 0, + 6, + 4, // right + 0, + 1, + 3, + 0, + 3, + 2, // start face + 0, + 4, + 5, + 0, + 5, + 1, // bottom + 5, + 3, + 1, + 5, + 7, + 3, // left + 7, + 5, + 4, + 7, + 4, + 6, // end face + 7, + 6, + 2, + 7, + 2, + 3, // top +]; +var REFERENCE_INDICES_LENGTH = REFERENCE_INDICES.length; + +var positionScratch = new Cartesian3(); +var scratchStartEllipsoidNormal = new Cartesian3(); +var scratchStartFaceNormal = new Cartesian3(); +var scratchEndEllipsoidNormal = new Cartesian3(); +var scratchEndFaceNormal = new Cartesian3(); +VertexAttributesAndIndices.prototype.addVolume = function ( + preStartRTC, + startRTC, + endRTC, + postEndRTC, + startHeight, + endHeight, + halfWidth, + batchId, + center, + ellipsoid +) { + var position = Cartesian3.add(startRTC, center, positionScratch); + var startEllipsoidNormal = ellipsoid.geodeticSurfaceNormal( + position, + scratchStartEllipsoidNormal + ); + position = Cartesian3.add(endRTC, center, positionScratch); + var endEllipsoidNormal = ellipsoid.geodeticSurfaceNormal( + position, + scratchEndEllipsoidNormal + ); + + var startFaceNormal = computeMiteredNormal( + preStartRTC, + startRTC, + endRTC, + startEllipsoidNormal, + scratchStartFaceNormal + ); + var endFaceNormal = computeMiteredNormal( + postEndRTC, + endRTC, + startRTC, + endEllipsoidNormal, + scratchEndFaceNormal + ); + + var startEllipsoidNormals = this.startEllipsoidNormals; + var endEllipsoidNormals = this.endEllipsoidNormals; + var startPositionAndHeights = this.startPositionAndHeights; + var startFaceNormalAndVertexCorners = this.startFaceNormalAndVertexCorners; + var endPositionAndHeights = this.endPositionAndHeights; + var endFaceNormalAndHalfWidths = this.endFaceNormalAndHalfWidths; + var vertexBatchIds = this.vertexBatchIds; + + var batchIdOffset = this.batchIdOffset; + var vec3Offset = this.vec3Offset; + var vec4Offset = this.vec4Offset; + + var i; + for (i = 0; i < 8; i++) { + Cartesian3.pack(startEllipsoidNormal, startEllipsoidNormals, vec3Offset); + Cartesian3.pack(endEllipsoidNormal, endEllipsoidNormals, vec3Offset); + + Cartesian3.pack(startRTC, startPositionAndHeights, vec4Offset); + startPositionAndHeights[vec4Offset + 3] = startHeight; + + Cartesian3.pack(endRTC, endPositionAndHeights, vec4Offset); + endPositionAndHeights[vec4Offset + 3] = endHeight; + + Cartesian3.pack( + startFaceNormal, + startFaceNormalAndVertexCorners, + vec4Offset + ); + startFaceNormalAndVertexCorners[vec4Offset + 3] = i; + + Cartesian3.pack(endFaceNormal, endFaceNormalAndHalfWidths, vec4Offset); + endFaceNormalAndHalfWidths[vec4Offset + 3] = halfWidth; + + vertexBatchIds[batchIdOffset++] = batchId; + + vec3Offset += 3; + vec4Offset += 4; + } + + this.batchIdOffset = batchIdOffset; + this.vec3Offset = vec3Offset; + this.vec4Offset = vec4Offset; + var indices = this.indices; + var volumeStartIndex = this.volumeStartIndex; + + var indexOffset = this.indexOffset; + for (i = 0; i < REFERENCE_INDICES_LENGTH; i++) { + indices[indexOffset + i] = REFERENCE_INDICES[i] + volumeStartIndex; + } + + this.volumeStartIndex += 8; + this.indexOffset += REFERENCE_INDICES_LENGTH; +}; + +var scratchRectangle = new Rectangle(); +var scratchEllipsoid = new Ellipsoid(); +var scratchCenter = new Cartesian3(); + +var scratchPrev = new Cartesian3(); +var scratchP0 = new Cartesian3(); +var scratchP1 = new Cartesian3(); +var scratchNext = new Cartesian3(); +function createVectorTileClampedPolylines(parameters, transferableObjects) { + var encodedPositions = new Uint16Array(parameters.positions); + var widths = new Uint16Array(parameters.widths); + var counts = new Uint32Array(parameters.counts); + var batchIds = new Uint16Array(parameters.batchIds); + + // Unpack tile decoding parameters + var rectangle = scratchRectangle; + var ellipsoid = scratchEllipsoid; + var center = scratchCenter; + var packedBuffer = new Float64Array(parameters.packedBuffer); + + var offset = 0; + var minimumHeight = packedBuffer[offset++]; + var maximumHeight = packedBuffer[offset++]; + + Rectangle.unpack(packedBuffer, offset, rectangle); + offset += Rectangle.packedLength; + + Ellipsoid.unpack(packedBuffer, offset, ellipsoid); + offset += Ellipsoid.packedLength; + + Cartesian3.unpack(packedBuffer, offset, center); + + var i; + + // Unpack positions and generate volumes + var positionsLength = encodedPositions.length / 3; + var uBuffer = encodedPositions.subarray(0, positionsLength); + var vBuffer = encodedPositions.subarray(positionsLength, 2 * positionsLength); + var heightBuffer = encodedPositions.subarray( + 2 * positionsLength, + 3 * positionsLength + ); + AttributeCompression.zigZagDeltaDecode(uBuffer, vBuffer, heightBuffer); + + removeDuplicates(uBuffer, vBuffer, heightBuffer, counts); + + // Figure out how many volumes and how many vertices there will be. + var countsLength = counts.length; + var volumesCount = 0; + for (i = 0; i < countsLength; i++) { + var polylinePositionCount = counts[i]; + volumesCount += polylinePositionCount - 1; + } + + var attribsAndIndices = new VertexAttributesAndIndices(volumesCount); + + var positionsRTC = decodePositionsToRtc( + uBuffer, + vBuffer, + heightBuffer, + rectangle, + minimumHeight, + maximumHeight, + ellipsoid, + center + ); + + var currentPositionIndex = 0; + var currentHeightIndex = 0; + for (i = 0; i < countsLength; i++) { + var polylineVolumeCount = counts[i] - 1; + var halfWidth = widths[i] * 0.5; + var batchId = batchIds[i]; + var volumeFirstPositionIndex = currentPositionIndex; + for (var j = 0; j < polylineVolumeCount; j++) { + var volumeStart = Cartesian3.unpack( + positionsRTC, + currentPositionIndex, + scratchP0 + ); + var volumeEnd = Cartesian3.unpack( + positionsRTC, + currentPositionIndex + 3, + scratchP1 + ); + + var startHeight = heightBuffer[currentHeightIndex]; + var endHeight = heightBuffer[currentHeightIndex + 1]; + startHeight = CesiumMath.lerp( + minimumHeight, + maximumHeight, + startHeight / MAX_SHORT + ); + endHeight = CesiumMath.lerp( + minimumHeight, + maximumHeight, + endHeight / MAX_SHORT + ); + + currentHeightIndex++; + + var preStart = scratchPrev; + var postEnd = scratchNext; + if (j === 0) { + // Check if this volume is like a loop + var finalPositionIndex = + volumeFirstPositionIndex + polylineVolumeCount * 3; + var finalPosition = Cartesian3.unpack( + positionsRTC, + finalPositionIndex, + scratchPrev + ); + if (Cartesian3.equals(finalPosition, volumeStart)) { + Cartesian3.unpack(positionsRTC, finalPositionIndex - 3, preStart); + } else { + var offsetPastStart = Cartesian3.subtract( + volumeStart, + volumeEnd, + scratchPrev + ); + preStart = Cartesian3.add(offsetPastStart, volumeStart, scratchPrev); + } + } else { + Cartesian3.unpack(positionsRTC, currentPositionIndex - 3, preStart); + } + + if (j === polylineVolumeCount - 1) { + // Check if this volume is like a loop + var firstPosition = Cartesian3.unpack( + positionsRTC, + volumeFirstPositionIndex, + scratchNext + ); + if (Cartesian3.equals(firstPosition, volumeEnd)) { + Cartesian3.unpack( + positionsRTC, + volumeFirstPositionIndex + 3, + postEnd + ); + } else { + var offsetPastEnd = Cartesian3.subtract( + volumeEnd, + volumeStart, + scratchNext + ); + postEnd = Cartesian3.add(offsetPastEnd, volumeEnd, scratchNext); + } + } else { + Cartesian3.unpack(positionsRTC, currentPositionIndex + 6, postEnd); + } + + attribsAndIndices.addVolume( + preStart, + volumeStart, + volumeEnd, + postEnd, + startHeight, + endHeight, + halfWidth, + batchId, + center, + ellipsoid + ); + + currentPositionIndex += 3; + } + currentPositionIndex += 3; + currentHeightIndex++; + } + + var indices = attribsAndIndices.indices; + + transferableObjects.push(attribsAndIndices.startEllipsoidNormals.buffer); + transferableObjects.push(attribsAndIndices.endEllipsoidNormals.buffer); + transferableObjects.push(attribsAndIndices.startPositionAndHeights.buffer); + transferableObjects.push( + attribsAndIndices.startFaceNormalAndVertexCorners.buffer + ); + transferableObjects.push(attribsAndIndices.endPositionAndHeights.buffer); + transferableObjects.push(attribsAndIndices.endFaceNormalAndHalfWidths.buffer); + transferableObjects.push(attribsAndIndices.vertexBatchIds.buffer); + transferableObjects.push(indices.buffer); + + return { + indexDatatype: + indices.BYTES_PER_ELEMENT === 2 + ? IndexDatatype.UNSIGNED_SHORT + : IndexDatatype.UNSIGNED_INT, + startEllipsoidNormals: attribsAndIndices.startEllipsoidNormals.buffer, + endEllipsoidNormals: attribsAndIndices.endEllipsoidNormals.buffer, + startPositionAndHeights: attribsAndIndices.startPositionAndHeights.buffer, + startFaceNormalAndVertexCorners: + attribsAndIndices.startFaceNormalAndVertexCorners.buffer, + endPositionAndHeights: attribsAndIndices.endPositionAndHeights.buffer, + endFaceNormalAndHalfWidths: + attribsAndIndices.endFaceNormalAndHalfWidths.buffer, + vertexBatchIds: attribsAndIndices.vertexBatchIds.buffer, + indices: indices.buffer, + }; +} +export default createTaskProcessorWorker(createVectorTileClampedPolylines); diff --git a/Source/WorkersES6/createVectorTilePolylines.js b/Source/WorkersES6/createVectorTilePolylines.js index 240db47d20..4bc9f82b17 100644 --- a/Source/WorkersES6/createVectorTilePolylines.js +++ b/Source/WorkersES6/createVectorTilePolylines.js @@ -1,58 +1,10 @@ -import AttributeCompression from "../Core/AttributeCompression.js"; import Cartesian3 from "../Core/Cartesian3.js"; -import Cartographic from "../Core/Cartographic.js"; +import decodeVectorPolylinePositions from "../Core/decodeVectorPolylinePositions.js"; import Ellipsoid from "../Core/Ellipsoid.js"; import IndexDatatype from "../Core/IndexDatatype.js"; -import CesiumMath from "../Core/Math.js"; import Rectangle from "../Core/Rectangle.js"; import createTaskProcessorWorker from "./createTaskProcessorWorker.js"; -var maxShort = 32767; - -var scratchBVCartographic = new Cartographic(); -var scratchEncodedPosition = new Cartesian3(); - -function decodePositions( - positions, - rectangle, - minimumHeight, - maximumHeight, - ellipsoid -) { - var positionsLength = positions.length / 3; - var uBuffer = positions.subarray(0, positionsLength); - var vBuffer = positions.subarray(positionsLength, 2 * positionsLength); - var heightBuffer = positions.subarray( - 2 * positionsLength, - 3 * positionsLength - ); - AttributeCompression.zigZagDeltaDecode(uBuffer, vBuffer, heightBuffer); - - var decoded = new Float64Array(positions.length); - for (var i = 0; i < positionsLength; ++i) { - var u = uBuffer[i]; - var v = vBuffer[i]; - var h = heightBuffer[i]; - - var lon = CesiumMath.lerp(rectangle.west, rectangle.east, u / maxShort); - var lat = CesiumMath.lerp(rectangle.south, rectangle.north, v / maxShort); - var alt = CesiumMath.lerp(minimumHeight, maximumHeight, h / maxShort); - - var cartographic = Cartographic.fromRadians( - lon, - lat, - alt, - scratchBVCartographic - ); - var decodedPosition = ellipsoid.cartographicToCartesian( - cartographic, - scratchEncodedPosition - ); - Cartesian3.pack(decodedPosition, decoded, i * 3); - } - return decoded; -} - var scratchRectangle = new Rectangle(); var scratchEllipsoid = new Ellipsoid(); var scratchCenter = new Cartesian3(); @@ -96,7 +48,7 @@ function createVectorTilePolylines(parameters, transferableObjects) { var minimumHeight = scratchMinMaxHeights.min; var maximumHeight = scratchMinMaxHeights.max; - var positions = decodePositions( + var positions = decodeVectorPolylinePositions( encodedPositions, rectangle, minimumHeight, From ce4ac2558af45aafc2bd2b6e1d6999a0d95eb44d Mon Sep 17 00:00:00 2001 From: ebogo1 Date: Tue, 2 Mar 2021 15:11:58 -0500 Subject: [PATCH 08/76] add diff changes --- Source/Renderer/DrawCommand.js | 25 + Source/Scene/Batched3DModel3DTileContent.js | 14 +- Source/Scene/Cesium3DTile.js | 10 +- Source/Scene/Cesium3DTileset.js | 20 + Source/Scene/Scene.js | 37 + Source/Scene/TranslucentTileClassification.js | 615 +++++++++++++++ Source/Scene/View.js | 7 + .../CompareAndPackTranslucentDepth.glsl | 12 + .../CompositeTranslucentClassification.glsl | 28 + Source/Shaders/ShadowVolumeAppearanceFS.glsl | 11 +- .../TranslucentTileClassificationSpec.js | 741 ++++++++++++++++++ 11 files changed, 1512 insertions(+), 8 deletions(-) create mode 100644 Source/Scene/TranslucentTileClassification.js create mode 100644 Source/Shaders/CompareAndPackTranslucentDepth.glsl create mode 100644 Source/Shaders/PostProcessStages/CompositeTranslucentClassification.glsl create mode 100644 Specs/Scene/TranslucentTileClassificationSpec.js diff --git a/Source/Renderer/DrawCommand.js b/Source/Renderer/DrawCommand.js index 85dfe5afce..0b715b4064 100644 --- a/Source/Renderer/DrawCommand.js +++ b/Source/Renderer/DrawCommand.js @@ -43,6 +43,11 @@ function DrawCommand(options) { this._pickId = options.pickId; this._pickOnly = defaultValue(options.pickOnly, false); + this._depthForTranslucentClassification = defaultValue( + options.depthForTranslucentClassification, + false + ); + this.dirty = true; this.lastDirtyTime = 0; @@ -513,6 +518,24 @@ Object.defineProperties(DrawCommand.prototype, { } }, }, + /** + * Whether this command should be derived to draw depth for classification of translucent primitives. + * + * @memberof DrawCommand.prototype + * @type {Boolean} + * @default false + */ + depthForTranslucentClassification: { + get: function () { + return this._depthForTranslucentClassification; + }, + set: function (value) { + if (this._depthForTranslucentClassification !== value) { + this._depthForTranslucentClassification = value; + this.dirty = true; + } + }, + }, }); /** @@ -549,6 +572,8 @@ DrawCommand.shallowClone = function (command, result) { result._receiveShadows = command._receiveShadows; result._pickId = command._pickId; result._pickOnly = command._pickOnly; + result._depthForTranslucentClassification = + command._depthForTranslucentClassification; result.dirty = true; result.lastDirtyTime = 0; diff --git a/Source/Scene/Batched3DModel3DTileContent.js b/Source/Scene/Batched3DModel3DTileContent.js index 488c019071..797c2e7c38 100644 --- a/Source/Scene/Batched3DModel3DTileContent.js +++ b/Source/Scene/Batched3DModel3DTileContent.js @@ -47,6 +47,10 @@ function Batched3DModel3DTileContent( this._batchTable = undefined; this._features = undefined; + this._classificationType = tileset.noClassificationModels + ? undefined + : tileset.classificationType; + // Populate from gltf when available this._batchIdAttributeName = undefined; this._diffuseAttributeOrUniformName = {}; @@ -161,7 +165,7 @@ function getBatchIdAttributeName(gltf) { function getVertexShaderCallback(content) { return function (vs, programId) { var batchTable = content._batchTable; - var handleTranslucent = !defined(content._tileset.classificationType); + var handleTranslucent = !defined(content._classificationType); var gltf = content._model.gltf; if (defined(gltf)) { @@ -183,7 +187,7 @@ function getVertexShaderCallback(content) { function getFragmentShaderCallback(content) { return function (fs, programId) { var batchTable = content._batchTable; - var handleTranslucent = !defined(content._tileset.classificationType); + var handleTranslucent = !defined(content._classificationType); var gltf = content._model.gltf; if (defined(gltf)) { @@ -348,7 +352,7 @@ function initialize(content, arrayBuffer, byteOffset) { } var colorChangedCallback; - if (defined(tileset.classificationType)) { + if (defined(content._classificationType)) { colorChangedCallback = createColorChangedCallback(content); } @@ -403,7 +407,7 @@ function initialize(content, arrayBuffer, byteOffset) { new Matrix4() ); - if (!defined(tileset.classificationType)) { + if (!defined(content._classificationType)) { // PERFORMANCE_IDEA: patch the shader on demand, e.g., the first time show/color changes. // The pick shader still needs to be patched. content._model = new Model({ @@ -571,7 +575,7 @@ Batched3DModel3DTileContent.prototype.update = function (tileset, frameState) { if ( commandStart < commandEnd && (frameState.passes.render || frameState.passes.pick) && - !defined(tileset.classificationType) + !defined(this._classificationType) ) { this._batchTable.addDerivedCommands(frameState, commandStart); } diff --git a/Source/Scene/Cesium3DTile.js b/Source/Scene/Cesium3DTile.js index feced6246f..8408437b3d 100644 --- a/Source/Scene/Cesium3DTile.js +++ b/Source/Scene/Cesium3DTile.js @@ -34,6 +34,7 @@ import SceneMode from "./SceneMode.js"; import TileBoundingRegion from "./TileBoundingRegion.js"; import TileBoundingSphere from "./TileBoundingSphere.js"; import TileOrientedBoundingBox from "./TileOrientedBoundingBox.js"; +import Pass from "../Renderer/Pass.js"; /** * A tile in a {@link Cesium3DTileset}. When a tile is first created, its content is not loaded; @@ -1615,7 +1616,14 @@ Cesium3DTile.prototype.update = function (tileset, frameState, passOptions) { updateClippingPlanes(this, tileset); applyDebugSettings(this, tileset, frameState, passOptions); updateContent(this, tileset, frameState); - this._commandsLength = frameState.commandList.length - initCommandLength; + var commandsLength = (this._commandsLength = + frameState.commandList.length - initCommandLength); + + for (var i = 0; i < commandsLength; ++i) { + var command = frameState.commandList[initCommandLength + i]; + command.depthForTranslucentClassification = + command.pass === Pass.TRANSLUCENT; + } this.clippingPlanesDirty = false; // reset after content update }; diff --git a/Source/Scene/Cesium3DTileset.js b/Source/Scene/Cesium3DTileset.js index 243c50b30f..5c3087ffaf 100644 --- a/Source/Scene/Cesium3DTileset.js +++ b/Source/Scene/Cesium3DTileset.js @@ -102,6 +102,7 @@ import TileOrientedBoundingBox from "./TileOrientedBoundingBox.js"; * @param {Boolean} [options.debugShowRenderingStatistics=false] For debugging only. When true, draws labels to indicate the number of commands, points, triangles and features for each tile. * @param {Boolean} [options.debugShowMemoryUsage=false] For debugging only. When true, draws labels to indicate the texture and geometry memory in megabytes used by each tile. * @param {Boolean} [options.debugShowUrl=false] For debugging only. When true, draws labels to indicate the url of each tile. + * @param {Boolean} [options.noClassificationModels=false] Do not use b3dms for classification. * * @exception {DeveloperError} The tileset must be 3D Tiles version 0.0 or 1.0. * @@ -274,6 +275,11 @@ function Cesium3DTileset(options) { this._clippingPlanesOriginMatrix = undefined; // Combines the above with any run-time transforms. this._clippingPlanesOriginMatrixDirty = true; + this._noClassificationModels = defaultValue( + options.noClassificationModels, + false + ); + /** * Preload tiles when tileset.show is false. Loads tiles as if the tileset is visible but does not render them. * @@ -1662,6 +1668,20 @@ Object.defineProperties(Cesium3DTileset.prototype, { Cartesian2.clone(value, this._imageBasedLightingFactor); }, }, + + /** + * Indicates that the tileset's b3dms should be used for classification. + * + * @memberof Cesium3DTileset.prototype + * + * @type {Boolean} + * @default false + */ + noClassificationModels: { + get: function () { + return this._noClassificationModels; + }, + }, }); /** diff --git a/Source/Scene/Scene.js b/Source/Scene/Scene.js index f3b31a4c3c..3a1f2c7ff7 100644 --- a/Source/Scene/Scene.js +++ b/Source/Scene/Scene.js @@ -2658,6 +2658,28 @@ function executeCommands(scene, passState) { invertClassification ); + // Classification for translucent 3D Tiles + var hasClassificationOnTranslucent = + frustumCommands.indices[Pass.CESIUM_3D_TILE_CLASSIFICATION] > 0; + if ( + hasClassificationOnTranslucent && + view.translucentTileClassification.isSupported() + ) { + view.translucentTileClassification.executeTranslucentCommands( + scene, + executeCommand, + passState, + commands, + globeDepth.framebuffer + ); + view.translucentTileClassification.executeClassificationCommands( + scene, + executeCommand, + passState, + frustumCommands + ); + } + if ( context.depthTexture && scene.useDepthPicking && @@ -3439,6 +3461,13 @@ function updateAndClearFramebuffers(scene, passState, clearColor) { environmentState.useOIT = oit.isSupported(); } + if ( + useGlobeDepthFramebuffer && + view.translucentTileClassification.isSupported() + ) { + view.translucentTileClassification.clear(context, passState); + } + var postProcess = scene.postProcessStages; var usePostProcess = (environmentState.usePostProcess = !picking && @@ -3545,6 +3574,14 @@ Scene.prototype.resolveFramebuffers = function (passState) { view.oit.execute(context, passState); } + var translucentTileClassification = view.translucentTileClassification; + if ( + translucentTileClassification.hasTranslucentDepth && + translucentTileClassification.isSupported() + ) { + translucentTileClassification.execute(this, passState); + } + if (usePostProcess) { var inputFramebuffer = sceneFramebuffer; if (useGlobeDepthFramebuffer && !useOIT) { diff --git a/Source/Scene/TranslucentTileClassification.js b/Source/Scene/TranslucentTileClassification.js new file mode 100644 index 0000000000..85ebbe570c --- /dev/null +++ b/Source/Scene/TranslucentTileClassification.js @@ -0,0 +1,615 @@ +import BoundingRectangle from "../Core/BoundingRectangle.js"; +import Color from "../Core/Color.js"; +import defined from "../Core/defined.js"; +import destroyObject from "../Core/destroyObject.js"; +import PixelFormat from "../Core/PixelFormat.js"; +import ClearCommand from "../Renderer/ClearCommand.js"; +import DrawCommand from "../Renderer/DrawCommand.js"; +import Framebuffer from "../Renderer/Framebuffer.js"; +import Pass from "../Renderer/Pass.js"; +import PixelDatatype from "../Renderer/PixelDatatype.js"; +import RenderState from "../Renderer/RenderState.js"; +import Sampler from "../Renderer/Sampler.js"; +import ShaderSource from "../Renderer/ShaderSource.js"; +import Texture from "../Renderer/Texture.js"; +import TextureWrap from "../Renderer/TextureWrap.js"; +import TextureMagnificationFilter from "../Renderer/TextureMagnificationFilter.js"; +import TextureMinificationFilter from "../Renderer/TextureMinificationFilter.js"; +import CompareAndPackTranslucentDepth from "../Shaders/CompareAndPackTranslucentDepth.js"; +import CompositeTranslucentClassification from "../Shaders/PostProcessStages/CompositeTranslucentClassification.js"; +import BlendingState from "./BlendingState.js"; +import DerivedCommand from "./DerivedCommand.js"; +import StencilConstants from "./StencilConstants.js"; +import StencilFunction from "./StencilFunction.js"; + +var debugShowPackedDepth = false; + +/** + * Handles buffers, drawing, and deriving commands needed for classifying translucent 3D Tiles. + * Uses a depth texture, so classification on translucent 3D Tiles is not available in Internet Explorer. + * + * @private + */ +function TranslucentTileClassification(context) { + this._drawClassificationFBO = undefined; + this._accumulationFBO = undefined; + this._packFBO = undefined; + + this._opaqueDepthStencilTexture = undefined; + + this._colorTexture = undefined; + this._accumulationTexture = undefined; + + // Reference to either colorTexture or accumulationTexture + this._textureToComposite = undefined; + + this._translucentDepthStencilTexture = undefined; + this._packedTranslucentDepth = undefined; + + this._packDepthCommand = undefined; + this._accumulateCommand = undefined; + this._compositeCommand = undefined; + this._copyCommand = undefined; + + this._clearColorCommand = new ClearCommand({ + color: new Color(0.0, 0.0, 0.0, 0.0), + owner: this, + }); + + this._clearDepthStencilCommand = new ClearCommand({ + depth: 1.0, + stencil: 0, + owner: this, + }); + + this._supported = context.depthTexture; + + this._viewport = new BoundingRectangle(); + this._rsDepth = undefined; + this._rsAccumulate = undefined; + this._rsComp = undefined; + this._useScissorTest = undefined; + this._scissorRectangle = undefined; + + this._hasTranslucentDepth = false; + this._frustumsDrawn = 0; +} + +Object.defineProperties(TranslucentTileClassification.prototype, { + /** + * Gets whether or not translucent depth was rendered. + * @memberof TranslucentTileClassification.prototype + * + * @type {Boolean} + * @readonly + */ + hasTranslucentDepth: { + get: function () { + return this._hasTranslucentDepth; + }, + }, +}); + +function destroyTextures(transpClass) { + transpClass._colorTexture = + transpClass._colorTexture && + !transpClass._colorTexture.isDestroyed() && + transpClass._colorTexture.destroy(); + + transpClass._accumulationTexture = + transpClass._accumulationTexture && + !transpClass._accumulationTexture.isDestroyed() && + transpClass._accumulationTexture.destroy(); + transpClass._textureToComposite = undefined; + + transpClass._translucentDepthStencilTexture = + transpClass._translucentDepthStencilTexture && + !transpClass._translucentDepthStencilTexture.isDestroyed() && + transpClass._translucentDepthStencilTexture.destroy(); + transpClass._packedTranslucentDepth = + transpClass._packedTranslucentDepth && + !transpClass._packedTranslucentDepth.isDestroyed() && + transpClass._packedTranslucentDepth.destroy(); +} + +function destroyFramebuffers(transpClass) { + transpClass._drawClassificationFBO = + transpClass._drawClassificationFBO && + !transpClass._drawClassificationFBO.isDestroyed() && + transpClass._drawClassificationFBO.destroy(); + transpClass._accumulationFBO = + transpClass._accumulationFBO && + !transpClass._accumulationFBO.isDestroyed() && + transpClass._accumulationFBO.destroy(); + + transpClass._packFBO = + transpClass._packFBO && + !transpClass._packFBO.isDestroyed() && + transpClass._packFBO.destroy(); +} + +function rgbaTexture(context, width, height) { + return new Texture({ + context: context, + width: width, + height: height, + pixelFormat: PixelFormat.RGBA, + pixelDatatype: PixelDatatype.UNSIGNED_BYTE, + sampler: new Sampler({ + wrapS: TextureWrap.CLAMP_TO_EDGE, + wrapT: TextureWrap.CLAMP_TO_EDGE, + minificationFilter: TextureMinificationFilter.NEAREST, + magnificationFilter: TextureMagnificationFilter.NEAREST, + }), + }); +} + +function updateTextures(transpClass, context, width, height) { + destroyTextures(transpClass); + + transpClass._colorTexture = rgbaTexture(context, width, height); + transpClass._accumulationTexture = rgbaTexture(context, width, height); + + transpClass._translucentDepthStencilTexture = new Texture({ + context: context, + width: width, + height: height, + pixelFormat: PixelFormat.DEPTH_STENCIL, + pixelDatatype: PixelDatatype.UNSIGNED_INT_24_8, + sampler: new Sampler({ + wrapS: TextureWrap.CLAMP_TO_EDGE, + wrapT: TextureWrap.CLAMP_TO_EDGE, + minificationFilter: TextureMinificationFilter.NEAREST, + magnificationFilter: TextureMagnificationFilter.NEAREST, + }), + }); + + transpClass._packedTranslucentDepth = new Texture({ + context: context, + width: width, + height: height, + pixelFormat: PixelFormat.RGBA, + pixelDatatype: PixelDatatype.UNSIGNED_BYTE, + sampler: new Sampler({ + wrapS: TextureWrap.CLAMP_TO_EDGE, + wrapT: TextureWrap.CLAMP_TO_EDGE, + minificationFilter: TextureMinificationFilter.NEAREST, + magnificationFilter: TextureMagnificationFilter.NEAREST, + }), + }); +} + +function updateFramebuffers(transpClass, context) { + destroyFramebuffers(transpClass); + + transpClass._drawClassificationFBO = new Framebuffer({ + context: context, + colorTextures: [transpClass._colorTexture], + depthStencilTexture: transpClass._translucentDepthStencilTexture, + destroyAttachments: false, + }); + + transpClass._accumulationFBO = new Framebuffer({ + context: context, + colorTextures: [transpClass._accumulationTexture], + depthStencilTexture: transpClass._translucentDepthStencilTexture, + destroyAttachments: false, + }); + + transpClass._packFBO = new Framebuffer({ + context: context, + colorTextures: [transpClass._packedTranslucentDepth], + destroyAttachments: false, + }); +} + +function updateResources( + transpClass, + context, + passState, + globeDepthFramebuffer +) { + if (!transpClass.isSupported()) { + return; + } + + transpClass._opaqueDepthStencilTexture = + globeDepthFramebuffer.depthStencilTexture; + + var width = transpClass._opaqueDepthStencilTexture.width; + var height = transpClass._opaqueDepthStencilTexture.height; + + var colorTexture = transpClass._colorTexture; + var textureChanged = + !defined(colorTexture) || + colorTexture.width !== width || + colorTexture.height !== height; + if (textureChanged) { + updateTextures(transpClass, context, width, height); + } + + if (!defined(transpClass._drawClassificationFBO) || textureChanged) { + updateFramebuffers(transpClass, context); + } + + var fs; + var uniformMap; + + if (!defined(transpClass._packDepthCommand)) { + fs = new ShaderSource({ + sources: [CompareAndPackTranslucentDepth], + }); + + uniformMap = { + u_opaqueDepthTexture: function () { + return transpClass._opaqueDepthStencilTexture; + }, + u_translucentDepthTexture: function () { + return transpClass._translucentDepthStencilTexture; + }, + }; + + transpClass._packDepthCommand = context.createViewportQuadCommand(fs, { + uniformMap: uniformMap, + owner: transpClass, + }); + } + + if (!defined(transpClass._compositeCommand)) { + fs = new ShaderSource({ + sources: [CompositeTranslucentClassification], + }); + + uniformMap = { + colorTexture: function () { + return transpClass._textureToComposite; + }, + }; + + if (debugShowPackedDepth) { + fs.defines = ["DEBUG_SHOW_DEPTH"]; + uniformMap.u_packedTranslucentDepth = function () { + return transpClass._packedTranslucentDepth; + }; + } + + transpClass._compositeCommand = context.createViewportQuadCommand(fs, { + uniformMap: uniformMap, + owner: transpClass, + }); + + var compositeCommand = transpClass._compositeCommand; + var compositeProgram = compositeCommand.shaderProgram; + var compositePickProgram = context.shaderCache.createDerivedShaderProgram( + compositeProgram, + "pick", + { + vertexShaderSource: compositeProgram.vertexShaderSource, + fragmentShaderSource: new ShaderSource({ + sources: fs.sources, + defines: ["PICK"], + }), + attributeLocations: compositeProgram._attributeLocations, + } + ); + var compositePickCommand = DrawCommand.shallowClone(compositeCommand); + compositePickCommand.shaderProgram = compositePickProgram; + compositeCommand.derivedCommands.pick = compositePickCommand; + } + + if (!defined(transpClass._copyCommand)) { + fs = new ShaderSource({ + sources: [CompositeTranslucentClassification], + }); + + uniformMap = { + colorTexture: function () { + return transpClass._colorTexture; + }, + }; + + transpClass._copyCommand = context.createViewportQuadCommand(fs, { + uniformMap: uniformMap, + owner: transpClass, + }); + } + + if (!defined(transpClass._accumulateCommand)) { + fs = new ShaderSource({ + sources: [CompositeTranslucentClassification], + }); + + uniformMap = { + colorTexture: function () { + return transpClass._colorTexture; + }, + }; + + transpClass._accumulateCommand = context.createViewportQuadCommand(fs, { + uniformMap: uniformMap, + owner: transpClass, + }); + } + + transpClass._viewport.width = width; + transpClass._viewport.height = height; + + var useScissorTest = !BoundingRectangle.equals( + transpClass._viewport, + passState.viewport + ); + var updateScissor = useScissorTest !== transpClass._useScissorTest; + transpClass._useScissorTest = useScissorTest; + + if ( + !BoundingRectangle.equals(transpClass._scissorRectangle, passState.viewport) + ) { + transpClass._scissorRectangle = BoundingRectangle.clone( + passState.viewport, + transpClass._scissorRectangle + ); + updateScissor = true; + } + + if ( + !defined(transpClass._rsDepth) || + !BoundingRectangle.equals( + transpClass._viewport, + transpClass._rsDepth.viewport + ) || + updateScissor + ) { + transpClass._rsDepth = RenderState.fromCache({ + viewport: transpClass._viewport, + scissorTest: { + enabled: transpClass._useScissorTest, + rectangle: transpClass._scissorRectangle, + }, + }); + } + + if (defined(transpClass._packDepthCommand)) { + transpClass._packDepthCommand.renderState = transpClass._rsDepth; + } + + if ( + !defined(transpClass._rsAccumulate) || + !BoundingRectangle.equals( + transpClass._viewport, + transpClass._rsAccumulate.viewport + ) || + updateScissor + ) { + transpClass._rsAccumulate = RenderState.fromCache({ + viewport: transpClass._viewport, + scissorTest: { + enabled: transpClass._useScissorTest, + rectangle: transpClass._scissorRectangle, + }, + stencilTest: { + enabled: true, + frontFunction: StencilFunction.EQUAL, + reference: StencilConstants.CESIUM_3D_TILE_MASK, + }, + }); + } + + if (defined(transpClass._accumulateCommand)) { + transpClass._accumulateCommand.renderState = transpClass._rsAccumulate; + } + + if ( + !defined(transpClass._rsComp) || + !BoundingRectangle.equals( + transpClass._viewport, + transpClass._rsComp.viewport + ) || + updateScissor + ) { + transpClass._rsComp = RenderState.fromCache({ + viewport: transpClass._viewport, + scissorTest: { + enabled: transpClass._useScissorTest, + rectangle: transpClass._scissorRectangle, + }, + blending: BlendingState.ALPHA_BLEND, + }); + } + + if (defined(transpClass._compositeCommand)) { + transpClass._compositeCommand.renderState = transpClass._rsComp; + transpClass._compositeCommand.derivedCommands.pick.renderState = + transpClass._rsComp; + } +} + +TranslucentTileClassification.prototype.executeTranslucentCommands = function ( + scene, + executeCommand, + passState, + commands, + globeDepthFramebuffer +) { + // Check for translucent commands that should be classified + var length = commands.length; + var command; + var i; + + var useLogDepth = scene.frameState.useLogDepth; + var context = scene.context; + var framebuffer = passState.framebuffer; + + for (i = 0; i < length; ++i) { + command = commands[i]; + command = useLogDepth ? command.derivedCommands.logDepth.command : command; + + if (command.depthForTranslucentClassification) { + this._hasTranslucentDepth = true; + break; + } + } + + if (!this._hasTranslucentDepth) { + return; + } + + updateResources(this, context, passState, globeDepthFramebuffer); + + // Get translucent depth + passState.framebuffer = this._drawClassificationFBO; + + // Clear depth for multifrustum + this._clearDepthStencilCommand.execute(context, passState); + + for (i = 0; i < length; ++i) { + command = commands[i]; + command = useLogDepth ? command.derivedCommands.logDepth.command : command; + + if (!command.depthForTranslucentClassification) { + continue; + } + + var derivedCommand = getDerivedCommand(command, scene, context); + executeCommand(derivedCommand.depthOnlyCommand, scene, context, passState); + } + + this._frustumsDrawn += this._hasTranslucentDepth ? 1 : 0; + + // Pack depth if any translucent depth commands were performed + if (this._hasTranslucentDepth) { + passState.framebuffer = this._packFBO; + this._packDepthCommand.execute(context, passState); + } + + passState.framebuffer = framebuffer; +}; + +TranslucentTileClassification.prototype.executeClassificationCommands = function ( + scene, + executeCommand, + passState, + frustumCommands +) { + if (!this._hasTranslucentDepth) { + return; + } + + var context = scene.context; + var us = context.uniformState; + var framebuffer = passState.framebuffer; + + if (this._frustumsDrawn === 2) { + // copy classification from first frustum + passState.framebuffer = this._accumulationFBO; + this._copyCommand.execute(context, passState); + } + + passState.framebuffer = this._drawClassificationFBO; + if (this._frustumsDrawn > 1) { + this._clearColorCommand.execute(context, passState); + } + + us.updatePass(Pass.CESIUM_3D_TILE_CLASSIFICATION); + var swapGlobeDepth = us.globeDepthTexture; + us.globeDepthTexture = this._packedTranslucentDepth; + var commands = frustumCommands.commands[Pass.CESIUM_3D_TILE_CLASSIFICATION]; + var length = frustumCommands.indices[Pass.CESIUM_3D_TILE_CLASSIFICATION]; + for (var i = 0; i < length; ++i) { + executeCommand(commands[i], scene, context, passState); + } + + us.globeDepthTexture = swapGlobeDepth; + passState.framebuffer = framebuffer; + + if (this._frustumsDrawn === 1) { + return; + } + + passState.framebuffer = this._accumulationFBO; + this._accumulateCommand.execute(context, passState); + + passState.framebuffer = framebuffer; +}; + +TranslucentTileClassification.prototype.execute = function (scene, passState) { + if (!this._hasTranslucentDepth) { + return; + } + if (this._frustumsDrawn === 1) { + this._textureToComposite = this._colorTexture; + } else { + this._textureToComposite = this._accumulationTexture; + } + + var command = scene.frameState.passes.pick + ? this._compositeCommand.derivedCommands.pick + : this._compositeCommand; + command.execute(scene.context, passState); +}; + +TranslucentTileClassification.prototype.clear = function (context, passState) { + if (!this._hasTranslucentDepth) { + return; + } + var framebuffer = passState.framebuffer; + + passState.framebuffer = this._drawClassificationFBO; + this._clearColorCommand.execute(context, passState); + + passState.framebuffer = framebuffer; + + if (this._frustumsDrawn > 1) { + passState.framebuffer = this._accumulationFBO; + this._clearColorCommand.execute(context, passState); + } + + this._hasTranslucentDepth = false; + this._frustumsDrawn = 0; +}; + +TranslucentTileClassification.prototype.isSupported = function () { + return this._supported; +}; + +TranslucentTileClassification.prototype.isDestroyed = function () { + return false; +}; + +TranslucentTileClassification.prototype.destroy = function () { + destroyTextures(this); + destroyFramebuffers(this); + + if (defined(this._compositeCommand)) { + this._compositeCommand.shaderProgram = + this._compositeCommand.shaderProgram && + this._compositeCommand.shaderProgram.destroy(); + } + + if (defined(this._packDepthCommand)) { + this._packDepthCommand.shaderProgram = + this._packDepthCommand.shaderProgram && + this._packDepthCommand.shaderProgram.destroy(); + } + return destroyObject(this); +}; + +function getDerivedCommand(command, scene, context) { + var derivedCommands = command.derivedCommands; + var depthForClassification = derivedCommands.depthForClassification; + if (!defined(depthForClassification)) { + depthForClassification = derivedCommands.depthForClassification = DerivedCommand.createDepthOnlyDerivedCommand( + scene, + command, + context, + derivedCommands.depthForClassification + ); + + var depthOnlyCommand = depthForClassification.depthOnlyCommand; + var rs = RenderState.getState(depthOnlyCommand.renderState); + rs.stencilTest = StencilConstants.setCesium3DTileBit(); + + depthOnlyCommand.renderState = RenderState.fromCache(rs); + } + return depthForClassification; +} +export default TranslucentTileClassification; diff --git a/Source/Scene/View.js b/Source/Scene/View.js index 31d1bb60e2..de46dd0c25 100644 --- a/Source/Scene/View.js +++ b/Source/Scene/View.js @@ -19,6 +19,7 @@ import PickFramebuffer from "./PickFramebuffer.js"; import SceneFramebuffer from "./SceneFramebuffer.js"; import SceneMode from "./SceneMode.js"; import ShadowMap from "./ShadowMap.js"; +import TranslucentTileClassification from "./TranslucentTileClassification.js"; function CommandExtent() { this.command = undefined; @@ -58,6 +59,9 @@ function View(scene, camera, viewport) { this.globeDepth = globeDepth; this.globeTranslucencyFramebuffer = new GlobeTranslucencyFramebuffer(); this.oit = oit; + this.translucentTileClassification = new TranslucentTileClassification( + context + ); this.pickDepths = []; this.debugGlobeDepths = []; this.frustumCommandsList = []; @@ -411,6 +415,9 @@ View.prototype.destroy = function () { this.sceneFramebuffer && this.sceneFramebuffer.destroy(); this.globeDepth = this.globeDepth && this.globeDepth.destroy(); this.oit = this.oit && this.oit.destroy(); + this.translucentTileClassification = + this.translucentTileClassification && + this.translucentTileClassification.destroy(); this.globeTranslucencyFramebuffer = this.globeTranslucencyFramebuffer && this.globeTranslucencyFramebuffer.destroy(); diff --git a/Source/Shaders/CompareAndPackTranslucentDepth.glsl b/Source/Shaders/CompareAndPackTranslucentDepth.glsl new file mode 100644 index 0000000000..448d1c63f0 --- /dev/null +++ b/Source/Shaders/CompareAndPackTranslucentDepth.glsl @@ -0,0 +1,12 @@ +uniform sampler2D u_opaqueDepthTexture; +uniform sampler2D u_translucentDepthTexture; + +varying vec2 v_textureCoordinates; + +void main() +{ + float opaqueDepth = texture2D(u_opaqueDepthTexture, v_textureCoordinates).r; + float translucentDepth = texture2D(u_translucentDepthTexture, v_textureCoordinates).r; + translucentDepth = czm_branchFreeTernary(translucentDepth > opaqueDepth, 1.0, translucentDepth); + gl_FragColor = czm_packDepth(translucentDepth); +} diff --git a/Source/Shaders/PostProcessStages/CompositeTranslucentClassification.glsl b/Source/Shaders/PostProcessStages/CompositeTranslucentClassification.glsl new file mode 100644 index 0000000000..544ab61cc9 --- /dev/null +++ b/Source/Shaders/PostProcessStages/CompositeTranslucentClassification.glsl @@ -0,0 +1,28 @@ +uniform sampler2D colorTexture; + +#ifdef DEBUG_SHOW_DEPTH +uniform sampler2D u_packedTranslucentDepth; +#endif + +varying vec2 v_textureCoordinates; + +void main() +{ +#ifdef DEBUG_SHOW_DEPTH + if (v_textureCoordinates.x < 0.5) + { + gl_FragColor.rgb = vec3(czm_unpackDepth(texture2D(u_packedTranslucentDepth, v_textureCoordinates))); + gl_FragColor.a = 1.0; + } +#else + vec4 color = texture2D(colorTexture, v_textureCoordinates); + +#ifdef PICK + if (color == vec4(0.0)) + { + discard; + } +#endif + gl_FragColor = color; +#endif +} diff --git a/Source/Shaders/ShadowVolumeAppearanceFS.glsl b/Source/Shaders/ShadowVolumeAppearanceFS.glsl index 51496c9ccf..44f41477e1 100644 --- a/Source/Shaders/ShadowVolumeAppearanceFS.glsl +++ b/Source/Shaders/ShadowVolumeAppearanceFS.glsl @@ -71,7 +71,11 @@ void main(void) #ifdef PICK #ifdef CULL_FRAGMENTS - if (0.0 <= uv.x && uv.x <= 1.0 && 0.0 <= uv.y && uv.y <= 1.0) { + // When classifying translucent geometry, logDepthOrDepth == 0.0 + // indicates a region that should not be classified, possibly due to there + // being opaque pixels there in another buffer. + // Check for logDepthOrDepth != 0.0 to make sure this should be classified. + if (0.0 <= uv.x && uv.x <= 1.0 && 0.0 <= uv.y && uv.y <= 1.0 || logDepthOrDepth != 0.0) { gl_FragColor.a = 1.0; // 0.0 alpha leads to discard from ShaderSource.createPickFragmentShaderSource czm_writeDepthClamp(); } @@ -81,7 +85,10 @@ void main(void) #else // PICK #ifdef CULL_FRAGMENTS - if (uv.x <= 0.0 || 1.0 <= uv.x || uv.y <= 0.0 || 1.0 <= uv.y) { + // When classifying translucent geometry, logDepthOrDepth == 0.0 + // indicates a region that should not be classified, possibly due to there + // being opaque pixels there in another buffer. + if (uv.x <= 0.0 || 1.0 <= uv.x || uv.y <= 0.0 || 1.0 <= uv.y || logDepthOrDepth == 0.0) { discard; } #endif diff --git a/Specs/Scene/TranslucentTileClassificationSpec.js b/Specs/Scene/TranslucentTileClassificationSpec.js new file mode 100644 index 0000000000..1d593dac5f --- /dev/null +++ b/Specs/Scene/TranslucentTileClassificationSpec.js @@ -0,0 +1,741 @@ +import { TranslucentTileClassification } from "../../Source/Cesium.js"; +import { ApproximateTerrainHeights } from "../../Source/Cesium.js"; +import { Cartesian3 } from "../../Source/Cesium.js"; +import { Color } from "../../Source/Cesium.js"; +import { ColorGeometryInstanceAttribute } from "../../Source/Cesium.js"; +import { defined } from "../../Source/Cesium.js"; +import { destroyObject } from "../../Source/Cesium.js"; +import { Ellipsoid } from "../../Source/Cesium.js"; +import { GeometryInstance } from "../../Source/Cesium.js"; +import { GroundPolylineGeometry } from "../../Source/Cesium.js"; +import { PixelFormat } from "../../Source/Cesium.js"; +import { Rectangle } from "../../Source/Cesium.js"; +import { RectangleGeometry } from "../../Source/Cesium.js"; +import { ClearCommand } from "../../Source/Cesium.js"; +import { Framebuffer } from "../../Source/Cesium.js"; +import { Pass } from "../../Source/Cesium.js"; +import { PixelDatatype } from "../../Source/Cesium.js"; +import { Texture } from "../../Source/Cesium.js"; +import { ClassificationType } from "../../Source/Cesium.js"; +import { GroundPolylinePrimitive } from "../../Source/Cesium.js"; +import { PerInstanceColorAppearance } from "../../Source/Cesium.js"; +import { Primitive } from "../../Source/Cesium.js"; +import createScene from "../createScene.js"; + +describe( + "Scene/TranslucentTileClassification", + function () { + var scene; + var context; + var passState; + var globeDepthFramebuffer; + var ellipsoid; + + var positions = Cartesian3.fromDegreesArray([0.01, 0.0, 0.03, 0.0]); + + var lookPosition = Cartesian3.fromDegrees(0.02, 0.0); + var lookOffset = new Cartesian3(0.0, 0.0, 10.0); + var translucentPrimitive; + var groundPolylinePrimitive; + + beforeAll(function () { + scene = createScene(); + scene.postProcessStages.fxaa.enabled = false; + scene.render(); // generate globeDepth.framebuffer + + context = scene.context; + passState = scene._defaultView.passState; + if (defined(scene._defaultView.globeDepth)) { + globeDepthFramebuffer = scene._defaultView.globeDepth.framebuffer; + } + + ellipsoid = Ellipsoid.WGS84; + return GroundPolylinePrimitive.initializeTerrainHeights(); + }); + + afterAll(function () { + scene.destroyForSpecs(); + + // Leave ground primitive uninitialized + ApproximateTerrainHeights._initPromise = undefined; + ApproximateTerrainHeights._terrainHeights = undefined; + }); + + function SpecPrimitive(primitive, pass) { + this._primitive = primitive; + this._depthForTranslucentClassification = pass === Pass.TRANSLUCENT; + this._pass = pass; + this.commands = []; + } + + SpecPrimitive.prototype.update = function (frameState) { + var commandList = frameState.commandList; + var startLength = commandList.length; + this._primitive.update(frameState); + + this.commands = []; + for (var i = startLength; i < commandList.length; ++i) { + var command = commandList[i]; + command.pass = this._pass; + command.depthForTranslucentClassification = this._depthForTranslucentClassification; + this.commands.push(command); + } + }; + + SpecPrimitive.prototype.isDestroyed = function () { + return false; + }; + + SpecPrimitive.prototype.destroy = function () { + this._primitive.destroy(); + return destroyObject(this); + }; + + beforeEach(function () { + scene.morphTo3D(0); + scene.render(); // clear any afterRender commands + scene.camera.lookAt(lookPosition, lookOffset); + + var primitive = new Primitive({ + geometryInstances: new GeometryInstance({ + geometry: new RectangleGeometry({ + ellipsoid: ellipsoid, + rectangle: Rectangle.fromDegrees(-0.1, -0.1, 0.1, 0.1), + height: 1.0, + }), + attributes: { + color: ColorGeometryInstanceAttribute.fromColor( + new Color(0.0, 0.0, 1.0, 0.5) + ), + }, + }), + appearance: new PerInstanceColorAppearance({ + translucent: true, + flat: true, + }), + asynchronous: false, + }); + + translucentPrimitive = new SpecPrimitive(primitive, Pass.TRANSLUCENT); + scene.primitives.add(translucentPrimitive); + + primitive = new GroundPolylinePrimitive({ + geometryInstances: new GeometryInstance({ + geometry: new GroundPolylineGeometry({ + positions: positions, + granularity: 0.0, + width: 1.0, + loop: false, + ellipsoid: ellipsoid, + }), + }), + asynchronous: false, + classificationType: ClassificationType.CESIUM_3D_TILE, + }); + + groundPolylinePrimitive = new SpecPrimitive( + primitive, + Pass.CESIUM_3D_TILE_CLASSIFICATION + ); + scene.groundPrimitives.add(groundPolylinePrimitive); + }); + + afterEach(function () { + scene.primitives.removeAll(); + scene.groundPrimitives.removeAll(); + + translucentPrimitive = + translucentPrimitive && + !translucentPrimitive.isDestroyed() && + translucentPrimitive.destroy(); + + groundPolylinePrimitive = + groundPolylinePrimitive && + !groundPolylinePrimitive.isDestroyed() && + groundPolylinePrimitive.destroy(); + }); + + it("checks for support in the context on construction", function () { + var translucentTileClassification = new TranslucentTileClassification({ + depthTexture: true, + }); + + expect(translucentTileClassification.isSupported()).toBe(true); + translucentTileClassification.destroy(); + + translucentTileClassification = new TranslucentTileClassification({ + depthTexture: false, + }); + expect(translucentTileClassification.isSupported()).toBe(false); + translucentTileClassification.destroy(); + }); + + function expectResources(translucentTileClassification, toBeDefined) { + expect( + defined(translucentTileClassification._drawClassificationFBO) + ).toBe(toBeDefined); + expect(defined(translucentTileClassification._packFBO)).toBe(toBeDefined); + expect( + defined(translucentTileClassification._opaqueDepthStencilTexture) + ).toBe(toBeDefined); + expect(defined(translucentTileClassification._colorTexture)).toBe( + toBeDefined + ); + expect( + defined(translucentTileClassification._translucentDepthStencilTexture) + ).toBe(toBeDefined); + expect( + defined(translucentTileClassification._packedTranslucentDepth) + ).toBe(toBeDefined); + expect(defined(translucentTileClassification._packDepthCommand)).toBe( + toBeDefined + ); + expect(defined(translucentTileClassification._accumulateCommand)).toBe( + toBeDefined + ); + expect(defined(translucentTileClassification._compositeCommand)).toBe( + toBeDefined + ); + expect(defined(translucentTileClassification._copyCommand)).toBe( + toBeDefined + ); + } + + it("does not create resources if unsupported", function () { + var translucentTileClassification = new TranslucentTileClassification({ + depthTexture: false, + }); + + expectResources(translucentTileClassification, false); + + translucentTileClassification.executeTranslucentCommands( + scene, + executeCommand, + passState, + translucentPrimitive.commands, + globeDepthFramebuffer + ); + + expectResources(translucentTileClassification, false); + + translucentTileClassification.destroy(); + }); + + it("creates resources on demand", function () { + var translucentTileClassification = new TranslucentTileClassification( + context + ); + if (!translucentTileClassification.isSupported()) { + return; // don't fail because of lack of support + } + + scene.render(); // prep scene + + expectResources(translucentTileClassification, false); + + translucentTileClassification.executeTranslucentCommands( + scene, + executeCommand, + passState, + [], + globeDepthFramebuffer + ); + + expectResources(translucentTileClassification, false); + + translucentTileClassification.executeTranslucentCommands( + scene, + executeCommand, + passState, + translucentPrimitive.commands, + globeDepthFramebuffer + ); + + expectResources(translucentTileClassification, true); + + translucentTileClassification.destroy(); + }); + + function readPixels(fbo) { + return context.readPixels({ + framebuffer: fbo, + }); + } + + function executeCommand(command, scene, context, passState) { + command.execute(context, passState); + } + + it("draws translucent commands into a buffer for depth", function () { + var translucentTileClassification = new TranslucentTileClassification( + context + ); + if (!translucentTileClassification.isSupported()) { + return; // don't fail because of lack of support + } + scene.render(); // prep scene + + var packedDepthFBO = translucentTileClassification._packFBO; + + translucentTileClassification.executeTranslucentCommands( + scene, + executeCommand, + passState, + translucentPrimitive.commands, + globeDepthFramebuffer + ); + + expect(translucentTileClassification.hasTranslucentDepth).toBe(true); + + var postTranslucentPixels = readPixels(packedDepthFBO); + expect(postTranslucentPixels).not.toEqual([0, 0, 0, 0]); + + translucentTileClassification.destroy(); + }); + + it("draws classification commands into a buffer", function () { + var translucentTileClassification = new TranslucentTileClassification( + context + ); + if (!translucentTileClassification.isSupported()) { + return; // don't fail because of lack of support + } + scene.render(); // prep scene + + translucentTileClassification.executeTranslucentCommands( + scene, + executeCommand, + passState, + translucentPrimitive.commands, + globeDepthFramebuffer + ); + + var drawClassificationFBO = + translucentTileClassification._drawClassificationFBO; + var preClassifyPixels = readPixels(drawClassificationFBO); + + var frustumCommands = { + commands: [], + indices: [], + }; + frustumCommands.commands[Pass.CESIUM_3D_TILE_CLASSIFICATION] = + groundPolylinePrimitive.commands; + frustumCommands.indices[Pass.CESIUM_3D_TILE_CLASSIFICATION] = [1]; + + translucentTileClassification.executeClassificationCommands( + scene, + executeCommand, + passState, + frustumCommands + ); + + var postClassifyPixels = readPixels(drawClassificationFBO); + expect(postClassifyPixels).not.toEqual(preClassifyPixels); + + translucentTileClassification.destroy(); + }); + + it("draws classification commands into a separate accumulation buffer for multifrustum", function () { + var translucentTileClassification = new TranslucentTileClassification( + context + ); + if (!translucentTileClassification.isSupported()) { + return; // don't fail because of lack of support + } + scene.render(); // prep scene + + translucentTileClassification.executeTranslucentCommands( + scene, + executeCommand, + passState, + translucentPrimitive.commands, + globeDepthFramebuffer + ); + + var accumulationFBO = translucentTileClassification._accumulationFBO; + var drawClassificationFBO = + translucentTileClassification._drawClassificationFBO; + + var frustumCommands = { + commands: [], + indices: [], + }; + frustumCommands.commands[Pass.CESIUM_3D_TILE_CLASSIFICATION] = + groundPolylinePrimitive.commands; + frustumCommands.indices[Pass.CESIUM_3D_TILE_CLASSIFICATION] = [1]; + + translucentTileClassification.executeClassificationCommands( + scene, + executeCommand, + passState, + frustumCommands + ); + + expect(readPixels(accumulationFBO)).toEqual([0, 0, 0, 0]); + + translucentTileClassification.executeTranslucentCommands( + scene, + executeCommand, + passState, + translucentPrimitive.commands, + globeDepthFramebuffer + ); + translucentTileClassification.executeClassificationCommands( + scene, + executeCommand, + passState, + frustumCommands + ); + + var secondFrustumAccumulation = accumulationFBO; + expect(readPixels(secondFrustumAccumulation)).not.toEqual([0, 0, 0, 0]); + expect(readPixels(secondFrustumAccumulation)).toEqual( + readPixels(drawClassificationFBO) + ); + + translucentTileClassification.destroy(); + }); + + it("does not draw classification commands if there is no translucent depth", function () { + var translucentTileClassification = new TranslucentTileClassification( + context + ); + if (!translucentTileClassification.isSupported()) { + return; // don't fail because of lack of support + } + scene.render(); // prep scene + + var drawClassificationFBO = + translucentTileClassification._drawClassificationFBO; + + translucentTileClassification.executeTranslucentCommands( + scene, + executeCommand, + passState, + [], + globeDepthFramebuffer + ); + + var preClassifyPixels = readPixels(drawClassificationFBO); + + var frustumCommands = { + commands: [], + indices: [], + }; + frustumCommands.commands[Pass.CESIUM_3D_TILE_CLASSIFICATION] = + groundPolylinePrimitive.commands; + frustumCommands.indices[Pass.CESIUM_3D_TILE_CLASSIFICATION] = [1]; + + translucentTileClassification.executeClassificationCommands( + scene, + executeCommand, + passState, + frustumCommands + ); + + var postClassifyPixels = readPixels(drawClassificationFBO); + expect(postClassifyPixels).toEqual(preClassifyPixels); + + translucentTileClassification.destroy(); + }); + + it("composites classification into a buffer", function () { + var translucentTileClassification = new TranslucentTileClassification( + context + ); + if (!translucentTileClassification.isSupported()) { + return; // don't fail because of lack of support + } + + var colorTexture = new Texture({ + context: context, + width: 1, + height: 1, + pixelFormat: PixelFormat.RGBA, + pixelDatatype: PixelDatatype.UNSIGNED_BYTE, + }); + + var targetColorFBO = new Framebuffer({ + context: context, + colorTextures: [colorTexture], + }); + + scene.render(); // prep scene + + translucentTileClassification.executeTranslucentCommands( + scene, + executeCommand, + passState, + translucentPrimitive.commands, + globeDepthFramebuffer + ); + + var frustumCommands = { + commands: [], + indices: [], + }; + frustumCommands.commands[Pass.CESIUM_3D_TILE_CLASSIFICATION] = + groundPolylinePrimitive.commands; + frustumCommands.indices[Pass.CESIUM_3D_TILE_CLASSIFICATION] = [1]; + + translucentTileClassification.executeClassificationCommands( + scene, + executeCommand, + passState, + frustumCommands + ); + + var preCompositePixels = readPixels(targetColorFBO); + var pixelsToComposite = readPixels( + translucentTileClassification._drawClassificationFBO + ); + + var framebuffer = passState.framebuffer; + passState.framebuffer = targetColorFBO; + translucentTileClassification.execute(scene, passState); + passState.framebuffer = framebuffer; + + var postCompositePixels = readPixels(targetColorFBO); + expect(postCompositePixels).not.toEqual(preCompositePixels); + + var normalizedAlpha = pixelsToComposite[3] / 255; + var red = + Math.round(normalizedAlpha * pixelsToComposite[0]) + + preCompositePixels[0]; + var green = + Math.round(normalizedAlpha * pixelsToComposite[1]) + + preCompositePixels[1]; + var blue = + Math.round(normalizedAlpha * pixelsToComposite[2]) + + preCompositePixels[2]; + var alpha = pixelsToComposite[3] + preCompositePixels[3]; + + expect(postCompositePixels[0]).toEqual(red); + expect(postCompositePixels[1]).toEqual(green); + expect(postCompositePixels[2]).toEqual(blue); + expect(postCompositePixels[3]).toEqual(alpha); + + translucentTileClassification.destroy(); + targetColorFBO.destroy(); + }); + + it("composites from an accumulation texture when there are multiple frustums", function () { + var translucentTileClassification = new TranslucentTileClassification( + context + ); + if (!translucentTileClassification.isSupported()) { + return; // don't fail because of lack of support + } + + var clearCommandRed = new ClearCommand({ + color: new Color(1.0, 0.0, 0.0, 1.0), + }); + + var clearCommandGreen = new ClearCommand({ + color: new Color(0.0, 1.0, 0.0, 1.0), + }); + + var colorTexture = new Texture({ + context: context, + width: 1, + height: 1, + pixelFormat: PixelFormat.RGBA, + pixelDatatype: PixelDatatype.UNSIGNED_BYTE, + }); + + var targetColorFBO = new Framebuffer({ + context: context, + colorTextures: [colorTexture], + }); + + scene.render(); // prep scene + + translucentTileClassification.executeTranslucentCommands( + scene, + executeCommand, + passState, + translucentPrimitive.commands, + globeDepthFramebuffer + ); + + var frustumCommands = { + commands: [], + indices: [], + }; + + // First Frustum + frustumCommands.commands[Pass.CESIUM_3D_TILE_CLASSIFICATION] = + groundPolylinePrimitive.commands; + frustumCommands.indices[Pass.CESIUM_3D_TILE_CLASSIFICATION] = [1]; + + translucentTileClassification.executeClassificationCommands( + scene, + executeCommand, + passState, + frustumCommands + ); + + // Second Frustum + translucentTileClassification.executeTranslucentCommands( + scene, + executeCommand, + passState, + translucentPrimitive.commands, + globeDepthFramebuffer + ); + + translucentTileClassification.executeClassificationCommands( + scene, + executeCommand, + passState, + frustumCommands + ); + + var framebuffer = passState.framebuffer; + + // Replace classification and accumulation colors to distinguish which is composited + passState.framebuffer = + translucentTileClassification._drawClassificationFBO; + clearCommandRed.execute(context, passState); + passState.framebuffer = translucentTileClassification._accumulationFBO; + clearCommandGreen.execute(context, passState); + + passState.framebuffer = targetColorFBO; + translucentTileClassification.execute(scene, passState); + passState.framebuffer = framebuffer; + + var postCompositePixels = readPixels(targetColorFBO); + expect(postCompositePixels).toEqual([0, 255, 0, 255]); + + translucentTileClassification.destroy(); + targetColorFBO.destroy(); + }); + + it("does not composite classification if there is no translucent depth", function () { + var translucentTileClassification = new TranslucentTileClassification( + context + ); + if (!translucentTileClassification.isSupported()) { + return; // don't fail because of lack of support + } + + var colorTexture = new Texture({ + context: context, + width: 1, + height: 1, + pixelFormat: PixelFormat.RGBA, + pixelDatatype: PixelDatatype.UNSIGNED_BYTE, + }); + + var targetColorFBO = new Framebuffer({ + context: context, + colorTextures: [colorTexture], + }); + + scene.render(); // prep scene + + translucentTileClassification.executeTranslucentCommands( + scene, + executeCommand, + passState, + [], + globeDepthFramebuffer + ); + + var frustumCommands = { + commands: [], + indices: [], + }; + frustumCommands.commands[Pass.CESIUM_3D_TILE_CLASSIFICATION] = + groundPolylinePrimitive.commands; + frustumCommands.indices[Pass.CESIUM_3D_TILE_CLASSIFICATION] = [1]; + + translucentTileClassification.executeClassificationCommands( + scene, + executeCommand, + passState, + frustumCommands + ); + + var preCompositePixels = readPixels(targetColorFBO); + + var framebuffer = passState.framebuffer; + passState.framebuffer = targetColorFBO; + translucentTileClassification.execute(scene, passState); + passState.framebuffer = framebuffer; + + var postCompositePixels = readPixels(targetColorFBO); + expect(postCompositePixels).toEqual(preCompositePixels); + + translucentTileClassification.destroy(); + targetColorFBO.destroy(); + }); + + it("clears the classification buffer", function () { + var translucentTileClassification = new TranslucentTileClassification( + context + ); + if (!translucentTileClassification.isSupported()) { + return; // don't fail because of lack of support + } + scene.render(); // prep scene + + translucentTileClassification.executeTranslucentCommands( + scene, + executeCommand, + passState, + translucentPrimitive.commands, + globeDepthFramebuffer + ); + + var drawClassificationFBO = + translucentTileClassification._drawClassificationFBO; + var preClassifyPixels = readPixels(drawClassificationFBO); + + var frustumCommands = { + commands: [], + indices: [], + }; + frustumCommands.commands[Pass.CESIUM_3D_TILE_CLASSIFICATION] = + groundPolylinePrimitive.commands; + frustumCommands.indices[Pass.CESIUM_3D_TILE_CLASSIFICATION] = [1]; + + translucentTileClassification.executeClassificationCommands( + scene, + executeCommand, + passState, + frustumCommands + ); + + var postClassifyPixels = readPixels(drawClassificationFBO); + expect(postClassifyPixels).not.toEqual(preClassifyPixels); + + translucentTileClassification.clear(context, passState); + + var postClearPixels = readPixels(drawClassificationFBO); + expect(postClearPixels).not.toEqual(postClassifyPixels); + expect(postClearPixels).toEqual(preClassifyPixels); + + translucentTileClassification.destroy(); + }); + + it("does not clear the classification buffer if there is no translucent depth", function () { + var translucentTileClassification = new TranslucentTileClassification( + context + ); + if (!translucentTileClassification.isSupported()) { + return; // don't fail because of lack of support + } + + spyOn(translucentTileClassification._clearColorCommand, "execute"); + + translucentTileClassification.clear(context, passState); + + expect( + translucentTileClassification._clearColorCommand.execute + ).not.toHaveBeenCalled(); + translucentTileClassification.destroy(); + }); + }, + "WebGL" +); From 38b8bc62129b695687fa3200bc57de00cbb87329 Mon Sep 17 00:00:00 2001 From: ebogo1 Date: Tue, 2 Mar 2021 18:06:11 -0500 Subject: [PATCH 09/76] changes.md --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index 97d18b1e1a..e17b0fe8bc 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,6 +5,7 @@ ##### Additions :tada: - Added `Cesium3DTileset.pickPrimitive` for rendering primitives instead of the tileset during the pick pass. +- Added `TranslucentTileClassification` for classifying translucent 3D Tiles. ### 1.79.1 - 2021-03-01 From a2ac850bf8ca36b3358c8136068251ac23d36dd0 Mon Sep 17 00:00:00 2001 From: ebogo1 Date: Wed, 3 Mar 2021 12:18:52 -0500 Subject: [PATCH 10/76] renaming, remove unused property --- Source/Scene/Cesium3DTileset.js | 16 +--------- Source/Scene/Vector3DTileClampedPolylines.js | 20 ++++++------ .../Vector3DTileClampedPolylinesVS.glsl | 31 +++++++++---------- 3 files changed, 26 insertions(+), 41 deletions(-) diff --git a/Source/Scene/Cesium3DTileset.js b/Source/Scene/Cesium3DTileset.js index f4e88edd41..403627dd86 100644 --- a/Source/Scene/Cesium3DTileset.js +++ b/Source/Scene/Cesium3DTileset.js @@ -901,7 +901,7 @@ function Cesium3DTileset(options) { /** * Function for examining vector lines as they are being streamed. * - * @private + * @experimental */ this.examineVectorLinesFunction = undefined; @@ -1661,20 +1661,6 @@ Object.defineProperties(Cesium3DTileset.prototype, { Cartesian2.clone(value, this._imageBasedLightingFactor); }, }, - - /** - * Minimum and maximum heights that vector tiles clamped to surfaces will clamp to. - * - * @memberof Cesium3DTileset.prototype - * - * @type {Cartesian2} - * @default undefined - */ - minimumMaximumVectorHeights: { - get: function () { - return this._minimumMaximumVectorHeights; - }, - }, }); /** diff --git a/Source/Scene/Vector3DTileClampedPolylines.js b/Source/Scene/Vector3DTileClampedPolylines.js index e984ef5777..cad5b034e9 100644 --- a/Source/Scene/Vector3DTileClampedPolylines.js +++ b/Source/Scene/Vector3DTileClampedPolylines.js @@ -88,7 +88,7 @@ function Vector3DTileClampedPolylines(options) { this._startEllipsoidNormals = undefined; this._endEllipsoidNormals = undefined; this._startPositionAndHeights = undefined; - this._startFaceNormalAndVertexCorners = undefined; + this._startFaceNormalAndVertexCornerIds = undefined; this._endPositionAndHeights = undefined; this._endFaceNormalAndHalfWidths = undefined; this._vertexBatchIds = undefined; @@ -270,8 +270,8 @@ function createVertexArray(polylines, context) { polylines._startPositionAndHeights = new Float32Array( result.startPositionAndHeights ); - polylines._startFaceNormalAndVertexCorners = new Float32Array( - result.startFaceNormalAndVertexCorners + polylines._startFaceNormalAndVertexCornerIds = new Float32Array( + result.startFaceNormalAndVertexCornerIds ); polylines._endPositionAndHeights = new Float32Array( result.endPositionAndHeights @@ -298,8 +298,8 @@ function createVertexArray(polylines, context) { var endEllipsoidNormals = polylines._endEllipsoidNormals; var startPositionAndHeights = polylines._startPositionAndHeights; var endPositionAndHeights = polylines._endPositionAndHeights; - var startFaceNormalAndVertexCorners = - polylines._startFaceNormalAndVertexCorners; + var startFaceNormalAndVertexCornerIds = + polylines._startFaceNormalAndVertexCornerIds; var endFaceNormalAndHalfWidths = polylines._endFaceNormalAndHalfWidths; var batchIdAttribute = polylines._vertexBatchIds; @@ -310,7 +310,7 @@ function createVertexArray(polylines, context) { byteLength += startPositionAndHeights.byteLength + endPositionAndHeights.byteLength; byteLength += - startFaceNormalAndVertexCorners.byteLength + + startFaceNormalAndVertexCornerIds.byteLength + endFaceNormalAndHalfWidths.byteLength; byteLength += batchIdAttribute.byteLength + indices.byteLength; @@ -337,9 +337,9 @@ function createVertexArray(polylines, context) { typedArray: endPositionAndHeights, usage: BufferUsage.STATIC_DRAW, }); - var startFaceNormalAndVertexCornersBuffer = Buffer.createVertexBuffer({ + var startFaceNormalAndVertexCornerIdsBuffer = Buffer.createVertexBuffer({ context: context, - typedArray: startFaceNormalAndVertexCorners, + typedArray: startFaceNormalAndVertexCornerIds, usage: BufferUsage.STATIC_DRAW, }); var endFaceNormalAndHalfWidthsBuffer = Buffer.createVertexBuffer({ @@ -390,7 +390,7 @@ function createVertexArray(polylines, context) { }, { index: attributeLocations.startFaceNormalAndVertexCorner, - vertexBuffer: startFaceNormalAndVertexCornersBuffer, + vertexBuffer: startFaceNormalAndVertexCornerIdsBuffer, componentDatatype: ComponentDatatype.FLOAT, componentsPerAttribute: 4, }, @@ -429,7 +429,7 @@ function createVertexArray(polylines, context) { polylines._startEllipsoidNormals = undefined; polylines._endEllipsoidNormals = undefined; polylines._startPositionAndHeights = undefined; - polylines._startFaceNormalAndVertexCorners = undefined; + polylines._startFaceNormalAndVertexCornerIds = undefined; polylines._endPositionAndHeights = undefined; polylines._endFaceNormalAndHalfWidths = undefined; polylines._vertexBatchIds = undefined; diff --git a/Source/Shaders/Vector3DTileClampedPolylinesVS.glsl b/Source/Shaders/Vector3DTileClampedPolylinesVS.glsl index eaa3e6c365..07668ffbe5 100644 --- a/Source/Shaders/Vector3DTileClampedPolylinesVS.glsl +++ b/Source/Shaders/Vector3DTileClampedPolylinesVS.glsl @@ -17,7 +17,6 @@ varying vec3 v_volumeUpEC; void main() { - vec3 scratchNormal; // vertex corner IDs // 3-----------7 // /| left /| @@ -30,22 +29,22 @@ void main() float isEnd = floor(startFaceNormalAndVertexCorner.w * 0.251); // 0 for front, 1 for end float isTop = floor(startFaceNormalAndVertexCorner.w * mix(0.51, 0.19, isEnd)); // 0 for bottom, 1 for top - scratchNormal = endPositionAndHeight.xyz - startPositionAndHeight.xyz; // scratchNormal = forward - vec3 right = normalize(cross(scratchNormal, startEllipsoidNormal)); + vec3 forward = endPositionAndHeight.xyz - startPositionAndHeight.xyz; + vec3 right = normalize(cross(forward, startEllipsoidNormal)); vec4 position = vec4(startPositionAndHeight.xyz, 1.0); - position.xyz += scratchNormal * isEnd; // scratchNormal = forward + position.xyz += forward * isEnd; - v_volumeUpEC = czm_normal * normalize(cross(right, scratchNormal)); + v_volumeUpEC = czm_normal * normalize(cross(right, forward)); // Push for volume height float offset; - scratchNormal = mix(startEllipsoidNormal, endEllipsoidNormal, isEnd); // scratchNormal = ellipsoidNormal + vec3 ellipsoidNormal = mix(startEllipsoidNormal, endEllipsoidNormal, isEnd); // offset height to create volume offset = mix(startPositionAndHeight.w, endPositionAndHeight.w, isEnd); offset = mix(u_minimumMaximumVectorHeights.y, u_minimumMaximumVectorHeights.x, isTop) - offset; - position.xyz += offset * scratchNormal; // scratchNormal = ellipsoidNormal + position.xyz += offset * ellipsoidNormal; // move from RTC to EC position = u_modifiedModelView * position; @@ -64,23 +63,23 @@ void main() // { \| } // o---------- polyline segment ----> // - scratchNormal = mix(-startFaceNormalAndVertexCorner.xyz, endFaceNormalAndHalfWidth.xyz, isEnd); + vec3 scratchNormal = mix(-startFaceNormalAndVertexCorner.xyz, endFaceNormalAndHalfWidth.xyz, isEnd); scratchNormal = cross(scratchNormal, mix(startEllipsoidNormal, endEllipsoidNormal, isEnd)); - scratchNormal = czm_normal * normalize(scratchNormal); + vec3 miterPushNormal = czm_normal * normalize(scratchNormal); offset = 2.0 * endFaceNormalAndHalfWidth.w * max(0.0, czm_metersPerPixel(position)); // offset = widthEC - offset = offset / dot(scratchNormal, right); - position.xyz += scratchNormal * (offset * sign(0.5 - mod(startFaceNormalAndVertexCorner.w, 2.0))); + offset = offset / dot(miterPushNormal, right); + position.xyz += miterPushNormal * (offset * sign(0.5 - mod(startFaceNormalAndVertexCorner.w, 2.0))); gl_Position = czm_depthClamp(czm_projection * position); position = u_modifiedModelView * vec4(startPositionAndHeight.xyz, 1.0); - scratchNormal = czm_normal * startFaceNormalAndVertexCorner.xyz; - v_startPlaneEC = vec4(scratchNormal, -dot(scratchNormal, position.xyz)); + vec3 startNormalEC = czm_normal * startFaceNormalAndVertexCorner.xyz; + v_startPlaneEC = vec4(startNormalEC, -dot(startNormalEC, position.xyz)); v_rightPlaneEC = vec4(right, -dot(right, position.xyz)); position = u_modifiedModelView * vec4(endPositionAndHeight.xyz, 1.0); - scratchNormal = czm_normal * endFaceNormalAndHalfWidth.xyz; - v_endPlaneEC = vec4(scratchNormal, -dot(scratchNormal, position.xyz)); + vec3 endNormalEC = czm_normal * endFaceNormalAndHalfWidth.xyz; + v_endPlaneEC = vec4(endNormalEC, -dot(endNormalEC, position.xyz)); v_halfWidth = endFaceNormalAndHalfWidth.w; -} \ No newline at end of file +} From d16882a0833174b5b1a20cca930609181f9c8524 Mon Sep 17 00:00:00 2001 From: ebogo1 Date: Wed, 3 Mar 2021 13:30:22 -0500 Subject: [PATCH 11/76] add experimental option to constructor --- Source/Scene/Cesium3DTileset.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Scene/Cesium3DTileset.js b/Source/Scene/Cesium3DTileset.js index 403627dd86..7568b273eb 100644 --- a/Source/Scene/Cesium3DTileset.js +++ b/Source/Scene/Cesium3DTileset.js @@ -101,7 +101,7 @@ import TileOrientedBoundingBox from "./TileOrientedBoundingBox.js"; * @param {Boolean} [options.debugShowRenderingStatistics=false] For debugging only. When true, draws labels to indicate the number of commands, points, triangles and features for each tile. * @param {Boolean} [options.debugShowMemoryUsage=false] For debugging only. When true, draws labels to indicate the texture and geometry memory in megabytes used by each tile. * @param {Boolean} [options.debugShowUrl=false] For debugging only. When true, draws labels to indicate the url of each tile. - * + * @param {Function} [options.examineVectorLinesFunction] Callback function for examining vector lines as they are being streamed. * @exception {DeveloperError} The tileset must be 3D Tiles version 0.0 or 1.0. * * @example @@ -903,7 +903,7 @@ function Cesium3DTileset(options) { * * @experimental */ - this.examineVectorLinesFunction = undefined; + this.examineVectorLinesFunction = options.examineVectorLinesFunction; var that = this; var resource; From 58b5789f43640084406af9142e27d2d6adc79789 Mon Sep 17 00:00:00 2001 From: ebogo1 Date: Wed, 3 Mar 2021 13:31:16 -0500 Subject: [PATCH 12/76] add missing line --- Source/Scene/Cesium3DTileset.js | 1 + 1 file changed, 1 insertion(+) diff --git a/Source/Scene/Cesium3DTileset.js b/Source/Scene/Cesium3DTileset.js index 7568b273eb..cf03decfa7 100644 --- a/Source/Scene/Cesium3DTileset.js +++ b/Source/Scene/Cesium3DTileset.js @@ -102,6 +102,7 @@ import TileOrientedBoundingBox from "./TileOrientedBoundingBox.js"; * @param {Boolean} [options.debugShowMemoryUsage=false] For debugging only. When true, draws labels to indicate the texture and geometry memory in megabytes used by each tile. * @param {Boolean} [options.debugShowUrl=false] For debugging only. When true, draws labels to indicate the url of each tile. * @param {Function} [options.examineVectorLinesFunction] Callback function for examining vector lines as they are being streamed. + * * @exception {DeveloperError} The tileset must be 3D Tiles version 0.0 or 1.0. * * @example From e07921d4745a72557e66f39988cf1db5368909b2 Mon Sep 17 00:00:00 2001 From: ebogo1 Date: Wed, 3 Mar 2021 15:40:00 -0500 Subject: [PATCH 13/76] improve docs --- Source/Scene/Cesium3DTileset.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Source/Scene/Cesium3DTileset.js b/Source/Scene/Cesium3DTileset.js index cf03decfa7..fba84ec2b1 100644 --- a/Source/Scene/Cesium3DTileset.js +++ b/Source/Scene/Cesium3DTileset.js @@ -886,7 +886,10 @@ function Cesium3DTileset(options) { * @type {Boolean} * @default false */ - this.debugShowMemoryUsage = defaultValue(options.debugShowMemoryUsage, false); + this.debugShowMtyemoryUsage = defaultValue( + options.debugShowMemoryUsage, + false + ); /** * This property is for debugging only; it is not optimized for production use. @@ -902,7 +905,9 @@ function Cesium3DTileset(options) { /** * Function for examining vector lines as they are being streamed. * - * @experimental + * @type {Function} + * @default undefined + * @experimental This feature is experimental and is subject to change without Cesium's standard deprecation policy. */ this.examineVectorLinesFunction = options.examineVectorLinesFunction; From 5e36ac5ceb9f48df4b7bb3447ed9125cd8ba64f8 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Wed, 3 Mar 2021 19:54:57 -0500 Subject: [PATCH 14/76] Tweak CHANGES.md since TranslucentTileClassification is not public --- CHANGES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index e17b0fe8bc..f73bf84367 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,7 +5,7 @@ ##### Additions :tada: - Added `Cesium3DTileset.pickPrimitive` for rendering primitives instead of the tileset during the pick pass. -- Added `TranslucentTileClassification` for classifying translucent 3D Tiles. +- Added support for for drawing ground primitives on translucent 3D Tiles. [#9399](https://github.com/CesiumGS/cesium/pull/9399) ### 1.79.1 - 2021-03-01 From fc9a44bd8edc18db1d60526fbe3a19363e68c520 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Wed, 3 Mar 2021 20:14:03 -0500 Subject: [PATCH 15/76] Minor style/readability tweaks to Cesium3DTile commands --- Source/Scene/Cesium3DTile.js | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/Source/Scene/Cesium3DTile.js b/Source/Scene/Cesium3DTile.js index 8408437b3d..de6676cbf5 100644 --- a/Source/Scene/Cesium3DTile.js +++ b/Source/Scene/Cesium3DTile.js @@ -1612,17 +1612,20 @@ function updateClippingPlanes(tile, tileset) { * @private */ Cesium3DTile.prototype.update = function (tileset, frameState, passOptions) { - var initCommandLength = frameState.commandList.length; + var commandStart = frameState.commandList.length; + updateClippingPlanes(this, tileset); applyDebugSettings(this, tileset, frameState, passOptions); updateContent(this, tileset, frameState); - var commandsLength = (this._commandsLength = - frameState.commandList.length - initCommandLength); + + var commandEnd = frameState.commandList.length; + var commandsLength = commandEnd - commandStart; + this._commandsLength = commandsLength; for (var i = 0; i < commandsLength; ++i) { - var command = frameState.commandList[initCommandLength + i]; - command.depthForTranslucentClassification = - command.pass === Pass.TRANSLUCENT; + var command = frameState.commandList[commandStart + i]; + var translucent = command.pass === Pass.TRANSLUCENT; + command.depthForTranslucentClassification = translucent; } this.clippingPlanesDirty = false; // reset after content update From beaaaef2359639760b428f8139fb9de7763f068a Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Wed, 3 Mar 2021 20:20:14 -0500 Subject: [PATCH 16/76] Rename --- Source/Scene/Scene.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Scene/Scene.js b/Source/Scene/Scene.js index 3a1f2c7ff7..eb4f67f064 100644 --- a/Source/Scene/Scene.js +++ b/Source/Scene/Scene.js @@ -2659,10 +2659,10 @@ function executeCommands(scene, passState) { ); // Classification for translucent 3D Tiles - var hasClassificationOnTranslucent = + var has3DTilesClassificationCommands = frustumCommands.indices[Pass.CESIUM_3D_TILE_CLASSIFICATION] > 0; if ( - hasClassificationOnTranslucent && + has3DTilesClassificationCommands && view.translucentTileClassification.isSupported() ) { view.translucentTileClassification.executeTranslucentCommands( From 0351120f0763b88677d20326cdcb31e2cc05e1fc Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Wed, 3 Mar 2021 21:09:07 -0500 Subject: [PATCH 17/76] Premultiplied alpha to fix colors for globe translucency --- Source/Scene/Vector3DTileClampedPolylines.js | 2 +- Source/Shaders/Vector3DTileClampedPolylinesFS.glsl | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Source/Scene/Vector3DTileClampedPolylines.js b/Source/Scene/Vector3DTileClampedPolylines.js index cad5b034e9..a8074171d8 100644 --- a/Source/Scene/Vector3DTileClampedPolylines.js +++ b/Source/Scene/Vector3DTileClampedPolylines.js @@ -478,7 +478,7 @@ function getRenderState(mask3DTiles) { cull: { enabled: true, // prevent double-draw. Geometry is "inverted" (reversed winding order) so we're drawing backfaces. }, - blending: BlendingState.ALPHA_BLEND, + blending: BlendingState.PRE_MULTIPLIED_ALPHA_BLEND, depthMask: false, stencilTest: { enabled: mask3DTiles, diff --git a/Source/Shaders/Vector3DTileClampedPolylinesFS.glsl b/Source/Shaders/Vector3DTileClampedPolylinesFS.glsl index 444edf3e0c..2bb70a206f 100644 --- a/Source/Shaders/Vector3DTileClampedPolylinesFS.glsl +++ b/Source/Shaders/Vector3DTileClampedPolylinesFS.glsl @@ -48,5 +48,8 @@ void main() } gl_FragColor = u_highlightColor; + // Premultiply alpha. Required for classification primitives on translucent globe. + gl_FragColor.rgb *= gl_FragColor.a; + czm_writeDepthClamp(); } \ No newline at end of file From 43f6efd46b0dff2715cb223bb102e84bd52b602c Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Thu, 4 Mar 2021 13:52:51 -0500 Subject: [PATCH 18/76] Fix classification color by doing premultiplied alpha --- .../PostProcessStages/CompositeTranslucentClassification.glsl | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Source/Shaders/PostProcessStages/CompositeTranslucentClassification.glsl b/Source/Shaders/PostProcessStages/CompositeTranslucentClassification.glsl index 544ab61cc9..cf7ec7cdfb 100644 --- a/Source/Shaders/PostProcessStages/CompositeTranslucentClassification.glsl +++ b/Source/Shaders/PostProcessStages/CompositeTranslucentClassification.glsl @@ -22,6 +22,9 @@ void main() { discard; } +#else + // Reverse premultiplication process to get the correct composited result of the classification primitives + color.rgb /= color.a; #endif gl_FragColor = color; #endif From 8614b5990b71ceb8693e10cae4dcda811bac567e Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Thu, 4 Mar 2021 15:04:48 -0500 Subject: [PATCH 19/76] Use Sampler.NEAREST --- Source/Scene/TranslucentTileClassification.js | 24 +++---------------- 1 file changed, 3 insertions(+), 21 deletions(-) diff --git a/Source/Scene/TranslucentTileClassification.js b/Source/Scene/TranslucentTileClassification.js index 85ebbe570c..a37b81673d 100644 --- a/Source/Scene/TranslucentTileClassification.js +++ b/Source/Scene/TranslucentTileClassification.js @@ -12,9 +12,6 @@ import RenderState from "../Renderer/RenderState.js"; import Sampler from "../Renderer/Sampler.js"; import ShaderSource from "../Renderer/ShaderSource.js"; import Texture from "../Renderer/Texture.js"; -import TextureWrap from "../Renderer/TextureWrap.js"; -import TextureMagnificationFilter from "../Renderer/TextureMagnificationFilter.js"; -import TextureMinificationFilter from "../Renderer/TextureMinificationFilter.js"; import CompareAndPackTranslucentDepth from "../Shaders/CompareAndPackTranslucentDepth.js"; import CompositeTranslucentClassification from "../Shaders/PostProcessStages/CompositeTranslucentClassification.js"; import BlendingState from "./BlendingState.js"; @@ -135,12 +132,7 @@ function rgbaTexture(context, width, height) { height: height, pixelFormat: PixelFormat.RGBA, pixelDatatype: PixelDatatype.UNSIGNED_BYTE, - sampler: new Sampler({ - wrapS: TextureWrap.CLAMP_TO_EDGE, - wrapT: TextureWrap.CLAMP_TO_EDGE, - minificationFilter: TextureMinificationFilter.NEAREST, - magnificationFilter: TextureMagnificationFilter.NEAREST, - }), + sampler: Sampler.NEAREST, }); } @@ -156,12 +148,7 @@ function updateTextures(transpClass, context, width, height) { height: height, pixelFormat: PixelFormat.DEPTH_STENCIL, pixelDatatype: PixelDatatype.UNSIGNED_INT_24_8, - sampler: new Sampler({ - wrapS: TextureWrap.CLAMP_TO_EDGE, - wrapT: TextureWrap.CLAMP_TO_EDGE, - minificationFilter: TextureMinificationFilter.NEAREST, - magnificationFilter: TextureMagnificationFilter.NEAREST, - }), + sampler: Sampler.NEAREST, }); transpClass._packedTranslucentDepth = new Texture({ @@ -170,12 +157,7 @@ function updateTextures(transpClass, context, width, height) { height: height, pixelFormat: PixelFormat.RGBA, pixelDatatype: PixelDatatype.UNSIGNED_BYTE, - sampler: new Sampler({ - wrapS: TextureWrap.CLAMP_TO_EDGE, - wrapT: TextureWrap.CLAMP_TO_EDGE, - minificationFilter: TextureMinificationFilter.NEAREST, - magnificationFilter: TextureMagnificationFilter.NEAREST, - }), + sampler: Sampler.NEAREST, }); } From d41a71d69073ff271102cc8bcebee0b6f75351de Mon Sep 17 00:00:00 2001 From: hpinkos Date: Fri, 5 Mar 2021 14:37:52 -0500 Subject: [PATCH 20/76] use var version --- gulpfile.cjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gulpfile.cjs b/gulpfile.cjs index bfb7e26dd8..57262b23e8 100644 --- a/gulpfile.cjs +++ b/gulpfile.cjs @@ -1445,7 +1445,7 @@ export default "' + } function createCesiumJs() { - let contents = `export const VERSION = '${version}';\n`; + let contents = `export var VERSION = '${version}';\n`; globby.sync(sourceFiles).forEach(function (file) { file = path.relative("Source", file); From a7b1859c0b23e7b2a4a7217f94cb5cbf4cca2b0f Mon Sep 17 00:00:00 2001 From: ebogo1 Date: Fri, 5 Mar 2021 16:08:46 -0500 Subject: [PATCH 21/76] minor fixes --- Source/Scene/Cesium3DTileset.js | 11 +++-------- .../Shaders/Vector3DTileClampedPolylinesFS.glsl | 2 +- .../createVectorTileClampedPolylines.js | 15 ++++++++------- 3 files changed, 12 insertions(+), 16 deletions(-) diff --git a/Source/Scene/Cesium3DTileset.js b/Source/Scene/Cesium3DTileset.js index fba84ec2b1..384b2c3e00 100644 --- a/Source/Scene/Cesium3DTileset.js +++ b/Source/Scene/Cesium3DTileset.js @@ -101,7 +101,6 @@ import TileOrientedBoundingBox from "./TileOrientedBoundingBox.js"; * @param {Boolean} [options.debugShowRenderingStatistics=false] For debugging only. When true, draws labels to indicate the number of commands, points, triangles and features for each tile. * @param {Boolean} [options.debugShowMemoryUsage=false] For debugging only. When true, draws labels to indicate the texture and geometry memory in megabytes used by each tile. * @param {Boolean} [options.debugShowUrl=false] For debugging only. When true, draws labels to indicate the url of each tile. - * @param {Function} [options.examineVectorLinesFunction] Callback function for examining vector lines as they are being streamed. * * @exception {DeveloperError} The tileset must be 3D Tiles version 0.0 or 1.0. * @@ -886,10 +885,7 @@ function Cesium3DTileset(options) { * @type {Boolean} * @default false */ - this.debugShowMtyemoryUsage = defaultValue( - options.debugShowMemoryUsage, - false - ); + this.debugShowMemoryUsage = defaultValue(options.debugShowMemoryUsage, false); /** * This property is for debugging only; it is not optimized for production use. @@ -906,10 +902,9 @@ function Cesium3DTileset(options) { * Function for examining vector lines as they are being streamed. * * @type {Function} - * @default undefined - * @experimental This feature is experimental and is subject to change without Cesium's standard deprecation policy. + * @private */ - this.examineVectorLinesFunction = options.examineVectorLinesFunction; + this.examineVectorLinesFunction = undefined; var that = this; var resource; diff --git a/Source/Shaders/Vector3DTileClampedPolylinesFS.glsl b/Source/Shaders/Vector3DTileClampedPolylinesFS.glsl index 2bb70a206f..6126cd973e 100644 --- a/Source/Shaders/Vector3DTileClampedPolylinesFS.glsl +++ b/Source/Shaders/Vector3DTileClampedPolylinesFS.glsl @@ -52,4 +52,4 @@ void main() gl_FragColor.rgb *= gl_FragColor.a; czm_writeDepthClamp(); -} \ No newline at end of file +} diff --git a/Source/WorkersES6/createVectorTileClampedPolylines.js b/Source/WorkersES6/createVectorTileClampedPolylines.js index 3a41d5b197..603326da2b 100644 --- a/Source/WorkersES6/createVectorTileClampedPolylines.js +++ b/Source/WorkersES6/createVectorTileClampedPolylines.js @@ -101,7 +101,7 @@ function VertexAttributesAndIndices(volumesCount) { this.startEllipsoidNormals = new Float32Array(vec3Floats); this.endEllipsoidNormals = new Float32Array(vec3Floats); this.startPositionAndHeights = new Float32Array(vec4Floats); - this.startFaceNormalAndVertexCorners = new Float32Array(vec4Floats); + this.startFaceNormalAndVertexCornerIds = new Float32Array(vec4Floats); this.endPositionAndHeights = new Float32Array(vec4Floats); this.endFaceNormalAndHalfWidths = new Float32Array(vec4Floats); this.vertexBatchIds = new Uint16Array(vertexCount); @@ -253,7 +253,8 @@ VertexAttributesAndIndices.prototype.addVolume = function ( var startEllipsoidNormals = this.startEllipsoidNormals; var endEllipsoidNormals = this.endEllipsoidNormals; var startPositionAndHeights = this.startPositionAndHeights; - var startFaceNormalAndVertexCorners = this.startFaceNormalAndVertexCorners; + var startFaceNormalAndVertexCornerIds = this + .startFaceNormalAndVertexCornerIds; var endPositionAndHeights = this.endPositionAndHeights; var endFaceNormalAndHalfWidths = this.endFaceNormalAndHalfWidths; var vertexBatchIds = this.vertexBatchIds; @@ -275,10 +276,10 @@ VertexAttributesAndIndices.prototype.addVolume = function ( Cartesian3.pack( startFaceNormal, - startFaceNormalAndVertexCorners, + startFaceNormalAndVertexCornerIds, vec4Offset ); - startFaceNormalAndVertexCorners[vec4Offset + 3] = i; + startFaceNormalAndVertexCornerIds[vec4Offset + 3] = i; Cartesian3.pack(endFaceNormal, endFaceNormalAndHalfWidths, vec4Offset); endFaceNormalAndHalfWidths[vec4Offset + 3] = halfWidth; @@ -480,7 +481,7 @@ function createVectorTileClampedPolylines(parameters, transferableObjects) { transferableObjects.push(attribsAndIndices.endEllipsoidNormals.buffer); transferableObjects.push(attribsAndIndices.startPositionAndHeights.buffer); transferableObjects.push( - attribsAndIndices.startFaceNormalAndVertexCorners.buffer + attribsAndIndices.startFaceNormalAndVertexCornerIds.buffer ); transferableObjects.push(attribsAndIndices.endPositionAndHeights.buffer); transferableObjects.push(attribsAndIndices.endFaceNormalAndHalfWidths.buffer); @@ -495,8 +496,8 @@ function createVectorTileClampedPolylines(parameters, transferableObjects) { startEllipsoidNormals: attribsAndIndices.startEllipsoidNormals.buffer, endEllipsoidNormals: attribsAndIndices.endEllipsoidNormals.buffer, startPositionAndHeights: attribsAndIndices.startPositionAndHeights.buffer, - startFaceNormalAndVertexCorners: - attribsAndIndices.startFaceNormalAndVertexCorners.buffer, + startFaceNormalAndVertexCornerIds: + attribsAndIndices.startFaceNormalAndVertexCornerIds.buffer, endPositionAndHeights: attribsAndIndices.endPositionAndHeights.buffer, endFaceNormalAndHalfWidths: attribsAndIndices.endFaceNormalAndHalfWidths.buffer, From 28231f6317cb47ac131f0c9d1b506609561535d6 Mon Sep 17 00:00:00 2001 From: Scott Hunter Date: Mon, 8 Mar 2021 10:49:06 -0500 Subject: [PATCH 22/76] Update CZML validation document. --- Specs/Data/CZML/ValidationDocument.czml | 273 +++++++++++++++++++++++- Specs/DataSources/CzmlDataSourceSpec.js | 12 ++ 2 files changed, 282 insertions(+), 3 deletions(-) diff --git a/Specs/Data/CZML/ValidationDocument.czml b/Specs/Data/CZML/ValidationDocument.czml index 2688bbd0c3..6ef9fd7750 100644 --- a/Specs/Data/CZML/ValidationDocument.czml +++ b/Specs/Data/CZML/ValidationDocument.czml @@ -742,7 +742,19 @@ ] }, "environmentIntersectionWidth":13317, - "showThroughEllipsoid":true + "showThroughEllipsoid":true, + "showViewshed":true, + "viewshedVisibleColor":{ + "rgba":[ + 80,142,248,57 + ] + }, + "viewshedOccludedColor":{ + "rgba":[ + 166,20,225,110 + ] + }, + "viewshedResolution":9164 }, "agi_customPatternSensor":{ "show":true, @@ -819,7 +831,19 @@ ] }, "environmentIntersectionWidth":53576, - "showThroughEllipsoid":true + "showThroughEllipsoid":true, + "showViewshed":true, + "viewshedVisibleColor":{ + "rgba":[ + 38,54,223,91 + ] + }, + "viewshedOccludedColor":{ + "rgba":[ + 229,38,249,99 + ] + }, + "viewshedResolution":25862 }, "agi_rectangularSensor":{ "show":true, @@ -892,7 +916,19 @@ ] }, "environmentIntersectionWidth":64839, - "showThroughEllipsoid":true + "showThroughEllipsoid":true, + "showViewshed":true, + "viewshedVisibleColor":{ + "rgba":[ + 239,86,8,93 + ] + }, + "viewshedOccludedColor":{ + "rgba":[ + 20,22,45,26 + ] + }, + "viewshedResolution":33690 }, "agi_fan":{ "show":true, @@ -5103,6 +5139,26 @@ } } }, + { + "id":"constant_agi_conicSensor_viewshedVisibleColor_rgbaf", + "agi_conicSensor":{ + "viewshedVisibleColor":{ + "rgbaf":[ + 0.5490196078431373,0.23529411764705882,0.34509803921568627,0.20392156862745098 + ] + } + } + }, + { + "id":"constant_agi_conicSensor_viewshedOccludedColor_rgbaf", + "agi_conicSensor":{ + "viewshedOccludedColor":{ + "rgbaf":[ + 0.9529411764705882,0.3843137254901961,0.8901960784313725,0.00784313725490196 + ] + } + } + }, { "id":"constant_agi_customPatternSensor_directions_unitSpherical", "agi_customPatternSensor":{ @@ -6131,6 +6187,26 @@ } } }, + { + "id":"constant_agi_customPatternSensor_viewshedVisibleColor_rgbaf", + "agi_customPatternSensor":{ + "viewshedVisibleColor":{ + "rgbaf":[ + 0.011764705882352941,0.3058823529411765,0.09019607843137255,0.9490196078431372 + ] + } + } + }, + { + "id":"constant_agi_customPatternSensor_viewshedOccludedColor_rgbaf", + "agi_customPatternSensor":{ + "viewshedOccludedColor":{ + "rgbaf":[ + 0.6196078431372549,0.08627450980392157,0.47843137254901963,0.2549019607843137 + ] + } + } + }, { "id":"constant_agi_rectangularSensor_intersectionColor_rgbaf", "agi_rectangularSensor":{ @@ -7126,6 +7202,26 @@ } } }, + { + "id":"constant_agi_rectangularSensor_viewshedVisibleColor_rgbaf", + "agi_rectangularSensor":{ + "viewshedVisibleColor":{ + "rgbaf":[ + 0.9215686274509803,0.3411764705882353,0.16470588235294117,0.054901960784313725 + ] + } + } + }, + { + "id":"constant_agi_rectangularSensor_viewshedOccludedColor_rgbaf", + "agi_rectangularSensor":{ + "viewshedOccludedColor":{ + "rgbaf":[ + 0.1843137254901961,0.6392156862745098,0.6666666666666666,0.5568627450980392 + ] + } + } + }, { "id":"constant_agi_fan_directions_unitSpherical", "agi_fan":{ @@ -8482,6 +8578,18 @@ }, "showThroughEllipsoid":{ "reference":"Constant#conicSensor.showThroughEllipsoid" + }, + "showViewshed":{ + "reference":"Constant#conicSensor.showViewshed" + }, + "viewshedVisibleColor":{ + "reference":"Constant#conicSensor.viewshedVisibleColor" + }, + "viewshedOccludedColor":{ + "reference":"Constant#conicSensor.viewshedOccludedColor" + }, + "viewshedResolution":{ + "reference":"Constant#conicSensor.viewshedResolution" } }, "agi_customPatternSensor":{ @@ -8567,6 +8675,18 @@ }, "showThroughEllipsoid":{ "reference":"Constant#customPatternSensor.showThroughEllipsoid" + }, + "showViewshed":{ + "reference":"Constant#customPatternSensor.showViewshed" + }, + "viewshedVisibleColor":{ + "reference":"Constant#customPatternSensor.viewshedVisibleColor" + }, + "viewshedOccludedColor":{ + "reference":"Constant#customPatternSensor.viewshedOccludedColor" + }, + "viewshedResolution":{ + "reference":"Constant#customPatternSensor.viewshedResolution" } }, "agi_rectangularSensor":{ @@ -8658,6 +8778,18 @@ }, "showThroughEllipsoid":{ "reference":"Constant#rectangularSensor.showThroughEllipsoid" + }, + "showViewshed":{ + "reference":"Constant#rectangularSensor.showViewshed" + }, + "viewshedVisibleColor":{ + "reference":"Constant#rectangularSensor.viewshedVisibleColor" + }, + "viewshedOccludedColor":{ + "reference":"Constant#rectangularSensor.viewshedOccludedColor" + }, + "viewshedResolution":{ + "reference":"Constant#rectangularSensor.viewshedResolution" } }, "agi_fan":{ @@ -12538,6 +12670,27 @@ 0,54383, 3600,48814 ] + }, + "viewshedVisibleColor":{ + "epoch":"2016-06-17T12:00:00Z", + "rgba":[ + 0,219,102,126,208, + 3600,165,12,88,3 + ] + }, + "viewshedOccludedColor":{ + "epoch":"2016-06-17T12:00:00Z", + "rgba":[ + 0,47,229,96,105, + 3600,65,153,9,64 + ] + }, + "viewshedResolution":{ + "epoch":"2016-06-17T12:00:00Z", + "number":[ + 0,38357, + 3600,30316 + ] } }, "agi_customPatternSensor":{ @@ -12630,6 +12783,27 @@ 0,15461, 3600,8e3 ] + }, + "viewshedVisibleColor":{ + "epoch":"2016-06-17T12:00:00Z", + "rgba":[ + 0,221,155,36,117, + 3600,249,126,78,129 + ] + }, + "viewshedOccludedColor":{ + "epoch":"2016-06-17T12:00:00Z", + "rgba":[ + 0,228,27,237,134, + 3600,254,162,80,84 + ] + }, + "viewshedResolution":{ + "epoch":"2016-06-17T12:00:00Z", + "number":[ + 0,44590, + 3600,31959 + ] } }, "agi_rectangularSensor":{ @@ -12736,6 +12910,27 @@ 0,637, 3600,10677 ] + }, + "viewshedVisibleColor":{ + "epoch":"2016-06-17T12:00:00Z", + "rgba":[ + 0,220,157,178,92, + 3600,213,65,75,26 + ] + }, + "viewshedOccludedColor":{ + "epoch":"2016-06-17T12:00:00Z", + "rgba":[ + 0,202,44,192,10, + 3600,107,226,246,207 + ] + }, + "viewshedResolution":{ + "epoch":"2016-06-17T12:00:00Z", + "number":[ + 0,24619, + 3600,54818 + ] } }, "agi_fan":{ @@ -17901,6 +18096,30 @@ } } }, + { + "id":"sampled_conicSensor_viewshedVisibleColor_rgbaf", + "agi_conicSensor":{ + "viewshedVisibleColor":{ + "epoch":"2016-06-17T12:00:00Z", + "rgbaf":[ + 0,0.6666666666666666,0.13333333333333333,0.2784313725490196,0.2549019607843137, + 3600,0.11372549019607843,0.5372549019607843,0.4666666666666667,0.7098039215686275 + ] + } + } + }, + { + "id":"sampled_conicSensor_viewshedOccludedColor_rgbaf", + "agi_conicSensor":{ + "viewshedOccludedColor":{ + "epoch":"2016-06-17T12:00:00Z", + "rgbaf":[ + 0,0.32941176470588235,0.29411764705882354,0.4666666666666667,0.00784313725490196, + 3600,0.2901960784313726,0.5254901960784314,0.6,0.5882352941176471 + ] + } + } + }, { "id":"sampled_customPatternSensor_intersectionColor_rgbaf", "agi_customPatternSensor":{ @@ -19155,6 +19374,30 @@ } } }, + { + "id":"sampled_customPatternSensor_viewshedVisibleColor_rgbaf", + "agi_customPatternSensor":{ + "viewshedVisibleColor":{ + "epoch":"2016-06-17T12:00:00Z", + "rgbaf":[ + 0,0.10196078431372549,0.23529411764705882,0.5372549019607843,0.6509803921568628, + 3600,0.9450980392156862,0.047058823529411764,0.3686274509803922,0.7450980392156863 + ] + } + } + }, + { + "id":"sampled_customPatternSensor_viewshedOccludedColor_rgbaf", + "agi_customPatternSensor":{ + "viewshedOccludedColor":{ + "epoch":"2016-06-17T12:00:00Z", + "rgbaf":[ + 0,0.6588235294117647,0.7058823529411765,0.4823529411764706,0.6078431372549019, + 3600,0.9176470588235294,0.5568627450980392,0.596078431372549,0.7372549019607844 + ] + } + } + }, { "id":"sampled_rectangularSensor_intersectionColor_rgbaf", "agi_rectangularSensor":{ @@ -20409,6 +20652,30 @@ } } }, + { + "id":"sampled_rectangularSensor_viewshedVisibleColor_rgbaf", + "agi_rectangularSensor":{ + "viewshedVisibleColor":{ + "epoch":"2016-06-17T12:00:00Z", + "rgbaf":[ + 0,0.26666666666666666,0.15294117647058825,0.8196078431372549,0.08235294117647059, + 3600,0.19215686274509805,0.06274509803921569,0.4666666666666667,0.06274509803921569 + ] + } + } + }, + { + "id":"sampled_rectangularSensor_viewshedOccludedColor_rgbaf", + "agi_rectangularSensor":{ + "viewshedOccludedColor":{ + "epoch":"2016-06-17T12:00:00Z", + "rgbaf":[ + 0,0.35294117647058826,0.32941176470588235,0.050980392156862744,0.1843137254901961, + 3600,0.7254901960784313,0.16470588235294117,0.06666666666666667,0.27450980392156865 + ] + } + } + }, { "id":"sampled_fan_material_solidColor_color", "agi_fan":{ diff --git a/Specs/DataSources/CzmlDataSourceSpec.js b/Specs/DataSources/CzmlDataSourceSpec.js index da7e93db7e..b0454f4554 100644 --- a/Specs/DataSources/CzmlDataSourceSpec.js +++ b/Specs/DataSources/CzmlDataSourceSpec.js @@ -7932,6 +7932,8 @@ describe("DataSources/CzmlDataSource", function () { expect(e = dataSource.entities.getById('constant_conicSensor_environmentOcclusionMaterial_checkerboard_evenColor')).toBeDefined(); expect(e = dataSource.entities.getById('constant_conicSensor_environmentOcclusionMaterial_checkerboard_oddColor')).toBeDefined(); expect(e = dataSource.entities.getById('constant_agi_conicSensor_environmentIntersectionColor_rgbaf')).toBeDefined(); + expect(e = dataSource.entities.getById('constant_agi_conicSensor_viewshedVisibleColor_rgbaf')).toBeDefined(); + expect(e = dataSource.entities.getById('constant_agi_conicSensor_viewshedOccludedColor_rgbaf')).toBeDefined(); expect(e = dataSource.entities.getById('constant_agi_customPatternSensor_directions_unitSpherical')).toBeDefined(); expect(e = dataSource.entities.getById('constant_agi_customPatternSensor_directions_cartesian')).toBeDefined(); expect(e = dataSource.entities.getById('constant_agi_customPatternSensor_directions_unitCartesian')).toBeDefined(); @@ -7992,6 +7994,8 @@ describe("DataSources/CzmlDataSource", function () { expect(e = dataSource.entities.getById('constant_customPatternSensor_environmentOcclusionMaterial_checkerboard_evenColor')).toBeDefined(); expect(e = dataSource.entities.getById('constant_customPatternSensor_environmentOcclusionMaterial_checkerboard_oddColor')).toBeDefined(); expect(e = dataSource.entities.getById('constant_agi_customPatternSensor_environmentIntersectionColor_rgbaf')).toBeDefined(); + expect(e = dataSource.entities.getById('constant_agi_customPatternSensor_viewshedVisibleColor_rgbaf')).toBeDefined(); + expect(e = dataSource.entities.getById('constant_agi_customPatternSensor_viewshedOccludedColor_rgbaf')).toBeDefined(); expect(e = dataSource.entities.getById('constant_agi_rectangularSensor_intersectionColor_rgbaf')).toBeDefined(); expect(e = dataSource.entities.getById('constant_rectangularSensor_lateralSurfaceMaterial_solidColor_color')).toBeDefined(); expect(e = dataSource.entities.getById('material_rectangularSensor_lateralSurfaceMaterial_image')).toBeDefined(); @@ -8049,6 +8053,8 @@ describe("DataSources/CzmlDataSource", function () { expect(e = dataSource.entities.getById('constant_rectangularSensor_environmentOcclusionMaterial_checkerboard_evenColor')).toBeDefined(); expect(e = dataSource.entities.getById('constant_rectangularSensor_environmentOcclusionMaterial_checkerboard_oddColor')).toBeDefined(); expect(e = dataSource.entities.getById('constant_agi_rectangularSensor_environmentIntersectionColor_rgbaf')).toBeDefined(); + expect(e = dataSource.entities.getById('constant_agi_rectangularSensor_viewshedVisibleColor_rgbaf')).toBeDefined(); + expect(e = dataSource.entities.getById('constant_agi_rectangularSensor_viewshedOccludedColor_rgbaf')).toBeDefined(); expect(e = dataSource.entities.getById('constant_agi_fan_directions_unitSpherical')).toBeDefined(); expect(e = dataSource.entities.getById('constant_agi_fan_directions_cartesian')).toBeDefined(); expect(e = dataSource.entities.getById('constant_agi_fan_directions_unitCartesian')).toBeDefined(); @@ -9881,6 +9887,8 @@ describe("DataSources/CzmlDataSource", function () { expect(e = dataSource.entities.getById('sampled_conicSensor_environmentOcclusionMaterial_checkerboard_evenColor')).toBeDefined(); expect(e = dataSource.entities.getById('sampled_conicSensor_environmentOcclusionMaterial_checkerboard_oddColor')).toBeDefined(); expect(e = dataSource.entities.getById('sampled_conicSensor_environmentIntersectionColor_rgbaf')).toBeDefined(); + expect(e = dataSource.entities.getById('sampled_conicSensor_viewshedVisibleColor_rgbaf')).toBeDefined(); + expect(e = dataSource.entities.getById('sampled_conicSensor_viewshedOccludedColor_rgbaf')).toBeDefined(); expect(e = dataSource.entities.getById('sampled_customPatternSensor_intersectionColor_rgbaf')).toBeDefined(); expect(e = dataSource.entities.getById('sampled_customPatternSensor_lateralSurfaceMaterial_solidColor_color')).toBeDefined(); expect(e = dataSource.entities.getById('sampled_customPatternSensor_lateralSurfaceMaterial_image')).toBeDefined(); @@ -9938,6 +9946,8 @@ describe("DataSources/CzmlDataSource", function () { expect(e = dataSource.entities.getById('sampled_customPatternSensor_environmentOcclusionMaterial_checkerboard_evenColor')).toBeDefined(); expect(e = dataSource.entities.getById('sampled_customPatternSensor_environmentOcclusionMaterial_checkerboard_oddColor')).toBeDefined(); expect(e = dataSource.entities.getById('sampled_customPatternSensor_environmentIntersectionColor_rgbaf')).toBeDefined(); + expect(e = dataSource.entities.getById('sampled_customPatternSensor_viewshedVisibleColor_rgbaf')).toBeDefined(); + expect(e = dataSource.entities.getById('sampled_customPatternSensor_viewshedOccludedColor_rgbaf')).toBeDefined(); expect(e = dataSource.entities.getById('sampled_rectangularSensor_intersectionColor_rgbaf')).toBeDefined(); expect(e = dataSource.entities.getById('sampled_rectangularSensor_lateralSurfaceMaterial_solidColor_color')).toBeDefined(); expect(e = dataSource.entities.getById('sampled_rectangularSensor_lateralSurfaceMaterial_image')).toBeDefined(); @@ -9995,6 +10005,8 @@ describe("DataSources/CzmlDataSource", function () { expect(e = dataSource.entities.getById('sampled_rectangularSensor_environmentOcclusionMaterial_checkerboard_evenColor')).toBeDefined(); expect(e = dataSource.entities.getById('sampled_rectangularSensor_environmentOcclusionMaterial_checkerboard_oddColor')).toBeDefined(); expect(e = dataSource.entities.getById('sampled_rectangularSensor_environmentIntersectionColor_rgbaf')).toBeDefined(); + expect(e = dataSource.entities.getById('sampled_rectangularSensor_viewshedVisibleColor_rgbaf')).toBeDefined(); + expect(e = dataSource.entities.getById('sampled_rectangularSensor_viewshedOccludedColor_rgbaf')).toBeDefined(); expect(e = dataSource.entities.getById('sampled_fan_material_solidColor_color')).toBeDefined(); expect(e = dataSource.entities.getById('sampled_fan_material_image')).toBeDefined(); expect(e = dataSource.entities.getById('sampled_fan_material_grid')).toBeDefined(); From 9c0c676c54c50e8bdf2ee32540a334f3d8f66589 Mon Sep 17 00:00:00 2001 From: Ian Lilley Date: Mon, 8 Mar 2021 13:18:05 -0500 Subject: [PATCH 23/76] removed request scheduler changes --- Source/Core/RequestScheduler.js | 58 +++++++++++++++--------------- Specs/Core/RequestSchedulerSpec.js | 43 +++++++++++----------- 2 files changed, 51 insertions(+), 50 deletions(-) diff --git a/Source/Core/RequestScheduler.js b/Source/Core/RequestScheduler.js index 33dd71ae0f..cc364908d8 100644 --- a/Source/Core/RequestScheduler.js +++ b/Source/Core/RequestScheduler.js @@ -3,15 +3,14 @@ import when from "../ThirdParty/when.js"; import Check from "./Check.js"; import defaultValue from "./defaultValue.js"; import defined from "./defined.js"; -import DoubleEndedPriorityQueue from "./DoubleEndedPriorityQueue.js"; import Event from "./Event.js"; +import Heap from "./Heap.js"; import isBlobUri from "./isBlobUri.js"; import isDataUri from "./isDataUri.js"; import RequestState from "./RequestState.js"; function sortRequests(a, b) { - // lower number = higher priority, so flip the order - return b.priority - a.priority; + return a.priority - b.priority; } var statistics = { @@ -24,11 +23,12 @@ var statistics = { lastNumberOfActiveRequests: 0, }; -var priorityQueueLength = 20; -var requestQueue = new DoubleEndedPriorityQueue({ +var priorityHeapLength = 20; +var requestHeap = new Heap({ comparator: sortRequests, - maximumLength: priorityQueueLength, }); +requestHeap.maximumLength = priorityHeapLength; +requestHeap.reserve(priorityHeapLength); var activeRequests = []; var numberOfActiveRequestsByServer = {}; @@ -121,7 +121,7 @@ Object.defineProperties(RequestScheduler, { }, /** - * The maximum size of the priority 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 * @@ -129,20 +129,22 @@ Object.defineProperties(RequestScheduler, { * @default 20 * @private */ - priorityQueueLength: { + priorityHeapLength: { get: function () { - return priorityQueueLength; + return priorityHeapLength; }, set: function (value) { - // If the new length shrinks the queue, need to cancel some of the requests. - if (value < priorityQueueLength) { - while (requestQueue.length > value) { - var request = requestQueue.removeMinimum(); + // 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); } } - priorityQueueLength = value; - requestQueue.maximumLength = value; + priorityHeapLength = value; + requestHeap.maximumLength = value; + requestHeap.reserve(value); }, }, }); @@ -269,13 +271,13 @@ RequestScheduler.update = function () { } activeRequests.length -= removeCount; - // Update priority of issued requests and resort the queue - var issuedRequests = requestQueue.internalArray; - var issuedLength = requestQueue.length; + // 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]); } - requestQueue.resort(); + 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 @@ -284,9 +286,9 @@ RequestScheduler.update = function () { 0 ); var filledSlots = 0; - while (filledSlots < openSlots && requestQueue.length > 0) { - // Loop until all open slots are filled or the queue becomes empty - request = requestQueue.removeMaximum(); + 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); @@ -381,17 +383,17 @@ RequestScheduler.request = function (request) { return undefined; } - // Insert into the priority queue and see if a request was bumped off. If this request is the lowest + // 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) { // Request does not have high enough priority to be issued return undefined; } - // A previously issued request has been bumped off the priority queue, so cancel it + // A previously issued request has been bumped off the priority heap, so cancel it cancelRequest(removedRequest); } @@ -446,8 +448,8 @@ function updateStatistics() { * @private */ RequestScheduler.clearForSpecs = function () { - while (requestQueue.length > 0) { - var request = requestQueue.removeMinimum(); + while (requestHeap.length > 0) { + var request = requestHeap.pop(); cancelRequest(request); } var length = activeRequests.length; @@ -481,5 +483,5 @@ RequestScheduler.numberOfActiveRequestsByServer = function (serverKey) { * * @private */ -RequestScheduler.requestQueue = requestQueue; +RequestScheduler.requestHeap = requestHeap; export default RequestScheduler; diff --git a/Specs/Core/RequestSchedulerSpec.js b/Specs/Core/RequestSchedulerSpec.js index d91eb3dc5a..ab3cb72722 100644 --- a/Specs/Core/RequestSchedulerSpec.js +++ b/Specs/Core/RequestSchedulerSpec.js @@ -6,14 +6,14 @@ import { when } from "../../Source/Cesium.js"; describe("Core/RequestScheduler", function () { var originalMaximumRequests; var originalMaximumRequestsPerServer; - var originalPriorityQueueLength; + var originalPriorityHeapLength; var originalRequestsByServer; beforeAll(function () { originalMaximumRequests = RequestScheduler.maximumRequests; originalMaximumRequestsPerServer = RequestScheduler.maximumRequestsPerServer; - originalPriorityQueueLength = RequestScheduler.priorityQueueLength; + originalPriorityHeapLength = RequestScheduler.priorityHeapLength; originalRequestsByServer = RequestScheduler.requestsByServer; }); @@ -25,7 +25,7 @@ describe("Core/RequestScheduler", function () { afterEach(function () { RequestScheduler.maximumRequests = originalMaximumRequests; RequestScheduler.maximumRequestsPerServer = originalMaximumRequestsPerServer; - RequestScheduler.priorityQueueLength = originalPriorityQueueLength; + RequestScheduler.priorityHeapLength = originalPriorityHeapLength; RequestScheduler.requestsByServer = originalRequestsByServer; }); @@ -207,7 +207,7 @@ describe("Core/RequestScheduler", function () { } }); - it("honors priorityQueueLength", function () { + it("honors priorityHeapLength", function () { var deferreds = []; var requests = []; @@ -228,24 +228,23 @@ describe("Core/RequestScheduler", function () { return request; } - RequestScheduler.priorityQueueLength = 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.priorityQueueLength = 3; + RequestScheduler.priorityHeapLength = 3; promise = RequestScheduler.request(createRequest(2.0)); - var lastRequest = createRequest(3.0); - promise = RequestScheduler.request(lastRequest); + 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 queue length - RequestScheduler.priorityQueueLength = 2; - expect(lastRequest.state).toBe(RequestState.CANCELLED); + // A request is cancelled to accommodate the new heap length + RequestScheduler.priorityHeapLength = 2; + expect(firstRequest.state).toBe(RequestState.CANCELLED); var length = deferreds.length; for (var i = 0; i < length; ++i) { @@ -464,7 +463,7 @@ describe("Core/RequestScheduler", function () { }); } - var length = RequestScheduler.priorityQueueLength; + var length = RequestScheduler.priorityHeapLength; for (var i = 0; i < length; ++i) { var priority = Math.random(); RequestScheduler.request(createRequest(priority)); @@ -501,7 +500,7 @@ describe("Core/RequestScheduler", function () { var i; var request; - var length = RequestScheduler.priorityQueueLength; + var length = RequestScheduler.priorityHeapLength; for (i = 0; i < length; ++i) { var priority = i / (length - 1); request = createRequest(priority); @@ -513,25 +512,25 @@ describe("Core/RequestScheduler", function () { RequestScheduler.maximumRequests = 0; RequestScheduler.update(); - var requestQueue = RequestScheduler.requestQueue; + var requestHeap = RequestScheduler.requestHeap; var requests = []; var currentTestId = 0; - while (requestQueue.length > 0) { - request = requestQueue.removeMaximum(); + while (requestHeap.length > 0) { + request = requestHeap.pop(); requests.push(request); expect(request.testId).toBeGreaterThanOrEqualTo(currentTestId); currentTestId = request.testId; } for (i = 0; i < length; ++i) { - requestQueue.insert(requests[i]); + requestHeap.insert(requests[i]); } invertPriority = true; RequestScheduler.update(); - while (requestQueue.length > 0) { - request = requestQueue.removeMaximum(); + while (requestHeap.length > 0) { + request = requestHeap.pop(); expect(request.testId).toBeLessThanOrEqualTo(currentTestId); currentTestId = request.testId; } @@ -555,17 +554,17 @@ describe("Core/RequestScheduler", function () { var mediumPriority = 0.5; var lowPriority = 1.0; - var length = RequestScheduler.priorityQueueLength; + var length = RequestScheduler.priorityHeapLength; for (var i = 0; i < length; ++i) { RequestScheduler.request(createRequest(mediumPriority)); } - // Queue is full so low priority request is not even issued + // Heap is full so low priority request is not even issued var promise = RequestScheduler.request(createRequest(lowPriority)); expect(promise).toBeUndefined(); expect(RequestScheduler.statistics.numberOfCancelledRequests).toBe(0); - // Queue is full so high priority request bumps off lower priority request + // Heap is full so high priority request bumps off lower priority request promise = RequestScheduler.request(createRequest(highPriority)); expect(promise).toBeDefined(); expect(RequestScheduler.statistics.numberOfCancelledRequests).toBe(1); From 065565f1d128e8a6c4ae3b72897dc99f04bf4486 Mon Sep 17 00:00:00 2001 From: Ian Lilley Date: Mon, 8 Mar 2021 14:12:28 -0500 Subject: [PATCH 24/76] pr feedback --- Source/Core/DoubleEndedPriorityQueue.js | 429 +++++++++++++----------- 1 file changed, 224 insertions(+), 205 deletions(-) diff --git a/Source/Core/DoubleEndedPriorityQueue.js b/Source/Core/DoubleEndedPriorityQueue.js index 220e0a6cf7..b8a803e1c1 100644 --- a/Source/Core/DoubleEndedPriorityQueue.js +++ b/Source/Core/DoubleEndedPriorityQueue.js @@ -113,21 +113,237 @@ Object.defineProperties(DoubleEndedPriorityQueue.prototype, { }, }); -function swap(that, index0, index1) { +/** + * Clones the double ended priority queue. + * + * @returns {DoubleEndedPriorityQueue} The cloned double ended priority queue. + */ +DoubleEndedPriorityQueue.prototype.clone = function () { + var maximumLength = this._maximumLength; + var comparator = this._comparator; + var array = this._array; + var length = this._length; + + var result = new DoubleEndedPriorityQueue({ + comparator: comparator, + maximumLength: maximumLength, + }); + + result._length = length; + for (var i = 0; i < length; i++) { + result._array[i] = array[i]; + } + + return result; +}; + +/** + * Removes all elements from the queue. + */ +DoubleEndedPriorityQueue.prototype.reset = function () { + this._length = 0; + + // Dereference elements + var maximumLength = this._maximumLength; + if (defined(maximumLength)) { + // Dereference all elements but keep the array the same size + for (var i = 0; i < maximumLength; i++) { + this._array[i] = undefined; + } + } else { + // Dereference all elements by clearing the array + this._array.length = 0; + } +}; + +/** + * Resort the queue. + */ +DoubleEndedPriorityQueue.prototype.resort = function () { + var length = this._length; + + // Fix the queue from the top-down + for (var i = 0; i < length; i++) { + pushUp(this, i); + } +}; + +/** + * Inserts an element into the queue. + * If the queue is at full capacity, the minimum element is removed. + * The new element is returned (and not added) if it is less than or equal priority to the minimum element. + * + * @param {*} element + * @returns {*|undefined} The minimum element if the queue is at full capacity. Returns undefined if there is no maximum length. + */ +DoubleEndedPriorityQueue.prototype.insert = function (element) { + var removedElement; + + var maximumLength = this._maximumLength; + if (defined(maximumLength)) { + if (maximumLength === 0) { + return undefined; + } else if (this._length === maximumLength) { + // It's faster to access the minimum directly instead of calling the getter + // because it avoids the length === 0 check. + var minimumElement = this._array[0]; + if (this._comparator(element, minimumElement) <= 0.0) { + // The element that is being inserted is less than or equal to + // the minimum element, so don't insert anything and exit early. + return element; + } + removedElement = this.removeMinimum(); + } + } + + var index = this._length; + this._array[index] = element; + this._length++; + pushUp(this, index); + + return removedElement; +}; + +/** + * Removes the minimum element from the queue and returns it. + * If the queue is empty, the return value is undefined. + * + * @returns {*|undefined} The minimum element, or undefined if the queue is empty. + */ +DoubleEndedPriorityQueue.prototype.removeMinimum = function () { + var length = this._length; + if (length === 0) { + return undefined; + } + + this._length--; + + // The minimum element is always the root + var minimumElement = this._array[0]; + + if (length >= 2) { + this._array[0] = this._array[length - 1]; + pushDown(this, 0); + } + + // Dereference removed element + this._array[length - 1] = undefined; + + return minimumElement; +}; + +/** + * Removes the maximum element from the queue and returns it. + * If the queue is empty, the return value is undefined. + * + * @returns {*|undefined} The maximum element, or undefined if the queue is empty. + */ +DoubleEndedPriorityQueue.prototype.removeMaximum = function () { + var length = this._length; + if (length === 0) { + return undefined; + } + + this._length--; + var maximumElement; + + // If the root has no children, the maximum is the root. + // If the root has one child, the maximum is the child. + if (length <= 2) { + maximumElement = this._array[length - 1]; + } else { + // Otherwise, the maximum is the larger of the root's two children. + var maximumElementIndex = greaterThan(this, 1, 2) ? 1 : 2; + maximumElement = this._array[maximumElementIndex]; + + // Re-balance the heap + this._array[maximumElementIndex] = this._array[length - 1]; + if (length >= 4) { + pushDown(this, maximumElementIndex); + } + } + + // Dereference removed element + this._array[length - 1] = undefined; + + return maximumElement; +}; + +/** + * Gets the minimum element in the queue. + * If the queue is empty, the result is undefined. + * + * @returns {*|undefined} element + */ + +DoubleEndedPriorityQueue.prototype.getMinimum = function () { + var length = this._length; + if (length === 0) { + return undefined; + } + + // The minimum element is always the root + return this._array[0]; +}; + +/** + * Gets the maximum element in the queue. + * If the queue is empty, the result is undefined. + * + * @returns {*|undefined} element + */ +DoubleEndedPriorityQueue.prototype.getMaximum = function () { + var length = this._length; + if (length === 0) { + return undefined; + } + + // If the root has no children, the maximum is the root. + // If the root has one child, the maximum is the child. + if (length <= 2) { + return this._array[length - 1]; + } + + // Otherwise, the maximum is the larger of the root's two children. + return this._array[greaterThan(this, 1, 2) ? 1 : 2]; +}; + +// Helper functions + +/** + * @param {DoubleEndedPriorityQueue} that + * @param {Number} indexA + * @param {Number} indexB + */ +function swap(that, indexA, indexB) { var array = that._array; - var temp = array[index0]; - array[index0] = array[index1]; - array[index1] = temp; + var temp = array[indexA]; + array[indexA] = array[indexB]; + array[indexB] = temp; } +/** + * @param {DoubleEndedPriorityQueue} that + * @param {Number} indexA + * @param {Number} indexB + */ function lessThan(that, indexA, indexB) { return that._comparator(that._array[indexA], that._array[indexB]) < 0.0; } +/** + * @param {DoubleEndedPriorityQueue} that + * @param {Number} indexA + * @param {Number} indexB + */ function greaterThan(that, indexA, indexB) { return that._comparator(that._array[indexA], that._array[indexB]) > 0.0; } +/** + * @param {DoubleEndedPriorityQueue} that + * @param {Number} index + */ function pushUp(that, index) { if (index === 0) { return; @@ -156,6 +372,10 @@ function pushUp(that, index) { } } +/** + * @param {DoubleEndedPriorityQueue} that + * @param {Number} index + */ function pushDown(that, index) { var length = that._length; var onMinLevel = Math.floor(CesiumMath.log2(index + 1)) % 2 === 0; @@ -195,207 +415,6 @@ function pushDown(that, index) { } } -/** - * Clones the double ended priority queue. - * - * @returns {DoubleEndedPriorityQueue} The cloned double ended priority queue. - */ -DoubleEndedPriorityQueue.prototype.clone = function () { - var that = this; - var maximumLength = that._maximumLength; - var comparator = that._comparator; - var array = that._array; - var length = that._length; - - var result = new DoubleEndedPriorityQueue({ - comparator: comparator, - maximumLength: maximumLength, - }); - - result._length = length; - for (var i = 0; i < length; i++) { - result._array[i] = array[i]; - } - - return result; -}; - -/** - * Removes all elements from the queue. - */ -DoubleEndedPriorityQueue.prototype.reset = function () { - this._length = 0; - - // Dereference elements - var maximumLength = this._maximumLength; - if (defined(maximumLength)) { - // Dereference all elements but keep the array the same size - for (var i = 0; i < maximumLength; i++) { - this._array[i] = undefined; - } - } else { - // Dereference all elements by clearing the array - this._array.length = 0; - } -}; - -/** - * Resort the queue. - */ -DoubleEndedPriorityQueue.prototype.resort = function () { - var that = this; - var length = that._length; - - // Fix the queue from the top-down - for (var i = 0; i < length; i++) { - pushUp(that, i); - } -}; - -/** - * Inserts an element into the queue. - * If the queue is at full capacity, the minimum element is removed. - * The new element is returned (and not added) if it is less than or equal priority to the minimum element. - * - * @param {*} element - * @returns {*|undefined} The minimum element if the queue is at full capacity. Returns undefined if there is no maximum length. - */ -DoubleEndedPriorityQueue.prototype.insert = function (element) { - var removedElement; - - var that = this; - var maximumLength = that._maximumLength; - if (defined(maximumLength)) { - if (maximumLength === 0) { - return undefined; - } else if (that._length === maximumLength) { - // It's faster to access the minimum directly instead of calling the getter - // because it avoids the length === 0 check. - var minimumElement = that._array[0]; - if (that._comparator(element, minimumElement) <= 0.0) { - // The element that is being inserted is less than or equal to - // the minimum element, so don't insert anything and exit early. - return element; - } - removedElement = that.removeMinimum(); - } - } - - var index = that._length; - that._array[index] = element; - that._length++; - pushUp(that, index); - - return removedElement; -}; - -/** - * Removes the minimum element from the queue and returns it. - * If the queue is empty, the return value is undefined. - * - * @returns {*|undefined} The minimum element, or undefined if the queue is empty. - */ -DoubleEndedPriorityQueue.prototype.removeMinimum = function () { - var that = this; - var length = that._length; - if (length === 0) { - return undefined; - } - - that._length--; - - // The minimum element is always the root - var minimumElement = that._array[0]; - - if (length >= 2) { - that._array[0] = that._array[length - 1]; - pushDown(that, 0); - } - - // Dereference removed element - that._array[length - 1] = undefined; - - return minimumElement; -}; - -/** - * Removes the maximum element from the queue and returns it. - * If the queue is empty, the return value is undefined. - * - * @returns {*|undefined} The maximum element, or undefined if the queue is empty. - */ -DoubleEndedPriorityQueue.prototype.removeMaximum = function () { - var that = this; - var length = that._length; - if (length === 0) { - return undefined; - } - - that._length--; - var maximumElement; - - // If the root has no children, the maximum is the root. - // If the root has one child, the maximum is the child. - if (length <= 2) { - maximumElement = that._array[length - 1]; - } else { - // Otherwise, the maximum is the larger of the root's two children. - var maximumElementIndex = greaterThan(that, 1, 2) ? 1 : 2; - maximumElement = that._array[maximumElementIndex]; - - // Re-balance the heap - that._array[maximumElementIndex] = that._array[length - 1]; - if (length >= 4) { - pushDown(that, maximumElementIndex); - } - } - - // Dereference removed element - that._array[length - 1] = undefined; - - return maximumElement; -}; - -/** - * Gets the minimum element in the queue. - * If the queue is empty, the result is undefined. - * - * @param {*|undefined} element - */ - -DoubleEndedPriorityQueue.prototype.getMinimum = function () { - var length = this._length; - if (length === 0) { - return undefined; - } - - // The minimum element is always the root - return this._array[0]; -}; - -/** - * Gets the maximum element in the queue. - * If the queue is empty, the result is undefined. - * - * @param {*|undefined} element - */ -DoubleEndedPriorityQueue.prototype.getMaximum = function () { - var length = this._length; - if (length === 0) { - return undefined; - } - - // If the root has no children, the maximum is the root. - // If the root has one child, the maximum is the child. - if (length <= 2) { - return this._array[length - 1]; - } - - // Otherwise, the maximum is the larger of the root's two children. - var that = this; - return this._array[greaterThan(that, 1, 2) ? 1 : 2]; -}; - /** * The comparator to use for the queue. * @callback DoubleEndedPriorityQueue.ComparatorCallback From 33052692c3e28d3b1ce14f042f8722287074cc7f Mon Sep 17 00:00:00 2001 From: Ian Lilley Date: Mon, 8 Mar 2021 14:14:24 -0500 Subject: [PATCH 25/76] another private doc fix --- Source/Core/DoubleEndedPriorityQueue.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Source/Core/DoubleEndedPriorityQueue.js b/Source/Core/DoubleEndedPriorityQueue.js index b8a803e1c1..4d6579a564 100644 --- a/Source/Core/DoubleEndedPriorityQueue.js +++ b/Source/Core/DoubleEndedPriorityQueue.js @@ -326,6 +326,7 @@ function swap(that, indexA, indexB) { * @param {DoubleEndedPriorityQueue} that * @param {Number} indexA * @param {Number} indexB + * @returns {Boolean} */ function lessThan(that, indexA, indexB) { return that._comparator(that._array[indexA], that._array[indexB]) < 0.0; @@ -335,6 +336,7 @@ function lessThan(that, indexA, indexB) { * @param {DoubleEndedPriorityQueue} that * @param {Number} indexA * @param {Number} indexB + * @returns {Boolean} */ function greaterThan(that, indexA, indexB) { return that._comparator(that._array[indexA], that._array[indexB]) > 0.0; From 7b70087eaf4e5e94af5fc860ced6436cb5fc0fdf Mon Sep 17 00:00:00 2001 From: Ian Lilley Date: Mon, 8 Mar 2021 16:04:21 -0500 Subject: [PATCH 26/76] fixed ts-build failure --- Source/Core/DoubleEndedPriorityQueue.js | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/Source/Core/DoubleEndedPriorityQueue.js b/Source/Core/DoubleEndedPriorityQueue.js index 4d6579a564..fa22f3025a 100644 --- a/Source/Core/DoubleEndedPriorityQueue.js +++ b/Source/Core/DoubleEndedPriorityQueue.js @@ -310,11 +310,6 @@ DoubleEndedPriorityQueue.prototype.getMaximum = function () { // Helper functions -/** - * @param {DoubleEndedPriorityQueue} that - * @param {Number} indexA - * @param {Number} indexB - */ function swap(that, indexA, indexB) { var array = that._array; var temp = array[indexA]; @@ -322,30 +317,14 @@ function swap(that, indexA, indexB) { array[indexB] = temp; } -/** - * @param {DoubleEndedPriorityQueue} that - * @param {Number} indexA - * @param {Number} indexB - * @returns {Boolean} - */ function lessThan(that, indexA, indexB) { return that._comparator(that._array[indexA], that._array[indexB]) < 0.0; } -/** - * @param {DoubleEndedPriorityQueue} that - * @param {Number} indexA - * @param {Number} indexB - * @returns {Boolean} - */ function greaterThan(that, indexA, indexB) { return that._comparator(that._array[indexA], that._array[indexB]) > 0.0; } -/** - * @param {DoubleEndedPriorityQueue} that - * @param {Number} index - */ function pushUp(that, index) { if (index === 0) { return; @@ -374,10 +353,6 @@ function pushUp(that, index) { } } -/** - * @param {DoubleEndedPriorityQueue} that - * @param {Number} index - */ function pushDown(that, index) { var length = that._length; var onMinLevel = Math.floor(CesiumMath.log2(index + 1)) % 2 === 0; From 841eb6703552243c185a71e6026bdcd977ce15f9 Mon Sep 17 00:00:00 2001 From: ebogo1 Date: Mon, 8 Mar 2021 17:36:36 -0500 Subject: [PATCH 27/76] remove classificationModels changes --- Source/Scene/Batched3DModel3DTileContent.js | 6 +----- Source/Scene/Cesium3DTileset.js | 20 -------------------- 2 files changed, 1 insertion(+), 25 deletions(-) diff --git a/Source/Scene/Batched3DModel3DTileContent.js b/Source/Scene/Batched3DModel3DTileContent.js index 797c2e7c38..6d6538b7d9 100644 --- a/Source/Scene/Batched3DModel3DTileContent.js +++ b/Source/Scene/Batched3DModel3DTileContent.js @@ -47,10 +47,6 @@ function Batched3DModel3DTileContent( this._batchTable = undefined; this._features = undefined; - this._classificationType = tileset.noClassificationModels - ? undefined - : tileset.classificationType; - // Populate from gltf when available this._batchIdAttributeName = undefined; this._diffuseAttributeOrUniformName = {}; @@ -165,7 +161,7 @@ function getBatchIdAttributeName(gltf) { function getVertexShaderCallback(content) { return function (vs, programId) { var batchTable = content._batchTable; - var handleTranslucent = !defined(content._classificationType); + var handleTranslucent = !defined(content._tileset.classificationType); var gltf = content._model.gltf; if (defined(gltf)) { diff --git a/Source/Scene/Cesium3DTileset.js b/Source/Scene/Cesium3DTileset.js index 5c3087ffaf..243c50b30f 100644 --- a/Source/Scene/Cesium3DTileset.js +++ b/Source/Scene/Cesium3DTileset.js @@ -102,7 +102,6 @@ import TileOrientedBoundingBox from "./TileOrientedBoundingBox.js"; * @param {Boolean} [options.debugShowRenderingStatistics=false] For debugging only. When true, draws labels to indicate the number of commands, points, triangles and features for each tile. * @param {Boolean} [options.debugShowMemoryUsage=false] For debugging only. When true, draws labels to indicate the texture and geometry memory in megabytes used by each tile. * @param {Boolean} [options.debugShowUrl=false] For debugging only. When true, draws labels to indicate the url of each tile. - * @param {Boolean} [options.noClassificationModels=false] Do not use b3dms for classification. * * @exception {DeveloperError} The tileset must be 3D Tiles version 0.0 or 1.0. * @@ -275,11 +274,6 @@ function Cesium3DTileset(options) { this._clippingPlanesOriginMatrix = undefined; // Combines the above with any run-time transforms. this._clippingPlanesOriginMatrixDirty = true; - this._noClassificationModels = defaultValue( - options.noClassificationModels, - false - ); - /** * Preload tiles when tileset.show is false. Loads tiles as if the tileset is visible but does not render them. * @@ -1668,20 +1662,6 @@ Object.defineProperties(Cesium3DTileset.prototype, { Cartesian2.clone(value, this._imageBasedLightingFactor); }, }, - - /** - * Indicates that the tileset's b3dms should be used for classification. - * - * @memberof Cesium3DTileset.prototype - * - * @type {Boolean} - * @default false - */ - noClassificationModels: { - get: function () { - return this._noClassificationModels; - }, - }, }); /** From 2bc9f4bf607631cdc40cae1dfc1012bcb79bff48 Mon Sep 17 00:00:00 2001 From: ebogo1 Date: Mon, 8 Mar 2021 17:37:07 -0500 Subject: [PATCH 28/76] remove classificationModels changes --- Source/Scene/Batched3DModel3DTileContent.js | 4 +++ Source/Scene/Cesium3DTileset.js | 34 +++++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/Source/Scene/Batched3DModel3DTileContent.js b/Source/Scene/Batched3DModel3DTileContent.js index 6d6538b7d9..144fb67717 100644 --- a/Source/Scene/Batched3DModel3DTileContent.js +++ b/Source/Scene/Batched3DModel3DTileContent.js @@ -47,6 +47,10 @@ function Batched3DModel3DTileContent( this._batchTable = undefined; this._features = undefined; + this._classificationType = tileset.noClassificationModels + ? undefined + : tileset.classificationType; + // Populate from gltf when available this._batchIdAttributeName = undefined; this._diffuseAttributeOrUniformName = {}; diff --git a/Source/Scene/Cesium3DTileset.js b/Source/Scene/Cesium3DTileset.js index 243c50b30f..144ad0ea18 100644 --- a/Source/Scene/Cesium3DTileset.js +++ b/Source/Scene/Cesium3DTileset.js @@ -102,6 +102,7 @@ import TileOrientedBoundingBox from "./TileOrientedBoundingBox.js"; * @param {Boolean} [options.debugShowRenderingStatistics=false] For debugging only. When true, draws labels to indicate the number of commands, points, triangles and features for each tile. * @param {Boolean} [options.debugShowMemoryUsage=false] For debugging only. When true, draws labels to indicate the texture and geometry memory in megabytes used by each tile. * @param {Boolean} [options.debugShowUrl=false] For debugging only. When true, draws labels to indicate the url of each tile. + * @param {Boolean} [options.noClassificationModels=false] Do not use b3dms for classification. * * @exception {DeveloperError} The tileset must be 3D Tiles version 0.0 or 1.0. * @@ -274,6 +275,11 @@ function Cesium3DTileset(options) { this._clippingPlanesOriginMatrix = undefined; // Combines the above with any run-time transforms. this._clippingPlanesOriginMatrixDirty = true; + this._noClassificationModels = defaultValue( + options.noClassificationModels, + false + ); + /** * Preload tiles when tileset.show is false. Loads tiles as if the tileset is visible but does not render them. * @@ -1662,6 +1668,34 @@ Object.defineProperties(Cesium3DTileset.prototype, { Cartesian2.clone(value, this._imageBasedLightingFactor); }, }, + + /** + * Minimum and maximum heights that vector tiles clamped to surfaces will clamp to. + * + * @memberof Cesium3DTileset.prototype + * + * @type {Cartesian2} + * @default undefined + */ + minimumMaximumVectorHeights: { + get: function () { + return this._minimumMaximumVectorHeights; + }, + }, + + /** + * Indicates that the tileset's b3dms should not be used for classification. + * + * @memberof Cesium3Dtileset.prototype + * + * @type {Boolean} + * @default false + */ + noClassificationModels: { + get: function () { + return this._noClassificationModels; + }, + }, }); /** From 42a0a700ce51f7ffdb293f971f59200a7d0fdc7e Mon Sep 17 00:00:00 2001 From: ebogo1 Date: Mon, 8 Mar 2021 17:52:14 -0500 Subject: [PATCH 29/76] revert remaining changes in Batch3DModel3DTileContent --- Source/Scene/Batched3DModel3DTileContent.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Source/Scene/Batched3DModel3DTileContent.js b/Source/Scene/Batched3DModel3DTileContent.js index 144fb67717..7d452c3e33 100644 --- a/Source/Scene/Batched3DModel3DTileContent.js +++ b/Source/Scene/Batched3DModel3DTileContent.js @@ -187,7 +187,7 @@ function getVertexShaderCallback(content) { function getFragmentShaderCallback(content) { return function (fs, programId) { var batchTable = content._batchTable; - var handleTranslucent = !defined(content._classificationType); + var handleTranslucent = !defined(content._tileset.classificationType); var gltf = content._model.gltf; if (defined(gltf)) { @@ -352,7 +352,7 @@ function initialize(content, arrayBuffer, byteOffset) { } var colorChangedCallback; - if (defined(content._classificationType)) { + if (defined(content._tileset.classificationType)) { colorChangedCallback = createColorChangedCallback(content); } @@ -407,7 +407,7 @@ function initialize(content, arrayBuffer, byteOffset) { new Matrix4() ); - if (!defined(content._classificationType)) { + if (!defined(content._tileset.classificationType)) { // PERFORMANCE_IDEA: patch the shader on demand, e.g., the first time show/color changes. // The pick shader still needs to be patched. content._model = new Model({ @@ -575,7 +575,7 @@ Batched3DModel3DTileContent.prototype.update = function (tileset, frameState) { if ( commandStart < commandEnd && (frameState.passes.render || frameState.passes.pick) && - !defined(this._classificationType) + !defined(tileset.classificationType) ) { this._batchTable.addDerivedCommands(frameState, commandStart); } From 0398f152f81086e9b3bb3c794e3e7a7e422ec69a Mon Sep 17 00:00:00 2001 From: ebogo1 Date: Mon, 8 Mar 2021 18:35:34 -0500 Subject: [PATCH 30/76] revert bad commit --- Source/Scene/Batched3DModel3DTileContent.js | 4 --- Source/Scene/Cesium3DTileset.js | 34 --------------------- 2 files changed, 38 deletions(-) diff --git a/Source/Scene/Batched3DModel3DTileContent.js b/Source/Scene/Batched3DModel3DTileContent.js index 7d452c3e33..557bb51aae 100644 --- a/Source/Scene/Batched3DModel3DTileContent.js +++ b/Source/Scene/Batched3DModel3DTileContent.js @@ -47,10 +47,6 @@ function Batched3DModel3DTileContent( this._batchTable = undefined; this._features = undefined; - this._classificationType = tileset.noClassificationModels - ? undefined - : tileset.classificationType; - // Populate from gltf when available this._batchIdAttributeName = undefined; this._diffuseAttributeOrUniformName = {}; diff --git a/Source/Scene/Cesium3DTileset.js b/Source/Scene/Cesium3DTileset.js index 144ad0ea18..243c50b30f 100644 --- a/Source/Scene/Cesium3DTileset.js +++ b/Source/Scene/Cesium3DTileset.js @@ -102,7 +102,6 @@ import TileOrientedBoundingBox from "./TileOrientedBoundingBox.js"; * @param {Boolean} [options.debugShowRenderingStatistics=false] For debugging only. When true, draws labels to indicate the number of commands, points, triangles and features for each tile. * @param {Boolean} [options.debugShowMemoryUsage=false] For debugging only. When true, draws labels to indicate the texture and geometry memory in megabytes used by each tile. * @param {Boolean} [options.debugShowUrl=false] For debugging only. When true, draws labels to indicate the url of each tile. - * @param {Boolean} [options.noClassificationModels=false] Do not use b3dms for classification. * * @exception {DeveloperError} The tileset must be 3D Tiles version 0.0 or 1.0. * @@ -275,11 +274,6 @@ function Cesium3DTileset(options) { this._clippingPlanesOriginMatrix = undefined; // Combines the above with any run-time transforms. this._clippingPlanesOriginMatrixDirty = true; - this._noClassificationModels = defaultValue( - options.noClassificationModels, - false - ); - /** * Preload tiles when tileset.show is false. Loads tiles as if the tileset is visible but does not render them. * @@ -1668,34 +1662,6 @@ Object.defineProperties(Cesium3DTileset.prototype, { Cartesian2.clone(value, this._imageBasedLightingFactor); }, }, - - /** - * Minimum and maximum heights that vector tiles clamped to surfaces will clamp to. - * - * @memberof Cesium3DTileset.prototype - * - * @type {Cartesian2} - * @default undefined - */ - minimumMaximumVectorHeights: { - get: function () { - return this._minimumMaximumVectorHeights; - }, - }, - - /** - * Indicates that the tileset's b3dms should not be used for classification. - * - * @memberof Cesium3Dtileset.prototype - * - * @type {Boolean} - * @default false - */ - noClassificationModels: { - get: function () { - return this._noClassificationModels; - }, - }, }); /** From a22f03dbde610d840feaf282d5ee71bf96444691 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Mon, 8 Mar 2021 18:40:29 -0500 Subject: [PATCH 31/76] Remove some redundant getters --- Source/Scene/Batched3DModel3DTileContent.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Scene/Batched3DModel3DTileContent.js b/Source/Scene/Batched3DModel3DTileContent.js index 557bb51aae..488c019071 100644 --- a/Source/Scene/Batched3DModel3DTileContent.js +++ b/Source/Scene/Batched3DModel3DTileContent.js @@ -348,7 +348,7 @@ function initialize(content, arrayBuffer, byteOffset) { } var colorChangedCallback; - if (defined(content._tileset.classificationType)) { + if (defined(tileset.classificationType)) { colorChangedCallback = createColorChangedCallback(content); } @@ -403,7 +403,7 @@ function initialize(content, arrayBuffer, byteOffset) { new Matrix4() ); - if (!defined(content._tileset.classificationType)) { + if (!defined(tileset.classificationType)) { // PERFORMANCE_IDEA: patch the shader on demand, e.g., the first time show/color changes. // The pick shader still needs to be patched. content._model = new Model({ From 902f6689d3d7f3542e29792368a5dafbdd91341d Mon Sep 17 00:00:00 2001 From: ebogo1 Date: Mon, 8 Mar 2021 18:46:06 -0500 Subject: [PATCH 32/76] add classificationType changes --- Source/Scene/Batched3DModel3DTileContent.js | 14 +++++++++----- Source/Scene/Cesium3DTileset.js | 6 ++++++ 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/Source/Scene/Batched3DModel3DTileContent.js b/Source/Scene/Batched3DModel3DTileContent.js index 488c019071..797c2e7c38 100644 --- a/Source/Scene/Batched3DModel3DTileContent.js +++ b/Source/Scene/Batched3DModel3DTileContent.js @@ -47,6 +47,10 @@ function Batched3DModel3DTileContent( this._batchTable = undefined; this._features = undefined; + this._classificationType = tileset.noClassificationModels + ? undefined + : tileset.classificationType; + // Populate from gltf when available this._batchIdAttributeName = undefined; this._diffuseAttributeOrUniformName = {}; @@ -161,7 +165,7 @@ function getBatchIdAttributeName(gltf) { function getVertexShaderCallback(content) { return function (vs, programId) { var batchTable = content._batchTable; - var handleTranslucent = !defined(content._tileset.classificationType); + var handleTranslucent = !defined(content._classificationType); var gltf = content._model.gltf; if (defined(gltf)) { @@ -183,7 +187,7 @@ function getVertexShaderCallback(content) { function getFragmentShaderCallback(content) { return function (fs, programId) { var batchTable = content._batchTable; - var handleTranslucent = !defined(content._tileset.classificationType); + var handleTranslucent = !defined(content._classificationType); var gltf = content._model.gltf; if (defined(gltf)) { @@ -348,7 +352,7 @@ function initialize(content, arrayBuffer, byteOffset) { } var colorChangedCallback; - if (defined(tileset.classificationType)) { + if (defined(content._classificationType)) { colorChangedCallback = createColorChangedCallback(content); } @@ -403,7 +407,7 @@ function initialize(content, arrayBuffer, byteOffset) { new Matrix4() ); - if (!defined(tileset.classificationType)) { + if (!defined(content._classificationType)) { // PERFORMANCE_IDEA: patch the shader on demand, e.g., the first time show/color changes. // The pick shader still needs to be patched. content._model = new Model({ @@ -571,7 +575,7 @@ Batched3DModel3DTileContent.prototype.update = function (tileset, frameState) { if ( commandStart < commandEnd && (frameState.passes.render || frameState.passes.pick) && - !defined(tileset.classificationType) + !defined(this._classificationType) ) { this._batchTable.addDerivedCommands(frameState, commandStart); } diff --git a/Source/Scene/Cesium3DTileset.js b/Source/Scene/Cesium3DTileset.js index 384b2c3e00..1b5c30e177 100644 --- a/Source/Scene/Cesium3DTileset.js +++ b/Source/Scene/Cesium3DTileset.js @@ -101,6 +101,7 @@ import TileOrientedBoundingBox from "./TileOrientedBoundingBox.js"; * @param {Boolean} [options.debugShowRenderingStatistics=false] For debugging only. When true, draws labels to indicate the number of commands, points, triangles and features for each tile. * @param {Boolean} [options.debugShowMemoryUsage=false] For debugging only. When true, draws labels to indicate the texture and geometry memory in megabytes used by each tile. * @param {Boolean} [options.debugShowUrl=false] For debugging only. When true, draws labels to indicate the url of each tile. + * @param {Boolean} [options.noClassificationModels=false] Do not use b3dms for classification. * * @exception {DeveloperError} The tileset must be 3D Tiles version 0.0 or 1.0. * @@ -273,6 +274,11 @@ function Cesium3DTileset(options) { this._clippingPlanesOriginMatrix = undefined; // Combines the above with any run-time transforms. this._clippingPlanesOriginMatrixDirty = true; + this._noClassificationModels = defaultValue( + options.noClassificationModels, + false + ); + /** * Preload tiles when tileset.show is false. Loads tiles as if the tileset is visible but does not render them. * From 397ece9edba301ca49a728f9015329b6c0cd1699 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Mon, 8 Mar 2021 19:19:37 -0500 Subject: [PATCH 33/76] Fix tests after premultiplied alpha change --- .../TranslucentTileClassificationSpec.js | 29 ++++++++++--------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/Specs/Scene/TranslucentTileClassificationSpec.js b/Specs/Scene/TranslucentTileClassificationSpec.js index 1d593dac5f..73a696d0ee 100644 --- a/Specs/Scene/TranslucentTileClassificationSpec.js +++ b/Specs/Scene/TranslucentTileClassificationSpec.js @@ -388,10 +388,18 @@ describe( ); var secondFrustumAccumulation = accumulationFBO; - expect(readPixels(secondFrustumAccumulation)).not.toEqual([0, 0, 0, 0]); - expect(readPixels(secondFrustumAccumulation)).toEqual( - readPixels(drawClassificationFBO) - ); + var accumulationPixels = readPixels(secondFrustumAccumulation); + var classificationPixels = readPixels(drawClassificationFBO); + var expectedPixels = [ + // Multiply by two to account for premultiplied alpha + classificationPixels[0] * 2, + classificationPixels[1] * 2, + classificationPixels[2] * 2, + classificationPixels[3], + ]; + + expect(accumulationPixels).not.toEqual([0, 0, 0, 0]); + expect(accumulationPixels).toEqualEpsilon(expectedPixels, 1); translucentTileClassification.destroy(); }); @@ -498,16 +506,9 @@ describe( var postCompositePixels = readPixels(targetColorFBO); expect(postCompositePixels).not.toEqual(preCompositePixels); - var normalizedAlpha = pixelsToComposite[3] / 255; - var red = - Math.round(normalizedAlpha * pixelsToComposite[0]) + - preCompositePixels[0]; - var green = - Math.round(normalizedAlpha * pixelsToComposite[1]) + - preCompositePixels[1]; - var blue = - Math.round(normalizedAlpha * pixelsToComposite[2]) + - preCompositePixels[2]; + var red = Math.round(pixelsToComposite[0]) + preCompositePixels[0]; + var green = Math.round(pixelsToComposite[1]) + preCompositePixels[1]; + var blue = Math.round(pixelsToComposite[2]) + preCompositePixels[2]; var alpha = pixelsToComposite[3] + preCompositePixels[3]; expect(postCompositePixels[0]).toEqual(red); From 4668ca6988c5315697af3b06c0d292c7bdddc093 Mon Sep 17 00:00:00 2001 From: ebogo1 Date: Mon, 8 Mar 2021 20:37:10 -0500 Subject: [PATCH 34/76] add back Cesium3DTileset properties --- Source/Scene/Cesium3DTileset.js | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/Source/Scene/Cesium3DTileset.js b/Source/Scene/Cesium3DTileset.js index 1b5c30e177..768c46f823 100644 --- a/Source/Scene/Cesium3DTileset.js +++ b/Source/Scene/Cesium3DTileset.js @@ -101,7 +101,7 @@ import TileOrientedBoundingBox from "./TileOrientedBoundingBox.js"; * @param {Boolean} [options.debugShowRenderingStatistics=false] For debugging only. When true, draws labels to indicate the number of commands, points, triangles and features for each tile. * @param {Boolean} [options.debugShowMemoryUsage=false] For debugging only. When true, draws labels to indicate the texture and geometry memory in megabytes used by each tile. * @param {Boolean} [options.debugShowUrl=false] For debugging only. When true, draws labels to indicate the url of each tile. - * @param {Boolean} [options.noClassificationModels=false] Do not use b3dms for classification. + * @param {Boolean} [options.noClassificationModels=false] Indicates that the tileset's b3dms should not be used for classification. * * @exception {DeveloperError} The tileset must be 3D Tiles version 0.0 or 1.0. * @@ -1668,6 +1668,34 @@ Object.defineProperties(Cesium3DTileset.prototype, { Cartesian2.clone(value, this._imageBasedLightingFactor); }, }, + + /** + * Minimum and maximum heights that vector tiles clamped to surfaces will clamp to. + * + * @memberof Cesium3DTileset.prototype + * + * @type {Cartesian2} + * @default undefined + */ + minimumMaximumVectorHeights: { + get: function () { + return this._minimumMaximumVectorHeights; + }, + }, + + /** + * Indicates that the tileset's b3dms should not be used for classification. + * + * @memberof Cesium3DTileset.prototype + * + * @type {Boolean} + * @default false + */ + noClassificationModels: { + get: function () { + return this._noClassificationModels; + }, + }, }); /** From 1ac71d128f12fa5ac4636f2aaef7c326dbcf6dab Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Mon, 8 Mar 2021 21:08:08 -0500 Subject: [PATCH 35/76] Revert "Premultiplied alpha to fix colors for globe translucency" This reverts commit 0351120f0763b88677d20326cdcb31e2cc05e1fc. --- Source/Scene/Vector3DTileClampedPolylines.js | 2 +- Source/Shaders/Vector3DTileClampedPolylinesFS.glsl | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/Source/Scene/Vector3DTileClampedPolylines.js b/Source/Scene/Vector3DTileClampedPolylines.js index a8074171d8..cad5b034e9 100644 --- a/Source/Scene/Vector3DTileClampedPolylines.js +++ b/Source/Scene/Vector3DTileClampedPolylines.js @@ -478,7 +478,7 @@ function getRenderState(mask3DTiles) { cull: { enabled: true, // prevent double-draw. Geometry is "inverted" (reversed winding order) so we're drawing backfaces. }, - blending: BlendingState.PRE_MULTIPLIED_ALPHA_BLEND, + blending: BlendingState.ALPHA_BLEND, depthMask: false, stencilTest: { enabled: mask3DTiles, diff --git a/Source/Shaders/Vector3DTileClampedPolylinesFS.glsl b/Source/Shaders/Vector3DTileClampedPolylinesFS.glsl index 6126cd973e..0fa201cbd8 100644 --- a/Source/Shaders/Vector3DTileClampedPolylinesFS.glsl +++ b/Source/Shaders/Vector3DTileClampedPolylinesFS.glsl @@ -48,8 +48,5 @@ void main() } gl_FragColor = u_highlightColor; - // Premultiply alpha. Required for classification primitives on translucent globe. - gl_FragColor.rgb *= gl_FragColor.a; - czm_writeDepthClamp(); } From 7ada1a79c6529d17236b4211cacc84dab76ab583 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Mon, 8 Mar 2021 21:30:16 -0500 Subject: [PATCH 36/76] Premultiplied alpha to fix colors for globe translucency --- Source/Scene/Batched3DModel3DTileContent.js | 3 ++- Source/Scene/Cesium3DTileBatchTable.js | 22 ++++++++++++++++---- Source/Scene/ModelInstanceCollection.js | 6 ++++-- Source/Scene/PointCloud3DTileContent.js | 3 ++- Source/Scene/Vector3DTileClampedPolylines.js | 10 ++++----- Source/Scene/Vector3DTilePolylines.js | 8 +++---- Source/Scene/Vector3DTilePrimitive.js | 10 ++++----- 7 files changed, 40 insertions(+), 22 deletions(-) diff --git a/Source/Scene/Batched3DModel3DTileContent.js b/Source/Scene/Batched3DModel3DTileContent.js index 797c2e7c38..f9e21534d2 100644 --- a/Source/Scene/Batched3DModel3DTileContent.js +++ b/Source/Scene/Batched3DModel3DTileContent.js @@ -197,7 +197,8 @@ function getFragmentShaderCallback(content) { } var callback = batchTable.getFragmentShaderCallback( handleTranslucent, - content._diffuseAttributeOrUniformName[programId] + content._diffuseAttributeOrUniformName[programId], + false ); return defined(callback) ? callback(fs) : fs; }; diff --git a/Source/Scene/Cesium3DTileBatchTable.js b/Source/Scene/Cesium3DTileBatchTable.js index 90255e28fa..1e2591e8f3 100644 --- a/Source/Scene/Cesium3DTileBatchTable.js +++ b/Source/Scene/Cesium3DTileBatchTable.js @@ -1195,7 +1195,8 @@ function modifyDiffuse(source, diffuseAttributeOrUniformName, applyHighlight) { Cesium3DTileBatchTable.prototype.getFragmentShaderCallback = function ( handleTranslucent, - diffuseAttributeOrUniformName + diffuseAttributeOrUniformName, + hasPremultipliedAlpha ) { if (this.featuresLength === 0) { return; @@ -1210,8 +1211,13 @@ Cesium3DTileBatchTable.prototype.getFragmentShaderCallback = function ( "varying vec4 tile_featureColor; \n" + "void main() \n" + "{ \n" + - " tile_color(tile_featureColor); \n" + - "}"; + " tile_color(tile_featureColor); \n"; + + if (hasPremultipliedAlpha) { + source += " gl_FragColor.rgb *= gl_FragColor.a; \n"; + } + + source += "}"; } else { if (handleTranslucent) { source += "uniform bool tile_translucentCommand; \n"; @@ -1246,7 +1252,13 @@ Cesium3DTileBatchTable.prototype.getFragmentShaderCallback = function ( " } \n"; } - source += " tile_color(featureProperties); \n" + "} \n"; + source += " tile_color(featureProperties); \n"; + + if (hasPremultipliedAlpha) { + source += " gl_FragColor.rgb *= gl_FragColor.a; \n"; + } + + source += "} \n"; } return source; }; @@ -1268,6 +1280,7 @@ Cesium3DTileBatchTable.prototype.getClassificationFragmentShaderCallback = funct "{ \n" + " tile_main(); \n" + " gl_FragColor = tile_featureColor; \n" + + " gl_FragColor.rgb *= gl_FragColor.a; \n" + "}"; } else { source += @@ -1282,6 +1295,7 @@ Cesium3DTileBatchTable.prototype.getClassificationFragmentShaderCallback = funct " discard; \n" + " } \n" + " gl_FragColor = featureProperties; \n" + + " gl_FragColor.rgb *= gl_FragColor.a; \n" + "} \n"; } return source; diff --git a/Source/Scene/ModelInstanceCollection.js b/Source/Scene/ModelInstanceCollection.js index a949565239..0876cea956 100644 --- a/Source/Scene/ModelInstanceCollection.js +++ b/Source/Scene/ModelInstanceCollection.js @@ -455,7 +455,8 @@ function getFragmentShaderCallback(collection) { ); fs = batchTable.getFragmentShaderCallback( true, - diffuseAttributeOrUniformName + diffuseAttributeOrUniformName, + false )(fs); } else { fs = "varying vec4 v_pickColor;\n" + fs; @@ -536,7 +537,8 @@ function getFragmentShaderNonInstancedCallback(collection) { ); fs = batchTable.getFragmentShaderCallback( true, - diffuseAttributeOrUniformName + diffuseAttributeOrUniformName, + false )(fs); } else { fs = "uniform vec4 czm_pickColor;\n" + fs; diff --git a/Source/Scene/PointCloud3DTileContent.js b/Source/Scene/PointCloud3DTileContent.js index 50ec733be0..e45056fccc 100644 --- a/Source/Scene/PointCloud3DTileContent.js +++ b/Source/Scene/PointCloud3DTileContent.js @@ -153,7 +153,8 @@ function getFragmentShaderLoaded(content) { if (defined(content._batchTable)) { return content._batchTable.getFragmentShaderCallback( false, - undefined + undefined, + false )(fs); } return "uniform vec4 czm_pickColor;\n" + fs; diff --git a/Source/Scene/Vector3DTileClampedPolylines.js b/Source/Scene/Vector3DTileClampedPolylines.js index cad5b034e9..5e20fa3610 100644 --- a/Source/Scene/Vector3DTileClampedPolylines.js +++ b/Source/Scene/Vector3DTileClampedPolylines.js @@ -478,7 +478,7 @@ function getRenderState(mask3DTiles) { cull: { enabled: true, // prevent double-draw. Geometry is "inverted" (reversed winding order) so we're drawing backfaces. }, - blending: BlendingState.ALPHA_BLEND, + blending: BlendingState.PRE_MULTIPLIED_ALPHA_BLEND, depthMask: false, stencilTest: { enabled: mask3DTiles, @@ -521,11 +521,11 @@ function createShaders(primitive, context) { "a_batchId", undefined )(Vector3DTileClampedPolylinesVS); - var fsSource = batchTable.getFragmentShaderCallback()( - Vector3DTileClampedPolylinesFS, + var fsSource = batchTable.getFragmentShaderCallback( false, - undefined - ); + undefined, + true + )(Vector3DTileClampedPolylinesFS); var vs = new ShaderSource({ defines: [ diff --git a/Source/Scene/Vector3DTilePolylines.js b/Source/Scene/Vector3DTilePolylines.js index 3473c5ba2c..2771a2d0a5 100644 --- a/Source/Scene/Vector3DTilePolylines.js +++ b/Source/Scene/Vector3DTilePolylines.js @@ -432,11 +432,11 @@ function createShaders(primitive, context) { "a_batchId", undefined )(Vector3DTilePolylinesVS); - var fsSource = batchTable.getFragmentShaderCallback()( - PolylineFS, + var fsSource = batchTable.getFragmentShaderCallback( false, - undefined - ); + undefined, + false + )(PolylineFS); var vs = new ShaderSource({ defines: [ diff --git a/Source/Scene/Vector3DTilePrimitive.js b/Source/Scene/Vector3DTilePrimitive.js index fdc3edce63..1c51a4e44e 100644 --- a/Source/Scene/Vector3DTilePrimitive.js +++ b/Source/Scene/Vector3DTilePrimitive.js @@ -302,11 +302,11 @@ function createShaders(primitive, context) { "a_batchId", undefined )(VectorTileVS); - var fsSource = batchTable.getFragmentShaderCallback()( - ShadowVolumeFS, + var fsSource = batchTable.getFragmentShaderCallback( false, - undefined - ); + undefined, + true + )(ShadowVolumeFS); pickId = batchTable.getPickId(); @@ -427,7 +427,7 @@ var colorRenderState = { enabled: false, }, depthMask: false, - blending: BlendingState.ALPHA_BLEND, + blending: BlendingState.PRE_MULTIPLIED_ALPHA_BLEND, }; var pickRenderState = { From 9849b6cda4de405cdb2aec62860b4ea2eb218a9e Mon Sep 17 00:00:00 2001 From: ebogo1 Date: Wed, 10 Mar 2021 17:04:47 -0500 Subject: [PATCH 37/76] first pass of specs --- .../Scene/Vector3DTileClampedPolylinesSpec.js | 175 ++++++++++++++++++ 1 file changed, 175 insertions(+) create mode 100644 Specs/Scene/Vector3DTileClampedPolylinesSpec.js diff --git a/Specs/Scene/Vector3DTileClampedPolylinesSpec.js b/Specs/Scene/Vector3DTileClampedPolylinesSpec.js new file mode 100644 index 0000000000..55a60db6b2 --- /dev/null +++ b/Specs/Scene/Vector3DTileClampedPolylinesSpec.js @@ -0,0 +1,175 @@ +import { Cartesian3 } from "../../Source/Cesium.js"; +import { Cesium3DTileStyle } from "../../Source/Cesium.js"; +import { ClassificationType } from "../../Source/Cesium.js"; +import { Color } from "../../Source/Cesium.js"; +import { ColorGeometryInstanceAttribute } from "../../Source/Cesium.js"; +import { destroyObject } from "../../Source/Cesium.js"; +import { Ellipsoid } from "../../Source/Cesium.js"; +import { GeometryInstance } from "../../Source/Cesium.js"; +import { Rectangle } from "../../Source/Cesium.js"; +import { RectangleGeometry } from "../../Source/Cesium.js"; +import { Cesium3DTileBatchTable } from "../../Source/Cesium.js"; +import { ColorBlendMode } from "../../Source/Cesium.js"; +import { Pass } from "../../Source/Cesium.js"; +import { PerInstanceColorAppearance } from "../../Source/Cesium.js"; +import { Primitive } from "../../Source/Cesium.js"; +import { Vector3DTileClampedPolylines } from "../../Source/Cesium.js"; +import Cesium3DTilesTester from "../Cesium3DTilesTester.js"; +import createScene from "../createScene.js"; + +describe( + "Scene/Vector3DTileClampedPolylines", + function () { + var scene; + var rectangle; + var polylines; + + var ellipsoid = Ellipsoid.WGS84; + + var depthRectanglePrimitive; + var vectorPolylines = + "./Data/Cesium3DTiles/Vector/VectorTilePolylines/tileset.json"; + + beforeAll(function () { + scene = createScene(); + }); + + afterAll(function () { + scene.destroyForSpecs(); + }); + + var mockTileset = { + _statistics: { + texturesByteLength: 0, + }, + tileset: { + _statistics: { + batchTableByteLength: 0, + }, + colorBlendMode: ColorBlendMode.HIGHLIGHT, + }, + getFeature: function (id) { + return { batchId: id }; + }, + }; + + function MockGlobePrimitive(primitive) { + this._primitive = primitive; + this.pass = Pass.GLOBE; + } + + MockGlobePrimitive.prototype.update = function (frameState) { + var commandList = frameState.commandList; + var startLength = commandList.length; + this._primitive.update(frameState); + + for (var i = startLength; i < commandList.length; ++i) { + var command = commandList[i]; + command.pass = this.pass; + } + }; + + MockGlobePrimitive.prototype.isDestroyed = function () { + return false; + }; + + MockGlobePrimitive.prototype.destroy = function () { + this._primitive.destroy(); + return destroyObject(this); + }; + + beforeEach(function () { + rectangle = Rectangle.fromDegrees(-40.0, -40.0, 40.0, 40.0); + var depthpolylineColorAttribute = ColorGeometryInstanceAttribute.fromColor( + new Color(0.0, 0.0, 1.0, 1.0) + ); + var primitive = new Primitive({ + geometryInstances: new GeometryInstance({ + geometry: new RectangleGeometry({ + ellipsoid: ellipsoid, + rectangle: rectangle, + }), + id: "depth rectangle", + attributes: { + color: depthpolylineColorAttribute, + }, + }), + appearance: new PerInstanceColorAppearance({ + translucent: false, + flat: true, + }), + asynchronous: false, + }); + + // wrap rectangle primitive so it gets executed during the globe pass to lay down depth + depthRectanglePrimitive = new MockGlobePrimitive(primitive); + }); + + afterEach(function () { + scene.primitives.removeAll(); + polylines = polylines && !polylines.isDestroyed() && polylines.destroy(); + }); + + it("renders clamped polylines", function () { + return Cesium3DTilesTester.loadTileset(scene, vectorPolylines, { + classificationType: ClassificationType.TERRAIN, + }).then(function (tileset) { + tileset.style = new Cesium3DTileStyle({ + color: "rgba(255, 0, 0, 1.0)", + }); + tileset.maximumScreenSpaceError = 0.0; + + var batchTable = new Cesium3DTileBatchTable(mockTileset, 1); + batchTable.update(mockTileset, scene.frameState); + + scene.primitives.add(depthRectanglePrimitive); + + scene.camera.lookAt( + Cartesian3.fromDegrees(0.01, 0.0, 1.5), + new Cartesian3(0.0, 0.0, 1.0) + ); + expect(scene).toRender([0, 0, 255, 255]); + }); + }); + + it("picks a clamped polyline", function () { + return Cesium3DTilesTester.loadTileset(scene, vectorPolylines, { + classificationType: ClassificationType.TERRAIN, + }).then(function (tileset) { + var batchTable = new Cesium3DTileBatchTable(mockTileset, 1); + batchTable.update(mockTileset, scene.frameState); + + scene.primitives.add(depthRectanglePrimitive); + + scene.camera.lookAt( + Cartesian3.fromDegrees(0.5, 0.0, 1.5), + new Cartesian3(0.0, 0.0, 1.0) + ); + + var features = []; + tileset.createFeatures(mockTileset, features); + + var getFeature = mockTileset.getFeature; + mockTileset.getFeature = function (index) { + return features[index]; + }; + + scene.frameState.passes.pick = true; + batchTable.update(mockTileset, scene.frameState); + expect(scene).toPickAndCall(function (result) { + expect(result).toBe(features[0]); + }); + + mockTileset.getFeature = getFeature; + }); + }); + + it("isDestroyed", function () { + polylines = new Vector3DTileClampedPolylines({}); + expect(polylines.isDestroyed()).toEqual(false); + polylines.destroy(); + expect(polylines.isDestroyed()).toEqual(true); + }); + }, + "WebGL" +); From 0c31cdfe325d174f6ce0ee25d284312cdcb147a3 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Thu, 11 Mar 2021 07:58:07 -0500 Subject: [PATCH 38/76] Fix typo [skip ci] --- CHANGES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index f73bf84367..f86532ab4e 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,7 +5,7 @@ ##### Additions :tada: - Added `Cesium3DTileset.pickPrimitive` for rendering primitives instead of the tileset during the pick pass. -- Added support for for drawing ground primitives on translucent 3D Tiles. [#9399](https://github.com/CesiumGS/cesium/pull/9399) +- Added support for drawing ground primitives on translucent 3D Tiles. [#9399](https://github.com/CesiumGS/cesium/pull/9399) ### 1.79.1 - 2021-03-01 From d0c3b517fc8d5c8578dc0fff8f403291881d3c24 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Thu, 11 Mar 2021 08:01:16 -0500 Subject: [PATCH 39/76] Revert #9395 Cesium3DTileset pickPrimitive --- CHANGES.md | 1 - Source/Scene/Cesium3DTileset.js | 26 +++++++------------------- Specs/Scene/Cesium3DTilesetSpec.js | 23 ----------------------- 3 files changed, 7 insertions(+), 43 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index f86532ab4e..f973fdd663 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,7 +4,6 @@ ##### Additions :tada: -- Added `Cesium3DTileset.pickPrimitive` for rendering primitives instead of the tileset during the pick pass. - Added support for drawing ground primitives on translucent 3D Tiles. [#9399](https://github.com/CesiumGS/cesium/pull/9399) ### 1.79.1 - 2021-03-01 diff --git a/Source/Scene/Cesium3DTileset.js b/Source/Scene/Cesium3DTileset.js index 243c50b30f..19094051d8 100644 --- a/Source/Scene/Cesium3DTileset.js +++ b/Source/Scene/Cesium3DTileset.js @@ -91,7 +91,6 @@ import TileOrientedBoundingBox from "./TileOrientedBoundingBox.js"; * @param {String} [options.specularEnvironmentMaps] A URL to a KTX file that contains a cube map of the specular lighting and the convoluted specular mipmaps. * @param {Boolean} [options.backFaceCulling=true] Whether to cull back-facing geometry. When true, back face culling is determined by the glTF material's doubleSided property; when false, back face culling is disabled. * @param {String} [options.debugHeatmapTilePropertyName] The tile variable to colorize as a heatmap. All rendered tiles will be colorized relative to each other's specified variable value. - * @param {Object} [options.pickPrimitive] The primitive to be rendered during the pick pass instead of the tileset. * @param {Boolean} [options.debugFreezeFrame=false] For debugging only. Determines if only the tiles from last frame should be used for rendering. * @param {Boolean} [options.debugColorizeTiles=false] For debugging only. When true, assigns a random color to each tile. * @param {Boolean} [options.debugWireframe=false] For debugging only. When true, render's each tile's content as a wireframe. @@ -762,13 +761,6 @@ function Cesium3DTileset(options) { */ this.backFaceCulling = defaultValue(options.backFaceCulling, true); - /** - * The primitive to be rendered during the pick pass instead of the tileset. - * - * @type {Object} - */ - this.pickPrimitive = options.pickPrimitive; - /** * This property is for debugging only; it is not optimized for production use. *

@@ -2552,17 +2544,13 @@ Cesium3DTileset.prototype.updateForPass = function ( var passStatistics = this._statisticsPerPass[pass]; if (this.show || ignoreCommands) { - if (frameState.passes.pick && defined(this.pickPrimitive)) { - this.pickPrimitive.update(frameState); - } else { - this._pass = pass; - tilesetPassState.ready = update( - this, - frameState, - passStatistics, - passOptions - ); - } + this._pass = pass; + tilesetPassState.ready = update( + this, + frameState, + passStatistics, + passOptions + ); } if (ignoreCommands) { diff --git a/Specs/Scene/Cesium3DTilesetSpec.js b/Specs/Scene/Cesium3DTilesetSpec.js index 27a68cd986..76c442441d 100644 --- a/Specs/Scene/Cesium3DTilesetSpec.js +++ b/Specs/Scene/Cesium3DTilesetSpec.js @@ -719,29 +719,6 @@ describe( ); }); - function loadTilesetAtFullDetail(url) { - return Cesium3DTilesTester.loadTileset(scene, url).then(function ( - tileset - ) { - tileset.maximumScreenSpaceError = 0.0; - return Cesium3DTilesTester.waitForTilesLoaded(scene, tileset); - }); - } - - it("renders pickPrimitive during pick pass if defined", function () { - viewRootOnly(); - return when - .all([ - loadTilesetAtFullDetail(tilesetUrl), - loadTilesetAtFullDetail(withBatchTableUrl), - ]) - .then(function (tilesets) { - tilesets[0].pickPrimitive = tilesets[1]; - expect(tilesets[0].pickPrimitive).toEqual(tilesets[1]); - expect(scene).toPickPrimitive(tilesets[1]); - }); - }); - it("verify statistics", function () { options.url = tilesetUrl; var tileset = scene.primitives.add(new Cesium3DTileset(options)); From 6e4d7ef9b8c96520f1b6cfc5fe5f6aec11724efb Mon Sep 17 00:00:00 2001 From: ebogo1 Date: Thu, 11 Mar 2021 11:38:13 -0500 Subject: [PATCH 40/76] clamped polyline specs --- Specs/Scene/Vector3DTileClampedPolylinesSpec.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Specs/Scene/Vector3DTileClampedPolylinesSpec.js b/Specs/Scene/Vector3DTileClampedPolylinesSpec.js index 55a60db6b2..b3ee57740c 100644 --- a/Specs/Scene/Vector3DTileClampedPolylinesSpec.js +++ b/Specs/Scene/Vector3DTileClampedPolylinesSpec.js @@ -110,7 +110,7 @@ describe( polylines = polylines && !polylines.isDestroyed() && polylines.destroy(); }); - it("renders clamped polylines", function () { + xit("renders clamped polylines", function () { return Cesium3DTilesTester.loadTileset(scene, vectorPolylines, { classificationType: ClassificationType.TERRAIN, }).then(function (tileset) { @@ -119,8 +119,8 @@ describe( }); tileset.maximumScreenSpaceError = 0.0; - var batchTable = new Cesium3DTileBatchTable(mockTileset, 1); - batchTable.update(mockTileset, scene.frameState); + // var batchTable = new Cesium3DTileBatchTable(mockTileset, 1); + // batchTable.update(mockTileset, scene.frameState); scene.primitives.add(depthRectanglePrimitive); @@ -132,7 +132,7 @@ describe( }); }); - it("picks a clamped polyline", function () { + xit("picks a clamped polyline", function () { return Cesium3DTilesTester.loadTileset(scene, vectorPolylines, { classificationType: ClassificationType.TERRAIN, }).then(function (tileset) { @@ -147,7 +147,8 @@ describe( ); var features = []; - tileset.createFeatures(mockTileset, features); + var content = tileset._root._content; + content._polylines.createFeatures(mockTileset, features); var getFeature = mockTileset.getFeature; mockTileset.getFeature = function (index) { From da38f7872f2502a0326cc2a3554ae334aa19be75 Mon Sep 17 00:00:00 2001 From: Kangning Li Date: Fri, 12 Mar 2021 10:04:59 -0500 Subject: [PATCH 41/76] fix clamped vector polylines not drawing when camera is inside volume by culling front faces --- Source/Scene/Vector3DTileClampedPolylines.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Source/Scene/Vector3DTileClampedPolylines.js b/Source/Scene/Vector3DTileClampedPolylines.js index 5e20fa3610..ef5360e940 100644 --- a/Source/Scene/Vector3DTileClampedPolylines.js +++ b/Source/Scene/Vector3DTileClampedPolylines.js @@ -28,6 +28,7 @@ import when from "../ThirdParty/when.js"; import BlendingState from "./BlendingState.js"; import Cesium3DTileFeature from "./Cesium3DTileFeature.js"; import ClassificationType from "./ClassificationType.js"; +import CullFace from "./CullFace.js"; import StencilConstants from "./StencilConstants.js"; import StencilFunction from "./StencilFunction.js"; import StencilOperation from "./StencilOperation.js"; @@ -474,9 +475,17 @@ function createUniformMap(primitive, context) { } function getRenderState(mask3DTiles) { + /** + * Cull front faces of each volume (relative to camera) to prevent + * classification drawing from both the front and back faces, double-draw. + * The geometry is "inverted" (inside-out winding order for the indices) but + * the vertex shader seems to re-invert so that the triangles face "out" again. + * So cull FRONT faces. + */ return RenderState.fromCache({ cull: { - enabled: true, // prevent double-draw. Geometry is "inverted" (reversed winding order) so we're drawing backfaces. + enabled: true, + face: CullFace.FRONT, }, blending: BlendingState.PRE_MULTIPLIED_ALPHA_BLEND, depthMask: false, From 66025019be65856ee01e33fecdcc3b4076649be3 Mon Sep 17 00:00:00 2001 From: Kangning Li Date: Fri, 12 Mar 2021 10:17:18 -0500 Subject: [PATCH 42/76] use bounding volume that covers whole shadow volume for draw commands in Vector3DTileClampedPolylines --- Source/Scene/Vector3DTileClampedPolylines.js | 19 +++++++++++++++---- .../Scene/Vector3DTileClampedPolylinesSpec.js | 4 +++- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/Source/Scene/Vector3DTileClampedPolylines.js b/Source/Scene/Vector3DTileClampedPolylines.js index ef5360e940..abf0142fb5 100644 --- a/Source/Scene/Vector3DTileClampedPolylines.js +++ b/Source/Scene/Vector3DTileClampedPolylines.js @@ -10,6 +10,7 @@ import destroyObject from "../Core/destroyObject.js"; import Ellipsoid from "../Core/Ellipsoid.js"; import FeatureDetection from "../Core/FeatureDetection.js"; import IndexDatatype from "../Core/IndexDatatype.js"; +import OrientedBoundingBox from "../Core/OrientedBoundingBox.js"; import Matrix4 from "../Core/Matrix4.js"; import Rectangle from "../Core/Rectangle.js"; import TaskProcessor from "../Core/TaskProcessor.js"; @@ -49,7 +50,6 @@ import StencilOperation from "./StencilOperation.js"; * @param {Cartesian3} [options.center=Cartesian3.ZERO] The RTC center. * @param {Cesium3DTileBatchTable} options.batchTable The batch table for the tile containing the batched polylines. * @param {Uint16Array} options.batchIds The batch ids for each polyline. - * @param {BoundingSphere} options.boundingVolume The bounding volume for the entire batch of polylines. * @param {Cesium3DTileset} options.tileset Tileset carrying minimum and maximum clamping heights. * * @private @@ -68,7 +68,6 @@ function Vector3DTileClampedPolylines(options) { this._center = options.center; this._rectangle = options.rectangle; - this._boundingVolume = options.boundingVolume; this._batchTable = options.batchTable; this._va = undefined; @@ -84,6 +83,12 @@ function Vector3DTileClampedPolylines(options) { ApproximateTerrainHeights._defaultMinTerrainHeight, ApproximateTerrainHeights._defaultMaxTerrainHeight ); + this._boundingVolume = OrientedBoundingBox.fromRectangle( + options.rectangle, + ApproximateTerrainHeights._defaultMinTerrainHeight, + ApproximateTerrainHeights._defaultMaxTerrainHeight, + this._ellipsoid + ); // Fat vertices - all information for each volume packed to a vec3 and 5 vec4s this._startEllipsoidNormals = undefined; @@ -164,9 +169,15 @@ function updateMinimumMaximumHeights(polylines, rectangle, ellipsoid) { rectangle, ellipsoid ); + var min = result.minimumTerrainHeight; + var max = result.maximumTerrainHeight; var minimumMaximumVectorHeights = polylines._minimumMaximumVectorHeights; - minimumMaximumVectorHeights.x = result.minimumTerrainHeight; - minimumMaximumVectorHeights.y = result.maximumTerrainHeight; + minimumMaximumVectorHeights.x = min; + minimumMaximumVectorHeights.y = max; + + var obb = polylines._boundingVolume; + var rect = polylines._rectangle; + OrientedBoundingBox.fromRectangle(rect, min, max, ellipsoid, obb); } function packBuffer(polylines) { diff --git a/Specs/Scene/Vector3DTileClampedPolylinesSpec.js b/Specs/Scene/Vector3DTileClampedPolylinesSpec.js index b3ee57740c..d32af4e8c6 100644 --- a/Specs/Scene/Vector3DTileClampedPolylinesSpec.js +++ b/Specs/Scene/Vector3DTileClampedPolylinesSpec.js @@ -166,7 +166,9 @@ describe( }); it("isDestroyed", function () { - polylines = new Vector3DTileClampedPolylines({}); + polylines = new Vector3DTileClampedPolylines({ + rectangle: new Rectangle(), + }); expect(polylines.isDestroyed()).toEqual(false); polylines.destroy(); expect(polylines.isDestroyed()).toEqual(true); From 624571613a15878b467bfd78860cad590fa814c1 Mon Sep 17 00:00:00 2001 From: ebogo1 Date: Fri, 12 Mar 2021 15:18:17 -0500 Subject: [PATCH 43/76] fix specs --- .../lines.vctr | Bin 0 -> 376 bytes .../tileset.json | 56 ++++++++++++++ .../Scene/Vector3DTileClampedPolylinesSpec.js | 73 ++++-------------- 3 files changed, 73 insertions(+), 56 deletions(-) create mode 100644 Specs/Data/Cesium3DTiles/Vector/VectorTilePolylineSegment64Wide/lines.vctr create mode 100644 Specs/Data/Cesium3DTiles/Vector/VectorTilePolylineSegment64Wide/tileset.json diff --git a/Specs/Data/Cesium3DTiles/Vector/VectorTilePolylineSegment64Wide/lines.vctr b/Specs/Data/Cesium3DTiles/Vector/VectorTilePolylineSegment64Wide/lines.vctr new file mode 100644 index 0000000000000000000000000000000000000000..39d36b3977dc6466339d416d925f2b187a43cb91 GIT binary patch literal 376 zcmaJ-O-sW-5M9NC2Y*BvuiKKuL`hD@BqR%Aw`8}7NC_G?c&JTj7BPhULw}Px_Crbq zAH132&Evt$TV>WKgphYEAK1^4FUT|G9v8czhpEUPa>f%`<_S+#27yg~#qusxQC6~8 zLeRr=XSAs6RvQSc;lLG%8CS;+_t0_AVwL>~anMpEDH9xm$FV)RalI?YcRbtoT^z3U z*z%|~vFJampEYtmj0m|Ur+8NnRoh0*qFG}qT-F=FoWkO%)2JOCLZND2SItr{jp|k! j0+?~hQl7-6%tWDpg4yaa3iGglVHj<&ec|WV?v>;lYV2NO literal 0 HcmV?d00001 diff --git a/Specs/Data/Cesium3DTiles/Vector/VectorTilePolylineSegment64Wide/tileset.json b/Specs/Data/Cesium3DTiles/Vector/VectorTilePolylineSegment64Wide/tileset.json new file mode 100644 index 0000000000..bbbb95f6cd --- /dev/null +++ b/Specs/Data/Cesium3DTiles/Vector/VectorTilePolylineSegment64Wide/tileset.json @@ -0,0 +1,56 @@ +{ + "asset": { + "version": "1.0" + }, + "geometricError": 221.1485516321866, + "root": { + "boundingVolume": { + "region": [ + -1.7453292519943296e-05, + 0.0, + 1.7453292519943296e-05, + 0.0, + 0.0, + 0.0 + ] + }, + "children": [ + { + "boundingVolume": { + "region": [ + -1.7453292519943296e-05, + 0.0, + 1.7453292519943296e-05, + 0.0, + 0.0, + 0.0 + ] + }, + "content": { + "uri": "lines.vctr" + }, + "geometricError": 0.0 + } + ], + "geometricError": 221.1485516321866, + "refine": "ADD", + "transform": [ + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0 + ] + } +} \ No newline at end of file diff --git a/Specs/Scene/Vector3DTileClampedPolylinesSpec.js b/Specs/Scene/Vector3DTileClampedPolylinesSpec.js index d32af4e8c6..4aa17d13d5 100644 --- a/Specs/Scene/Vector3DTileClampedPolylinesSpec.js +++ b/Specs/Scene/Vector3DTileClampedPolylinesSpec.js @@ -1,5 +1,4 @@ import { Cartesian3 } from "../../Source/Cesium.js"; -import { Cesium3DTileStyle } from "../../Source/Cesium.js"; import { ClassificationType } from "../../Source/Cesium.js"; import { Color } from "../../Source/Cesium.js"; import { ColorGeometryInstanceAttribute } from "../../Source/Cesium.js"; @@ -8,8 +7,6 @@ import { Ellipsoid } from "../../Source/Cesium.js"; import { GeometryInstance } from "../../Source/Cesium.js"; import { Rectangle } from "../../Source/Cesium.js"; import { RectangleGeometry } from "../../Source/Cesium.js"; -import { Cesium3DTileBatchTable } from "../../Source/Cesium.js"; -import { ColorBlendMode } from "../../Source/Cesium.js"; import { Pass } from "../../Source/Cesium.js"; import { PerInstanceColorAppearance } from "../../Source/Cesium.js"; import { Primitive } from "../../Source/Cesium.js"; @@ -38,21 +35,6 @@ describe( scene.destroyForSpecs(); }); - var mockTileset = { - _statistics: { - texturesByteLength: 0, - }, - tileset: { - _statistics: { - batchTableByteLength: 0, - }, - colorBlendMode: ColorBlendMode.HIGHLIGHT, - }, - getFeature: function (id) { - return { batchId: id }; - }, - }; - function MockGlobePrimitive(primitive) { this._primitive = primitive; this.pass = Pass.GLOBE; @@ -110,58 +92,37 @@ describe( polylines = polylines && !polylines.isDestroyed() && polylines.destroy(); }); - xit("renders clamped polylines", function () { + it("renders clamped polylines", function () { + scene.camera.lookAt( + Cartesian3.fromDegrees(0.0, 0.0, 1.5), + new Cartesian3(0.0, 0.0, 1.0) + ); return Cesium3DTilesTester.loadTileset(scene, vectorPolylines, { classificationType: ClassificationType.TERRAIN, }).then(function (tileset) { - tileset.style = new Cesium3DTileStyle({ - color: "rgba(255, 0, 0, 1.0)", - }); - tileset.maximumScreenSpaceError = 0.0; - - // var batchTable = new Cesium3DTileBatchTable(mockTileset, 1); - // batchTable.update(mockTileset, scene.frameState); - scene.primitives.add(depthRectanglePrimitive); - scene.camera.lookAt( - Cartesian3.fromDegrees(0.01, 0.0, 1.5), - new Cartesian3(0.0, 0.0, 1.0) - ); + tileset.show = false; expect(scene).toRender([0, 0, 255, 255]); + tileset.show = true; + expect(scene).toRender([255, 255, 255, 255]); }); }); - xit("picks a clamped polyline", function () { + it("picks a clamped polyline", function () { + scene.camera.lookAt( + Cartesian3.fromDegrees(0.0, 0.0, 1.5), + new Cartesian3(0.0, 0.0, 1.0) + ); return Cesium3DTilesTester.loadTileset(scene, vectorPolylines, { classificationType: ClassificationType.TERRAIN, }).then(function (tileset) { - var batchTable = new Cesium3DTileBatchTable(mockTileset, 1); - batchTable.update(mockTileset, scene.frameState); - scene.primitives.add(depthRectanglePrimitive); - scene.camera.lookAt( - Cartesian3.fromDegrees(0.5, 0.0, 1.5), - new Cartesian3(0.0, 0.0, 1.0) - ); - - var features = []; - var content = tileset._root._content; - content._polylines.createFeatures(mockTileset, features); - - var getFeature = mockTileset.getFeature; - mockTileset.getFeature = function (index) { - return features[index]; - }; - - scene.frameState.passes.pick = true; - batchTable.update(mockTileset, scene.frameState); - expect(scene).toPickAndCall(function (result) { - expect(result).toBe(features[0]); - }); - - mockTileset.getFeature = getFeature; + tileset.show = false; + expect(scene).toPickPrimitive(depthRectanglePrimitive._primitive); + tileset.show = true; + expect(scene).toPickPrimitive(tileset); }); }); From eef86b4cb377f64feee1c5dce9dcd2dd66361af4 Mon Sep 17 00:00:00 2001 From: ebogo1 Date: Fri, 12 Mar 2021 15:18:56 -0500 Subject: [PATCH 44/76] remove unused test tileset --- .../lines.vctr | Bin 376 -> 0 bytes .../tileset.json | 56 -------------- bad/lines.vctr | Bin 0 -> 1720 bytes bad/tileset.json | 1 + bad/tileset_nob3dm.json | 73 ++++++++++++++++++ bad/trianglesTile.b3dm | Bin 0 -> 251916 bytes bad/wireframeTile.b3dm | Bin 0 -> 180096 bytes 7 files changed, 74 insertions(+), 56 deletions(-) delete mode 100644 Specs/Data/Cesium3DTiles/Vector/VectorTilePolylineSegment64Wide/lines.vctr delete mode 100644 Specs/Data/Cesium3DTiles/Vector/VectorTilePolylineSegment64Wide/tileset.json create mode 100644 bad/lines.vctr create mode 100644 bad/tileset.json create mode 100644 bad/tileset_nob3dm.json create mode 100644 bad/trianglesTile.b3dm create mode 100644 bad/wireframeTile.b3dm diff --git a/Specs/Data/Cesium3DTiles/Vector/VectorTilePolylineSegment64Wide/lines.vctr b/Specs/Data/Cesium3DTiles/Vector/VectorTilePolylineSegment64Wide/lines.vctr deleted file mode 100644 index 39d36b3977dc6466339d416d925f2b187a43cb91..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 376 zcmaJ-O-sW-5M9NC2Y*BvuiKKuL`hD@BqR%Aw`8}7NC_G?c&JTj7BPhULw}Px_Crbq zAH132&Evt$TV>WKgphYEAK1^4FUT|G9v8czhpEUPa>f%`<_S+#27yg~#qusxQC6~8 zLeRr=XSAs6RvQSc;lLG%8CS;+_t0_AVwL>~anMpEDH9xm$FV)RalI?YcRbtoT^z3U z*z%|~vFJampEYtmj0m|Ur+8NnRoh0*qFG}qT-F=FoWkO%)2JOCLZND2SItr{jp|k! j0+?~hQl7-6%tWDpg4yaa3iGglVHj<&ec|WV?v>;lYV2NO diff --git a/Specs/Data/Cesium3DTiles/Vector/VectorTilePolylineSegment64Wide/tileset.json b/Specs/Data/Cesium3DTiles/Vector/VectorTilePolylineSegment64Wide/tileset.json deleted file mode 100644 index bbbb95f6cd..0000000000 --- a/Specs/Data/Cesium3DTiles/Vector/VectorTilePolylineSegment64Wide/tileset.json +++ /dev/null @@ -1,56 +0,0 @@ -{ - "asset": { - "version": "1.0" - }, - "geometricError": 221.1485516321866, - "root": { - "boundingVolume": { - "region": [ - -1.7453292519943296e-05, - 0.0, - 1.7453292519943296e-05, - 0.0, - 0.0, - 0.0 - ] - }, - "children": [ - { - "boundingVolume": { - "region": [ - -1.7453292519943296e-05, - 0.0, - 1.7453292519943296e-05, - 0.0, - 0.0, - 0.0 - ] - }, - "content": { - "uri": "lines.vctr" - }, - "geometricError": 0.0 - } - ], - "geometricError": 221.1485516321866, - "refine": "ADD", - "transform": [ - 1.0, - 0.0, - 0.0, - 0.0, - 0.0, - 1.0, - 0.0, - 0.0, - 0.0, - 0.0, - 1.0, - 0.0, - 0.0, - 0.0, - 0.0, - 1.0 - ] - } -} \ No newline at end of file diff --git a/bad/lines.vctr b/bad/lines.vctr new file mode 100644 index 0000000000000000000000000000000000000000..5ac72ca068376841fdc599cf690988ba232990d6 GIT binary patch literal 1720 zcmZ8idrXyO82>#l9Igr|UJw-NJLdI(obMdY%`_1aNZ=@!nioREgNhdfjuJy8=Zpb4 zG^R*~grHlIsoBi3bzN&^=4|U?ty*PmW~_@V7gdoR!BoE_!< z8Ub)V1h~dAjNCv5lXoAxX;^E}mU(kmZD(eh)i63No{Dy!o1ERkAh3?sV&R-R*MOb(h<& zJ1nUwX>NzjYPF}itZrwT)vjBH-KwyLeZt^T6=Z9dJT73UY`WaVcrE-lRV78Gd~%|C1}%t+5oFVGrC zHZ=A-@ocOV%R#v$;mWPbRIO@K&8kiHsFP|yoeId4>a03L-Qcj*7_Yj1;c36dSNM$h z6d&Vo*==#Dhp@L?l15%p-p!2r5&0G;SUA6~#2yoR%Q9dF}He1q$_iQmav_#Rhq5f^X-wa9`Q z(L6~aQt>zr;XV9@v7*Z|*(^`W^Kx3=kx%5BK*g(6wM4B}l>zwGHnl?;vQlkO>qfAa zV})9yR;mJ(trjZ}zp<$4Dp4h=I5k#{BEnRJisBeU8AUmcGEPlXvsDUZBIkdU6H+OO z@(*%RhBqT7ONjh|FK`aez>n2f0tco;LmVa$@t8(Ei8qMHCfY@gKkt z=E-`=lQOB2>oe9%mfUe1WL+8PAUma7dZkZ}6MX^n($Xadq($0@7NYqcj5ou!M|Kl? zh+&K2xi_F2-i^{E4YHl6m1?P@UPm<1V;}cCKz7Oz=?U!GN3ES}Rm()k(>B|nZJN;v@r9ftfD;T|6w?J*BO{anyJ}n$-+_;Gxl5P*MKJax3gNhiQWKv z(8IBlwYHD5<*dqC2w_$#(a${HMw~n+FUyNEQ&n@;%e8`tMXa$$kb)s{22;weNVgh{+;GC>x~Oqosn^nlUH#$$9xFk0m0P|6|vi6G7nAyVekW+qMaFiALP=g@zM(f$yl<#XEZ z;3xcz8-z)2;alp1c$c;dxWuy^O&T+6KrvR0V!F<{}vnumd!B Wpfi@K%!-5mO1tnV7IK$p?(!dyx`FKg literal 0 HcmV?d00001 diff --git a/bad/tileset.json b/bad/tileset.json new file mode 100644 index 0000000000..b7a78f2215 --- /dev/null +++ b/bad/tileset.json @@ -0,0 +1 @@ +{"asset":{"extras":{"cesium":{"heightmapUri":"heightmap.tiff"},"ion":{"georeferenced":true,"movable":false},"smart-construction":{"layers":["TIN border","Mesh","Wireframe"],"lineHeights":true}},"version":"1.0"},"geometricError":462.8602133118854,"root":{"boundingVolume":{"region":[-1.4792429884289426,0.5963114581975142,-1.4791717189474136,0.5964067126816758,226.73130227927786,282.64505475314047]},"children":[{"boundingVolume":{"box":[23.37433624267578,-43.70379638671875,-7.86527156829834,188.25613403320313,0.0,0.0,0.0,302.709716796875,0.0,0.0,0.0,27.96090316772461]},"content":{"uri":"trianglesTile.b3dm"},"geometricError":0.0},{"boundingVolume":{"box":[23.37433624267578,-43.70379638671875,-7.86527156829834,188.25613403320313,0.0,0.0,0.0,302.709716796875,0.0,0.0,0.0,27.96090316772461]},"content":{"uri":"wireframeTile.b3dm"},"geometricError":0.0},{"boundingVolume":{"region":[-1.4792429884289426,0.5963114581975142,-1.4791717189474136,0.5964067126816758,226.73130227927786,277.43482284638196]},"content":{"uri":"lines.vctr"},"geometricError":0.0}],"geometricError":462.8602133118854,"refine":"ADD","transform":[0.9958090657893894,0.09145657161551506,0.0,0.0,-0.051365628371451276,0.5592857626163465,0.827382020566472,0.0,0.07566952301732707,-0.8239145169512355,0.5616395570500197,0.0,483160.87982166663,-5260813.694,3562142.982781553,1.0]}} \ No newline at end of file diff --git a/bad/tileset_nob3dm.json b/bad/tileset_nob3dm.json new file mode 100644 index 0000000000..89b9797242 --- /dev/null +++ b/bad/tileset_nob3dm.json @@ -0,0 +1,73 @@ +{ + "asset": { + "extras": { + "cesium": { + "heightmapUri": "heightmap.tiff" + }, + "ion": { + "georeferenced": true, + "movable": false + }, + "smart-construction": { + "layers": [ + "TIN border", + "Mesh", + "Wireframe" + ], + "lineHeights": true + } + }, + "version": "1.0" + }, + "geometricError": 462.8602133118854, + "root": { + "boundingVolume": { + "region": [ + -1.4792429884289426, + 0.5963114581975142, + -1.4791717189474136, + 0.5964067126816758, + 226.73130227927786, + 282.64505475314047 + ] + }, + "children": [ + { + "boundingVolume": { + "region": [ + -1.4792429884289426, + 0.5963114581975142, + -1.4791717189474136, + 0.5964067126816758, + 226.73130227927786, + 277.43482284638196 + ] + }, + "content": { + "uri": "lines.vctr" + }, + "geometricError": 0.0 + } + ], + "geometricError": 462.8602133118854, + "refine": "ADD", + "transform": [ + 0.9958090657893894, + 0.09145657161551506, + 0.0, + 0.0, + -0.051365628371451276, + 0.5592857626163465, + 0.827382020566472, + 0.0, + 0.07566952301732707, + -0.8239145169512355, + 0.5616395570500197, + 0.0, + 483160.87982166663, + -5260813.694, + 3562142.982781553, + 1.0 + ] + } +} \ No newline at end of file diff --git a/bad/trianglesTile.b3dm b/bad/trianglesTile.b3dm new file mode 100644 index 0000000000000000000000000000000000000000..c1445ecbe88f7734346d060fc0632d2db8317dfb GIT binary patch literal 251916 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 NV0Xao!2f{`{13O`%ZC5} literal 0 HcmV?d00001 From 6b9086491bdfc8048c557ee00861d37beede0f1b Mon Sep 17 00:00:00 2001 From: ebogo1 Date: Fri, 12 Mar 2021 15:21:00 -0500 Subject: [PATCH 45/76] remove files from last commit --- bad/lines.vctr | Bin 1720 -> 0 bytes bad/tileset.json | 1 - bad/tileset_nob3dm.json | 73 ---------------------------------------- bad/trianglesTile.b3dm | Bin 251916 -> 0 bytes bad/wireframeTile.b3dm | Bin 180096 -> 0 bytes 5 files changed, 74 deletions(-) delete mode 100644 bad/lines.vctr delete mode 100644 bad/tileset.json delete mode 100644 bad/tileset_nob3dm.json delete mode 100644 bad/trianglesTile.b3dm delete mode 100644 bad/wireframeTile.b3dm diff --git a/bad/lines.vctr b/bad/lines.vctr deleted file mode 100644 index 5ac72ca068376841fdc599cf690988ba232990d6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1720 zcmZ8idrXyO82>#l9Igr|UJw-NJLdI(obMdY%`_1aNZ=@!nioREgNhdfjuJy8=Zpb4 zG^R*~grHlIsoBi3bzN&^=4|U?ty*PmW~_@V7gdoR!BoE_!< z8Ub)V1h~dAjNCv5lXoAxX;^E}mU(kmZD(eh)i63No{Dy!o1ERkAh3?sV&R-R*MOb(h<& zJ1nUwX>NzjYPF}itZrwT)vjBH-KwyLeZt^T6=Z9dJT73UY`WaVcrE-lRV78Gd~%|C1}%t+5oFVGrC zHZ=A-@ocOV%R#v$;mWPbRIO@K&8kiHsFP|yoeId4>a03L-Qcj*7_Yj1;c36dSNM$h z6d&Vo*==#Dhp@L?l15%p-p!2r5&0G;SUA6~#2yoR%Q9dF}He1q$_iQmav_#Rhq5f^X-wa9`Q z(L6~aQt>zr;XV9@v7*Z|*(^`W^Kx3=kx%5BK*g(6wM4B}l>zwGHnl?;vQlkO>qfAa zV})9yR;mJ(trjZ}zp<$4Dp4h=I5k#{BEnRJisBeU8AUmcGEPlXvsDUZBIkdU6H+OO z@(*%RhBqT7ONjh|FK`aez>n2f0tco;LmVa$@t8(Ei8qMHCfY@gKkt z=E-`=lQOB2>oe9%mfUe1WL+8PAUma7dZkZ}6MX^n($Xadq($0@7NYqcj5ou!M|Kl? zh+&K2xi_F2-i^{E4YHl6m1?P@UPm<1V;}cCKz7Oz=?U!GN3ES}Rm()k(>B|nZJN;v@r9ftfD;T|6w?J*BO{anyJ}n$-+_;Gxl5P*MKJax3gNhiQWKv z(8IBlwYHD5<*dqC2w_$#(a${HMw~n+FUyNEQ&n@;%e8`tMXa$$kb)s{22;weNVgh{+;GC>x~Oqosn^nlUH#$$9xFk0m0P|6|vi6G7nAyVekW+qMaFiALP=g@zM(f$yl<#XEZ z;3xcz8-z)2;alp1c$c;dxWuy^O&T+6KrvR0V!F<{}vnumd!B Wpfi@K%!-5mO1tnV7IK$p?(!dyx`FKg diff --git a/bad/tileset.json b/bad/tileset.json deleted file mode 100644 index b7a78f2215..0000000000 --- a/bad/tileset.json +++ /dev/null @@ -1 +0,0 @@ -{"asset":{"extras":{"cesium":{"heightmapUri":"heightmap.tiff"},"ion":{"georeferenced":true,"movable":false},"smart-construction":{"layers":["TIN border","Mesh","Wireframe"],"lineHeights":true}},"version":"1.0"},"geometricError":462.8602133118854,"root":{"boundingVolume":{"region":[-1.4792429884289426,0.5963114581975142,-1.4791717189474136,0.5964067126816758,226.73130227927786,282.64505475314047]},"children":[{"boundingVolume":{"box":[23.37433624267578,-43.70379638671875,-7.86527156829834,188.25613403320313,0.0,0.0,0.0,302.709716796875,0.0,0.0,0.0,27.96090316772461]},"content":{"uri":"trianglesTile.b3dm"},"geometricError":0.0},{"boundingVolume":{"box":[23.37433624267578,-43.70379638671875,-7.86527156829834,188.25613403320313,0.0,0.0,0.0,302.709716796875,0.0,0.0,0.0,27.96090316772461]},"content":{"uri":"wireframeTile.b3dm"},"geometricError":0.0},{"boundingVolume":{"region":[-1.4792429884289426,0.5963114581975142,-1.4791717189474136,0.5964067126816758,226.73130227927786,277.43482284638196]},"content":{"uri":"lines.vctr"},"geometricError":0.0}],"geometricError":462.8602133118854,"refine":"ADD","transform":[0.9958090657893894,0.09145657161551506,0.0,0.0,-0.051365628371451276,0.5592857626163465,0.827382020566472,0.0,0.07566952301732707,-0.8239145169512355,0.5616395570500197,0.0,483160.87982166663,-5260813.694,3562142.982781553,1.0]}} \ No newline at end of file diff --git a/bad/tileset_nob3dm.json b/bad/tileset_nob3dm.json deleted file mode 100644 index 89b9797242..0000000000 --- a/bad/tileset_nob3dm.json +++ /dev/null @@ -1,73 +0,0 @@ -{ - "asset": { - "extras": { - "cesium": { - "heightmapUri": "heightmap.tiff" - }, - "ion": { - "georeferenced": true, - "movable": false - }, - "smart-construction": { - "layers": [ - "TIN border", - "Mesh", - "Wireframe" - ], - "lineHeights": true - } - }, - "version": "1.0" - }, - "geometricError": 462.8602133118854, - "root": { - "boundingVolume": { - "region": [ - -1.4792429884289426, - 0.5963114581975142, - -1.4791717189474136, - 0.5964067126816758, - 226.73130227927786, - 282.64505475314047 - ] - }, - "children": [ - { - "boundingVolume": { - "region": [ - -1.4792429884289426, - 0.5963114581975142, - -1.4791717189474136, - 0.5964067126816758, - 226.73130227927786, - 277.43482284638196 - ] - }, - "content": { - "uri": "lines.vctr" - }, - "geometricError": 0.0 - } - ], - "geometricError": 462.8602133118854, - "refine": "ADD", - "transform": [ - 0.9958090657893894, - 0.09145657161551506, - 0.0, - 0.0, - -0.051365628371451276, - 0.5592857626163465, - 0.827382020566472, - 0.0, - 0.07566952301732707, - -0.8239145169512355, - 0.5616395570500197, - 0.0, - 483160.87982166663, - -5260813.694, - 3562142.982781553, - 1.0 - ] - } -} \ No newline at end of file diff --git a/bad/trianglesTile.b3dm b/bad/trianglesTile.b3dm deleted file mode 100644 index c1445ecbe88f7734346d060fc0632d2db8317dfb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 251916 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 NV0Xao!2f{`{13O`%ZC5} From 2fcc4094efa8976b57a144b3788aa97c2dc22919 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Fri, 12 Mar 2021 17:20:42 -0500 Subject: [PATCH 46/76] Renamed noClassificationModels to vectorClassificationOnly --- Source/Scene/Batched3DModel3DTileContent.js | 2 +- Source/Scene/Cesium3DTileset.js | 14 ++++++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/Source/Scene/Batched3DModel3DTileContent.js b/Source/Scene/Batched3DModel3DTileContent.js index f9e21534d2..59175afb73 100644 --- a/Source/Scene/Batched3DModel3DTileContent.js +++ b/Source/Scene/Batched3DModel3DTileContent.js @@ -47,7 +47,7 @@ function Batched3DModel3DTileContent( this._batchTable = undefined; this._features = undefined; - this._classificationType = tileset.noClassificationModels + this._classificationType = tileset.vectorClassificationOnly ? undefined : tileset.classificationType; diff --git a/Source/Scene/Cesium3DTileset.js b/Source/Scene/Cesium3DTileset.js index 768c46f823..fa7968db94 100644 --- a/Source/Scene/Cesium3DTileset.js +++ b/Source/Scene/Cesium3DTileset.js @@ -90,6 +90,7 @@ import TileOrientedBoundingBox from "./TileOrientedBoundingBox.js"; * @param {Cartesian3[]} [options.sphericalHarmonicCoefficients] The third order spherical harmonic coefficients used for the diffuse color of image-based lighting. * @param {String} [options.specularEnvironmentMaps] A URL to a KTX file that contains a cube map of the specular lighting and the convoluted specular mipmaps. * @param {Boolean} [options.backFaceCulling=true] Whether to cull back-facing geometry. When true, back face culling is determined by the glTF material's doubleSided property; when false, back face culling is disabled. + * @param {Boolean} [options.vectorClassificationOnly=false] Indicates that only the tileset's vector tiles should be used for classification. * @param {String} [options.debugHeatmapTilePropertyName] The tile variable to colorize as a heatmap. All rendered tiles will be colorized relative to each other's specified variable value. * @param {Boolean} [options.debugFreezeFrame=false] For debugging only. Determines if only the tiles from last frame should be used for rendering. * @param {Boolean} [options.debugColorizeTiles=false] For debugging only. When true, assigns a random color to each tile. @@ -101,7 +102,6 @@ import TileOrientedBoundingBox from "./TileOrientedBoundingBox.js"; * @param {Boolean} [options.debugShowRenderingStatistics=false] For debugging only. When true, draws labels to indicate the number of commands, points, triangles and features for each tile. * @param {Boolean} [options.debugShowMemoryUsage=false] For debugging only. When true, draws labels to indicate the texture and geometry memory in megabytes used by each tile. * @param {Boolean} [options.debugShowUrl=false] For debugging only. When true, draws labels to indicate the url of each tile. - * @param {Boolean} [options.noClassificationModels=false] Indicates that the tileset's b3dms should not be used for classification. * * @exception {DeveloperError} The tileset must be 3D Tiles version 0.0 or 1.0. * @@ -274,8 +274,8 @@ function Cesium3DTileset(options) { this._clippingPlanesOriginMatrix = undefined; // Combines the above with any run-time transforms. this._clippingPlanesOriginMatrixDirty = true; - this._noClassificationModels = defaultValue( - options.noClassificationModels, + this._vectorClassificationOnly = defaultValue( + options.vectorClassificationOnly, false ); @@ -1684,16 +1684,18 @@ Object.defineProperties(Cesium3DTileset.prototype, { }, /** - * Indicates that the tileset's b3dms should not be used for classification. + * Indicates that only the tileset's vector tiles should be used for classification. * * @memberof Cesium3DTileset.prototype * + * @experimental This feature is using part of the 3D Tiles spec that is not final and is subject to change without Cesium's standard deprecation policy. + * * @type {Boolean} * @default false */ - noClassificationModels: { + vectorClassificationOnly: { get: function () { - return this._noClassificationModels; + return this._vectorClassificationOnly; }, }, }); From 4fd49c5a28cfb2a29cb76c8304707bbe8f426fbe Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Fri, 12 Mar 2021 17:33:59 -0500 Subject: [PATCH 47/76] Remove tileset.minimumMaximumVectorHeights --- Source/Scene/Cesium3DTileset.js | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/Source/Scene/Cesium3DTileset.js b/Source/Scene/Cesium3DTileset.js index fa7968db94..f913414f15 100644 --- a/Source/Scene/Cesium3DTileset.js +++ b/Source/Scene/Cesium3DTileset.js @@ -1669,20 +1669,6 @@ Object.defineProperties(Cesium3DTileset.prototype, { }, }, - /** - * Minimum and maximum heights that vector tiles clamped to surfaces will clamp to. - * - * @memberof Cesium3DTileset.prototype - * - * @type {Cartesian2} - * @default undefined - */ - minimumMaximumVectorHeights: { - get: function () { - return this._minimumMaximumVectorHeights; - }, - }, - /** * Indicates that only the tileset's vector tiles should be used for classification. * From 2736e093f133e3fd0b1b7002148987562a97879e Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Fri, 12 Mar 2021 17:40:41 -0500 Subject: [PATCH 48/76] Attempt to add @experimental tag to examineVectorLinesFunction --- Source/Scene/Cesium3DTileset.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Source/Scene/Cesium3DTileset.js b/Source/Scene/Cesium3DTileset.js index f913414f15..2eef8fd6a5 100644 --- a/Source/Scene/Cesium3DTileset.js +++ b/Source/Scene/Cesium3DTileset.js @@ -907,8 +907,9 @@ function Cesium3DTileset(options) { /** * Function for examining vector lines as they are being streamed. * + * @experimental This feature is using part of the 3D Tiles spec that is not final and is subject to change without Cesium's standard deprecation policy. + * * @type {Function} - * @private */ this.examineVectorLinesFunction = undefined; From 082dcbad5a5302c0f055eae6e5dc7f8e8e62b2ca Mon Sep 17 00:00:00 2001 From: hpinkos Date: Sat, 13 Mar 2021 12:52:56 -0500 Subject: [PATCH 49/76] change cesiumjs.org urls in docs to cesium.com --- .github/ISSUE_TEMPLATE/bug_report.md | 2 +- Documentation/Contributors/BuildGuide/README.md | 6 +++--- Documentation/Contributors/PresentersGuide/README.md | 2 +- Documentation/Contributors/TestingGuide/README.md | 4 ---- Tools/eslint-config-cesium/README.md | 2 +- Tools/eslint-config-cesium/package.json | 2 +- Tools/jsdoc/cesium_template/tmpl/indexLayout.tmpl | 2 +- Tools/rollup-plugin-strip-pragma/package.json | 2 +- 8 files changed, 9 insertions(+), 13 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index bba36ec66c..d82b3cfdb4 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -6,7 +6,7 @@ about: Let us know so we can fix it! Sandcastle example: diff --git a/Documentation/Contributors/BuildGuide/README.md b/Documentation/Contributors/BuildGuide/README.md index 926ba94f52..f3b452c3b5 100644 --- a/Documentation/Contributors/BuildGuide/README.md +++ b/Documentation/Contributors/BuildGuide/README.md @@ -50,8 +50,8 @@ npm start Then browse to [http://localhost:8080/](http://localhost:8080/). The landing page includes apps and tools commonly used during development, including: -- **Hello World** : an example using the combined and minified Cesium library to create a 3D globe. [Tutorial here](http://cesiumjs.org/tutorials/cesium-up-and-running/) -- **Sandcastle** : an app for viewing and creating [code examples](https://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Hello%20World.html&label=Showcases), complete with a live preview +- **Hello World** : an example for how to create a 3D globe. [Tutorial here](https://cesium.com/docs/tutorials/quick-start//) +- **Sandcastle** : an app for viewing and creating [code examples](https://sandcastle.cesium.com?src=Hello%20World.html&label=Showcases), complete with a live preview - **Test Suites** : tests using [Jasmine](https://jasmine.github.io/). [Testing guide here.](https://github.com/CesiumGS/cesium/blob/master/Documentation/Contributors/TestingGuide/README.md#testing-guide) - **Documentation** : reference documentation built from source. [Documentation guide here.](https://github.com/CesiumGS/cesium/blob/master/Documentation/Contributors/DocumentationGuide/README.md#documentation-guide) @@ -124,7 +124,7 @@ Here's the full set of scripts and what they do. ## Travis and Continuous Integration -Cesium uses [Travis](https://travis-ci.org/) for continuous integration. The Travis configuration and all the steps of the build process are defined in `travis.yml`. The blog post [Cesium Continuous Integration](http://cesiumjs.org/2016/04/07/Cesium-Continuous-Integration/) contains an in-depth explaination of the travis build process. +Cesium uses [Travis](https://travis-ci.org/) for continuous integration. The Travis configuration and all the steps of the build process are defined in `travis.yml`. The blog post [Cesium Continuous Integration](http://cesium.com/blog/2016/04/07/Cesium-Continuous-Integration/) contains an in-depth explaination of the travis build process. Travis triggers a build whenever someone opens a pull request or pushes code to the Cesium repository. After the build has completed, at the bottom on the pull request, the status of the build is shown and you can access the build by clicking the "Details" link. diff --git a/Documentation/Contributors/PresentersGuide/README.md b/Documentation/Contributors/PresentersGuide/README.md index 35d415e706..c98e287b84 100644 --- a/Documentation/Contributors/PresentersGuide/README.md +++ b/Documentation/Contributors/PresentersGuide/README.md @@ -1,6 +1,6 @@ # Presenter's Guide -The Cesium team gives a lot of [talks](http://cesiumjs.org/publications.html). Here's some tips, based on our experience, for delivering a great talk. +The Cesium team gives a lot of [talks](http://cesium.com/docs/presentations/). Here's some tips, based on our experience, for delivering a great talk. - [Invest Time Preparing](#invest-time-preparing) - [Know the Audience](#know-the-audience) diff --git a/Documentation/Contributors/TestingGuide/README.md b/Documentation/Contributors/TestingGuide/README.md index 74b03affd3..4b44f2d6c3 100644 --- a/Documentation/Contributors/TestingGuide/README.md +++ b/Documentation/Contributors/TestingGuide/README.md @@ -197,10 +197,6 @@ It is also possible for Karma to run all tests against each browser installed on Sometimes it is useful to run a single test or suite for easier debugging purposes. To do this simply change the `it` function call for the desired test to `fit`, the `f` stands for `focused` in Jasmine speak. Likewise, to run an entire suite, use `fdescribe` instead of `describe`. -## Testing Previous Versions of CesiumJS - -Sometimes it is useful to see if an issue exists in a previous version of CesiumJS. The tests for all versions of CesiumJS back to b15 (April 2013) are hosted on the CesiumJS website via the [downloads page](http://cesiumjs.org/downloads.html). Use the "Documentation, Sandcastle, tests, etc." links. - ## `testfailure` Label for Issues Despite our best efforts, sometimes tests fail. This is often due to a new browser, OS, or driver bug that breaks a test that previously passed. If this indicates a bug in CesiumJS, we strive to quickly fix it. Likewise, if it indicates that CesiumJS needs to work around the issue (for example, as we [did for Safari 9](https://github.com/CesiumGS/cesium/issues/2989)), we also strive to quickly fix it. diff --git a/Tools/eslint-config-cesium/README.md b/Tools/eslint-config-cesium/README.md index 826a4c976c..e43c5619f9 100644 --- a/Tools/eslint-config-cesium/README.md +++ b/Tools/eslint-config-cesium/README.md @@ -1,4 +1,4 @@ -The official [shareable ESLint config](http://eslint.org/docs/developer-guide/shareable-configs) for the [Cesium](https://cesiumjs.org/) ecosystem. +The official [shareable ESLint config](http://eslint.org/docs/developer-guide/shareable-configs) for the [Cesium](https://cesium.com/) ecosystem. ## Usage diff --git a/Tools/eslint-config-cesium/package.json b/Tools/eslint-config-cesium/package.json index 265a485835..4128dd3ef2 100644 --- a/Tools/eslint-config-cesium/package.json +++ b/Tools/eslint-config-cesium/package.json @@ -2,7 +2,7 @@ "name": "eslint-config-cesium", "version": "8.0.1", "description": "ESLint shareable configs for Cesium", - "homepage": "http://cesiumjs.org", + "homepage": "http://cesium.com/", "license": "Apache-2.0", "author": { "name": "Cesium GS, Inc.", diff --git a/Tools/jsdoc/cesium_template/tmpl/indexLayout.tmpl b/Tools/jsdoc/cesium_template/tmpl/indexLayout.tmpl index bf28cad25a..c34cd1d6e6 100644 --- a/Tools/jsdoc/cesium_template/tmpl/indexLayout.tmpl +++ b/Tools/jsdoc/cesium_template/tmpl/indexLayout.tmpl @@ -17,7 +17,7 @@

diff --git a/package.json b/package.json index 9637c2bca1..3630987b3a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cesium", - "version": "1.79.1", + "version": "1.80.0", "description": "CesiumJS is a JavaScript library for creating 3D globes and 2D maps in a web browser without a plugin.", "homepage": "http://cesium.com/cesiumjs/", "license": "Apache-2.0", From ab07bcb130d99baaae08ce5e4346ae79899136e0 Mon Sep 17 00:00:00 2001 From: hpinkos Date: Thu, 1 Apr 2021 16:24:57 -0400 Subject: [PATCH 69/76] fix user stories link in release index.html --- index.release.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.release.html b/index.release.html index 88e9d908ac..a46852368b 100644 --- a/index.release.html +++ b/index.release.html @@ -136,7 +136,7 @@ - User Stories From 9fc1b0f1369263c1e908f4dcf31fded607dd782e Mon Sep 17 00:00:00 2001 From: ebogo1 Date: Fri, 9 Apr 2021 13:35:45 -0400 Subject: [PATCH 70/76] deprecation warnings in loadCRN and loadKTX --- Source/Core/loadCRN.js | 6 ++++++ Source/Core/loadKTX.js | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/Source/Core/loadCRN.js b/Source/Core/loadCRN.js index c1583f3a7a..0d5cbb6823 100644 --- a/Source/Core/loadCRN.js +++ b/Source/Core/loadCRN.js @@ -4,6 +4,7 @@ import defined from "./defined.js"; import DeveloperError from "./DeveloperError.js"; import Resource from "./Resource.js"; import TaskProcessor from "./TaskProcessor.js"; +import deprecationWarning from "./deprecationWarning.js"; var transcodeTaskProcessor = new TaskProcessor("transcodeCRNToDXT"); @@ -36,8 +37,13 @@ var transcodeTaskProcessor = new TaskProcessor("transcodeCRNToDXT"); * @see {@link https://github.com/BinomialLLC/crunch|crunch DXTc texture compression and transcoding library} * @see {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing} * @see {@link http://wiki.commonjs.org/wiki/Promises/A|CommonJS Promises/A} + * @deprecated This function has been deprecated and will be removed in Cesium 1.82. */ function loadCRN(resourceOrUrlOrBuffer) { + deprecationWarning( + "loadCRN", + "loadCRN is deprecated and will be removed in Cesium 1.82." + ); //>>includeStart('debug', pragmas.debug); if (!defined(resourceOrUrlOrBuffer)) { throw new DeveloperError("resourceOrUrlOrBuffer is required."); diff --git a/Source/Core/loadKTX.js b/Source/Core/loadKTX.js index 6fdab8ae14..e8374c4ea2 100644 --- a/Source/Core/loadKTX.js +++ b/Source/Core/loadKTX.js @@ -6,6 +6,7 @@ import PixelFormat from "./PixelFormat.js"; import Resource from "./Resource.js"; import RuntimeError from "./RuntimeError.js"; import WebGLConstants from "./WebGLConstants.js"; +import deprecationWarning from "./deprecationWarning.js"; /** * Asynchronously loads and parses the given URL to a KTX file or parses the raw binary data of a KTX file. @@ -57,8 +58,13 @@ import WebGLConstants from "./WebGLConstants.js"; * @see {@link https://www.khronos.org/opengles/sdk/tools/KTX/file_format_spec/|KTX file format} * @see {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing} * @see {@link http://wiki.commonjs.org/wiki/Promises/A|CommonJS Promises/A} + * @deprecated This function has been deprecated and will be removed in Cesium 1.82. */ function loadKTX(resourceOrUrlOrBuffer) { + deprecationWarning( + "loadKTX", + "loadKTX is deprecated and will be removed in Cesium 1.82." + ); //>>includeStart('debug', pragmas.debug); Check.defined("resourceOrUrlOrBuffer", resourceOrUrlOrBuffer); //>>includeEnd('debug'); From e2ad7dfa0590dc797308b197286678e9fa3b8d29 Mon Sep 17 00:00:00 2001 From: ebogo1 Date: Fri, 9 Apr 2021 13:44:01 -0400 Subject: [PATCH 71/76] update CHANGES.md --- CHANGES.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index bf161ba8a9..4c07765c17 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,12 @@ # Change Log +### 1.81 - 2021-05-01 + +##### Deprecated :hourglass_flowing_sand: + +- `loadCRN` was deprecated and will be removed in Cesium 1.82. It will be replaced with support for KTX2.[9478](https://github.com/CesiumGS/cesium/pull/9478) +- `loadKTX` was deprecated and will be removed in Cesium 1.82. It will be replaced with support for KTX2. [9478](https://github.com/CesiumGS/cesium/pull/9478) + ### 1.80 - 2021-04-01 ##### Additions :tada: From 37130dd873cc4acf33401e804f47d4554ebee8e3 Mon Sep 17 00:00:00 2001 From: ebogo1 Date: Fri, 9 Apr 2021 16:51:10 -0400 Subject: [PATCH 72/76] change deprecation wording --- CHANGES.md | 4 ++-- Source/Core/loadCRN.js | 4 ++-- Source/Core/loadKTX.js | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 4c07765c17..759b3ebfff 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,8 +4,8 @@ ##### Deprecated :hourglass_flowing_sand: -- `loadCRN` was deprecated and will be removed in Cesium 1.82. It will be replaced with support for KTX2.[9478](https://github.com/CesiumGS/cesium/pull/9478) -- `loadKTX` was deprecated and will be removed in Cesium 1.82. It will be replaced with support for KTX2. [9478](https://github.com/CesiumGS/cesium/pull/9478) +- `loadCRN` has been deprecated and will be removed in CesiumJS 1.82. It will be replaced with support for KTX2.[9478](https://github.com/CesiumGS/cesium/pull/9478) +- `loadKTX` has been deprecated and will be removed in CesiumJS 1.82. It will be replaced with support for KTX2. [9478](https://github.com/CesiumGS/cesium/pull/9478) ### 1.80 - 2021-04-01 diff --git a/Source/Core/loadCRN.js b/Source/Core/loadCRN.js index 0d5cbb6823..777c247541 100644 --- a/Source/Core/loadCRN.js +++ b/Source/Core/loadCRN.js @@ -37,12 +37,12 @@ var transcodeTaskProcessor = new TaskProcessor("transcodeCRNToDXT"); * @see {@link https://github.com/BinomialLLC/crunch|crunch DXTc texture compression and transcoding library} * @see {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing} * @see {@link http://wiki.commonjs.org/wiki/Promises/A|CommonJS Promises/A} - * @deprecated This function has been deprecated and will be removed in Cesium 1.82. + * @deprecated This function has been deprecated and will be removed in CesiumJS 1.82. */ function loadCRN(resourceOrUrlOrBuffer) { deprecationWarning( "loadCRN", - "loadCRN is deprecated and will be removed in Cesium 1.82." + "loadCRN is deprecated and will be removed in CesiumJS 1.82." ); //>>includeStart('debug', pragmas.debug); if (!defined(resourceOrUrlOrBuffer)) { diff --git a/Source/Core/loadKTX.js b/Source/Core/loadKTX.js index e8374c4ea2..4013c776cb 100644 --- a/Source/Core/loadKTX.js +++ b/Source/Core/loadKTX.js @@ -58,12 +58,12 @@ import deprecationWarning from "./deprecationWarning.js"; * @see {@link https://www.khronos.org/opengles/sdk/tools/KTX/file_format_spec/|KTX file format} * @see {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing} * @see {@link http://wiki.commonjs.org/wiki/Promises/A|CommonJS Promises/A} - * @deprecated This function has been deprecated and will be removed in Cesium 1.82. + * @deprecated This function has been deprecated and will be removed in CesiumJS 1.82. */ function loadKTX(resourceOrUrlOrBuffer) { deprecationWarning( "loadKTX", - "loadKTX is deprecated and will be removed in Cesium 1.82." + "loadKTX is deprecated and will be removed in CesiumJS 1.82." ); //>>includeStart('debug', pragmas.debug); Check.defined("resourceOrUrlOrBuffer", resourceOrUrlOrBuffer); From c73c5e2ef25871c69206502e8dba008810675111 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Fri, 9 Apr 2021 18:04:00 -0400 Subject: [PATCH 73/76] Update CHANGES.md --- CHANGES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 759b3ebfff..1c618d818c 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,7 +4,7 @@ ##### Deprecated :hourglass_flowing_sand: -- `loadCRN` has been deprecated and will be removed in CesiumJS 1.82. It will be replaced with support for KTX2.[9478](https://github.com/CesiumGS/cesium/pull/9478) +- `loadCRN` has been deprecated and will be removed in CesiumJS 1.82. It will be replaced with support for KTX2. [9478](https://github.com/CesiumGS/cesium/pull/9478) - `loadKTX` has been deprecated and will be removed in CesiumJS 1.82. It will be replaced with support for KTX2. [9478](https://github.com/CesiumGS/cesium/pull/9478) ### 1.80 - 2021-04-01 From fadf3e23e0e1721d734868778ea0d66c3ff9e9ca Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Fri, 9 Apr 2021 18:04:13 -0400 Subject: [PATCH 74/76] Update CHANGES.md --- CHANGES.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 1c618d818c..6e83c5be31 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,8 +4,8 @@ ##### Deprecated :hourglass_flowing_sand: -- `loadCRN` has been deprecated and will be removed in CesiumJS 1.82. It will be replaced with support for KTX2. [9478](https://github.com/CesiumGS/cesium/pull/9478) -- `loadKTX` has been deprecated and will be removed in CesiumJS 1.82. It will be replaced with support for KTX2. [9478](https://github.com/CesiumGS/cesium/pull/9478) +- `loadCRN` has been deprecated and will be removed in CesiumJS 1.82. It will be replaced with support for KTX2. [#9478](https://github.com/CesiumGS/cesium/pull/9478) +- `loadKTX` has been deprecated and will be removed in CesiumJS 1.82. It will be replaced with support for KTX2. [#9478](https://github.com/CesiumGS/cesium/pull/9478) ### 1.80 - 2021-04-01 From a0e36cf47e7e8917dbc621201ef6e56eaaa4e943 Mon Sep 17 00:00:00 2001 From: Ethan Wong Date: Mon, 12 Apr 2021 13:41:00 +0800 Subject: [PATCH 75/76] Use existing function to determine if clipping is enabled in model update --- Source/Scene/Model.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Source/Scene/Model.js b/Source/Scene/Model.js index 77e49cad21..43d471d6d3 100644 --- a/Source/Scene/Model.js +++ b/Source/Scene/Model.js @@ -5516,10 +5516,6 @@ Model.prototype.update = function (frameState) { // Regenerate shaders if ClippingPlaneCollection state changed or it was removed var clippingPlanes = this._clippingPlanes; var currentClippingPlanesState = 0; - var useClippingPlanes = - defined(clippingPlanes) && - clippingPlanes.enabled && - clippingPlanes.length > 0; // If defined, use the reference matrix to transform miscellaneous properties like // clipping planes and IBL instead of the modelMatrix. This is so that when @@ -5527,7 +5523,7 @@ Model.prototype.update = function (frameState) { // a common reference (such as the root). var referenceMatrix = defaultValue(this.referenceMatrix, modelMatrix); - if (useClippingPlanes) { + if (isClippingEnabled(this)) { var clippingPlanesMatrix = scratchClippingPlanesMatrix; clippingPlanesMatrix = Matrix4.multiply( context.uniformState.view3D, From 3a6b5003943b1b20bcc369650035867b2b1cc3c8 Mon Sep 17 00:00:00 2001 From: Ethan Wong Date: Mon, 12 Apr 2021 15:58:14 +0800 Subject: [PATCH 76/76] Updte CONTRIBUTOS.md --- CONTRIBUTORS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index b66f45ce29..4a58d6d4b2 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -285,3 +285,4 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for details on how to contribute to Cesiu - [Will Jadkowski](https://github.com/willjad) - [Mac Clayton](https://github.com/mclayton7) - [Ben Murphy](https://github.com/littlemurph) +- [Ethan Wong](https://github.com/GetToSet)