diff --git a/Apps/Sandcastle/gallery/development/Geometry and Appearances.html b/Apps/Sandcastle/gallery/development/Geometry and Appearances.html
index e865176f58db..70eff33f1e86 100644
--- a/Apps/Sandcastle/gallery/development/Geometry and Appearances.html
+++ b/Apps/Sandcastle/gallery/development/Geometry and Appearances.html
@@ -33,7 +33,7 @@
var primitives = scene.primitives;
var solidWhite = Cesium.ColorGeometryInstanceAttribute.fromColor(Cesium.Color.WHITE);
-// Combine instances for an rectangle, polygon, ellipse, and circle into a single primitive.
+// Combine instances for a rectangle, polygon, ellipse, and circle into a single primitive.
var rectangle = Cesium.Rectangle.fromDegrees(-92.0, 20.0, -86.0, 27.0);
var rectangleInstance = new Cesium.GeometryInstance({
diff --git a/Documentation/Contributors/CodingGuide/README.md b/Documentation/Contributors/CodingGuide/README.md
index c4e1db5cff73..07282f9dc009 100644
--- a/Documentation/Contributors/CodingGuide/README.md
+++ b/Documentation/Contributors/CodingGuide/README.md
@@ -290,12 +290,12 @@ Cesium3DTileset.prototype.update = function(frameState) {
updateTiles(this, frameState);
};
-function processTiles(tiles3D, frameState) {
- var tiles = tiles3D._processingQueue;
+function processTiles(tileset, frameState) {
+ var tiles = tileset._processingQueue;
var length = tiles.length;
for (var i = length - 1; i >= 0; --i) {
- tiles[i].process(tiles3D, frameState);
+ tiles[i].process(tileset, frameState);
}
}
```
@@ -571,12 +571,12 @@ Cesium3DTileset.prototype.update = function(frameState) {
// ...
};
-Cesium3DTileset.prototype._processTiles(tiles3D, frameState) {
+Cesium3DTileset.prototype._processTiles(tileset, frameState) {
var tiles = this._processingQueue;
var length = tiles.length;
for (var i = length - 1; i >= 0; --i) {
- tiles[i].process(tiles3D, frameState);
+ tiles[i].process(tileset, frameState);
}
}
```
@@ -587,12 +587,12 @@ Cesium3DTileset.prototype.update = function(frameState) {
// ...
};
-function processTiles(tiles3D, frameState) {
- var tiles = tiles3D._processingQueue;
+function processTiles(tileset, frameState) {
+ var tiles = tileset._processingQueue;
var length = tiles.length;
for (var i = length - 1; i >= 0; --i) {
- tiles[i].process(tiles3D, frameState);
+ tiles[i].process(tileset, frameState);
}
}
```
@@ -672,13 +672,13 @@ Model.prototype.update = function(frameState) {
It is convenient for the constructor function to be at the top of the file even if it requires that helper functions rely on **hoisting**, for example, `Cesium3DTileset.js`,
```javascript
-function loadTilesJson(tileset, tilesJson, done) {
+function loadTileset(tileset, tilesJson, done) {
// ...
}
function Cesium3DTileset(options) {
// ...
- loadTilesJson(this, options.url, function(data) {
+ loadTileset(this, options.url, function(data) {
// ...
});
};
@@ -687,16 +687,16 @@ is better written as
```javascript
function Cesium3DTileset(options) {
// ...
- loadTilesJson(this, options.url, function(data) {
+ loadTileset(this, options.url, function(data) {
// ...
});
};
-function loadTilesJson(tileset, tilesJson, done) {
+function loadTileset(tileset, tilesJson, done) {
// ...
}
```
-even though it relies on implicitly hoisting the `loadTilesJson` function to the top of the file.
+even though it relies on implicitly hoisting the `loadTileset` function to the top of the file.
## Design
diff --git a/Documentation/Contributors/TestingGuide/README.md b/Documentation/Contributors/TestingGuide/README.md
index 98d109ac4613..8b67ca5a00b5 100644
--- a/Documentation/Contributors/TestingGuide/README.md
+++ b/Documentation/Contributors/TestingGuide/README.md
@@ -630,7 +630,7 @@ it('can create a billboard using a URL', function() {
return pollToPromise(function() {
return b.ready;
}).then(function() {
- expect(scene.renderForSpecs()).toEqual([0, 255, 0, 255]);
+ expect(scene).toRender([0, 255, 0, 255]);
});
});
```
diff --git a/Source/Core/BoundingRectangle.js b/Source/Core/BoundingRectangle.js
index ad7ca2a8a865..3fffcbfeca77 100644
--- a/Source/Core/BoundingRectangle.js
+++ b/Source/Core/BoundingRectangle.js
@@ -169,7 +169,7 @@ define([
var fromRectangleLowerLeft = new Cartographic();
var fromRectangleUpperRight = new Cartographic();
/**
- * Computes a bounding rectangle from an rectangle.
+ * Computes a bounding rectangle from a rectangle.
*
* @param {Rectangle} rectangle The valid rectangle used to create a bounding rectangle.
* @param {Object} [projection=GeographicProjection] The projection used to project the rectangle into 2D.
diff --git a/Source/Core/BoundingSphere.js b/Source/Core/BoundingSphere.js
index f91c30777dcb..03a20e5b1abc 100644
--- a/Source/Core/BoundingSphere.js
+++ b/Source/Core/BoundingSphere.js
@@ -221,7 +221,7 @@ define([
var fromRectangle2DNortheast = new Cartographic();
/**
- * Computes a bounding sphere from an rectangle projected in 2D.
+ * Computes a bounding sphere from a rectangle projected in 2D.
*
* @param {Rectangle} rectangle The rectangle around which to create a bounding sphere.
* @param {Object} [projection=GeographicProjection] The projection used to project the rectangle into 2D.
@@ -233,7 +233,7 @@ define([
};
/**
- * Computes a bounding sphere from an rectangle projected in 2D. The bounding sphere accounts for the
+ * Computes a bounding sphere from a rectangle projected in 2D. The bounding sphere accounts for the
* object's minimum and maximum heights over the rectangle.
*
* @param {Rectangle} rectangle The rectangle around which to create a bounding sphere.
@@ -279,7 +279,7 @@ define([
var fromRectangle3DScratch = [];
/**
- * Computes a bounding sphere from an rectangle in 3D. The bounding sphere is created using a subsample of points
+ * Computes a bounding sphere from a rectangle in 3D. The bounding sphere is created using a subsample of points
* on the ellipsoid and contained in the rectangle. It may not be accurate for all rectangles on all types of ellipsoids.
*
* @param {Rectangle} rectangle The valid rectangle used to create a bounding sphere.
diff --git a/Source/Core/EllipsoidalOccluder.js b/Source/Core/EllipsoidalOccluder.js
index 31c776faa4ee..3fad0be9bda6 100644
--- a/Source/Core/EllipsoidalOccluder.js
+++ b/Source/Core/EllipsoidalOccluder.js
@@ -241,7 +241,7 @@ define([
var subsampleScratch = [];
/**
- * Computes a point that can be used for horizon culling of an rectangle. If the point is below
+ * Computes a point that can be used for horizon culling of a rectangle. If the point is below
* the horizon, the ellipsoid-conforming rectangle is guaranteed to be below the horizon as well.
* The returned point is expressed in the ellipsoid-scaled space and is suitable for use with
* {@link EllipsoidalOccluder#isScaledSpacePointVisible}.
diff --git a/Source/Core/GeographicTilingScheme.js b/Source/Core/GeographicTilingScheme.js
index e7ab5ee8a15c..1ee704738475 100644
--- a/Source/Core/GeographicTilingScheme.js
+++ b/Source/Core/GeographicTilingScheme.js
@@ -104,7 +104,7 @@ define([
};
/**
- * Transforms an rectangle specified in geodetic radians to the native coordinate system
+ * Transforms a rectangle specified in geodetic radians to the native coordinate system
* of this tiling scheme.
*
* @param {Rectangle} rectangle The rectangle to transform.
@@ -137,7 +137,7 @@ define([
};
/**
- * Converts tile x, y coordinates and level to an rectangle expressed in the native coordinates
+ * Converts tile x, y coordinates and level to a rectangle expressed in the native coordinates
* of the tiling scheme.
*
* @param {Number} x The integer x coordinate of the tile.
diff --git a/Source/Core/HeightmapTessellator.js b/Source/Core/HeightmapTessellator.js
index 2195f819a985..ebf11928f13a 100644
--- a/Source/Core/HeightmapTessellator.js
+++ b/Source/Core/HeightmapTessellator.js
@@ -73,7 +73,7 @@ define([
* @param {Number} options.width The width of the heightmap, in height samples.
* @param {Number} options.height The height of the heightmap, in height samples.
* @param {Number} options.skirtHeight The height of skirts to drape at the edges of the heightmap.
- * @param {Rectangle} options.nativeRectangle An rectangle in the native coordinates of the heightmap's projection. For
+ * @param {Rectangle} options.nativeRectangle A rectangle in the native coordinates of the heightmap's projection. For
* a heightmap with a geographic projection, this is degrees. For the web mercator
* projection, this is meters.
* @param {Number} [options.exaggeration=1.0] The scale used to exaggerate the terrain.
diff --git a/Source/Core/IndexDatatype.js b/Source/Core/IndexDatatype.js
index 4aa9a3d73aad..2922700c3556 100644
--- a/Source/Core/IndexDatatype.js
+++ b/Source/Core/IndexDatatype.js
@@ -96,7 +96,7 @@ define([
* or Uint32Array
depending on the number of vertices.
*
* @param {Number} numberOfVertices Number of vertices that the indices will reference.
- * @param {Any} indicesLengthOrArray Passed through to the typed array constructor.
+ * @param {*} indicesLengthOrArray Passed through to the typed array constructor.
* @returns {Uint16Array|Uint32Array} A Uint16Array
or Uint32Array
constructed with indicesLengthOrArray
.
*
* @example
diff --git a/Source/Core/Occluder.js b/Source/Core/Occluder.js
index ce3658e72633..3fc9cc003616 100644
--- a/Source/Core/Occluder.js
+++ b/Source/Core/Occluder.js
@@ -174,7 +174,7 @@ define([
* var occluder = new Cesium.Occluder(littleSphere, cameraPosition);
* var point = new Cesium.Cartesian3(0, 0, -3);
* occluder.isPointVisible(point); //returns true
- *
+ *
* @see Occluder#computeVisibility
*/
Occluder.prototype.isPointVisible = function(occludee) {
@@ -206,7 +206,7 @@ define([
* var occluder = new Cesium.Occluder(littleSphere, cameraPosition);
* var bigSphere = new Cesium.BoundingSphere(new Cesium.Cartesian3(0, 0, -3), 1);
* occluder.isBoundingSphereVisible(bigSphere); //returns true
- *
+ *
* @see Occluder#computeVisibility
*/
Occluder.prototype.isBoundingSphereVisible = function(occludee) {
@@ -265,7 +265,7 @@ define([
* var cameraPosition = new Cesium.Cartesian3(0, 0, 0);
* var occluder = new Cesium.Occluder(sphere1, cameraPosition);
* occluder.computeVisibility(sphere2); //returns Visibility.NONE
- *
+ *
* @see Occluder#isVisible
*/
Occluder.prototype.computeVisibility = function(occludeeBS) {
@@ -406,7 +406,7 @@ define([
var computeOccludeePointFromRectangleScratch = [];
/**
- * Computes a point that can be used as the occludee position to the visibility functions from an rectangle.
+ * Computes a point that can be used as the occludee position to the visibility functions from a rectangle.
*
* @param {Rectangle} rectangle The rectangle used to create a bounding sphere.
* @param {Ellipsoid} [ellipsoid=Ellipsoid.WGS84] The ellipsoid used to determine positions of the rectangle.
diff --git a/Source/Core/PixelFormat.js b/Source/Core/PixelFormat.js
index ee8f9c48c12d..b7b2288f02f7 100644
--- a/Source/Core/PixelFormat.js
+++ b/Source/Core/PixelFormat.js
@@ -1,8 +1,10 @@
/*global define*/
define([
+ '../Renderer/PixelDatatype',
'./freezeObject',
'./WebGLConstants'
], function(
+ PixelDatatype,
freezeObject,
WebGLConstants) {
'use strict';
@@ -141,6 +143,26 @@ define([
*/
RGB_ETC1 : WebGLConstants.COMPRESSED_RGB_ETC1_WEBGL,
+ /**
+ * @private
+ */
+ componentsLength : function(pixelFormat) {
+ switch (pixelFormat) {
+ // Many GPUs store RGB as RGBA internally
+ // https://devtalk.nvidia.com/default/topic/699479/general-graphics-programming/rgb-auto-converted-to-rgba/post/4142379/#4142379
+ case PixelFormat.RGB:
+ case PixelFormat.RGBA:
+ return 4;
+ case PixelFormat.LUMINANCE_ALPHA:
+ return 2;
+ case PixelFormat.ALPHA:
+ case PixelFormat.LUMINANCE:
+ return 1;
+ default:
+ return 1;
+ }
+ },
+
/**
* @private
*/
@@ -227,7 +249,7 @@ define([
/**
* @private
*/
- compressedTextureSize : function(pixelFormat, width, height) {
+ compressedTextureSizeInBytes : function(pixelFormat, width, height) {
switch (pixelFormat) {
case PixelFormat.RGB_DXT1:
case PixelFormat.RGBA_DXT1:
@@ -249,6 +271,17 @@ define([
default:
return 0;
}
+ },
+
+ /**
+ * @private
+ */
+ textureSizeInBytes : function(pixelFormat, pixelDatatype, width, height) {
+ var componentsLength = PixelFormat.componentsLength(pixelFormat);
+ if (PixelDatatype.isPacked(pixelDatatype)) {
+ componentsLength = 1;
+ }
+ return componentsLength * PixelDatatype.sizeInBytes(pixelDatatype) * width * height;
}
};
diff --git a/Source/Core/Rectangle.js b/Source/Core/Rectangle.js
index fcbfeace3509..44f1cec294ec 100644
--- a/Source/Core/Rectangle.js
+++ b/Source/Core/Rectangle.js
@@ -177,7 +177,7 @@ define([
};
/**
- * Creates an rectangle given the boundary longitude and latitude in degrees.
+ * Creates a rectangle given the boundary longitude and latitude in degrees.
*
* @param {Number} [west=0.0] The westernmost longitude in degrees in the range [-180.0, 180.0].
* @param {Number} [south=0.0] The southernmost latitude in degrees in the range [-90.0, 90.0].
@@ -208,7 +208,7 @@ define([
};
/**
- * Creates an rectangle given the boundary longitude and latitude in radians.
+ * Creates a rectangle given the boundary longitude and latitude in radians.
*
* @param {Number} [west=0.0] The westernmost longitude in radians in the range [-Math.PI, Math.PI].
* @param {Number} [south=0.0] The southernmost latitude in radians in the range [-Math.PI/2, Math.PI/2].
@@ -458,7 +458,7 @@ define([
};
/**
- * Computes the southwest corner of an rectangle.
+ * Computes the southwest corner of a rectangle.
*
* @param {Rectangle} rectangle The rectangle for which to find the corner
* @param {Cartographic} [result] The object onto which to store the result.
@@ -479,7 +479,7 @@ define([
};
/**
- * Computes the northwest corner of an rectangle.
+ * Computes the northwest corner of a rectangle.
*
* @param {Rectangle} rectangle The rectangle for which to find the corner
* @param {Cartographic} [result] The object onto which to store the result.
@@ -500,7 +500,7 @@ define([
};
/**
- * Computes the northeast corner of an rectangle.
+ * Computes the northeast corner of a rectangle.
*
* @param {Rectangle} rectangle The rectangle for which to find the corner
* @param {Cartographic} [result] The object onto which to store the result.
@@ -521,7 +521,7 @@ define([
};
/**
- * Computes the southeast corner of an rectangle.
+ * Computes the southeast corner of a rectangle.
*
* @param {Rectangle} rectangle The rectangle for which to find the corner
* @param {Cartographic} [result] The object onto which to store the result.
@@ -542,7 +542,7 @@ define([
};
/**
- * Computes the center of an rectangle.
+ * Computes the center of a rectangle.
*
* @param {Rectangle} rectangle The rectangle for which to find the center
* @param {Cartographic} [result] The object onto which to store the result.
@@ -776,7 +776,7 @@ define([
var subsampleLlaScratch = new Cartographic();
/**
- * Samples an rectangle so that it includes a list of Cartesian points suitable for passing to
+ * Samples a rectangle so that it includes a list of Cartesian points suitable for passing to
* {@link BoundingSphere#fromPoints}. Sampling is necessary to account
* for rectangles that cover the poles or cross the equator.
*
diff --git a/Source/Core/RectangleGeometry.js b/Source/Core/RectangleGeometry.js
index 034e8d4fa45d..3eb2a510a391 100644
--- a/Source/Core/RectangleGeometry.js
+++ b/Source/Core/RectangleGeometry.js
@@ -624,7 +624,7 @@ define([
* @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Rectangle.html|Cesium Sandcastle Rectangle Demo}
*
* @example
- * // 1. create an rectangle
+ * // 1. create a rectangle
* var rectangle = new Cesium.RectangleGeometry({
* ellipsoid : Cesium.Ellipsoid.WGS84,
* rectangle : Cesium.Rectangle.fromDegrees(-80.0, 39.0, -74.0, 42.0),
@@ -809,7 +809,7 @@ define([
var quaternionScratch = new Quaternion();
var centerScratch = new Cartographic();
/**
- * Computes the geometric representation of an rectangle, including its vertices, indices, and a bounding sphere.
+ * Computes the geometric representation of a rectangle, including its vertices, indices, and a bounding sphere.
*
* @param {RectangleGeometry} rectangleGeometry A description of the rectangle.
* @returns {Geometry|undefined} The computed vertices and indices.
diff --git a/Source/Core/RectangleOutlineGeometry.js b/Source/Core/RectangleOutlineGeometry.js
index 1d6cc7ce96f1..785a381b42ce 100644
--- a/Source/Core/RectangleOutlineGeometry.js
+++ b/Source/Core/RectangleOutlineGeometry.js
@@ -320,7 +320,7 @@ define([
var nwScratch = new Cartographic();
/**
- * Computes the geometric representation of an outline of an rectangle, including its vertices, indices, and a bounding sphere.
+ * Computes the geometric representation of an outline of a rectangle, including its vertices, indices, and a bounding sphere.
*
* @param {RectangleOutlineGeometry} rectangleGeometry A description of the rectangle outline.
* @returns {Geometry|undefined} The computed vertices and indices.
diff --git a/Source/Core/TilingScheme.js b/Source/Core/TilingScheme.js
index f5e203ad9c5d..1e98a22e5703 100644
--- a/Source/Core/TilingScheme.js
+++ b/Source/Core/TilingScheme.js
@@ -75,7 +75,7 @@ define([
TilingScheme.prototype.getNumberOfYTilesAtLevel = DeveloperError.throwInstantiationError;
/**
- * Transforms an rectangle specified in geodetic radians to the native coordinate system
+ * Transforms a rectangle specified in geodetic radians to the native coordinate system
* of this tiling scheme.
* @function
*
@@ -88,7 +88,7 @@ define([
TilingScheme.prototype.rectangleToNativeRectangle = DeveloperError.throwInstantiationError;
/**
- * Converts tile x, y coordinates and level to an rectangle expressed in the native coordinates
+ * Converts tile x, y coordinates and level to a rectangle expressed in the native coordinates
* of the tiling scheme.
* @function
*
diff --git a/Source/Core/Transforms.js b/Source/Core/Transforms.js
index df5684d91a93..27edaabc93ec 100644
--- a/Source/Core/Transforms.js
+++ b/Source/Core/Transforms.js
@@ -794,14 +794,17 @@ define([
return result;
};
+ var swizzleMatrix = new Matrix4(
+ 0.0, 0.0, 1.0, 0.0,
+ 1.0, 0.0, 0.0, 0.0,
+ 0.0, 1.0, 0.0, 0.0,
+ 0.0, 0.0, 0.0, 1.0
+ );
+
var scratchCartographic = new Cartographic();
var scratchCartesian3Projection = new Cartesian3();
- var scratchCartesian3 = new Cartesian3();
- var scratchCartesian4Origin = new Cartesian4();
- var scratchCartesian4NewOrigin = new Cartesian4();
- var scratchCartesian4NewXAxis = new Cartesian4();
- var scratchCartesian4NewYAxis = new Cartesian4();
- var scratchCartesian4NewZAxis = new Cartesian4();
+ var scratchCenter = new Cartesian3();
+ var scratchRotation = new Matrix3();
var scratchFromENU = new Matrix4();
var scratchToENU = new Matrix4();
@@ -821,60 +824,25 @@ define([
}
//>>includeEnd('debug');
+ var rtcCenter = Matrix4.getTranslation(matrix, scratchCenter);
var ellipsoid = projection.ellipsoid;
- var origin = Matrix4.getColumn(matrix, 3, scratchCartesian4Origin);
- var cartographic = ellipsoid.cartesianToCartographic(origin, scratchCartographic);
+ // Get the 2D Center
+ var cartographic = ellipsoid.cartesianToCartographic(rtcCenter, scratchCartographic);
+ var projectedPosition = projection.project(cartographic, scratchCartesian3Projection);
+ Cartesian3.fromElements(projectedPosition.z, projectedPosition.x, projectedPosition.y, projectedPosition);
- var fromENU = Transforms.eastNorthUpToFixedFrame(origin, ellipsoid, scratchFromENU);
+ // Assuming the instance are positioned in WGS84, invert the WGS84 transform to get the local transform and then convert to 2D
+ var fromENU = Transforms.eastNorthUpToFixedFrame(rtcCenter, ellipsoid, scratchFromENU);
var toENU = Matrix4.inverseTransformation(fromENU, scratchToENU);
-
- var projectedPosition = projection.project(cartographic, scratchCartesian3Projection);
- var newOrigin = scratchCartesian4NewOrigin;
- newOrigin.x = projectedPosition.z;
- newOrigin.y = projectedPosition.x;
- newOrigin.z = projectedPosition.y;
- newOrigin.w = 1.0;
-
- var xAxis = Matrix4.getColumn(matrix, 0, scratchCartesian3);
- var xScale = Cartesian3.magnitude(xAxis);
- var newXAxis = Matrix4.multiplyByVector(toENU, xAxis, scratchCartesian4NewXAxis);
- Cartesian4.fromElements(newXAxis.z, newXAxis.x, newXAxis.y, 0.0, newXAxis);
-
- var yAxis = Matrix4.getColumn(matrix, 1, scratchCartesian3);
- var yScale = Cartesian3.magnitude(yAxis);
- var newYAxis = Matrix4.multiplyByVector(toENU, yAxis, scratchCartesian4NewYAxis);
- Cartesian4.fromElements(newYAxis.z, newYAxis.x, newYAxis.y, 0.0, newYAxis);
-
- var zAxis = Matrix4.getColumn(matrix, 2, scratchCartesian3);
- var zScale = Cartesian3.magnitude(zAxis);
-
- var newZAxis = scratchCartesian4NewZAxis;
- Cartesian3.cross(newXAxis, newYAxis, newZAxis);
- Cartesian3.normalize(newZAxis, newZAxis);
- Cartesian3.cross(newYAxis, newZAxis, newXAxis);
- Cartesian3.normalize(newXAxis, newXAxis);
- Cartesian3.cross(newZAxis, newXAxis, newYAxis);
- Cartesian3.normalize(newYAxis, newYAxis);
-
- Cartesian3.multiplyByScalar(newXAxis, xScale, newXAxis);
- Cartesian3.multiplyByScalar(newYAxis, yScale, newYAxis);
- Cartesian3.multiplyByScalar(newZAxis, zScale, newZAxis);
-
- Matrix4.setColumn(result, 0, newXAxis, result);
- Matrix4.setColumn(result, 1, newYAxis, result);
- Matrix4.setColumn(result, 2, newZAxis, result);
- Matrix4.setColumn(result, 3, newOrigin, result);
+ var rotation = Matrix4.getRotation(matrix, scratchRotation);
+ var local = Matrix4.multiplyByMatrix3(toENU, rotation, result);
+ Matrix4.multiply(swizzleMatrix, local, result); // Swap x, y, z for 2D
+ Matrix4.setTranslation(result, projectedPosition, result); // Use the projected center
return result;
};
- var swizzleMatrix = new Matrix4(
- 0.0, 0.0, 1.0, 0.0,
- 1.0, 0.0, 0.0, 0.0,
- 0.0, 1.0, 0.0, 0.0,
- 0.0, 0.0, 0.0, 1.0);
-
/**
* @private
*/
@@ -898,13 +866,9 @@ define([
var cartographic = ellipsoid.cartesianToCartographic(center, scratchCartographic);
var projectedPosition = projection.project(cartographic, scratchCartesian3Projection);
- var newOrigin = scratchCartesian4NewOrigin;
- newOrigin.x = projectedPosition.z;
- newOrigin.y = projectedPosition.x;
- newOrigin.z = projectedPosition.y;
- newOrigin.w = 1.0;
+ Cartesian3.fromElements(projectedPosition.z, projectedPosition.x, projectedPosition.y, projectedPosition);
- var translation = Matrix4.fromTranslation(newOrigin, scratchFromENU);
+ var translation = Matrix4.fromTranslation(projectedPosition, scratchFromENU);
Matrix4.multiply(swizzleMatrix, toENU, result);
Matrix4.multiply(translation, result, result);
diff --git a/Source/Core/WebMercatorTilingScheme.js b/Source/Core/WebMercatorTilingScheme.js
index 1790351242cf..5d347d2ad086 100644
--- a/Source/Core/WebMercatorTilingScheme.js
+++ b/Source/Core/WebMercatorTilingScheme.js
@@ -121,7 +121,7 @@ define([
};
/**
- * Transforms an rectangle specified in geodetic radians to the native coordinate system
+ * Transforms a rectangle specified in geodetic radians to the native coordinate system
* of this tiling scheme.
*
* @param {Rectangle} rectangle The rectangle to transform.
@@ -147,7 +147,7 @@ define([
};
/**
- * Converts tile x, y coordinates and level to an rectangle expressed in the native coordinates
+ * Converts tile x, y coordinates and level to a rectangle expressed in the native coordinates
* of the tiling scheme.
*
* @param {Number} x The integer x coordinate of the tile.
diff --git a/Source/Core/arrayFill.js b/Source/Core/arrayFill.js
new file mode 100644
index 000000000000..ac6d36ce4a2a
--- /dev/null
+++ b/Source/Core/arrayFill.js
@@ -0,0 +1,56 @@
+/*global define*/
+define([
+ './Check',
+ './defaultValue',
+ './defined'
+ ], function(
+ Check,
+ defaultValue,
+ defined) {
+ 'use strict';
+
+ /**
+ * Fill an array or a portion of an array with a given value.
+ *
+ * @param {Array} array The array to fill.
+ * @param {*} value The value to fill the array with.
+ * @param {Number} [start=0] The index to start filling at.
+ * @param {Number} [end=array.length] The index to end stop at.
+ *
+ * @returns {Array} The resulting array.
+ * @private
+ */
+ function arrayFill(array, value, start, end) {
+ //>>includeStart('debug', pragmas.debug);
+ Check.defined('array', array);
+ Check.defined('value', value);
+ if (defined(start)) {
+ Check.typeOf.number('start', start);
+ }
+ if (defined(end)) {
+ Check.typeOf.number('end', end);
+ }
+ //>>includeEnd('debug');
+
+ if (typeof array.fill === 'function') {
+ return array.fill(value, start, end);
+ }
+
+ var length = array.length >>> 0;
+ var relativeStart = defaultValue(start, 0);
+ // If negative, find wrap around position
+ var k = (relativeStart < 0) ? Math.max(length + relativeStart, 0) : Math.min(relativeStart, length);
+ var relativeEnd = defaultValue(end, length);
+ // If negative, find wrap around position
+ var final = (relativeEnd < 0) ? Math.max(length + relativeEnd, 0) : Math.min(relativeEnd, length);
+
+ // Fill array accordingly
+ while (k < final) {
+ array[k] = value;
+ k++;
+ }
+ return array;
+ }
+
+ return arrayFill;
+});
diff --git a/Source/Core/isDataUri.js b/Source/Core/isDataUri.js
new file mode 100644
index 000000000000..116630ced119
--- /dev/null
+++ b/Source/Core/isDataUri.js
@@ -0,0 +1,29 @@
+/*global define*/
+define([
+ './defined'
+ ], function(
+ defined) {
+ 'use strict';
+
+ var dataUriRegex = /^data:/i;
+
+ /**
+ * Determines if the specified uri is a data uri.
+ *
+ * @exports isDataUri
+ *
+ * @param {String} uri The uri to test.
+ * @returns {Boolean} true when the uri is a data uri; otherwise, false.
+ *
+ * @private
+ */
+ function isDataUri(uri) {
+ if (defined(uri)) {
+ return dataUriRegex.test(uri);
+ }
+
+ return false;
+ }
+
+ return isDataUri;
+});
diff --git a/Source/Core/joinUrls.js b/Source/Core/joinUrls.js
index 0eeab040e0a4..3a47587edbd6 100644
--- a/Source/Core/joinUrls.js
+++ b/Source/Core/joinUrls.js
@@ -18,6 +18,8 @@ define([
* @param {String|Uri} first The base URL.
* @param {String|Uri} second The URL path to join to the base URL. If this URL is absolute, it is returned unmodified.
* @param {Boolean} [appendSlash=true] The boolean determining whether there should be a forward slash between first and second.
+ *
+ * @return {String} The combined url
* @private
*/
function joinUrls(first, second, appendSlash) {
@@ -40,6 +42,16 @@ define([
second = new Uri(second);
}
+ // Don't try to join a data uri
+ if (first.scheme === 'data') {
+ return first.toString();
+ }
+
+ // Don't try to join a data uri
+ if (second.scheme === 'data') {
+ return second.toString();
+ }
+
// Uri.isAbsolute returns false for a URL like '//foo.com'. So if we have an authority but
// not a scheme, add a scheme matching the page's scheme.
if (defined(second.authority) && !defined(second.scheme)) {
diff --git a/Source/Core/loadKTX.js b/Source/Core/loadKTX.js
index 997f669b9dd9..5baa4b493dfc 100644
--- a/Source/Core/loadKTX.js
+++ b/Source/Core/loadKTX.js
@@ -209,7 +209,7 @@ define([
// Only use the level 0 mipmap
if (PixelFormat.isCompressedFormat(glInternalFormat) && numberOfMipmapLevels > 1) {
- var levelSize = PixelFormat.compressedTextureSize(glInternalFormat, pixelWidth, pixelHeight);
+ var levelSize = PixelFormat.compressedTextureSizeInBytes(glInternalFormat, pixelWidth, pixelHeight);
texture = texture.slice(0, levelSize);
}
diff --git a/Source/DataSources/PropertyBag.js b/Source/DataSources/PropertyBag.js
index 507f4b0b371a..5bd13dfbd397 100644
--- a/Source/DataSources/PropertyBag.js
+++ b/Source/DataSources/PropertyBag.js
@@ -102,7 +102,7 @@ define([
* Adds a property to this object.
*
* @param {String} propertyName The name of the property to add.
- * @param {Any} [value] The value of the new property, if provided.
+ * @param {*} [value] The value of the new property, if provided.
* @param {Function} [createPropertyCallback] A function that will be called when the value of this new property is set to a value that is not a Property.
*
* @exception {DeveloperError} "propertyName" is already a registered property.
diff --git a/Source/Renderer/ClearCommand.js b/Source/Renderer/ClearCommand.js
index 8df9098f2e54..898282117f65 100644
--- a/Source/Renderer/ClearCommand.js
+++ b/Source/Renderer/ClearCommand.js
@@ -77,6 +77,15 @@ define([
* @see Scene#debugCommandFilter
*/
this.owner = options.owner;
+
+ /**
+ * The pass in which to run this command.
+ *
+ * @type {Pass}
+ *
+ * @default undefined
+ */
+ this.pass = options.pass;
}
/**
diff --git a/Source/Renderer/CubeMap.js b/Source/Renderer/CubeMap.js
index 4947d22d9e72..8f29baa1343b 100644
--- a/Source/Renderer/CubeMap.js
+++ b/Source/Renderer/CubeMap.js
@@ -108,6 +108,8 @@ define([
}
//>>includeEnd('debug');
+ var sizeInBytes = PixelFormat.textureSizeInBytes(pixelFormat, pixelDatatype, size, size) * 6;
+
// Use premultiplied alpha for opaque textures should perform better on Chrome:
// http://media.tojicode.com/webglCamp4/#20
var preMultiplyAlpha = options.preMultiplyAlpha || ((pixelFormat === PixelFormat.RGB) || (pixelFormat === PixelFormat.LUMINANCE));
@@ -156,6 +158,8 @@ define([
this._pixelFormat = pixelFormat;
this._pixelDatatype = pixelDatatype;
this._size = size;
+ this._hasMipmap = false;
+ this._sizeInBytes = sizeInBytes;
this._preMultiplyAlpha = preMultiplyAlpha;
this._flipY = flipY;
this._sampler = undefined;
@@ -253,11 +257,19 @@ define([
return this._size;
}
},
- height: {
+ height : {
get : function() {
return this._size;
}
},
+ sizeInBytes : {
+ get : function() {
+ if (this._hasMipmap) {
+ return Math.floor(this._sizeInBytes * 4 / 3);
+ }
+ return this._sizeInBytes;
+ }
+ },
preMultiplyAlpha : {
get : function() {
return this._preMultiplyAlpha;
@@ -306,6 +318,8 @@ define([
}
//>>includeEnd('debug');
+ this._hasMipmap = true;
+
var gl = this._gl;
var target = this._textureTarget;
gl.hint(gl.GENERATE_MIPMAP_HINT, hint);
diff --git a/Source/Renderer/PixelDatatype.js b/Source/Renderer/PixelDatatype.js
index 96358380f8c9..ad5b4846a8ee 100644
--- a/Source/Renderer/PixelDatatype.js
+++ b/Source/Renderer/PixelDatatype.js
@@ -20,6 +20,29 @@ define([
UNSIGNED_SHORT_5_5_5_1 : WebGLConstants.UNSIGNED_SHORT_5_5_5_1,
UNSIGNED_SHORT_5_6_5 : WebGLConstants.UNSIGNED_SHORT_5_6_5,
+ isPacked : function(pixelDatatype) {
+ return pixelDatatype === PixelDatatype.UNSIGNED_INT_24_8 ||
+ pixelDatatype === PixelDatatype.UNSIGNED_SHORT_4_4_4_4 ||
+ pixelDatatype === PixelDatatype.UNSIGNED_SHORT_5_5_5_1 ||
+ pixelDatatype === PixelDatatype.UNSIGNED_SHORT_5_6_5;
+ },
+
+ sizeInBytes : function(pixelDatatype) {
+ switch (pixelDatatype) {
+ case PixelDatatype.UNSIGNED_BYTE:
+ return 1;
+ case PixelDatatype.UNSIGNED_SHORT:
+ case PixelDatatype.UNSIGNED_SHORT_4_4_4_4:
+ case PixelDatatype.UNSIGNED_SHORT_5_5_5_1:
+ case PixelDatatype.UNSIGNED_SHORT_5_6_5:
+ return 2;
+ case PixelDatatype.UNSIGNED_INT:
+ case PixelDatatype.FLOAT:
+ case PixelDatatype.UNSIGNED_INT_24_8:
+ return 4;
+ }
+ },
+
validate : function(pixelDatatype) {
return ((pixelDatatype === PixelDatatype.UNSIGNED_BYTE) ||
(pixelDatatype === PixelDatatype.UNSIGNED_SHORT) ||
diff --git a/Source/Renderer/ShaderSource.js b/Source/Renderer/ShaderSource.js
index 10ec9a7fcd24..09a35221884a 100644
--- a/Source/Renderer/ShaderSource.js
+++ b/Source/Renderer/ShaderSource.js
@@ -280,7 +280,7 @@ define([
this.pickColorQualifier = pickColorQualifier;
this.includeBuiltIns = defaultValue(options.includeBuiltIns, true);
}
-
+
ShaderSource.prototype.clone = function() {
return new ShaderSource({
sources : this.sources,
diff --git a/Source/Renderer/Texture.js b/Source/Renderer/Texture.js
index 242c91bfba4d..a330e3aecfc0 100644
--- a/Source/Renderer/Texture.js
+++ b/Source/Renderer/Texture.js
@@ -139,7 +139,7 @@ define([
throw new DeveloperError('When options.pixelFormat is ETC1 compressed, this WebGL implementation must support the WEBGL_texture_compression_etc1 extension. Check context.etc1.');
}
- if (PixelFormat.compressedTextureSize(internalFormat, width, height) !== source.arrayBufferView.byteLength) {
+ if (PixelFormat.compressedTextureSizeInBytes(internalFormat, width, height) !== source.arrayBufferView.byteLength) {
throw new DeveloperError('The byte length of the array buffer is invalid for the compressed texture with the given width and height.');
}
}
@@ -189,6 +189,13 @@ define([
}
gl.bindTexture(textureTarget, null);
+ var sizeInBytes;
+ if (isCompressed) {
+ sizeInBytes = PixelFormat.compressedTextureSizeInBytes(pixelFormat, width, height);
+ } else {
+ sizeInBytes = PixelFormat.textureSizeInBytes(pixelFormat, pixelDatatype, width, height);
+ }
+
this._context = context;
this._textureFilterAnisotropic = context._textureFilterAnisotropic;
this._textureTarget = textureTarget;
@@ -198,6 +205,8 @@ define([
this._width = width;
this._height = height;
this._dimensions = new Cartesian2(width, height);
+ this._hasMipmap = false;
+ this._sizeInBytes = sizeInBytes;
this._preMultiplyAlpha = preMultiplyAlpha;
this._flipY = flipY;
this._sampler = undefined;
@@ -380,6 +389,14 @@ define([
return this._height;
}
},
+ sizeInBytes : {
+ get : function() {
+ if (this._hasMipmap) {
+ return Math.floor(this._sizeInBytes * 4 / 3);
+ }
+ return this._sizeInBytes;
+ }
+ },
_target : {
get : function() {
return this._textureTarget;
@@ -527,6 +544,7 @@ define([
* @param {MipmapHint} [hint=MipmapHint.DONT_CARE] optional.
*
* @exception {DeveloperError} Cannot call generateMipmap when the texture pixel format is DEPTH_COMPONENT or DEPTH_STENCIL.
+ * @exception {DeveloperError} Cannot call generateMipmap when the texture pixel format is a compressed format.
* @exception {DeveloperError} hint is invalid.
* @exception {DeveloperError} This texture's width must be a power of two to call generateMipmap().
* @exception {DeveloperError} This texture's height must be a power of two to call generateMipmap().
@@ -553,6 +571,8 @@ define([
}
//>>includeEnd('debug');
+ this._hasMipmap = true;
+
var gl = this._context._gl;
var target = this._textureTarget;
diff --git a/Source/Scene/Camera.js b/Source/Scene/Camera.js
index 67b6f8c6e628..0855bd10d864 100644
--- a/Source/Scene/Camera.js
+++ b/Source/Scene/Camera.js
@@ -2307,7 +2307,7 @@ define([
}
/**
- * Get the camera position needed to view an rectangle on an ellipsoid or map
+ * Get the camera position needed to view a rectangle on an ellipsoid or map
*
* @param {Rectangle} rectangle The rectangle to view.
* @param {Cartesian3} [result] The camera position needed to view the rectangle
diff --git a/Source/Scene/Fog.js b/Source/Scene/Fog.js
index 15d9d4b2aa84..7e8ae1126354 100644
--- a/Source/Scene/Fog.js
+++ b/Source/Scene/Fog.js
@@ -131,7 +131,7 @@ define([
// Fade fog in as the camera tilts toward the horizon.
var positionNormal = Cartesian3.normalize(camera.positionWC, scratchPositionNormal);
- var dot = CesiumMath.clamp(Cartesian3.dot(camera.directionWC, positionNormal), 0.0, 1.0);
+ var dot = Math.abs(Cartesian3.dot(camera.directionWC, positionNormal));
density *= 1.0 - dot;
frameState.fog.density = density;
diff --git a/Source/Scene/FrameState.js b/Source/Scene/FrameState.js
index 6cd842411391..962ed8ea890f 100644
--- a/Source/Scene/FrameState.js
+++ b/Source/Scene/FrameState.js
@@ -11,13 +11,14 @@ define([
*
* @param {Context} context The rendering context.
* @param {CreditDisplay} creditDisplay Handles adding and removing credits from an HTML element
+ * @param {JobScheduler} jobScheduler The job scheduler
*
* @alias FrameState
* @constructor
*
* @private
*/
- function FrameState(context, creditDisplay) {
+ function FrameState(context, creditDisplay, jobScheduler) {
/**
* The rendering context.
* @type {Context}
@@ -67,6 +68,13 @@ define([
*/
this.time = undefined;
+ /**
+ * The job scheduler.
+ *
+ * @type {JobScheduler}
+ */
+ this.jobScheduler = jobScheduler;
+
/**
* The map projection to use in 2D and Columbus View modes.
*
@@ -181,9 +189,10 @@ define([
};
/**
- * A scalar used to exaggerate the terrain.
- * @type {Number}
- */
+ * A scalar used to exaggerate the terrain.
+ * @type {Number}
+ * @default 1.0
+ */
this.terrainExaggeration = 1.0;
this.shadowHints = {
@@ -266,6 +275,10 @@ define([
this.minimumDisableDepthTestDistance = undefined;
}
+ FrameState.prototype.addCommand = function(command) {
+ this.commandList.push(command);
+ };
+
/**
* A function that will be called at the end of the frame.
* @callback FrameState~AfterRenderCallback
diff --git a/Source/Scene/GlobeSurfaceTile.js b/Source/Scene/GlobeSurfaceTile.js
index 3d0a226758ac..d386273ab938 100644
--- a/Source/Scene/GlobeSurfaceTile.js
+++ b/Source/Scene/GlobeSurfaceTile.js
@@ -18,6 +18,7 @@ define([
'./QuadtreeTileLoadState',
'./SceneMode',
'./TerrainState',
+ './TileBoundingRegion',
'./TileTerrain'
], function(
BoundingSphere,
@@ -38,6 +39,7 @@ define([
QuadtreeTileLoadState,
SceneMode,
TerrainState,
+ TileBoundingRegion,
TileTerrain) {
'use strict';
@@ -69,7 +71,7 @@ define([
this.boundingSphere3D = new BoundingSphere();
this.boundingSphere2D = new BoundingSphere();
this.orientedBoundingBox = undefined;
- this.tileBoundingBox = undefined;
+ this.tileBoundingRegion = undefined;
this.occludeePointInScaledSpace = new Cartesian3();
this.loadedTerrain = undefined;
diff --git a/Source/Scene/GlobeSurfaceTileProvider.js b/Source/Scene/GlobeSurfaceTileProvider.js
index 15b234d32be3..c9434ae19d46 100644
--- a/Source/Scene/GlobeSurfaceTileProvider.js
+++ b/Source/Scene/GlobeSurfaceTileProvider.js
@@ -570,8 +570,8 @@ define([
*/
GlobeSurfaceTileProvider.prototype.computeDistanceToTile = function(tile, frameState) {
var surfaceTile = tile.data;
- var tileBoundingBox = surfaceTile.tileBoundingBox;
- return tileBoundingBox.distanceToCamera(frameState);
+ var tileBoundingRegion = surfaceTile.tileBoundingRegion;
+ return tileBoundingRegion.distanceToCamera(frameState);
};
/**
diff --git a/Source/Scene/JobScheduler.js b/Source/Scene/JobScheduler.js
new file mode 100644
index 000000000000..cc39fafcb580
--- /dev/null
+++ b/Source/Scene/JobScheduler.js
@@ -0,0 +1,194 @@
+/*global define*/
+define([
+ '../Core/defined',
+ '../Core/defineProperties',
+ '../Core/DeveloperError',
+ '../Core/getTimestamp',
+ './JobType'
+ ], function(
+ defined,
+ defineProperties,
+ DeveloperError,
+ getTimestamp,
+ JobType) {
+ 'use strict';
+
+ function JobTypeBudget(total) {
+ /**
+ * Total budget, in milliseconds, allowed for one frame
+ */
+ this._total = total;
+
+ /**
+ * Time, in milliseconds, used so far during this frame
+ */
+ this.usedThisFrame = 0.0;
+
+ /**
+ * Time, in milliseconds, that other job types stole this frame
+ */
+ this.stolenFromMeThisFrame = 0.0;
+
+ /**
+ * Indicates if this job type was starved this frame, i.e., a job
+ * tried to run but didn't have budget
+ */
+ this.starvedThisFrame = false;
+
+ /**
+ * Indicates if this job was starved last frame. This prevents it
+ * from being stolen from this frame.
+ */
+ this.starvedLastFrame = false;
+ }
+
+ defineProperties(JobTypeBudget.prototype, {
+ total : {
+ get : function() {
+ return this._total;
+ }
+ }
+ });
+
+ /**
+ * Engine for time slicing jobs during a frame to amortize work over multiple frames. This supports:
+ *
+ * -
+ * Separate budgets for different job types, e.g., texture, shader program, and buffer creation. This
+ * allows all job types to make progress each frame.
+ *
+ * -
+ * Stealing from other jobs type budgets if they were not exhausted in the previous frame. This allows
+ * using the entire budget for all job types each frame even if, for example, all the jobs are the same type.
+ *
+ * -
+ * Guaranteed progress on all job types each frame, even if it means exceeding the total budget for the frame.
+ * This prevents, for example, several expensive texture uploads over many frames from prevent a shader compile.
+ *
+ *
+ *
+ * @private
+ */
+ function JobScheduler(budgets) {
+ //>>includeStart('debug', pragmas.debug);
+ if (defined(budgets) && (budgets.length !== JobType.NUMBER_OF_JOB_TYPES)) {
+ throw new DeveloperError('A budget must be specified for each job type; budgets.length should equal JobType.NUMBER_OF_JOB_TYPES.');
+ }
+ //>>includeEnd('debug');
+
+ // Total for defaults is half of of one frame at 10 fps
+ var jobBudgets = new Array(JobType.NUMBER_OF_JOB_TYPES);
+ jobBudgets[JobType.TEXTURE] = new JobTypeBudget(defined(budgets) ? budgets[JobType.TEXTURE] : 10.0);
+ // On cache miss, this most likely only allows one shader compile per frame
+ jobBudgets[JobType.PROGRAM] = new JobTypeBudget(defined(budgets) ? budgets[JobType.PROGRAM] : 10.0);
+ jobBudgets[JobType.BUFFER] = new JobTypeBudget(defined(budgets) ? budgets[JobType.BUFFER] : 30.0);
+
+ var length = jobBudgets.length;
+ var i;
+
+ var totalBudget = 0.0;
+ for (i = 0; i < length; ++i) {
+ totalBudget += jobBudgets[i].total;
+ }
+
+ var executedThisFrame = new Array(length);
+ for (i = 0; i < length; ++i) {
+ executedThisFrame[i] = false;
+ }
+
+ this._totalBudget = totalBudget;
+ this._totalUsedThisFrame = 0.0;
+ this._budgets = jobBudgets;
+ this._executedThisFrame = executedThisFrame;
+ }
+
+ // For unit testing
+ JobScheduler.getTimestamp = getTimestamp;
+
+ defineProperties(JobScheduler.prototype, {
+ totalBudget : {
+ get : function() {
+ return this._totalBudget;
+ }
+ }
+ });
+
+ JobScheduler.prototype.disableThisFrame = function() {
+ // Prevent jobs from running this frame
+ this._totalUsedThisFrame = this._totalBudget;
+ };
+
+ JobScheduler.prototype.resetBudgets = function() {
+ var budgets = this._budgets;
+ var length = budgets.length;
+ for (var i = 0; i < length; ++i) {
+ var budget = budgets[i];
+ budget.starvedLastFrame = budget.starvedThisFrame;
+ budget.starvedThisFrame = false;
+ budget.usedThisFrame = 0.0;
+ budget.stolenFromMeThisFrame = 0.0;
+ }
+ this._totalUsedThisFrame = 0.0;
+ };
+
+ JobScheduler.prototype.execute = function(job, jobType) {
+ var budgets = this._budgets;
+ var budget = budgets[jobType];
+
+ // This ensures each job type makes progress each frame by executing at least once
+ var progressThisFrame = this._executedThisFrame[jobType];
+
+ if ((this._totalUsedThisFrame >= this._totalBudget) && progressThisFrame) {
+ // No budget left this frame for jobs of any type
+ budget.starvedThisFrame = true;
+ return false;
+ }
+
+ var stolenBudget;
+
+ if ((budget.usedThisFrame + budget.stolenFromMeThisFrame >= budget.total)) {
+ // No budget remaining for jobs of this type. Try to steal from other job types.
+ var length = budgets.length;
+ for (var i = 0; i < length; ++i) {
+ stolenBudget = budgets[i];
+
+ // Steal from this budget if it has time left and it wasn't starved last fame
+ if ((stolenBudget.usedThisFrame + stolenBudget.stolenFromMeThisFrame < stolenBudget.total) &&
+ (!stolenBudget.starvedLastFrame)) {
+ break;
+ }
+ }
+
+ if ((i === length) && progressThisFrame) {
+ // No other job types can give up their budget this frame, and
+ // this job type already progressed this frame
+ return false;
+ }
+
+ if (progressThisFrame) {
+ // It is considered "starved" even if it executes using stolen time so that
+ // next frame, no other job types can steal time from it.
+ budget.starvedThisFrame = true;
+ }
+ }
+
+ var startTime = JobScheduler.getTimestamp();
+ job.execute();
+ var duration = JobScheduler.getTimestamp() - startTime;
+
+ // Track both time remaining for this job type and all jobs
+ // so budget stealing does send us way over the total budget.
+ this._totalUsedThisFrame += duration;
+
+ if (stolenBudget) {
+ stolenBudget.stolenFromMeThisFrame += duration;
+ } else {
+ budget.usedThisFrame += duration;
+ }
+ this._executedThisFrame[jobType] = true;
+
+ return true;
+ };
+
+ return JobScheduler;
+});
diff --git a/Source/Scene/JobType.js b/Source/Scene/JobType.js
new file mode 100644
index 000000000000..b5fd6d1d1ede
--- /dev/null
+++ b/Source/Scene/JobType.js
@@ -0,0 +1,19 @@
+/*global define*/
+define([
+ '../Core/freezeObject'
+ ], function(
+ freezeObject) {
+ 'use strict';
+
+ /**
+ * @private
+ */
+ var JobType = {
+ TEXTURE : 0,
+ PROGRAM : 1,
+ BUFFER : 2,
+ NUMBER_OF_JOB_TYPES : 3
+ };
+
+ return freezeObject(JobType);
+});
diff --git a/Source/Scene/Model.js b/Source/Scene/Model.js
index 98f23b653444..b82d18a03ea5 100644
--- a/Source/Scene/Model.js
+++ b/Source/Scene/Model.js
@@ -20,6 +20,7 @@ define([
'../Core/getMagic',
'../Core/getStringFromTypedArray',
'../Core/IndexDatatype',
+ '../Core/joinUrls',
'../Core/loadArrayBuffer',
'../Core/loadCRN',
'../Core/loadImage',
@@ -58,6 +59,7 @@ define([
'./getAttributeOrUniformBySemantic',
'./getBinaryAccessor',
'./HeightReference',
+ './JobType',
'./ModelAnimationCache',
'./ModelAnimationCollection',
'./ModelMaterial',
@@ -87,6 +89,7 @@ define([
getMagic,
getStringFromTypedArray,
IndexDatatype,
+ joinUrls,
loadArrayBuffer,
loadCRN,
loadImage,
@@ -125,6 +128,7 @@ define([
getAttributeOrUniformBySemantic,
getBinaryAccessor,
HeightReference,
+ JobType,
ModelAnimationCache,
ModelAnimationCollection,
ModelMaterial,
@@ -154,7 +158,8 @@ define([
var defaultModelAccept = 'model/vnd.gltf.binary,model/vnd.gltf+json,model/gltf.binary,model/gltf+json;q=0.8,application/json;q=0.2,*/*;q=0.01';
function LoadResources() {
- this.buffersToCreate = new Queue();
+ this.vertexBuffersToCreate = new Queue();
+ this.indexBuffersToCreate = new Queue();
this.buffers = {};
this.pendingBufferLoads = 0;
@@ -188,7 +193,9 @@ define([
};
LoadResources.prototype.finishedBuffersCreation = function() {
- return ((this.pendingBufferLoads === 0) && (this.buffersToCreate.length === 0));
+ return ((this.pendingBufferLoads === 0) &&
+ (this.vertexBuffersToCreate.length === 0) &&
+ (this.indexBuffersToCreate.length === 0));
};
LoadResources.prototype.finishedProgramCreation = function() {
@@ -209,7 +216,8 @@ define([
(this.pendingBufferLoads === 0) &&
(this.pendingShaderLoads === 0);
var finishedResourceCreation =
- (this.buffersToCreate.length === 0) &&
+ (this.vertexBuffersToCreate.length === 0) &&
+ (this.indexBuffersToCreate.length === 0) &&
(this.programsToCreate.length === 0) &&
(this.pendingBufferViewToImage === 0);
@@ -402,10 +410,8 @@ define([
setCachedGltf(this, cachedGltf);
this._basePath = defaultValue(options.basePath, '');
-
- var docUri = new Uri(document.location.href);
- var modelUri = new Uri(this._basePath);
- this._baseUri = modelUri.resolve(docUri);
+ var baseUri = getBaseUri(document.location.href);
+ this._baseUri = joinUrls(baseUri, this._basePath);
/**
* Determines if the model primitive will be shown.
@@ -668,13 +674,18 @@ define([
pickPrograms : {},
silhouettePrograms: {},
textures : {},
-
samplers : {},
renderStates : {}
};
this._cachedRendererResources = undefined;
this._loadRendererResourcesFromCache = false;
+ this._cachedVertexMemorySizeInBytes = 0;
+ this._cachedTextureMemorySizeInBytes = 0;
+ this._vertexMemorySizeInBytes = 0;
+ this._textureMemorySizeInBytes = 0;
+ this._trianglesLength = 0;
+
this._nodeCommands = [];
this._pickIds = [];
@@ -976,6 +987,61 @@ define([
get : function() {
return this._upAxis;
}
+ },
+
+ /**
+ * Gets the model's triangle count.
+ *
+ * @private
+ */
+ trianglesLength : {
+ get : function() {
+ return this._trianglesLength;
+ }
+ },
+
+ /**
+ * Gets the model's vertex memory in bytes. This includes all vertex and index buffers.
+ *
+ * @private
+ */
+ vertexMemorySizeInBytes : {
+ get : function() {
+ return this._vertexMemorySizeInBytes;
+ }
+ },
+
+ /**
+ * Gets the model's texture memory in bytes.
+ *
+ * @private
+ */
+ textureMemorySizeInBytes : {
+ get : function() {
+ return this._textureMemorySizeInBytes;
+ }
+ },
+
+ /**
+ * Gets the model's cached vertex memory in bytes. This includes all vertex and index buffers.
+ *
+ * @private
+ */
+ cachedVertexMemorySizeInBytes : {
+ get : function() {
+ return this._cachedVertexMemorySizeInBytes;
+ }
+ },
+
+ /**
+ * Gets the model's cached texture memory in bytes.
+ *
+ * @private
+ */
+ cachedTextureMemorySizeInBytes : {
+ get : function() {
+ return this._cachedTextureMemorySizeInBytes;
+ }
}
});
@@ -1120,7 +1186,7 @@ define([
var cacheKey = defaultValue(options.cacheKey, getAbsoluteUri(url));
options = clone(options);
- options.basePath = getBaseUri(url);
+ options.basePath = getBaseUri(url, true);
options.cacheKey = cacheKey;
var model = new Model(options);
@@ -1350,8 +1416,7 @@ define([
}
else if (buffer.type === 'arraybuffer') {
++model._loadResources.pendingBufferLoads;
- var uri = new Uri(buffer.uri);
- var bufferPath = uri.resolve(model._baseUri).toString();
+ var bufferPath = joinUrls(model._baseUri, buffer.uri);
loadArrayBuffer(bufferPath).then(bufferLoad(model, id)).otherwise(getFailedLoadFunction(model, 'buffer', bufferPath));
}
}
@@ -1360,20 +1425,52 @@ define([
function parseBufferViews(model) {
var bufferViews = model.gltf.bufferViews;
- for (var id in bufferViews) {
+ var id;
+
+ var vertexBuffersToCreate = model._loadResources.vertexBuffersToCreate;
+
+ // Only ARRAY_BUFFER here. ELEMENT_ARRAY_BUFFER created below.
+ for (id in bufferViews) {
if (bufferViews.hasOwnProperty(id)) {
if (bufferViews[id].target === WebGLConstants.ARRAY_BUFFER) {
- model._loadResources.buffersToCreate.enqueue(id);
+ vertexBuffersToCreate.enqueue(id);
+ }
+ }
+ }
+
+ var indexBuffersToCreate = model._loadResources.indexBuffersToCreate;
+ var indexBufferIds = {};
+
+ // The Cesium Renderer requires knowing the datatype for an index buffer
+ // at creation type, which is not part of the glTF bufferview so loop
+ // through glTF accessors to create the bufferview's index buffer.
+ var accessors = model.gltf.accessors;
+ for (id in accessors) {
+ if (accessors.hasOwnProperty(id)) {
+ var accessor = accessors[id];
+ var bufferViewId = accessor.bufferView;
+ var bufferView = bufferViews[bufferViewId];
+
+ if ((bufferView.target === WebGLConstants.ELEMENT_ARRAY_BUFFER) && !defined(indexBufferIds[bufferViewId])) {
+ indexBufferIds[bufferViewId] = true;
+ indexBuffersToCreate.enqueue({
+ id : bufferViewId,
+ // In theory, several glTF accessors with different componentTypes could
+ // point to the same glTF bufferView, which would break this.
+ // In practice, it is unlikely as it will be UNSIGNED_SHORT.
+ componentType : accessor.componentType
+ });
}
}
}
}
- function shaderLoad(model, id) {
+ function shaderLoad(model, type, id) {
return function(source) {
var loadResources = model._loadResources;
loadResources.shaders[id] = {
source : source,
+ type : type,
bufferView : undefined
};
--loadResources.pendingShaderLoads;
@@ -1401,9 +1498,8 @@ define([
};
} else {
++model._loadResources.pendingShaderLoads;
- var uri = new Uri(shader.uri);
- var shaderPath = uri.resolve(model._baseUri).toString();
- loadText(shaderPath).then(shaderLoad(model, id)).otherwise(getFailedLoadFunction(model, 'shader', shaderPath));
+ var shaderPath = joinUrls(model._baseUri, shader.uri);
+ loadText(shaderPath).then(shaderLoad(model, shader.type, id)).otherwise(getFailedLoadFunction(model, 'shader', shaderPath));
}
}
}
@@ -1500,16 +1596,17 @@ define([
});
} else {
++model._loadResources.pendingTextureLoads;
- uri = new Uri(uri);
- var imagePath = uri.resolve(model._baseUri).toString();
+ var imagePath = joinUrls(model._baseUri, gltfImage.uri);
+ var promise;
if (ktxRegex.test(imagePath)) {
- loadKTX(imagePath).then(imageLoad(model, id)).otherwise(getFailedLoadFunction(model, 'image', imagePath));
+ promise = loadKTX(imagePath);
} else if (crnRegex.test(imagePath)) {
- loadCRN(imagePath).then(imageLoad(model, id)).otherwise(getFailedLoadFunction(model, 'image', imagePath));
+ promise = loadCRN(imagePath);
} else {
- loadImage(imagePath).then(imageLoad(model, id)).otherwise(getFailedLoadFunction(model, 'image', imagePath));
+ promise = loadImage(imagePath);
}
+ promise.then(imageLoad(model, id)).otherwise(getFailedLoadFunction(model, 'image', imagePath));
}
}
}
@@ -1678,53 +1775,117 @@ define([
///////////////////////////////////////////////////////////////////////////
- function createBuffers(model, context) {
+ var CreateVertexBufferJob = function() {
+ this.id = undefined;
+ this.model = undefined;
+ this.context = undefined;
+ };
+
+ CreateVertexBufferJob.prototype.set = function(id, model, context) {
+ this.id = id;
+ this.model = model;
+ this.context = context;
+ };
+
+ CreateVertexBufferJob.prototype.execute = function() {
+ createVertexBuffer(this.id, this.model, this.context);
+ };
+
+ ///////////////////////////////////////////////////////////////////////////
+
+ function createVertexBuffer(bufferViewId, model, context) {
var loadResources = model._loadResources;
+ var bufferViews = model.gltf.bufferViews;
+ var bufferView = bufferViews[bufferViewId];
- if (loadResources.pendingBufferLoads !== 0) {
- return;
- }
+ var vertexBuffer = Buffer.createVertexBuffer({
+ context : context,
+ typedArray : loadResources.getBuffer(bufferView),
+ usage : BufferUsage.STATIC_DRAW
+ });
+ vertexBuffer.vertexArrayDestroyable = false;
+ model._rendererResources.buffers[bufferViewId] = vertexBuffer;
+ model._vertexMemorySizeInBytes += vertexBuffer.sizeInBytes;
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+
+ var CreateIndexBufferJob = function() {
+ this.id = undefined;
+ this.componentType = undefined;
+ this.model = undefined;
+ this.context = undefined;
+ };
+
+ CreateIndexBufferJob.prototype.set = function(id, componentType, model, context) {
+ this.id = id;
+ this.componentType = componentType;
+ this.model = model;
+ this.context = context;
+ };
+
+ CreateIndexBufferJob.prototype.execute = function() {
+ createIndexBuffer(this.id, this.componentType, this.model, this.context);
+ };
+
+ ///////////////////////////////////////////////////////////////////////////
- var bufferView;
+ function createIndexBuffer(bufferViewId, componentType, model, context) {
+ var loadResources = model._loadResources;
var bufferViews = model.gltf.bufferViews;
- var rendererBuffers = model._rendererResources.buffers;
+ var bufferView = bufferViews[bufferViewId];
- while (loadResources.buffersToCreate.length > 0) {
- var bufferViewId = loadResources.buffersToCreate.dequeue();
- bufferView = bufferViews[bufferViewId];
+ var indexBuffer = Buffer.createIndexBuffer({
+ context : context,
+ typedArray : loadResources.getBuffer(bufferView),
+ usage : BufferUsage.STATIC_DRAW,
+ indexDatatype : componentType
+ });
+ indexBuffer.vertexArrayDestroyable = false;
+ model._rendererResources.buffers[bufferViewId] = indexBuffer;
+ model._vertexMemorySizeInBytes += indexBuffer.sizeInBytes;
+ }
- // Only ARRAY_BUFFER here. ELEMENT_ARRAY_BUFFER created below.
- var vertexBuffer = Buffer.createVertexBuffer({
- context : context,
- typedArray : loadResources.getBuffer(bufferView),
- usage : BufferUsage.STATIC_DRAW
- });
- vertexBuffer.vertexArrayDestroyable = false;
- rendererBuffers[bufferViewId] = vertexBuffer;
+ var scratchVertexBufferJob = new CreateVertexBufferJob();
+ var scratchIndexBufferJob = new CreateIndexBufferJob();
+
+ function createBuffers(model, frameState) {
+ var loadResources = model._loadResources;
+
+ if (loadResources.pendingBufferLoads !== 0) {
+ return;
}
- // The Cesium Renderer requires knowing the datatype for an index buffer
- // at creation type, which is not part of the glTF bufferview so loop
- // through glTF accessors to create the bufferview's index buffer.
- var accessors = model.gltf.accessors;
- for (var id in accessors) {
- if (accessors.hasOwnProperty(id)) {
- var accessor = accessors[id];
- bufferView = bufferViews[accessor.bufferView];
+ var context = frameState.context;
+ var vertexBuffersToCreate = loadResources.vertexBuffersToCreate;
+ var indexBuffersToCreate = loadResources.indexBuffersToCreate;
+ var i;
- if ((bufferView.target === WebGLConstants.ELEMENT_ARRAY_BUFFER) && !defined(rendererBuffers[accessor.bufferView])) {
- var indexBuffer = Buffer.createIndexBuffer({
- context : context,
- typedArray : loadResources.getBuffer(bufferView),
- usage : BufferUsage.STATIC_DRAW,
- indexDatatype : accessor.componentType
- });
- indexBuffer.vertexArrayDestroyable = false;
- rendererBuffers[accessor.bufferView] = indexBuffer;
- // In theory, several glTF accessors with different componentTypes could
- // point to the same glTF bufferView, which would break this.
- // In practice, it is unlikely as it will be UNSIGNED_SHORT.
+ if (model.asynchronous) {
+ while (vertexBuffersToCreate.length > 0) {
+ scratchVertexBufferJob.set(vertexBuffersToCreate.peek(), model, context);
+ if (!frameState.jobScheduler.execute(scratchVertexBufferJob, JobType.BUFFER)) {
+ break;
+ }
+ vertexBuffersToCreate.dequeue();
+ }
+
+ while (indexBuffersToCreate.length > 0) {
+ i = indexBuffersToCreate.peek();
+ scratchIndexBufferJob.set(i.id, i.componentType, model, context);
+ if (!frameState.jobScheduler.execute(scratchIndexBufferJob, JobType.BUFFER)) {
+ break;
}
+ indexBuffersToCreate.dequeue();
+ }
+ } else {
+ while (vertexBuffersToCreate.length > 0) {
+ createVertexBuffer(vertexBuffersToCreate.dequeue(), model, context);
+ }
+
+ while (indexBuffersToCreate.length > 0) {
+ i = indexBuffersToCreate.dequeue();
+ createIndexBuffer(i.id, i.componentType, model, context);
}
}
}
@@ -1740,7 +1901,7 @@ define([
// the normal attribute.
for (i = 1; i < length; ++i) {
var attribute = attributes[i];
- if (/position/i.test(attribute)) {
+ if (/pos/i.test(attribute)) {
attributes[i] = attributes[0];
attributes[0] = attribute;
break;
@@ -1934,6 +2095,24 @@ define([
return shader;
}
+ var CreateProgramJob = function() {
+ this.id = undefined;
+ this.model = undefined;
+ this.context = undefined;
+ };
+
+ CreateProgramJob.prototype.set = function(id, model, context) {
+ this.id = id;
+ this.model = model;
+ this.context = context;
+ };
+
+ CreateProgramJob.prototype.execute = function() {
+ createProgram(this.id, this.model, this.context);
+ };
+
+ ///////////////////////////////////////////////////////////////////////////
+
function createProgram(id, model, context) {
var programs = model.gltf.programs;
var shaders = model._loadResources.shaders;
@@ -1989,9 +2168,11 @@ define([
}
}
- function createPrograms(model, context) {
+ var scratchCreateProgramJob = new CreateProgramJob();
+
+ function createPrograms(model, frameState) {
var loadResources = model._loadResources;
- var id;
+ var programsToCreate = loadResources.programsToCreate;
if (loadResources.pendingShaderLoads !== 0) {
return;
@@ -2003,17 +2184,20 @@ define([
return;
}
+ var context = frameState.context;
+
if (model.asynchronous) {
- // Create one program per frame
- if (loadResources.programsToCreate.length > 0) {
- id = loadResources.programsToCreate.dequeue();
- createProgram(id, model, context);
+ while (programsToCreate.length > 0) {
+ scratchCreateProgramJob.set(programsToCreate.peek(), model, context);
+ if (!frameState.jobScheduler.execute(scratchCreateProgramJob, JobType.PROGRAM)) {
+ break;
+ }
+ programsToCreate.dequeue();
}
} else {
// Create all loaded programs this frame
- while (loadResources.programsToCreate.length > 0) {
- id = loadResources.programsToCreate.dequeue();
- createProgram(id, model, context);
+ while (programsToCreate.length > 0) {
+ createProgram(programsToCreate.dequeue(), model, context);
}
}
}
@@ -2083,6 +2267,26 @@ define([
}
}
+ ///////////////////////////////////////////////////////////////////////////
+
+ var CreateTextureJob = function() {
+ this.gltfTexture = undefined;
+ this.model = undefined;
+ this.context = undefined;
+ };
+
+ CreateTextureJob.prototype.set = function(gltfTexture, model, context) {
+ this.gltfTexture = gltfTexture;
+ this.model = model;
+ this.context = context;
+ };
+
+ CreateTextureJob.prototype.execute = function() {
+ createTexture(this.gltfTexture, this.model, this.context);
+ };
+
+ ///////////////////////////////////////////////////////////////////////////
+
function createTexture(gltfTexture, model, context) {
var textures = model.gltf.textures;
var texture = textures[gltfTexture.id];
@@ -2149,23 +2353,27 @@ define([
}
model._rendererResources.textures[gltfTexture.id] = tx;
+ model._textureMemorySizeInBytes += tx.sizeInBytes;
}
- function createTextures(model, context) {
- var loadResources = model._loadResources;
- var gltfTexture;
+ var scratchCreateTextureJob = new CreateTextureJob();
+
+ function createTextures(model, frameState) {
+ var context = frameState.context;
+ var texturesToCreate = model._loadResources.texturesToCreate;
if (model.asynchronous) {
- // Create one texture per frame
- if (loadResources.texturesToCreate.length > 0) {
- gltfTexture = loadResources.texturesToCreate.dequeue();
- createTexture(gltfTexture, model, context);
+ while (texturesToCreate.length > 0) {
+ scratchCreateTextureJob.set(texturesToCreate.peek(), model, context);
+ if (!frameState.jobScheduler.execute(scratchCreateTextureJob, JobType.TEXTURE)) {
+ break;
+ }
+ texturesToCreate.dequeue();
}
} else {
// Create all loaded textures this frame
- while (loadResources.texturesToCreate.length > 0) {
- gltfTexture = loadResources.texturesToCreate.dequeue();
- createTexture(gltfTexture, model, context);
+ while (texturesToCreate.length > 0) {
+ createTexture(texturesToCreate.dequeue(), model, context);
}
}
}
@@ -2178,21 +2386,37 @@ define([
// Retrieve the compiled shader program to assign index values to attributes
var attributeLocations = {};
+ var location;
+ var index;
var technique = techniques[materials[primitive.material].technique];
var parameters = technique.parameters;
var attributes = technique.attributes;
- var programAttributeLocations = model._rendererResources.programs[technique.program].vertexAttributes;
+ var program = model._rendererResources.programs[technique.program];
+ var programVertexAttributes = program.vertexAttributes;
+ var programAttributeLocations = program._attributeLocations;
- // Note: WebGL shader compiler may have optimized and removed some attributes from programAttributeLocations
- for (var location in programAttributeLocations){
- if (programAttributeLocations.hasOwnProperty(location)) {
+ // Note: WebGL shader compiler may have optimized and removed some attributes from programVertexAttributes
+ for (location in programVertexAttributes){
+ if (programVertexAttributes.hasOwnProperty(location)) {
var attribute = attributes[location];
- var index = programAttributeLocations[location].index;
+ index = programVertexAttributes[location].index;
if (defined(attribute)) {
var parameter = parameters[attribute];
attributeLocations[parameter.semantic] = index;
- } else {
- // Pre-created attributes
+ }
+ }
+ }
+
+ // Always add pre-created attributes.
+ // Some pre-created attributes, like per-instance pickIds, may be compiled out of the draw program
+ // but should be included in the list of attribute locations for the pick program.
+ // This is safe to do since programVertexAttributes and programAttributeLocations are equivalent except
+ // that programVertexAttributes optimizes out unused attributes.
+ var precreatedAttributes = model._precreatedAttributes;
+ if (defined(precreatedAttributes)) {
+ for (location in precreatedAttributes) {
+ if (precreatedAttributes.hasOwnProperty(location)) {
+ index = programAttributeLocations[location];
attributeLocations[location] = index;
}
}
@@ -2421,6 +2645,7 @@ define([
// with an attribute that wasn't used and the asset wasn't optimized.
if (defined(attributeLocation)) {
var a = accessors[primitiveAttributes[attributeName]];
+
attributes.push({
index : attributeLocation,
vertexBuffer : rendererBuffers[a.bufferView],
@@ -3113,6 +3338,18 @@ define([
};
}
+ function triangleCountFromPrimitiveIndices(primitive, indicesCount) {
+ switch (primitive.mode) {
+ case PrimitiveType.TRIANGLES:
+ return (indicesCount / 3);
+ case PrimitiveType.TRIANGLE_STRIP:
+ case PrimitiveType.TRIANGLE_FAN:
+ return Math.max(indicesCount - 2, 0);
+ default:
+ return 0;
+ }
+ }
+
function createCommand(model, gltfNode, runtimeNode, context, scene3DOnly) {
var nodeCommands = model._nodeCommands;
var pickIds = model._pickIds;
@@ -3172,6 +3409,9 @@ define([
offset = 0;
}
+ // Update model triangle count using number of indices
+ model._trianglesLength += triangleCountFromPrimitiveIndices(primitive, count);
+
var um = uniformMaps[primitive.material];
var uniformMap = um.uniformMap;
if (defined(um.jointMatrixUniformName)) {
@@ -3381,6 +3621,26 @@ define([
model._runtime.nodes = runtimeNodes;
}
+ function getVertexMemorySizeInBytes(buffers) {
+ var memory = 0;
+ for (var id in buffers) {
+ if (buffers.hasOwnProperty(id)) {
+ memory += buffers[id].sizeInBytes;
+ }
+ }
+ return memory;
+ }
+
+ function getTextureMemorySizeInBytes(textures) {
+ var memory = 0;
+ for (var id in textures) {
+ if (textures.hasOwnProperty(id)) {
+ memory += textures[id].sizeInBytes;
+ }
+ }
+ return memory;
+ }
+
function createResources(model, frameState) {
var context = frameState.context;
var scene3DOnly = frameState.scene3DOnly;
@@ -3402,12 +3662,15 @@ define([
if (defined(model._precreatedAttributes)) {
createVertexArrays(model, context);
}
+
+ model._cachedVertexMemorySizeInBytes += getVertexMemorySizeInBytes(cachedResources.buffers);
+ model._cachedTextureMemorySizeInBytes += getTextureMemorySizeInBytes(cachedResources.textures);
} else {
- createBuffers(model, context); // using glTF bufferViews
- createPrograms(model, context);
+ createBuffers(model, frameState); // using glTF bufferViews
+ createPrograms(model, frameState);
createSamplers(model, context);
loadTexturesFromBufferViews(model);
- createTextures(model, context);
+ createTextures(model, frameState);
}
createSkins(model);
@@ -3457,7 +3720,7 @@ define([
var nodeStack = scratchNodeStack;
var computedModelMatrix = model._computedModelMatrix;
- if (model._mode !== SceneMode.SCENE3D) {
+ if ((model._mode !== SceneMode.SCENE3D) && !model._ignoreCommands) {
var translation = Matrix4.getColumn(computedModelMatrix, 3, scratchComputedTranslation);
if (!Cartesian4.equals(translation, Cartesian4.UNIT_W)) {
computedModelMatrix = Transforms.basisTo2D(projection, computedModelMatrix, scratchComputedMatrixIn2D);
@@ -3590,7 +3853,6 @@ define([
}
}
-
function updatePerNodeShow(model) {
// Totally not worth it, but we could optimize this:
// http://blogs.agi.com/insight3d/index.php/2008/02/13/deletion-in-bounding-volume-hierarchies/
@@ -4008,7 +4270,7 @@ define([
var extensionsUsed = model.gltf.extensionsUsed;
if (defined(extensionsUsed)) {
var extensionsUsedCount = extensionsUsed.length;
- for (var index=0;index idl2D || boundingVolume.center.y - boundingVolume.radius < idl2D)) {
var command2D = translucent ? nc.translucentCommand2D : nc.command2D;
command2D = silhouette ? nc.silhouetteModelCommand2D : command2D;
- commandList.push(command2D);
+ frameState.addCommand(command2D);
}
}
}
@@ -4417,11 +4678,11 @@ define([
for (i = 0; i < length; ++i) {
nc = nodeCommands[i];
if (nc.show) {
- commandList.push(nc.silhouetteColorCommand);
+ frameState.addCommand(nc.silhouetteColorCommand);
boundingVolume = nc.command.boundingVolume;
if (frameState.mode === SceneMode.SCENE2D &&
(boundingVolume.center.y + boundingVolume.radius > idl2D || boundingVolume.center.y - boundingVolume.radius < idl2D)) {
- commandList.push(nc.silhouetteColorCommand2D);
+ frameState.addCommand(nc.silhouetteColorCommand2D);
}
}
}
@@ -4433,12 +4694,12 @@ define([
nc = nodeCommands[i];
if (nc.show) {
var pickCommand = nc.pickCommand;
- commandList.push(pickCommand);
+ frameState.addCommand(pickCommand);
boundingVolume = pickCommand.boundingVolume;
if (frameState.mode === SceneMode.SCENE2D &&
(boundingVolume.center.y + boundingVolume.radius > idl2D || boundingVolume.center.y - boundingVolume.radius < idl2D)) {
- commandList.push(nc.pickCommand2D);
+ frameState.addCommand(nc.pickCommand2D);
}
}
}
diff --git a/Source/Scene/Scene.js b/Source/Scene/Scene.js
index d0bcf630c963..ab89877362d4 100644
--- a/Source/Scene/Scene.js
+++ b/Source/Scene/Scene.js
@@ -55,6 +55,7 @@ define([
'./FrustumCommands',
'./FXAA',
'./GlobeDepth',
+ './JobScheduler',
'./MapMode2D',
'./OIT',
'./OrthographicFrustum',
@@ -129,6 +130,7 @@ define([
FrustumCommands,
FXAA,
GlobeDepth,
+ JobScheduler,
MapMode2D,
OIT,
OrthographicFrustum,
@@ -242,7 +244,8 @@ define([
}
this._id = createGuid();
- this._frameState = new FrameState(context, new CreditDisplay(creditContainer));
+ this._jobScheduler = new JobScheduler();
+ this._frameState = new FrameState(context, new CreditDisplay(creditContainer), this._jobScheduler);
this._frameState.scene3DOnly = defaultValue(options.scene3DOnly, false);
var ps = new PassState(context);
@@ -1343,7 +1346,7 @@ define([
break;
}
- var pass = command instanceof ClearCommand ? Pass.OPAQUE : command.pass;
+ var pass = command.pass;
var index = frustumCommands.indices[pass]++;
frustumCommands.commands[pass][index] = command;
@@ -1679,7 +1682,7 @@ define([
scene._debugVolume = new Primitive({
geometryInstances : new GeometryInstance({
geometry : geometry,
- modelMatrix : Matrix4.multiplyByTranslation(Matrix4.IDENTITY, center, new Matrix4()),
+ modelMatrix : Matrix4.fromTranslation(center),
attributes : {
color : new ColorGeometryInstanceAttribute(1.0, 0.0, 0.0, 1.0)
}
@@ -2571,6 +2574,7 @@ define([
}
scene._preRender.raiseEvent(scene, time);
+ scene._jobScheduler.resetBudgets();
var context = scene.context;
var us = context.uniformState;
@@ -2786,6 +2790,8 @@ define([
this._pickFramebuffer = context.createPickFramebuffer();
}
+ this._jobScheduler.disableThisFrame();
+
// Update with previous frame's number and time, assuming that render is called before picking.
updateFrameState(this, frameState.frameNumber, frameState.time);
frameState.cullingVolume = getPickCullingVolume(this, drawingBufferPosition, rectangleWidth, rectangleHeight);
diff --git a/Source/Scene/TileBoundingBox.js b/Source/Scene/TileBoundingRegion.js
similarity index 72%
rename from Source/Scene/TileBoundingBox.js
rename to Source/Scene/TileBoundingRegion.js
index 9acf9a3ce90e..885d4e535f59 100644
--- a/Source/Scene/TileBoundingBox.js
+++ b/Source/Scene/TileBoundingRegion.js
@@ -1,46 +1,65 @@
/*global define*/
define([
+ '../Core/BoundingSphere',
'../Core/Cartesian3',
'../Core/Cartographic',
+ '../Core/Check',
+ '../Core/ColorGeometryInstanceAttribute',
'../Core/defaultValue',
'../Core/defined',
- '../Core/DeveloperError',
+ '../Core/defineProperties',
'../Core/Ellipsoid',
+ '../Core/GeometryInstance',
'../Core/IntersectionTests',
+ '../Core/Matrix4',
+ '../Core/OrientedBoundingBox',
'../Core/Plane',
'../Core/Ray',
'../Core/Rectangle',
+ '../Core/RectangleOutlineGeometry',
+ './PerInstanceColorAppearance',
+ './Primitive',
'./SceneMode'
], function(
+ BoundingSphere,
Cartesian3,
Cartographic,
+ Check,
+ ColorGeometryInstanceAttribute,
defaultValue,
defined,
- DeveloperError,
+ defineProperties,
Ellipsoid,
+ GeometryInstance,
IntersectionTests,
+ Matrix4,
+ OrientedBoundingBox,
Plane,
Ray,
Rectangle,
+ RectangleOutlineGeometry,
+ PerInstanceColorAppearance,
+ Primitive,
SceneMode) {
'use strict';
/**
+ * A tile bounding volume specified as a longitude/latitude/height region.
+ * @alias TileBoundingRegion
+ * @constructor
+ *
* @param {Object} options Object with the following properties:
- * @param {Rectangle} options.rectangle
- * @param {Number} [options.minimumHeight=0.0]
- * @param {Number} [options.maximumHeight=0.0]
- * @param {Ellipsoid} [options.ellipsoid=Cesium.Ellipsoid.WGS84]
+ * @param {Rectangle} options.rectangle The rectangle specifying the longitude and latitude range of the region.
+ * @param {Number} [options.minimumHeight=0.0] The minimum height of the region.
+ * @param {Number} [options.maximumHeight=0.0] The maximum height of the region.
+ * @param {Ellipsoid} [options.ellipsoid=Cesium.Ellipsoid.WGS84] The ellipsoid.
*
* @private
*/
- var TileBoundingBox = function(options) {
- options = defaultValue(options, defaultValue.EMPTY_OBJECT);
-
+ function TileBoundingRegion(options) {
//>>includeStart('debug', pragmas.debug);
- if (!defined(options.rectangle)) {
- throw new DeveloperError('options.url is required.');
- }
+ Check.typeOf.object('options', options);
+ Check.typeOf.object('options.rectangle', options.rectangle);
//>>includeEnd('debug');
this.rectangle = Rectangle.clone(options.rectangle);
@@ -105,7 +124,41 @@ define([
var ellipsoid = defaultValue(options.ellipsoid, Ellipsoid.WGS84);
computeBox(this, options.rectangle, ellipsoid);
- };
+
+ // An oriented bounding box that encloses this tile's region. This is used to calculate tile visibility.
+ this._orientedBoundingBox = OrientedBoundingBox.fromRectangle(this.rectangle, this.minimumHeight, this.maximumHeight, ellipsoid);
+
+ this._boundingSphere = BoundingSphere.fromOrientedBoundingBox(this._orientedBoundingBox);
+ }
+
+ defineProperties(TileBoundingRegion.prototype, {
+ /**
+ * The underlying bounding volume
+ *
+ * @memberof TileBoundingRegion.prototype
+ *
+ * @type {Object}
+ * @readonly
+ */
+ boundingVolume : {
+ get : function() {
+ return this._orientedBoundingBox;
+ }
+ },
+ /**
+ * The underlying bounding sphere
+ *
+ * @memberof TileBoundingRegion.prototype
+ *
+ * @type {BoundingSphere}
+ * @readonly
+ */
+ boundingSphere : {
+ get : function() {
+ return this._boundingSphere;
+ }
+ }
+ });
var cartesian3Scratch = new Cartesian3();
var cartesian3Scratch2 = new Cartesian3();
@@ -194,10 +247,12 @@ define([
* Gets the distance from the camera to the closest point on the tile. This is used for level-of-detail selection.
*
* @param {FrameState} frameState The state information of the current rendering frame.
- *
* @returns {Number} The distance from the camera to the closest point on the tile, in meters.
*/
- TileBoundingBox.prototype.distanceToCamera = function(frameState) {
+ TileBoundingRegion.prototype.distanceToCamera = function(frameState) {
+ //>>includeStart('debug', pragmas.debug);
+ Check.defined('frameState', frameState);
+ //>>includeEnd('debug');
var camera = frameState.camera;
var cameraCartesianPosition = camera.positionWC;
var cameraCartographicPosition = camera.positionCartographic;
@@ -263,5 +318,56 @@ define([
return Math.sqrt(result);
};
- return TileBoundingBox;
+ /**
+ * Determines which side of a plane this box is located.
+ *
+ * @param {Plane} plane The plane to test against.
+ * @returns {Intersect} {@link Intersect.INSIDE} if the entire box is on the side of the plane
+ * the normal is pointing, {@link Intersect.OUTSIDE} if the entire box is
+ * on the opposite side, and {@link Intersect.INTERSECTING} if the box
+ * intersects the plane.
+ */
+ TileBoundingRegion.prototype.intersectPlane = function(plane) {
+ //>>includeStart('debug', pragmas.debug);
+ Check.defined('plane', plane);
+ //>>includeEnd('debug');
+ return this._orientedBoundingBox.intersectPlane(plane);
+ };
+
+ /**
+ * Creates a debug primitive that shows the outline of the tile bounding region.
+ *
+ * @param {Color} color The desired color of the primitive's mesh
+ * @return {Primitive}
+ */
+ TileBoundingRegion.prototype.createDebugVolume = function(color) {
+ //>>includeStart('debug', pragmas.debug);
+ Check.defined('color', color);
+ //>>includeEnd('debug');
+
+ var modelMatrix = new Matrix4.clone(Matrix4.IDENTITY);
+ var geometry = new RectangleOutlineGeometry({
+ rectangle : this.rectangle,
+ height : this.minimumHeight,
+ extrudedHeight: this.maximumHeight
+ });
+ var instance = new GeometryInstance({
+ geometry : geometry,
+ modelMatrix : modelMatrix,
+ attributes : {
+ color : ColorGeometryInstanceAttribute.fromColor(color)
+ }
+ });
+
+ return new Primitive({
+ geometryInstances : instance,
+ appearance : new PerInstanceColorAppearance({
+ translucent : false,
+ flat : true
+ }),
+ asynchronous : false
+ });
+ };
+
+ return TileBoundingRegion;
});
diff --git a/Source/Scene/TileBoundingSphere.js b/Source/Scene/TileBoundingSphere.js
new file mode 100644
index 000000000000..4c00385fdf4a
--- /dev/null
+++ b/Source/Scene/TileBoundingSphere.js
@@ -0,0 +1,171 @@
+/*global define*/
+define([
+ '../Core/BoundingSphere',
+ '../Core/Cartesian3',
+ '../Core/Check',
+ '../Core/ColorGeometryInstanceAttribute',
+ '../Core/defined',
+ '../Core/defineProperties',
+ '../Core/GeometryInstance',
+ '../Core/Matrix4',
+ '../Core/SphereOutlineGeometry',
+ './PerInstanceColorAppearance',
+ './Primitive'
+ ], function(
+ BoundingSphere,
+ Cartesian3,
+ Check,
+ ColorGeometryInstanceAttribute,
+ defined,
+ defineProperties,
+ GeometryInstance,
+ Matrix4,
+ SphereOutlineGeometry,
+ PerInstanceColorAppearance,
+ Primitive) {
+ 'use strict';
+
+ /**
+ * A tile bounding volume specified as a sphere.
+ * @alias TileBoundingSphere
+ * @constructor
+ *
+ * @param {Cartesian3} [center=Cartesian3.ZERO] The center of the bounding sphere.
+ * @param {Number} [radius=0.0] The radius of the bounding sphere.
+ *
+ * @private
+ */
+ function TileBoundingSphere(center, radius) {
+ this._boundingSphere = new BoundingSphere(center, radius);
+ }
+
+ defineProperties(TileBoundingSphere.prototype, {
+ /**
+ * The center of the bounding sphere
+ *
+ * @memberof TileBoundingSphere.prototype
+ *
+ * @type {Cartesian3}
+ * @readonly
+ */
+ center : {
+ get : function() {
+ return this._boundingSphere.center;
+ }
+ },
+
+ /**
+ * The radius of the bounding sphere
+ *
+ * @memberof TileBoundingSphere.prototype
+ *
+ * @type {Number}
+ * @readonly
+ */
+ radius : {
+ get : function() {
+ return this._boundingSphere.radius;
+ }
+ },
+
+ /**
+ * The underlying bounding volume
+ *
+ * @memberof TileBoundingSphere.prototype
+ *
+ * @type {Object}
+ * @readonly
+ */
+ boundingVolume : {
+ get : function() {
+ return this._boundingSphere;
+ }
+ },
+ /**
+ * The underlying bounding sphere
+ *
+ * @memberof TileBoundingSphere.prototype
+ *
+ * @type {BoundingSphere}
+ * @readonly
+ */
+ boundingSphere : {
+ get : function() {
+ return this._boundingSphere;
+ }
+ }
+ });
+
+ /**
+ * Computes the distance between this bounding sphere and the camera attached to frameState.
+ *
+ * @param {FrameState} frameState The frameState to which the camera is attached.
+ * @returns {Number} The distance between the camera and the bounding sphere in meters. Returns 0 if the camera is inside the bounding volume.
+ *
+ */
+ TileBoundingSphere.prototype.distanceToCamera = function(frameState) {
+ //>>includeStart('debug', pragmas.debug);
+ Check.defined('frameState', frameState);
+ //>>includeEnd('debug');
+ var bs = this._boundingSphere;
+ return Math.max(0.0, Cartesian3.distance(bs.center, frameState.camera.positionWC) - bs.radius);
+ };
+
+ /**
+ * Determines which side of a plane this sphere is located.
+ *
+ * @param {Plane} plane The plane to test against.
+ * @returns {Intersect} {@link Intersect.INSIDE} if the entire sphere is on the side of the plane
+ * the normal is pointing, {@link Intersect.OUTSIDE} if the entire sphere is
+ * on the opposite side, and {@link Intersect.INTERSECTING} if the sphere
+ * intersects the plane.
+ */
+ TileBoundingSphere.prototype.intersectPlane = function(plane) {
+ //>>includeStart('debug', pragmas.debug);
+ Check.defined('plane', plane);
+ //>>includeEnd('debug');
+ return BoundingSphere.intersectPlane(this._boundingSphere, plane);
+ };
+
+ /**
+ * Update the bounding sphere after the tile is transformed.
+ */
+ TileBoundingSphere.prototype.update = function(center, radius) {
+ Cartesian3.clone(center, this._boundingSphere.center);
+ this._boundingSphere.radius = radius;
+ };
+
+ /**
+ * Creates a debug primitive that shows the outline of the sphere.
+ *
+ * @param {Color} color The desired color of the primitive's mesh
+ * @return {Primitive}
+ */
+ TileBoundingSphere.prototype.createDebugVolume = function(color) {
+ //>>includeStart('debug', pragmas.debug);
+ Check.defined('color', color);
+ //>>includeEnd('debug');
+ var geometry = new SphereOutlineGeometry({
+ radius: this.radius
+ });
+ var modelMatrix = Matrix4.fromTranslation(this.center, new Matrix4.clone(Matrix4.IDENTITY));
+ var instance = new GeometryInstance({
+ geometry : geometry,
+ modelMatrix : modelMatrix,
+ attributes : {
+ color : ColorGeometryInstanceAttribute.fromColor(color)
+ }
+ });
+
+ return new Primitive({
+ geometryInstances : instance,
+ appearance : new PerInstanceColorAppearance({
+ translucent : false,
+ flat : true
+ }),
+ asynchronous : false
+ });
+ };
+
+ return TileBoundingSphere;
+});
diff --git a/Source/Scene/TileBoundingVolume.js b/Source/Scene/TileBoundingVolume.js
new file mode 100644
index 000000000000..bb16a303a032
--- /dev/null
+++ b/Source/Scene/TileBoundingVolume.js
@@ -0,0 +1,77 @@
+/*global define*/
+define([
+ '../Core/DeveloperError'
+ ], function(
+ DeveloperError) {
+ 'use strict';
+
+ /**
+ * Defines a bounding volume for a tile. This type describes an interface
+ * and is not intended to be instantiated directly.
+ *
+ * @see TileBoundingRegion
+ * @see TileBoundingSphere
+ * @see TileOrientedBoundingBox
+ *
+ * @private
+ */
+ function TileBoundingVolume() {
+ }
+
+ /**
+ * The underlying bounding volume
+ *
+ * @memberof TileBoundingVolume.prototype
+ *
+ * @type {Object}
+ * @readonly
+ */
+ TileBoundingVolume.prototype.boundingVolume = undefined;
+
+ /**
+ * The underlying bounding sphere
+ *
+ * @memberof TileBoundingVolume.prototype
+ *
+ * @type {BoundingSphere}
+ * @readonly
+ */
+ TileBoundingVolume.prototype.boundingSphere = undefined;
+
+ /**
+ * Creates a debug primitive that shows the outline of the tile bounding
+ * volume.
+ *
+ * @param {Color} color The desired color of the primitive's mesh
+ * @return {Primitive}
+ */
+ TileBoundingVolume.prototype.createDebugVolume = function(color) {
+ DeveloperError.throwInstantiationError();
+ };
+
+ /**
+ * Calculates the distance between the tile and the camera.
+ *
+ * @param {FrameState} frameState The frame state.
+ * @return {Number} The distance between the tile and the camera, in meters.
+ * Returns 0.0 if the camera is inside the tile.
+ */
+ TileBoundingVolume.prototype.distanceToCamera = function(frameState) {
+ DeveloperError.throwInstantiationError();
+ };
+
+ /**
+ * Determines which side of a plane this volume is located.
+ *
+ * @param {Plane} plane The plane to test against.
+ * @returns {Intersect} {@link Intersect.INSIDE} if the entire volume is on the side of the plane
+ * the normal is pointing, {@link Intersect.OUTSIDE} if the entire volume is
+ * on the opposite side, and {@link Intersect.INTERSECTING} if the volume
+ * intersects the plane.
+ */
+ TileBoundingVolume.prototype.intersectPlane = function(plane) {
+ DeveloperError.throwInstantiationError();
+ };
+
+ return TileBoundingVolume;
+});
diff --git a/Source/Scene/TileOrientedBoundingBox.js b/Source/Scene/TileOrientedBoundingBox.js
new file mode 100644
index 000000000000..5acf800e86a8
--- /dev/null
+++ b/Source/Scene/TileOrientedBoundingBox.js
@@ -0,0 +1,154 @@
+/*global define*/
+define([
+ '../Core/BoundingSphere',
+ '../Core/BoxOutlineGeometry',
+ '../Core/Cartesian3',
+ '../Core/Check',
+ '../Core/ColorGeometryInstanceAttribute',
+ '../Core/defaultValue',
+ '../Core/defined',
+ '../Core/defineProperties',
+ '../Core/GeometryInstance',
+ '../Core/Matrix3',
+ '../Core/Matrix4',
+ '../Core/OrientedBoundingBox',
+ './PerInstanceColorAppearance',
+ './Primitive'
+ ], function(
+ BoundingSphere,
+ BoxOutlineGeometry,
+ Cartesian3,
+ Check,
+ ColorGeometryInstanceAttribute,
+ defaultValue,
+ defined,
+ defineProperties,
+ GeometryInstance,
+ Matrix3,
+ Matrix4,
+ OrientedBoundingBox,
+ PerInstanceColorAppearance,
+ Primitive) {
+ 'use strict';
+
+ /**
+ * A tile bounding volume specified as an oriented bounding box.
+ * @alias TileOrientedBoundingBox
+ * @constructor
+ *
+ * @param {Cartesian3} [center=Cartesian3.ZERO] The center of the box.
+ * @param {Matrix3} [halfAxes=Matrix3.ZERO] The three orthogonal half-axes of the bounding box.
+ * Equivalently, the transformation matrix, to rotate and scale a 2x2x2
+ * cube centered at the origin.
+ *
+ * @private
+ */
+ function TileOrientedBoundingBox(center, halfAxes) {
+ this._orientedBoundingBox = new OrientedBoundingBox(center, halfAxes);
+ this._boundingSphere = BoundingSphere.fromOrientedBoundingBox(this._orientedBoundingBox);
+ }
+
+ defineProperties(TileOrientedBoundingBox.prototype, {
+ /**
+ * The underlying bounding volume
+ *
+ * @memberof TileOrientedBoundingBox.prototype
+ *
+ * @type {Object}
+ * @readonly
+ */
+ boundingVolume : {
+ get : function() {
+ return this._orientedBoundingBox;
+ }
+ },
+ /**
+ * The underlying bounding sphere
+ *
+ * @memberof TileOrientedBoundingBox.prototype
+ *
+ * @type {BoundingSphere}
+ * @readonly
+ */
+ boundingSphere : {
+ get : function() {
+ return this._boundingSphere;
+ }
+ }
+ });
+
+ /**
+ * Computes the distance between this bounding box and the camera attached to frameState.
+ *
+ * @param {FrameState} frameState The frameState to which the camera is attached.
+ * @returns {Number} The distance between the camera and the bounding box in meters. Returns 0 if the camera is inside the bounding volume.
+ */
+ TileOrientedBoundingBox.prototype.distanceToCamera = function(frameState) {
+ //>>includeStart('debug', pragmas.debug);
+ Check.defined('frameState', frameState);
+ //>>includeEnd('debug');
+ return Math.sqrt(this._orientedBoundingBox.distanceSquaredTo(frameState.camera.positionWC));
+ };
+
+ /**
+ * Determines which side of a plane this box is located.
+ *
+ * @param {Plane} plane The plane to test against.
+ * @returns {Intersect} {@link Intersect.INSIDE} if the entire box is on the side of the plane
+ * the normal is pointing, {@link Intersect.OUTSIDE} if the entire box is
+ * on the opposite side, and {@link Intersect.INTERSECTING} if the box
+ * intersects the plane.
+ */
+ TileOrientedBoundingBox.prototype.intersectPlane = function(plane) {
+ //>>includeStart('debug', pragmas.debug);
+ Check.defined('plane', plane);
+ //>>includeEnd('debug');
+ return this._orientedBoundingBox.intersectPlane(plane);
+ };
+
+ /**
+ * Update the bounding box after the tile is transformed.
+ */
+ TileOrientedBoundingBox.prototype.update = function(center, halfAxes) {
+ Cartesian3.clone(center, this._orientedBoundingBox.center);
+ Matrix3.clone(halfAxes, this._orientedBoundingBox.halfAxes);
+ BoundingSphere.fromOrientedBoundingBox(this._orientedBoundingBox, this._boundingSphere);
+ };
+
+ /**
+ * Creates a debug primitive that shows the outline of the box.
+ *
+ * @param {Color} color The desired color of the primitive's mesh
+ * @return {Primitive}
+ */
+ TileOrientedBoundingBox.prototype.createDebugVolume = function(color) {
+ //>>includeStart('debug', pragmas.debug);
+ Check.defined('color', color);
+ //>>includeEnd('debug');
+
+ var geometry = new BoxOutlineGeometry({
+ // Make a 2x2x2 cube
+ minimum: new Cartesian3(-1.0, -1.0, -1.0),
+ maximum: new Cartesian3(1.0, 1.0, 1.0)
+ });
+ var modelMatrix = Matrix4.fromRotationTranslation(this.boundingVolume.halfAxes, this.boundingVolume.center);
+ var instance = new GeometryInstance({
+ geometry : geometry,
+ modelMatrix : modelMatrix,
+ attributes : {
+ color : ColorGeometryInstanceAttribute.fromColor(color)
+ }
+ });
+
+ return new Primitive({
+ geometryInstances : instance,
+ appearance : new PerInstanceColorAppearance({
+ translucent : false,
+ flat : true
+ }),
+ asynchronous : false
+ });
+ };
+
+ return TileOrientedBoundingBox;
+});
diff --git a/Source/Scene/TileTerrain.js b/Source/Scene/TileTerrain.js
index f152e32cdbb7..d97b629b6ef3 100644
--- a/Source/Scene/TileTerrain.js
+++ b/Source/Scene/TileTerrain.js
@@ -12,7 +12,7 @@ define([
'../Renderer/VertexArray',
'../ThirdParty/when',
'./TerrainState',
- './TileBoundingBox'
+ './TileBoundingRegion'
], function(
BoundingSphere,
Cartesian3,
@@ -26,7 +26,7 @@ define([
VertexArray,
when,
TerrainState,
- TileBoundingBox) {
+ TileBoundingRegion) {
'use strict';
/**
@@ -83,13 +83,12 @@ define([
surfaceTile.maximumHeight = mesh.maximumHeight;
surfaceTile.boundingSphere3D = BoundingSphere.clone(mesh.boundingSphere3D, surfaceTile.boundingSphere3D);
surfaceTile.orientedBoundingBox = OrientedBoundingBox.clone(mesh.orientedBoundingBox, surfaceTile.orientedBoundingBox);
- surfaceTile.tileBoundingBox = new TileBoundingBox({
+ surfaceTile.tileBoundingRegion = new TileBoundingRegion({
rectangle : tile.rectangle,
minimumHeight : mesh.minimumHeight,
maximumHeight : mesh.maximumHeight,
ellipsoid : tile.tilingScheme.ellipsoid
});
-
tile.data.occludeePointInScaledSpace = Cartesian3.clone(mesh.occludeePointInScaledSpace, surfaceTile.occludeePointInScaledSpace);
};
diff --git a/Source/Scene/modelMaterialsCommon.js b/Source/Scene/modelMaterialsCommon.js
index 92b997f8d000..27875e305d3f 100644
--- a/Source/Scene/modelMaterialsCommon.js
+++ b/Source/Scene/modelMaterialsCommon.js
@@ -167,7 +167,7 @@ define([
var vertexShaderCount = 0;
var fragmentShaderCount = 0;
var programCount = 0;
- function generateTechnique(gltf, khrMaterialsCommon, lightParameters) {
+ function generateTechnique(gltf, khrMaterialsCommon, lightParameters, options) {
var techniques = gltf.techniques;
var shaders = gltf.shaders;
var programs = gltf.programs;
@@ -195,7 +195,7 @@ define([
var techniqueParameters = {
// Add matrices
modelViewMatrix: {
- semantic: 'MODELVIEW',
+ semantic: options.useCesiumRTCMatrixInShaders ? 'CESIUM_RTC_MODELVIEW' : 'MODELVIEW',
type: WebGLConstants.FLOAT_MAT4
},
projectionMatrix: {
@@ -678,11 +678,13 @@ define([
*
* @private
*/
- function modelMaterialsCommon(gltf) {
+ function modelMaterialsCommon(gltf, options) {
if (!defined(gltf)) {
return undefined;
}
+ options = defaultValue(options, defaultValue.EMPTY_OBJECT);
+
var hasExtension = false;
var extensionsUsed = gltf.extensionsUsed;
if (defined(extensionsUsed)) {
@@ -709,6 +711,8 @@ define([
var lightParameters = generateLightParameters(gltf);
+ var hasCesiumRTCExtension = defined(gltf.extensions) && defined(gltf.extensions.CESIUM_RTC);
+
var techniques = {};
var materials = gltf.materials;
for (var name in materials) {
@@ -719,7 +723,9 @@ define([
var techniqueKey = getTechniqueKey(khrMaterialsCommon);
var technique = techniques[techniqueKey];
if (!defined(technique)) {
- technique = generateTechnique(gltf, khrMaterialsCommon, lightParameters);
+ technique = generateTechnique(gltf, khrMaterialsCommon, lightParameters, {
+ useCesiumRTCMatrixInShaders : hasCesiumRTCExtension
+ });
techniques[techniqueKey] = technique;
}
diff --git a/Source/Shaders/Builtin/Functions/HSBToRGB.glsl b/Source/Shaders/Builtin/Functions/HSBToRGB.glsl
new file mode 100644
index 000000000000..63036c4f1478
--- /dev/null
+++ b/Source/Shaders/Builtin/Functions/HSBToRGB.glsl
@@ -0,0 +1,24 @@
+/**
+ * Converts an HSB color (hue, saturation, brightness) to RGB
+ * HSB <-> RGB conversion with minimal branching: {@link http://lolengine.net/blog/2013/07/27/rgb-to-hsv-in-glsl}
+ *
+ * @name czm_HSBToRGB
+ * @glslFunction
+ *
+ * @param {vec3} hsb The color in HSB.
+ *
+ * @returns {vec3} The color in RGB.
+ *
+ * @example
+ * vec3 hsb = czm_RGBToHSB(rgb);
+ * hsb.z *= 0.1;
+ * rgb = czm_HSBToRGB(hsb);
+ */
+
+const vec4 K_HSB2RGB = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
+
+vec3 czm_HSBToRGB(vec3 hsb)
+{
+ vec3 p = abs(fract(hsb.xxx + K_HSB2RGB.xyz) * 6.0 - K_HSB2RGB.www);
+ return hsb.z * mix(K_HSB2RGB.xxx, clamp(p - K_HSB2RGB.xxx, 0.0, 1.0), hsb.y);
+}
diff --git a/Source/Shaders/Builtin/Functions/HSLToRGB.glsl b/Source/Shaders/Builtin/Functions/HSLToRGB.glsl
new file mode 100644
index 000000000000..59b06220cd2f
--- /dev/null
+++ b/Source/Shaders/Builtin/Functions/HSLToRGB.glsl
@@ -0,0 +1,31 @@
+/**
+ * Converts an HSL color (hue, saturation, lightness) to RGB
+ * HSL <-> RGB conversion: {@link http://www.chilliant.com/rgb2hsv.html}
+ *
+ * @name czm_HSLToRGB
+ * @glslFunction
+ *
+ * @param {vec3} rgb The color in HSL.
+ *
+ * @returns {vec3} The color in RGB.
+ *
+ * @example
+ * vec3 hsl = czm_RGBToHSL(rgb);
+ * hsl.z *= 0.1;
+ * rgb = czm_HSLToRGB(hsl);
+ */
+
+vec3 hueToRGB(float hue)
+{
+ float r = abs(hue * 6.0 - 3.0) - 1.0;
+ float g = 2.0 - abs(hue * 6.0 - 2.0);
+ float b = 2.0 - abs(hue * 6.0 - 4.0);
+ return clamp(vec3(r, g, b), 0.0, 1.0);
+}
+
+vec3 czm_HSLToRGB(vec3 hsl)
+{
+ vec3 rgb = hueToRGB(hsl.x);
+ float c = (1.0 - abs(2.0 * hsl.z - 1.0)) * hsl.y;
+ return (rgb - 0.5) * c + hsl.z;
+}
diff --git a/Source/Shaders/Builtin/Functions/RGBToHSB.glsl b/Source/Shaders/Builtin/Functions/RGBToHSB.glsl
new file mode 100644
index 000000000000..9826d72723e2
--- /dev/null
+++ b/Source/Shaders/Builtin/Functions/RGBToHSB.glsl
@@ -0,0 +1,27 @@
+/**
+ * Converts an RGB color to HSB (hue, saturation, brightness)
+ * HSB <-> RGB conversion with minimal branching: {@link http://lolengine.net/blog/2013/07/27/rgb-to-hsv-in-glsl}
+ *
+ * @name czm_RGBToHSB
+ * @glslFunction
+ *
+ * @param {vec3} rgb The color in RGB.
+ *
+ * @returns {vec3} The color in HSB.
+ *
+ * @example
+ * vec3 hsb = czm_RGBToHSB(rgb);
+ * hsb.z *= 0.1;
+ * rgb = czm_HSBToRGB(hsb);
+ */
+
+const vec4 K_RGB2HSB = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);
+
+vec3 czm_RGBToHSB(vec3 rgb)
+{
+ vec4 p = mix(vec4(rgb.bg, K_RGB2HSB.wz), vec4(rgb.gb, K_RGB2HSB.xy), step(rgb.b, rgb.g));
+ vec4 q = mix(vec4(p.xyw, rgb.r), vec4(rgb.r, p.yzx), step(p.x, rgb.r));
+
+ float d = q.x - min(q.w, q.y);
+ return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + czm_epsilon7)), d / (q.x + czm_epsilon7), q.x);
+}
diff --git a/Source/Shaders/Builtin/Functions/RGBToHSL.glsl b/Source/Shaders/Builtin/Functions/RGBToHSL.glsl
new file mode 100644
index 000000000000..190f1d63c7bf
--- /dev/null
+++ b/Source/Shaders/Builtin/Functions/RGBToHSL.glsl
@@ -0,0 +1,34 @@
+/**
+ * Converts an RGB color to HSL (hue, saturation, lightness)
+ * HSL <-> RGB conversion: {@link http://www.chilliant.com/rgb2hsv.html}
+ *
+ * @name czm_RGBToHSL
+ * @glslFunction
+ *
+ * @param {vec3} rgb The color in RGB.
+ *
+ * @returns {vec3} The color in HSL.
+ *
+ * @example
+ * vec3 hsl = czm_RGBToHSL(rgb);
+ * hsl.z *= 0.1;
+ * rgb = czm_HSLToRGB(hsl);
+ */
+
+vec3 RGBtoHCV(vec3 rgb)
+{
+ // Based on work by Sam Hocevar and Emil Persson
+ vec4 p = (rgb.g < rgb.b) ? vec4(rgb.bg, -1.0, 2.0 / 3.0) : vec4(rgb.gb, 0.0, -1.0 / 3.0);
+ vec4 q = (rgb.r < p.x) ? vec4(p.xyw, rgb.r) : vec4(rgb.r, p.yzx);
+ float c = q.x - min(q.w, q.y);
+ float h = abs((q.w - q.y) / (6.0 * c + czm_epsilon7) + q.z);
+ return vec3(h, c, q.x);
+}
+
+vec3 czm_RGBToHSL(vec3 rgb)
+{
+ vec3 hcv = RGBtoHCV(rgb);
+ float l = hcv.z - hcv.y * 0.5;
+ float s = hcv.y / (1.0 - abs(l * 2.0 - 1.0) + czm_epsilon7);
+ return vec3(hcv.x, s, l);
+}
diff --git a/Source/Shaders/SkyAtmosphereFS.glsl b/Source/Shaders/SkyAtmosphereFS.glsl
index 9fd4000bb26e..f067c1e71797 100644
--- a/Source/Shaders/SkyAtmosphereFS.glsl
+++ b/Source/Shaders/SkyAtmosphereFS.glsl
@@ -2,11 +2,11 @@
* @license
* Copyright (c) 2000-2005, Sean O'Neil (s_p_oneil@hotmail.com)
* All rights reserved.
- *
+ *
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
- *
+ *
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice,
@@ -15,7 +15,7 @@
* * Neither the name of the project nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
- *
+ *
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
@@ -29,10 +29,9 @@
*
* Modifications made by Analytical Graphics, Inc.
*/
-
+
// Code: http://sponeil.net/
// GPU Gems 2 Article: http://http.developer.nvidia.com/GPUGems2/gpugems2_chapter16.html
- // HSV/HSB <-> RGB conversion with minimal branching: http://lolengine.net/blog/2013/07/27/rgb-to-hsv-in-glsl
#ifdef COLOR_CORRECT
uniform vec3 u_hsbShift; // Hue, saturation, brightness
@@ -40,40 +39,21 @@ uniform vec3 u_hsbShift; // Hue, saturation, brightness
const float g = -0.95;
const float g2 = g * g;
-const vec4 K_RGB2HSB = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);
-const vec4 K_HSB2RGB = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
varying vec3 v_rayleighColor;
varying vec3 v_mieColor;
varying vec3 v_toCamera;
varying vec3 v_positionEC;
-#ifdef COLOR_CORRECT
-vec3 rgb2hsb(vec3 rgbColor)
-{
- vec4 p = mix(vec4(rgbColor.bg, K_RGB2HSB.wz), vec4(rgbColor.gb, K_RGB2HSB.xy), step(rgbColor.b, rgbColor.g));
- vec4 q = mix(vec4(p.xyw, rgbColor.r), vec4(rgbColor.r, p.yzx), step(p.x, rgbColor.r));
-
- float d = q.x - min(q.w, q.y);
- return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + czm_epsilon7)), d / (q.x + czm_epsilon7), q.x);
-}
-
-vec3 hsb2rgb(vec3 hsbColor)
-{
- vec3 p = abs(fract(hsbColor.xxx + K_HSB2RGB.xyz) * 6.0 - K_HSB2RGB.www);
- return hsbColor.z * mix(K_HSB2RGB.xxx, clamp(p - K_HSB2RGB.xxx, 0.0, 1.0), hsbColor.y);
-}
-#endif
-
void main (void)
{
// Extra normalize added for Android
float cosAngle = dot(czm_sunDirectionWC, normalize(v_toCamera)) / length(v_toCamera);
float rayleighPhase = 0.75 * (1.0 + cosAngle * cosAngle);
float miePhase = 1.5 * ((1.0 - g2) / (2.0 + g2)) * (1.0 + cosAngle * cosAngle) / pow(1.0 + g2 - 2.0 * g * cosAngle, 1.5);
-
+
const float exposure = 2.0;
-
+
vec3 rgb = rayleighPhase * v_rayleighColor + miePhase * v_mieColor;
rgb = vec3(1.0) - exp(-exposure * rgb);
// Compute luminance before color correction to avoid strangely gray night skies
@@ -81,13 +61,13 @@ void main (void)
#ifdef COLOR_CORRECT
// Convert rgb color to hsb
- vec3 hsb = rgb2hsb(rgb);
+ vec3 hsb = czm_RGBToHSB(rgb);
// Perform hsb shift
hsb.x += u_hsbShift.x; // hue
hsb.y = clamp(hsb.y + u_hsbShift.y, 0.0, 1.0); // saturation
hsb.z = hsb.z > czm_epsilon7 ? hsb.z + u_hsbShift.z : 0.0; // brightness
// Convert shifted hsb back to rgb
- rgb = hsb2rgb(hsb);
+ rgb = czm_HSBToRGB(hsb);
// Check if correction decreased the luminance to 0
l = min(l, czm_luminance(rgb));
diff --git a/Source/Widgets/CesiumInspector/CesiumInspector.js b/Source/Widgets/CesiumInspector/CesiumInspector.js
index ec0864d7daf6..b04ddae222f9 100644
--- a/Source/Widgets/CesiumInspector/CesiumInspector.js
+++ b/Source/Widgets/CesiumInspector/CesiumInspector.js
@@ -26,9 +26,6 @@ define([
* @param {Element|String} container The DOM element or ID that will contain the widget.
* @param {Scene} scene The Scene instance to use.
*
- * @exception {DeveloperError} container is required.
- * @exception {DeveloperError} scene is required.
- *
* @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Cesium%20Inspector.html|Cesium Sandcastle Cesium Inspector Demo}
*/
function CesiumInspector(container, scene) {
diff --git a/Source/Widgets/CesiumInspector/CesiumInspectorViewModel.js b/Source/Widgets/CesiumInspector/CesiumInspectorViewModel.js
index e1929395ae64..cf1c0b650e6f 100644
--- a/Source/Widgets/CesiumInspector/CesiumInspectorViewModel.js
+++ b/Source/Widgets/CesiumInspector/CesiumInspectorViewModel.js
@@ -71,8 +71,6 @@ define([
*
* @param {Scene} scene The scene instance to use.
* @param {PerformanceContainer} performanceContainer The instance to use for performance container.
- *
- * @exception {DeveloperError} scene is required.
*/
function CesiumInspectorViewModel(scene, performanceContainer) {
//>>includeStart('debug', pragmas.debug);
@@ -570,6 +568,10 @@ define([
eventHandler.removeInputAction(ScreenSpaceEventType.LEFT_CLICK);
}
});
+
+ this._removePostRenderEvent = scene.postRender.addEventListener(function() {
+ that._update();
+ });
}
defineProperties(CesiumInspectorViewModel.prototype, {
@@ -850,6 +852,9 @@ define([
* @type {Command}
*/
primitive : {
+ get : function() {
+ return this._primitive;
+ },
set : function(newPrimitive) {
var oldPrimitive = this._primitive;
if (newPrimitive !== oldPrimitive) {
@@ -871,10 +876,6 @@ define([
this.showPrimitiveReferenceFrame();
this.doFilterPrimitive();
}
- },
-
- get : function() {
- return this._primitive;
}
},
@@ -885,6 +886,9 @@ define([
* @type {Command}
*/
tile : {
+ get : function() {
+ return this._tile;
+ },
set : function(newTile) {
if (defined(newTile)) {
this.hasPickedTile = true;
@@ -907,41 +911,36 @@ define([
this.hasPickedTile = false;
this._tile = undefined;
}
- },
-
- get : function() {
- return this._tile;
}
- },
+ }
+ });
- update : {
- get : function() {
- var that = this;
- return function() {
- if (that.frustums) {
- that.frustumStatisticText = frustumStatsToString(that._scene.debugFrustumStatistics);
- }
+ /**
+ * Updates the view model
+ * @private
+ */
+ CesiumInspectorViewModel.prototype._update = function() {
+ if (this.frustums) {
+ this.frustumStatisticText = frustumStatsToString(this._scene.debugFrustumStatistics);
+ }
- // Determine the number of frustums being used.
- var numberOfFrustums = that._scene.numberOfFrustums;
- that._numberOfFrustums = numberOfFrustums;
- // Bound the frustum to be displayed.
- that.depthFrustum = boundDepthFrustum(1, numberOfFrustums, that.depthFrustum);
- // Update the displayed text.
- that.depthFrustumText = that.depthFrustum + ' of ' + numberOfFrustums;
+ // Determine the number of frustums being used.
+ var numberOfFrustums = this._scene.numberOfFrustums;
+ this._numberOfFrustums = numberOfFrustums;
+ // Bound the frustum to be displayed.
+ this.depthFrustum = boundDepthFrustum(1, numberOfFrustums, this.depthFrustum);
+ // Update the displayed text.
+ this.depthFrustumText = this.depthFrustum + ' of ' + numberOfFrustums;
- if (that.performance) {
- that._performanceDisplay.update();
- }
- if (that.primitiveReferenceFrame) {
- that._modelMatrixPrimitive.modelMatrix = that._primitive.modelMatrix;
- }
-
- that.shaderCacheText = 'Cached shaders: ' + that._scene.context.shaderCache.numberOfShaders;
- };
- }
+ if (this.performance) {
+ this._performanceDisplay.update();
}
- });
+ if (this.primitiveReferenceFrame) {
+ this._modelMatrixPrimitive.modelMatrix = this._primitive.modelMatrix;
+ }
+
+ this.shaderCacheText = 'Cached shaders: ' + this._scene.context.shaderCache.numberOfShaders;
+ };
/**
* @returns {Boolean} true if the object has been destroyed, false otherwise.
@@ -956,6 +955,7 @@ define([
*/
CesiumInspectorViewModel.prototype.destroy = function() {
this._eventHandler.destroy();
+ this._removePostRenderEvent();
this._frustumsSubscription.dispose();
this._frustumPlanesSubscription.dispose();
this._performanceSubscription.dispose();
diff --git a/Source/Workers/transcodeCRNToDXT.js b/Source/Workers/transcodeCRNToDXT.js
index 5f781025bc68..4eaa40f2a7e8 100644
--- a/Source/Workers/transcodeCRNToDXT.js
+++ b/Source/Workers/transcodeCRNToDXT.js
@@ -110,7 +110,7 @@ define([
var dstSize = 0;
var i;
for (i = 0; i < levels; ++i) {
- dstSize += PixelFormat.compressedTextureSize(format, width >> i, height >> i);
+ dstSize += PixelFormat.compressedTextureSizeInBytes(format, width >> i, height >> i);
}
// Allocate enough space on the emscripten heap to hold the decoded DXT data
@@ -133,7 +133,7 @@ define([
// Mipmaps are unsupported, so copy the level 0 texture
// When mipmaps are supported, a copy will still be necessary as dxtData is a view on the heap.
- var length = PixelFormat.compressedTextureSize(format, width, height);
+ var length = PixelFormat.compressedTextureSizeInBytes(format, width, height);
var level0DXTData = new Uint8Array(length);
level0DXTData.set(dxtData, 0);
diff --git a/Specs/Core/BoundingRectangleSpec.js b/Specs/Core/BoundingRectangleSpec.js
index a97bc5a1225c..770c285351d3 100644
--- a/Specs/Core/BoundingRectangleSpec.js
+++ b/Specs/Core/BoundingRectangleSpec.js
@@ -111,7 +111,7 @@ defineSuite([
expect(rectangle.height).toEqual(0.0);
});
- it('create a bounding rectangle from an rectangle', function() {
+ it('create a bounding rectangle from a rectangle', function() {
var rectangle = Rectangle.MAX_VALUE;
var projection = new GeographicProjection(Ellipsoid.UNIT_SPHERE);
var expected = new BoundingRectangle(rectangle.west, rectangle.south, rectangle.east - rectangle.west, rectangle.north - rectangle.south);
diff --git a/Specs/Core/OccluderSpec.js b/Specs/Core/OccluderSpec.js
index 7fbfa0a4a49d..fba1b77ebbcb 100644
--- a/Specs/Core/OccluderSpec.js
+++ b/Specs/Core/OccluderSpec.js
@@ -255,7 +255,7 @@ defineSuite([
expect(occluder.isBoundingSphereVisible(new BoundingSphere(result, 0.0))).toEqual(true);
});
- it('compute occludee point from rectangle throws without an rectangle', function() {
+ it('compute occludee point from rectangle throws without a rectangle', function() {
expect(function() {
return Occluder.computeOccludeePointFromRectangle();
}).toThrowDeveloperError();
diff --git a/Specs/Core/arrayFillSpec.js b/Specs/Core/arrayFillSpec.js
new file mode 100644
index 000000000000..52705448db4c
--- /dev/null
+++ b/Specs/Core/arrayFillSpec.js
@@ -0,0 +1,57 @@
+/*global defineSuite*/
+defineSuite([
+ 'Core/arrayFill'
+ ], function(
+ arrayFill) {
+ 'use strict';
+
+ var array;
+
+ beforeEach(function() {
+ array = [0, 0, 0, 0];
+ });
+
+ it('will fill an entire array', function() {
+ arrayFill(array, 1);
+ expect(array).toEqual([1, 1, 1, 1]);
+ });
+
+ it('will fill a portion of an array', function() {
+ arrayFill(array, 1, 1, 3);
+ expect(array).toEqual([0, 1, 1, 0]);
+ });
+
+ it('will wrap around negative values', function() {
+ arrayFill(array, 1, -2, -1);
+ expect(array).toEqual([0, 0, 1, 0]);
+ });
+
+ it('will fill until end if no end is provided', function() {
+ arrayFill(array, 1, 1);
+ expect(array).toEqual([0, 1, 1, 1]);
+ });
+
+ it('will throw an error if no array is provided', function() {
+ expect(function() {
+ arrayFill(undefined, 1, 0, 1);
+ }).toThrowDeveloperError('array is required.');
+ });
+
+ it('will throw an error if no array is provided', function() {
+ expect(function() {
+ arrayFill(array, undefined, 0, 1);
+ }).toThrowDeveloperError('value is required.');
+ });
+
+ it('will throw an error if given an invalid start index', function() {
+ expect(function() {
+ arrayFill(array, 1, array, 1);
+ }).toThrowDeveloperError('start must be a valid index.');
+ });
+
+ it('will throw an error if given an invalid end index', function() {
+ expect(function() {
+ arrayFill(array, 1, 1, array);
+ }).toThrowDeveloperError('end must be a valid index.');
+ });
+});
diff --git a/Specs/Core/isDataUriSpec.js b/Specs/Core/isDataUriSpec.js
new file mode 100644
index 000000000000..9f2b4294de16
--- /dev/null
+++ b/Specs/Core/isDataUriSpec.js
@@ -0,0 +1,17 @@
+/*global defineSuite*/
+defineSuite([
+ 'Core/isDataUri'
+ ], function(
+ isDataUri) {
+ 'use strict';
+
+ it('Determines that a uri is not a data uri', function() {
+ expect(isDataUri(undefined)).toEqual(false);
+ expect(isDataUri('http://cesiumjs.org/')).toEqual(false);
+ });
+
+ it('Determines that a uri is a data uri', function() {
+ var uri = 'data:text/plain;base64,' + btoa('a data uri');
+ expect(isDataUri(uri)).toEqual(true);
+ });
+});
diff --git a/Specs/Core/joinUrlsSpec.js b/Specs/Core/joinUrlsSpec.js
index 65a243c9261c..94ee2a10b1b8 100644
--- a/Specs/Core/joinUrlsSpec.js
+++ b/Specs/Core/joinUrlsSpec.js
@@ -160,4 +160,13 @@ defineSuite([
var result = joinUrls('http://www.xyz.com/', 'MODULE');
expect(result).toEqual('http://www.xyz.com/MODULE');
});
+
+ it('does not join data uris', function() {
+ var dataUri = 'data:text/plain;base64,SGVsbG8sIFdvcmxkIQ%3D%3D';
+ var result = joinUrls(dataUri, relativePath);
+ expect(result).toEqual(dataUri);
+
+ result = joinUrls(absolutePath, dataUri);
+ expect(result).toEqual(dataUri);
+ });
});
diff --git a/Specs/Renderer/CubeMapSpec.js b/Specs/Renderer/CubeMapSpec.js
index 4962c6d4b222..4d4c03b8ae4f 100644
--- a/Specs/Renderer/CubeMapSpec.js
+++ b/Specs/Renderer/CubeMapSpec.js
@@ -80,6 +80,7 @@ defineSuite([
var blueImage;
var blueAlphaImage;
var blueOverRedImage;
+ var red16x16Image;
beforeAll(function() {
context = createContext();
@@ -97,6 +98,9 @@ defineSuite([
promises.push(loadImage('./Data/Images/BlueOverRed.png').then(function(result) {
blueOverRedImage = result;
}));
+ promises.push(loadImage('./Data/Images/Red16x16.png').then(function(result) {
+ red16x16Image = result;
+ }));
return when.all(promises);
});
@@ -174,6 +178,16 @@ defineSuite([
expect(cubeMap.height).toEqual(16);
});
+ it('gets size in bytes', function() {
+ cubeMap = new CubeMap({
+ context : context,
+ width : 16,
+ height : 16
+ });
+
+ expect(cubeMap.sizeInBytes).toEqual(256 * 4 * 6);
+ });
+
it('gets flip Y', function() {
cubeMap = new CubeMap({
context : context,
@@ -613,6 +627,24 @@ defineSuite([
}).contextToRender([0, 0, 255, 255]);
});
+ it('gets size in bytes for mipmap', function() {
+ cubeMap = new CubeMap({
+ context : context,
+ source : {
+ positiveX : red16x16Image,
+ negativeX : red16x16Image,
+ positiveY : red16x16Image,
+ negativeY : red16x16Image,
+ positiveZ : red16x16Image,
+ negativeZ : red16x16Image
+ }
+ });
+ cubeMap.generateMipmap();
+
+ // Allow for some leniency with the sizeInBytes approximation
+ expect(cubeMap.sizeInBytes).toEqualEpsilon((16*16 + 8*8 + 4*4 + 2*2 + 1) * 4 * 6, 10);
+ });
+
it('destroys', function() {
var c = new CubeMap({
context : context,
diff --git a/Specs/Renderer/TextureSpec.js b/Specs/Renderer/TextureSpec.js
index 3ecf5c20b21c..aaae78a5248f 100644
--- a/Specs/Renderer/TextureSpec.js
+++ b/Specs/Renderer/TextureSpec.js
@@ -38,6 +38,7 @@ defineSuite([
var blueImage;
var blueAlphaImage;
var blueOverRedImage;
+ var red16x16Image;
var greenDXTImage;
var greenPVRImage;
@@ -69,6 +70,9 @@ defineSuite([
promises.push(loadImage('./Data/Images/BlueOverRed.png').then(function(image) {
blueOverRedImage = image;
}));
+ promises.push(loadImage('./Data/Images/Red16x16.png').then(function(image) {
+ red16x16Image = image;
+ }));
promises.push(loadKTX('./Data/Images/Green4x4DXT1.ktx').then(function(image) {
greenDXTImage = image;
}));
@@ -109,8 +113,12 @@ defineSuite([
texture = Texture.fromFramebuffer({
context : context
});
- expect(texture.width).toEqual(context.canvas.clientWidth);
- expect(texture.height).toEqual(context.canvas.clientHeight);
+
+ var expectedWidth = context.canvas.clientWidth;
+ var expectedHeight = context.canvas.clientHeight;
+ expect(texture.width).toEqual(expectedWidth);
+ expect(texture.height).toEqual(expectedHeight);
+ expect(texture.sizeInBytes).toEqual(expectedWidth * expectedHeight * 4);
command.color = Color.WHITE;
command.execute(context);
@@ -146,6 +154,12 @@ defineSuite([
texture.copyFromFramebuffer();
+ var expectedWidth = context.canvas.clientWidth;
+ var expectedHeight = context.canvas.clientHeight;
+ expect(texture.width).toEqual(expectedWidth);
+ expect(texture.height).toEqual(expectedHeight);
+ expect(texture.sizeInBytes).toEqual(expectedWidth * expectedHeight * 4);
+
// Clear to white
command.color = Color.WHITE;
command.execute(context);
@@ -189,6 +203,8 @@ defineSuite([
}
});
+ expect(texture.sizeInBytes).toEqual(16);
+
expect({
context : context,
fragmentShader : fs,
@@ -212,6 +228,8 @@ defineSuite([
}
});
+ expect(texture.sizeInBytes).toBe(8);
+
expect({
context : context,
fragmentShader : fs,
@@ -234,6 +252,8 @@ defineSuite([
}
});
+ expect(texture.sizeInBytes).toBe(32);
+
expect({
context : context,
fragmentShader : fs,
@@ -256,6 +276,8 @@ defineSuite([
}
});
+ expect(texture.sizeInBytes).toBe(8);
+
expect({
context : context,
fragmentShader : fs,
@@ -346,6 +368,10 @@ defineSuite([
}
});
+ expect(texture.width).toEqual(1);
+ expect(texture.height).toEqual(1);
+ expect(texture.sizeInBytes).toEqual(4);
+
expect({
context : context,
fragmentShader : fs,
@@ -369,6 +395,10 @@ defineSuite([
arrayBufferView : bytes
});
+ expect(texture.width).toEqual(1);
+ expect(texture.height).toEqual(1);
+ expect(texture.sizeInBytes).toEqual(4);
+
expect({
context : context,
fragmentShader : fs,
@@ -437,19 +467,20 @@ defineSuite([
it('can generate mipmaps', function() {
texture = new Texture({
context : context,
- source : blueImage,
+ source : red16x16Image,
pixelFormat : PixelFormat.RGBA,
sampler : new Sampler({
minificationFilter : TextureMinificationFilter.NEAREST_MIPMAP_LINEAR
})
});
texture.generateMipmap();
+ expect(texture.sizeInBytes).toEqualEpsilon((16*16 + 8*8 + 4*4 + 2*2 + 1) * 4, 1);
expect({
context : context,
fragmentShader : fs,
uniformMap : uniformMap
- }).contextToRender([0, 0, 255, 255]);
+ }).contextToRender([255, 0, 0, 255]);
});
it('can set a sampler property', function() {
@@ -530,6 +561,34 @@ defineSuite([
expect(texture.dimensions).toEqual(new Cartesian2(64, 16));
});
+ function expectTextureByteSize(width, height, pixelFormat, pixelDatatype, expectedSize) {
+ texture = new Texture({
+ context : context,
+ width : width,
+ height : height,
+ pixelFormat : pixelFormat,
+ pixelDatatype : pixelDatatype
+ });
+ expect(texture.sizeInBytes).toBe(expectedSize);
+ texture = texture && texture.destroy();
+ }
+
+ it('can get the size in bytes of a texture', function() {
+ // Depth textures
+ if (context.depthTexture) {
+ expectTextureByteSize(16, 16, PixelFormat.DEPTH_COMPONENT, PixelDatatype.UNSIGNED_SHORT, 256 * 2);
+ expectTextureByteSize(16, 16, PixelFormat.DEPTH_COMPONENT, PixelDatatype.UNSIGNED_INT, 256 * 4);
+ expectTextureByteSize(16, 16, PixelFormat.DEPTH_STENCIL, PixelDatatype.UNSIGNED_INT_24_8, 256 * 4);
+ }
+
+ // Uncompressed formats
+ expectTextureByteSize(16, 16, PixelFormat.ALPHA, PixelDatatype.UNSIGNED_BYTE, 256);
+ expectTextureByteSize(16, 16, PixelFormat.RGB, PixelDatatype.UNSIGNED_BYTE, 256 * 4);
+ expectTextureByteSize(16, 16, PixelFormat.RGBA, PixelDatatype.UNSIGNED_BYTE, 256 * 4);
+ expectTextureByteSize(16, 16, PixelFormat.LUMINANCE, PixelDatatype.UNSIGNED_BYTE, 256);
+ expectTextureByteSize(16, 16, PixelFormat.LUMINANCE_ALPHA, PixelDatatype.UNSIGNED_BYTE, 256 * 2);
+ });
+
it('can be destroyed', function() {
var t = new Texture({
context : context,
diff --git a/Specs/Scene/GlobeSurfaceTileProviderSpec.js b/Specs/Scene/GlobeSurfaceTileProviderSpec.js
index 9c21718503ad..c17ded56f546 100644
--- a/Specs/Scene/GlobeSurfaceTileProviderSpec.js
+++ b/Specs/Scene/GlobeSurfaceTileProviderSpec.js
@@ -378,6 +378,7 @@ defineSuite([
var oldFog = scene.fog;
scene.fog = new Fog();
switchViewMode(SceneMode.SCENE3D, new GeographicProjection(Ellipsoid.WGS84));
+ scene.camera.lookUp(1.2); // Horizon-view
return updateUntilDone(scene.globe).then(function() {
expect(scene).notToRender([0, 0, 0, 255]);
@@ -400,6 +401,7 @@ defineSuite([
var oldFog = scene.fog;
scene.fog = new Fog();
switchViewMode(SceneMode.SCENE3D, new GeographicProjection(Ellipsoid.WGS84));
+ scene.camera.lookUp(1.2); // Horizon-view
return updateUntilDone(scene.globe).then(function() {
expect(scene).notToRender([0, 0, 0, 255]);
diff --git a/Specs/Scene/GlobeSurfaceTileSpec.js b/Specs/Scene/GlobeSurfaceTileSpec.js
index 1078a639c616..ce53254c832d 100644
--- a/Specs/Scene/GlobeSurfaceTileSpec.js
+++ b/Specs/Scene/GlobeSurfaceTileSpec.js
@@ -9,7 +9,6 @@ defineSuite([
'Core/GeographicTilingScheme',
'Core/Ray',
'Core/Rectangle',
- 'Core/WebMercatorTilingScheme',
'Scene/Imagery',
'Scene/ImageryLayer',
'Scene/ImageryLayerCollection',
@@ -31,7 +30,6 @@ defineSuite([
GeographicTilingScheme,
Ray,
Rectangle,
- WebMercatorTilingScheme,
Imagery,
ImageryLayer,
ImageryLayerCollection,
@@ -97,7 +95,7 @@ defineSuite([
});
beforeEach(function() {
- tilingScheme = new WebMercatorTilingScheme();
+ tilingScheme = new GeographicTilingScheme();
alwaysDeferTerrainProvider.tilingScheme = tilingScheme;
alwaysFailTerrainProvider.tilingScheme = tilingScheme;
rootTiles = QuadtreeTile.createLevelZeroTiles(tilingScheme);
diff --git a/Specs/Scene/JobSchedulerSpec.js b/Specs/Scene/JobSchedulerSpec.js
new file mode 100644
index 000000000000..21d607d788d5
--- /dev/null
+++ b/Specs/Scene/JobSchedulerSpec.js
@@ -0,0 +1,195 @@
+/*global defineSuite*/
+defineSuite([
+ 'Scene/JobScheduler',
+ 'Scene/JobType'
+ ], function(
+ JobScheduler,
+ JobType) {
+ 'use strict';
+
+ var originalGetTimestamp;
+
+ beforeAll(function() {
+ originalGetTimestamp = JobScheduler.getTimestamp;
+
+ var time = 0.0;
+ JobScheduler.getTimestamp = function() {
+ return time++;
+ };
+ });
+
+ afterAll(function() {
+ JobScheduler.getTimestamp = originalGetTimestamp;
+ });
+
+ ///////////////////////////////////////////////////////////////////////////
+
+ var MockJob = function() {
+ this.executed = false;
+ };
+
+ MockJob.prototype.execute = function() {
+ this.executed = true;
+ };
+
+ ///////////////////////////////////////////////////////////////////////////
+
+ it('constructs with defaults', function() {
+ var js = new JobScheduler();
+ expect(js.totalBudget).toEqual(50.0);
+
+ var budgets = js._budgets;
+ expect(budgets.length).toEqual(JobType.NUMBER_OF_JOB_TYPES);
+ expect(budgets[JobType.TEXTURE].total).toEqual(10.0);
+ expect(budgets[JobType.PROGRAM].total).toEqual(10.0);
+ expect(budgets[JobType.BUFFER].total).toEqual(30.0);
+ });
+
+ it('executes a job', function() {
+ var js = new JobScheduler([2.0, // JobType.TEXTURE
+ 0.0, // JobType.PROGRAM
+ 0.0]); // JobType.BUFFER
+
+ var job = new MockJob();
+ var executed = js.execute(job, JobType.TEXTURE);
+
+ expect(executed).toEqual(true);
+ expect(job.executed).toEqual(true);
+ expect(js._totalUsedThisFrame).toEqual(1.0);
+ expect(js._budgets[JobType.TEXTURE].total).toEqual(2.0);
+ expect(js._budgets[JobType.TEXTURE].usedThisFrame).toEqual(1.0);
+ });
+
+ it('disableThisFrame does not execute a job', function() {
+ var js = new JobScheduler([2.0, 0.0, 0.0]);
+ expect(js.execute(new MockJob(), JobType.TEXTURE)).toEqual(true);
+
+ js.disableThisFrame();
+ expect(js.execute(new MockJob(), JobType.TEXTURE)).toEqual(false);
+ });
+
+ it('executes different job types', function() {
+ var js = new JobScheduler([1.0, 1.0, 1.0]);
+ expect(js.execute(new MockJob(), JobType.TEXTURE)).toEqual(true);
+ expect(js.execute(new MockJob(), JobType.PROGRAM)).toEqual(true);
+ expect(js.execute(new MockJob(), JobType.BUFFER)).toEqual(true);
+
+ expect(js._totalUsedThisFrame).toEqual(3.0);
+ var budgets = js._budgets;
+ expect(budgets[JobType.TEXTURE].usedThisFrame).toEqual(1.0);
+ expect(budgets[JobType.PROGRAM].usedThisFrame).toEqual(1.0);
+ expect(budgets[JobType.BUFFER].usedThisFrame).toEqual(1.0);
+ });
+
+ it('executes a second job', function() {
+ var js = new JobScheduler([2.0, 0.0, 0.0]);
+ expect(js.execute(new MockJob(), JobType.TEXTURE)).toEqual(true);
+ expect(js.execute(new MockJob(), JobType.TEXTURE)).toEqual(true);
+ expect(js._totalUsedThisFrame).toEqual(2.0);
+ expect(js._budgets[JobType.TEXTURE].usedThisFrame).toEqual(2.0);
+ });
+
+ it('does not execute second job (exceeds total time)', function() {
+ var js = new JobScheduler([1.0, 0.0, 0.0]);
+ expect(js.execute(new MockJob(), JobType.TEXTURE)).toEqual(true);
+ expect(js.execute(new MockJob(), JobType.TEXTURE)).toEqual(false);
+ expect(js._budgets[JobType.TEXTURE].starvedThisFrame).toEqual(true);
+ });
+
+ it('executes a second job (TEXTURE steals PROGRAM budget)', function() {
+ var js = new JobScheduler([1.0, 1.0, 0.0]);
+ expect(js.execute(new MockJob(), JobType.TEXTURE)).toEqual(true);
+ expect(js.execute(new MockJob(), JobType.TEXTURE)).toEqual(true);
+ expect(js._totalUsedThisFrame).toEqual(2.0);
+
+ var budgets = js._budgets;
+ expect(budgets[JobType.TEXTURE].usedThisFrame).toEqual(1.0);
+ expect(budgets[JobType.TEXTURE].starvedThisFrame).toEqual(true);
+ expect(budgets[JobType.PROGRAM].usedThisFrame).toEqual(0.0);
+ expect(budgets[JobType.PROGRAM].stolenFromMeThisFrame).toEqual(1.0);
+ expect(budgets[JobType.PROGRAM].starvedThisFrame).toEqual(false);
+
+ // There are no budgets left to steal from
+ expect(js.execute(new MockJob(), JobType.TEXTURE)).toEqual(false);
+ expect(js.execute(new MockJob(), JobType.PROGRAM)).toEqual(true); // Allowed once per frame
+ expect(js.execute(new MockJob(), JobType.PROGRAM)).toEqual(false);
+ expect(budgets[JobType.PROGRAM].starvedThisFrame).toEqual(true);
+ });
+
+ it('does not steal in the same frame', function() {
+ var js = new JobScheduler([1.0, 1.0, 1.0]);
+ expect(js.execute(new MockJob(), JobType.TEXTURE)).toEqual(true);
+ expect(js.execute(new MockJob(), JobType.PROGRAM)).toEqual(true);
+ expect(js.execute(new MockJob(), JobType.BUFFER)).toEqual(true);
+
+ // Exhaust budget for all job types in the first frame
+ expect(js.execute(new MockJob(), JobType.TEXTURE)).toEqual(false);
+ expect(js.execute(new MockJob(), JobType.PROGRAM)).toEqual(false);
+ expect(js.execute(new MockJob(), JobType.BUFFER)).toEqual(false);
+
+ // In this next frame, no job type can steal from another since
+ // they all exhausted their budgets in the previous frame
+ js.resetBudgets();
+ expect(js.execute(new MockJob(), JobType.TEXTURE)).toEqual(true);
+ expect(js.execute(new MockJob(), JobType.TEXTURE)).toEqual(false);
+
+ expect(js.execute(new MockJob(), JobType.PROGRAM)).toEqual(true);
+ expect(js.execute(new MockJob(), JobType.PROGRAM)).toEqual(false);
+
+ expect(js.execute(new MockJob(), JobType.BUFFER)).toEqual(true);
+ expect(js.execute(new MockJob(), JobType.BUFFER)).toEqual(false);
+ });
+
+ it('does not steal from starving job types over multiple frames', function() {
+ var js = new JobScheduler([1.0, 1.0, 0.0]);
+
+ // Exhaust in first frame
+ expect(js.execute(new MockJob(), JobType.TEXTURE)).toEqual(true);
+ expect(js.execute(new MockJob(), JobType.TEXTURE)).toEqual(true); // Stolen from PROGRAM
+ expect(js.execute(new MockJob(), JobType.TEXTURE)).toEqual(false);
+
+ js.resetBudgets();
+ expect(js.execute(new MockJob(), JobType.PROGRAM)).toEqual(true);
+ expect(js.execute(new MockJob(), JobType.PROGRAM)).toEqual(false); // Can't steal from TEXTURE
+ expect(js.execute(new MockJob(), JobType.TEXTURE)).toEqual(true);
+ expect(js.execute(new MockJob(), JobType.TEXTURE)).toEqual(false);
+
+ js.resetBudgets();
+ expect(js.execute(new MockJob(), JobType.PROGRAM)).toEqual(true);
+ expect(js.execute(new MockJob(), JobType.PROGRAM)).toEqual(false); // Can't steal from TEXTURE yet
+
+ js.resetBudgets();
+ expect(js.execute(new MockJob(), JobType.PROGRAM)).toEqual(true);
+ expect(js.execute(new MockJob(), JobType.PROGRAM)).toEqual(true); // Can steal from TEXTURE since it wasn't exhausted last frame
+ });
+
+ it('Allows progress on all job types once per frame', function() {
+ var js = new JobScheduler([1.0, 1.0, 1.0]);
+
+ expect(js.execute(new MockJob(), JobType.TEXTURE)).toEqual(true);
+ expect(js.execute(new MockJob(), JobType.TEXTURE)).toEqual(true); // Steal from PROGRAM
+ expect(js.execute(new MockJob(), JobType.TEXTURE)).toEqual(true); // Steal from BUFFER
+
+ expect(js.execute(new MockJob(), JobType.TEXTURE)).toEqual(false);
+
+ expect(js.execute(new MockJob(), JobType.PROGRAM)).toEqual(true); // Still gets to make progress once this frame
+ expect(js.execute(new MockJob(), JobType.PROGRAM)).toEqual(false);
+
+ expect(js.execute(new MockJob(), JobType.BUFFER)).toEqual(true); // Still gets to make progress once this frame
+ expect(js.execute(new MockJob(), JobType.BUFFER)).toEqual(false);
+ });
+
+ it('Long job still allows progress on other job types once per frame', function() {
+ // Job duration is always 1.0 in the tests so shorten budget
+ var js = new JobScheduler([0.5, 0.2, 0.2]);
+ expect(js.execute(new MockJob(), JobType.TEXTURE)).toEqual(true); // Goes over total budget
+ expect(js.execute(new MockJob(), JobType.PROGRAM)).toEqual(true); // Still gets to make progress once this frame
+ expect(js.execute(new MockJob(), JobType.BUFFER)).toEqual(true); // Still gets to make progress once this frame
+ });
+
+ it('constructor throws when budgets.length is not JobType.NUMBER_OF_JOB_TYPES', function() {
+ expect(function() {
+ return new JobScheduler([1.0]);
+ }).toThrowDeveloperError();
+ });
+});
diff --git a/Specs/Scene/ModelSpec.js b/Specs/Scene/ModelSpec.js
index 36af82b173af..e9d07dc47350 100644
--- a/Specs/Scene/ModelSpec.js
+++ b/Specs/Scene/ModelSpec.js
@@ -262,6 +262,16 @@ defineSuite([
expect(texturedBoxModel.colorBlendAmount).toEqual(0.5);
});
+ it('preserves query string in url', function() {
+ var params = '?param1=1¶m2=2';
+ var url = texturedBoxUrl + params;
+ var model = Model.fromGltf({
+ url: url
+ });
+ expect(model._basePath).toEndWith(params);
+ expect(model._baseUri).toEndWith(params);
+ });
+
it('renders', function() {
verifyRender(texturedBoxModel);
});
@@ -400,6 +410,24 @@ defineSuite([
});
});
+ it('rejects readyPromise on error', function() {
+ var invalidGltf = clone(texturedBoxModel.gltf, true);
+ invalidGltf.shaders.CesiumTexturedBoxTest0FS.uri = 'invalid.glsl';
+
+ var model = primitives.add(new Model({
+ gltf : invalidGltf
+ }));
+
+ scene.renderForSpecs();
+
+ return model.readyPromise.then(function(model) {
+ fail('should not resolve');
+ }).otherwise(function(error) {
+ expect(model.ready).toEqual(false);
+ primitives.remove(model);
+ });
+ });
+
it('renders from glTF', function() {
// Simulate using procedural glTF as opposed to loading it from a file
return loadModelJson(texturedBoxModel.gltf).then(function(model) {
@@ -2265,6 +2293,36 @@ defineSuite([
});
});
+ it('gets triangle count', function() {
+ expect(texturedBoxModel.trianglesLength).toBe(12);
+ expect(cesiumAirModel.trianglesLength).toBe(5984);
+ });
+
+ it('gets memory usage', function() {
+ // Texture is originally 211*211 but is scaled up to 256*256 to support its minification filter and then is mipmapped
+ var expectedTextureMemory = Math.floor(256*256*4*(4/3));
+ var expectedVertexMemory = 840;
+ var options = {
+ cacheKey : 'memory-usage-test',
+ incrementallyLoadTextures : false
+ };
+ return loadModel(texturedBoxUrl, options).then(function(model) {
+ // The first model owns the resources
+ expect(model.vertexMemorySizeInBytes).toBe(expectedVertexMemory);
+ expect(model.textureMemorySizeInBytes).toBe(expectedTextureMemory);
+ expect(model.cachedVertexMemorySizeInBytes).toBe(0);
+ expect(model.cachedTextureMemorySizeInBytes).toBe(0);
+
+ return loadModel(texturedBoxUrl, options).then(function(model) {
+ // The second model is sharing the resources, so its memory usage is reported as 0
+ expect(model.vertexMemorySizeInBytes).toBe(0);
+ expect(model.textureMemorySizeInBytes).toBe(0);
+ expect(model.cachedVertexMemorySizeInBytes).toBe(expectedVertexMemory);
+ expect(model.cachedTextureMemorySizeInBytes).toBe(expectedTextureMemory);
+ });
+ });
+ });
+
describe('height referenced model', function() {
function createMockGlobe() {
var globe = {
diff --git a/Specs/Scene/TileBoundingBoxSpec.js b/Specs/Scene/TileBoundingRegionSpec.js
similarity index 57%
rename from Specs/Scene/TileBoundingBoxSpec.js
rename to Specs/Scene/TileBoundingRegionSpec.js
index 0eb35f667f5b..b2fe4de76a83 100644
--- a/Specs/Scene/TileBoundingBoxSpec.js
+++ b/Specs/Scene/TileBoundingRegionSpec.js
@@ -1,28 +1,39 @@
/*global defineSuite*/
defineSuite([
- 'Scene/TileBoundingBox',
+ 'Scene/TileBoundingRegion',
'Core/Cartesian2',
'Core/Cartesian3',
'Core/Cartographic',
+ 'Core/Color',
'Core/Ellipsoid',
'Core/GeographicTilingScheme',
+ 'Core/Intersect',
'Core/Math',
+ 'Core/Plane',
'Core/Rectangle',
'Scene/SceneMode',
'Specs/createFrameState'
], function(
- TileBoundingBox,
+ TileBoundingRegion,
Cartesian2,
Cartesian3,
Cartographic,
+ Color,
Ellipsoid,
GeographicTilingScheme,
+ Intersect,
CesiumMath,
+ Plane,
Rectangle,
SceneMode,
createFrameState) {
'use strict';
+ var boundingVolumeRegion = [0.0, 0.0, 1.0, 1.0, 0, 1];
+ var regionBox = boundingVolumeRegion.slice(0, 4);
+ var rectangle = new Rectangle(regionBox[0], regionBox[1], regionBox[2], regionBox[3]);
+ var tileBoundingRegion = new TileBoundingRegion({maximumHeight: boundingVolumeRegion[5], minimumHeight: boundingVolumeRegion[4], rectangle: rectangle});
+
var frameState;
var camera;
@@ -32,11 +43,49 @@ defineSuite([
});
it('throws when options.rectangle is undefined', function() {
- expect(function(){
- return new TileBoundingBox();
+ expect(function() {
+ return new TileBoundingRegion();
}).toThrowDeveloperError();
});
+ it('can be instantiated with rectangle and heights', function() {
+ var minimumHeight = boundingVolumeRegion[4];
+ var maximumHeight = boundingVolumeRegion[5];
+ var tbr = new TileBoundingRegion({maximumHeight: maximumHeight, minimumHeight: minimumHeight, rectangle: rectangle});
+ expect(tbr).toBeDefined();
+ expect(tbr.boundingVolume).toBeDefined();
+ expect(tbr.boundingSphere).toBeDefined();
+ expect(tbr.rectangle).toEqual(rectangle);
+ expect(tbr.minimumHeight).toEqual(minimumHeight);
+ expect(tbr.maximumHeight).toEqual(maximumHeight);
+ });
+
+ it('can be instantiated with only a rectangle', function() {
+ var tbr = new TileBoundingRegion({rectangle: rectangle});
+ expect(tbr).toBeDefined();
+ expect(tbr.boundingVolume).toBeDefined();
+ expect(tbr.boundingSphere).toBeDefined();
+ expect(tbr.rectangle).toEqual(rectangle);
+ expect(tbr.minimumHeight).toBeDefined();
+ expect(tbr.maximumHeight).toBeDefined();
+ });
+
+ it('distanceToCamera throws when frameState is undefined', function() {
+ expect(function() {
+ return tileBoundingRegion.distanceToCamera();
+ }).toThrowDeveloperError();
+ });
+
+ it('distance to camera is 0 when camera is inside bounding region', function() {
+ camera.position = Cartesian3.fromRadians(regionBox[0] + CesiumMath.EPSILON6, regionBox[1], 0);
+ expect(tileBoundingRegion.distanceToCamera(frameState)).toEqual(0.0);
+ });
+
+ it('distance to camera is correct when camera is outside bounding region', function() {
+ camera.position = Cartesian3.fromRadians(regionBox[0], regionBox[1], 2.0);
+ expect(tileBoundingRegion.distanceToCamera(frameState)).toEqualEpsilon(1.0, CesiumMath.EPSILON6);
+ });
+
it('distanceToCamera', function() {
var offset = 0.0001;
var west = -0.001;
@@ -44,7 +93,7 @@ defineSuite([
var east = 0.001;
var north = 0.001;
- var tile = new TileBoundingBox({
+ var tile = new TileBoundingRegion({
rectangle : new Rectangle(west, south, east, north),
minimumHeight : 0.0,
maximumHeight : 10.0
@@ -81,7 +130,7 @@ defineSuite([
cameraPositionCartographic.south -= CesiumMath.EPSILON8;
- var tile = new TileBoundingBox({
+ var tile = new TileBoundingRegion({
rectangle : rectangle,
minimumHeight : 0.0,
maximumHeight : 10.0
@@ -92,7 +141,7 @@ defineSuite([
expect(tile.distanceToCamera(frameState)).toBeLessThan(CesiumMath.EPSILON8 * ellipsoid.maximumRadius);
});
- it('distanceToCamera close to north plane at the southern hemisphere', function() {
+ it('distanceToCamera close to north plane at the southern hemisphere', function() {
var ellipsoid = Ellipsoid.WGS84;
var tilingScheme = new GeographicTilingScheme({ellipsoid : ellipsoid});
@@ -102,7 +151,7 @@ defineSuite([
cameraPositionCartographic.north += CesiumMath.EPSILON8;
- var tile = new TileBoundingBox({
+ var tile = new TileBoundingRegion({
rectangle : rectangle,
minimumHeight : 0.0,
maximumHeight : 10.0
@@ -122,7 +171,7 @@ defineSuite([
var east = 0.001;
var north = 0.001;
- var tile = new TileBoundingBox({
+ var tile = new TileBoundingRegion({
rectangle : new Rectangle(west, south, east, north),
minimumHeight : 0.0,
maximumHeight : 10.0
@@ -144,4 +193,32 @@ defineSuite([
camera.position = Cartesian3.fromRadians(position3D.longitude, position3D.latitude);
expect(tile.distanceToCamera(frameState)).toEqualEpsilon(expectedDistance, 10.0);
});
+
+ it('createDebugVolume throws when color is undefined', function() {
+ expect(function() {
+ return tileBoundingRegion.createDebugVolume();
+ }).toThrowDeveloperError();
+ });
+
+ it('can create a debug volume', function() {
+ var debugVolume = tileBoundingRegion.createDebugVolume(Color.BLUE);
+ expect(debugVolume).toBeDefined();
+ });
+
+ it('intersectPlane throws when plane is undefined', function() {
+ expect(function() {
+ return tileBoundingRegion.intersectPlane();
+ }).toThrowDeveloperError();
+ });
+
+ it('intersects plane', function() {
+ var normal = new Cartesian3();
+ Cartesian3.normalize(Cartesian3.fromRadians(0.0, 0.0, 1.0), normal);
+ var distanceFromCenter = Cartesian3.distance(
+ new Cartesian3(0.0, 0.0, 0.0),
+ Cartesian3.fromRadians(0.0, 0.0, 0.0)
+ );
+ var plane = new Plane(normal, -distanceFromCenter);
+ expect(tileBoundingRegion.intersectPlane(plane)).toEqual(Intersect.INTERSECTING);
+ });
});
diff --git a/Specs/Scene/TileBoundingSphereSpec.js b/Specs/Scene/TileBoundingSphereSpec.js
new file mode 100644
index 000000000000..bdf9482b7c4c
--- /dev/null
+++ b/Specs/Scene/TileBoundingSphereSpec.js
@@ -0,0 +1,73 @@
+/*global defineSuite*/
+defineSuite([
+ 'Scene/TileBoundingSphere',
+ 'Core/Cartesian3',
+ 'Core/Color',
+ 'Core/Intersect',
+ 'Core/Math',
+ 'Core/Plane',
+ 'Specs/createFrameState'
+ ], function(
+ TileBoundingSphere,
+ Cartesian3,
+ Color,
+ Intersect,
+ CesiumMath,
+ Plane,
+ createFrameState) {
+ 'use strict';
+
+ var tileBoundingSphere = new TileBoundingSphere(new Cartesian3(0.0, 0.0, 0.0), 1.0);
+ var frameState = createFrameState();
+
+ it('can be instantiated with center and radius', function() {
+ var center = new Cartesian3(0.0, 0.0, 0.0);
+ var radius = 1.0;
+ var tbs = new TileBoundingSphere(center, radius);
+ expect(tbs).toBeDefined();
+ expect(tbs.boundingVolume).toBeDefined();
+ expect(tbs.boundingSphere).toBeDefined();
+ expect(tbs.center).toEqual(center);
+ expect(tbs.radius).toEqual(radius);
+ });
+
+ it('createDebugVolume throws when color is undefined', function() {
+ expect(function() {
+ return tileBoundingSphere.createDebugVolume();
+ }).toThrowDeveloperError();
+ });
+
+ it('can create a debug volume', function() {
+ var debugVolume = tileBoundingSphere.createDebugVolume(Color.BLUE);
+ expect(debugVolume).toBeDefined();
+ });
+
+ it('distanceToCamera throws when frameState is undefined', function() {
+ expect(function() {
+ return tileBoundingSphere.distanceToCamera();
+ }).toThrowDeveloperError();
+ });
+
+ it('distance to camera is 0 when camera is inside bounding sphere', function() {
+ frameState.camera.position = new Cartesian3(0.0, 0.0, 0.0);
+ expect(tileBoundingSphere.distanceToCamera(frameState)).toEqual(0.0);
+ });
+
+ it('distance to camera is correct when camera is outside bounding region', function() {
+ frameState.camera.position = new Cartesian3(0.0, 2.0, 0.0);
+ expect(tileBoundingSphere.distanceToCamera(frameState)).toEqual(1.0);
+ });
+
+ it('intersectPlane throws when plane is undefined', function() {
+ expect(function() {
+ return tileBoundingSphere.intersectPlane();
+ }).toThrowDeveloperError();
+ });
+
+ it('intersects plane', function() {
+ var normal = new Cartesian3(0.0, 0.0, 1.0);
+ var plane = new Plane(normal, CesiumMath.EPSILON6);
+ expect(tileBoundingSphere.intersectPlane(plane)).toEqual(Intersect.INTERSECTING);
+ });
+
+});
diff --git a/Specs/Scene/TileBoundingVolumeSpec.js b/Specs/Scene/TileBoundingVolumeSpec.js
new file mode 100644
index 000000000000..cf64783bb482
--- /dev/null
+++ b/Specs/Scene/TileBoundingVolumeSpec.js
@@ -0,0 +1,20 @@
+/*global defineSuite*/
+defineSuite([
+ 'Scene/TileBoundingVolume'
+ ], function(
+ TileBoundingVolume) {
+ 'use strict';
+
+ it('throws', function() {
+ var boundingVolume = new TileBoundingVolume();
+ expect(function() {
+ boundingVolume.createDebugVolume();
+ }).toThrowDeveloperError();
+ expect(function() {
+ boundingVolume.distanceToCamera();
+ }).toThrowDeveloperError();
+ expect(function() {
+ boundingVolume.intersectPlane();
+ }).toThrowDeveloperError();
+ });
+});
diff --git a/Specs/Scene/TileOrientedBoundingBoxSpec.js b/Specs/Scene/TileOrientedBoundingBoxSpec.js
new file mode 100644
index 000000000000..79d3248b6648
--- /dev/null
+++ b/Specs/Scene/TileOrientedBoundingBoxSpec.js
@@ -0,0 +1,108 @@
+/*global defineSuite*/
+defineSuite([
+ 'Scene/TileOrientedBoundingBox',
+ 'Core/Cartesian3',
+ 'Core/Color',
+ 'Core/Intersect',
+ 'Core/Math',
+ 'Core/Matrix3',
+ 'Core/Plane',
+ 'Specs/createFrameState'
+ ], function(
+ TileOrientedBoundingBox,
+ Cartesian3,
+ Color,
+ Intersect,
+ CesiumMath,
+ Matrix3,
+ Plane,
+ createFrameState) {
+ 'use strict';
+
+ var center = new Cartesian3(0.0, 0.0, 0.0);
+ var halfAxes = Matrix3.fromScale(new Cartesian3(0.5, 0.5, 0.5), new Matrix3());
+ var tileBoundingVolume = new TileOrientedBoundingBox(center, halfAxes);
+
+ var frameState = createFrameState();
+
+ it('can be instantiated with center and half-axes', function() {
+ expect(tileBoundingVolume.boundingVolume.center).toEqual(center);
+ expect(tileBoundingVolume.boundingVolume.halfAxes).toEqual(halfAxes);
+ expect(tileBoundingVolume.boundingSphere.center).toEqual(center);
+ expect(tileBoundingVolume.boundingSphere.radius).toEqual(0.5);
+ });
+
+ it('createDebugVolume throws when color is undefined', function() {
+ expect(function() {
+ return tileBoundingVolume.createDebugVolume();
+ }).toThrowDeveloperError();
+ });
+
+ it('can create debug volume', function() {
+ expect(tileBoundingVolume.createDebugVolume(Color.BLUE)).toBeDefined();
+ });
+
+ it('distanceToCamera throws when frameState is undefined', function() {
+ expect(function() {
+ return tileBoundingVolume.distanceToCamera();
+ }).toThrowDeveloperError();
+ });
+
+ it('has distance 0 to camera if camera is inside', function() {
+ frameState.camera.position = new Cartesian3(0.0, 0.0, 0.0);
+ expect(tileBoundingVolume.distanceToCamera(frameState)).toEqual(0.0);
+
+ frameState.camera.position = new Cartesian3(-0.5, -0.5, -0.5);
+ expect(tileBoundingVolume.distanceToCamera(frameState)).toEqual(0.0);
+ frameState.camera.position = new Cartesian3(0.5, 0.5, 0.5);
+ expect(tileBoundingVolume.distanceToCamera(frameState)).toEqual(0.0);
+ });
+
+ it('has correct distance to camera if camera is slightly outside box', function() {
+ var eps6 = CesiumMath.EPSILON6;
+ frameState.camera.position = new Cartesian3(0.5 + eps6, 0.5, 0.5);
+ expect(tileBoundingVolume.distanceToCamera(frameState)).not.toEqual(0.0);
+ frameState.camera.position = new Cartesian3(-0.5, -0.5, -0.5 - eps6);
+ expect(tileBoundingVolume.distanceToCamera(frameState)).not.toEqual(0.0);
+ frameState.camera.position = new Cartesian3(100.5, 100.5, 100.5);
+ expect(tileBoundingVolume.distanceToCamera(frameState)).toEqual(Math.sqrt(30000.0));
+ });
+
+ it('has correct distance to camera for large distances', function() {
+ frameState.camera.position = new Cartesian3(2170456.713380141, -36351235.19646463, 28403328.27058654);
+ expect(tileBoundingVolume.distanceToCamera(frameState)).toEqualEpsilon(46183029.05370139, CesiumMath.EPSILON6);
+ });
+
+ it('intersectPlane throws when plane is undefined', function() {
+ expect(function() {
+ return tileBoundingVolume.intersectPlane();
+ }).toThrowDeveloperError();
+ });
+
+ it('intersects plane', function() {
+ var plane = new Plane(Cartesian3.UNIT_X, 0.0);
+ expect(tileBoundingVolume.intersectPlane(plane)).toEqual(Intersect.INTERSECTING);
+ plane = new Plane(Cartesian3.UNIT_X, 0.5 - CesiumMath.EPSILON6);
+ expect(tileBoundingVolume.intersectPlane(plane)).toEqual(Intersect.INTERSECTING);
+ plane = new Plane(Cartesian3.UNIT_X, -0.5 + CesiumMath.EPSILON6);
+ expect(tileBoundingVolume.intersectPlane(plane)).toEqual(Intersect.INTERSECTING);
+ });
+
+ it('does not intersect plane', function() {
+ var eps6 = CesiumMath.EPSILON6;
+ var plane = new Plane(Cartesian3.UNIT_X, 0.5 + eps6);
+ expect(tileBoundingVolume.intersectPlane(plane)).toEqual(Intersect.INSIDE);
+ plane = new Plane(Cartesian3.UNIT_Y, 0.5 + eps6);
+ expect(tileBoundingVolume.intersectPlane(plane)).toEqual(Intersect.INSIDE);
+ plane = new Plane(Cartesian3.UNIT_Z, 0.5 + eps6);
+ expect(tileBoundingVolume.intersectPlane(plane)).toEqual(Intersect.INSIDE);
+
+ plane = new Plane(Cartesian3.UNIT_X, -0.5 - eps6);
+ expect(tileBoundingVolume.intersectPlane(plane)).toEqual(Intersect.OUTSIDE);
+ plane = new Plane(Cartesian3.UNIT_Y, -0.5 - eps6);
+ expect(tileBoundingVolume.intersectPlane(plane)).toEqual(Intersect.OUTSIDE);
+ plane = new Plane(Cartesian3.UNIT_Z, -0.5 - eps6);
+ expect(tileBoundingVolume.intersectPlane(plane)).toEqual(Intersect.OUTSIDE);
+ });
+
+});
diff --git a/Specs/createFrameState.js b/Specs/createFrameState.js
index 65fbc10abeff..418201a8cc13 100644
--- a/Specs/createFrameState.js
+++ b/Specs/createFrameState.js
@@ -5,19 +5,21 @@ define([
'Core/JulianDate',
'Scene/Camera',
'Scene/CreditDisplay',
- 'Scene/FrameState'
+ 'Scene/FrameState',
+ 'Scene/JobScheduler'
], function(
defaultValue,
GeographicProjection,
JulianDate,
Camera,
CreditDisplay,
- FrameState) {
+ FrameState,
+ JobScheduler) {
'use strict';
function createFrameState(context, camera, frameNumber, time) {
// Mock frame-state for testing.
- var frameState = new FrameState(context, new CreditDisplay(document.createElement('div')));
+ var frameState = new FrameState(context, new CreditDisplay(document.createElement('div')), new JobScheduler());
var projection = new GeographicProjection();
frameState.mapProjection = projection;
diff --git a/Specs/createScene.js b/Specs/createScene.js
index eae9dcb7d60f..ecceb37b4b44 100644
--- a/Specs/createScene.js
+++ b/Specs/createScene.js
@@ -1,5 +1,6 @@
/*global define*/
define([
+ 'Core/Cartesian2',
'Core/clone',
'Core/defaultValue',
'Core/defined',
@@ -7,6 +8,7 @@ define([
'Specs/createCanvas',
'Specs/getWebGLStub'
], function(
+ Cartesian2,
clone,
defaultValue,
defined,
@@ -57,6 +59,10 @@ define([
this.render(time);
};
+ scene.pickForSpecs = function() {
+ this.pick(new Cartesian2(0, 0));
+ };
+
scene.rethrowRenderErrors = defaultValue(options.rethrowRenderErrors, true);
return scene;
diff --git a/Specs/pick.js b/Specs/pick.js
index 3af52acd40bd..711798051a7d 100644
--- a/Specs/pick.js
+++ b/Specs/pick.js
@@ -6,7 +6,8 @@ define([
'Renderer/ClearCommand',
'Renderer/Pass',
'Scene/CreditDisplay',
- 'Scene/FrameState'
+ 'Scene/FrameState',
+ 'Scene/JobScheduler'
], function(
BoundingRectangle,
Color,
@@ -14,7 +15,8 @@ define([
ClearCommand,
Pass,
CreditDisplay,
- FrameState) {
+ FrameState,
+ JobScheduler) {
'use strict';
function executeCommands(context, passState, commands) {
@@ -34,7 +36,7 @@ define([
var passState = pickFramebuffer.begin(rectangle);
var oldPasses = frameState.passes;
- frameState.passes = (new FrameState(new CreditDisplay(document.createElement('div')))).passes;
+ frameState.passes = (new FrameState(new CreditDisplay(document.createElement('div')), new JobScheduler())).passes;
frameState.passes.pick = true;
primitives.update(frameState);
diff --git a/gulpfile.js b/gulpfile.js
index 13be1624c703..27cd4b73e29c 100644
--- a/gulpfile.js
+++ b/gulpfile.js
@@ -410,7 +410,7 @@ function deployCesium(bucketName, uploadDirectory, cacheControl, done) {
var mimeLookup = getMimeType(blobName);
var contentType = mimeLookup.type;
var compress = mimeLookup.compress;
- var contentEncoding = compress ? 'gzip' : undefined;
+ var contentEncoding = compress || mimeLookup.isCompressed ? 'gzip' : undefined;
var etag;
totalFiles++;
@@ -530,21 +530,22 @@ function deployCesium(bucketName, uploadDirectory, cacheControl, done) {
function getMimeType(filename) {
var ext = path.extname(filename);
if (ext === '.bin' || ext === '.terrain') {
- return { type : 'application/octet-stream', compress : true };
+ return {type : 'application/octet-stream', compress : true, isCompressed : false};
} else if (ext === '.md' || ext === '.glsl') {
- return { type : 'text/plain', compress : true };
+ return {type : 'text/plain', compress : true, isCompressed : false};
} else if (ext === '.czml' || ext === '.geojson' || ext === '.json') {
- return { type : 'application/json', compress : true };
+ return {type : 'application/json', compress : true, isCompressed : false};
} else if (ext === '.js') {
- return { type : 'application/javascript', compress : true };
+ return {type : 'application/javascript', compress : true, isCompressed : false};
} else if (ext === '.svg') {
- return { type : 'image/svg+xml', compress : true };
+ return {type : 'image/svg+xml', compress : true, isCompressed : false};
} else if (ext === '.woff') {
- return { type : 'application/font-woff', compress : false };
+ return {type : 'application/font-woff', compress : false, isCompressed : false};
}
var mimeType = mime.lookup(filename);
- return {type : mimeType, compress : compressible(mimeType)};
+ var compress = compressible(mimeType);
+ return {type : mimeType, compress : compress, isCompressed : false};
}
// get all files currently in bucket asynchronously