From da3df651a9c895c42ca2894720bf05133832ea8a Mon Sep 17 00:00:00 2001 From: Subhajit Date: Thu, 3 Feb 2022 10:06:02 +0530 Subject: [PATCH 01/40] Add parameter range validations in GregorianDate --- Source/Core/GregorianDate.js | 39 +++++++++++++++++++++++++++--------- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/Source/Core/GregorianDate.js b/Source/Core/GregorianDate.js index cd4c53c1d1a3..fe5366609879 100644 --- a/Source/Core/GregorianDate.js +++ b/Source/Core/GregorianDate.js @@ -16,15 +16,17 @@ * @see JulianDate#toGregorianDate */ function GregorianDate( - year, - month, - day, - hour, - minute, - second, - millisecond, - isLeapSecond + year = 1, + month = 1, + day = 1, + hour = 0, + minute = 0, + second = 0, + millisecond = 0, + isLeapSecond = false ) { + validateRange(); + /** * Gets or sets the year as a whole number. * @type {Number} @@ -56,7 +58,7 @@ function GregorianDate( */ this.second = second; /** - * Gets or sets the millisecond of the second as a floating point number with range [0.0, 1000.0). + * Gets or sets the millisecond of the second as a floating point number with range [0.0, 1000.0]). * @type {Number} */ this.millisecond = millisecond; @@ -65,5 +67,24 @@ function GregorianDate( * @type {Boolean} */ this.isLeapSecond = isLeapSecond; + + function validateRange() { + if (year < 1 || year > 9999) + throw "Year parameter represent an invalid date"; + if (month < 1 || month > 12) + throw "Month parameter represent an invalid date"; + if (day < 1 || day > 31) throw "Day parameter represent an invalid date"; + if (hour < 0 || hour > 23) throw "Hour parameter represent an invalid date"; + if (minute < 0 || minute > 59) + throw "Combination of Minute and IsLeapSecond parameters represent an invalid date"; + if ( + second < 0 || + (second > 59 && !isLeapSecond) || + (second > 60 && isLeapSecond) + ) + throw "Second parameter represent an invalid date"; + if (millisecond < 0 || millisecond >= 1000) + throw "Millisecond parameter represent an invalid date"; + } } export default GregorianDate; From 0587e51b8bae70a57315acbe8172051736695c78 Mon Sep 17 00:00:00 2001 From: Subhajit Date: Fri, 4 Feb 2022 10:21:39 +0530 Subject: [PATCH 02/40] Validate for proper date --- Source/Core/GregorianDate.js | 54 ++++++++++++++++++++++++++++++------ 1 file changed, 45 insertions(+), 9 deletions(-) diff --git a/Source/Core/GregorianDate.js b/Source/Core/GregorianDate.js index fe5366609879..c49746d46e83 100644 --- a/Source/Core/GregorianDate.js +++ b/Source/Core/GregorianDate.js @@ -26,6 +26,7 @@ function GregorianDate( isLeapSecond = false ) { validateRange(); + validateDate(); /** * Gets or sets the year as a whole number. @@ -69,22 +70,57 @@ function GregorianDate( this.isLeapSecond = isLeapSecond; function validateRange() { - if (year < 1 || year > 9999) + const maxYear = 9999; + const minYear = 1; + const minMonth = 1; + const maxMonth = 12; + const maxDay = 31; + const minDay = 1; + const minHour = 0; + const maxHour = 23; + const maxMinute = 59; + const minMinute = 0; + const minSecond = 0; + const maxSecond = 59; + const minMilisecond = 0; + const excludedMaxMilisecond = 1000; + + if (year < minYear || year > maxYear) throw "Year parameter represent an invalid date"; - if (month < 1 || month > 12) + if (month < minMonth || month > maxMonth) throw "Month parameter represent an invalid date"; - if (day < 1 || day > 31) throw "Day parameter represent an invalid date"; - if (hour < 0 || hour > 23) throw "Hour parameter represent an invalid date"; - if (minute < 0 || minute > 59) + if (day < minDay || day > maxDay) + throw "Day parameter represent an invalid date"; + if (hour < minHour || hour > maxHour) + throw "Hour parameter represent an invalid date"; + if (minute < minMinute || minute > maxMinute) throw "Combination of Minute and IsLeapSecond parameters represent an invalid date"; if ( - second < 0 || - (second > 59 && !isLeapSecond) || - (second > 60 && isLeapSecond) + second < minSecond || + (second > maxSecond && !isLeapSecond) || + (second > maxSecond + 1 && isLeapSecond) ) throw "Second parameter represent an invalid date"; - if (millisecond < 0 || millisecond >= 1000) + if (millisecond < minMilisecond || millisecond >= excludedMaxMilisecond) throw "Millisecond parameter represent an invalid date"; } + + // Javascript date object supports only dates greater than 1901. Thus validating with custom logic + function validateDate() { + const minNumberOfDaysInMonth = 28; + const monthsWith31Days = [1, 3, 5, 7, 8, 10, 12]; + + if ( + month === 2 && + ((year % 4 === 0 && day > minNumberOfDaysInMonth + 1) || + (year % 4 !== 0 && day > minNumberOfDaysInMonth)) + ) + throw "Year, Month and Day represents invalid date"; + else if ( + monthsWith31Days.indexOf(month) === -1 && + day > minNumberOfDaysInMonth + 2 + ) + throw "Month and Day represents invalid date"; + } } export default GregorianDate; From 870c5d8e575709c13116a9f48b1b73eef6399d6e Mon Sep 17 00:00:00 2001 From: Subhajit Date: Fri, 4 Feb 2022 15:33:23 +0530 Subject: [PATCH 03/40] Add name in Contributers.md --- CONTRIBUTORS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 081b8ed297b1..0d3190cd3c03 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -313,3 +313,4 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for details on how to contribute to Cesiu - [Jon Beniston](https://github.com/srcejon) - [Ugnius Malukas](https://github.com/ugnelis) - [Justin Peter](https://github.com/themagicnacho) +- [Subhajit Saha](https://github.com/subhajits) From 7091079d4751e0f4c3277198b4f59dc22c797dfc Mon Sep 17 00:00:00 2001 From: Subhajit Date: Fri, 4 Feb 2022 16:25:59 +0530 Subject: [PATCH 04/40] Remove default parameters and check for invalid value --- Source/Core/GregorianDate.js | 51 +++++++++++++++++++++++++----------- 1 file changed, 35 insertions(+), 16 deletions(-) diff --git a/Source/Core/GregorianDate.js b/Source/Core/GregorianDate.js index c49746d46e83..82f961be3530 100644 --- a/Source/Core/GregorianDate.js +++ b/Source/Core/GregorianDate.js @@ -16,15 +16,30 @@ * @see JulianDate#toGregorianDate */ function GregorianDate( - year = 1, - month = 1, - day = 1, - hour = 0, - minute = 0, - second = 0, - millisecond = 0, - isLeapSecond = false + year, + month, + day, + hour, + minute, + second, + millisecond, + isLeapSecond ) { + const minYear = 1; + const minMonth = 1; + const minDay = 1; + const minHour = 0; + const minMinute = 0; + const minSecond = 0; + const minMillisecond = 0; + + if (!year) year = minYear; + if (!month) month = minMonth; + if (!day) day = minDay; + if (!hour) hour = minHour; + if (!minute) minute = minMinute; + if (!second) second = minSecond; + if (!millisecond) millisecond = minMillisecond; validateRange(); validateDate(); @@ -71,20 +86,24 @@ function GregorianDate( function validateRange() { const maxYear = 9999; - const minYear = 1; - const minMonth = 1; const maxMonth = 12; const maxDay = 31; - const minDay = 1; - const minHour = 0; const maxHour = 23; const maxMinute = 59; - const minMinute = 0; - const minSecond = 0; const maxSecond = 59; - const minMilisecond = 0; const excludedMaxMilisecond = 1000; + if ( + isNaN(year) || + isNaN(month) || + isNaN(day) || + isNaN(hour) || + isNaN(minute) || + isNaN(second) || + isNaN(millisecond) + ) + throw "Invalid value passed in parameter"; + if (year < minYear || year > maxYear) throw "Year parameter represent an invalid date"; if (month < minMonth || month > maxMonth) @@ -101,7 +120,7 @@ function GregorianDate( (second > maxSecond + 1 && isLeapSecond) ) throw "Second parameter represent an invalid date"; - if (millisecond < minMilisecond || millisecond >= excludedMaxMilisecond) + if (millisecond < minMillisecond || millisecond >= excludedMaxMilisecond) throw "Millisecond parameter represent an invalid date"; } From 460aaf547ee692e6b689223f5794703b558d3424 Mon Sep 17 00:00:00 2001 From: Subhajit Date: Sat, 5 Feb 2022 02:09:58 +0530 Subject: [PATCH 05/40] Add testcases and refactor code --- Source/Core/GregorianDate.js | 104 ++++++----- Specs/Core/GregorianDateSpec.js | 300 ++++++++++++++++++++++++++++++++ 2 files changed, 356 insertions(+), 48 deletions(-) create mode 100644 Specs/Core/GregorianDateSpec.js diff --git a/Source/Core/GregorianDate.js b/Source/Core/GregorianDate.js index 82f961be3530..eec1adcf5e27 100644 --- a/Source/Core/GregorianDate.js +++ b/Source/Core/GregorianDate.js @@ -1,3 +1,7 @@ +import defaultValue from "./defaultValue.js"; +import DeveloperError from "./DeveloperError.js"; +import isLeapYear from "./isLeapYear.js"; + /** * Represents a Gregorian date in a more precise format than the JavaScript Date object. * In addition to submillisecond precision, this object can also represent leap seconds. @@ -25,21 +29,21 @@ function GregorianDate( millisecond, isLeapSecond ) { - const minYear = 1; - const minMonth = 1; - const minDay = 1; - const minHour = 0; - const minMinute = 0; - const minSecond = 0; - const minMillisecond = 0; + const minimumYear = 1; + const minimumMonth = 1; + const minimumDay = 1; + const minimumHour = 0; + const minimumMinute = 0; + const minimumSecond = 0; + const minimumMillisecond = 0; - if (!year) year = minYear; - if (!month) month = minMonth; - if (!day) day = minDay; - if (!hour) hour = minHour; - if (!minute) minute = minMinute; - if (!second) second = minSecond; - if (!millisecond) millisecond = minMillisecond; + year = defaultValue(year, minimumYear); + month = defaultValue(month, minimumMonth); + day = defaultValue(day, minimumDay); + hour = defaultValue(hour, minimumHour); + minute = defaultValue(minute, minimumMinute); + second = defaultValue(second, minimumSecond); + millisecond = defaultValue(millisecond, minimumMillisecond); validateRange(); validateDate(); @@ -85,13 +89,13 @@ function GregorianDate( this.isLeapSecond = isLeapSecond; function validateRange() { - const maxYear = 9999; - const maxMonth = 12; - const maxDay = 31; - const maxHour = 23; - const maxMinute = 59; - const maxSecond = 59; - const excludedMaxMilisecond = 1000; + const maximumYear = 9999; + const maximumMonth = 12; + const maximumDay = 31; + const maximumHour = 23; + const maximumMinute = 59; + const maximumSecond = 59; + const excludedMaximumMilisecond = 1000; if ( isNaN(year) || @@ -102,44 +106,48 @@ function GregorianDate( isNaN(second) || isNaN(millisecond) ) - throw "Invalid value passed in parameter"; + throw new DeveloperError("Invalid value passed in parameter"); - if (year < minYear || year > maxYear) - throw "Year parameter represent an invalid date"; - if (month < minMonth || month > maxMonth) - throw "Month parameter represent an invalid date"; - if (day < minDay || day > maxDay) - throw "Day parameter represent an invalid date"; - if (hour < minHour || hour > maxHour) - throw "Hour parameter represent an invalid date"; - if (minute < minMinute || minute > maxMinute) - throw "Combination of Minute and IsLeapSecond parameters represent an invalid date"; + if (year < minimumYear || year > maximumYear) + throw new DeveloperError("Year parameter represent an invalid date"); + if (month < minimumMonth || month > maximumMonth) + throw new DeveloperError("Month parameter represent an invalid date"); + if (day < minimumDay || day > maximumDay) + throw new DeveloperError("Day parameter represent an invalid date"); + if (hour < minimumHour || hour > maximumHour) + throw new DeveloperError("Hour parameter represent an invalid date"); + if (minute < minimumMinute || minute > maximumMinute) + throw new DeveloperError( + "Combination of Minute and IsLeapSecond parameters represent an invalid date" + ); + if ( + second < minimumSecond || + (second > maximumSecond && !isLeapSecond) || + (second > maximumSecond + 1 && isLeapSecond) + ) + throw new DeveloperError("Second parameter represent an invalid date"); if ( - second < minSecond || - (second > maxSecond && !isLeapSecond) || - (second > maxSecond + 1 && isLeapSecond) + millisecond < minimumMillisecond || + millisecond >= excludedMaximumMilisecond ) - throw "Second parameter represent an invalid date"; - if (millisecond < minMillisecond || millisecond >= excludedMaxMilisecond) - throw "Millisecond parameter represent an invalid date"; + throw new DeveloperError( + "Millisecond parameter represent an invalid date" + ); } // Javascript date object supports only dates greater than 1901. Thus validating with custom logic function validateDate() { - const minNumberOfDaysInMonth = 28; - const monthsWith31Days = [1, 3, 5, 7, 8, 10, 12]; + const minimumDaysInMonth = 28; + const daysInYear = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; if ( month === 2 && - ((year % 4 === 0 && day > minNumberOfDaysInMonth + 1) || - (year % 4 !== 0 && day > minNumberOfDaysInMonth)) - ) - throw "Year, Month and Day represents invalid date"; - else if ( - monthsWith31Days.indexOf(month) === -1 && - day > minNumberOfDaysInMonth + 2 + ((isLeapYear(year) && day > minimumDaysInMonth + 1) || + (!isLeapYear(year) && day > minimumDaysInMonth)) ) - throw "Month and Day represents invalid date"; + throw new DeveloperError("Year, Month and Day represents invalid date"); + else if (month !== 2 && day > daysInYear[month - 1]) + throw new DeveloperError("Month and Day represents invalid date"); } } export default GregorianDate; diff --git a/Specs/Core/GregorianDateSpec.js b/Specs/Core/GregorianDateSpec.js new file mode 100644 index 000000000000..b0c61451e6a7 --- /dev/null +++ b/Specs/Core/GregorianDateSpec.js @@ -0,0 +1,300 @@ +import { GregorianDate } from "../../Source/Cesium.js"; + +describe("Core/GregorianDate", function () { + describe("Positive scenarios", function () { + it("Constructs any valid date", function () { + const validDate = new GregorianDate(2022, 2, 4, 23, 54, 0, 999.9, false); + expect(validDate.year).toEqual(2022); + expect(validDate.month).toEqual(2); + expect(validDate.day).toEqual(4); + expect(validDate.hour).toEqual(23); + expect(validDate.minute).toEqual(54); + expect(validDate.second).toEqual(0); + expect(validDate.millisecond).toEqual(999.9); + expect(validDate.isLeapSecond).toBeFalsy(); + }); + + it("Constructs valid leap year date", function () { + const validDate = new GregorianDate(2024, 2, 29, 23, 54, 0, 999.9, false); + expect(validDate.year).toEqual(2024); + expect(validDate.month).toEqual(2); + expect(validDate.day).toEqual(29); + expect(validDate.hour).toEqual(23); + expect(validDate.minute).toEqual(54); + expect(validDate.second).toEqual(0); + expect(validDate.millisecond).toEqual(999.9); + expect(validDate.isLeapSecond).toBeFalsy(); + }); + + it("Constructs the minimum date when no parameters are passed", function () { + const minimumDate = new GregorianDate(); + expect(minimumDate.year).toEqual(1); + expect(minimumDate.month).toEqual(1); + expect(minimumDate.day).toEqual(1); + expect(minimumDate.hour).toEqual(0); + expect(minimumDate.minute).toEqual(0); + expect(minimumDate.second).toEqual(0); + expect(minimumDate.millisecond).toEqual(0); + expect(minimumDate.isLeapSecond).toBeFalsy(); + }); + + it("Constructs valid dates for edge cases of days", function () { + expect(function () { + new GregorianDate(2022, 1, 31); + }).not.toThrowDeveloperError(); + expect(function () { + new GregorianDate(2022, 3, 31); + }).not.toThrowDeveloperError(); + expect(function () { + new GregorianDate(2022, 4, 30); + }).not.toThrowDeveloperError(); + expect(function () { + new GregorianDate(2022, 5, 31); + }).not.toThrowDeveloperError(); + expect(function () { + new GregorianDate(2022, 6, 30); + }).not.toThrowDeveloperError(); + expect(function () { + new GregorianDate(2022, 7, 31); + }).not.toThrowDeveloperError(); + expect(function () { + new GregorianDate(2022, 8, 31); + }).not.toThrowDeveloperError(); + expect(function () { + new GregorianDate(2022, 9, 30); + }).not.toThrowDeveloperError(); + expect(function () { + new GregorianDate(2022, 10, 31); + }).not.toThrowDeveloperError(); + expect(function () { + new GregorianDate(2022, 11, 30); + }).not.toThrowDeveloperError(); + expect(function () { + new GregorianDate(2022, 12, 31); + }).not.toThrowDeveloperError(); + }); + + it("Constructs the minimum possible date of the year when only year parameter is passed", function () { + const minimumDate = new GregorianDate(2022); + expect(minimumDate.year).toEqual(2022); + expect(minimumDate.month).toEqual(1); + expect(minimumDate.day).toEqual(1); + expect(minimumDate.hour).toEqual(0); + expect(minimumDate.minute).toEqual(0); + expect(minimumDate.second).toEqual(0); + expect(minimumDate.millisecond).toEqual(0); + expect(minimumDate.isLeapSecond).toBeFalsy(); + }); + + it("Constructs the minimum possible day of the month when only year and month parameters are passed", function () { + const minimumDate = new GregorianDate(2022, 2); + expect(minimumDate.year).toEqual(2022); + expect(minimumDate.month).toEqual(2); + expect(minimumDate.day).toEqual(1); + expect(minimumDate.hour).toEqual(0); + expect(minimumDate.minute).toEqual(0); + expect(minimumDate.second).toEqual(0); + expect(minimumDate.millisecond).toEqual(0); + expect(minimumDate.isLeapSecond).toBeFalsy(); + }); + + it("Constructs the minimum possible time of the day when only year, month and day parameters are passed", function () { + const minimumDate = new GregorianDate(2022, 2, 28); + expect(minimumDate.year).toEqual(2022); + expect(minimumDate.month).toEqual(2); + expect(minimumDate.day).toEqual(28); + expect(minimumDate.hour).toEqual(0); + expect(minimumDate.minute).toEqual(0); + expect(minimumDate.second).toEqual(0); + expect(minimumDate.millisecond).toEqual(0); + expect(minimumDate.isLeapSecond).toBeFalsy(); + }); + + it("Constructs the minimum possible time of the day when only year, month, day and hour parameters are passed", function () { + const minimumDate = new GregorianDate(2022, 2, 28, 10); + expect(minimumDate.year).toEqual(2022); + expect(minimumDate.month).toEqual(2); + expect(minimumDate.day).toEqual(28); + expect(minimumDate.hour).toEqual(10); + expect(minimumDate.minute).toEqual(0); + expect(minimumDate.second).toEqual(0); + expect(minimumDate.millisecond).toEqual(0); + expect(minimumDate.isLeapSecond).toBeFalsy(); + }); + + it("Constructs the minimum possible time of the day when only year, month, day, hour and minutes parameters are passed", function () { + const minimumDate = new GregorianDate(2022, 2, 28, 10, 59); + expect(minimumDate.year).toEqual(2022); + expect(minimumDate.month).toEqual(2); + expect(minimumDate.day).toEqual(28); + expect(minimumDate.hour).toEqual(10); + expect(minimumDate.minute).toEqual(59); + expect(minimumDate.second).toEqual(0); + expect(minimumDate.millisecond).toEqual(0); + expect(minimumDate.isLeapSecond).toBeFalsy(); + }); + + it("Constructs the minimum possible time of the day when only year, month, day, hour, minutes and seconds parameters are passed", function () { + const minimumDate = new GregorianDate(2022, 2, 28, 10, 59, 59); + expect(minimumDate.year).toEqual(2022); + expect(minimumDate.month).toEqual(2); + expect(minimumDate.day).toEqual(28); + expect(minimumDate.hour).toEqual(10); + expect(minimumDate.minute).toEqual(59); + expect(minimumDate.second).toEqual(59); + expect(minimumDate.millisecond).toEqual(0); + expect(minimumDate.isLeapSecond).toBeFalsy(); + }); + + it("Constructs the date with leap second", function () { + const minimumDate = new GregorianDate(2022, 2, 28, 10, 59, 60, 100, true); + expect(minimumDate.year).toEqual(2022); + expect(minimumDate.month).toEqual(2); + expect(minimumDate.day).toEqual(28); + expect(minimumDate.hour).toEqual(10); + expect(minimumDate.minute).toEqual(59); + expect(minimumDate.second).toEqual(60); + expect(minimumDate.millisecond).toEqual(100); + expect(minimumDate.isLeapSecond).toBeTruthy(); + }); + }); + describe("Negative scenarios", function () { + it("Should throw error if invalid year is passed", function () { + expect(function () { + new GregorianDate(-1, 2, 4, 23, 54, 0, 999.9, false); + }).toThrowDeveloperError(); + expect(function () { + new GregorianDate(0, 2, 4, 23, 54, 0, 999.9, false); + }).toThrowDeveloperError(); + expect(function () { + new GregorianDate(10000, 2, 4, 23, 54, 0, 999.9, false); + }).toThrowDeveloperError(); + }); + + it("Should throw error if invalid month is passed", function () { + expect(function () { + new GregorianDate(2022, -1, 4, 23, 54, 0, 999.9, false); + }).toThrowDeveloperError(); + expect(function () { + new GregorianDate(2022, 0, 4, 23, 54, 0, 999.9, false); + }).toThrowDeveloperError(); + expect(function () { + new GregorianDate(2022, 13, 4, 23, 54, 0, 999.9, false); + }).toThrowDeveloperError(); + }); + + it("Should throw error if invalid day is passed", function () { + expect(function () { + new GregorianDate(2022, 12, -10, 23, 54, 0, 999.9, false); + }).toThrowDeveloperError(); + expect(function () { + new GregorianDate(2022, 12, 0, 23, 54, 0, 999.9, false); + }).toThrowDeveloperError(); + expect(function () { + new GregorianDate(2022, 12, 32, 23, 54, 0, 999.9, false); + }).toThrowDeveloperError(); + expect(function () { + new GregorianDate(2020, 2, 30, 23, 54, 0, 999.9, false); + }).toThrowDeveloperError(); + expect(function () { + new GregorianDate(2022, 2, 29, 23, 54, 0, 999.9, false); + }).toThrowDeveloperError(); + expect(function () { + new GregorianDate(2022, 11, 31, 23, 54, 0, 999.9, false); + }).toThrowDeveloperError(); + expect(function () { + new GregorianDate(2022, 4, 31, 23, 54, 0, 999.9, false); + }).toThrowDeveloperError(); + expect(function () { + new GregorianDate(2022, 6, 31, 23, 54, 0, 999.9, false); + }).toThrowDeveloperError(); + expect(function () { + new GregorianDate(2022, 9, 31, 23, 54, 0, 999.9, false); + }).toThrowDeveloperError(); + }); + + it("Should throw error if invalid hours is passed", function () { + expect(function () { + new GregorianDate(2022, 2, 4, -10, 54, 0, 999.9, false); + }).toThrowDeveloperError(); + expect(function () { + new GregorianDate(2022, 2, 4, -1, 54, 0, 999.9, false); + }).toThrowDeveloperError(); + expect(function () { + new GregorianDate(2022, 2, 4, 24, 54, 0, 999.9, false); + }).toThrowDeveloperError(); + expect(function () { + new GregorianDate(2022, 11, 4, 100, 54, 0, 999.9, false); + }).toThrowDeveloperError(); + }); + + it("Should throw error if invalid minute is passed", function () { + expect(function () { + new GregorianDate(2022, 2, 4, 15, -1, 0, 999.9, false); + }).toThrowDeveloperError(); + expect(function () { + new GregorianDate(2022, 2, 4, 15, 60, 0, 999.9, false); + }).toThrowDeveloperError(); + expect(function () { + new GregorianDate(2022, 2, 4, 7, 60, 0, 999.9, true); + }).toThrowDeveloperError(); + expect(function () { + new GregorianDate(2022, 11, 4, 0, -1, 0, 999.9, true); + }).toThrowDeveloperError(); + }); + + it("Should throw error if invalid second is passed", function () { + expect(function () { + new GregorianDate(2022, 2, 4, 15, 1, -1, 999.9, false); + }).toThrowDeveloperError(); + expect(function () { + new GregorianDate(2022, 2, 4, 15, 59, 60, 999.9, false); + }).toThrowDeveloperError(); + expect(function () { + new GregorianDate(2022, 2, 4, 7, 59, 61, 999.9, true); + }).toThrowDeveloperError(); + expect(function () { + new GregorianDate(2022, 11, 4, 0, 1, -1, 999.9, true); + }).toThrowDeveloperError(); + }); + + it("Should throw error if invalid millisecond is passed", function () { + expect(function () { + new GregorianDate(2022, 2, 4, 15, 1, 0, -1, false); + }).toThrowDeveloperError(); + expect(function () { + new GregorianDate(2022, 2, 4, 15, 59, 59, 1000, false); + }).toThrowDeveloperError(); + expect(function () { + new GregorianDate(2022, 2, 4, 7, 59, 60, 1000, true); + }).toThrowDeveloperError(); + expect(function () { + new GregorianDate(2022, 11, 4, 0, 1, 0, -12, true); + }).toThrowDeveloperError(); + }); + + it("Should throw error if invalid type is passed", function () { + expect(function () { + new GregorianDate("2022A", 2, 4, 15, 1, 0, 999.9, false); + }).toThrowDeveloperError(); + expect(function () { + new GregorianDate(2022, "Two", 4, 15, 1, 0, 999.9, false); + }).toThrowDeveloperError(); + expect(function () { + new GregorianDate(2022, 2, "F0UR", 15, 1, 0, 999.9, false); + }).toThrowDeveloperError(); + expect(function () { + new GregorianDate(2022, 2, 4, "15th", 1, 0, 999.9, false); + }).toThrowDeveloperError(); + expect(function () { + new GregorianDate(2022, 2, 4, 15, "1st", 0, 999.9, false); + }).toThrowDeveloperError(); + expect(function () { + new GregorianDate(2022, 2, 4, 15, 1, "Zero", 999.9, false); + }).toThrowDeveloperError(); + expect(function () { + new GregorianDate(2022, 2, 4, 15, 1, 0, "999,9O", false); + }).toThrowDeveloperError(); + }); + }); +}); From 8a24a4921d9a28017c80cecbfcdf1144ff51db25 Mon Sep 17 00:00:00 2001 From: Subhajit Date: Sat, 5 Feb 2022 18:51:26 +0530 Subject: [PATCH 06/40] Refactor code to incorporate review comments --- Source/Core/GregorianDate.js | 83 ++++++++++++++-------------- Specs/Core/GregorianDateSpec.js | 98 ++++++++++++++++----------------- 2 files changed, 89 insertions(+), 92 deletions(-) diff --git a/Source/Core/GregorianDate.js b/Source/Core/GregorianDate.js index eec1adcf5e27..6d4e29ab1cb7 100644 --- a/Source/Core/GregorianDate.js +++ b/Source/Core/GregorianDate.js @@ -1,3 +1,4 @@ +import Check from "./Check.js"; import defaultValue from "./defaultValue.js"; import DeveloperError from "./DeveloperError.js"; import isLeapYear from "./isLeapYear.js"; @@ -44,8 +45,11 @@ function GregorianDate( minute = defaultValue(minute, minimumMinute); second = defaultValue(second, minimumSecond); millisecond = defaultValue(millisecond, minimumMillisecond); + isLeapSecond = defaultValue(isLeapSecond, false); + //>>includeStart('debug', pragmas.debug); validateRange(); validateDate(); + //>>includeEnd('debug'); /** * Gets or sets the year as a whole number. @@ -97,56 +101,49 @@ function GregorianDate( const maximumSecond = 59; const excludedMaximumMilisecond = 1000; - if ( - isNaN(year) || - isNaN(month) || - isNaN(day) || - isNaN(hour) || - isNaN(minute) || - isNaN(second) || - isNaN(millisecond) - ) - throw new DeveloperError("Invalid value passed in parameter"); + Check.typeOf.number.greaterThanOrEquals("Year", year, minimumYear); + Check.typeOf.number.lessThanOrEquals("Year", year, maximumYear); - if (year < minimumYear || year > maximumYear) - throw new DeveloperError("Year parameter represent an invalid date"); - if (month < minimumMonth || month > maximumMonth) - throw new DeveloperError("Month parameter represent an invalid date"); - if (day < minimumDay || day > maximumDay) - throw new DeveloperError("Day parameter represent an invalid date"); - if (hour < minimumHour || hour > maximumHour) - throw new DeveloperError("Hour parameter represent an invalid date"); - if (minute < minimumMinute || minute > maximumMinute) - throw new DeveloperError( - "Combination of Minute and IsLeapSecond parameters represent an invalid date" - ); - if ( - second < minimumSecond || - (second > maximumSecond && !isLeapSecond) || - (second > maximumSecond + 1 && isLeapSecond) - ) - throw new DeveloperError("Second parameter represent an invalid date"); - if ( - millisecond < minimumMillisecond || - millisecond >= excludedMaximumMilisecond - ) - throw new DeveloperError( - "Millisecond parameter represent an invalid date" - ); + Check.typeOf.number.greaterThanOrEquals("Month", month, minimumMonth); + Check.typeOf.number.lessThanOrEquals("Month", month, maximumMonth); + + Check.typeOf.number.greaterThanOrEquals("Day", day, minimumDay); + Check.typeOf.number.lessThanOrEquals("Day", day, maximumDay); + + Check.typeOf.number.greaterThanOrEquals("Hour", hour, minimumHour); + Check.typeOf.number.lessThanOrEquals("Hour", hour, maximumHour); + + Check.typeOf.number.greaterThanOrEquals("Minute", minute, minimumMinute); + Check.typeOf.number.lessThanOrEquals("Minute", minute, maximumMinute); + + Check.typeOf.bool("IsLeapSecond", isLeapSecond); + + Check.typeOf.number.greaterThanOrEquals("Second", second, minimumSecond); + Check.typeOf.number.lessThanOrEquals( + "Second", + second, + isLeapSecond ? maximumSecond + 1 : maximumSecond + ); + + Check.typeOf.number.greaterThanOrEquals( + "Millisecond", + millisecond, + minimumMillisecond + ); + Check.typeOf.number.lessThan( + "Millisecond", + millisecond, + excludedMaximumMilisecond + ); } // Javascript date object supports only dates greater than 1901. Thus validating with custom logic function validateDate() { - const minimumDaysInMonth = 28; const daysInYear = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; - if ( - month === 2 && - ((isLeapYear(year) && day > minimumDaysInMonth + 1) || - (!isLeapYear(year) && day > minimumDaysInMonth)) - ) - throw new DeveloperError("Year, Month and Day represents invalid date"); - else if (month !== 2 && day > daysInYear[month - 1]) + if (month === 2 && isLeapYear(year)) daysInYear[month - 1] += 1; + + if (day > daysInYear[month - 1]) throw new DeveloperError("Month and Day represents invalid date"); } } diff --git a/Specs/Core/GregorianDateSpec.js b/Specs/Core/GregorianDateSpec.js index b0c61451e6a7..3dd35bc086ca 100644 --- a/Specs/Core/GregorianDateSpec.js +++ b/Specs/Core/GregorianDateSpec.js @@ -40,37 +40,37 @@ describe("Core/GregorianDate", function () { it("Constructs valid dates for edge cases of days", function () { expect(function () { - new GregorianDate(2022, 1, 31); + return new GregorianDate(2022, 1, 31); }).not.toThrowDeveloperError(); expect(function () { - new GregorianDate(2022, 3, 31); + return new GregorianDate(2022, 3, 31); }).not.toThrowDeveloperError(); expect(function () { - new GregorianDate(2022, 4, 30); + return new GregorianDate(2022, 4, 30); }).not.toThrowDeveloperError(); expect(function () { - new GregorianDate(2022, 5, 31); + return new GregorianDate(2022, 5, 31); }).not.toThrowDeveloperError(); expect(function () { - new GregorianDate(2022, 6, 30); + return new GregorianDate(2022, 6, 30); }).not.toThrowDeveloperError(); expect(function () { - new GregorianDate(2022, 7, 31); + return new GregorianDate(2022, 7, 31); }).not.toThrowDeveloperError(); expect(function () { - new GregorianDate(2022, 8, 31); + return new GregorianDate(2022, 8, 31); }).not.toThrowDeveloperError(); expect(function () { - new GregorianDate(2022, 9, 30); + return new GregorianDate(2022, 9, 30); }).not.toThrowDeveloperError(); expect(function () { - new GregorianDate(2022, 10, 31); + return new GregorianDate(2022, 10, 31); }).not.toThrowDeveloperError(); expect(function () { - new GregorianDate(2022, 11, 30); + return new GregorianDate(2022, 11, 30); }).not.toThrowDeveloperError(); expect(function () { - new GregorianDate(2022, 12, 31); + return new GregorianDate(2022, 12, 31); }).not.toThrowDeveloperError(); }); @@ -161,139 +161,139 @@ describe("Core/GregorianDate", function () { describe("Negative scenarios", function () { it("Should throw error if invalid year is passed", function () { expect(function () { - new GregorianDate(-1, 2, 4, 23, 54, 0, 999.9, false); + return new GregorianDate(-1, 2, 4, 23, 54, 0, 999.9, false); }).toThrowDeveloperError(); expect(function () { - new GregorianDate(0, 2, 4, 23, 54, 0, 999.9, false); + return new GregorianDate(0, 2, 4, 23, 54, 0, 999.9, false); }).toThrowDeveloperError(); expect(function () { - new GregorianDate(10000, 2, 4, 23, 54, 0, 999.9, false); + return new GregorianDate(10000, 2, 4, 23, 54, 0, 999.9, false); }).toThrowDeveloperError(); }); it("Should throw error if invalid month is passed", function () { expect(function () { - new GregorianDate(2022, -1, 4, 23, 54, 0, 999.9, false); + return new GregorianDate(2022, -1, 4, 23, 54, 0, 999.9, false); }).toThrowDeveloperError(); expect(function () { - new GregorianDate(2022, 0, 4, 23, 54, 0, 999.9, false); + return new GregorianDate(2022, 0, 4, 23, 54, 0, 999.9, false); }).toThrowDeveloperError(); expect(function () { - new GregorianDate(2022, 13, 4, 23, 54, 0, 999.9, false); + return new GregorianDate(2022, 13, 4, 23, 54, 0, 999.9, false); }).toThrowDeveloperError(); }); it("Should throw error if invalid day is passed", function () { expect(function () { - new GregorianDate(2022, 12, -10, 23, 54, 0, 999.9, false); + return new GregorianDate(2022, 12, -10, 23, 54, 0, 999.9, false); }).toThrowDeveloperError(); expect(function () { - new GregorianDate(2022, 12, 0, 23, 54, 0, 999.9, false); + return new GregorianDate(2022, 12, 0, 23, 54, 0, 999.9, false); }).toThrowDeveloperError(); expect(function () { - new GregorianDate(2022, 12, 32, 23, 54, 0, 999.9, false); + return new GregorianDate(2022, 12, 32, 23, 54, 0, 999.9, false); }).toThrowDeveloperError(); expect(function () { - new GregorianDate(2020, 2, 30, 23, 54, 0, 999.9, false); + return new GregorianDate(2020, 2, 30, 23, 54, 0, 999.9, false); }).toThrowDeveloperError(); expect(function () { - new GregorianDate(2022, 2, 29, 23, 54, 0, 999.9, false); + return new GregorianDate(2022, 2, 29, 23, 54, 0, 999.9, false); }).toThrowDeveloperError(); expect(function () { - new GregorianDate(2022, 11, 31, 23, 54, 0, 999.9, false); + return new GregorianDate(2022, 11, 31, 23, 54, 0, 999.9, false); }).toThrowDeveloperError(); expect(function () { - new GregorianDate(2022, 4, 31, 23, 54, 0, 999.9, false); + return new GregorianDate(2022, 4, 31, 23, 54, 0, 999.9, false); }).toThrowDeveloperError(); expect(function () { - new GregorianDate(2022, 6, 31, 23, 54, 0, 999.9, false); + return new GregorianDate(2022, 6, 31, 23, 54, 0, 999.9, false); }).toThrowDeveloperError(); expect(function () { - new GregorianDate(2022, 9, 31, 23, 54, 0, 999.9, false); + return new GregorianDate(2022, 9, 31, 23, 54, 0, 999.9, false); }).toThrowDeveloperError(); }); it("Should throw error if invalid hours is passed", function () { expect(function () { - new GregorianDate(2022, 2, 4, -10, 54, 0, 999.9, false); + return new GregorianDate(2022, 2, 4, -10, 54, 0, 999.9, false); }).toThrowDeveloperError(); expect(function () { - new GregorianDate(2022, 2, 4, -1, 54, 0, 999.9, false); + return new GregorianDate(2022, 2, 4, -1, 54, 0, 999.9, false); }).toThrowDeveloperError(); expect(function () { - new GregorianDate(2022, 2, 4, 24, 54, 0, 999.9, false); + return new GregorianDate(2022, 2, 4, 24, 54, 0, 999.9, false); }).toThrowDeveloperError(); expect(function () { - new GregorianDate(2022, 11, 4, 100, 54, 0, 999.9, false); + return new GregorianDate(2022, 11, 4, 100, 54, 0, 999.9, false); }).toThrowDeveloperError(); }); it("Should throw error if invalid minute is passed", function () { expect(function () { - new GregorianDate(2022, 2, 4, 15, -1, 0, 999.9, false); + return new GregorianDate(2022, 2, 4, 15, -1, 0, 999.9, false); }).toThrowDeveloperError(); expect(function () { - new GregorianDate(2022, 2, 4, 15, 60, 0, 999.9, false); + return new GregorianDate(2022, 2, 4, 15, 60, 0, 999.9, false); }).toThrowDeveloperError(); expect(function () { - new GregorianDate(2022, 2, 4, 7, 60, 0, 999.9, true); + return new GregorianDate(2022, 2, 4, 7, 60, 0, 999.9, true); }).toThrowDeveloperError(); expect(function () { - new GregorianDate(2022, 11, 4, 0, -1, 0, 999.9, true); + return new GregorianDate(2022, 11, 4, 0, -1, 0, 999.9, true); }).toThrowDeveloperError(); }); it("Should throw error if invalid second is passed", function () { expect(function () { - new GregorianDate(2022, 2, 4, 15, 1, -1, 999.9, false); + return new GregorianDate(2022, 2, 4, 15, 1, -1, 999.9, false); }).toThrowDeveloperError(); expect(function () { - new GregorianDate(2022, 2, 4, 15, 59, 60, 999.9, false); + return new GregorianDate(2022, 2, 4, 15, 59, 60, 999.9, false); }).toThrowDeveloperError(); expect(function () { - new GregorianDate(2022, 2, 4, 7, 59, 61, 999.9, true); + return new GregorianDate(2022, 2, 4, 7, 59, 61, 999.9, true); }).toThrowDeveloperError(); expect(function () { - new GregorianDate(2022, 11, 4, 0, 1, -1, 999.9, true); + return new GregorianDate(2022, 11, 4, 0, 1, -1, 999.9, true); }).toThrowDeveloperError(); }); it("Should throw error if invalid millisecond is passed", function () { expect(function () { - new GregorianDate(2022, 2, 4, 15, 1, 0, -1, false); + return new GregorianDate(2022, 2, 4, 15, 1, 0, -1, false); }).toThrowDeveloperError(); expect(function () { - new GregorianDate(2022, 2, 4, 15, 59, 59, 1000, false); + return new GregorianDate(2022, 2, 4, 15, 59, 59, 1000, false); }).toThrowDeveloperError(); expect(function () { - new GregorianDate(2022, 2, 4, 7, 59, 60, 1000, true); + return new GregorianDate(2022, 2, 4, 7, 59, 60, 1000, true); }).toThrowDeveloperError(); expect(function () { - new GregorianDate(2022, 11, 4, 0, 1, 0, -12, true); + return new GregorianDate(2022, 11, 4, 0, 1, 0, -12, true); }).toThrowDeveloperError(); }); it("Should throw error if invalid type is passed", function () { expect(function () { - new GregorianDate("2022A", 2, 4, 15, 1, 0, 999.9, false); + return new GregorianDate("2022A", 2, 4, 15, 1, 0, 999.9, false); }).toThrowDeveloperError(); expect(function () { - new GregorianDate(2022, "Two", 4, 15, 1, 0, 999.9, false); + return new GregorianDate(2022, "Two", 4, 15, 1, 0, 999.9, false); }).toThrowDeveloperError(); expect(function () { - new GregorianDate(2022, 2, "F0UR", 15, 1, 0, 999.9, false); + return new GregorianDate(2022, 2, "F0UR", 15, 1, 0, 999.9, false); }).toThrowDeveloperError(); expect(function () { - new GregorianDate(2022, 2, 4, "15th", 1, 0, 999.9, false); + return new GregorianDate(2022, 2, 4, "15th", 1, 0, 999.9, false); }).toThrowDeveloperError(); expect(function () { - new GregorianDate(2022, 2, 4, 15, "1st", 0, 999.9, false); + return new GregorianDate(2022, 2, 4, 15, "1st", 0, 999.9, false); }).toThrowDeveloperError(); expect(function () { - new GregorianDate(2022, 2, 4, 15, 1, "Zero", 999.9, false); + return new GregorianDate(2022, 2, 4, 15, 1, "Zero", 999.9, false); }).toThrowDeveloperError(); expect(function () { - new GregorianDate(2022, 2, 4, 15, 1, 0, "999,9O", false); + return new GregorianDate(2022, 2, 4, 15, 1, 0, "999,9O", false); }).toThrowDeveloperError(); }); }); From 27eb4480e997ac55262963d482b69b5991edb43d Mon Sep 17 00:00:00 2001 From: Subhajit Date: Sat, 12 Feb 2022 20:51:06 +0530 Subject: [PATCH 07/40] Update CHANGES.md --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index 0d133a8847ba..73606febbb85 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -13,6 +13,7 @@ ##### Fixes :wrench: - Fixed a bug where updating `ModelExperimental`'s model matrix would not update its bounding sphere. [#10078](https://github.com/CesiumGS/cesium/pull/10078) +- Fixed a bug where `GregorianDate` constructor would not validate the input parameters for valid date. Also default date is set as 0001-01-01 00:00:00:0000. [#10075](https://github.com/CesiumGS/cesium/pull/10075) ### 1.90 - 2022-02-01 From 45acd1428ba450c0caf904c128f97555fc297a90 Mon Sep 17 00:00:00 2001 From: Subhajit Date: Sat, 12 Feb 2022 21:08:18 +0530 Subject: [PATCH 08/40] Refactor test cases for more readability --- Specs/Core/GregorianDateSpec.js | 42 +++++++++++++++++++++------------ 1 file changed, 27 insertions(+), 15 deletions(-) diff --git a/Specs/Core/GregorianDateSpec.js b/Specs/Core/GregorianDateSpec.js index 3dd35bc086ca..9973d13bb225 100644 --- a/Specs/Core/GregorianDateSpec.js +++ b/Specs/Core/GregorianDateSpec.js @@ -1,7 +1,7 @@ import { GregorianDate } from "../../Source/Cesium.js"; describe("Core/GregorianDate", function () { - describe("Positive scenarios", function () { + describe("With valid parameters", function () { it("Constructs any valid date", function () { const validDate = new GregorianDate(2022, 2, 4, 23, 54, 0, 999.9, false); expect(validDate.year).toEqual(2022); @@ -11,7 +11,7 @@ describe("Core/GregorianDate", function () { expect(validDate.minute).toEqual(54); expect(validDate.second).toEqual(0); expect(validDate.millisecond).toEqual(999.9); - expect(validDate.isLeapSecond).toBeFalsy(); + expect(validDate.isLeapSecond).toBe(false); }); it("Constructs valid leap year date", function () { @@ -23,7 +23,7 @@ describe("Core/GregorianDate", function () { expect(validDate.minute).toEqual(54); expect(validDate.second).toEqual(0); expect(validDate.millisecond).toEqual(999.9); - expect(validDate.isLeapSecond).toBeFalsy(); + expect(validDate.isLeapSecond).toBe(false); }); it("Constructs the minimum date when no parameters are passed", function () { @@ -35,13 +35,19 @@ describe("Core/GregorianDate", function () { expect(minimumDate.minute).toEqual(0); expect(minimumDate.second).toEqual(0); expect(minimumDate.millisecond).toEqual(0); - expect(minimumDate.isLeapSecond).toBeFalsy(); + expect(minimumDate.isLeapSecond).toBe(false); }); it("Constructs valid dates for edge cases of days", function () { expect(function () { return new GregorianDate(2022, 1, 31); }).not.toThrowDeveloperError(); + expect(function () { + return new GregorianDate(2000, 2, 28); + }).not.toThrowDeveloperError(); + expect(function () { + return new GregorianDate(2020, 2, 29); + }).not.toThrowDeveloperError(); expect(function () { return new GregorianDate(2022, 3, 31); }).not.toThrowDeveloperError(); @@ -83,7 +89,7 @@ describe("Core/GregorianDate", function () { expect(minimumDate.minute).toEqual(0); expect(minimumDate.second).toEqual(0); expect(minimumDate.millisecond).toEqual(0); - expect(minimumDate.isLeapSecond).toBeFalsy(); + expect(minimumDate.isLeapSecond).toBe(false); }); it("Constructs the minimum possible day of the month when only year and month parameters are passed", function () { @@ -95,7 +101,7 @@ describe("Core/GregorianDate", function () { expect(minimumDate.minute).toEqual(0); expect(minimumDate.second).toEqual(0); expect(minimumDate.millisecond).toEqual(0); - expect(minimumDate.isLeapSecond).toBeFalsy(); + expect(minimumDate.isLeapSecond).toBe(false); }); it("Constructs the minimum possible time of the day when only year, month and day parameters are passed", function () { @@ -107,7 +113,7 @@ describe("Core/GregorianDate", function () { expect(minimumDate.minute).toEqual(0); expect(minimumDate.second).toEqual(0); expect(minimumDate.millisecond).toEqual(0); - expect(minimumDate.isLeapSecond).toBeFalsy(); + expect(minimumDate.isLeapSecond).toBe(false); }); it("Constructs the minimum possible time of the day when only year, month, day and hour parameters are passed", function () { @@ -119,7 +125,7 @@ describe("Core/GregorianDate", function () { expect(minimumDate.minute).toEqual(0); expect(minimumDate.second).toEqual(0); expect(minimumDate.millisecond).toEqual(0); - expect(minimumDate.isLeapSecond).toBeFalsy(); + expect(minimumDate.isLeapSecond).toBe(false); }); it("Constructs the minimum possible time of the day when only year, month, day, hour and minutes parameters are passed", function () { @@ -131,7 +137,7 @@ describe("Core/GregorianDate", function () { expect(minimumDate.minute).toEqual(59); expect(minimumDate.second).toEqual(0); expect(minimumDate.millisecond).toEqual(0); - expect(minimumDate.isLeapSecond).toBeFalsy(); + expect(minimumDate.isLeapSecond).toBe(false); }); it("Constructs the minimum possible time of the day when only year, month, day, hour, minutes and seconds parameters are passed", function () { @@ -143,7 +149,7 @@ describe("Core/GregorianDate", function () { expect(minimumDate.minute).toEqual(59); expect(minimumDate.second).toEqual(59); expect(minimumDate.millisecond).toEqual(0); - expect(minimumDate.isLeapSecond).toBeFalsy(); + expect(minimumDate.isLeapSecond).toBe(false); }); it("Constructs the date with leap second", function () { @@ -155,10 +161,10 @@ describe("Core/GregorianDate", function () { expect(minimumDate.minute).toEqual(59); expect(minimumDate.second).toEqual(60); expect(minimumDate.millisecond).toEqual(100); - expect(minimumDate.isLeapSecond).toBeTruthy(); + expect(minimumDate.isLeapSecond).toBe(true); }); }); - describe("Negative scenarios", function () { + describe("With invalid parameters", function () { it("Should throw error if invalid year is passed", function () { expect(function () { return new GregorianDate(-1, 2, 4, 23, 54, 0, 999.9, false); @@ -193,12 +199,12 @@ describe("Core/GregorianDate", function () { expect(function () { return new GregorianDate(2022, 12, 32, 23, 54, 0, 999.9, false); }).toThrowDeveloperError(); + }); + + it("Should throw error if day is out of range for month", function () { expect(function () { return new GregorianDate(2020, 2, 30, 23, 54, 0, 999.9, false); }).toThrowDeveloperError(); - expect(function () { - return new GregorianDate(2022, 2, 29, 23, 54, 0, 999.9, false); - }).toThrowDeveloperError(); expect(function () { return new GregorianDate(2022, 11, 31, 23, 54, 0, 999.9, false); }).toThrowDeveloperError(); @@ -213,6 +219,12 @@ describe("Core/GregorianDate", function () { }).toThrowDeveloperError(); }); + it("Should throw error if leap day is given for non-leap year", function () { + expect(function () { + return new GregorianDate(2022, 2, 29, 23, 54, 0, 999.9, false); + }).toThrowDeveloperError(); + }); + it("Should throw error if invalid hours is passed", function () { expect(function () { return new GregorianDate(2022, 2, 4, -10, 54, 0, 999.9, false); From d697a23a6c9f46d334c06b4a8aba83b8532cac12 Mon Sep 17 00:00:00 2001 From: Subhajit Date: Fri, 18 Feb 2022 00:08:34 +0530 Subject: [PATCH 09/40] Update changes.md --- CHANGES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 73606febbb85..b2d4e8982cfd 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -13,7 +13,7 @@ ##### Fixes :wrench: - Fixed a bug where updating `ModelExperimental`'s model matrix would not update its bounding sphere. [#10078](https://github.com/CesiumGS/cesium/pull/10078) -- Fixed a bug where `GregorianDate` constructor would not validate the input parameters for valid date. Also default date is set as 0001-01-01 00:00:00:0000. [#10075](https://github.com/CesiumGS/cesium/pull/10075) +- Fixed a bug where `GregorianDate` constructor would not validate the input parameters for valid date. [#10075](https://github.com/CesiumGS/cesium/pull/10075) ### 1.90 - 2022-02-01 From 5f3539302ec0fd5591f86d330682d15f517a91f4 Mon Sep 17 00:00:00 2001 From: Peter Gagliardi Date: Thu, 21 Dec 2023 13:51:55 -0500 Subject: [PATCH 10/40] Enable dynamicScreenSpaceError, document parameters --- .../engine/Source/Scene/Cesium3DTileset.js | 66 ++++++++++++------- 1 file changed, 41 insertions(+), 25 deletions(-) diff --git a/packages/engine/Source/Scene/Cesium3DTileset.js b/packages/engine/Source/Scene/Cesium3DTileset.js index 0f5f3b56bcb9..dc55bef4019f 100644 --- a/packages/engine/Source/Scene/Cesium3DTileset.js +++ b/packages/engine/Source/Scene/Cesium3DTileset.js @@ -75,10 +75,10 @@ import Cesium3DTilesetSkipTraversal from "./Cesium3DTilesetSkipTraversal.js"; * @property {boolean} [preloadWhenHidden=false] Preload tiles when tileset.show is false. Loads tiles as if the tileset is visible but does not render them. * @property {boolean} [preloadFlightDestinations=true] Optimization option. Preload tiles at the camera's flight destination while the camera is in flight. * @property {boolean} [preferLeaves=false] Optimization option. Prefer loading of leaves first. - * @property {boolean} [dynamicScreenSpaceError=false] Optimization option. Reduce the screen space error for tiles that are further away from the camera. - * @property {number} [dynamicScreenSpaceErrorDensity=0.00278] Density used to adjust the dynamic screen space error, similar to fog density. - * @property {number} [dynamicScreenSpaceErrorFactor=4.0] A factor used to increase the computed dynamic screen space error. - * @property {number} [dynamicScreenSpaceErrorHeightFalloff=0.25] A ratio of the tileset's height at which the density starts to falloff. + * @property {boolean} [dynamicScreenSpaceError=true] Optimization option. Reduce the screen space error for tiles that are further away from the camera so that lower resolution tiles are used. + * @property {number} [dynamicScreenSpaceErrorDensity=0.00278] Similar to fog density, this value determines how far away from the camera that screen space error falls off. Larger values will cause tiles closer to the camera to be affected by dynamicScreenSpaceError. + * @property {number} [dynamicScreenSpaceErrorFactor=4.0] The maximum screen space error adjustment in px to apply to a tile for tiles far away on the horizon. Increase this value to make the SSE adjustment stronger, which in turn will select lower LOD tiles near the horizon. + * @property {number} [dynamicScreenSpaceErrorHeightFalloff=0.25] A ratio of the tileset's height above which the density decreases for dynamic screen space error. This way, dynamicScreenSpaceError has the strongest effect when the camera is close to street level. * @property {number} [progressiveResolutionHeightFraction=0.3] Optimization option. If between (0.0, 0.5], tiles at or above the screen space error for the reduced screen resolution of progressiveResolutionHeightFraction*screenHeight will be prioritized first. This can help get a quick layer of tiles down while full resolution tiles continue to load. * @property {boolean} [foveatedScreenSpaceError=true] Optimization option. Prioritize loading tiles in the center of the screen by temporarily raising the screen space error for tiles around the edge of the screen. Screen space error returns to normal once all the tiles in the center of the screen as determined by the {@link Cesium3DTileset#foveatedConeSize} are loaded. * @property {number} [foveatedConeSize=0.1] Optimization option. Used when {@link Cesium3DTileset#foveatedScreenSpaceError} is true to control the cone size that determines which tiles are deferred. Tiles that are inside this cone are loaded immediately. Tiles outside the cone are potentially deferred based on how far outside the cone they are and their screen space error. This is controlled by {@link Cesium3DTileset#foveatedInterpolationCallback} and {@link Cesium3DTileset#foveatedMinimumScreenSpaceErrorRelaxation}. Setting this to 0.0 means the cone will be the line formed by the camera position and its view direction. Setting this to 1.0 means the cone encompasses the entire field of view of the camera, disabling the effect. @@ -361,18 +361,18 @@ function Cesium3DTileset(options) { this._pass = undefined; // Cesium3DTilePass /** - * Optimization option. Whether the tileset should refine based on a dynamic screen space error. Tiles that are further - * away will be rendered with lower detail than closer tiles. This improves performance by rendering fewer - * tiles and making less requests, but may result in a slight drop in visual quality for tiles in the distance. - * The algorithm is biased towards "street views" where the camera is close to the ground plane of the tileset and looking - * at the horizon. In addition results are more accurate for tightly fitting bounding volumes like box and region. + * Optimization option. Whether the tileset should adjust the screen space error for tiles on the horizon. Tiles that + * are far away will be adjusted to render at lower detail than tiles closer at the camera. This improves performance + * by loading and rendering fewer tiles, but may result in a slight drop in visual quality in the distance. This + * optimization is strongest when the camera is close to the ground plane of the tileset and looking at the horizon. + * Furthermore, the results are more accurate for tightly fitting bounding volumes like box and region. * * @type {boolean} * @default false */ this.dynamicScreenSpaceError = defaultValue( options.dynamicScreenSpaceError, - false + true ); /** @@ -416,18 +416,22 @@ function Cesium3DTileset(options) { this.foveatedTimeDelay = defaultValue(options.foveatedTimeDelay, 0.2); /** - * A scalar that determines the density used to adjust the dynamic screen space error, similar to {@link Fog}. Increasing this - * value has the effect of increasing the maximum screen space error for all tiles, but in a non-linear fashion. - * The error starts at 0.0 and increases exponentially until a midpoint is reached, and then approaches 1.0 asymptotically. - * This has the effect of keeping high detail in the closer tiles and lower detail in the further tiles, with all tiles - * beyond a certain distance all roughly having an error of 1.0. + * A parameter that controls how far away from the camera screen space error (SSE) falls off when + * dynamicScreenSpaceError is enabled. This is similar to the density parameter of + * {@link Fog}. This value must be non-negative. + *

