diff --git a/AGICHANGES.md b/AGICHANGES.md index 156bbfc6bb1..099b3e86a2d 100644 --- a/AGICHANGES.md +++ b/AGICHANGES.md @@ -6,6 +6,7 @@ Beta Releases ### b28 - 2014-05-01 +* Added support for non-convex `CustomSensorVolume` visualization. * Added `FanGeometry`, `FanOutlineGeometry`, and `DynamicFan`. A Fan is defined by an origin and list of directions. This is useful for drawing static projected shapes such as azimuth elevation masks and body masks. ### b27 - 2014-04-01 diff --git a/Apps/Sandcastle/gallery/Sensors.html b/Apps/Sandcastle/gallery/Sensors.html index 22ca86cce72..1c5ddc2aa13 100644 --- a/Apps/Sandcastle/gallery/Sensors.html +++ b/Apps/Sandcastle/gallery/Sensors.html @@ -69,7 +69,7 @@ require([ 'Cesium', 'dijit/form/DropDownButton', 'dijit/DropDownMenu', 'dijit/MenuItem', 'dijit/form/HorizontalSlider', 'dijit/TitlePane' ], function( - Cesium, DropDownButton, DropDownMenu, MenuItem, HorizontalSlider, TitlePane) + Cesium, DropDownButton, DropDownMenu, MenuItem, HorizontalSlider, TitlePane) { "use strict"; @@ -137,6 +137,52 @@ return customSensor; } + function addFootprint(ellipsoid) { + var foot = new Cesium.CustomSensorVolume(); + + var directions = []; + directions.push({ clock : Cesium.Math.toRadians(0.0), cone : Cesium.Math.toRadians(14.03624347)}); + directions.push({ clock : Cesium.Math.toRadians(74.3), cone : Cesium.Math.toRadians(32.98357092)}); + directions.push({ clock : Cesium.Math.toRadians(80.54), cone : Cesium.Math.toRadians(37.23483398)}); + directions.push({ clock : Cesium.Math.toRadians(90.0), cone : Cesium.Math.toRadians(37.77568431)}); + directions.push({ clock : Cesium.Math.toRadians(101.31), cone : Cesium.Math.toRadians(37.41598862)}); + directions.push({ clock : Cesium.Math.toRadians(116.56), cone : Cesium.Math.toRadians(29.20519037)}); + directions.push({ clock : Cesium.Math.toRadians(180.0), cone : Cesium.Math.toRadians(14.03624347)}); + directions.push({ clock : Cesium.Math.toRadians(247.2), cone : Cesium.Math.toRadians(34.11764003)}); + directions.push({ clock : Cesium.Math.toRadians(251.56), cone : Cesium.Math.toRadians(44.67589157)}); + directions.push({ clock : Cesium.Math.toRadians(253.96), cone : Cesium.Math.toRadians(46.12330271)}); + directions.push({ clock : Cesium.Math.toRadians(259.63), cone : Cesium.Math.toRadians(46.19202903)}); + directions.push({ clock : Cesium.Math.toRadians(262.87), cone : Cesium.Math.toRadians(45.21405547)}); + directions.push({ clock : Cesium.Math.toRadians(262.4), cone : Cesium.Math.toRadians(37.09839406)}); + directions.push({ clock : Cesium.Math.toRadians(266.47), cone : Cesium.Math.toRadians(45.42651157)}); + directions.push({ clock : Cesium.Math.toRadians(270.0), cone : Cesium.Math.toRadians(45.42651157)}); + directions.push({ clock : Cesium.Math.toRadians(270.97), cone : Cesium.Math.toRadians(36.40877457)}); + directions.push({ clock : Cesium.Math.toRadians(272.79), cone : Cesium.Math.toRadians(45.70731937)}); + directions.push({ clock : Cesium.Math.toRadians(278.33), cone : Cesium.Math.toRadians(45.98533395)}); + directions.push({ clock : Cesium.Math.toRadians(281.89), cone : Cesium.Math.toRadians(36.0358894)}); + directions.push({ clock : Cesium.Math.toRadians(282.99), cone : Cesium.Math.toRadians(45.0)}); + directions.push({ clock : Cesium.Math.toRadians(286.99), cone : Cesium.Math.toRadians(43.26652934)}); + directions.push({ clock : Cesium.Math.toRadians(291.8), cone : Cesium.Math.toRadians(33.97011942)}); + directions.push({ clock : Cesium.Math.toRadians(293.3), cone : Cesium.Math.toRadians(41.50882857)}); + directions.push({ clock : Cesium.Math.toRadians(300.96), cone : Cesium.Math.toRadians(36.0826946)}); + directions.push({ clock : Cesium.Math.toRadians(319.18), cone : Cesium.Math.toRadians(19.9888568)}); + + foot.modelMatrix = getModelMatrix(ellipsoid); + foot.radius = 10000000.0; + foot.setDirections(directions); + foot.portionToDisplay = portion; + + foot.material = Cesium.Material.fromType('Color'); + foot.material.uniforms.color = new Cesium.Color(0.0, 1.0, 1.0, 0.5); + + foot.ellipsoidHorizonSurfaceMaterial = Cesium.Material.fromType('Color'); + foot.ellipsoidHorizonSurfaceMaterial.uniforms.color = new Cesium.Color(1.0, 0.0, 1.0, 0.5); + + foot.domeSurfaceMaterial = Cesium.Material.fromType('Color'); + foot.domeSurfaceMaterial.uniforms.color = new Cesium.Color(1.0, 1.0, 0.0, 0.5); + return foot; + } + function createUserInterface(viewer) { var tp = new TitlePane({ title: 'Manipulate Sensor', @@ -162,6 +208,9 @@ case 'Custom': sensor = addCustomSensor(ellipsoid); break; + case 'Foot': + sensor = addFootprint(ellipsoid); + break; case 'Rectangular': sensor = addRectangularSensor(ellipsoid); } @@ -192,6 +241,14 @@ } })); + sensorMenu.addChild(new MenuItem({ + label: 'Foot', + onClick: function() { + selection = 'Foot'; + updateSensor(); + } + })); + new DropDownButton({ label : 'Select a sensor', dropDown: sensorMenu diff --git a/Source/Core/SphericalPolygon.js b/Source/Core/SphericalPolygon.js new file mode 100644 index 00000000000..f15ae2875f1 --- /dev/null +++ b/Source/Core/SphericalPolygon.js @@ -0,0 +1,405 @@ +/*global define*/ +define([ + './freezeObject', + './defaultValue', + './defined', + './defineProperties', + './DeveloperError', + './Math', + './Cartesian3' + ], function( + freezeObject, + defaultValue, + defined, + defineProperties, + DeveloperError, + CesiumMath, + Cartesian3) { + "use strict"; + + var stride = 10; + var directionsOffset = 0; + var normalsOffset = 3; + + /** + * A simple polygon on the unit sphere {S2}. + * + * @private + */ + var SphericalPolygon = function(vertices) { + this._isConvex = undefined; + this._vertices = undefined; + + this._referenceAxis = undefined; + this._referenceDistance = undefined; + + this._directionsNormalsAndBisectorsWithMagnitudeSquared = undefined; + + if (defined(vertices)) { + this.vertices = vertices; + } + + this._convexHull = []; + }; + + var chord21 = new Cartesian3(); + var chord23 = new Cartesian3(); + var average = new Cartesian3(); + + function computeCircumscribingConeFromThreePoints(p1, p2, p3, axis) { + chord21 = Cartesian3.subtract(p1, p2, chord21); + chord23 = Cartesian3.subtract(p3, p2, chord23); + axis = Cartesian3.normalize(Cartesian3.cross(chord23, chord21, axis), axis); + average = Cartesian3.divideByScalar(Cartesian3.add(Cartesian3.add(p1, p2, average), p3, average), 3.0, average); + var distance = Cartesian3.dot(average, axis); + return distance; + } + + function computeCircumscribingConeFromTwoPoints(p1, p2, axis) { + axis = Cartesian3.divideByScalar(Cartesian3.add(p1, p2, axis), 2.0, axis); + var distance = Cartesian3.magnitude(axis); + axis = Cartesian3.normalize(axis, axis); + return distance; + } + + var axis12 = new Cartesian3(); + var axis23 = new Cartesian3(); + var axis31 = new Cartesian3(); + + function computeMinimumBoundingConeFromThreePoints(p1, p2, p3, axis) { + var distance12 = computeCircumscribingConeFromTwoPoints(p1, p2, axis12); + var distance23 = computeCircumscribingConeFromTwoPoints(p2, p3, axis23); + var distance31 = computeCircumscribingConeFromTwoPoints(p3, p1, axis31); + + if (distance12 <= distance23) { + if (distance12 <= distance31) { // cone12 is largest. + if (Cartesian3.dot(p3, axis12) >= distance12) { // p3 is inside cone12 + axis = Cartesian3.clone(axis12, axis); + return distance12; + } else { + return computeCircumscribingConeFromThreePoints(p1, p2, p3, axis); + } + } else { // cone 31 is largest. + if (Cartesian3.dot(p2, axis31) >= distance31) { // p2 is inside cone31 + axis = Cartesian3.clone(axis31, axis); + return distance31; + } else { + return computeCircumscribingConeFromThreePoints(p1, p2, p3, axis); + } + } + } else if (distance23 <= distance31) { // cone23 is largest. + if (Cartesian3.dot(p1, axis23) >= distance23) { // p1 is inside cone23 + axis = Cartesian3.clone(axis23, axis); + return distance23; + } else { + return computeCircumscribingConeFromThreePoints(p1, p2, p3, axis); + } + } else { // cone31 is largest. + if (Cartesian3.dot(p2, axis31) >= distance31) { // p2 is inside cone31 + axis = Cartesian3.clone(axis31, axis); + return distance31; + } else { + return computeCircumscribingConeFromThreePoints(p1, p2, p3, axis); + } + } + } + + var lastDirection = new Cartesian3(); + var lastNormal = new Cartesian3(); + var direction = new Cartesian3(); + var nextDirection = new Cartesian3(); + var nextNormal = new Cartesian3(); + var crossProduct = new Cartesian3(); + + SphericalPolygon.findConvexHull = function(floats, stride, directionsOffset, sign, initialIndex, finalIndex, hull) { + var numberOfVertices = floats.length / stride; + + hull.length = 0; + if (initialIndex < finalIndex) { + for (var i = initialIndex; i <= finalIndex; ++i) { + hull.push(i); + } + } else { + for (var j = initialIndex; j < numberOfVertices; ++j) { + hull.push(j); + } + for (var jj = 0; jj <= finalIndex; ++jj) { + hull.push(jj); + } + } + + var originalLength = hull.length; + + var initialLength; + do { + initialLength = hull.length; + var previousIndex = initialLength - 1; + var index = 0; + var nextIndex = 1; + do { + var offset = hull[previousIndex % hull.length] * stride + directionsOffset; + lastDirection = Cartesian3.fromArray(floats, offset, lastDirection); + offset = hull[index % hull.length] * stride + directionsOffset; + direction = Cartesian3.fromArray(floats, offset, direction); + offset = hull[nextIndex % hull.length] * stride + directionsOffset; + nextDirection = Cartesian3.fromArray(floats, offset, nextDirection); + + lastNormal = Cartesian3.cross(direction, lastDirection, lastNormal); + nextNormal = Cartesian3.cross(nextDirection, direction, nextNormal); + + if (sign * Cartesian3.dot(Cartesian3.cross(lastNormal, nextNormal, crossProduct), direction) >= 0.0) { + previousIndex = index; + index = index + 1; + nextIndex = index + 1; + } else { + hull.splice(index, 1); + } + } while (index !== hull.length); + } while (hull.length !== initialLength); + + var hole; + if (hull.length !== originalLength) { + hull.holes = []; + for (var k = 0; k < hull.length - 1; ++k) { + var current = hull[k]; + var next = hull[k + 1]; + var difference = (current < next) ? next - current : next + numberOfVertices - current; + if (difference > 1) { + hole = []; + SphericalPolygon.findConvexHull(floats, stride, directionsOffset, sign * -1.0, current, next, hole); + hull.holes.push(hole); + } + } + + var firstIndex = hull[0]; + var lastIndex = hull[hull.length - 1]; + if (lastIndex === finalIndex && firstIndex !== initialIndex) { + hole = []; + SphericalPolygon.findConvexHull(floats, stride, directionsOffset, sign * -1.0, finalIndex, firstIndex, hole); + hull.holes.push(hole); + } else if (lastIndex !== finalIndex && firstIndex === initialIndex) { + hole = []; + SphericalPolygon.findConvexHull(floats, stride, directionsOffset, sign * -1.0, lastIndex, initialIndex, hole); + hull.holes.push(hole); + } else if (lastIndex !== finalIndex && firstIndex !== initialIndex) { + hole = []; + SphericalPolygon.findConvexHull(floats, stride, directionsOffset, sign * -1.0, lastIndex, firstIndex, hole); + hull.holes.push(hole); + } + } + }; + + var first = new Cartesian3(); + var second = new Cartesian3(); + var third = new Cartesian3(); + var other = new Cartesian3(); + var tempAxis = new Cartesian3(); + + SphericalPolygon.prototype.computeBoundingCone = function(convexHull) { + var length = convexHull.length; + + for (var i = 0; i < length; ++i) { + var firstIndex = convexHull[i]; + first = Cartesian3.fromArray(this._directionsNormalsAndBisectorsWithMagnitudeSquared, firstIndex * stride + directionsOffset, first); + for (var j = i + 1; j < length; ++j) { + var secondIndex = convexHull[j]; + second = Cartesian3.fromArray(this._directionsNormalsAndBisectorsWithMagnitudeSquared, secondIndex * stride + directionsOffset, second); + for (var k = j + 1; k < length; ++k) { + var thirdIndex = convexHull[k]; + third = Cartesian3.fromArray(this._directionsNormalsAndBisectorsWithMagnitudeSquared, thirdIndex * stride + directionsOffset, third); + var tempDistance = computeMinimumBoundingConeFromThreePoints(first, second, third, tempAxis); + for (var l = 0; l < length; ++l) { + if (l !== i && l !== j && l !== k) { + var otherIndex = convexHull[l]; + other = Cartesian3.fromArray(this._directionsNormalsAndBisectorsWithMagnitudeSquared, otherIndex * stride + directionsOffset, other); + if (Cartesian3.dot(other, tempAxis) < tempDistance) { + break; + } + } + } + if (l === length) { + this._referenceAxis = Cartesian3.clone(tempAxis, this._referenceAxis); + this._referenceDistance = tempDistance; + } + } + } + } + }; + + SphericalPolygon.prototype.computeBoundingCone2 = function() { + var convexHull = this.convexHull; + var length = convexHull.length; + + // Find the two vertices with the greatest half-angle as the initial bounding cone. + var index1 = -1; + var index2 = -1; + var distance = 1.0; + for (var i = 0; i < length; ++i) { + var firstIndex = convexHull[i]; + first = Cartesian3.fromArray(this._directionsNormalsAndBisectorsWithMagnitudeSquared, firstIndex * stride + directionsOffset, first); + for (var j = i + 1; j < length; ++j) { + var secondIndex = convexHull[j]; + second = Cartesian3.fromArray(this._directionsNormalsAndBisectorsWithMagnitudeSquared, secondIndex * stride + directionsOffset, second); + var tempDistance = computeCircumscribingConeFromTwoPoints(first, second, tempAxis); + if (tempDistance < distance) { + index1 = i; + index2 = j; + this._referenceAxis = Cartesian3.clone(tempAxis, this._referenceAxis); + this._referenceDistance = tempDistance; + distance = tempDistance; + } + } + } + + // Form the set of vertices from the two vertices that define the initial bounding cone and the vertices which lay ouside. + var hull = []; + for (var k = 0; k < length; ++k) { + if (k === index1 || k === index2) { + hull.push(convexHull[k]); + } else { + var index = convexHull[k]; + direction = Cartesian3.fromArray(this._directionsNormalsAndBisectorsWithMagnitudeSquared, index * stride + directionsOffset, direction); + var dotProduct = Cartesian3.dot(direction, this._referenceAxis); + if (dotProduct < this._referenceDistance) { + hull.push(index); + } + } + } + + // If there are vertices outside the bounding cone, find the minimum bounding cone. + if (hull.length > 2) { + this.computeBoundingCone(hull); + } + }; + + var bisector = new Cartesian3(); + var normal = new Cartesian3(); + var initialNormal = new Cartesian3(); + var finalDirection = new Cartesian3(); + + defineProperties(SphericalPolygon.prototype, { + /** + * Gets a value indicating whether the spherical polygon is convex. + * + * @memberof SphericalPolygon.prototype + * @type {Boolean} + */ + isConvex : { + get: function() { + return this._isConvex; + } + }, + + /** + * Gets and sets the vertices which define the spherical polygon. The list of vertices should conform to the following restrictions: + * + * + * @memberof SphericalPolygon.prototype + * @type {Array} + */ + vertices : { + get: function() { + return this._vertices; + }, + set : function(vertices) { + var length = vertices.length; + var size = length * 3; + + this._directionsNormalsAndBisectorsWithMagnitudeSquared = new Float32Array(3 * size + length); + + var convexVertices = []; + + this._isConvex = true; + finalDirection = Cartesian3.fromSpherical(vertices[length - 1], finalDirection); + lastDirection = Cartesian3.clone(finalDirection, lastDirection); + + for (var index = 0; index < length; ++index) { + direction = Cartesian3.fromSpherical(vertices[index], direction); + bisector = Cartesian3.divideByScalar(Cartesian3.add(lastDirection, direction, bisector), 2.0, bisector); + normal = Cartesian3.normalize(Cartesian3.cross(direction, lastDirection, normal), normal); + + if (index === 0) { + initialNormal = Cartesian3.clone(normal, initialNormal); + } else if (Cartesian3.dot(Cartesian3.cross(lastNormal, normal, crossProduct), lastDirection) < 0.0) { + this._isConvex = false; + } + + var offset = index * 10; + this._directionsNormalsAndBisectorsWithMagnitudeSquared[offset] = direction.x; + this._directionsNormalsAndBisectorsWithMagnitudeSquared[offset + 1] = direction.y; + this._directionsNormalsAndBisectorsWithMagnitudeSquared[offset + 2] = direction.z; + this._directionsNormalsAndBisectorsWithMagnitudeSquared[offset + 3] = normal.x; + this._directionsNormalsAndBisectorsWithMagnitudeSquared[offset + 4] = normal.y; + this._directionsNormalsAndBisectorsWithMagnitudeSquared[offset + 5] = normal.z; + this._directionsNormalsAndBisectorsWithMagnitudeSquared[offset + 6] = bisector.x; + this._directionsNormalsAndBisectorsWithMagnitudeSquared[offset + 7] = bisector.y; + this._directionsNormalsAndBisectorsWithMagnitudeSquared[offset + 8] = bisector.z; + this._directionsNormalsAndBisectorsWithMagnitudeSquared[offset + 9] = Cartesian3.magnitudeSquared(bisector); + + lastDirection = Cartesian3.clone(direction, lastDirection); + lastNormal = Cartesian3.clone(normal, lastNormal); + } + if (Cartesian3.dot(Cartesian3.cross(lastNormal, initialNormal, crossProduct), finalDirection) < 0.0) { + this._isConvex = false; + } + + this._vertices = vertices; + this._convexHull = []; + this._referenceAxis = undefined; + this._referenceDistance = undefined; + } + }, + + /** + * Gets the array of vertex indices which form the convex hull of this sherical polygon. + * + * @memberof SphericalPolygon.prototype + * @type {Array} + */ + convexHull : { + get: function() { + if (defined(this._vertices) && this._convexHull.length === 0) { + SphericalPolygon.findConvexHull(this._directionsNormalsAndBisectorsWithMagnitudeSquared, stride, directionsOffset, 1.0, 0, this._vertices.length - 1, this._convexHull); + } + return this._convexHull; + } + }, + + /** + * Gets the reference axis for the spherical polygon. + * With the SphericalPolygon#referenceDistance, this axis defines the minimum bounding cone. + * + * @memberof SphericalPolygon.prototype + * @type {Cartesian3} + */ + referenceAxis : { + get: function() { + if (!defined(this._referenceAxis) && this.convexHull.length > 0) { + this.computeBoundingCone2(); + } + return this._referenceAxis; + } + }, + + /** + * Gets the reference distance for the spherical polygon. + * With the SphericalPolygon#referenceAxis, this distance defines the minimum bounding cone. + * + * @memberof SphericalPolygon.prototype + * @type {Number} + */ + referenceDistance : { + get: function() { + if (!defined(this._referenceDistance) && this.convexHull.length > 0) { + this.computeBoundingCone2(); + } + return this._referenceDistance; + } + } + }); + + return SphericalPolygon; +}); diff --git a/Source/DynamicScene/DynamicConeVisualizerUsingCustomSensor.js b/Source/DynamicScene/DynamicConeVisualizerUsingCustomSensor.js index 5aa7a66110b..7a600e05258 100644 --- a/Source/DynamicScene/DynamicConeVisualizerUsingCustomSensor.js +++ b/Source/DynamicScene/DynamicConeVisualizerUsingCustomSensor.js @@ -47,16 +47,25 @@ define([ spherical.magnitude = 1.0; } - function computeDirections(minimumClockAngle, maximumClockAngle, innerHalfAngle, outerHalfAngle, result) { + function computeDirections(sphericalPolygon, minimumClockAngle, maximumClockAngle, innerHalfAngle, outerHalfAngle, result) { var n = innerHalfAngle ? 90 : 180; // number of divisions of a full circle. var angleStep = CesiumMath.TWO_PI / n; var angle; var i = 0; + + // Define the bounding cone for the custom sensor to improve performance. + sphericalPolygon._referenceAxis = new Cartesian3(); + sphericalPolygon._referenceAxis = Cartesian3.clone(Cartesian3.UNIT_Z, sphericalPolygon._referenceAxis); + sphericalPolygon._referenceDistance = Math.cos(outerHalfAngle); + if (minimumClockAngle === 0.0 && maximumClockAngle === CesiumMath.TWO_PI) { // No clock angle limits, so this is just a circle. // There might be a hole but we're ignoring it for now. angle = 0.0; + // Define the convex hull for the custom sensor to improve performance. + var convexHull = sphericalPolygon._convexHull; for (i = 0; i < n; ++i) { + convexHull.push(i); assignSpherical(i, result, angle, outerHalfAngle); angle += angleStep; } @@ -331,7 +340,7 @@ define([ innerHalfAngle !== cone.innerHalfAngle || outerHalfAngle !== cone.outerHalfAngle) { - cone.setDirections(computeDirections(minimumClockAngle, maximumClockAngle, innerHalfAngle, outerHalfAngle, cone._directionsScratch)); + cone.setDirections(computeDirections(cone._sphericalPolygon, minimumClockAngle, maximumClockAngle, innerHalfAngle, outerHalfAngle, cone._directionsScratch)); cone.innerHalfAngle = innerHalfAngle; cone.maximumClockAngle = maximumClockAngle; cone.outerHalfAngle = outerHalfAngle; diff --git a/Source/Scene/CustomSensorVolume.js b/Source/Scene/CustomSensorVolume.js index 46ec5cfeada..cf769ae42cc 100644 --- a/Source/Scene/CustomSensorVolume.js +++ b/Source/Scene/CustomSensorVolume.js @@ -16,6 +16,7 @@ define([ '../Core/ComponentDatatype', '../Core/PrimitiveType', '../Core/BoundingSphere', + '../Core/SphericalPolygon', '../Renderer/BufferUsage', '../Renderer/BlendingState', '../Renderer/DrawCommand', @@ -36,6 +37,7 @@ define([ '../Shaders/EllipsoidHorizonFacetFS', '../Shaders/EllipsoidHorizonFacetInsideFS', '../Shaders/EllipsoidHorizonFacetOutsideFS', + './SphericalPolygonShaderSupport', './SceneMode' ], function( defaultValue, @@ -54,6 +56,7 @@ define([ ComponentDatatype, PrimitiveType, BoundingSphere, + SphericalPolygon, BufferUsage, BlendingState, DrawCommand, @@ -74,6 +77,7 @@ define([ EllipsoidHorizonFacetFS, EllipsoidHorizonFacetInsideFS, EllipsoidHorizonFacetOutsideFS, + SphericalPolygonShaderSupport, SceneMode) { "use strict"; @@ -92,6 +96,13 @@ define([ var numberOfFloatsPerHorizonCommand = numberOfVerticesPerHorizonCommand * numberOfFloatsPerVertex; var numberOfFloatsPerDomeCommand = 3 * 4 * 3 * numberOfFloatsPerVertex; // (3 sides per command)(4 triangles/side)(3 vertices/triangle)(2 Cartesians/vertex)(3 floats/Cartesian) + var Crossing = function(r, cosine, sine, kind) { + this.r = Cartesian3.clone(r); + this.cosine = cosine; + this.sine = sine; + this.kind = kind; + }; + /** * DOC_TBA * @@ -109,20 +120,18 @@ define([ this._pickCommand = new DrawCommand(); this._ellipsoidHorizonSurfaceColorCommands = []; this._ellipsoidHorizonSurfaceColorCommandsSource = []; - this._domeColorCommands = []; - this._domeColorCommandsSource = []; + this._domeColorCommand = new DrawCommand(); + this._domeColorCommandSource = undefined; this._ellipsoidHorizonSurfaceCommandsVertices = undefined; this._ellipsoidHorizonSurfaceCommandsVertexArray = undefined; this._ellipsoidHorizonSurfaceCommandsBuffer = undefined; this._ellipsoidHorizonSurfaceColorCommandList = []; - this._domeVertexCount = 0; this._domeCommandsVertices = undefined; this._domeCommandsVertexArray = undefined; this._domeCommandsBuffer = undefined; this._domeColorCommandList = []; - this._completeDomeVertexCount = 0; this._completeDomeBoundingVolumeMC = undefined; this._boundingSphere = new BoundingSphere(); @@ -236,7 +245,6 @@ define([ this._sphericals = undefined; this._sphericalsDirty = false; - this._directionsNormalsAndBisectorsWithMagnitudeSquared = undefined; this.setDirections(options.directions); /** @@ -409,6 +417,10 @@ define([ this._cameraIsInsideEllipsoidHorizonCone = undefined; this._cameraIsInsideDome = undefined; + this._sphericalPolygon = new SphericalPolygon(); + this._sensorGlsl = undefined; + this._sensorUniforms = undefined; + // These elements are for the scaled ellipsoid horizon cone. this._p = undefined; this._q = undefined; @@ -419,7 +431,12 @@ define([ }; /** - * DOC_TBA + * Sets the directions which define the vertices of the custom sensor volume. + * The list of vertices should conform to the following restrictions: + * * * @memberof CustomSensorVolume * @@ -462,14 +479,20 @@ define([ return context.createVertexArray(attributes); } + var axis = new Cartesian3(); + function updateDefinitionDependentData(customSensorVolume, context) { + var sphericalPolygon = customSensorVolume._sphericalPolygon; + sphericalPolygon.vertices = customSensorVolume._sphericals; + + customSensorVolume._sensorGlsl = SphericalPolygonShaderSupport.implicitSurfaceFunction(sphericalPolygon); + customSensorVolume._sensorUniforms = SphericalPolygonShaderSupport.uniforms(sphericalPolygon); + var sphericals = customSensorVolume._sphericals; var length = sphericals.length; var size = length * 3; - customSensorVolume._directionsNormalsAndBisectorsWithMagnitudeSquared = new Float32Array(3 * size + length); customSensorVolume._ellipsoidHorizonSurfaceColorCommands = new Array(length + 1); - customSensorVolume._domeColorCommands = new Array(length + 1); var horizonVertices = new Float32Array(numberOfFloatsForCompleteHorizonPyramidalFrustumCommand + numberOfFloatsPerHorizonCommand * length); // vertices for each side. customSensorVolume._ellipsoidHorizonSurfaceCommandsVertices = horizonVertices; @@ -478,52 +501,20 @@ define([ var horizonVertexArray = makeVertexArray(customSensorVolume, context, horizonBuffer); customSensorVolume._ellipsoidHorizonSurfaceCommandsVertexArray = horizonVertexArray; - var numberOfFloatsForCompleteDomeCommand = length * 4 * 3 * numberOfFloatsPerVertex; // ("length" sides for command)(4 triangles/side)(3 vertices/triangle)(2 Cartesians/vertex)(3 floats/Cartesian) - var domeVertices = new Float32Array(numberOfFloatsForCompleteDomeCommand + numberOfFloatsPerDomeCommand * length); // vertices for each side. - customSensorVolume._domeCommandsVertices = domeVertices; - var domeBuffer = context.createVertexBuffer(domeVertices, customSensorVolume.bufferUsage); - customSensorVolume._domeCommandsBuffer = domeBuffer; - var domeVertexArray = makeVertexArray(customSensorVolume, context, domeBuffer); - customSensorVolume._domeCommandsVertexArray = domeVertexArray; - var directions = []; var primitiveType = customSensorVolume.debugShowProxyGeometry ? PrimitiveType.LINES : customSensorVolume._frontFaceColorCommand.primitiveType; - var last = Cartesian3.fromSpherical(sphericals[length - 1]); for (var index = 0; index < length; ++index) { var direction = Cartesian3.fromSpherical(sphericals[index]); - var bisector = Cartesian3.divideByScalar(Cartesian3.add(last, direction), 2.0); - var normal = Cartesian3.normalize(Cartesian3.cross(direction, last)); - last = direction; - directions.push(Cartesian3.clone(direction)); - var offset = index * 10; - customSensorVolume._directionsNormalsAndBisectorsWithMagnitudeSquared[offset] = direction.x; - customSensorVolume._directionsNormalsAndBisectorsWithMagnitudeSquared[offset + 1] = direction.y; - customSensorVolume._directionsNormalsAndBisectorsWithMagnitudeSquared[offset + 2] = direction.z; - customSensorVolume._directionsNormalsAndBisectorsWithMagnitudeSquared[offset + 3] = normal.x; - customSensorVolume._directionsNormalsAndBisectorsWithMagnitudeSquared[offset + 4] = normal.y; - customSensorVolume._directionsNormalsAndBisectorsWithMagnitudeSquared[offset + 5] = normal.z; - customSensorVolume._directionsNormalsAndBisectorsWithMagnitudeSquared[offset + 6] = bisector.x; - customSensorVolume._directionsNormalsAndBisectorsWithMagnitudeSquared[offset + 7] = bisector.y; - customSensorVolume._directionsNormalsAndBisectorsWithMagnitudeSquared[offset + 8] = bisector.z; - customSensorVolume._directionsNormalsAndBisectorsWithMagnitudeSquared[offset + 9] = Cartesian3.magnitudeSquared(bisector); - var horizonCommand = new DrawCommand(); horizonCommand.primitiveType = primitiveType; horizonCommand.owner = customSensorVolume; horizonCommand.vertexArray = horizonVertexArray; customSensorVolume._ellipsoidHorizonSurfaceColorCommands[index + 1] = horizonCommand; customSensorVolume._ellipsoidHorizonSurfaceColorCommandsSource[index + 1] = RectangularPyramidSensorVolume; - - var domeCommand = new DrawCommand(); - domeCommand.primitiveType = primitiveType; - domeCommand.owner = customSensorVolume; - domeCommand.vertexArray = domeVertexArray; - customSensorVolume._domeColorCommands[index + 1] = domeCommand; - customSensorVolume._domeColorCommandsSource[index + 1] = RectangularPyramidSensorVolume; } var positions = new Float32Array(size); @@ -586,14 +577,18 @@ define([ customSensorVolume._ellipsoidHorizonSurfaceColorCommands[0] = command; customSensorVolume._ellipsoidHorizonSurfaceColorCommandsSource[0] = kDopImplicitSurfaceFunction(numberOfSidesForCompleteHorizonCommand); - command = new DrawCommand(); - command.primitiveType = primitiveType; - command.owner = customSensorVolume; - command.vertexArray = domeVertexArray; - customSensorVolume._domeColorCommands[0] = command; - customSensorVolume._domeVertexCount = 0; - customSensorVolume._completeDomeBoundingVolumeMC = updateDomeCommand(command, 0, customSensorVolume, context, directions, r); - customSensorVolume._completeDomeVertexCount = customSensorVolume._domeVertexCount; + var numberOfFloatsForCompleteDomeCommand = length * 4 * 3 * numberOfFloatsPerVertex; // ("length" sides for command)(4 triangles/side)(3 vertices/triangle)(2 Cartesians/vertex)(3 floats/Cartesian) + var domeVertices = new Float32Array(numberOfFloatsForCompleteDomeCommand); // vertices for each side. + customSensorVolume._domeCommandsVertices = domeVertices; + var domeBuffer = context.createVertexBuffer(domeVertices, customSensorVolume.bufferUsage); + customSensorVolume._domeCommandsBuffer = domeBuffer; + var domeVertexArray = makeVertexArray(customSensorVolume, context, domeBuffer); + customSensorVolume._domeCommandsVertexArray = domeVertexArray; + + customSensorVolume._domeColorCommand.primitiveType = primitiveType; + customSensorVolume._domeColorCommand.owner = customSensorVolume; + customSensorVolume._domeColorCommand.vertexArray = domeVertexArray; + customSensorVolume._completeDomeBoundingVolumeMC = updateDomeCommand(customSensorVolume._domeColorCommand, customSensorVolume, context, r); var vertexBuffer = context.createVertexBuffer(vertices, customSensorVolume.bufferUsage); return makeVertexArray(customSensorVolume, context, vertexBuffer); @@ -1089,18 +1084,23 @@ define([ customSensorVolume._ellipsoidHorizonSurfaceCommandsBuffer.copyFromArrayView(vertices, Float32Array.BYTES_PER_ELEMENT * (numberOfFloatsForCompleteHorizonPyramidalFrustumCommand + numberOfFloatsPerHorizonCommand * index)); } - function computeBoundingPyramidalFrustumVertices(directions, frontCenter, frontSides, backCenter, backSides, vertices) { + var previous = new Cartesian3(); + var current = new Cartesian3(); + var next = new Cartesian3(); + + function computeBoundingPyramidalFrustumVertices(directionsNormalsAndBisectorsWithMagnitudeSquared, directions, frontCenter, frontSides, backCenter, backSides, vertices) { + var stride = 10; var length = directions.length; var normals = []; var k = -1; - var last = directions[length - 1]; + previous = Cartesian3.fromArray(directionsNormalsAndBisectorsWithMagnitudeSquared, directions[length - 1] * stride, previous); var lastFront = frontSides[length - 1]; var lastBack = backSides[length - 1]; for (var index = 0; index < length; ++index) { - var direction = directions[index]; + current = Cartesian3.fromArray(directionsNormalsAndBisectorsWithMagnitudeSquared, directions[index] * stride, current); var front = frontSides[index]; var back = backSides[index]; - n = Cartesian3.normalize(Cartesian3.cross(direction, last, n), n); + n = Cartesian3.normalize(Cartesian3.cross(current, previous, n), n); normals.push(Cartesian3.clone(n)); Cartesian3.pack(front, vertices, ++k * 3); Cartesian3.pack(n, vertices, ++k * 3); @@ -1130,7 +1130,55 @@ define([ Cartesian3.pack(lastBack, vertices, ++k * 3); Cartesian3.pack(n, vertices, ++k * 3); - last = direction; + previous = Cartesian3.clone(current, previous); + lastFront = front; + lastBack = back; + } + + return normals; + } + + function computeBoundingPyramidalFrustumVerticesFromDirections(directions, frontCenter, frontSides, backCenter, backSides, vertices) { + var length = directions.length; + var normals = []; + var k = -1; + var lastIndex = length - 1; + var lastFront = frontSides[length - 1]; + var lastBack = backSides[length - 1]; + for (var index = 0; index < length; ++index) { + var front = frontSides[index]; + var back = backSides[index]; + n = Cartesian3.normalize(Cartesian3.cross(directions[index], directions[lastIndex], n), n); + normals.push(Cartesian3.clone(n)); + Cartesian3.pack(front, vertices, ++k * 3); + Cartesian3.pack(n, vertices, ++k * 3); + Cartesian3.pack(back, vertices, ++k * 3); + Cartesian3.pack(n, vertices, ++k * 3); + Cartesian3.pack(lastBack, vertices, ++k * 3); + Cartesian3.pack(n, vertices, ++k * 3); + Cartesian3.pack(lastBack, vertices, ++k * 3); + Cartesian3.pack(n, vertices, ++k * 3); + Cartesian3.pack(lastFront, vertices, ++k * 3); + Cartesian3.pack(n, vertices, ++k * 3); + Cartesian3.pack(front, vertices, ++k * 3); + Cartesian3.pack(n, vertices, ++k * 3); + + n = Cartesian3.normalize(Cartesian3.cross(Cartesian3.cross(front, frontCenter), Cartesian3.cross(lastFront, frontCenter)), n); + Cartesian3.pack(lastFront, vertices, ++k * 3); + Cartesian3.pack(n, vertices, ++k * 3); + Cartesian3.pack(frontCenter, vertices, ++k * 3); + Cartesian3.pack(n, vertices, ++k * 3); + Cartesian3.pack(front, vertices, ++k * 3); + Cartesian3.pack(n, vertices, ++k * 3); + n = Cartesian3.normalize(Cartesian3.cross(Cartesian3.cross(lastBack, backCenter), Cartesian3.cross(back, backCenter)), n); + Cartesian3.pack(back, vertices, ++k * 3); + Cartesian3.pack(n, vertices, ++k * 3); + Cartesian3.pack(backCenter, vertices, ++k * 3); + Cartesian3.pack(n, vertices, ++k * 3); + Cartesian3.pack(lastBack, vertices, ++k * 3); + Cartesian3.pack(n, vertices, ++k * 3); + + lastIndex = index; lastFront = front; lastBack = back; } @@ -1174,63 +1222,68 @@ define([ var centerFront = new Cartesian3(); var centerBack = new Cartesian3(); - function computeDomeVertices(customSensorVolume, directions, radius, vertices) { + function computeDomeVertices(customSensor, directionsNormalsAndBisectorsWithMagnitudeSquared, directions, radius, vertices) { + var stride = 10; + + d = Cartesian3.clone(customSensor._sphericalPolygon.referenceAxis, d); + var length = directions.length; - d = Cartesian3.clone(Cartesian3.ZERO, d); - for (var ii = 0; ii < length; ++ii) { - d = Cartesian3.add(d, directions[ii], d); - } - d = Cartesian3.magnitudeSquared(d) > CesiumMath.EPSILON15 ? Cartesian3.normalize(d, d) : Cartesian3.clone(Cartesian3.UNIT_Z, d); var minDot = 1.0; + var maxDot = -1.0; + var lastIndex = directions[length - 1]; for (var iii = 0; iii < length; ++iii) { - minDot = Math.min(Cartesian3.dot(directions[iii], d), minDot); + var index = directions[iii]; + previous = Cartesian3.fromArray(directionsNormalsAndBisectorsWithMagnitudeSquared, lastIndex * stride, previous); + current = Cartesian3.fromArray(directionsNormalsAndBisectorsWithMagnitudeSquared, index * stride, current); + minDot = Math.min(Cartesian3.dot(current, d), minDot); + bisector = Cartesian3.normalize(Cartesian3.add(previous, current, bisector), bisector); + maxDot = Math.max(Cartesian3.dot(bisector, d), maxDot); + lastIndex = index; } var fronts = []; var backs = []; - for ( var i = length - 2, j = length - 1, k = 0; k < length; i = j++, j = k++) { - // PERFORMANCE_IDEA: We can avoid redundant operations for adjacent edges. - var previous = directions[i]; - var temp = directions[j]; - var next = directions[k]; + for ( var i = 0; i < length; ++i) { + current = Cartesian3.fromArray(directionsNormalsAndBisectorsWithMagnitudeSquared, directions[i] * stride, current); - // Extend position so the volume encompasses the sensor's radius. Use the shorter bisector in the scaling. - var factor = 2.0 / Math.min(Cartesian3.magnitude(Cartesian3.add(previous, temp)), Cartesian3.magnitude(Cartesian3.add(temp, next))); - var dot = Cartesian3.dot(temp, d); - fronts.push(Cartesian3.add(Cartesian3.multiplyByScalar(temp, radius * factor), Cartesian3.multiplyByScalar(d, radius * (minDot - factor * dot)))); - backs.push(Cartesian3.add(Cartesian3.multiplyByScalar(temp, radius * factor), Cartesian3.multiplyByScalar(d, radius * (1.0 - factor * dot)))); + var dot = Cartesian3.dot(current, d); + if (dot === 0.0) { + fronts.push(Cartesian3.multiplyByScalar(current, radius)); + backs.push(Cartesian3.add(Cartesian3.multiplyByScalar(current, radius), Cartesian3.multiplyByScalar(d, radius))); + } else { + fronts.push(Cartesian3.subtract(Cartesian3.multiplyByScalar(current, radius * maxDot / dot), Cartesian3.multiplyByScalar(d, radius * (maxDot - minDot)))); + backs.push(Cartesian3.add(Cartesian3.multiplyByScalar(current, radius * maxDot / dot), Cartesian3.multiplyByScalar(d, radius * (1.0 - maxDot)))); + } } centerFront = Cartesian3.multiplyByScalar(d, radius * minDot, centerFront); centerBack = Cartesian3.multiplyByScalar(d, radius, centerBack); - return computeBoundingPyramidalFrustumVertices(directions, centerFront, fronts, centerBack, backs, vertices); + return computeBoundingPyramidalFrustumVertices(directionsNormalsAndBisectorsWithMagnitudeSquared, directions, centerFront, fronts, centerBack, backs, vertices); } - function updateDomeCommand(command, index, customSensorVolume, context, directions, radius) { - var numberOfVertices = directions.length * 4 * 3; // (sides)(4 triangles/side)(3 vertices/triangle) + function updateDomeCommand(command, customSensorVolume, context, radius) { + var convexHull = customSensorVolume._sphericalPolygon.convexHull; + var numberOfVertices = convexHull.length * 4 * 3; // (sides)(4 triangles/side)(3 vertices/triangle) var numberOfFloats = numberOfVertices * numberOfFloatsPerVertex; - var domeFloatOffset = customSensorVolume._domeVertexCount * numberOfFloatsPerVertex; - var vertices = new Float32Array(customSensorVolume._domeCommandsVertices.buffer, Float32Array.BYTES_PER_ELEMENT * domeFloatOffset, numberOfFloats); - var normals = computeDomeVertices(customSensorVolume, directions, radius, vertices); - var uniforms = kDopUniforms(normals); + var vertices = new Float32Array(customSensorVolume._domeCommandsVertices.buffer, 0, numberOfFloats); + var normals = computeDomeVertices(customSensorVolume, customSensorVolume._sphericalPolygon._directionsNormalsAndBisectorsWithMagnitudeSquared, convexHull, radius, vertices); + var uniforms = customSensorVolume._sensorUniforms; - command.offset = customSensorVolume._domeVertexCount; + command.offset = 0; var boundingVolume = BoundingSphere.fromVertices(vertices, undefined, numberOfFloatsPerVertex, command.boundingVolume); command.uniformMap = combine(combine(customSensorVolume._uniforms, customSensorVolume._domeSurfaceMaterial._uniforms), uniforms); command.boundingVolume = BoundingSphere.transform(boundingVolume, customSensorVolume.modelMatrix, command.boundingVolume); command.modelMatrix = customSensorVolume.modelMatrix; - customSensorVolume._domeVertexCount += numberOfVertices; - customSensorVolume._domeCommandsBuffer.copyFromArrayView(vertices, Float32Array.BYTES_PER_ELEMENT * domeFloatOffset); + customSensorVolume._domeCommandsBuffer.copyFromArrayView(vertices, 0); if (command.count !== numberOfVertices) { command.count = numberOfVertices; - var glsl = kDopImplicitSurfaceFunction(normals.length); - customSensorVolume._domeColorCommandsSource[index] = glsl; + var glsl = customSensorVolume._sensorGlsl; var sensorDomeFS = customSensorVolume._cameraIsInsideDome ? SensorDomeInsideFS : SensorDomeOutsideFS; @@ -1276,7 +1329,7 @@ define([ centerBack = Cartesian3.multiplyByScalar(d, radius, centerBack); if (customSensorVolume.portionToDisplay === SensorVolumePortionToDisplay.COMPLETE) { - return computeBoundingPyramidalFrustumVertices(directions, centerFront, fronts, centerBack, backs, vertices); + return computeBoundingPyramidalFrustumVerticesFromDirections(directions, centerFront, fronts, centerBack, backs, vertices); } else if (customSensorVolume.showThroughEllipsoid || customSensorVolume.portionToDisplay === SensorVolumePortionToDisplay.ABOVE_ELLIPSOID_HORIZON) { return computeBoundingPyramidalVertices(directions, centerBack, backs, vertices); } else if (customSensorVolume.portionToDisplay === SensorVolumePortionToDisplay.BELOW_ELLIPSOID_HORIZON) { @@ -1336,12 +1389,30 @@ define([ } function renderCompleteDome(customSensorVolume, context, radius) { - var command = customSensorVolume._domeColorCommands[0]; + var command = customSensorVolume._domeColorCommand; command.boundingVolume = BoundingSphere.transform(customSensorVolume._completeDomeBoundingVolumeMC, customSensorVolume.modelMatrix, command.boundingVolume); command.modelMatrix = customSensorVolume.modelMatrix; customSensorVolume._domeColorCommandList.push(command); } + function angularSortUsingSineAndCosine(a, b) { + function computeSortValue(o) { + if (o.sine > 0.0) { + return -o.cosine - 1.0; + } else if (o.sine < 0.0) { + return o.cosine + 1.0; + } else if (o.cosine > 0.0) { + return -2.0; + } else if (o.cosine < 0.0) { + return 0.0; + } else { + throw new DeveloperError('Angle value is undefined (sine and cosine are both zero).'); + } + } + + return computeSortValue(a) - computeSortValue(b); + } + // Scratch variables... var modelToWorld = new Matrix3(); var worldToModel = new Matrix3(); @@ -1362,8 +1433,13 @@ define([ var offCrossing = new Cartesian3(); var firstOnCrossing = new Cartesian3(); var firstOnCrossingDirection = new Cartesian3(); + var firstOffCrossing = new Cartesian3(); var g = new Cartesian3(); var scaledQ = new Cartesian3(); + var crossings = []; + var xAxis = new Cartesian3(); + var yAxis = new Cartesian3(); + var t = new Cartesian3(); function computeCrossings(customSensorVolume, context) { modelToWorld = Matrix4.getRotation(customSensorVolume.modelMatrix, modelToWorld); @@ -1402,12 +1478,20 @@ define([ var earthCenterIsInsideSensor = true; var noLateralFacetsIntersectEllipsoidHorizonSurface = true; + var isConvex = customSensorVolume._sphericalPolygon.isConvex; + if (!isConvex) { + crossings.length = 0; + mostOrthogonalAxis = Cartesian3.mostOrthogonalAxis(qUnit, mostOrthogonalAxis); + xAxis = Cartesian3.normalize(Cartesian3.cross(mostOrthogonalAxis, qUnit, xAxis), xAxis); + yAxis = Cartesian3.normalize(Cartesian3.cross(qUnit, xAxis, yAxis), yAxis); + } + var count = 0; var length = customSensorVolume._sphericals.length; for (var index = 0; index < length; ++index) { var offset = index * 10; - direction = Cartesian3.fromArray(customSensorVolume._directionsNormalsAndBisectorsWithMagnitudeSquared, offset, direction); - normal = Cartesian3.fromArray(customSensorVolume._directionsNormalsAndBisectorsWithMagnitudeSquared, offset + 3, normal); + direction = Cartesian3.fromArray(customSensorVolume._sphericalPolygon._directionsNormalsAndBisectorsWithMagnitudeSquared, offset, direction); + normal = Cartesian3.fromArray(customSensorVolume._sphericalPolygon._directionsNormalsAndBisectorsWithMagnitudeSquared, offset + 3, normal); b = customSensorVolume.ellipsoid.transformPositionFromScaledSpace(Matrix3.multiplyByVector(modelToWorld, normal, b), b); bUnit = Cartesian3.normalize(b, bUnit); var cosineSigma = Cartesian3.dot(q, bUnit); @@ -1431,8 +1515,8 @@ define([ s = customSensorVolume.ellipsoid.transformPositionFromScaledSpace(r, s); v = Matrix3.multiplyByVector(worldToModel, Cartesian3.subtract(s, p, v), v); d = Cartesian3.normalize(v, d); - bisector = Cartesian3.fromArray(customSensorVolume._directionsNormalsAndBisectorsWithMagnitudeSquared, offset + 6, bisector); - var bisectorMagnitudeSquared = customSensorVolume._directionsNormalsAndBisectorsWithMagnitudeSquared[offset + 9]; + bisector = Cartesian3.fromArray(customSensorVolume._sphericalPolygon._directionsNormalsAndBisectorsWithMagnitudeSquared, offset + 6, bisector); + var bisectorMagnitudeSquared = customSensorVolume._sphericalPolygon._directionsNormalsAndBisectorsWithMagnitudeSquared[offset + 9]; if ((customSensorVolume.portionToDisplay !== SensorVolumePortionToDisplay.COMPLETE || Cartesian3.magnitudeSquared(v) <= radiusSquared) && Cartesian3.dot(d, bisector) > bisectorMagnitudeSquared) { if (!foundOffCrossing) { @@ -1445,6 +1529,15 @@ define([ directions.push(Cartesian3.clone(d)); foundOnCrossing = true; } + + if (!isConvex) { + t = Cartesian3.normalize(Cartesian3.subtract(r, qUnit, t), t); + var cosineOn = Cartesian3.dot(t, xAxis); + var sineOn = Cartesian3.dot(t, yAxis); + var cOn = new Crossing(r, cosineOn, sineOn, 1); + crossings.push(cOn); + } + if (customSensorVolume.debugShowCrossingPoints) { labelCollection.add({ position : v, @@ -1454,15 +1547,10 @@ define([ } else if (foundOffCrossing) { directions.push(Cartesian3.clone(direction)); } - if (foundOnCrossing && foundOffCrossing) { + if (isConvex && foundOnCrossing && foundOffCrossing) { var command = customSensorVolume._ellipsoidHorizonSurfaceColorCommands[count + 1]; updateHorizonCommand(count, command, customSensorVolume, context, offCrossing, onCrossing, worldToModel, p, q, qMagnitudeSquared, radius); customSensorVolume._ellipsoidHorizonSurfaceColorCommandList.push(command); - if (customSensorVolume.portionToDisplay !== SensorVolumePortionToDisplay.BELOW_ELLIPSOID_HORIZON) { - var domeCommand = customSensorVolume._domeColorCommands[count + 1]; - updateDomeCommand(domeCommand, count + 1, customSensorVolume, context, directions, radius); - customSensorVolume._domeColorCommandList.push(domeCommand); - } foundOnCrossing = false; foundOffCrossing = false; directions.length = 0; @@ -1478,6 +1566,15 @@ define([ directions.push(Cartesian3.clone(d)); directions.push(Cartesian3.clone(direction)); foundOffCrossing = true; + + if (!isConvex) { + t = Cartesian3.normalize(Cartesian3.subtract(r, qUnit, t), t); + var cosineOff = Cartesian3.dot(t, xAxis); + var sineOff = Cartesian3.dot(t, yAxis); + var cOff = new Crossing(r, cosineOff, sineOff, -1); + crossings.push(cOff); + } + if (customSensorVolume.debugShowCrossingPoints) { labelCollection.add({ position : v, @@ -1492,10 +1589,10 @@ define([ directions.push(Cartesian3.clone(direction)); } } - if (foundOnCrossingFirst && foundOffCrossing) { + if (isConvex && foundOnCrossingFirst && foundOffCrossing) { for (var i = 0; i < firstOnCrossingIndex; ++i) { var offset2 = i * 10; - direction = Cartesian3.fromArray(customSensorVolume._directionsNormalsAndBisectorsWithMagnitudeSquared, offset2, direction); + direction = Cartesian3.fromArray(customSensorVolume._sphericalPolygon._directionsNormalsAndBisectorsWithMagnitudeSquared, offset2, direction); directions.push(Cartesian3.clone(direction)); } directions.push(Cartesian3.clone(firstOnCrossingDirection)); @@ -1503,13 +1600,47 @@ define([ updateHorizonCommand(count, hc, customSensorVolume, context, offCrossing, firstOnCrossing, worldToModel, p, q, qMagnitudeSquared, radius); customSensorVolume._ellipsoidHorizonSurfaceColorCommandList.push(hc); - if (customSensorVolume.portionToDisplay !== SensorVolumePortionToDisplay.BELOW_ELLIPSOID_HORIZON) { - var dc = customSensorVolume._domeColorCommands[count + 1]; - updateDomeCommand(dc, count + 1, customSensorVolume, context, directions, radius); - customSensorVolume._domeColorCommandList.push(dc); + ++count; + } + + if (!isConvex && crossings.length > 0) { + crossings.sort(angularSortUsingSineAndCosine); + foundOnCrossing = false; + foundOffCrossing = false; + var foundOffCrossingFirst = false; + count = 0; + for (var j = 0; j < crossings.length; ++j) { + var c = crossings[j]; + if (c.kind === -1) { + if (!foundOnCrossing) { + Cartesian3.clone(c.r, firstOffCrossing); + foundOffCrossingFirst = true; + } else { + Cartesian3.clone(c.r, offCrossing); + foundOffCrossing = true; + } + } + if (foundOnCrossing && foundOffCrossing) { + var command2 = customSensorVolume._ellipsoidHorizonSurfaceColorCommands[count + 1]; + updateHorizonCommand(count, command2, customSensorVolume, context, offCrossing, onCrossing, worldToModel, p, q, qMagnitudeSquared, radius); + customSensorVolume._ellipsoidHorizonSurfaceColorCommandList.push(command2); + foundOnCrossing = false; + foundOffCrossing = false; + + ++count; + } + if (c.kind === 1) { + Cartesian3.clone(c.r, onCrossing); + foundOnCrossing = true; + } } + if (foundOffCrossingFirst && foundOnCrossing) { + var hc2 = customSensorVolume._ellipsoidHorizonSurfaceColorCommands[count + 1]; + updateHorizonCommand(count, hc2, customSensorVolume, context, onCrossing, firstOffCrossing, worldToModel, p, q, qMagnitudeSquared, radius); + customSensorVolume._ellipsoidHorizonSurfaceColorCommandList.push(hc2); - ++count; + ++count; + } } // PERFORMANCE_IDEA: Tighten the proxy geometry for the BELOW_ELLIPSOID_HORIZON case. @@ -1523,8 +1654,7 @@ define([ renderCompleteEllipsoidHorizonSurface(customSensorVolume, context, radius, p, q, qMagnitudeSquared, oneOverQ, qUnit, modelToWorld, worldToModel); } } - } else if (customSensorVolume.portionToDisplay !== SensorVolumePortionToDisplay.ABOVE_ELLIPSOID_HORIZON && - customSensorVolume.portionToDisplay !== SensorVolumePortionToDisplay.BELOW_ELLIPSOID_HORIZON) { + } else if (customSensorVolume.portionToDisplay !== SensorVolumePortionToDisplay.BELOW_ELLIPSOID_HORIZON) { renderCompleteDome(customSensorVolume, context, radius); } } @@ -1733,7 +1863,7 @@ define([ // Initial render state creation if (definitionChanged || showThroughEllipsoidChanged || - (!defined(this._domeColorCommands)) || + (!defined(this._domeColorCommand)) || (this._domeSurfaceIsTranslucent !== domeSurfaceIsTranslucent)) { this._domeSurfaceIsTranslucent = domeSurfaceIsTranslucent; @@ -1767,12 +1897,9 @@ define([ }); } - var length2 = this._domeColorCommands.length; - for (var index2 = 0; index2 < length2; ++index2) { - var domeColorCommand = this._domeColorCommands[index2]; - domeColorCommand.renderState = domeSurfaceRenderState; - domeColorCommand.pass = domeSurfaceIsTranslucent ? Pass.TRANSLUCENT : Pass.OPAQUE; - } + var domeColorCommand = this._domeColorCommand; + domeColorCommand.renderState = domeSurfaceRenderState; + domeColorCommand.pass = domeSurfaceIsTranslucent ? Pass.TRANSLUCENT : Pass.OPAQUE; } if (!defined(this._frontFaceColorCommand.vertexArray)) { @@ -1811,7 +1938,6 @@ define([ if (portionToDisplayChanged || definitionChanged || modelMatrixChanged) { this._ellipsoidHorizonSurfaceColorCommandList.length = 0; this._domeColorCommandList.length = 0; - this._domeVertexCount = this._completeDomeVertexCount; // These vertices are reserved for rendering the complete dome. computeCrossings(this, context); } @@ -1869,25 +1995,22 @@ define([ var sensorDomeFS = cameraIsInsideDome ? SensorDomeInsideFS : SensorDomeOutsideFS; this._cameraIsInsideDome = cameraIsInsideDome; - var ll = this._domeColorCommands.length; - for (var ii = 0; ii < ll; ++ii) { - var domeCommand = this._domeColorCommands[ii]; - var domeSource = this._domeColorCommandsSource[ii]; - if (domeSurfaceMaterialChanged) { - domeCommand.uniformMap = combine(this._domeSurfaceMaterial._uniforms, domeCommand.uniformMap); - } - - var fragmentShaderSource = createShaderSource({ - defines : [ this.debugShowProxyGeometry ? 'ONLY_WIRE_FRAME' : '', - context.fragmentDepth ? 'WRITE_DEPTH' : '' , - this.showIntersection ? 'SHOW_INTERSECTION' : '', - this.showThroughEllipsoid ? 'SHOW_THROUGH_ELLIPSOID' : '', - SensorVolumePortionToDisplay.toString(this.portionToDisplay)], - sources : [ShadersSensorVolume, domeSource, this._domeSurfaceMaterial.shaderSource, SensorDomeFS, sensorDomeFS] - }); - domeCommand.primitiveType = primitiveType; - domeCommand.shaderProgram = context.shaderCache.replaceShaderProgram(domeCommand.shaderProgram, CommonSensorVolumeVS, fragmentShaderSource, attributeLocations); + var domeCommand = this._domeColorCommand; + var domeSource = this._sensorGlsl; + if (domeSurfaceMaterialChanged) { + domeCommand.uniformMap = combine(this._domeSurfaceMaterial._uniforms, domeCommand.uniformMap); } + + var fragmentShaderSource = createShaderSource({ + defines : [ this.debugShowProxyGeometry ? 'ONLY_WIRE_FRAME' : '', + context.fragmentDepth ? 'WRITE_DEPTH' : '' , + this.showIntersection ? 'SHOW_INTERSECTION' : '', + this.showThroughEllipsoid ? 'SHOW_THROUGH_ELLIPSOID' : '', + SensorVolumePortionToDisplay.toString(this.portionToDisplay)], + sources : [ShadersSensorVolume, domeSource, this._domeSurfaceMaterial.shaderSource, SensorDomeFS, sensorDomeFS] + }); + domeCommand.primitiveType = primitiveType; + domeCommand.shaderProgram = context.shaderCache.replaceShaderProgram(domeCommand.shaderProgram, CommonSensorVolumeVS, fragmentShaderSource, attributeLocations); } if (pass.render) { @@ -1972,10 +2095,7 @@ define([ for (var index = 0; index < length; ++index) { this._ellipsoidHorizonSurfaceColorCommands[index].shaderProgram = this._ellipsoidHorizonSurfaceColorCommands[index].shaderProgram && this._ellipsoidHorizonSurfaceColorCommands[index].shaderProgram.release(); } - length = this._domeColorCommands.length; - for (var index2 = 0; index2 < length; ++index2) { - this._domeColorCommands[index2].shaderProgram = this._domeColorCommands[index2].shaderProgram && this._domeColorCommands[index2].shaderProgram.release(); - } + this._domeColorCommand.shaderProgram = this._domeColorCommand.shaderProgram && this._domeColorCommand.shaderProgram.release(); this._pickCommand.shaderProgram = this._pickCommand.shaderProgram && this._pickCommand.shaderProgram.release(); this._pickId = this._pickId && this._pickId.destroy(); return destroyObject(this); diff --git a/Source/Scene/SphericalPolygonShaderSupport.js b/Source/Scene/SphericalPolygonShaderSupport.js new file mode 100644 index 00000000000..f11a9199ac0 --- /dev/null +++ b/Source/Scene/SphericalPolygonShaderSupport.js @@ -0,0 +1,174 @@ +/*global define*/ +define([ + '../Core/freezeObject', + '../Core/defaultValue', + '../Core/defined', + '../Core/defineProperties', + '../Core/DeveloperError', + '../Core/Math', + '../Core/Cartesian3', + '../Core/SphericalPolygon' + ], function( + freezeObject, + defaultValue, + defined, + defineProperties, + DeveloperError, + CesiumMath, + Cartesian3, + SphericalPolygon) { + "use strict"; + + var stride = 10; + var directionsOffset = 0; + var normalsOffset = 3; + + /** + * @private + */ + var SphericalPolygonShaderSupport = function() {}; + + function kDopFacetNormalName(i, j) { + return 'u_kDopFacetNormal_' + i + '_' + j; + } + + function convexHullImplicitSurfaceFunction(vertices, name, sign) { + var length = vertices.length; + var glsl = ''; + var result = ''; + var oppositeSign = (sign === '+') ? '-' : '+'; + for (var i = 0; i < length; ++i) { + var j = (i + 1 === length) ? 0 : i + 1; + var initialIndex = vertices[i]; + var finalIndex = vertices[j]; + var uniform = (initialIndex < finalIndex) ? sign + kDopFacetNormalName(initialIndex, finalIndex) : oppositeSign + kDopFacetNormalName(finalIndex, initialIndex); + if (i === 0) { + result += '\tfloat value = dot(displacement, ' + uniform + ');\n'; + } else { + result += '\tvalue = max(value, dot(displacement, ' + uniform + '));\n'; + } + } + glsl += '\nfloat ' + name + '(vec3 displacement)\n{\n' + result + '\treturn value;\n}\n'; + return glsl; + } + + function sphericalPolygonImplicitSurfaceFunction(hull, name, sign) { + var result = ''; + if (defined(hull.holes)) { + var oppositeSign = (sign === '+') ? '-' : '+'; + for (var h = 0; h < hull.holes.length; ++h) { + var functionName = name + '_' + h; + result += sphericalPolygonImplicitSurfaceFunction(hull.holes[h], functionName, oppositeSign); + } + } + result += convexHullImplicitSurfaceFunction(hull, name, sign); + return result; + } + + function sphericalPolygonImplicitSurfaceFunctionUniforms(hull, uniforms) { + var result = ''; + if (defined(hull.holes)) { + for (var h = 0; h < hull.holes.length; ++h) { + result += sphericalPolygonImplicitSurfaceFunctionUniforms(hull.holes[h], uniforms); + } + } + var length = hull.length; + for (var i = 0; i < length; ++i) { + var j = (i + 1 === length) ? 0 : i + 1; + var initialIndex = hull[i]; + var finalIndex = hull[j]; + var name = (initialIndex < finalIndex) ? kDopFacetNormalName(initialIndex, finalIndex) : kDopFacetNormalName(finalIndex, initialIndex); + + if (!defined(uniforms[name])) { + uniforms[name] = true; + result += 'uniform vec3 ' + name + ';\n'; + } + } + return result; + } + + function aggregateFunction(hull, functionName, variableName) { + var result = '\tfloat ' + variableName + ' = ' + functionName + '(displacement);\n'; + if (defined(hull.holes)) { + for (var i = 0; i < hull.holes.length; ++i) { + var name = functionName + '_' + i; + result += '\t' + variableName + ' = max(' + variableName + ', -' + name + '(displacement));\n'; + var hole = hull.holes[i]; + if (defined(hole.holes)) { + var variable = variableName + '_' + i; + for (var j = 0; j < hole.holes.length; ++j) { + var v = variable + '_' + j; + result += aggregateFunction(hole.holes[j], name + '_' + j, v); + result += '\t' + variableName + ' = min(' + variableName + ', ' + v + ');\n'; + } + } + } + } + return result; + } + + function returnNormal(normal) { + return function() { + return normal; + }; + } + + var lastDirection = new Cartesian3(); + var offset = new Cartesian3(); + var direction = new Cartesian3(); + + function addUniforms(floats, hull, uniforms) { + var numberOfVertices = floats.length / stride; + + if (defined(hull.holes)) { + for (var h = 0; h < hull.holes.length; ++h) { + addUniforms(floats, hull.holes[h], uniforms); + } + } + var length = hull.length; + for (var i = 0; i < length; ++i) { + var j = (i + 1 === length) ? 0 : i + 1; + var initialIndex = hull[i]; + var finalIndex = hull[j]; + + var offset = initialIndex * stride + directionsOffset; + lastDirection = Cartesian3.fromArray(floats, offset, lastDirection); + offset = finalIndex * stride + directionsOffset; + direction = Cartesian3.fromArray(floats, offset, direction); + var name = (initialIndex < finalIndex) ? kDopFacetNormalName(initialIndex, finalIndex) : kDopFacetNormalName(finalIndex, initialIndex); + + if (!defined(uniforms[name])) { + var difference = (initialIndex < finalIndex) ? finalIndex - initialIndex : finalIndex + numberOfVertices - initialIndex; + if (difference === 1) { + uniforms[name] = (initialIndex < finalIndex) ? returnNormal(Cartesian3.fromArray(floats, finalIndex * stride + normalsOffset)) : returnNormal(Cartesian3.negate(Cartesian3.fromArray(floats, finalIndex * stride + normalsOffset))); + } else { + uniforms[name] = (initialIndex < finalIndex) ? returnNormal(Cartesian3.cross(direction, lastDirection)) : returnNormal(Cartesian3.cross(lastDirection, direction)); + } + } + } + } + + SphericalPolygonShaderSupport.uniforms = function(sphericalPolygon) { + var uniforms = {}; + addUniforms(sphericalPolygon._directionsNormalsAndBisectorsWithMagnitudeSquared, sphericalPolygon.convexHull, uniforms); + return uniforms; + }; + + SphericalPolygonShaderSupport.implicitSurfaceFunction = function(sphericalPolygon) { + var convexHull = sphericalPolygon.convexHull; + var glsl = '\n'; + // Emit uniforms. + var uniforms = {}; + glsl += sphericalPolygonImplicitSurfaceFunctionUniforms(convexHull, uniforms); + // Emit convex hull funtions. + var functionName = 'convexHull'; + var variableName = 'value'; + glsl += sphericalPolygonImplicitSurfaceFunction(convexHull, functionName, '+'); + // Emit implicit surface function. + var result = aggregateFunction(convexHull, functionName, variableName); + glsl += '\nfloat sensorSurfaceFunction(vec3 displacement)\n{\n' + result + '\treturn ' + variableName + ';\n}\n'; + return glsl; + }; + + return SphericalPolygonShaderSupport; +}); diff --git a/Source/Shaders/SensorVolume.glsl b/Source/Shaders/SensorVolume.glsl index 0fd07ad2245..c319899fc4b 100644 --- a/Source/Shaders/SensorVolume.glsl +++ b/Source/Shaders/SensorVolume.glsl @@ -17,7 +17,7 @@ float getIntersectionWidth() vec2 sensorCartesianToNormalizedPolarTextureCoordinates(float radius, vec3 point) { - // Maps (-180 to 180, -radius to +radius) coordinates both to the range [0.0, 1.0]. + // Maps (-180 to 180, 0 to +radius) coordinates both to the range [0.0, 1.0]. return vec2(atan(point.y, point.x) * czm_oneOverTwoPi + 0.5, length(point) / radius); } diff --git a/Specs/Core/SphericalPolygonSpec.js b/Specs/Core/SphericalPolygonSpec.js new file mode 100644 index 00000000000..f4c54cf9c6a --- /dev/null +++ b/Specs/Core/SphericalPolygonSpec.js @@ -0,0 +1,203 @@ +/*global defineSuite*/ +defineSuite([ + 'Core/SphericalPolygon', + 'Core/Cartesian3', + 'Core/Math' + ], function( + SphericalPolygon, + Cartesian3, + CesiumMath) { + "use strict"; + /*global jasmine,describe,xdescribe,it,xit,expect,beforeEach,afterEach,beforeAll,afterAll,spyOn,runs,waits,waitsFor*/ + + it('default constructor has undefined properties', function() { + var s = new SphericalPolygon(); + expect(s.isConvex).toEqual(undefined); + expect(s.vertices).toEqual(undefined); + expect(s.convexHull.length).toEqual(0); + expect(s.referenceAxis).toEqual(undefined); + expect(s.referenceDistance).toEqual(undefined); + }); + + it('constructor sets expected properties', function() { + var vertices = []; + vertices.push({ clock : CesiumMath.toRadians(-135.0), cone : CesiumMath.toRadians(60.0)}); + vertices.push({ clock : CesiumMath.toRadians(-45.0), cone : CesiumMath.toRadians(60.0)}); + vertices.push({ clock : CesiumMath.toRadians(45.0), cone : CesiumMath.toRadians(60.0)}); + vertices.push({ clock : CesiumMath.toRadians(135.0), cone : CesiumMath.toRadians(60.0)}); + + var s = new SphericalPolygon(vertices); + expect(s.isConvex).toEqual(true); + expect(s.vertices).toEqual(vertices); + expect(s.convexHull.length).toEqual(4); + expect(s.convexHull[0]).toEqual(0); + expect(s.convexHull[1]).toEqual(1); + expect(s.convexHull[2]).toEqual(2); + expect(s.convexHull[3]).toEqual(3); + expect(s.referenceAxis).toEqual(Cartesian3.UNIT_Z); + expect(s.referenceDistance).toEqual(0.5); + }); + + it('changing vertices causes properties to be recomputed', function() { + var vertices = []; + vertices.push({ clock : CesiumMath.toRadians(-135.0), cone : CesiumMath.toRadians(60.0)}); + vertices.push({ clock : CesiumMath.toRadians(-45.0), cone : CesiumMath.toRadians(60.0)}); + vertices.push({ clock : CesiumMath.toRadians(45.0), cone : CesiumMath.toRadians(60.0)}); + vertices.push({ clock : CesiumMath.toRadians(135.0), cone : CesiumMath.toRadians(60.0)}); + + var s = new SphericalPolygon(vertices); + expect(s.isConvex).toEqual(true); + expect(s.vertices).toEqual(vertices); + expect(s.convexHull.length).toEqual(4); + expect(s.convexHull[0]).toEqual(0); + expect(s.convexHull[1]).toEqual(1); + expect(s.convexHull[2]).toEqual(2); + expect(s.convexHull[3]).toEqual(3); + expect(s.referenceAxis).toEqual(Cartesian3.UNIT_Z); + expect(s.referenceDistance).toEqual(0.5); + + vertices.length = 0; + vertices.push({ clock : CesiumMath.toRadians(135.0), cone : CesiumMath.toRadians(135.0)}); + vertices.push({ clock : CesiumMath.toRadians(-135.0), cone : CesiumMath.toRadians(150.0)}); + vertices.push({ clock : CesiumMath.toRadians(-45.0), cone : CesiumMath.toRadians(135.0)}); + vertices.push({ clock : CesiumMath.toRadians(-135.0), cone : CesiumMath.toRadians(135.0)}); + s.vertices = vertices; + expect(s.isConvex).toEqual(false); + expect(s.vertices).toEqual(vertices); + expect(s.convexHull.length).toEqual(3); + expect(s.convexHull[0]).toEqual(0); + expect(s.convexHull[1]).toEqual(2); + expect(s.convexHull[2]).toEqual(3); + expect(s.referenceAxis).toEqual(Cartesian3.negate(Cartesian3.UNIT_Z)); + expect(s.referenceDistance).toEqualEpsilon(1.0 / Math.sqrt(2.0), CesiumMath.EPSILON7); + }); + + it('foot sensor produces correct results', function() { + var vertices = []; + vertices.push({ clock : CesiumMath.toRadians(0.0), cone : CesiumMath.toRadians(14.03624347)}); + vertices.push({ clock : CesiumMath.toRadians(74.3), cone : CesiumMath.toRadians(32.98357092)}); + vertices.push({ clock : CesiumMath.toRadians(80.54), cone : CesiumMath.toRadians(37.23483398)}); + vertices.push({ clock : CesiumMath.toRadians(90.0), cone : CesiumMath.toRadians(37.77568431)}); + vertices.push({ clock : CesiumMath.toRadians(101.31), cone : CesiumMath.toRadians(37.41598862)}); + vertices.push({ clock : CesiumMath.toRadians(116.56), cone : CesiumMath.toRadians(29.20519037)}); + vertices.push({ clock : CesiumMath.toRadians(180.0), cone : CesiumMath.toRadians(14.03624347)}); + vertices.push({ clock : CesiumMath.toRadians(247.2), cone : CesiumMath.toRadians(34.11764003)}); + vertices.push({ clock : CesiumMath.toRadians(251.56), cone : CesiumMath.toRadians(44.67589157)}); + vertices.push({ clock : CesiumMath.toRadians(253.96), cone : CesiumMath.toRadians(46.12330271)}); + vertices.push({ clock : CesiumMath.toRadians(259.63), cone : CesiumMath.toRadians(46.19202903)}); + vertices.push({ clock : CesiumMath.toRadians(262.87), cone : CesiumMath.toRadians(45.21405547)}); + vertices.push({ clock : CesiumMath.toRadians(262.4), cone : CesiumMath.toRadians(37.09839406)}); + vertices.push({ clock : CesiumMath.toRadians(266.47), cone : CesiumMath.toRadians(45.42651157)}); + vertices.push({ clock : CesiumMath.toRadians(270.0), cone : CesiumMath.toRadians(45.42651157)}); + vertices.push({ clock : CesiumMath.toRadians(270.97), cone : CesiumMath.toRadians(36.40877457)}); + vertices.push({ clock : CesiumMath.toRadians(272.79), cone : CesiumMath.toRadians(45.70731937)}); + vertices.push({ clock : CesiumMath.toRadians(278.33), cone : CesiumMath.toRadians(45.98533395)}); + vertices.push({ clock : CesiumMath.toRadians(281.89), cone : CesiumMath.toRadians(36.0358894)}); + vertices.push({ clock : CesiumMath.toRadians(282.99), cone : CesiumMath.toRadians(45.0)}); + vertices.push({ clock : CesiumMath.toRadians(286.99), cone : CesiumMath.toRadians(43.26652934)}); + vertices.push({ clock : CesiumMath.toRadians(291.8), cone : CesiumMath.toRadians(33.97011942)}); + vertices.push({ clock : CesiumMath.toRadians(293.3), cone : CesiumMath.toRadians(41.50882857)}); + vertices.push({ clock : CesiumMath.toRadians(300.96), cone : CesiumMath.toRadians(36.0826946)}); + vertices.push({ clock : CesiumMath.toRadians(319.18), cone : CesiumMath.toRadians(19.9888568)}); + + var s = new SphericalPolygon(vertices); + expect(s.isConvex).toEqual(false); + expect(s.vertices).toEqual(vertices); + expect(s.convexHull.length).toEqual(12); + expect(s.convexHull[0]).toEqual(1); + expect(s.convexHull[1]).toEqual(2); + expect(s.convexHull[2]).toEqual(3); + expect(s.convexHull[3]).toEqual(4); + expect(s.convexHull[4]).toEqual(5); + expect(s.convexHull[5]).toEqual(8); + expect(s.convexHull[6]).toEqual(9); + expect(s.convexHull[7]).toEqual(10); + expect(s.convexHull[8]).toEqual(17); + expect(s.convexHull[9]).toEqual(19); + expect(s.convexHull[10]).toEqual(22); + expect(s.convexHull[11]).toEqual(23); + expect(s.convexHull.holes.length).toEqual(5); + + var hole = s.convexHull.holes[0]; + expect(hole.length).toEqual(4); + expect(hole[0]).toEqual(5); + expect(hole[1]).toEqual(6); + expect(hole[2]).toEqual(7); + expect(hole[3]).toEqual(8); + expect(hole.holes).toEqual(undefined); + + hole = s.convexHull.holes[1]; + expect(hole.length).toEqual(4); + expect(hole[0]).toEqual(10); + expect(hole[1]).toEqual(12); + expect(hole[2]).toEqual(15); + expect(hole[3]).toEqual(17); + expect(hole.holes.length).toEqual(3); + + var hull = hole.holes[0]; + expect(hull.length).toEqual(3); + expect(hull[0]).toEqual(10); + expect(hull[1]).toEqual(11); + expect(hull[2]).toEqual(12); + expect(hull.holes).toEqual(undefined); + + hull = hole.holes[1]; + expect(hull.length).toEqual(4); + expect(hull[0]).toEqual(12); + expect(hull[1]).toEqual(13); + expect(hull[2]).toEqual(14); + expect(hull[3]).toEqual(15); + expect(hull.holes).toEqual(undefined); + + hull = hole.holes[2]; + expect(hull.length).toEqual(3); + expect(hull[0]).toEqual(15); + expect(hull[1]).toEqual(16); + expect(hull[2]).toEqual(17); + expect(hull.holes).toEqual(undefined); + + hole = s.convexHull.holes[2]; + expect(hole.length).toEqual(3); + expect(hole[0]).toEqual(17); + expect(hole[1]).toEqual(18); + expect(hole[2]).toEqual(19); + expect(hole.holes).toEqual(undefined); + + hole = s.convexHull.holes[3]; + expect(hole.length).toEqual(3); + expect(hole[0]).toEqual(19); + expect(hole[1]).toEqual(21); + expect(hole[2]).toEqual(22); + expect(hole.holes.length).toEqual(1); + + hull = hole.holes[0]; + expect(hull.length).toEqual(3); + expect(hull[0]).toEqual(19); + expect(hull[1]).toEqual(20); + expect(hull[2]).toEqual(21); + expect(hull.holes).toEqual(undefined); + + hole = s.convexHull.holes[4]; + expect(hole.length).toEqual(3); + expect(hole[0]).toEqual(23); + expect(hole[1]).toEqual(24); + expect(hole[2]).toEqual(1); + expect(hole.holes.length).toEqual(1); + + hull = hole.holes[0]; + expect(hull.length).toEqual(3); + expect(hull[0]).toEqual(24); + expect(hull[1]).toEqual(0); + expect(hull[2]).toEqual(1); + expect(hull.holes).toEqual(undefined); + + var axis = new Cartesian3( + -0.011600696329179922, + -0.07289265444780975, + 0.9972723222732247 + ); + + expect(s.referenceAxis).toEqualEpsilon(axis, CesiumMath.EPSILON7); + expect(s.referenceDistance).toEqualEpsilon(0.7436070769796277, CesiumMath.EPSILON7); + }); +}); diff --git a/Specs/Scene/CustomSensorVolumeSpec.js b/Specs/Scene/CustomSensorVolumeSpec.js index ba93fbccdf3..bbf55814244 100644 --- a/Specs/Scene/CustomSensorVolumeSpec.js +++ b/Specs/Scene/CustomSensorVolumeSpec.js @@ -268,6 +268,56 @@ defineSuite([ expect(context.readPixels()).not.toEqual([0, 0, 0, 255]); }); + it('renders nonconvex volume', function() { + var camera = scene.camera; + camera.direction = Cartesian3.clone(Cartesian3.UNIT_Z, camera.direction); + camera.up = Cartesian3.clone(Cartesian3.UNIT_Y, camera.up); + var x = 1000000.0; + var y = 1000000.0; + var z = -(Ellipsoid.WGS84.radii.z + 10000000.0); + camera.position = new Cartesian3(x, y, z); + + scene.render(); + expect(context.readPixels()).toEqual([0, 0, 0, 255]); + var sensor = addSensor({ + latitude : -90.0, + altitude : 9000000.0 + }); + + var directions = []; + directions.push({ clock : CesiumMath.toRadians(0.0), cone : CesiumMath.toRadians(14.03624347)}); + directions.push({ clock : CesiumMath.toRadians(74.3), cone : CesiumMath.toRadians(32.98357092)}); + directions.push({ clock : CesiumMath.toRadians(80.54), cone : CesiumMath.toRadians(37.23483398)}); + directions.push({ clock : CesiumMath.toRadians(90.0), cone : CesiumMath.toRadians(37.77568431)}); + directions.push({ clock : CesiumMath.toRadians(101.31), cone : CesiumMath.toRadians(37.41598862)}); + directions.push({ clock : CesiumMath.toRadians(116.56), cone : CesiumMath.toRadians(29.20519037)}); + directions.push({ clock : CesiumMath.toRadians(180.0), cone : CesiumMath.toRadians(14.03624347)}); + directions.push({ clock : CesiumMath.toRadians(247.2), cone : CesiumMath.toRadians(34.11764003)}); + directions.push({ clock : CesiumMath.toRadians(251.56), cone : CesiumMath.toRadians(44.67589157)}); + directions.push({ clock : CesiumMath.toRadians(253.96), cone : CesiumMath.toRadians(46.12330271)}); + directions.push({ clock : CesiumMath.toRadians(259.63), cone : CesiumMath.toRadians(46.19202903)}); + directions.push({ clock : CesiumMath.toRadians(262.87), cone : CesiumMath.toRadians(45.21405547)}); + directions.push({ clock : CesiumMath.toRadians(262.4), cone : CesiumMath.toRadians(37.09839406)}); + directions.push({ clock : CesiumMath.toRadians(266.47), cone : CesiumMath.toRadians(45.42651157)}); + directions.push({ clock : CesiumMath.toRadians(270.0), cone : CesiumMath.toRadians(45.42651157)}); + directions.push({ clock : CesiumMath.toRadians(270.97), cone : CesiumMath.toRadians(36.40877457)}); + directions.push({ clock : CesiumMath.toRadians(272.79), cone : CesiumMath.toRadians(45.70731937)}); + directions.push({ clock : CesiumMath.toRadians(278.33), cone : CesiumMath.toRadians(45.98533395)}); + directions.push({ clock : CesiumMath.toRadians(281.89), cone : CesiumMath.toRadians(36.0358894)}); + directions.push({ clock : CesiumMath.toRadians(282.99), cone : CesiumMath.toRadians(45.0)}); + directions.push({ clock : CesiumMath.toRadians(286.99), cone : CesiumMath.toRadians(43.26652934)}); + directions.push({ clock : CesiumMath.toRadians(291.8), cone : CesiumMath.toRadians(33.97011942)}); + directions.push({ clock : CesiumMath.toRadians(293.3), cone : CesiumMath.toRadians(41.50882857)}); + directions.push({ clock : CesiumMath.toRadians(300.96), cone : CesiumMath.toRadians(36.0826946)}); + directions.push({ clock : CesiumMath.toRadians(319.18), cone : CesiumMath.toRadians(19.9888568)}); + + sensor.radius = 10000000.0; + sensor.setDirections(directions); + + scene.render(); + expect(context.readPixels()).not.toEqual([0, 0, 0, 255]); + }); + it('does not render when show is false', function() { var sensor = addSensor({ latitude : -90.0,