diff --git a/Apps/Sandcastle/gallery/development/Coplanar Polygon Outline.html b/Apps/Sandcastle/gallery/development/Coplanar Polygon Outline.html
new file mode 100644
index 000000000000..cd2422717461
--- /dev/null
+++ b/Apps/Sandcastle/gallery/development/Coplanar Polygon Outline.html
@@ -0,0 +1,69 @@
+
+
+
+
+
+
+
+
+ Cesium Demo
+
+
+
+
+
+
+
+Loading...
+
+
+
+
diff --git a/Apps/Sandcastle/gallery/development/Coplanar Polygon Outline.jpg b/Apps/Sandcastle/gallery/development/Coplanar Polygon Outline.jpg
new file mode 100644
index 000000000000..af7488de9916
Binary files /dev/null and b/Apps/Sandcastle/gallery/development/Coplanar Polygon Outline.jpg differ
diff --git a/Apps/Sandcastle/gallery/development/Coplanar Polygon.html b/Apps/Sandcastle/gallery/development/Coplanar Polygon.html
new file mode 100644
index 000000000000..8cfce4882ae8
--- /dev/null
+++ b/Apps/Sandcastle/gallery/development/Coplanar Polygon.html
@@ -0,0 +1,69 @@
+
+
+
+
+
+
+
+
+ Cesium Demo
+
+
+
+
+
+
+
+Loading...
+
+
+
+
diff --git a/Apps/Sandcastle/gallery/development/Coplanar Polygon.jpg b/Apps/Sandcastle/gallery/development/Coplanar Polygon.jpg
new file mode 100644
index 000000000000..2bb5ae4334dd
Binary files /dev/null and b/Apps/Sandcastle/gallery/development/Coplanar Polygon.jpg differ
diff --git a/CHANGES.md b/CHANGES.md
index a91c2f588467..7414ce2bb570 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -3,13 +3,14 @@ Change Log
### 1.48 - 2018-08-01
-##### Additions :tada:
-* Added support for loading Draco compressed Point Cloud tiles for 2-3x better compression. [#6559](https://github.com/AnalyticalGraphicsInc/cesium/pull/6559)
-
##### Deprecated :hourglass_flowing_sand:
* Support for 3D Tiles `content.url` is deprecated to reflect updates to the [3D Tiles spec](https://github.com/AnalyticalGraphicsInc/3d-tiles/pull/301). Use `content.uri instead`. Support for `content.url` will remain for backwards compatibility. [#6744](https://github.com/AnalyticalGraphicsInc/cesium/pull/6744)
-##### Fixes :wrench:
+##### Additions :tada:
+* Added support for loading Draco compressed Point Cloud tiles for 2-3x better compression. [#6559](https://github.com/AnalyticalGraphicsInc/cesium/pull/6559)
+* Added `CoplanarPolygonGeometry` and `CoplanarPolygonGeometryOutline` for drawing polygons composed of coplanar positions that are not necessarliy on the ellipsoid surface. [#6769](https://github.com/AnalyticalGraphicsInc/cesium/pull/6769)
+
+#### Fixes :wrench:
* Fixed bug causing billboards and labels to appear the wrong size when switching scene modes [#6745](https://github.com/AnalyticalGraphicsInc/cesium/issues/6745)
* Fixed a bug that was preventing 3D Tilesets on the opposite side of the globe from being occluded [#6714](https://github.com/AnalyticalGraphicsInc/cesium/issues/6714)
diff --git a/Source/Core/CoplanarPolygonGeometry.js b/Source/Core/CoplanarPolygonGeometry.js
new file mode 100644
index 000000000000..3957cb66e3c9
--- /dev/null
+++ b/Source/Core/CoplanarPolygonGeometry.js
@@ -0,0 +1,318 @@
+/*global define*/
+define([
+ './arrayRemoveDuplicates',
+ './BoundingRectangle',
+ './BoundingSphere',
+ './Cartesian2',
+ './Cartesian3',
+ './Check',
+ './ComponentDatatype',
+ './CoplanarPolygonGeometryLibrary',
+ './defaultValue',
+ './defined',
+ './Geometry',
+ './GeometryAttribute',
+ './GeometryAttributes',
+ './IndexDatatype',
+ './Math',
+ './PolygonPipeline',
+ './PrimitiveType',
+ './VertexFormat',
+ './WindingOrder'
+ ], function(
+ arrayRemoveDuplicates,
+ BoundingRectangle,
+ BoundingSphere,
+ Cartesian2,
+ Cartesian3,
+ Check,
+ ComponentDatatype,
+ CoplanarPolygonGeometryLibrary,
+ defaultValue,
+ defined,
+ Geometry,
+ GeometryAttribute,
+ GeometryAttributes,
+ IndexDatatype,
+ CesiumMath,
+ PolygonPipeline,
+ PrimitiveType,
+ VertexFormat,
+ WindingOrder) {
+ 'use strict';
+
+ var scratchPositions2D = [];
+ var scratchBR = new BoundingRectangle();
+ var textureCoordinatesOrigin = new Cartesian2();
+ var scratchNormal = new Cartesian3();
+ var scratchTangent = new Cartesian3();
+ var scratchBitangent = new Cartesian3();
+
+ /**
+ * A description of a polygon composed of arbitrary coplanar positions.
+ *
+ * @alias CoplanarPolygonGeometry
+ * @constructor
+ *
+ * @param {Object} options Object with the following properties:
+ * @param {Cartesian3[]} options.positions The positions of the polygon
+ * @param {VertexFormat} [options.vertexFormat=VertexFormat.DEFAULT] The vertex attributes to be computed.
+ *
+ * @example
+ * var polygon = new Cesium.CoplanarPolygonGeometry({
+ * positions : Cesium.Cartesian3.fromDegreesArrayHeights([
+ * -90.0, 30.0, 0.0,
+ * -90.0, 30.0, 1000.0,
+ * -80.0, 30.0, 1000.0,
+ * -80.0, 30.0, 0.0
+ * ])
+ * });
+ * var geometry = Cesium.CoplanarPolygonGeometry.createGeometry(polygon);
+ *
+ * @see CoplanarPolygonGeometry.createGeometry
+ */
+ function CoplanarPolygonGeometry(options) {
+ options = defaultValue(options, defaultValue.EMPTY_OBJECT);
+ var positions = options.positions;
+ //>>includeStart('debug', pragmas.debug);
+ Check.defined('options.positions', positions);
+ //>>includeEnd('debug');
+
+ var vertexFormat = defaultValue(options.vertexFormat, VertexFormat.DEFAULT);
+ this._vertexFormat = VertexFormat.clone(vertexFormat);
+ this._positions = positions;
+ this._workerName = 'createCoplanarPolygonGeometry';
+
+ /**
+ * The number of elements used to pack the object into an array.
+ * @type {Number}
+ */
+ this.packedLength = 1 + positions.length * Cartesian3.packedLength + VertexFormat.packedLength;
+ }
+
+ /**
+ * Stores the provided instance into the provided array.
+ *
+ * @param {CoplanarPolygonGeometry} value The value to pack.
+ * @param {Number[]} array The array to pack into.
+ * @param {Number} [startingIndex=0] The index into the array at which to start packing the elements.
+ *
+ * @returns {Number[]} The array that was packed into
+ */
+ CoplanarPolygonGeometry.pack = function(value, array, startingIndex) {
+ //>>includeStart('debug', pragmas.debug);
+ Check.typeOf.object('value', value);
+ Check.defined('array', array);
+ //>>includeEnd('debug');
+
+ startingIndex = defaultValue(startingIndex, 0);
+
+ var positions = value._positions;
+ var length = positions.length;
+ array[startingIndex++] = length;
+
+ for (var i = 0; i < length; ++i, startingIndex += Cartesian3.packedLength) {
+ Cartesian3.pack(positions[i], array, startingIndex);
+ }
+
+ VertexFormat.pack(value._vertexFormat, array, startingIndex);
+
+ return array;
+ };
+
+ var scratchVertexFormat = new VertexFormat();
+ var scratchOptions = {
+ positions : undefined,
+ vertexFormat : scratchVertexFormat
+ };
+ /**
+ * Retrieves an instance from a packed array.
+ *
+ * @param {Number[]} array The packed array.
+ * @param {Number} [startingIndex=0] The starting index of the element to be unpacked.
+ * @param {CoplanarPolygonGeometry} [result] The object into which to store the result.
+ * @returns {CoplanarPolygonGeometry} The modified result parameter or a new CoplanarPolygonGeometry instance if one was not provided.
+ */
+ CoplanarPolygonGeometry.unpack = function(array, startingIndex, result) {
+ //>>includeStart('debug', pragmas.debug);
+ Check.defined('array', array);
+ //>>includeEnd('debug');
+
+ startingIndex = defaultValue(startingIndex, 0);
+
+ var i;
+
+ var length = array[startingIndex++];
+ var positions = new Array(length);
+
+ for (i = 0; i < length; ++i, startingIndex += Cartesian3.packedLength) {
+ positions[i] = Cartesian3.unpack(array, startingIndex);
+ }
+
+ var vertexFormat = VertexFormat.unpack(array, startingIndex, scratchVertexFormat);
+
+ if (!defined(result)) {
+ scratchOptions.positions = positions;
+ scratchOptions.vertexFormat = vertexFormat;
+ return new CoplanarPolygonGeometry(scratchOptions);
+ }
+
+ result._positions = positions;
+ result._vertexFormat = VertexFormat.clone(vertexFormat, result._vertexFormat);
+
+ return result;
+ };
+
+ /**
+ * Computes the geometric representation of an arbitrary coplanar polygon, including its vertices, indices, and a bounding sphere.
+ *
+ * @param {CoplanarPolygonGeometry} polygonGeometry A description of the polygon.
+ * @returns {Geometry|undefined} The computed vertices and indices.
+ */
+ CoplanarPolygonGeometry.createGeometry = function(polygonGeometry) {
+ var vertexFormat = polygonGeometry._vertexFormat;
+ var positions = polygonGeometry._positions;
+ positions = arrayRemoveDuplicates(positions, Cartesian3.equalsEpsilon, true);
+ if (positions.length < 3) {
+ return;
+ }
+ var boundingSphere = BoundingSphere.fromPoints(positions);
+
+ var normal;
+ var tangent;
+ var bitangent;
+ if (vertexFormat.normal) {
+ normal = scratchNormal;
+ }
+ if (vertexFormat.tangent) {
+ tangent = scratchTangent;
+ }
+ if (vertexFormat.bitangent) {
+ bitangent = scratchBitangent;
+ }
+ var positions2D = CoplanarPolygonGeometryLibrary.projectTo2D(positions, scratchPositions2D, normal, tangent, bitangent);
+ if (!defined(positions2D)) {
+ return;
+ }
+
+ if (PolygonPipeline.computeWindingOrder2D(positions2D) === WindingOrder.CLOCKWISE) {
+ positions2D.reverse();
+ positions = positions.slice().reverse();
+ }
+
+ var indices = PolygonPipeline.triangulate(positions2D);
+ if (indices.length < 3) {
+ return;
+ }
+ var newIndices = IndexDatatype.createTypedArray(positions.length, indices.length);
+ newIndices.set(indices);
+
+ var boundingRectangle;
+ var stOrigin = textureCoordinatesOrigin;
+ if (vertexFormat.st) {
+ boundingRectangle = BoundingRectangle.fromPoints(positions2D, scratchBR);
+ stOrigin.x = boundingRectangle.x;
+ stOrigin.y = boundingRectangle.y;
+ }
+
+ var length = positions.length;
+ var size = length * 3;
+ var flatPositions = new Float64Array(size);
+ var normals = vertexFormat.normal ? new Float32Array(size) : undefined;
+ var tangents = vertexFormat.tangent ? new Float32Array(size) : undefined;
+ var bitangents = vertexFormat.bitangent ? new Float32Array(size) : undefined;
+ var textureCoordinates = vertexFormat.st ? new Float32Array(length * 2) : undefined;
+
+ var positionIndex = 0;
+ var normalIndex = 0;
+ var bitangentIndex = 0;
+ var tangentIndex = 0;
+ var stIndex = 0;
+
+ for (var i = 0; i < length; i++) {
+ var position = positions[i];
+ flatPositions[positionIndex++] = position.x;
+ flatPositions[positionIndex++] = position.y;
+ flatPositions[positionIndex++] = position.z;
+
+ if (vertexFormat.st) {
+ var st = positions2D[i];
+ st = Cartesian2.subtract(st, stOrigin, st);
+
+ var stx = CesiumMath.clamp(st.x / boundingRectangle.width, 0, 1);
+ var sty = CesiumMath.clamp(st.y / boundingRectangle.height, 0, 1);
+ textureCoordinates[stIndex++] = stx;
+ textureCoordinates[stIndex++] = sty;
+ }
+
+ if (vertexFormat.normal) {
+ normals[normalIndex++] = normal.x;
+ normals[normalIndex++] = normal.y;
+ normals[normalIndex++] = normal.z;
+ }
+
+ if (vertexFormat.tangent) {
+ tangents[tangentIndex++] = tangent.x;
+ tangents[tangentIndex++] = tangent.y;
+ tangents[tangentIndex++] = tangent.z;
+ }
+
+ if (vertexFormat.bitangent) {
+ bitangents[bitangentIndex++] = bitangent.x;
+ bitangents[bitangentIndex++] = bitangent.y;
+ bitangents[bitangentIndex++] = bitangent.z;
+ }
+ }
+
+ var attributes = new GeometryAttributes();
+
+ if (vertexFormat.position) {
+ attributes.position = new GeometryAttribute({
+ componentDatatype : ComponentDatatype.DOUBLE,
+ componentsPerAttribute : 3,
+ values : flatPositions
+ });
+ }
+
+ if (vertexFormat.normal) {
+ attributes.normal = new GeometryAttribute({
+ componentDatatype : ComponentDatatype.FLOAT,
+ componentsPerAttribute : 3,
+ values : normals
+ });
+ }
+
+ if (vertexFormat.tangent) {
+ attributes.tangent = new GeometryAttribute({
+ componentDatatype : ComponentDatatype.FLOAT,
+ componentsPerAttribute : 3,
+ values : tangents
+ });
+ }
+
+ if (vertexFormat.bitangent) {
+ attributes.bitangent = new GeometryAttribute({
+ componentDatatype : ComponentDatatype.FLOAT,
+ componentsPerAttribute : 3,
+ values : bitangents
+ });
+ }
+
+ if (vertexFormat.st) {
+ attributes.st = new GeometryAttribute({
+ componentDatatype : ComponentDatatype.FLOAT,
+ componentsPerAttribute : 2,
+ values : textureCoordinates
+ });
+ }
+
+ return new Geometry({
+ attributes : attributes,
+ indices : newIndices,
+ primitiveType : PrimitiveType.TRIANGLES,
+ boundingSphere : boundingSphere
+ });
+ };
+
+ return CoplanarPolygonGeometry;
+});
diff --git a/Source/Core/CoplanarPolygonGeometryLibrary.js b/Source/Core/CoplanarPolygonGeometryLibrary.js
new file mode 100644
index 000000000000..8942e138e4af
--- /dev/null
+++ b/Source/Core/CoplanarPolygonGeometryLibrary.js
@@ -0,0 +1,100 @@
+define([
+ './defined',
+ './Cartesian2',
+ './Cartesian3',
+ './Check',
+ './IntersectionTests',
+ './Math',
+ './Matrix3',
+ './OrientedBoundingBox'
+ ], function(
+ defined,
+ Cartesian2,
+ Cartesian3,
+ Check,
+ IntersectionTests,
+ CesiumMath,
+ Matrix3,
+ OrientedBoundingBox
+ ) {
+ 'use strict';
+
+ /**
+ * @private
+ */
+ var PolygonGeometryLibrary = {};
+
+ var scratchIntersectionPoint = new Cartesian3();
+ var scratchXAxis = new Cartesian3();
+ var scratchYAxis = new Cartesian3();
+ var scratchZAxis = new Cartesian3();
+ var obbScratch = new OrientedBoundingBox();
+
+ // call after removeDuplicates
+ PolygonGeometryLibrary.projectTo2D = function(positions, positionsResult, normalResult, tangentResult, bitangentResult) {
+ //>>includeStart('debug', pragmas.debug);
+ Check.defined('positions', positions);
+ Check.defined('positionsResult', positionsResult);
+ //>>includeEnd('debug');
+
+ var orientedBoundingBox = OrientedBoundingBox.fromPoints(positions, obbScratch);
+ var halfAxes = orientedBoundingBox.halfAxes;
+ var xAxis = Matrix3.getColumn(halfAxes, 0, scratchXAxis);
+ var yAxis = Matrix3.getColumn(halfAxes, 1, scratchYAxis);
+ var zAxis = Matrix3.getColumn(halfAxes, 2, scratchZAxis);
+
+ var xMag = Cartesian3.magnitude(xAxis);
+ var yMag = Cartesian3.magnitude(yAxis);
+ var zMag = Cartesian3.magnitude(zAxis);
+ var min = Math.min(xMag, yMag, zMag);
+
+ // If all the points are on a line return undefined because we can't draw a polygon
+ if ((xMag === 0 && (yMag === 0 || zMag === 0)) || (yMag === 0 && zMag === 0)) {
+ return;
+ }
+
+ var planeAxis1;
+ var planeAxis2;
+
+ if (min === yMag || min === zMag) {
+ planeAxis1 = xAxis;
+ }
+ if (min === xMag) {
+ planeAxis1 = yAxis;
+ } else if (min === zMag) {
+ planeAxis2 = yAxis;
+ }
+ if (min === xMag || min === yMag) {
+ planeAxis2 = zAxis;
+ }
+
+ planeAxis1 = Cartesian3.normalize(planeAxis1, planeAxis1);
+ planeAxis2 = Cartesian3.normalize(planeAxis2, planeAxis2);
+
+ if (defined(normalResult)) {
+ normalResult = Cartesian3.cross(planeAxis1, planeAxis2, normalResult);
+ normalResult = Cartesian3.normalize(normalResult, normalResult);
+ }
+ if (defined(tangentResult)) {
+ Cartesian3.clone(planeAxis1, tangentResult);
+ }
+ if (defined(bitangentResult)) {
+ Cartesian3.clone(planeAxis2, bitangentResult);
+ }
+
+ for (var i = 0; i < positions.length; i++) {
+ var position = positions[i];
+ var v = Cartesian3.subtract(position, orientedBoundingBox.center, scratchIntersectionPoint);
+ var x = Cartesian3.dot(planeAxis1, v);
+ var y = Cartesian3.dot(planeAxis2, v);
+
+ positionsResult[i] = Cartesian2.fromElements(x, y, positionsResult[i]);
+ }
+
+ positionsResult.length = positions.length;
+
+ return positionsResult;
+ };
+
+ return PolygonGeometryLibrary;
+});
diff --git a/Source/Core/CoplanarPolygonOutlineGeometry.js b/Source/Core/CoplanarPolygonOutlineGeometry.js
new file mode 100644
index 000000000000..1ea4495b59aa
--- /dev/null
+++ b/Source/Core/CoplanarPolygonOutlineGeometry.js
@@ -0,0 +1,200 @@
+/*global define*/
+define([
+ './arrayRemoveDuplicates',
+ './BoundingSphere',
+ './Cartesian3',
+ './Check',
+ './ComponentDatatype',
+ './CoplanarPolygonGeometryLibrary',
+ './defaultValue',
+ './defined',
+ './Geometry',
+ './GeometryAttribute',
+ './GeometryAttributes',
+ './IndexDatatype',
+ './PolygonPipeline',
+ './PrimitiveType',
+ './WindingOrder'
+], function(
+ arrayRemoveDuplicates,
+ BoundingSphere,
+ Cartesian3,
+ Check,
+ ComponentDatatype,
+ CoplanarPolygonGeometryLibrary,
+ defaultValue,
+ defined,
+ Geometry,
+ GeometryAttribute,
+ GeometryAttributes,
+ IndexDatatype,
+ PolygonPipeline,
+ PrimitiveType,
+ WindingOrder) {
+ 'use strict';
+
+ var scratchPositions2D = [];
+
+ /**
+ * A description of the outline of a polygon composed of arbitrary coplanar positions.
+ *
+ * @alias CoplanarPolygonOutlineGeometry
+ * @constructor
+ *
+ * @param {Object} options Object with the following properties:
+ * @param {Cartesian3[]} options.positions The positions of the polygon
+ *
+ * @see CoplanarPolygonOutlineGeometry.createGeometry
+ *
+ * @example
+ * var polygonOutline = new Cesium.CoplanarPolygonOutlineGeometry({
+ * positions : Cesium.Cartesian3.fromDegreesArrayHeights([
+ * -90.0, 30.0, 0.0,
+ * -90.0, 30.0, 1000.0,
+ * -80.0, 30.0, 1000.0,
+ * -80.0, 30.0, 0.0
+ * ])
+ * });
+ * var geometry = Cesium.CoplanarPolygonOutlineGeometry.createGeometry(polygonOutline);
+ */
+ function CoplanarPolygonOutlineGeometry(options) {
+ options = defaultValue(options, defaultValue.EMPTY_OBJECT);
+ var positions = options.positions;
+ //>>includeStart('debug', pragmas.debug);
+ Check.defined('options.positions', positions);
+ //>>includeEnd('debug');
+
+ this._positions = positions;
+ this._workerName = 'createCoplanarPolygonOutlineGeometry';
+
+ /**
+ * The number of elements used to pack the object into an array.
+ * @type {Number}
+ */
+ this.packedLength = 1 + positions.length * Cartesian3.packedLength;
+ }
+
+ /**
+ * Stores the provided instance into the provided array.
+ *
+ * @param {CoplanarPolygonOutlineGeometry} value The value to pack.
+ * @param {Number[]} array The array to pack into.
+ * @param {Number} [startingIndex=0] The index into the array at which to start packing the elements.
+ *
+ * @returns {Number[]} The array that was packed into
+ */
+ CoplanarPolygonOutlineGeometry.pack = function(value, array, startingIndex) {
+ //>>includeStart('debug', pragmas.debug);
+ Check.typeOf.object('value', value);
+ Check.defined('array', array);
+ //>>includeEnd('debug');
+
+ startingIndex = defaultValue(startingIndex, 0);
+
+ var positions = value._positions;
+ var length = positions.length;
+ array[startingIndex++] = length;
+
+ for (var i = 0; i < length; ++i, startingIndex += Cartesian3.packedLength) {
+ Cartesian3.pack(positions[i], array, startingIndex);
+ }
+
+ return array;
+ };
+
+ var scratchOptions = {
+ positions : undefined
+ };
+ /**
+ * Retrieves an instance from a packed array.
+ *
+ * @param {Number[]} array The packed array.
+ * @param {Number} [startingIndex=0] The starting index of the element to be unpacked.
+ * @param {CoplanarPolygonOutlineGeometry} [result] The object into which to store the result.
+ * @returns {CoplanarPolygonOutlineGeometry} The modified result parameter or a new CoplanarPolygonOutlineGeometry instance if one was not provided.
+ */
+ CoplanarPolygonOutlineGeometry.unpack = function(array, startingIndex, result) {
+ //>>includeStart('debug', pragmas.debug);
+ Check.defined('array', array);
+ //>>includeEnd('debug');
+
+ startingIndex = defaultValue(startingIndex, 0);
+
+ var i;
+
+ var length = array[startingIndex++];
+ var positions = new Array(length);
+
+ for (i = 0; i < length; ++i, startingIndex += Cartesian3.packedLength) {
+ positions[i] = Cartesian3.unpack(array, startingIndex);
+ }
+
+ if (!defined(result)) {
+ scratchOptions.positions = positions;
+ return new CoplanarPolygonOutlineGeometry(scratchOptions);
+ }
+
+ result._positions = positions;
+
+ return result;
+ };
+
+ /**
+ * Computes the geometric representation of an arbitrary coplanar polygon, including its vertices, indices, and a bounding sphere.
+ *
+ * @param {CoplanarPolygonOutlineGeometry} polygonGeometry A description of the polygon.
+ * @returns {Geometry|undefined} The computed vertices and indices.
+ */
+ CoplanarPolygonOutlineGeometry.createGeometry = function(polygonGeometry) {
+ var positions = polygonGeometry._positions;
+ positions = arrayRemoveDuplicates(positions, Cartesian3.equalsEpsilon, true);
+ if (positions.length < 3) {
+ return;
+ }
+ var boundingSphere = BoundingSphere.fromPoints(positions);
+
+ var positions2D = CoplanarPolygonGeometryLibrary.projectTo2D(positions, scratchPositions2D);
+ if (!defined(positions2D)) {
+ return;
+ }
+
+ if (PolygonPipeline.computeWindingOrder2D(positions2D) === WindingOrder.CLOCKWISE) {
+ positions2D.reverse();
+ positions = positions.slice().reverse();
+ }
+
+ var length = positions.length;
+ var flatPositions = new Float64Array(length * 3);
+ var indices = IndexDatatype.createTypedArray(length, length * 2);
+
+ var positionIndex = 0;
+ var index = 0;
+
+ for (var i = 0; i < length; i++) {
+ var position = positions[i];
+ flatPositions[positionIndex++] = position.x;
+ flatPositions[positionIndex++] = position.y;
+ flatPositions[positionIndex++] = position.z;
+
+ indices[index++] = i;
+ indices[index++] = (i + 1) % length;
+ }
+
+ var attributes = new GeometryAttributes({
+ position: new GeometryAttribute({
+ componentDatatype : ComponentDatatype.DOUBLE,
+ componentsPerAttribute : 3,
+ values : flatPositions
+ })
+ });
+
+ return new Geometry({
+ attributes : attributes,
+ indices : indices,
+ primitiveType : PrimitiveType.LINES,
+ boundingSphere : boundingSphere
+ });
+ };
+
+ return CoplanarPolygonOutlineGeometry;
+});
diff --git a/Source/Workers/createCoplanarPolygonGeometry.js b/Source/Workers/createCoplanarPolygonGeometry.js
new file mode 100644
index 000000000000..1e76b76c23c8
--- /dev/null
+++ b/Source/Workers/createCoplanarPolygonGeometry.js
@@ -0,0 +1,17 @@
+define([
+ '../Core/defined',
+ '../Core/CoplanarPolygonGeometry'
+], function(
+ defined,
+ CoplanarPolygonGeometry) {
+ 'use strict';
+
+ function createCoplanarPolygonGeometry(polygonGeometry, offset) {
+ if (defined(offset)) {
+ polygonGeometry = CoplanarPolygonGeometry.unpack(polygonGeometry, offset);
+ }
+ return CoplanarPolygonGeometry.createGeometry(polygonGeometry);
+ }
+
+ return createCoplanarPolygonGeometry;
+});
diff --git a/Source/Workers/createCoplanarPolygonOutlineGeometry.js b/Source/Workers/createCoplanarPolygonOutlineGeometry.js
new file mode 100644
index 000000000000..d989368e9e87
--- /dev/null
+++ b/Source/Workers/createCoplanarPolygonOutlineGeometry.js
@@ -0,0 +1,17 @@
+define([
+ '../Core/defined',
+ '../Core/CoplanarPolygonOutlineGeometry'
+], function(
+ defined,
+ CoplanarPolygonOutlineGeometry) {
+ 'use strict';
+
+ function createCoplanarPolygonOutlineGeometry(polygonGeometry, offset) {
+ if (defined(offset)) {
+ polygonGeometry = CoplanarPolygonOutlineGeometry.unpack(polygonGeometry, offset);
+ }
+ return CoplanarPolygonOutlineGeometry.createGeometry(polygonGeometry);
+ }
+
+ return createCoplanarPolygonOutlineGeometry;
+});
diff --git a/Specs/Core/CoplanarPolygonGeometrySpec.js b/Specs/Core/CoplanarPolygonGeometrySpec.js
new file mode 100644
index 000000000000..6e69043fa18e
--- /dev/null
+++ b/Specs/Core/CoplanarPolygonGeometrySpec.js
@@ -0,0 +1,90 @@
+defineSuite([
+ 'Core/CoplanarPolygonGeometry',
+ 'Core/Cartesian3',
+ 'Core/Ellipsoid',
+ 'Core/Math',
+ 'Core/VertexFormat',
+ 'Specs/createPackableSpecs'
+], function(
+ CoplanarPolygonGeometry,
+ Cartesian3,
+ Ellipsoid,
+ CesiumMath,
+ VertexFormat,
+ createPackableSpecs) {
+ 'use strict';
+
+ it('throws with no positions', function() {
+ expect(function() {
+ return new CoplanarPolygonGeometry();
+ }).toThrowDeveloperError();
+ });
+
+ it('returns undefined with less than 3 unique positions', function() {
+ var geometry = CoplanarPolygonGeometry.createGeometry(new CoplanarPolygonGeometry({
+ positions : Cartesian3.fromDegreesArrayHeights([
+ 49.0, 18.0, 1000.0,
+ 49.0, 18.0, 5000.0,
+ 49.0, 18.0, 5000.0,
+ 49.0, 18.0, 1000.0
+ ])
+ }));
+ expect(geometry).toBeUndefined();
+ });
+
+ it('returns undefined when positions are linear', function() {
+ var geometry = CoplanarPolygonGeometry.createGeometry(new CoplanarPolygonGeometry({
+ positions : Cartesian3.fromDegreesArrayHeights([
+ 0.0, 0.0, 1.0,
+ 0.0, 0.0, 2.0,
+ 0.0, 0.0, 3.0
+ ])
+ }));
+ expect(geometry).toBeUndefined();
+ });
+
+ it('computes positions', function() {
+ var p = CoplanarPolygonGeometry.createGeometry(new CoplanarPolygonGeometry({
+ vertexFormat : VertexFormat.POSITION_ONLY,
+ positions : Cartesian3.fromDegreesArrayHeights([
+ -1.0, -1.0, 0.0,
+ -1.0, 0.0, 1.0,
+ -1.0, 1.0, 1.0,
+ -1.0, 2.0, 0.0
+ ])
+ }));
+
+ expect(p.attributes.position.values.length).toEqual(4 * 3);
+ expect(p.indices.length).toEqual(2 * 3);
+ });
+
+ it('computes all attributes', function() {
+ var p = CoplanarPolygonGeometry.createGeometry(new CoplanarPolygonGeometry({
+ vertexFormat : VertexFormat.ALL,
+ positions : Cartesian3.fromDegreesArrayHeights([
+ -1.0, -1.0, 0.0,
+ -1.0, 0.0, 1.0,
+ -1.0, 1.0, 1.0,
+ -1.0, 2.0, 0.0
+ ])
+ }));
+
+ var numVertices = 4;
+ var numTriangles = 2;
+ expect(p.attributes.position.values.length).toEqual(numVertices * 3);
+ expect(p.attributes.st.values.length).toEqual(numVertices * 2);
+ expect(p.attributes.normal.values.length).toEqual(numVertices * 3);
+ expect(p.attributes.tangent.values.length).toEqual(numVertices * 3);
+ expect(p.attributes.bitangent.values.length).toEqual(numVertices * 3);
+ expect(p.indices.length).toEqual(numTriangles * 3);
+ });
+
+ var positions = [new Cartesian3(-1.0, 0.0, 0.0), new Cartesian3(0.0, 0.0, 1.0), new Cartesian3(-1.0, 0.0, 0.0)];
+ var polygon = new CoplanarPolygonGeometry({
+ positions : positions,
+ vertexFormat : VertexFormat.POSITION_ONLY
+ });
+ var packedInstance = [3.0, -1.0, 0.0, 0.0, 0.0, 0.0, 1.0, -1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0];
+ createPackableSpecs(CoplanarPolygonGeometry, polygon, packedInstance);
+});
+
diff --git a/Specs/Core/CoplanarPolygonOutlineGeometrySpec.js b/Specs/Core/CoplanarPolygonOutlineGeometrySpec.js
new file mode 100644
index 000000000000..576be81b9fd0
--- /dev/null
+++ b/Specs/Core/CoplanarPolygonOutlineGeometrySpec.js
@@ -0,0 +1,64 @@
+defineSuite([
+ 'Core/CoplanarPolygonOutlineGeometry',
+ 'Core/Cartesian3',
+ 'Core/Ellipsoid',
+ 'Core/Math',
+ 'Specs/createPackableSpecs'
+], function(
+ CoplanarPolygonOutlineGeometry,
+ Cartesian3,
+ Ellipsoid,
+ CesiumMath,
+ createPackableSpecs) {
+ 'use strict';
+
+ it('throws with no positions', function() {
+ expect(function() {
+ return new CoplanarPolygonOutlineGeometry();
+ }).toThrowDeveloperError();
+ });
+
+ it('returns undefined with less than 3 unique positions', function() {
+ var geometry = CoplanarPolygonOutlineGeometry.createGeometry(new CoplanarPolygonOutlineGeometry({
+ positions : Cartesian3.fromDegreesArrayHeights([
+ 49.0, 18.0, 1000.0,
+ 49.0, 18.0, 5000.0,
+ 49.0, 18.0, 5000.0,
+ 49.0, 18.0, 1000.0
+ ])
+ }));
+ expect(geometry).toBeUndefined();
+ });
+
+ it('returns undefined when positions are linear', function() {
+ var geometry = CoplanarPolygonOutlineGeometry.createGeometry(new CoplanarPolygonOutlineGeometry({
+ positions : Cartesian3.fromDegreesArrayHeights([
+ 0.0, 0.0, 1.0,
+ 0.0, 0.0, 2.0,
+ 0.0, 0.0, 3.0
+ ])
+ }));
+ expect(geometry).toBeUndefined();
+ });
+
+ it('creates positions', function() {
+ var geometry = CoplanarPolygonOutlineGeometry.createGeometry(new CoplanarPolygonOutlineGeometry({
+ positions : Cartesian3.fromDegreesArrayHeights([
+ -1.0, -1.0, 0.0,
+ -1.0, 0.0, 1.0,
+ -1.0, 1.0, 1.0,
+ -1.0, 2.0, 0.0
+ ])
+ }));
+
+ expect(geometry.attributes.position.values.length).toEqual(4 * 3);
+ expect(geometry.indices.length).toEqual(4 * 2);
+ });
+
+ var positions = [new Cartesian3(-1.0, 0.0, 0.0), new Cartesian3(0.0, 0.0, 1.0), new Cartesian3(-1.0, 0.0, 0.0)];
+ var polygon = new CoplanarPolygonOutlineGeometry({
+ positions : positions
+ });
+ var packedInstance = [3.0, -1.0, 0.0, 0.0, 0.0, 0.0, 1.0, -1.0, 0.0, 0.0];
+ createPackableSpecs(CoplanarPolygonOutlineGeometry, polygon, packedInstance);
+});
diff --git a/Specs/Scene/GeometryRenderingSpec.js b/Specs/Scene/GeometryRenderingSpec.js
index a066fdc64b1f..20dfedb08ea5 100644
--- a/Specs/Scene/GeometryRenderingSpec.js
+++ b/Specs/Scene/GeometryRenderingSpec.js
@@ -6,6 +6,7 @@ defineSuite([
'Core/CircleGeometry',
'Core/Color',
'Core/ColorGeometryInstanceAttribute',
+ 'Core/CoplanarPolygonGeometry',
'Core/ComponentDatatype',
'Core/CornerType',
'Core/CorridorGeometry',
@@ -46,6 +47,7 @@ defineSuite([
CircleGeometry,
Color,
ColorGeometryInstanceAttribute,
+ CoplanarPolygonGeometry,
ComponentDatatype,
CornerType,
CorridorGeometry,
@@ -332,6 +334,49 @@ defineSuite([
});
}, 'WebGL');
+ describe('CoplanarPolygonGeometry', function() {
+ var instance;
+ beforeAll(function() {
+ instance = new GeometryInstance({
+ geometry : new CoplanarPolygonGeometry({
+ positions : Cartesian3.fromDegreesArrayHeights([
+ 71.0, -10.0, 0.0,
+ 70.0, 0.0, 20000.0,
+ 69.0, 0.0, 20000.0,
+ 68.0, -10.0, 0.0
+ ]),
+ vertexFormat : PerInstanceColorAppearance.FLAT_VERTEX_FORMAT
+ }),
+ id: 'coplanar polygon',
+ attributes : {
+ color : new ColorGeometryInstanceAttribute(Math.random(), Math.random(), Math.random(), 0.5)
+ }
+ });
+ geometry = CoplanarPolygonGeometry.createGeometry(instance.geometry);
+ geometry.boundingSphereWC = BoundingSphere.transform(geometry.boundingSphere, instance.modelMatrix);
+ });
+
+ it('3D', function() {
+ render3D(instance);
+ });
+
+ it('Columbus view', function() {
+ renderCV(instance);
+ });
+
+ it('2D', function() {
+ render2D(instance);
+ });
+
+ it('pick', function() {
+ pickGeometry(instance);
+ });
+
+ it('async', function() {
+ return renderAsync(instance);
+ });
+ });
+
describe('CylinderGeometry', function() {
var instance;
beforeAll(function() {