+ * dynamicScreenSpaceError causes the tile SSE to fall off with distance from the camera like a bell + * curve. At the camera, no adjustment is made (peak of bell curve). For tiles further away, the SSE is reduced + * up to a limit for tiles on the horizon. The maximum reduction is determined by the + * dynamicScreenSpaceErrorFactor. Reducing the SSE allows the rendering algorithm to select + * lower-resolution tiles. + *

*

- * The dynamic error is in the range [0.0, 1.0) and is multiplied by dynamicScreenSpaceErrorFactor to produce the - * final dynamic error. This dynamic error is then subtracted from the tile's actual screen space error. + * Increasing the density makes the bell curve narrower. This means that the SSE adjustment will start happening + * closer to the camera. This is analgous to moving fog closer to the camera. *

*

- * Increasing dynamicScreenSpaceErrorDensity has the effect of moving the error midpoint closer to the camera. - * It is analogous to moving fog closer to the camera. + * When the density is 0, no tiles will have their SSE adjusted. *

* * @type {number} @@ -436,8 +440,19 @@ function Cesium3DTileset(options) { this.dynamicScreenSpaceErrorDensity = 0.00278; /** - * A factor used to increase the screen space error of tiles for dynamic screen space error. As this value increases less tiles - * are requested for rendering and tiles in the distance will have lower detail. If set to zero, the feature will be disabled. + * The maximum screen space error (SSE) reduction when {@link Cesium3DTileset#dynamicScreenSpaceError} is used. The + * value must be non-negative. + *

+ * Increasing the SSE factor increases the maximum amount to reduce a tile's SSE. This maximum reduction happens + * for tiles near the horizon. For tiles closer to the camera, the SSE adjustment can be small as 0 (at the camera). + * In between, the SSE falls off like a bell curve. See {@link Cesium3DTileset#dynamicScreenSpaceErrorDensity} + *

+ *

+ * When the SSE factor is set to 0, the adjustment will be 0 for all tiles. + *

+ * A factor used to increase the screen space error of tiles for dynamic screen space error. As this value increases + * less tiles are requested for rendering and tiles in the distance will have lower detail. If set to zero, the + * feature will be disabled. * * @type {number} * @default 4.0 @@ -445,9 +460,9 @@ function Cesium3DTileset(options) { this.dynamicScreenSpaceErrorFactor = 4.0; /** - * A ratio of the tileset's height at which the density starts to falloff. If the camera is below this height the - * full computed density is applied, otherwise the density falls off. This has the effect of higher density at - * street level views. + * A ratio of the tileset's height above which the effects of {@link Cesium3DTileset#dynamicScreenSpaceError} begin + * to fall off. This determines what is considered "street level" for the tileset. The dynamic screen space error + * optimization is intended for when the camera is at street level and pointed at the horizon. *

* Valid values are between 0.0 and 1.0. *

@@ -457,7 +472,8 @@ function Cesium3DTileset(options) { */ this.dynamicScreenSpaceErrorHeightFalloff = 0.25; - this._dynamicScreenSpaceErrorComputedDensity = 0.0; // Updated based on the camera position and direction + // Updated based on the camera position and direction + this._dynamicScreenSpaceErrorComputedDensity = 0.0; /** * Determines whether the tileset casts or receives shadows from light sources. From 642c7a376fd7daf1e53d3e9c974107369e1ff6b8 Mon Sep 17 00:00:00 2001 From: Peter Gagliardi Date: Thu, 21 Dec 2023 13:59:03 -0500 Subject: [PATCH 11/40] Update CHANGES.md --- CHANGES.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index ba1296873239..28bc2d05be98 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,6 +4,10 @@ #### @cesium/engine +##### Additions :tada: + +- The `Cesium3DTileset.dynamicScreenSpaceError` optimization is now enabled by default, as this improves performance for street-level horizon views. TODO: PR link + ##### Fixes :wrench: - Changes the default `RequestScheduler.maximumRequestsPerServer` from 6 to 18. This should improve performance on HTTP/2 servers and above [#11627](https://github.com/CesiumGS/cesium/issues/11627) From 5a0679d4bd166b839f12f7570ce17bd0d6375aa8 Mon Sep 17 00:00:00 2001 From: Peter Gagliardi Date: Thu, 21 Dec 2023 15:32:25 -0500 Subject: [PATCH 12/40] Add missing defaultValue() calls --- .../engine/Source/Scene/Cesium3DTileset.js | 30 ++++++++++++++----- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/packages/engine/Source/Scene/Cesium3DTileset.js b/packages/engine/Source/Scene/Cesium3DTileset.js index dc55bef4019f..cfa854fd7f7d 100644 --- a/packages/engine/Source/Scene/Cesium3DTileset.js +++ b/packages/engine/Source/Scene/Cesium3DTileset.js @@ -17,6 +17,7 @@ import IonResource from "../Core/IonResource.js"; import JulianDate from "../Core/JulianDate.js"; import ManagedArray from "../Core/ManagedArray.js"; import CesiumMath from "../Core/Math.js"; +import Matrix3 from "../Core/Matrix3.js"; import Matrix4 from "../Core/Matrix4.js"; import Resource from "../Core/Resource.js"; import RuntimeError from "../Core/RuntimeError.js"; @@ -437,7 +438,10 @@ function Cesium3DTileset(options) { * @type {number} * @default 0.00278 */ - this.dynamicScreenSpaceErrorDensity = 0.00278; + this.dynamicScreenSpaceErrorDensity = defaultValue( + options.dynamicScreenSpaceErrorDensity, + 0.00278 + ); /** * The maximum screen space error (SSE) reduction when {@link Cesium3DTileset#dynamicScreenSpaceError} is used. The @@ -457,7 +461,10 @@ function Cesium3DTileset(options) { * @type {number} * @default 4.0 */ - this.dynamicScreenSpaceErrorFactor = 4.0; + this.dynamicScreenSpaceErrorFactor = defaultValue( + options.dynamicScreenSpaceErrorFactor, + 4.0 + ); /** * A ratio of the tileset's height above which the effects of {@link Cesium3DTileset#dynamicScreenSpaceError} begin @@ -470,7 +477,10 @@ function Cesium3DTileset(options) { * @type {number} * @default 0.25 */ - this.dynamicScreenSpaceErrorHeightFalloff = 0.25; + this.dynamicScreenSpaceErrorHeightFalloff = defaultValue( + options.dynamicScreenSpaceErrorHeightFalloff, + 0.25 + ); // Updated based on the camera position and direction this._dynamicScreenSpaceErrorComputedDensity = 0.0; @@ -2282,6 +2292,7 @@ const scratchMatrix = new Matrix4(); const scratchCenter = new Cartesian3(); const scratchPosition = new Cartesian3(); const scratchDirection = new Cartesian3(); +const scratchHalfHeight = new Cartesian3(); /** * @private @@ -2346,10 +2357,15 @@ function updateDynamicScreenSpaceError(tileset, frameState) { direction = Cartesian3.normalize(direction, direction); height = positionLocal.z; if (tileBoundingVolume instanceof TileOrientedBoundingBox) { - // Assuming z-up, the last component stores the half-height of the box - const boxHeight = root._header.boundingVolume.box[11]; - minimumHeight = centerLocal.z - boxHeight; - maximumHeight = centerLocal.z + boxHeight; + // Assuming z-up, the last column + const halfHeightVector = Matrix3.getColumn( + boundingVolume.halfAxes, + 2, + scratchHalfHeight + ); + const halfHeight = Cartesian3.magnitude(halfHeightVector); + minimumHeight = centerLocal.z - halfHeight; + maximumHeight = centerLocal.z + halfHeight; } else if (tileBoundingVolume instanceof TileBoundingSphere) { const radius = boundingVolume.radius; minimumHeight = centerLocal.z - radius; From fde33ff77c03f3353d43f5759d685d089d8752b4 Mon Sep 17 00:00:00 2001 From: Peter Gagliardi Date: Thu, 21 Dec 2023 15:34:11 -0500 Subject: [PATCH 13/40] Turn down dynamicSSE to fix picking tests --- packages/engine/Specs/Scene/PickingSpec.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/engine/Specs/Scene/PickingSpec.js b/packages/engine/Specs/Scene/PickingSpec.js index 4474051a0adc..6e9c2198945c 100644 --- a/packages/engine/Specs/Scene/PickingSpec.js +++ b/packages/engine/Specs/Scene/PickingSpec.js @@ -134,6 +134,11 @@ describe( function createTileset(url) { const options = { maximumScreenSpaceError: 0, + // Since unit tests render to a tiny canvas, tileset rendering is + // extremely sensitive to changes in screen space error. Turn the + // dynamic screen space error down significantly so we don't cull tiles + // needed for the unit tests. + dynamicScreenSpaceErrorFactor: 0.0001, }; return Cesium3DTilesTester.loadTileset(scene, url, options).then( function (tileset) { From b309cbff2f57b05268bc6973e06a531521d06940 Mon Sep 17 00:00:00 2001 From: Peter Gagliardi Date: Thu, 21 Dec 2023 15:44:53 -0500 Subject: [PATCH 14/40] Turn off dynamic SSE instead --- packages/engine/Specs/Scene/PickingSpec.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/engine/Specs/Scene/PickingSpec.js b/packages/engine/Specs/Scene/PickingSpec.js index 6e9c2198945c..af868c13044f 100644 --- a/packages/engine/Specs/Scene/PickingSpec.js +++ b/packages/engine/Specs/Scene/PickingSpec.js @@ -134,11 +134,10 @@ describe( function createTileset(url) { const options = { maximumScreenSpaceError: 0, - // Since unit tests render to a tiny canvas, tileset rendering is - // extremely sensitive to changes in screen space error. Turn the - // dynamic screen space error down significantly so we don't cull tiles - // needed for the unit tests. - dynamicScreenSpaceErrorFactor: 0.0001, + // The camera is zoomed pretty far out for these tests, so + // turn off dynamicScreenSpaceError so tiles don't get culled + // unintentionally. + dynamicScreenSpaceError: false, }; return Cesium3DTilesTester.loadTileset(scene, url, options).then( function (tileset) { From 6dad6f6d911aad06e2403197b95bdd58d091dd7e Mon Sep 17 00:00:00 2001 From: Peter Gagliardi Date: Thu, 21 Dec 2023 16:20:06 -0500 Subject: [PATCH 15/40] Fix inspector dynamicScreenSpaceErrorDensity slider --- .../Cesium3DTilesInspectorViewModel.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/widgets/Source/Cesium3DTilesInspector/Cesium3DTilesInspectorViewModel.js b/packages/widgets/Source/Cesium3DTilesInspector/Cesium3DTilesInspectorViewModel.js index 42ec352db523..b19aed8e7b08 100644 --- a/packages/widgets/Source/Cesium3DTilesInspector/Cesium3DTilesInspectorViewModel.js +++ b/packages/widgets/Source/Cesium3DTilesInspector/Cesium3DTilesInspectorViewModel.js @@ -740,7 +740,11 @@ function Cesium3DTilesInspectorViewModel(scene, performanceContainer) { return Math.pow(dynamicScreenSpaceErrorDensity(), 1 / 6); }, set: function (value) { - dynamicScreenSpaceErrorDensity(Math.pow(value, 6)); + const scaledValue = Math.pow(value, 6); + dynamicScreenSpaceErrorDensity(scaledValue); + if (defined(that._tileset)) { + that._tileset.dynamicScreenSpaceErrorDensity = scaledValue; + } }, }); From 12f226f01999311c59d0cc015e42531e40ff4784 Mon Sep 17 00:00:00 2001 From: Peter Gagliardi Date: Thu, 21 Dec 2023 16:25:18 -0500 Subject: [PATCH 16/40] Update changelog for inspector fix --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index 28bc2d05be98..45d3f7d908dc 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -13,6 +13,7 @@ - Changes the default `RequestScheduler.maximumRequestsPerServer` from 6 to 18. This should improve performance on HTTP/2 servers and above [#11627](https://github.com/CesiumGS/cesium/issues/11627) - Corrected JSDoc and Typescript definitions that marked optional arguments as required in `ImageryProvider` constructor [#11625](https://github.com/CesiumGS/cesium/issues/11625) - The `Quaternion.computeAxis` function created an axis that was `(0,0,0)` for the unit quaternion, and an axis that was `(NaN,NaN,NaN)` for the quaternion `(0,0,0,-1)` (which describes a rotation about 360 degrees). Now, it returns the x-axis `(1,0,0)` in both of these cases. +- Fixed a bug where the 3D Tiles Inspector's `dynamicScreenSpaceErrorDensity` slider did not update the tileset [#6143](https://github.com/CesiumGS/cesium/issues/6143) ### 1.112 - 2023-12-01 From 236f8d6c47dfebaa7bad3367e92cb67a1247b026 Mon Sep 17 00:00:00 2001 From: Peter Gagliardi Date: Thu, 21 Dec 2023 16:54:31 -0500 Subject: [PATCH 17/40] Add PR number to the changelog --- CHANGES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 45d3f7d908dc..ac49f8389ebb 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -6,7 +6,7 @@ ##### Additions :tada: -- The `Cesium3DTileset.dynamicScreenSpaceError` optimization is now enabled by default, as this improves performance for street-level horizon views. TODO: PR link +- The `Cesium3DTileset.dynamicScreenSpaceError` optimization is now enabled by default, as this improves performance for street-level horizon views. [#11718](https://github.com/CesiumGS/cesium/pull/11718) ##### Fixes :wrench: From 03ab5f9fe164aea1de5c3d0c09c7fe9a2cc2274a Mon Sep 17 00:00:00 2001 From: Jared Webber Date: Sat, 30 Dec 2023 11:52:19 -0600 Subject: [PATCH 18/40] create branch, build issues --- CHANGES.md | 13 +------------ packages/engine/Source/Core/GregorianDate.js | 15 +++++++++------ packages/engine/Specs/Core/GregorianDateSpec.js | 2 +- 3 files changed, 11 insertions(+), 19 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 124b763514f8..6d66fb762db5 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -229,7 +229,6 @@ try { - `Cesium3DTileset` constructor parameter `options.url`, `Cesium3DTileset.ready`, and `Cesium3DTileset.readyPromise` have been removed. Use `Cesium3DTileset.fromUrl` instead. - `createOsmBuildings` was removed. Use `createOsmBuildingsAsync` instead. - `Model.fromGltf`, `Model.readyPromise`, and `Model.texturesLoadedPromise` have been removed. Use `Model.fromGltfAsync`, `Model.readyEvent`, `Model.errorEvent`, and `Model.texturesReadyEvent` instead. For example: - ```js try { const model = await Cesium.Model.fromGltfAsync({ @@ -243,7 +242,6 @@ try { console.log(`Failed to load model. ${error}`); } ``` - - `I3SDataProvider` construction parameter `options.url`, `I3SDataProvider.ready`, and `I3SDataProvider.readyPromise` have been removed. Use `I3SDataProvider.fromUrl` instead. - `TimeDynamicPointCloud.readyPromise` was removed. Use `TimeDynamicPointCloud.frameFailed` to track any errors. - `VoxelProvider.ready` and `VoxelProvider.readyPromise` have been removed. @@ -415,7 +413,6 @@ try { - `Cesium3DTileset` constructor parameter `options.url`, `Cesium3DTileset.ready`, and `Cesium3DTileset.readyPromise` were deprecated in CesiumJS 1.104. They will be removed in 1.107. Use `Cesium3DTileset.fromUrl` instead. - `createOsmBuildings` was deprecated in CesiumJS 1.104. It will be removed in 1.107. Use `createOsmBuildingsAsync` instead. - `Model.fromGltf`, `Model.readyPromise`, and `Model.texturesLoadedPromise` were deprecated in CesiumJS 1.104. They will be removed in 1.107. Use `Model.fromGltfAsync`, `Model.readyEvent`, `Model.errorEvent`, and `Model.texturesReadyEvent` instead. For example: - ```js try { const model = await Cesium.Model.fromGltfAsync({ @@ -429,7 +426,6 @@ try { console.log(`Failed to load model. ${error}`); } ``` - - `I3SDataProvider` construction parameter `options.url`, `I3SDataProvider.ready`, and `I3SDataProvider.readyPromise` were deprecated in CesiumJS 1.104. They will be removed in 1.107. Use `I3SDataProvider.fromUrl` instead. - `TimeDynamicPointCloud.readyPromise` was deprecated in CesiumJS 1.104. It will be removed in 1.107. Use `TimeDynamicPointCloud.frameFailed` to track any errors. - `VoxelProvider.ready` and `VoxelProvider.readyPromise` were deprecated in CesiumJS 1.104. They will be removed in 1.107. @@ -482,7 +478,6 @@ try { - WebGL1 is supported. If WebGL2 is not available, CesiumJS will automatically fall back to WebGL1. - In order to work in a WebGL2 context, any custom materials, custom primitives or custom shaders will need to be upgraded to use GLSL 300. - Otherwise to request a WebGL 1 context, set `requestWebgl1` to `true` when providing `ContextOptions` as shown below: - ```js const viewer = new Viewer("cesiumContainer", { contextOptions: { @@ -509,6 +504,7 @@ try { #### Major Announcements :loudspeaker: - Starting with version 1.102, CesiumJS will default to using a WebGL2 context for rendering. WebGL2 is widely supported on all platforms and this change will result in better feature support across devices, especially mobile. + - WebGL1 will still be supported. If WebGL2 is not available, CesiumJS will automatically fall back to WebGL1. - In order to work in a WebGL2 context, any custom materials, custom primitive or custom shaders will need to be upgraded to use GLSL 300. - Otherwise to request a WebGL 1 context, set `requestWebgl1` to `true` when providing `ContextOptions` as shown below: @@ -1890,13 +1886,11 @@ _This is an npm-only release to fix a publishing issue_. - Clipping planes on tilesets now use the root tile's transform, or the root tile's bounding sphere if a transform is not defined. [#7034](https://github.com/CesiumGS/cesium/pull/7034) - This is to make clipping planes' coordinates always relative to the object they're attached to. So if you were positioning the clipping planes as in the example below, this is no longer necessary: - ```javascript clippingPlanes.modelMatrix = Cesium.Transforms.eastNorthUpToFixedFrame( tileset.boundingSphere.center ); ``` - - This also fixes several issues with clipping planes not using the correct transform for tilesets with children. ##### Additions :tada: @@ -2133,7 +2127,6 @@ _This is an npm-only release to fix a publishing issue_. - `BingMapsImageryProvider` is no longer the default base imagery layer. (Bing imagery itself is still the default, however it is provided through Cesium ion) - `BingMapsGeocoderService` is no longer the default geocoding service. - If you wish to continue to use your own Bing API key for imagery and geocoding, you can go back to the old default behavior by constructing the Viewer as follows: - ```javascript Cesium.BingMapsApi.defaultKey = "yourBingKey"; var viewer = new Cesium.Viewer("cesiumContainer", { @@ -2997,13 +2990,11 @@ _This is an npm-only release to fix a publishing issue_. - Added `VRButton` which is a simple, single-button widget that toggles VR mode. It is off by default. To enable the button, set the `vrButton` option to `Viewer` to `true`. Only Cardboard for mobile is supported. More VR devices will be supported when the WebVR API is more stable. - Added `Scene.useWebVR` to switch the scene to use stereoscopic rendering. - Cesium now honors `window.devicePixelRatio` on browsers that support the CSS `imageRendering` attribute. This greatly improves performance on mobile devices and high DPI displays by rendering at the browser-recommended resolution. This also reduces bandwidth usage and increases battery life in these cases. To enable the previous behavior, use the following code: - ```javascript if (Cesium.FeatureDetection.supportsImageRenderingPixelated()) { viewer.resolutionScale = window.devicePixelRatio; } ``` - - `GroundPrimitive` now supports batching geometry for better performance. - Improved compatibility with glTF KHR_binary_glTF and KHR_materials_common extensions - Added `ImageryLayer.getViewableRectangle` to make it easy to get the effective bounds of an imagery layer. @@ -3645,14 +3636,12 @@ _This is an npm-only release to fix a publishing issue_. - CZML changes: - CZML is now versioned using the . scheme. For example, any CZML 1.0 implementation will be able to load any 1. document (with graceful degradation). Major version number increases will be reserved for breaking changes. We fully expect these major version increases to happen, as CZML is still in development, but we wanted to give developers a stable target to work with. - A `"1.0"` version string is required to be on the document packet, which is required to be the first packet in a CZML file. Previously the `document` packet was optional; it is now mandatory. The simplest document packet is: - ``` { "id":"document", "version":"1.0" } ``` - - The `vertexPositions` property has been removed. There is now a `positions` property directly on objects that use it, currently `polyline`, `polygon`, and `wall`. - `cone`, `pyramid`, and `vector` have been removed from the core CZML schema. They are now treated as extensions maintained by Analytical Graphics and have been renamed to `agi_conicSensor`, `agi_customPatternSensor`, and `agi_vector` respectively. - The `orientation` property has been changed to match Cesium convention. To update existing CZML documents, conjugate the quaternion values. diff --git a/packages/engine/Source/Core/GregorianDate.js b/packages/engine/Source/Core/GregorianDate.js index 9e88973c3b63..f8eac25a9bb9 100644 --- a/packages/engine/Source/Core/GregorianDate.js +++ b/packages/engine/Source/Core/GregorianDate.js @@ -3,6 +3,8 @@ import defaultValue from "./defaultValue.js"; import DeveloperError from "./DeveloperError.js"; import isLeapYear from "./isLeapYear.js"; +const DAYS_IN_YEAR = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; + /** * Represents a Gregorian date in a more precise format than the JavaScript Date object. * In addition to submillisecond precision, this object can also represent leap seconds. @@ -15,7 +17,7 @@ import isLeapYear from "./isLeapYear.js"; * @param {number} [hour] The hour as a whole number with range [0, 23]. * @param {number} [minute] The minute of the hour as a whole number with range [0, 59]. * @param {number} [second] The second of the minute as a whole number with range [0, 60], with 60 representing a leap second. - * @param {number} [millisecond] The millisecond of the second as a floating point number with range [0.0, 1000.0). + * @param {number} [millisecond] The millisecond of the second as a floating point number with range [0.0, 1000.0]). * @param {boolean} [isLeapSecond] Whether this time is during a leap second. * * @see JulianDate#toGregorianDate @@ -83,7 +85,7 @@ function GregorianDate( this.second = second; /** * Gets or sets the millisecond of the second as a floating point number with range [0.0, 1000.0]). - * @type {Number} + * @type {number} */ this.millisecond = millisecond; /** @@ -139,12 +141,13 @@ function GregorianDate( // Javascript date object supports only dates greater than 1901. Thus validating with custom logic function validateDate() { - const daysInYear = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; - - if (month === 2 && isLeapYear(year)) daysInYear[month - 1] += 1; + if (month === 2 && isLeapYear(year)) { + DAYS_IN_YEAR[month - 1] += 1; + } - if (day > daysInYear[month - 1]) + if (day > DAYS_IN_YEAR[month - 1]) { throw new DeveloperError("Month and Day represents invalid date"); + } } } export default GregorianDate; diff --git a/packages/engine/Specs/Core/GregorianDateSpec.js b/packages/engine/Specs/Core/GregorianDateSpec.js index 9973d13bb225..e60fad446e95 100644 --- a/packages/engine/Specs/Core/GregorianDateSpec.js +++ b/packages/engine/Specs/Core/GregorianDateSpec.js @@ -1,4 +1,4 @@ -import { GregorianDate } from "../../Source/Cesium.js"; +import { GregorianDate } from "../../Source/Core/Cesium.js"; describe("Core/GregorianDate", function () { describe("With valid parameters", function () { From 76b24109788291b91cb0dfd7c597591e0f70527e Mon Sep 17 00:00:00 2001 From: Jared Webber Date: Sat, 30 Dec 2023 12:43:25 -0600 Subject: [PATCH 19/40] fix tests --- CHANGES.md | 2 -- packages/engine/Source/Core/GregorianDate.js | 11 ++++++----- packages/engine/Specs/Core/GregorianDateSpec.js | 3 ++- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 6d66fb762db5..b587d3ffe4d9 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -504,11 +504,9 @@ try { #### Major Announcements :loudspeaker: - Starting with version 1.102, CesiumJS will default to using a WebGL2 context for rendering. WebGL2 is widely supported on all platforms and this change will result in better feature support across devices, especially mobile. - - WebGL1 will still be supported. If WebGL2 is not available, CesiumJS will automatically fall back to WebGL1. - In order to work in a WebGL2 context, any custom materials, custom primitive or custom shaders will need to be upgraded to use GLSL 300. - Otherwise to request a WebGL 1 context, set `requestWebgl1` to `true` when providing `ContextOptions` as shown below: - ```js const viewer = new Viewer("cesiumContainer", { contextOptions: { diff --git a/packages/engine/Source/Core/GregorianDate.js b/packages/engine/Source/Core/GregorianDate.js index f8eac25a9bb9..7f271c6c2b73 100644 --- a/packages/engine/Source/Core/GregorianDate.js +++ b/packages/engine/Source/Core/GregorianDate.js @@ -3,7 +3,7 @@ import defaultValue from "./defaultValue.js"; import DeveloperError from "./DeveloperError.js"; import isLeapYear from "./isLeapYear.js"; -const DAYS_IN_YEAR = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; +const daysInYear = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; /** * Represents a Gregorian date in a more precise format than the JavaScript Date object. @@ -141,11 +141,12 @@ function GregorianDate( // Javascript date object supports only dates greater than 1901. Thus validating with custom logic function validateDate() { - if (month === 2 && isLeapYear(year)) { - DAYS_IN_YEAR[month - 1] += 1; - } + const daysInMonth = + month === 2 && isLeapYear(year) + ? daysInYear[month - 1] + 1 + : daysInYear[month - 1]; - if (day > DAYS_IN_YEAR[month - 1]) { + if (day > daysInMonth) { throw new DeveloperError("Month and Day represents invalid date"); } } diff --git a/packages/engine/Specs/Core/GregorianDateSpec.js b/packages/engine/Specs/Core/GregorianDateSpec.js index e60fad446e95..9725fc8c275f 100644 --- a/packages/engine/Specs/Core/GregorianDateSpec.js +++ b/packages/engine/Specs/Core/GregorianDateSpec.js @@ -1,4 +1,4 @@ -import { GregorianDate } from "../../Source/Core/Cesium.js"; +import { GregorianDate } from "../../index.js"; describe("Core/GregorianDate", function () { describe("With valid parameters", function () { @@ -164,6 +164,7 @@ describe("Core/GregorianDate", function () { expect(minimumDate.isLeapSecond).toBe(true); }); }); + describe("With invalid parameters", function () { it("Should throw error if invalid year is passed", function () { expect(function () { From 3eee526df20ff3fe9fa473025affb50926d921df Mon Sep 17 00:00:00 2001 From: Jared Webber Date: Sat, 30 Dec 2023 12:49:43 -0600 Subject: [PATCH 20/40] exlude 1000 millisecond comments --- packages/engine/Source/Core/GregorianDate.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/engine/Source/Core/GregorianDate.js b/packages/engine/Source/Core/GregorianDate.js index 7f271c6c2b73..4fb7446d61db 100644 --- a/packages/engine/Source/Core/GregorianDate.js +++ b/packages/engine/Source/Core/GregorianDate.js @@ -17,7 +17,7 @@ const daysInYear = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; * @param {number} [hour] The hour as a whole number with range [0, 23]. * @param {number} [minute] The minute of the hour as a whole number with range [0, 59]. * @param {number} [second] The second of the minute as a whole number with range [0, 60], with 60 representing a leap second. - * @param {number} [millisecond] The millisecond of the second as a floating point number with range [0.0, 1000.0]). + * @param {number} [millisecond] The millisecond of the second as a floating point number with range [0.0, 1000.0). * @param {boolean} [isLeapSecond] Whether this time is during a leap second. * * @see JulianDate#toGregorianDate @@ -84,7 +84,7 @@ function GregorianDate( */ this.second = second; /** - * Gets or sets the millisecond of the second as a floating point number with range [0.0, 1000.0]). + * Gets or sets the millisecond of the second as a floating point number with range [0.0, 1000.0). * @type {number} */ this.millisecond = millisecond; From e6e8179b7304360cf95f8cc15f79d63386452b8d Mon Sep 17 00:00:00 2001 From: Peter Gagliardi Date: Tue, 2 Jan 2024 09:10:52 -0500 Subject: [PATCH 21/40] PR feedback --- packages/engine/Source/Scene/Cesium3DTileset.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/engine/Source/Scene/Cesium3DTileset.js b/packages/engine/Source/Scene/Cesium3DTileset.js index cfa854fd7f7d..96cb09920680 100644 --- a/packages/engine/Source/Scene/Cesium3DTileset.js +++ b/packages/engine/Source/Scene/Cesium3DTileset.js @@ -454,9 +454,6 @@ function Cesium3DTileset(options) { *

* When the SSE factor is set to 0, the adjustment will be 0 for all tiles. *

- * A factor used to increase the screen space error of tiles for dynamic screen space error. As this value increases - * less tiles are requested for rendering and tiles in the distance will have lower detail. If set to zero, the - * feature will be disabled. * * @type {number} * @default 4.0 @@ -2357,7 +2354,8 @@ function updateDynamicScreenSpaceError(tileset, frameState) { direction = Cartesian3.normalize(direction, direction); height = positionLocal.z; if (tileBoundingVolume instanceof TileOrientedBoundingBox) { - // Assuming z-up, the last column + // Assuming z-up, the last column is the local z direction and + // represents the height of the bounding box. const halfHeightVector = Matrix3.getColumn( boundingVolume.halfAxes, 2, From e6bb5f558b78d950008d9a8e59c1c9e88ccefc76 Mon Sep 17 00:00:00 2001 From: Peter Gagliardi Date: Tue, 2 Jan 2024 09:17:37 -0500 Subject: [PATCH 22/40] The fog was unrealistically close to the camera --- packages/engine/Source/Scene/Cesium3DTileset.js | 10 +++++----- .../Cesium3DTilesInspectorViewModel.js | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/engine/Source/Scene/Cesium3DTileset.js b/packages/engine/Source/Scene/Cesium3DTileset.js index 96cb09920680..ce6140de61b5 100644 --- a/packages/engine/Source/Scene/Cesium3DTileset.js +++ b/packages/engine/Source/Scene/Cesium3DTileset.js @@ -77,7 +77,7 @@ import Cesium3DTilesetSkipTraversal from "./Cesium3DTilesetSkipTraversal.js"; * @property {boolean} [preloadFlightDestinations=true] Optimization option. Preload tiles at the camera's flight destination while the camera is in flight. * @property {boolean} [preferLeaves=false] Optimization option. Prefer loading of leaves first. * @property {boolean} [dynamicScreenSpaceError=true] Optimization option. Reduce the screen space error for tiles that are further away from the camera so that lower resolution tiles are used. - * @property {number} [dynamicScreenSpaceErrorDensity=0.00278] Similar to fog density, this value determines how far away from the camera that screen space error falls off. Larger values will cause tiles closer to the camera to be affected by dynamicScreenSpaceError. + * @property {number} [dynamicScreenSpaceErrorDensity=2.0e-4] Similar to fog density, this value determines how far away from the camera that screen space error falls off. Larger values will cause tiles closer to the camera to be affected by dynamicScreenSpaceError. * @property {number} [dynamicScreenSpaceErrorFactor=4.0] The maximum screen space error adjustment in px to apply to a tile for tiles far away on the horizon. Increase this value to make the SSE adjustment stronger, which in turn will select lower LOD tiles near the horizon. * @property {number} [dynamicScreenSpaceErrorHeightFalloff=0.25] A ratio of the tileset's height above which the density decreases for dynamic screen space error. This way, dynamicScreenSpaceError has the strongest effect when the camera is close to street level. * @property {number} [progressiveResolutionHeightFraction=0.3] Optimization option. If between (0.0, 0.5], tiles at or above the screen space error for the reduced screen resolution of progressiveResolutionHeightFraction*screenHeight will be prioritized first. This can help get a quick layer of tiles down while full resolution tiles continue to load. @@ -167,7 +167,7 @@ import Cesium3DTilesetSkipTraversal from "./Cesium3DTilesetSkipTraversal.js"; * const tileset = await Cesium.Cesium3DTileset.fromUrl( * "http://localhost:8002/tilesets/Seattle/tileset.json", { * dynamicScreenSpaceError: true, - * dynamicScreenSpaceErrorDensity: 0.00278, + * dynamicScreenSpaceErrorDensity: 2.0e-4, * dynamicScreenSpaceErrorFactor: 4.0, * dynamicScreenSpaceErrorHeightFalloff: 0.25 * }); @@ -436,11 +436,11 @@ function Cesium3DTileset(options) { *

* * @type {number} - * @default 0.00278 + * @default 2.0e-4 */ this.dynamicScreenSpaceErrorDensity = defaultValue( options.dynamicScreenSpaceErrorDensity, - 0.00278 + 2.0e-4 ); /** @@ -1991,7 +1991,7 @@ Cesium3DTileset.fromIonAssetId = async function (assetId, options) { * const tileset = await Cesium.Cesium3DTileset.fromUrl( * "http://localhost:8002/tilesets/Seattle/tileset.json", { * dynamicScreenSpaceError: true, - * dynamicScreenSpaceErrorDensity: 0.00278, + * dynamicScreenSpaceErrorDensity: 2.0e-4, * dynamicScreenSpaceErrorFactor: 4.0, * dynamicScreenSpaceErrorHeightFalloff: 0.25 * }); diff --git a/packages/widgets/Source/Cesium3DTilesInspector/Cesium3DTilesInspectorViewModel.js b/packages/widgets/Source/Cesium3DTilesInspector/Cesium3DTilesInspectorViewModel.js index b19aed8e7b08..41208c1b0ea2 100644 --- a/packages/widgets/Source/Cesium3DTilesInspector/Cesium3DTilesInspectorViewModel.js +++ b/packages/widgets/Source/Cesium3DTilesInspector/Cesium3DTilesInspectorViewModel.js @@ -722,9 +722,9 @@ function Cesium3DTilesInspectorViewModel(scene, performanceContainer) { * Gets or sets the dynamic screen space error density. This property is observable. * * @type {number} - * @default 0.00278 + * @default 2.0e-4 */ - this.dynamicScreenSpaceErrorDensity = 0.00278; + this.dynamicScreenSpaceErrorDensity = 2.0e-4; /** * Gets or sets the dynamic screen space error density slider value. @@ -732,7 +732,7 @@ function Cesium3DTilesInspectorViewModel(scene, performanceContainer) { * This property is observable. * * @type {number} - * @default 0.00278 + * @default 2.0e-4 */ this.dynamicScreenSpaceErrorDensitySliderValue = undefined; knockout.defineProperty(this, "dynamicScreenSpaceErrorDensitySliderValue", { From 112792603ff21922c0a44f4520bb8e49f2f94194 Mon Sep 17 00:00:00 2001 From: Peter Gagliardi Date: Tue, 2 Jan 2024 12:07:31 -0500 Subject: [PATCH 23/40] Put changelog entry in breaking changes section --- CHANGES.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 6bfba281f2f6..5365a8fd59b2 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,15 @@ # Change Log +### 1.114 - 2024-02-01 + +#### @cesium/engine + +##### Breaking Changes :mega: + +- The `Cesium3DTileset.dynamicScreenSpaceError` optimization is now enabled by default, as this improves performance for street-level horizon views. [#11718](https://github.com/CesiumGS/cesium/pull/11718) +- The default value of `Cesium3DTileset.dynamicScreenSpaceErrorDensity` was changed from 0.00278 to 0.0002 to be more consistent with terrain [#11718](https://github.com/CesiumGS/cesium/pull/11718) +- The default value of `Cesium3DTileset.dynamicScreenSpaceErrorFactor` was changed from 4 to 24 for improved performance when dynamic screen space error is used. [#11718](https://github.com/CesiumGS/cesium/pull/11718) + ### 1.113 - 2024-01-02 #### @cesium/engine @@ -7,7 +17,6 @@ ##### Additions :tada: - Vertical exaggeration can now be applied to a `Cesium3DTileset`. Exaggeration of `Terrain` and `Cesium3DTileset` can be controlled simultaneously via the new `Scene` properties `Scene.verticalExaggeration` and `Scene.verticalExaggerationRelativeHeight`. [#11655](https://github.com/CesiumGS/cesium/pull/11655) -- The `Cesium3DTileset.dynamicScreenSpaceError` optimization is now enabled by default, as this improves performance for street-level horizon views. [#11718](https://github.com/CesiumGS/cesium/pull/11718) ##### Fixes :wrench: From 1604f8e79498dae01e2cac200c45fdbc8a588562 Mon Sep 17 00:00:00 2001 From: Peter Gagliardi Date: Tue, 2 Jan 2024 12:08:18 -0500 Subject: [PATCH 24/40] PR feedback --- packages/engine/Source/Scene/Cesium3DTileset.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/engine/Source/Scene/Cesium3DTileset.js b/packages/engine/Source/Scene/Cesium3DTileset.js index ce6140de61b5..ead53b930df6 100644 --- a/packages/engine/Source/Scene/Cesium3DTileset.js +++ b/packages/engine/Source/Scene/Cesium3DTileset.js @@ -369,7 +369,7 @@ function Cesium3DTileset(options) { * Furthermore, the results are more accurate for tightly fitting bounding volumes like box and region. * * @type {boolean} - * @default false + * @default true */ this.dynamicScreenSpaceError = defaultValue( options.dynamicScreenSpaceError, From bddb6cc37645c61f0fdd5b50f339a55ada0c8ce6 Mon Sep 17 00:00:00 2001 From: Peter Gagliardi Date: Tue, 2 Jan 2024 12:08:37 -0500 Subject: [PATCH 25/40] Increase SSE Factor slider maximum --- .../Source/Cesium3DTilesInspector/Cesium3DTilesInspector.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/widgets/Source/Cesium3DTilesInspector/Cesium3DTilesInspector.js b/packages/widgets/Source/Cesium3DTilesInspector/Cesium3DTilesInspector.js index a88bb400ae96..742434dc7361 100644 --- a/packages/widgets/Source/Cesium3DTilesInspector/Cesium3DTilesInspector.js +++ b/packages/widgets/Source/Cesium3DTilesInspector/Cesium3DTilesInspector.js @@ -215,7 +215,7 @@ function Cesium3DTilesInspector(container, scene) { "Screen Space Error Factor", "dynamicScreenSpaceErrorFactor", 1, - 10, + 32, 0.1 ) ); From bb93b35b4c10d4660e61d6463229f1aad448082c Mon Sep 17 00:00:00 2001 From: Peter Gagliardi Date: Tue, 2 Jan 2024 13:47:11 -0500 Subject: [PATCH 26/40] Revise the parameter documentation --- .../engine/Source/Scene/Cesium3DTileset.js | 60 +++++++++---------- 1 file changed, 29 insertions(+), 31 deletions(-) diff --git a/packages/engine/Source/Scene/Cesium3DTileset.js b/packages/engine/Source/Scene/Cesium3DTileset.js index ead53b930df6..cc723f1a3efc 100644 --- a/packages/engine/Source/Scene/Cesium3DTileset.js +++ b/packages/engine/Source/Scene/Cesium3DTileset.js @@ -76,10 +76,10 @@ import Cesium3DTilesetSkipTraversal from "./Cesium3DTilesetSkipTraversal.js"; * @property {boolean} [preloadWhenHidden=false] Preload tiles when tileset.show is false. Loads tiles as if the tileset is visible but does not render them. * @property {boolean} [preloadFlightDestinations=true] Optimization option. Preload tiles at the camera's flight destination while the camera is in flight. * @property {boolean} [preferLeaves=false] Optimization option. Prefer loading of leaves first. - * @property {boolean} [dynamicScreenSpaceError=true] Optimization option. Reduce the screen space error for tiles that are further away from the camera so that lower resolution tiles are used. - * @property {number} [dynamicScreenSpaceErrorDensity=2.0e-4] Similar to fog density, this value determines how far away from the camera that screen space error falls off. Larger values will cause tiles closer to the camera to be affected by dynamicScreenSpaceError. - * @property {number} [dynamicScreenSpaceErrorFactor=4.0] The maximum screen space error adjustment in px to apply to a tile for tiles far away on the horizon. Increase this value to make the SSE adjustment stronger, which in turn will select lower LOD tiles near the horizon. - * @property {number} [dynamicScreenSpaceErrorHeightFalloff=0.25] A ratio of the tileset's height above which the density decreases for dynamic screen space error. This way, dynamicScreenSpaceError has the strongest effect when the camera is close to street level. + * @property {boolean} [dynamicScreenSpaceError=true] Optimization option. For street-level horizon views, use lower resolution tiles far from the camera. This reduces the amount of data loaded and improves tileset loading time with a slight drop in visual quality in the distance. + * @property {number} [dynamicScreenSpaceErrorDensity=2.0e-4] Similar to {@link Fog#density}, this option controls the camera distance at which the {@link Cesium3DTileset#dynamicScreenSpaceError} optimization applies. Larger values will cause tiles closer to the camera to be affected. + * @property {number} [dynamicScreenSpaceErrorFactor=4.0] A parameter that controls the intensity of the {@link Cesium3DTileset#dynamicScreenSpaceError} optimization for tiles on the horizon. Larger values cause lower resolution tiles to load, improving runtime performance at a slight reduction of visual quality. + * @property {number} [dynamicScreenSpaceErrorHeightFalloff=0.25] A ratio of the tileset's height that determines where "street level" camera views occur. When the camera is below this height, the {@link Cesium3DTileset#dynamicScreenSpaceError} optimization will have the maximum effect, and it will roll off above this value. * @property {number} [progressiveResolutionHeightFraction=0.3] Optimization option. If between (0.0, 0.5], tiles at or above the screen space error for the reduced screen resolution of progressiveResolutionHeightFraction*screenHeight will be prioritized first. This can help get a quick layer of tiles down while full resolution tiles continue to load. * @property {boolean} [foveatedScreenSpaceError=true] Optimization option. Prioritize loading tiles in the center of the screen by temporarily raising the screen space error for tiles around the edge of the screen. Screen space error returns to normal once all the tiles in the center of the screen as determined by the {@link Cesium3DTileset#foveatedConeSize} are loaded. * @property {number} [foveatedConeSize=0.1] Optimization option. Used when {@link Cesium3DTileset#foveatedScreenSpaceError} is true to control the cone size that determines which tiles are deferred. Tiles that are inside this cone are loaded immediately. Tiles outside the cone are potentially deferred based on how far outside the cone they are and their screen space error. This is controlled by {@link Cesium3DTileset#foveatedInterpolationCallback} and {@link Cesium3DTileset#foveatedMinimumScreenSpaceErrorRelaxation}. Setting this to 0.0 means the cone will be the line formed by the camera position and its view direction. Setting this to 1.0 means the cone encompasses the entire field of view of the camera, disabling the effect. @@ -362,11 +362,11 @@ function Cesium3DTileset(options) { this._pass = undefined; // Cesium3DTilePass /** - * Optimization option. Whether the tileset should adjust the screen space error for tiles on the horizon. Tiles that - * are far away will be adjusted to render at lower detail than tiles closer at the camera. This improves performance - * by loading and rendering fewer tiles, but may result in a slight drop in visual quality in the distance. This - * optimization is strongest when the camera is close to the ground plane of the tileset and looking at the horizon. - * Furthermore, the results are more accurate for tightly fitting bounding volumes like box and region. + * Optimization option. For street-level horizon views, use lower resolution tiles far from the camera. This reduces + * the amount of data loaded and improves tileset loading time with a slight drop in visual quality in the distance. + *

+ * This optimization is strongest when the camera is close to the ground plane of the tileset and looking at the + * horizon. Furthermore, the results are more accurate for tightly fitting bounding volumes like box and region. * * @type {boolean} * @default true @@ -417,22 +417,21 @@ function Cesium3DTileset(options) { this.foveatedTimeDelay = defaultValue(options.foveatedTimeDelay, 0.2); /** - * A parameter that controls how far away from the camera screen space error (SSE) falls off when - * dynamicScreenSpaceError is enabled. This is similar to the density parameter of - * {@link Fog}. This value must be non-negative. + * Similar to {@link Fog#density}, this option controls the camera distance at which the {@link Cesium3DTileset#dynamicScreenSpaceError} + * optimization applies. Larger values will cause tiles closer to the camera to be affected. This value must be + * non-negative. *

- * dynamicScreenSpaceError causes the tile SSE to fall off with distance from the camera like a bell - * curve. At the camera, no adjustment is made (peak of bell curve). For tiles further away, the SSE is reduced - * up to a limit for tiles on the horizon. The maximum reduction is determined by the - * dynamicScreenSpaceErrorFactor. Reducing the SSE allows the rendering algorithm to select - * lower-resolution tiles. + * This optimization works by rolling off the tile screen space error (SSE) with camera distance like a bell curve. + * This has the effect of selecting lower resolution tiles far from the camera. Near the camera, no adjustment is + * made. For tiles further away, the SSE is reduced by up to {@link Cesium3DTileset#dynamicScreenSpaceErrorFactor} + * (measured in pixels of error). *

*

- * Increasing the density makes the bell curve narrower. This means that the SSE adjustment will start happening - * closer to the camera. This is analgous to moving fog closer to the camera. + * Increasing the density makes the bell curve narrower so tiles closer to the camera are affected. This is analagous + * to moving fog closer to the camera. *

*

- * When the density is 0, no tiles will have their SSE adjusted. + * When the density is 0, the optimization will have no effect on the tileset. *

* * @type {number} @@ -444,15 +443,16 @@ function Cesium3DTileset(options) { ); /** - * The maximum screen space error (SSE) reduction when {@link Cesium3DTileset#dynamicScreenSpaceError} is used. The - * value must be non-negative. + * A parameter that controls the intensity of the {@link Cesium3DTileset#dynamicScreenSpaceError} optimization for + * tiles on the horizon. Larger values cause lower resolution tiles to load, improving runtime performance at a slight + * reduction of visual quality. The value must be non-negative. *

- * Increasing the SSE factor increases the maximum amount to reduce a tile's SSE. This maximum reduction happens - * for tiles near the horizon. For tiles closer to the camera, the SSE adjustment can be small as 0 (at the camera). - * In between, the SSE falls off like a bell curve. See {@link Cesium3DTileset#dynamicScreenSpaceErrorDensity} + * More specifically, this parameter represents the maximum adjustment to screen space error (SSE) in pixels for tiles + * far away from the camera. See {@link Cesium3DTileset#dynamicScreenSpaceErrorDensity} for more details about how + * this optimization works. *

*

- * When the SSE factor is set to 0, the adjustment will be 0 for all tiles. + * When the SSE factor is set to 0, the optimization will have no effect on the tileset. *

* * @type {number} @@ -464,12 +464,10 @@ function Cesium3DTileset(options) { ); /** - * A ratio of the tileset's height above which the effects of {@link Cesium3DTileset#dynamicScreenSpaceError} begin - * to fall off. This determines what is considered "street level" for the tileset. The dynamic screen space error - * optimization is intended for when the camera is at street level and pointed at the horizon. + * A ratio of the tileset's height that determines "street level" for the {@link Cesium3DTileset#dynamicScreenSpaceError} + * optimization. When the camera is below this height, the dynamic screen space error optimization will have the maximum + * effect, and it will roll off above this value. Valid values are between 0.0 and 1.0. *

- * Valid values are between 0.0 and 1.0. - *

* * @type {number} * @default 0.25 From f5e610123e354a16ecffd478dd96af559a1beb63 Mon Sep 17 00:00:00 2001 From: Peter Gagliardi Date: Tue, 2 Jan 2024 13:54:55 -0500 Subject: [PATCH 27/40] Add a changelog entry for fixing the ignored parameters --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index 1f3d2ab90cb2..dd4c8e9fccbe 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -13,6 +13,7 @@ ##### Fixes :wrench: - Fixed a bug where the 3D Tiles Inspector's `dynamicScreenSpaceErrorDensity` slider did not update the tileset [#6143](https://github.com/CesiumGS/cesium/issues/6143) +- Fixed a bug where the `Cesium3DTileset` constructor was ignoring the options `dynamicScreenSpaceError`, `dynamicScreenSpaceErrorDensity`, `dynamicScreenSpaceErrorFactor` and `dynamicScreenSpaceErrorHeightFalloff`. [#11677](https://github.com/CesiumGS/cesium/issues/11677) ### 1.113 - 2024-01-02 From f36c303a333265e4e6b8a1cad64ae48b89bddc20 Mon Sep 17 00:00:00 2001 From: Peter Gagliardi Date: Tue, 2 Jan 2024 16:02:02 -0500 Subject: [PATCH 28/40] Reorganize changelog --- CHANGES.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index dd4c8e9fccbe..e5b161633820 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -6,15 +6,18 @@ ##### Breaking Changes :mega: -- The `Cesium3DTileset.dynamicScreenSpaceError` optimization is now enabled by default, as this improves performance for street-level horizon views. [#11718](https://github.com/CesiumGS/cesium/pull/11718) -- The default value of `Cesium3DTileset.dynamicScreenSpaceErrorDensity` was changed from 0.00278 to 0.0002 to be more consistent with terrain [#11718](https://github.com/CesiumGS/cesium/pull/11718) -- The default value of `Cesium3DTileset.dynamicScreenSpaceErrorFactor` was changed from 4 to 24 for improved performance when dynamic screen space error is used. [#11718](https://github.com/CesiumGS/cesium/pull/11718) +- The `Cesium3DTileset.dynamicScreenSpaceError` optimization is now enabled by default, as this improves performance for street-level horizon views. Furthermore, the default settings of this feature were tuned for improved performance. `Cesium3DTileset.dynamicScreenSpaceErrorDensity` was changed from 0.00278 to 0.0002. `Cesium3DTileset.dynamicScreenSpaceErrorFactor` was changed from 4 to 24. [#11718](https://github.com/CesiumGS/cesium/pull/11718) ##### Fixes :wrench: -- Fixed a bug where the 3D Tiles Inspector's `dynamicScreenSpaceErrorDensity` slider did not update the tileset [#6143](https://github.com/CesiumGS/cesium/issues/6143) - Fixed a bug where the `Cesium3DTileset` constructor was ignoring the options `dynamicScreenSpaceError`, `dynamicScreenSpaceErrorDensity`, `dynamicScreenSpaceErrorFactor` and `dynamicScreenSpaceErrorHeightFalloff`. [#11677](https://github.com/CesiumGS/cesium/issues/11677) +#### @cesium/widgets + +##### Fixes :wrench: + +- Fixed a bug where the 3D Tiles Inspector's `dynamicScreenSpaceErrorDensity` slider did not update the tileset [#6143](https://github.com/CesiumGS/cesium/issues/6143) + ### 1.113 - 2024-01-02 #### @cesium/engine From 75dde943685aa657f13f85bb48421cc7f670b017 Mon Sep 17 00:00:00 2001 From: Peter Gagliardi Date: Tue, 2 Jan 2024 16:49:46 -0500 Subject: [PATCH 29/40] Update default setting for SSE factor to 24 --- packages/engine/Source/Scene/Cesium3DTileset.js | 10 +++++----- .../Cesium3DTilesInspectorViewModel.js | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/engine/Source/Scene/Cesium3DTileset.js b/packages/engine/Source/Scene/Cesium3DTileset.js index cc723f1a3efc..09592281a59d 100644 --- a/packages/engine/Source/Scene/Cesium3DTileset.js +++ b/packages/engine/Source/Scene/Cesium3DTileset.js @@ -78,7 +78,7 @@ import Cesium3DTilesetSkipTraversal from "./Cesium3DTilesetSkipTraversal.js"; * @property {boolean} [preferLeaves=false] Optimization option. Prefer loading of leaves first. * @property {boolean} [dynamicScreenSpaceError=true] Optimization option. For street-level horizon views, use lower resolution tiles far from the camera. This reduces the amount of data loaded and improves tileset loading time with a slight drop in visual quality in the distance. * @property {number} [dynamicScreenSpaceErrorDensity=2.0e-4] Similar to {@link Fog#density}, this option controls the camera distance at which the {@link Cesium3DTileset#dynamicScreenSpaceError} optimization applies. Larger values will cause tiles closer to the camera to be affected. - * @property {number} [dynamicScreenSpaceErrorFactor=4.0] A parameter that controls the intensity of the {@link Cesium3DTileset#dynamicScreenSpaceError} optimization for tiles on the horizon. Larger values cause lower resolution tiles to load, improving runtime performance at a slight reduction of visual quality. + * @property {number} [dynamicScreenSpaceErrorFactor=24.0] A parameter that controls the intensity of the {@link Cesium3DTileset#dynamicScreenSpaceError} optimization for tiles on the horizon. Larger values cause lower resolution tiles to load, improving runtime performance at a slight reduction of visual quality. * @property {number} [dynamicScreenSpaceErrorHeightFalloff=0.25] A ratio of the tileset's height that determines where "street level" camera views occur. When the camera is below this height, the {@link Cesium3DTileset#dynamicScreenSpaceError} optimization will have the maximum effect, and it will roll off above this value. * @property {number} [progressiveResolutionHeightFraction=0.3] Optimization option. If between (0.0, 0.5], tiles at or above the screen space error for the reduced screen resolution of progressiveResolutionHeightFraction*screenHeight will be prioritized first. This can help get a quick layer of tiles down while full resolution tiles continue to load. * @property {boolean} [foveatedScreenSpaceError=true] Optimization option. Prioritize loading tiles in the center of the screen by temporarily raising the screen space error for tiles around the edge of the screen. Screen space error returns to normal once all the tiles in the center of the screen as determined by the {@link Cesium3DTileset#foveatedConeSize} are loaded. @@ -168,7 +168,7 @@ import Cesium3DTilesetSkipTraversal from "./Cesium3DTilesetSkipTraversal.js"; * "http://localhost:8002/tilesets/Seattle/tileset.json", { * dynamicScreenSpaceError: true, * dynamicScreenSpaceErrorDensity: 2.0e-4, - * dynamicScreenSpaceErrorFactor: 4.0, + * dynamicScreenSpaceErrorFactor: 24.0, * dynamicScreenSpaceErrorHeightFalloff: 0.25 * }); * scene.primitives.add(tileset); @@ -456,11 +456,11 @@ function Cesium3DTileset(options) { *

* * @type {number} - * @default 4.0 + * @default 24.0 */ this.dynamicScreenSpaceErrorFactor = defaultValue( options.dynamicScreenSpaceErrorFactor, - 4.0 + 24.0 ); /** @@ -1990,7 +1990,7 @@ Cesium3DTileset.fromIonAssetId = async function (assetId, options) { * "http://localhost:8002/tilesets/Seattle/tileset.json", { * dynamicScreenSpaceError: true, * dynamicScreenSpaceErrorDensity: 2.0e-4, - * dynamicScreenSpaceErrorFactor: 4.0, + * dynamicScreenSpaceErrorFactor: 24.0, * dynamicScreenSpaceErrorHeightFalloff: 0.25 * }); * scene.primitives.add(tileset); diff --git a/packages/widgets/Source/Cesium3DTilesInspector/Cesium3DTilesInspectorViewModel.js b/packages/widgets/Source/Cesium3DTilesInspector/Cesium3DTilesInspectorViewModel.js index 41208c1b0ea2..093545e1572b 100644 --- a/packages/widgets/Source/Cesium3DTilesInspector/Cesium3DTilesInspectorViewModel.js +++ b/packages/widgets/Source/Cesium3DTilesInspector/Cesium3DTilesInspectorViewModel.js @@ -767,9 +767,9 @@ function Cesium3DTilesInspectorViewModel(scene, performanceContainer) { * Gets or sets the dynamic screen space error factor. This property is observable. * * @type {number} - * @default 4.0 + * @default 24.0 */ - this.dynamicScreenSpaceErrorFactor = 4.0; + this.dynamicScreenSpaceErrorFactor = 24.0; const pickTileset = getPickTileset(this); const pickActive = knockout.observable(); From 71157a0693c188d5340f6da1311ff0788a36635d Mon Sep 17 00:00:00 2001 From: Peter Gagliardi Date: Wed, 3 Jan 2024 11:18:52 -0500 Subject: [PATCH 30/40] Add spec for ensuring the constructor options work --- .../engine/Specs/Scene/Cesium3DTilesetSpec.js | 37 ++++++++++++++++++- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/packages/engine/Specs/Scene/Cesium3DTilesetSpec.js b/packages/engine/Specs/Scene/Cesium3DTilesetSpec.js index 2e3381f89d49..b6fc08faa464 100644 --- a/packages/engine/Specs/Scene/Cesium3DTilesetSpec.js +++ b/packages/engine/Specs/Scene/Cesium3DTilesetSpec.js @@ -1106,13 +1106,13 @@ describe( const center = Cartesian3.fromRadians(centerLongitude, centerLatitude); scene.camera.lookAt(center, new HeadingPitchRange(0.0, 0.0, distance)); - // Set dynamic SSE to false (default) + // Turn off dynamic SSE tileset.dynamicScreenSpaceError = false; scene.renderForSpecs(); expect(statistics.visited).toEqual(1); expect(statistics.numberOfCommands).toEqual(1); - // Set dynamic SSE to true, now the root is not rendered + // Turn on dynamic SSE, now the root is not rendered tileset.dynamicScreenSpaceError = true; tileset.dynamicScreenSpaceErrorDensity = 1.0; tileset.dynamicScreenSpaceErrorFactor = 10.0; @@ -1153,6 +1153,39 @@ describe( return testDynamicScreenSpaceError(withTransformSphereUrl, 144.0); }); + it("dynamic screen space error constructor options work", function () { + const options = { + dynamicScreenSpaceError: true, + dynamicScreenSpaceErrorDensity: 1.0, + dynamicScreenSpaceErrorFactor: 10.0, + dynamicScreenSpaceErrorHeightFalloff: 0.5, + }; + const distance = 103.0; + return Cesium3DTilesTester.loadTileset( + scene, + withTransformBoxUrl, + options + ).then(function (tileset) { + // Make sure the values match the constructor, not hard-coded defaults + // like in https://github.com/CesiumGS/cesium/issues/11677 + expect(tileset.dynamicScreenSpaceError).toBe(true); + expect(tileset.dynamicScreenSpaceErrorDensity).toBe(1.0); + expect(tileset.dynamicScreenSpaceErrorFactor).toBe(10.0); + expect(tileset.dynamicScreenSpaceErrorHeightFalloff).toBe(0.5); + + const statistics = tileset._statistics; + + // Horizon view, only root is in view, however due to dynamic SSE, + // it will not render. + const center = Cartesian3.fromRadians(centerLongitude, centerLatitude); + scene.camera.lookAt(center, new HeadingPitchRange(0.0, 0.0, distance)); + + scene.renderForSpecs(); + expect(statistics.visited).toEqual(0); + expect(statistics.numberOfCommands).toEqual(0); + }); + }); + it("additive refinement - selects root when sse is met", function () { viewRootOnly(); return Cesium3DTilesTester.loadTileset(scene, tilesetUrl).then(function ( From 0a1a80daa65941bbae4f13f8b8280c258b26eb58 Mon Sep 17 00:00:00 2001 From: Peter Gagliardi Date: Wed, 3 Jan 2024 16:03:40 -0500 Subject: [PATCH 31/40] Turn off dynamicSSE for debugging test --- packages/engine/Source/Scene/Cesium3DTile.js | 2 ++ packages/engine/Specs/Scene/PickingSpec.js | 4 ---- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/engine/Source/Scene/Cesium3DTile.js b/packages/engine/Source/Scene/Cesium3DTile.js index 7edd883817d8..f71c56e978cb 100644 --- a/packages/engine/Source/Scene/Cesium3DTile.js +++ b/packages/engine/Source/Scene/Cesium3DTile.js @@ -943,6 +943,8 @@ Cesium3DTile.prototype.getScreenSpaceError = function ( const factor = tileset.dynamicScreenSpaceErrorFactor; const dynamicError = CesiumMath.fog(distance, density) * factor; error -= dynamicError; + + error = Math.max(error, 0); } } diff --git a/packages/engine/Specs/Scene/PickingSpec.js b/packages/engine/Specs/Scene/PickingSpec.js index af868c13044f..4474051a0adc 100644 --- a/packages/engine/Specs/Scene/PickingSpec.js +++ b/packages/engine/Specs/Scene/PickingSpec.js @@ -134,10 +134,6 @@ describe( function createTileset(url) { const options = { maximumScreenSpaceError: 0, - // The camera is zoomed pretty far out for these tests, so - // turn off dynamicScreenSpaceError so tiles don't get culled - // unintentionally. - dynamicScreenSpaceError: false, }; return Cesium3DTilesTester.loadTileset(scene, url, options).then( function (tileset) { From d85bcafd84d4c57c6aea7a37227cd1193fe33b7b Mon Sep 17 00:00:00 2001 From: Peter Gagliardi Date: Wed, 3 Jan 2024 16:44:21 -0500 Subject: [PATCH 32/40] Disable dynamicSSE for picking tests, mention issue --- packages/engine/Specs/Scene/PickingSpec.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/engine/Specs/Scene/PickingSpec.js b/packages/engine/Specs/Scene/PickingSpec.js index 4474051a0adc..19cb2c659667 100644 --- a/packages/engine/Specs/Scene/PickingSpec.js +++ b/packages/engine/Specs/Scene/PickingSpec.js @@ -134,9 +134,16 @@ describe( function createTileset(url) { const options = { maximumScreenSpaceError: 0, + // Dynamic screen space error seems to cause a race condition in + // waitForTilesLoaded. + // See https://github.com/CesiumGS/cesium/issues/11732 + dynamicScreenSpaceError: false, }; return Cesium3DTilesTester.loadTileset(scene, url, options).then( function (tileset) { + // The tilesets used in these tests have transforms that are not + // what we want for our camera setup. Re-position the tileset + // in view of the camera const cartographic = Rectangle.center(largeRectangle); const cartesian = Cartographic.toCartesian(cartographic); tileset.root.transform = Matrix4.IDENTITY; From dcc5a61f6b4590927ccfcda7840575a00aefb2b6 Mon Sep 17 00:00:00 2001 From: Peter Gagliardi Date: Wed, 3 Jan 2024 16:46:22 -0500 Subject: [PATCH 33/40] Negative SSE wasn't the issue --- packages/engine/Source/Scene/Cesium3DTile.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/engine/Source/Scene/Cesium3DTile.js b/packages/engine/Source/Scene/Cesium3DTile.js index f71c56e978cb..7edd883817d8 100644 --- a/packages/engine/Source/Scene/Cesium3DTile.js +++ b/packages/engine/Source/Scene/Cesium3DTile.js @@ -943,8 +943,6 @@ Cesium3DTile.prototype.getScreenSpaceError = function ( const factor = tileset.dynamicScreenSpaceErrorFactor; const dynamicError = CesiumMath.fog(distance, density) * factor; error -= dynamicError; - - error = Math.max(error, 0); } } From e4dc64673b0c80d9104697783e1e321c2ff959ea Mon Sep 17 00:00:00 2001 From: Peter Gagliardi Date: Thu, 4 Jan 2024 10:29:12 -0500 Subject: [PATCH 34/40] Use async function for new tests --- .../engine/Specs/Scene/Cesium3DTilesetSpec.js | 75 +++++++++---------- 1 file changed, 36 insertions(+), 39 deletions(-) diff --git a/packages/engine/Specs/Scene/Cesium3DTilesetSpec.js b/packages/engine/Specs/Scene/Cesium3DTilesetSpec.js index b6fc08faa464..d0ba216f7cfd 100644 --- a/packages/engine/Specs/Scene/Cesium3DTilesetSpec.js +++ b/packages/engine/Specs/Scene/Cesium3DTilesetSpec.js @@ -1096,30 +1096,27 @@ describe( }); }); - function testDynamicScreenSpaceError(url, distance) { - return Cesium3DTilesTester.loadTileset(scene, url).then(function ( - tileset - ) { - const statistics = tileset._statistics; + async function testDynamicScreenSpaceError(url, distance) { + const tileset = await Cesium3DTilesTester.loadTileset(scene, url); + const statistics = tileset._statistics; - // Horizon view, only root is visible - const center = Cartesian3.fromRadians(centerLongitude, centerLatitude); - scene.camera.lookAt(center, new HeadingPitchRange(0.0, 0.0, distance)); + // Horizon view, only root is visible + const center = Cartesian3.fromRadians(centerLongitude, centerLatitude); + scene.camera.lookAt(center, new HeadingPitchRange(0.0, 0.0, distance)); - // Turn off dynamic SSE - tileset.dynamicScreenSpaceError = false; - scene.renderForSpecs(); - expect(statistics.visited).toEqual(1); - expect(statistics.numberOfCommands).toEqual(1); + // Turn off dynamic SSE + tileset.dynamicScreenSpaceError = false; + scene.renderForSpecs(); + expect(statistics.visited).toEqual(1); + expect(statistics.numberOfCommands).toEqual(1); - // Turn on dynamic SSE, now the root is not rendered - tileset.dynamicScreenSpaceError = true; - tileset.dynamicScreenSpaceErrorDensity = 1.0; - tileset.dynamicScreenSpaceErrorFactor = 10.0; - scene.renderForSpecs(); - expect(statistics.visited).toEqual(0); - expect(statistics.numberOfCommands).toEqual(0); - }); + // Turn on dynamic SSE, now the root is not rendered + tileset.dynamicScreenSpaceError = true; + tileset.dynamicScreenSpaceErrorDensity = 1.0; + tileset.dynamicScreenSpaceErrorFactor = 10.0; + scene.renderForSpecs(); + expect(statistics.visited).toEqual(0); + expect(statistics.numberOfCommands).toEqual(0); } function numberOfChildrenWithoutContent(tile) { @@ -1153,7 +1150,7 @@ describe( return testDynamicScreenSpaceError(withTransformSphereUrl, 144.0); }); - it("dynamic screen space error constructor options work", function () { + it("dynamic screen space error constructor options work", async function () { const options = { dynamicScreenSpaceError: true, dynamicScreenSpaceErrorDensity: 1.0, @@ -1161,29 +1158,29 @@ describe( dynamicScreenSpaceErrorHeightFalloff: 0.5, }; const distance = 103.0; - return Cesium3DTilesTester.loadTileset( + const tileset = await Cesium3DTilesTester.loadTileset( scene, withTransformBoxUrl, options - ).then(function (tileset) { - // Make sure the values match the constructor, not hard-coded defaults - // like in https://github.com/CesiumGS/cesium/issues/11677 - expect(tileset.dynamicScreenSpaceError).toBe(true); - expect(tileset.dynamicScreenSpaceErrorDensity).toBe(1.0); - expect(tileset.dynamicScreenSpaceErrorFactor).toBe(10.0); - expect(tileset.dynamicScreenSpaceErrorHeightFalloff).toBe(0.5); + ); - const statistics = tileset._statistics; + // Make sure the values match the constructor, not hard-coded defaults + // like in https://github.com/CesiumGS/cesium/issues/11677 + expect(tileset.dynamicScreenSpaceError).toBe(true); + expect(tileset.dynamicScreenSpaceErrorDensity).toBe(1.0); + expect(tileset.dynamicScreenSpaceErrorFactor).toBe(10.0); + expect(tileset.dynamicScreenSpaceErrorHeightFalloff).toBe(0.5); - // Horizon view, only root is in view, however due to dynamic SSE, - // it will not render. - const center = Cartesian3.fromRadians(centerLongitude, centerLatitude); - scene.camera.lookAt(center, new HeadingPitchRange(0.0, 0.0, distance)); + const statistics = tileset._statistics; - scene.renderForSpecs(); - expect(statistics.visited).toEqual(0); - expect(statistics.numberOfCommands).toEqual(0); - }); + // Horizon view, only root is in view, however due to dynamic SSE, + // it will not render. + const center = Cartesian3.fromRadians(centerLongitude, centerLatitude); + scene.camera.lookAt(center, new HeadingPitchRange(0.0, 0.0, distance)); + + scene.renderForSpecs(); + expect(statistics.visited).toEqual(0); + expect(statistics.numberOfCommands).toEqual(0); }); it("additive refinement - selects root when sse is met", function () { From a28e7010ad9e4c5cd40c9d94da5ce513d3dba387 Mon Sep 17 00:00:00 2001 From: Peter Gagliardi Date: Thu, 4 Jan 2024 10:40:42 -0500 Subject: [PATCH 35/40] Add spec for density slider --- .../Cesium3DTilesInspectorViewModelSpec.js | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/packages/widgets/Specs/Cesium3DTilesInspector/Cesium3DTilesInspectorViewModelSpec.js b/packages/widgets/Specs/Cesium3DTilesInspector/Cesium3DTilesInspectorViewModelSpec.js index d681f7cba70a..51267ff50f61 100644 --- a/packages/widgets/Specs/Cesium3DTilesInspector/Cesium3DTilesInspectorViewModelSpec.js +++ b/packages/widgets/Specs/Cesium3DTilesInspector/Cesium3DTilesInspectorViewModelSpec.js @@ -1,4 +1,9 @@ -import { Cesium3DTileset, Cesium3DTileStyle, Globe } from "@cesium/engine"; +import { + Cesium3DTileset, + Cesium3DTileStyle, + Globe, + Math as CesiumMath, +} from "@cesium/engine"; import { Cesium3DTilesInspectorViewModel } from "../../index.js"; import createScene from "../../../../Specs/createScene.js"; @@ -255,6 +260,23 @@ describe( expect(viewModel.tileset.dynamicScreenSpaceErrorFactor).toBe(2); expect(viewModel.tileset.dynamicScreenSpaceErrorDensity).toBe(0.1); }); + + it("dynamicScreenSpaceErrorDensity slider uses an exponential scale", function () { + // The HTML slider produces a linear range, but the actual density value + // varies exponentially. + const rawSliderValue = 0.2; + const scaledValue = Math.pow(rawSliderValue, 6); + + viewModel.dynamicScreenSpaceErrorDensitySliderValue = rawSliderValue; + expect( + viewModel.dynamicScreenSpaceErrorDensitySliderValue + ).toEqualEpsilon(rawSliderValue, CesiumMath.EPSILON8); + + expect(viewModel.tileset.dynamicScreenSpaceErrorDensity).toEqualEpsilon( + scaledValue, + CesiumMath.EPSILON8 + ); + }); }); describe("style options", function () { From 85e516cd50eb4f4a9dea8b9986d5cb6676f62345 Mon Sep 17 00:00:00 2001 From: Gabby Getz Date: Thu, 4 Jan 2024 13:39:20 -0500 Subject: [PATCH 36/40] Update copyright year --- packages/engine/LICENSE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/engine/LICENSE.md b/packages/engine/LICENSE.md index 7c3383354f82..4ace84f3d85d 100644 --- a/packages/engine/LICENSE.md +++ b/packages/engine/LICENSE.md @@ -1,4 +1,4 @@ -Copyright 2011-2022 CesiumJS Contributors +Copyright 2011-2024 CesiumJS Contributors Apache License Version 2.0, January 2004 From 39221f425ba1d21f6eeff74d23d0931adf718b1a Mon Sep 17 00:00:00 2001 From: Peter Gagliardi Date: Thu, 4 Jan 2024 14:11:29 -0500 Subject: [PATCH 37/40] Document additional limitations of 3D Tiles classification --- CHANGES.md | 1 + packages/engine/Source/Scene/Cesium3DTileset.js | 4 ++++ packages/engine/Source/Scene/Model/Model.js | 12 ++++++++---- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index d229c66b2ccc..be64cf1b4839 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -15,6 +15,7 @@ ##### Additions :tada: - Vertical exaggeration can now be applied to a `Cesium3DTileset`. Exaggeration of `Terrain` and `Cesium3DTileset` can be controlled simultaneously via the new `Scene` properties `Scene.verticalExaggeration` and `Scene.verticalExaggerationRelativeHeight`. [#11655](https://github.com/CesiumGS/cesium/pull/11655) +- Added documentation for `Cesium3DTileset.classificationType` and `Model.classificationType` to clarify additional requirements. The classification tileset must be watertight, and the receiving tileset/terrain must be opaque. ##### Fixes :wrench: diff --git a/packages/engine/Source/Scene/Cesium3DTileset.js b/packages/engine/Source/Scene/Cesium3DTileset.js index 0f5f3b56bcb9..0b21b6c1eb68 100644 --- a/packages/engine/Source/Scene/Cesium3DTileset.js +++ b/packages/engine/Source/Scene/Cesium3DTileset.js @@ -1635,6 +1635,7 @@ Object.defineProperties(Cesium3DTileset.prototype, { *
  • The glTF cannot contain morph targets, skins, or animations.
  • *
  • The glTF cannot contain the EXT_mesh_gpu_instancing extension.
  • *
  • Only meshes with TRIANGLES can be used to classify other assets.
  • + *
  • Meshes must be watertight
  • *
  • The POSITION semantic is required.
  • *
  • If _BATCHIDs and an index buffer are both present, all indices with the same batch id must occupy contiguous sections of the index buffer.
  • *
  • If _BATCHIDs are present with no index buffer, all positions with the same batch id must occupy contiguous sections of the position buffer.
  • @@ -1644,6 +1645,9 @@ Object.defineProperties(Cesium3DTileset.prototype, { * Additionally, classification is not supported for points or instanced 3D * models. *

    + *

    + * The 3D Tiles or terrain receiving the classification must be opaque. + *

    * * @memberof Cesium3DTileset.prototype * diff --git a/packages/engine/Source/Scene/Model/Model.js b/packages/engine/Source/Scene/Model/Model.js index 75d24a7a7761..cb3846de0907 100644 --- a/packages/engine/Source/Scene/Model/Model.js +++ b/packages/engine/Source/Scene/Model/Model.js @@ -112,7 +112,7 @@ import StyleCommandsNeeded from "./StyleCommandsNeeded.js"; * @internalConstructor * * @privateParam {ResourceLoader} options.loader The loader used to load resources for this model. - * @privateParam {ModelType} options.type Type of this model, to distinguish individual glTF files from 3D Tiles internally. + * @privateParam {ModelType} options.type Type of this model, to distinguish individual glTF files from 3D Tiles internally. * @privateParam {object} options Object with the following properties: * @privateParam {Resource} options.resource The Resource to the 3D model. * @privateParam {boolean} [options.show=true] Whether or not to render the model. @@ -154,7 +154,7 @@ import StyleCommandsNeeded from "./StyleCommandsNeeded.js"; * @privateParam {string|number} [options.instanceFeatureIdLabel="instanceFeatureId_0"] Label of the instance feature ID set used for picking and styling. If instanceFeatureIdLabel is set to an integer N, it is converted to the string "instanceFeatureId_N" automatically. If both per-primitive and per-instance feature IDs are present, the instance feature IDs take priority. * @privateParam {object} [options.pointCloudShading] Options for constructing a {@link PointCloudShading} object to control point attenuation based on geometric error and lighting. * @privateParam {ClassificationType} [options.classificationType] Determines whether terrain, 3D Tiles or both will be classified by this model. This cannot be set after the model has loaded. - + * * @see Model.fromGltfAsync * @@ -192,7 +192,7 @@ function Model(options) { * When this is the identity matrix, the model is drawn in world coordinates, i.e., Earth's Cartesian WGS84 coordinates. * Local reference frames can be used by providing a different transformation matrix, like that returned * by {@link Transforms.eastNorthUpToFixedFrame}. - * + * * @type {Matrix4} * @default {@link Matrix4.IDENTITY} @@ -1550,11 +1550,15 @@ Object.defineProperties(Model.prototype, { *
  • The glTF cannot contain morph targets, skins, or animations.
  • *
  • The glTF cannot contain the EXT_mesh_gpu_instancing extension.
  • *
  • Only meshes with TRIANGLES can be used to classify other assets.
  • - *
  • The position attribute is required.
  • + *
  • Meshes must be watertight
  • + *
  • The POSITION attribute is required.
  • *
  • If feature IDs and an index buffer are both present, all indices with the same feature id must occupy contiguous sections of the index buffer.
  • *
  • If feature IDs are present without an index buffer, all positions with the same feature id must occupy contiguous sections of the position buffer.
  • * *

    + *

    + * The 3D Tiles or terrain receiving the classification must be opaque. + *

    * * @memberof Model.prototype * From 895c7289230f8e8e1cf39d12b1601f475529e6fe Mon Sep 17 00:00:00 2001 From: Peter Gagliardi Date: Thu, 4 Jan 2024 14:33:15 -0500 Subject: [PATCH 38/40] self-review feedback --- CHANGES.md | 2 +- packages/engine/Source/Scene/Cesium3DTileset.js | 2 +- packages/engine/Source/Scene/Model/Model.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index be64cf1b4839..5d281593d44c 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -15,7 +15,7 @@ ##### Additions :tada: - Vertical exaggeration can now be applied to a `Cesium3DTileset`. Exaggeration of `Terrain` and `Cesium3DTileset` can be controlled simultaneously via the new `Scene` properties `Scene.verticalExaggeration` and `Scene.verticalExaggerationRelativeHeight`. [#11655](https://github.com/CesiumGS/cesium/pull/11655) -- Added documentation for `Cesium3DTileset.classificationType` and `Model.classificationType` to clarify additional requirements. The classification tileset must be watertight, and the receiving tileset/terrain must be opaque. +- Added documentation for `Cesium3DTileset.classificationType` and `Model.classificationType` to clarify additional requirements. The classification tileset must be watertight, and the receiving tileset/terrain must be opaque. [#11739](https://github.com/CesiumGS/cesium/pull/11739) ##### Fixes :wrench: diff --git a/packages/engine/Source/Scene/Cesium3DTileset.js b/packages/engine/Source/Scene/Cesium3DTileset.js index 0b21b6c1eb68..47e00614f2ff 100644 --- a/packages/engine/Source/Scene/Cesium3DTileset.js +++ b/packages/engine/Source/Scene/Cesium3DTileset.js @@ -1635,7 +1635,7 @@ Object.defineProperties(Cesium3DTileset.prototype, { *
  • The glTF cannot contain morph targets, skins, or animations.
  • *
  • The glTF cannot contain the EXT_mesh_gpu_instancing extension.
  • *
  • Only meshes with TRIANGLES can be used to classify other assets.
  • - *
  • Meshes must be watertight
  • + *
  • Meshes must be watertight.
  • *
  • The POSITION semantic is required.
  • *
  • If _BATCHIDs and an index buffer are both present, all indices with the same batch id must occupy contiguous sections of the index buffer.
  • *
  • If _BATCHIDs are present with no index buffer, all positions with the same batch id must occupy contiguous sections of the position buffer.
  • diff --git a/packages/engine/Source/Scene/Model/Model.js b/packages/engine/Source/Scene/Model/Model.js index cb3846de0907..3fcda4ea4fb7 100644 --- a/packages/engine/Source/Scene/Model/Model.js +++ b/packages/engine/Source/Scene/Model/Model.js @@ -1550,7 +1550,7 @@ Object.defineProperties(Model.prototype, { *
  • The glTF cannot contain morph targets, skins, or animations.
  • *
  • The glTF cannot contain the EXT_mesh_gpu_instancing extension.
  • *
  • Only meshes with TRIANGLES can be used to classify other assets.
  • - *
  • Meshes must be watertight
  • + *
  • The meshes must be watertight.
  • *
  • The POSITION attribute is required.
  • *
  • If feature IDs and an index buffer are both present, all indices with the same feature id must occupy contiguous sections of the index buffer.
  • *
  • If feature IDs are present without an index buffer, all positions with the same feature id must occupy contiguous sections of the position buffer.
  • From a8c6e22237715127ad46710b09eaacb535b9e5ba Mon Sep 17 00:00:00 2001 From: Peter Gagliardi Date: Thu, 4 Jan 2024 14:44:00 -0500 Subject: [PATCH 39/40] Forgot to hit save --- packages/engine/Source/Scene/Cesium3DTileset.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/engine/Source/Scene/Cesium3DTileset.js b/packages/engine/Source/Scene/Cesium3DTileset.js index 47e00614f2ff..eebb1459a662 100644 --- a/packages/engine/Source/Scene/Cesium3DTileset.js +++ b/packages/engine/Source/Scene/Cesium3DTileset.js @@ -1635,7 +1635,7 @@ Object.defineProperties(Cesium3DTileset.prototype, { *
  • The glTF cannot contain morph targets, skins, or animations.
  • *
  • The glTF cannot contain the EXT_mesh_gpu_instancing extension.
  • *
  • Only meshes with TRIANGLES can be used to classify other assets.
  • - *
  • Meshes must be watertight.
  • + *
  • The meshes must be watertight.
  • *
  • The POSITION semantic is required.
  • *
  • If _BATCHIDs and an index buffer are both present, all indices with the same batch id must occupy contiguous sections of the index buffer.
  • *
  • If _BATCHIDs are present with no index buffer, all positions with the same batch id must occupy contiguous sections of the position buffer.
  • From 13f858f5accb25267dcaf7cb4708e135ad590bbc Mon Sep 17 00:00:00 2001 From: Peter Gagliardi Date: Thu, 4 Jan 2024 15:42:54 -0500 Subject: [PATCH 40/40] Documentation doesn't impact the public API --- CHANGES.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 5d281593d44c..d229c66b2ccc 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -15,7 +15,6 @@ ##### Additions :tada: - Vertical exaggeration can now be applied to a `Cesium3DTileset`. Exaggeration of `Terrain` and `Cesium3DTileset` can be controlled simultaneously via the new `Scene` properties `Scene.verticalExaggeration` and `Scene.verticalExaggerationRelativeHeight`. [#11655](https://github.com/CesiumGS/cesium/pull/11655) -- Added documentation for `Cesium3DTileset.classificationType` and `Model.classificationType` to clarify additional requirements. The classification tileset must be watertight, and the receiving tileset/terrain must be opaque. [#11739](https://github.com/CesiumGS/cesium/pull/11739) ##### Fixes :wrench: