From bc4a24a3526d1c8c26be57c436dd0ad05ac6b379 Mon Sep 17 00:00:00 2001 From: Kangning Li Date: Fri, 18 May 2018 15:00:25 -0400 Subject: [PATCH 01/39] renderable polylines on terrain --- Source/Core/GroundLineGeometry.js | 400 ++++++++++++++++++ Source/Core/GroundPolylineGeometry.js | 299 +++++++++++++ Source/Scene/GroundPolylinePrimitive.js | 264 ++++++++++++ Source/Scene/Primitive.js | 40 +- .../approximateSphericalCoordinates.glsl | 37 +- .../Functions/fastApproximateAtan.glsl | 55 +++ Source/Shaders/PolylineShadowVolumeFS.glsl | 64 +++ Source/Shaders/PolylineShadowVolumeVS.glsl | 72 ++++ Source/Workers/createGroundLineGeometry.js | 17 + Specs/Renderer/BuiltinFunctionsSpec.js | 23 + Specs/Scene/PrimitiveSpec.js | 5 +- 11 files changed, 1236 insertions(+), 40 deletions(-) create mode 100644 Source/Core/GroundLineGeometry.js create mode 100644 Source/Core/GroundPolylineGeometry.js create mode 100644 Source/Scene/GroundPolylinePrimitive.js create mode 100644 Source/Shaders/Builtin/Functions/fastApproximateAtan.glsl create mode 100644 Source/Shaders/PolylineShadowVolumeFS.glsl create mode 100644 Source/Shaders/PolylineShadowVolumeVS.glsl create mode 100644 Source/Workers/createGroundLineGeometry.js diff --git a/Source/Core/GroundLineGeometry.js b/Source/Core/GroundLineGeometry.js new file mode 100644 index 000000000000..2fa35449c0d5 --- /dev/null +++ b/Source/Core/GroundLineGeometry.js @@ -0,0 +1,400 @@ +define([ + './BoundingSphere', + './Cartesian3', + './Cartographic', + './Check', + './ComponentDatatype', + './Math', + './defaultValue', + './defined', + './defineProperties', + './Ellipsoid', + './EncodedCartesian3', + './Geometry', + './GeometryAttribute', + './GeometryAttributes', + './GeometryInstanceAttribute', + './Matrix3', + './Quaternion' + ], function( + BoundingSphere, + Cartesian3, + Cartographic, + Check, + ComponentDatatype, + CesiumMath, + defaultValue, + defined, + defineProperties, + Ellipsoid, + EncodedCartesian3, + Geometry, + GeometryAttribute, + GeometryAttributes, + GeometryInstanceAttribute, + Matrix3, + Quaternion) { + 'use strict'; + + var MITER_BREAK_SMALL = Math.cos(CesiumMath.toRadians(30)); + var MITER_BREAK_LARGE = Math.cos(CesiumMath.toRadians(150)); + + /** + * Description of the volume used to draw a line segment on terrain. + * + * @alias GroundLineGeometry + * @constructor + * + * @private + * + * @see GroundLineGeometry#createGeometry + */ + function GroundLineGeometry() { + this._startBottom = new Cartesian3(); + this._startTop = new Cartesian3(); + this._startNormal = new Cartesian3(); + + this._endBottom = new Cartesian3(); + this._endTop = new Cartesian3(); + this._endNormal = new Cartesian3(); + + this._workerName = 'createGroundLineGeometry'; + this._segmentBottomLength = undefined; + } + + GroundLineGeometry.fromArrays = function(index, normalsArray, bottomPositionsArray, topPositionsArray) { + var geometry = new GroundLineGeometry(); + + Cartesian3.unpack(bottomPositionsArray, index, geometry._startBottom); + Cartesian3.unpack(topPositionsArray, index, geometry._startTop); + Cartesian3.unpack(normalsArray, index, geometry._startNormal); + + Cartesian3.unpack(bottomPositionsArray, index + 3, geometry._endBottom); + Cartesian3.unpack(topPositionsArray, index + 3, geometry._endTop); + Cartesian3.unpack(normalsArray, index + 3, geometry._endNormal); + + breakMiter(geometry); + + return geometry; + } + + // TODO: add function to lower the bottom or raise the top to a specific altitude + + function direction(end, start, result) { + Cartesian3.subtract(end, start, result); + Cartesian3.normalize(result, result); + return result; + } + + // If either of the normal angles is too steep compared to the direction of the line segment, + // "break" the miter by rotating the normal 90 degrees around the "up" direction at the point + // For ultra precision we would want to project into a plane, but in practice this is sufficient. + var lineDirectionScratch = new Cartesian3(); + var vertexUpScratch = new Cartesian3(); + var matrix3Scratch = new Matrix3(); + var quaternionScratch = new Quaternion(); + function breakMiter(geometry) { + var lineDirection = direction(geometry._endBottom, geometry._startBottom, lineDirectionScratch); + var quaternion; + var rotationMatrix; + var vertexUp; + var dot; + var angle; + + dot = Cartesian3.dot(lineDirection, geometry._startNormal); + if (dot > MITER_BREAK_SMALL || dot < MITER_BREAK_LARGE) { + vertexUp = direction(geometry._startTop, geometry._startBottom, vertexUpScratch); + angle = dot < MITER_BREAK_LARGE ? CesiumMath.PI_OVER_TWO : -CesiumMath.PI_OVER_TWO; + quaternion = Quaternion.fromAxisAngle(vertexUp, angle, quaternionScratch); + rotationMatrix = Matrix3.fromQuaternion(quaternion, matrix3Scratch); + Matrix3.multiplyByVector(rotationMatrix, geometry._startNormal, geometry._startNormal); + } + + dot = Cartesian3.dot(lineDirection, geometry._endNormal) + if (dot > MITER_BREAK_SMALL || dot < MITER_BREAK_LARGE) { + vertexUp = direction(geometry._endTop, geometry._endBottom, vertexUpScratch); + angle = dot < MITER_BREAK_LARGE ? CesiumMath.PI_OVER_TWO : -CesiumMath.PI_OVER_TWO; + quaternion = Quaternion.fromAxisAngle(vertexUp, angle, quaternionScratch); + rotationMatrix = Matrix3.fromQuaternion(quaternion, matrix3Scratch); + Matrix3.multiplyByVector(rotationMatrix, geometry._endNormal, geometry._endNormal); + } + } + + defineProperties(GroundLineGeometry.prototype, { + // TODO: doc + segmentBottomLength : { + get : function() { + if (!defined(this._segmentBottomLength)) { + this._segmentBottomLength = Cartesian3.distance(this._startBottom, this._endBottom); + } + return this._segmentBottomLength; + } + } + }); + + /** + * The number of elements used to pack the object into an packArray. + * @type {Number} + */ + GroundLineGeometry.packedLength = Cartesian3.packedLength * 6; + + /** + * Stores the provided instance into the provided packArray. + * + * @param {GroundLineGeometry} value The value to pack. + * @param {Number[]} packArray The packArray to pack into. + * @param {Number} [startingIndex=0] The index into the packArray at which to start packing the elements. + * + * @returns {Number[]} The packArray that was packed into + */ + GroundLineGeometry.pack = function(value, packArray, startingIndex) { + //>>includeStart('debug', pragmas.debug); + Check.typeOf.object('value', value); + Check.defined('packArray', packArray); + //>>includeEnd('debug'); + + startingIndex = defaultValue(startingIndex, 0); + + Cartesian3.pack(value._startBottom, packArray, startingIndex); + startingIndex += Cartesian3.packedLength; + Cartesian3.pack(value._endBottom, packArray, startingIndex); + startingIndex += Cartesian3.packedLength; + Cartesian3.pack(value._startTop, packArray, startingIndex); + startingIndex += Cartesian3.packedLength; + Cartesian3.pack(value._endTop, packArray, startingIndex); + startingIndex += Cartesian3.packedLength; + Cartesian3.pack(value._startNormal, packArray, startingIndex); + startingIndex += Cartesian3.packedLength; + Cartesian3.pack(value._endNormal, packArray, startingIndex); + + return packArray; + } + + /** + * Retrieves an instance from a packed packArray. + * + * @param {Number[]} packArray The packed packArray. + * @param {Number} [startingIndex=0] The starting index of the element to be unpacked. + * @param {GroundLineGeometry} [result] The object into which to store the result. + * @returns {GroundLineGeometry} The modified result parameter or a new RectangleGeometry instance if one was not provided. + */ + GroundLineGeometry.unpack = function(packArray, startingIndex, result) { + //>>includeStart('debug', pragmas.debug); + Check.defined('packArray', packArray); + //>>includeEnd('debug'); + + startingIndex = defaultValue(startingIndex, 0); + result = defaultValue(result, new GroundLineGeometry()); + + Cartesian3.unpack(packArray, startingIndex, result._startBottom); + startingIndex += Cartesian3.packedLength; + Cartesian3.unpack(packArray, startingIndex, result._endBottom); + startingIndex += Cartesian3.packedLength; + Cartesian3.unpack(packArray, startingIndex, result._startTop); + startingIndex += Cartesian3.packedLength; + Cartesian3.unpack(packArray, startingIndex, result._endTop); + startingIndex += Cartesian3.packedLength; + Cartesian3.unpack(packArray, startingIndex, result._startNormal); + startingIndex += Cartesian3.packedLength; + Cartesian3.unpack(packArray, startingIndex, result._endNormal); + + return result; + } + + var startNormalLeftScratch = new Cartesian3(); + var endNormalLeftScratch = new Cartesian3(); + /** + * + * @param {GroundLineGeometry} groundPolylineSegmentGeometry + */ + GroundLineGeometry.createGeometry = function(groundPolylineSegmentGeometry) { + var startBottom = groundPolylineSegmentGeometry._startBottom; + var endBottom = groundPolylineSegmentGeometry._endBottom; + var endTop = groundPolylineSegmentGeometry._endTop; + var startTop = groundPolylineSegmentGeometry._startTop; + var startNormal = groundPolylineSegmentGeometry._startNormal; + var endNormal = groundPolylineSegmentGeometry._endNormal; + + var positions = new Float64Array(24); // 8 vertices + var normals = new Float32Array(24); + + Cartesian3.pack(startBottom, positions, 0); + Cartesian3.pack(endBottom, positions, 1 * 3); + Cartesian3.pack(endTop, positions, 2 * 3); + Cartesian3.pack(startTop, positions, 3 * 3); + + Cartesian3.pack(startBottom, positions, 4 * 3); + Cartesian3.pack(endBottom, positions, 5 * 3); + Cartesian3.pack(endTop, positions, 6 * 3); + Cartesian3.pack(startTop, positions, 7 * 3); + + Cartesian3.pack(startNormal, normals, 0); + Cartesian3.pack(endNormal, normals, 1 * 3); + Cartesian3.pack(endNormal, normals, 2 * 3); + Cartesian3.pack(startNormal, normals, 3 * 3); + + startNormal = Cartesian3.multiplyByScalar(startNormal, -1.0, startNormalLeftScratch); + endNormal = Cartesian3.multiplyByScalar(endNormal, -1.0, endNormalLeftScratch); + Cartesian3.pack(startNormal, normals, 4 * 3); + Cartesian3.pack(endNormal, normals, 5 * 3); + Cartesian3.pack(endNormal, normals, 6 * 3); + Cartesian3.pack(startNormal, normals, 7 * 3); + + var indices = [ + 0, 1, 2, 0, 2, 3, // right + 0, 3, 7, 0, 7, 4, // start + 0, 4, 5, 0, 5, 1, // bottom + 5, 4, 7, 5, 7, 6, // left + 5, 6, 2, 5, 2, 1, // end + 3, 2, 6, 3, 6, 7 // top + ]; + var geometryAttributes = new GeometryAttributes({ + position : new GeometryAttribute({ + componentDatatype : ComponentDatatype.DOUBLE, + componentsPerAttribute : 3, + normalize : false, + values : positions + }), + normal : new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 3, + normalize : true, + values : normals + }) + }); + + return new Geometry({ + attributes : geometryAttributes, + indices : new Uint16Array(indices), + boundingSphere : BoundingSphere.fromPoints([startBottom, endBottom, endTop, startTop]) + }); + } + + GroundLineGeometry.prototype.segmentLength = function() { + return Cartesian3.distance(this._startBottom, this._endBottom); + } + + var encodeScratch = new EncodedCartesian3(); + var offsetScratch = new Cartesian3(); + var offsetScratch = new Cartesian3(); + var normal1Scratch = new Cartesian3(); + var normal2Scratch = new Cartesian3(); + var rightScratch = new Cartesian3(); + /** + * Gets GeometrtyInstanceAttributes for culling fragments that aren't part of the line and + * for computing texture coordinates along the line, enabling material support. + * + * Computing whether or not a fragment is part of the line requires: + * - plane at the beginning of the segment (rotated for miter) + * - plane at the end of the segment (rotated for miter) + * - right plane for the segment + * + * We encode the planes as normals, the start position in high precision, and an offset to the end position. + * This also gets us planes normal to the line direction that can be used for computing the linear + * texture coordinate local to the line. This texture coordinate then needs to be mapped to the entire line, + * which requires an additional set of attributes. + * + * TODO: Attributes for 2D + * + * @param {GroundLineGeometry} geometry GroundLineGeometry + * @param {Number} lengthSoFar Distance of the segment's start point along the line + * @param {Number} segmentLength Length of the segment + * @param {Number} totalLength Total length of the entire line + * @returns {Object} An object containing GeometryInstanceAttributes for the input geometry + */ + GroundLineGeometry.getAttributes = function(geometry, lengthSoFar, segmentLength, totalLength) { + //>>includeStart('debug', pragmas.debug); + Check.typeOf.object('geometry', geometry); + Check.typeOf.number('lengthSoFar', lengthSoFar); + Check.typeOf.number('segmentLength', segmentLength); + Check.typeOf.number('totalLength', totalLength); + //>>includeEnd('debug'); + + // Unpack values from geometry + var startBottom = geometry._startBottom; + var endBottom = geometry._endBottom; + var endTop = geometry._endTop; + var startTop = geometry._startTop; + + var startCartesianRightNormal = geometry._startNormal; + var endCartesianRightNormal = geometry._endNormal; + + // Encode start position and end position as high precision point + offset + var encodedStart = EncodedCartesian3.fromCartesian(startBottom, encodeScratch); + + var forwardOffset = Cartesian3.subtract(endBottom, startBottom, offsetScratch); + + var startHi_and_forwardOffsetX_Attribute = new GeometryInstanceAttribute({ + componentDatatype: ComponentDatatype.FLOAT, + componentsPerAttribute: 4, + normalize: false, + value : Cartesian3.pack(encodedStart.high, [0, 0, 0, forwardOffset.x]) + }); + + var startLo_and_forwardOffsetY_Attribute = new GeometryInstanceAttribute({ + componentDatatype: ComponentDatatype.FLOAT, + componentsPerAttribute: 4, + normalize: false, + value : Cartesian3.pack(encodedStart.low, [0, 0, 0, forwardOffset.y]) + }); + + var packArray = [0, 0, 0, forwardOffset.z]; + var forward = Cartesian3.normalize(forwardOffset, forwardOffset); + + // Right vector is computed as cross of startTop - startBottom and direction to segment end end point + var startUp = Cartesian3.subtract(startTop, startBottom, normal1Scratch); + startUp = Cartesian3.normalize(startUp, startUp); + + var right = Cartesian3.cross(forward, startUp, rightScratch); + right = Cartesian3.normalize(right, right); + + var rightNormal_attribute = new GeometryInstanceAttribute({ + componentDatatype: ComponentDatatype.FLOAT, + componentsPerAttribute: 3, + normalize: false, + value : Cartesian3.pack(right, [0, 0, 0]) + }); + + // Normal planes need to miter, so cross startTop - startBottom with geometry normal at start + var startNormal = Cartesian3.cross(startCartesianRightNormal, startUp, normal1Scratch); + startNormal = Cartesian3.normalize(startNormal, startNormal); + + var startNormal_and_forwardOffsetZ_attribute = new GeometryInstanceAttribute({ + componentDatatype: ComponentDatatype.FLOAT, + componentsPerAttribute: 4, + normalize: false, + value : Cartesian3.pack(startNormal, packArray) + }); + + var endUp = Cartesian3.subtract(endTop, endBottom, normal2Scratch); + endUp = Cartesian3.normalize(endUp, endUp); + var endNormal = Cartesian3.cross(endUp, endCartesianRightNormal, normal2Scratch); + endNormal = Cartesian3.normalize(endNormal, endNormal); + + var endNormal_attribute = new GeometryInstanceAttribute({ + componentDatatype: ComponentDatatype.FLOAT, + componentsPerAttribute: 3, + normalize: false, + value : Cartesian3.pack(endNormal, [0, 0, 0]) + }); + + // Texture coordinate localization parameters + var texcoordNormalization_attribute = new GeometryInstanceAttribute({ + componentDatatype: ComponentDatatype.FLOAT, + componentsPerAttribute: 3, + normalize: false, + value : [lengthSoFar, segmentLength, totalLength] + }); + + return { + startHi_and_forwardOffsetX : startHi_and_forwardOffsetX_Attribute, + startLo_and_forwardOffsetY : startLo_and_forwardOffsetY_Attribute, + startNormal_and_forwardOffsetZ : startNormal_and_forwardOffsetZ_attribute, + endNormal : endNormal_attribute, + rightNormal : rightNormal_attribute, + texcoordNormalization : texcoordNormalization_attribute + }; + }; + + return GroundLineGeometry; +}); diff --git a/Source/Core/GroundPolylineGeometry.js b/Source/Core/GroundPolylineGeometry.js new file mode 100644 index 000000000000..983b7e16e929 --- /dev/null +++ b/Source/Core/GroundPolylineGeometry.js @@ -0,0 +1,299 @@ +define([ + './Cartesian3', + './Cartographic', + './Check', + './Math', + './defaultValue', + './defined', + './defineProperties', + './Ellipsoid', + './EncodedCartesian3', + './Matrix3', + './Plane', + './Quaternion' + ], function( + Cartesian3, + Cartographic, + Check, + CesiumMath, + defaultValue, + defined, + defineProperties, + Ellipsoid, + EncodedCartesian3, + Matrix3, + Plane, + Quaternion) { + 'use strict'; + + /** + * A description of a polyline on terrain. Only to be used with GroundPolylinePrimitive. + * + * @alias GroundPolylineGeometry + * @constructor + * + * @param {Object} [options] Options with the following properties: + * @param {Cartographic[]} [options.positions] An array of {@link Cartographic} defining the polyline's points. Heights will be ignored. + * @param {Number} [options.granularity=CesiumMath.RADIANS_PER_DEGREE] The distance interval used for interpolating options.points. Zero indicates no interpolation. + * @param {Boolean} [options.loop=false] Whether during geometry creation a line segment will be added between the last and first line positions to make this Polyline a loop. + * @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] Ellipsoid for projecting cartographic coordinates to cartesian + * @param {Number} [options.width=1.0] Integer width for the polyline. + * + * @see GroundPolylinePrimitive + */ + function GroundPolylineGeometry(options) { + options = defaultValue(options, defaultValue.EMPTY_OBJECT); + + var granularity = defaultValue(options.granularity, CesiumMath.RADIANS_PER_DEGREE); + + this._positions = interpolatePoints(defaultValue(options.positions, []), granularity); + + /** + * The distance interval used for interpolating options.points. Zero indicates no interpolation. + * @type {Boolean} + */ + this.granularity = granularity; + + /** + * Whether during geometry creation a line segment will be added between the last and first line positions to make this Polyline a loop. + * @type {Boolean} + */ + this.loop = defaultValue(options.loop, false); + + this.ellipsoid = defaultValue(options.ellipsoid, Ellipsoid.WGS84); + + this._lengthOnEllipsoid = undefined; + + this.width = defaultValue(options.width, 1.0); + } + + function interpolatePoints(positions, granularity) { + var interpolatedPositions = []; + + // TODO: actually interpolate + return positions; + } + + var heightlessCartographicScratch = new Cartographic(); + function getPosition(ellipsoid, cartographic, height, result) { + Cartographic.clone(cartographic, heightlessCartographicScratch); + heightlessCartographicScratch.height = height; + return Cartographic.toCartesian(heightlessCartographicScratch, ellipsoid, result); + } + + defineProperties(GroundPolylineGeometry.prototype, { + /** + * Gets the interpolated Cartographic positions describing the Ground Polyline + * @memberof GroundPolylineGeometry.prototype + * @type {Cartographic[]} + */ + positions: { + get: function() { + return this._positions; + }, + // TODO: doc, interpolation, etc. + set: function(value) { + this._positions = defaultValue(value, []); + } + }, + lengthOnEllipsoid : { + get : function() { + if (!defined(this._lengthOnEllipsoid)) { + this._lengthOnEllipsoid = computeLengthOnEllipsoid(this._positions, this.loop, this.ellipsoid); + } + return this._lengthOnEllipsoid; + } + } + }); + + var colinearCartographicScratch = new Cartographic(); + var previousBottomScratch = new Cartesian3(); + var vertexBottomScratch = new Cartesian3(); + var vertexTopScratch = new Cartesian3(); + var nextBottomScratch = new Cartesian3(); + var vertexNormalScratch = new Cartesian3(); + GroundPolylineGeometry.createWallVertices = function(groundPolylineGeometry, wallHeight) { + var cartographics = groundPolylineGeometry.positions; + var loop = groundPolylineGeometry.loop; + var ellipsoid = groundPolylineGeometry.ellipsoid; + + // TODO: throw errors/negate loop if not enough points + + var cartographicsLength = cartographics.length; + var index; + var i; + + var floatCount = (cartographicsLength + (loop ? 1 : 0)) * 3; + var normalsArray = new Float32Array(floatCount); + var bottomPositionsArray = new Float64Array(floatCount); + var topPositionsArray = new Float64Array(floatCount); + + var previousBottom = previousBottomScratch; + var vertexBottom = vertexBottomScratch; + var vertexTop = vertexTopScratch; + var nextBottom = nextBottomScratch; + var vertexNormal = vertexNormalScratch; + + // First point - generate fake "previous" position for computing normal + var startCartographic = cartographics[0]; + var nextCartographic = cartographics[1]; + var prestartCartographic; + if (loop) { + prestartCartographic = cartographics[cartographicsLength - 1]; + } else { + prestartCartographic = colinearCartographicScratch; + prestartCartographic.longitude = startCartographic.longitude - (nextCartographic.longitude - startCartographic.longitude); + prestartCartographic.latitude = startCartographic.latitude - (nextCartographic.latitude - startCartographic.latitude); + } + + getPosition(ellipsoid, prestartCartographic, 0.0, previousBottom); + getPosition(ellipsoid, startCartographic, 0.0, vertexBottom); + getPosition(ellipsoid, startCartographic, wallHeight, vertexTop); + getPosition(ellipsoid, nextCartographic, 0.0, nextBottom); + computeVertexMiterNormal(previousBottom, vertexBottom, vertexTop, nextBottom, vertexNormal); + + Cartesian3.pack(vertexNormal, normalsArray, 0); + Cartesian3.pack(vertexBottom, bottomPositionsArray, 0); + Cartesian3.pack(vertexTop, topPositionsArray, 0); + + // All inbetween points + for (i = 1; i < cartographicsLength - 1; ++i) { + previousBottom = Cartesian3.clone(vertexBottom, previousBottom); + vertexBottom = Cartesian3.clone(nextBottom, vertexBottom); + getPosition(ellipsoid, cartographics[i], wallHeight, vertexTop); + getPosition(ellipsoid, cartographics[i + 1], 0.0, nextBottom); + + computeVertexMiterNormal(previousBottom, vertexBottom, vertexTop, nextBottom, vertexNormal); + + index = i * 3; + Cartesian3.pack(vertexNormal, normalsArray, index); + Cartesian3.pack(vertexBottom, bottomPositionsArray, index); + Cartesian3.pack(vertexTop, topPositionsArray, index); + } + + // Last point - generate fake "next" position for computing normal + var endCartographic = cartographics[cartographicsLength - 1]; + var preEndCartographic = cartographics[cartographicsLength - 2]; + + var postEndCartographic; + if (loop) { + postEndCartographic = cartographics[0]; + } else { + postEndCartographic = colinearCartographicScratch; + postEndCartographic.longitude = endCartographic.longitude + (endCartographic.longitude - preEndCartographic.longitude); + postEndCartographic.latitude = endCartographic.latitude + (endCartographic.latitude - preEndCartographic.latitude); + } + + getPosition(ellipsoid, preEndCartographic, 0.0, previousBottom); + getPosition(ellipsoid, endCartographic, 0.0, vertexBottom); + getPosition(ellipsoid, endCartographic, wallHeight, vertexTop); + getPosition(ellipsoid, postEndCartographic, 0.0, nextBottom); + computeVertexMiterNormal(previousBottom, vertexBottom, vertexTop, nextBottom, vertexNormal); + + index = (cartographicsLength - 1) * 3; + Cartesian3.pack(vertexNormal, normalsArray, index); + Cartesian3.pack(vertexBottom, bottomPositionsArray, index); + Cartesian3.pack(vertexTop, topPositionsArray, index); + + if (loop) { + index = cartographicsLength * 3; + // Copy the first vertex + for (i = 0; i < 3; ++i) { + normalsArray[index + i] = normalsArray[i]; + bottomPositionsArray[index + i] = bottomPositionsArray[i]; + topPositionsArray[index + i] = topPositionsArray[i]; + } + } + + return { + rightFacingNormals : normalsArray, + bottomPositions : bottomPositionsArray, + topPositions : topPositionsArray + }; + }; + + var MIN_HEIGHT = 0.0; + var MAX_HEIGHT = 10000.0; + + function direction(target, origin, result) { + Cartesian3.subtract(target, origin, result); + Cartesian3.normalize(result, result); + return result; + } + + // inputs are cartesians + var vertexUpScratch = new Cartesian3(); + var toPreviousScratch = new Cartesian3(); + var toNextScratch = new Cartesian3(); + var forwardScratch = new Cartesian3(); + var coplanarNormalScratch = new Cartesian3(); + var coplanarPlaneScratch = new Plane(Cartesian3.UNIT_X, 0.0); + var cosine90 = 0.0; + function computeVertexMiterNormal(previousBottom, vertexBottom, vertexTop, nextBottom, result) { + // Convention: "next" is locally forward and we are computing a normal pointing towards the local right side of the vertices. + + var up = direction(vertexTop, vertexBottom, vertexUpScratch); + var toPrevious = direction(previousBottom, vertexBottom, toPreviousScratch); + var toNext = direction(nextBottom, vertexBottom, toNextScratch); + + // Check if points are coplanar in a right-side-pointing plane that contains "up." + // This is roughly equivalent to the points being colinear in cartographic space. + var coplanarNormal = Cartesian3.cross(up, toPrevious, coplanarNormalScratch); + coplanarNormal = Cartesian3.normalize(coplanarNormal, coplanarNormal); + var coplanarPlane = Plane.fromPointNormal(vertexBottom, coplanarNormal, coplanarPlaneScratch); + var nextBottomDistance = Plane.getPointDistance(coplanarPlane, nextBottom); + if (CesiumMath.equalsEpsilon(nextBottomDistance, 0.0, CesiumMath.EPSILON7)) { + // If the points are coplanar, point the normal in the direction of the plane + Cartesian3.clone(coplanarNormal, result); + return result; + } + + // Average directions to previous and to next + result = Cartesian3.add(toNext, toPrevious, result); + result = Cartesian3.multiplyByScalar(result, 0.5, result); + result = Cartesian3.normalize(result, result); + + // Rotate this direction to be orthogonal to up + var forward = Cartesian3.cross(up, result, forwardScratch); + Cartesian3.normalize(forward, forward); + Cartesian3.cross(forward, up, result); + Cartesian3.normalize(result, result); + + // Flip the normal if it isn't pointing roughly bound right (aka if forward is pointing more "backwards") + if (Cartesian3.dot(toNext, forward) < cosine90) { + result = Cartesian3.multiplyByScalar(result, -1.0, result); + } + + return result; + } + + var segmentStartScratch = new Cartesian3(); + var segmentEndScratch = new Cartesian3(); + function computeLengthOnEllipsoid(cartographicPositions, loop, ellipsoid) { + var cartographicsLength = cartographicPositions.length; + + var segmentStart = segmentStartScratch; + var segmentEnd = segmentEndScratch; + getPosition(ellipsoid, cartographicPositions[0], 0.0, segmentStart); + getPosition(ellipsoid, cartographicPositions[1], 0.0, segmentEnd); + + var length = Cartesian3.distance(segmentStart, segmentEnd); + + for (var i = 1; i < cartographicsLength - 1; i++) { + segmentStart = Cartesian3.clone(segmentEnd, segmentStart); + getPosition(ellipsoid, cartographicPositions[i + 1], 0.0, segmentEnd); + + length += Cartesian3.distance(segmentStart, segmentEnd); + } + + if (loop) { + getPosition(ellipsoid, cartographicPositions[cartographicsLength - 1], 0.0, segmentStart); + getPosition(ellipsoid, cartographicPositions[0], 0.0, segmentEnd); + + length += Cartesian3.distance(segmentStart, segmentEnd); + } + return length; + } + + return GroundPolylineGeometry; +}); diff --git a/Source/Scene/GroundPolylinePrimitive.js b/Source/Scene/GroundPolylinePrimitive.js new file mode 100644 index 000000000000..ae23c265cada --- /dev/null +++ b/Source/Scene/GroundPolylinePrimitive.js @@ -0,0 +1,264 @@ +define([ + '../Core/Check', + '../Core/ComponentDatatype', + '../Core/defaultValue', + '../Core/defined', + '../Core/defineProperties', + '../Core/GeometryInstance', + '../Core/GeometryInstanceAttribute', + '../Core/GroundPolylineGeometry', + '../Core/GroundLineGeometry', + '../Core/isArray', + '../Shaders/PolylineShadowVolumeVS', + '../Shaders/PolylineShadowVolumeFS', + '../Renderer/RenderState', + './GroundPrimitive', + './Material', + './MaterialAppearance', + './Primitive' + ], function( + Check, + ComponentDatatype, + defaultValue, + defined, + defineProperties, + GeometryInstance, + GeometryInstanceAttribute, + GroundPolylineGeometry, + GroundLineGeometry, + isArray, + PolylineShadowVolumeVS, + PolylineShadowVolumeFS, + RenderState, + GroundPrimitive, + Material, + MaterialAppearance, + Primitive) { + 'use strict'; + + /** + * A GroundPolylinePrimitive represents a polyline draped over the terrain in the {@link Scene}. + *

+ * + * Only to be used with GeometryInstances containing GroundPolylineGeometries + * + * @param {Object} [options] Object with the following properties: + * @param {GeometryInstance[]|GeometryInstance} [options.polylineGeometryInstances] GeometryInstances containing GroundPolylineGeometry + * @param {Material} [options.material] The Material used to render the polyline. Defaults to a white color. + * @param {Boolean} [options.show=true] Determines if this primitive will be shown. + * @param {Boolean} [options.releaseGeometryInstances=true] When true, the primitive does not keep a reference to generated geometry or input cartographics to save memory. + * @param {Boolean} [options.allowPicking=true] When true, each geometry instance will only be pickable with {@link Scene#pick}. When false, GPU memory is saved. + * @param {Boolean} [options.asynchronous=true] Determines if the primitive will be created asynchronously or block until ready. If false GroundPrimitive.initializeTerrainHeights() must be called first. + * @param {Boolean} [options.debugShowBoundingVolume=false] For debugging only. Determines if this primitive's commands' bounding spheres are shown. + * @param {Boolean} [options.debugShowShadowVolume=false] For debugging only. Determines if the shadow volume for each geometry in the primitive is drawn. Must be true on + * creation for the volumes to be created before the geometry is released or options.releaseGeometryInstance must be false. + * + */ + function GroundPolylinePrimitive(options) { + options = defaultValue(options, defaultValue.EMPTY_OBJECT); + + this.polylineGeometryInstances = options.polylineGeometryInstances; + + var material = options.material; + if (!defined(material)) { + material = Material.fromType('Color'); + } + + this._material = material; + this._appearance = generatePolylineAppearance(material); + + this.show = defaultValue(options.show, true); + + this.releaseGeometryInstances = defaultValue(options.releaseGeometryInstances, true); + + this.asynchronous = defaultValue(options.asynchronous, true); + + /** + * TODO: other stuff, like debugging thingies + */ + + this._primitiveOptions = { + geometryInstances : undefined, + appearance : undefined, + releaseGeometryInstances : this.releaseGeometryInstances, + asynchronous : this.asynchronous, + fragmentLogDepth : true + }; + + this._primitive = undefined; + this._ready = false; + + this._maxTerrainHeight = GroundPrimitive._defaultMaxTerrainHeight; + this._minTerrainHeight = GroundPrimitive._defaultMinTerrainHeight; + + // Map for synchronizing geometry instance attributes between polylines and line segments + this._idsToInstanceIndices = {}; + } + + function getColorRenderState() { + return { + depthTest : { + enabled : false // Helps prevent problems when viewing very closely + } + }; + } + + function generatePolylineAppearance(material) { + return new MaterialAppearance({ + flat : true, + translucent : true, + closed : false, + materialSupport : MaterialAppearance.MaterialSupport.BASIC, + vertexShaderSource : PolylineShadowVolumeVS, + fragmentShaderSource : PolylineShadowVolumeFS, + material : material, + renderState : RenderState.fromCache(getColorRenderState()) + }); + } + + defineProperties(GroundPolylinePrimitive.prototype, { + material : { + get : function() { + return this._material; + }, + set : function(value) { + this._material = value; + this._appearance = generatePolylineAppearance(value); + } + } + }); + + function decompose(geometryInstance, polylineSegmentInstances, idsToInstanceIndices) { + var groundPolylineGeometry = geometryInstance.geometry; + // TODO: check and throw using instanceof? + + var commonId = geometryInstance.id; + + var wallVertices = GroundPolylineGeometry.createWallVertices(groundPolylineGeometry, GroundPrimitive._defaultMaxTerrainHeight); + var rightFacingNormals = wallVertices.rightFacingNormals; + var bottomPositions = wallVertices.bottomPositions; + var topPositions = wallVertices.topPositions; + + var totalLength = groundPolylineGeometry.lengthOnEllipsoid; + var lengthSoFar = 0.0; + + var verticesLength = rightFacingNormals.length; + var segmentIndicesStart = polylineSegmentInstances.length; + for (var i = 0; i < verticesLength - 3; i += 3) { + var groundPolylineSegmentGeometry = GroundLineGeometry.fromArrays(i, rightFacingNormals, bottomPositions, topPositions); + var segmentLength = groundPolylineSegmentGeometry.segmentBottomLength; + var attributes = GroundLineGeometry.getAttributes(groundPolylineSegmentGeometry, lengthSoFar, segmentLength, totalLength); + lengthSoFar += segmentLength; + + attributes.width = new GeometryInstanceAttribute({ + componentDatatype: ComponentDatatype.UNSIGNED_BYTE, + componentsPerAttribute: 1, + normalize : false, + value : [groundPolylineGeometry.width] + }); + + polylineSegmentInstances.push(new GeometryInstance({ + geometry : groundPolylineSegmentGeometry, + attributes : attributes, + id : commonId + })); + } + idsToInstanceIndices[commonId] = [segmentIndicesStart, polylineSegmentInstances.length - 1]; + } + + GroundPolylinePrimitive.prototype.update = function(frameState) { + if (!defined(this._primitive) && !defined(this.polylineGeometryInstances)) { + return; + } + + /* + if (!GroundPrimitive._initialized) { + //>>includeStart('debug', pragmas.debug); + if (!this.asynchronous) { + throw new DeveloperError('For synchronous GroundPolylinePrimitives, you must call GroundPrimitive.initializeTerrainHeights() and wait for the returned promise to resolve.'); + } + //>>includeEnd('debug'); + + GroundPrimitive.initializeTerrainHeights(); + return; + }*/ + + var i; + + var that = this; + var primitiveOptions = this._primitiveOptions; + if (!defined(this._primitive)) { + // Decompose GeometryInstances into an array of GeometryInstances containing GroundPolylineSegmentGeometries. + // Compute the overall bounding volume + // TODO later: compute rectangle for getting min/max heights + var ellipsoid = frameState.mapProjection.ellipsoid; + + var polylineSegmentInstances = []; + var geometryInstances = isArray(this.polylineGeometryInstances) ? this.polylineGeometryInstances : [this.polylineGeometryInstances]; + var geometryInstancesLength = geometryInstances.length; + for (i = 0; i < geometryInstancesLength; ++i) { + var geometryInstance = geometryInstances[i]; + var id = geometryInstance.id; + + decompose(geometryInstance, polylineSegmentInstances, this._idsToInstanceIndices); + } + + primitiveOptions.geometryInstances = polylineSegmentInstances; + primitiveOptions.appearance = this._appearance; + + this._primitive = new Primitive(primitiveOptions); + this._primitive.readyPromise.then(function(primitive) { + that._ready = true; + + if (that.releaseGeometryInstances) { + that.polylineGeometryInstances = undefined; + } + + var error = primitive._error; + if (!defined(error)) { + that._readyPromise.resolve(that); + } else { + that._readyPromise.reject(error); + } + }); + } + this._primitive.appearance = this._appearance; + this._primitive.show = this.show; + this._primitive.update(frameState); + }; + + GroundPolylinePrimitive.prototype.isDestroyed = function() { + return false; + }; + + GroundPolylinePrimitive.prototype.destroy = function() { + this._primitive = this._primitive && this._primitive.destroy(); + return destroyObject(this); + }; + + GroundPolylinePrimitive.prototype.getGeometryInstanceAttributes = function(id) { + //>>includeStart('debug', pragmas.debug); + if (!defined(this._primitive)) { + throw new DeveloperError('must call update before calling getGeometryInstanceAttributes'); + } + //>>includeEnd('debug'); + + // All GeometryInstances generated by decomposing a GroundPolylineGeometry will have + // the same pick ID, so we have to map from their individual instance attributes to a + // master instance attribute and synchronize changes as they happen. + return this._primitive.getGeometryInstanceAttributes(id); + }; + + /** + * Checks if the given Scene supports GroundPolylinePrimitives. + * GroundPolylinePrimitives require support for the WEBGL_depth_texture extension. + * + * @param {Scene} scene The current scene. + * @returns {Boolean} Whether or not the current scene supports GroundPolylinePrimitives. + */ + GroundPolylinePrimitive.isSupported = function(scene) { + return scene.frameState.context.depthTexture; + }; + + return GroundPolylinePrimitive; +}); diff --git a/Source/Scene/Primitive.js b/Source/Scene/Primitive.js index 4336730fdc3e..34e3b2a9a7e4 100644 --- a/Source/Scene/Primitive.js +++ b/Source/Scene/Primitive.js @@ -119,6 +119,7 @@ define([ * @param {Boolean} [options.cull=true] When true, the renderer frustum culls and horizon culls the primitive's commands based on their bounding volume. Set this to false for a small performance gain if you are manually culling the primitive. * @param {Boolean} [options.asynchronous=true] Determines if the primitive will be created asynchronously or block until ready. * @param {Boolean} [options.debugShowBoundingVolume=false] For debugging only. Determines if this primitive's commands' bounding spheres are shown. + * @param {Boolean} [options.fragmentLogDepth=false] Set to true when using geometry with large triangles and {@link Scene#logarithmicDepthBuffer} is true. * @param {ShadowMode} [options.shadows=ShadowMode.DISABLED] Determines whether this primitive casts or receives shadows from each light source. * * @example @@ -351,6 +352,7 @@ define([ this._frontFaceRS = undefined; this._backFaceRS = undefined; this._sp = undefined; + this._fragmentLogDepth = defaultValue(options.fragmentLogDepth, false); this._depthFailAppearance = undefined; this._spDepthFail = undefined; @@ -510,6 +512,22 @@ define([ get : function() { return this._readyPromise.promise; } + }, + + /** + * When true, logarithmic depth is computed per-fragment instead of per vertex. + * + * @memberof Primitive.prototype + * + * @type {Boolean} + * @readonly + * + * @default false + */ + fragmentLogDepth : { + get : function() { + return this._fragmentLogDepth; + } } }); @@ -1428,6 +1446,8 @@ define([ vs = Primitive._modifyShaderPosition(primitive, vs, frameState.scene3DOnly); var fs = appearance.getFragmentShaderSource(); + var vertexShaderDefines = primitive.fragmentLogDepth ? ['ENABLE_GL_POSITION_LOG_DEPTH_AT_HEIGHT'] : []; + // Create pick program if (primitive.allowPicking) { var vsPick = ShaderSource.createPickVertexShaderSource(vs); @@ -1436,14 +1456,20 @@ define([ primitive._pickSP = ShaderProgram.replaceCache({ context : context, shaderProgram : primitive._pickSP, - vertexShaderSource : vsPick, + vertexShaderSource : new ShaderSource({ + sources : [vsPick], + defines : vertexShaderDefines + }), fragmentShaderSource : ShaderSource.createPickFragmentShaderSource(fs, 'varying'), attributeLocations : attributeLocations }); } else { primitive._pickSP = ShaderProgram.fromCache({ context : context, - vertexShaderSource : vs, + vertexShaderSource : new ShaderSource({ + sources : [vs], + defines : vertexShaderDefines + }), fragmentShaderSource : fs, attributeLocations : attributeLocations }); @@ -1453,7 +1479,10 @@ define([ primitive._sp = ShaderProgram.replaceCache({ context : context, shaderProgram : primitive._sp, - vertexShaderSource : vs, + vertexShaderSource : new ShaderSource({ + sources : [vs], + defines : vertexShaderDefines + }), fragmentShaderSource : fs, attributeLocations : attributeLocations }); @@ -1473,7 +1502,10 @@ define([ primitive._spDepthFail = ShaderProgram.replaceCache({ context : context, shaderProgram : primitive._spDepthFail, - vertexShaderSource : vs, + vertexShaderSource : new ShaderSource({ + sources : [vs], + defines : vertexShaderDefines + }), fragmentShaderSource : fs, attributeLocations : attributeLocations }); diff --git a/Source/Shaders/Builtin/Functions/approximateSphericalCoordinates.glsl b/Source/Shaders/Builtin/Functions/approximateSphericalCoordinates.glsl index 90fe79393a3d..78f52d54ff52 100644 --- a/Source/Shaders/Builtin/Functions/approximateSphericalCoordinates.glsl +++ b/Source/Shaders/Builtin/Functions/approximateSphericalCoordinates.glsl @@ -1,36 +1,3 @@ -// Based on Michal Drobot's approximation from ShaderFastLibs, which in turn is based on -// "Efficient approximations for the arctangent function," Rajan, S. Sichun Wang Inkol, R. Joyal, A., May 2006. -// Adapted from ShaderFastLibs under MIT License. -// -// Chosen for the following characteristics over range [0, 1]: -// - basically no error at 0 and 1, important for getting around range limit (naive atan2 via atan requires infinite range atan) -// - no visible artifacts from first-derivative discontinuities, unlike latitude via range-reduced sqrt asin approximations (at equator) -// -// The original code is x * (-0.1784 * abs(x) - 0.0663 * x * x + 1.0301); -// Removed the abs() in here because it isn't needed, the input range is guaranteed as [0, 1] by how we're approximating atan2. -float fastApproximateAtan01(float x) { - return x * (-0.1784 * x - 0.0663 * x * x + 1.0301); -} - -// Range reduction math based on nvidia's cg reference implementation for atan2: http://developer.download.nvidia.com/cg/atan2.html -// However, we replaced their atan curve with Michael Drobot's. -float fastApproximateAtan2(float x, float y) { - // atan approximations are usually only reliable over [-1, 1], or, in our case, [0, 1] due to modifications. - // So range-reduce using abs and by flipping whether x or y is on top. - float t = abs(x); // t used as swap and atan result. - float opposite = abs(y); - float adjacent = max(t, opposite); - opposite = min(t, opposite); - - t = fastApproximateAtan01(opposite / adjacent); - - // Undo range reduction - t = czm_branchFreeTernaryFloat(abs(y) > abs(x), czm_piOverTwo - t, t); - t = czm_branchFreeTernaryFloat(x < 0.0, czm_pi - t, t); - t = czm_branchFreeTernaryFloat(y < 0.0, -t, t); - return t; -} - /** * Approximately computes spherical coordinates given a normal. * Uses approximate inverse trigonometry for speed and consistency, @@ -45,7 +12,7 @@ float fastApproximateAtan2(float x, float y) { */ vec2 czm_approximateSphericalCoordinates(vec3 normal) { // Project into plane with vertical for latitude - float latitudeApproximation = fastApproximateAtan2(sqrt(normal.x * normal.x + normal.y * normal.y), normal.z); - float longitudeApproximation = fastApproximateAtan2(normal.x, normal.y); + float latitudeApproximation = czm_fastApproximateAtan(sqrt(normal.x * normal.x + normal.y * normal.y), normal.z); + float longitudeApproximation = czm_fastApproximateAtan(normal.x, normal.y); return vec2(latitudeApproximation, longitudeApproximation); } diff --git a/Source/Shaders/Builtin/Functions/fastApproximateAtan.glsl b/Source/Shaders/Builtin/Functions/fastApproximateAtan.glsl new file mode 100644 index 000000000000..56dd587019b5 --- /dev/null +++ b/Source/Shaders/Builtin/Functions/fastApproximateAtan.glsl @@ -0,0 +1,55 @@ +/** + * Approxiamtes atan over the range [0, 1]. Safe to flip output for negative input. + * + * Based on Michal Drobot's approximation from ShaderFastLibs, which in turn is based on + * "Efficient approximations for the arctangent function," Rajan, S. Sichun Wang Inkol, R. Joyal, A., May 2006. + * Adapted from ShaderFastLibs under MIT License. + * + * Chosen for the following characteristics over range [0, 1]: + * - basically no error at 0 and 1, important for getting around range limit (naive atan2 via atan requires infinite range atan) + * - no visible artifacts from first-derivative discontinuities, unlike latitude via range-reduced sqrt asin approximations (at equator) + * + * The original code is x * (-0.1784 * abs(x) - 0.0663 * x * x + 1.0301); + * Removed the abs() in here because it isn't needed, the input range is guaranteed as [0, 1] by how we're approximating atan2. + * + * @name czm_fastApproximateAtan + * @glslFunction + * + * @param {float} x Value between 0 and 1 inclusive. + * + * @returns {float} Approximation of atan(x) + */ +float czm_fastApproximateAtan(float x) { + return x * (-0.1784 * x - 0.0663 * x * x + 1.0301); +} + +/** + * Approximation of atan2. + * + * Range reduction math based on nvidia's cg reference implementation for atan2: http://developer.download.nvidia.com/cg/atan2.html + * However, we replaced their atan curve with Michael Drobot's (see above). + * + * @name czm_fastApproximateAtan + * @glslFunction + * + * @param {float} x Value between -1 and 1 inclusive. + * @param {float} y Value between -1 and 1 inclusive. + * + * @returns {float} Approximation of atan2(x, y) + */ +float czm_fastApproximateAtan(float x, float y) { + // atan approximations are usually only reliable over [-1, 1], or, in our case, [0, 1] due to modifications. + // So range-reduce using abs and by flipping whether x or y is on top. + float t = abs(x); // t used as swap and atan result. + float opposite = abs(y); + float adjacent = max(t, opposite); + opposite = min(t, opposite); + + t = czm_fastApproximateAtan(opposite / adjacent); + + // Undo range reduction + t = czm_branchFreeTernaryFloat(abs(y) > abs(x), czm_piOverTwo - t, t); + t = czm_branchFreeTernaryFloat(x < 0.0, czm_pi - t, t); + t = czm_branchFreeTernaryFloat(y < 0.0, -t, t); + return t; +} diff --git a/Source/Shaders/PolylineShadowVolumeFS.glsl b/Source/Shaders/PolylineShadowVolumeFS.glsl new file mode 100644 index 000000000000..52723788c536 --- /dev/null +++ b/Source/Shaders/PolylineShadowVolumeFS.glsl @@ -0,0 +1,64 @@ +#ifdef GL_EXT_frag_depth +#extension GL_EXT_frag_depth : enable +#endif + +varying vec4 v_startPlaneEC; +varying vec4 v_endPlaneEC; +varying vec4 v_rightPlaneEC; +varying vec3 v_forwardDirectionEC; +varying vec2 v_alignedPlaneDistances; +varying vec3 v_texcoordNormalization; +varying float v_halfWidth; + +float rayPlaneDistance(vec3 origin, vec3 direction, vec3 planeNormal, float planeDistance) { + // We don't expect the ray to ever be parallel to the plane + return (-planeDistance - dot(planeNormal, origin)) / dot(planeNormal, direction); +} + +void main(void) +{ + float logDepthOrDepth = czm_unpackDepth(texture2D(czm_globeDepthTexture, gl_FragCoord.xy / czm_viewport.zw)); + + // Discard for sky + if (logDepthOrDepth == 0.0) { + discard; + } + + vec4 eyeCoordinate = czm_windowToEyeCoordinates(gl_FragCoord.xy, logDepthOrDepth); + eyeCoordinate /= eyeCoordinate.w; + + float halfMaxWidth = v_halfWidth * czm_metersPerPixel(eyeCoordinate); + // Check distance of the eye coordinate against the right-facing plane + float width = czm_planeDistance(v_rightPlaneEC, eyeCoordinate.xyz); + + // Check distance of the eye coordinate against the forward-facing plane + float distanceFromStart = rayPlaneDistance(eyeCoordinate.xyz, -v_forwardDirectionEC, v_startPlaneEC.xyz, v_startPlaneEC.w); + float distanceFromEnd = rayPlaneDistance(eyeCoordinate.xyz, v_forwardDirectionEC, v_endPlaneEC.xyz, v_endPlaneEC.w); + + if (abs(width) > halfMaxWidth || distanceFromStart < 0.0 || distanceFromEnd < 0.0) { + discard; + } + + // Use distances for planes aligned with segment to prevent skew in dashing + distanceFromStart = rayPlaneDistance(eyeCoordinate.xyz, -v_forwardDirectionEC, v_forwardDirectionEC.xyz, v_alignedPlaneDistances.x); + distanceFromEnd = rayPlaneDistance(eyeCoordinate.xyz, v_forwardDirectionEC, -v_forwardDirectionEC.xyz, v_alignedPlaneDistances.y); + + // Clamp - distance to aligned planes may be negative due to mitering + distanceFromStart = max(0.0, distanceFromStart); + distanceFromEnd = max(0.0, distanceFromEnd); + + float s = distanceFromStart / (distanceFromStart + distanceFromEnd); + s = ((s * v_texcoordNormalization.y) + v_texcoordNormalization.x) / v_texcoordNormalization.z; + float t = (width + halfMaxWidth) / (2.0 * halfMaxWidth); + + czm_materialInput materialInput; + + materialInput.s = s; + materialInput.st = vec2(s, t); + materialInput.str = vec3(s, t, 0.0); + + czm_material material = czm_getMaterial(materialInput); + gl_FragColor = vec4(material.diffuse + material.emission, material.alpha); + + czm_writeDepthClampedToFarPlane(); +} diff --git a/Source/Shaders/PolylineShadowVolumeVS.glsl b/Source/Shaders/PolylineShadowVolumeVS.glsl new file mode 100644 index 000000000000..1c39e5cc90c2 --- /dev/null +++ b/Source/Shaders/PolylineShadowVolumeVS.glsl @@ -0,0 +1,72 @@ +attribute vec3 position3DHigh; +attribute vec3 position3DLow; +attribute float batchId; +attribute vec3 normal; + +varying vec4 v_startPlaneEC; +varying vec4 v_endPlaneEC; +varying vec4 v_rightPlaneEC; +varying vec3 v_forwardDirectionEC; +varying vec2 v_alignedPlaneDistances; +varying vec3 v_texcoordNormalization; +varying float v_halfWidth; +varying float v_width; // for materials +varying float v_polylineAngle; + +void main() +{ + vec4 entry1 = czm_batchTable_startHi_and_forwardOffsetX(batchId); + vec4 entry2 = czm_batchTable_startLo_and_forwardOffsetY(batchId); + + vec3 ecStart = (czm_modelViewRelativeToEye * czm_translateRelativeToEye(entry1.xyz, entry2.xyz)).xyz; + vec3 offset = vec3(entry1.w, entry2.w, 0.0); + + entry1 = czm_batchTable_startNormal_and_forwardOffsetZ(batchId); + + offset.z = entry1.w; + offset = czm_normal * offset; + vec3 ecEnd = ecStart + offset; + + vec3 forwardDirectionEC = normalize(offset); + v_forwardDirectionEC = forwardDirectionEC; + + // end plane + vec3 ecEndNormal = czm_normal * czm_batchTable_endNormal(batchId); + v_endPlaneEC.xyz = ecEndNormal; + v_endPlaneEC.w = -dot(ecEndNormal, ecEnd); + + // Right plane + vec3 ecRight = czm_normal * czm_batchTable_rightNormal(batchId); + v_rightPlaneEC.xyz = ecRight; + v_rightPlaneEC.w = -dot(ecRight, ecStart); + + // start plane + vec3 ecStartNormal = czm_normal * entry1.xyz; + v_startPlaneEC.xyz = ecStartNormal; + v_startPlaneEC.w = -dot(ecStartNormal, ecStart); + + v_alignedPlaneDistances.x = -dot(forwardDirectionEC, ecStart); + v_alignedPlaneDistances.y = -dot(-forwardDirectionEC, ecEnd); + + v_texcoordNormalization = czm_batchTable_texcoordNormalization(batchId); + + // Position stuff + vec4 positionRelativeToEye = czm_computePosition(); + + // A "perfect" implementation would push along normals according to the angle against forward. + // In practice, just extending the shadow volume a more than needed works for most cases, + // and for very sharp turns we compute attributes to "break" the miter anyway. + float width = czm_batchTable_width(batchId); + v_width = width; + v_halfWidth = width * 0.5; + positionRelativeToEye.xyz += width * 2.0 * czm_metersPerPixel(positionRelativeToEye) * normal; + gl_Position = czm_depthClampFarPlane(czm_modelViewProjectionRelativeToEye * positionRelativeToEye); + + // Approximate relative screen space direction of the line. + // This doesn't work great if the view direction is roughly aligned with the line + // Directly copying what PolylineCommon.glsl does using ecStart and ecEnd is even worse. + // Might be worth just having this point in some direction if it's almost zero? herm no good... should probably go read that math later. + vec2 approxLineDirection = normalize(vec2(forwardDirectionEC.x, -forwardDirectionEC.y)); + approxLineDirection.y = czm_branchFreeTernaryFloat(approxLineDirection.x == 0.0 && approxLineDirection.y == 0.0, -1.0, approxLineDirection.y); + v_polylineAngle = czm_fastApproximateAtan(approxLineDirection.x, approxLineDirection.y); +} diff --git a/Source/Workers/createGroundLineGeometry.js b/Source/Workers/createGroundLineGeometry.js new file mode 100644 index 000000000000..690835286619 --- /dev/null +++ b/Source/Workers/createGroundLineGeometry.js @@ -0,0 +1,17 @@ +define([ + '../Core/defined', + '../Core/GroundLineGeometry' + ], function( + defined, + GroundLineGeometry) { + 'use strict'; + + function createGroundLineGeometry(groundPolylineSegmentGeometry, offset) { + if (defined(offset)) { + groundPolylineSegmentGeometry = GroundLineGeometry.unpack(groundPolylineSegmentGeometry, offset); + } + return GroundLineGeometry.createGeometry(groundPolylineSegmentGeometry); + } + + return createGroundLineGeometry; +}); diff --git a/Specs/Renderer/BuiltinFunctionsSpec.js b/Specs/Renderer/BuiltinFunctionsSpec.js index b766adab0c8e..a96845a61456 100644 --- a/Specs/Renderer/BuiltinFunctionsSpec.js +++ b/Specs/Renderer/BuiltinFunctionsSpec.js @@ -2,6 +2,7 @@ defineSuite([ 'Core/BoundingRectangle', 'Core/Cartesian3', 'Core/Cartesian4', + 'Core/Math', 'Core/EncodedCartesian3', 'Specs/createCamera', 'Specs/createContext', @@ -10,6 +11,7 @@ defineSuite([ BoundingRectangle, Cartesian3, Cartesian4, + CesiumMath, EncodedCartesian3, createCamera, createContext, @@ -465,4 +467,25 @@ defineSuite([ fragmentShader : fs }).contextToRender(); }); + + it('has czm_fastApproximateAtan', function() { + var fsAtan = + 'void main() { ' + + ' gl_FragColor = vec4(czm_fastApproximateAtan(0.0) == 0.0);' + + '}'; + expect({ + context : context, + fragmentShader : fsAtan + }).contextToRender(); + + var fsAtan2 = + 'void main() { ' + + ' gl_FragColor = vec4(czm_fastApproximateAtan(1.0, 0.0) == 0.0);' + + '}'; + expect({ + context : context, + fragmentShader : fsAtan2 + }).contextToRender(); + }); + }, 'WebGL'); diff --git a/Specs/Scene/PrimitiveSpec.js b/Specs/Scene/PrimitiveSpec.js index d19b70f9e2df..d43a693cc899 100644 --- a/Specs/Scene/PrimitiveSpec.js +++ b/Specs/Scene/PrimitiveSpec.js @@ -166,6 +166,7 @@ defineSuite([ expect(primitive.cull).toEqual(true); expect(primitive.asynchronous).toEqual(true); expect(primitive.debugShowBoundingVolume).toEqual(false); + expect(primitive.fragmentLogDepth).toEqual(false); }); it('Constructs with options', function() { @@ -187,7 +188,8 @@ defineSuite([ allowPicking : false, cull : false, asynchronous : false, - debugShowBoundingVolume : true + debugShowBoundingVolume : true, + fragmentLogDepth : true }); expect(primitive.geometryInstances).toEqual(geometryInstances); @@ -203,6 +205,7 @@ defineSuite([ expect(primitive.cull).toEqual(false); expect(primitive.asynchronous).toEqual(false); expect(primitive.debugShowBoundingVolume).toEqual(true); + expect(primitive.fragmentLogDepth).toEqual(true); }); it('releases geometry instances when releaseGeometryInstances is true', function() { From cbd2eb7f686c7c3ab8a791a1b0f0a08a81d90c85 Mon Sep 17 00:00:00 2001 From: Kangning Li Date: Fri, 18 May 2018 16:24:34 -0400 Subject: [PATCH 02/39] synchronization of ground polyline attributes by ID --- Source/Scene/GroundPolylinePrimitive.js | 85 ++++++++++++++++++++++++- 1 file changed, 83 insertions(+), 2 deletions(-) diff --git a/Source/Scene/GroundPolylinePrimitive.js b/Source/Scene/GroundPolylinePrimitive.js index ae23c265cada..1277c5df19d4 100644 --- a/Source/Scene/GroundPolylinePrimitive.js +++ b/Source/Scene/GroundPolylinePrimitive.js @@ -93,6 +93,7 @@ define([ // Map for synchronizing geometry instance attributes between polylines and line segments this._idsToInstanceIndices = {}; + this._attributeSynchronizerCache = {}; } function getColorRenderState() { @@ -233,12 +234,81 @@ define([ GroundPolylinePrimitive.prototype.destroy = function() { this._primitive = this._primitive && this._primitive.destroy(); + + //These objects may be fairly large and reference other large objects (like Entities) + //We explicitly set them to undefined here so that the memory can be freed + //even if a reference to the destroyed GroundPolylinePrimitive has been kept around. + this._idsToInstanceIndices = undefined; + this._attributeSynchronizerCache = undefined; + return destroyObject(this); }; + // An object that, on setting an attribute, will set all the instances' attributes. + function InstanceAttributeSynchronizer(batchTable, firstInstanceIndex, lastInstanceIndex, batchTableAttributeIndices) { + var properties = {}; + for (var name in batchTableAttributeIndices) { + if (batchTableAttributeIndices.hasOwnProperty(name)) { + var attributeIndex = batchTableAttributeIndices[name]; + properties[name] = { + get : createGetFunction(batchTable, firstInstanceIndex, attributeIndex), + set : createSetFunction(batchTable, firstInstanceIndex, lastInstanceIndex, attributeIndex) + }; + + // TODO: make some of these read only + } + } + defineProperties(this, properties); + } + + function getAttributeValue(value) { + var componentsPerAttribute = value.length; + if (componentsPerAttribute === 1) { + return value[0]; + } else if (componentsPerAttribute === 2) { + return Cartesian2.unpack(value, 0, scratchGetAttributeCartesian2); + } else if (componentsPerAttribute === 3) { + return Cartesian3.unpack(value, 0, scratchGetAttributeCartesian3); + } else if (componentsPerAttribute === 4) { + return Cartesian4.unpack(value, 0, scratchGetAttributeCartesian4); + } + } + + function createSetFunction(batchTable, firstInstanceIndex, lastInstanceIndex, attributeIndex) { + return function(value) { + //>>includeStart('debug', pragmas.debug); + if (!defined(value) || !defined(value.length) || value.length < 1 || value.length > 4) { + throw new DeveloperError('value must be and array with length between 1 and 4.'); + } + //>>includeEnd('debug'); + for (var i = firstInstanceIndex; i <= lastInstanceIndex; i++) { + var attributeValue = getAttributeValue(value); + batchTable.setBatchedAttribute(i, attributeIndex, attributeValue); + } + }; + } + + function createGetFunction(batchTable, instanceIndex, attributeIndex) { + return function() { + var attributeValue = batchTable.getBatchedAttribute(instanceIndex, attributeIndex); + var attribute = batchTable.attributes[attributeIndex]; + var componentsPerAttribute = attribute.componentsPerAttribute; + var value = ComponentDatatype.createTypedArray(attribute.componentDatatype, componentsPerAttribute); + if (defined(attributeValue.constructor.pack)) { + attributeValue.constructor.pack(attributeValue, value, 0); + } else { + value[0] = attributeValue; + } + return value; + }; + } + GroundPolylinePrimitive.prototype.getGeometryInstanceAttributes = function(id) { //>>includeStart('debug', pragmas.debug); - if (!defined(this._primitive)) { + if (!defined(id)) { + throw new DeveloperError('id is required'); + } + if (!defined(this._primitive) || !defined(this._primitive._batchTable)) { throw new DeveloperError('must call update before calling getGeometryInstanceAttributes'); } //>>includeEnd('debug'); @@ -246,7 +316,18 @@ define([ // All GeometryInstances generated by decomposing a GroundPolylineGeometry will have // the same pick ID, so we have to map from their individual instance attributes to a // master instance attribute and synchronize changes as they happen. - return this._primitive.getGeometryInstanceAttributes(id); + + var instanceIndices = this._idsToInstanceIndices[id]; + if (!defined(instanceIndices)) { + return undefined; + } + var attributeSynchronizer = this._attributeSynchronizerCache[id]; + if (!defined(attributeSynchronizer)) { + attributeSynchronizer = new InstanceAttributeSynchronizer(this._primitive._batchTable, + instanceIndices[0], instanceIndices[1], this._primitive._batchTableAttributeIndices); + this._attributeSynchronizerCache[id] = attributeSynchronizer; + } + return attributeSynchronizer; }; /** From b2ac735d4e36d4ef136243d0c91d692b91b5528f Mon Sep 17 00:00:00 2001 From: Kangning Li Date: Mon, 21 May 2018 14:37:17 -0400 Subject: [PATCH 03/39] picking GroundPolylinePrimitive ignores material type (useful for dashed), enabled debugging false shadow volume --- Source/Core/GroundLineGeometry.js | 6 +- Source/Scene/GroundPolylinePrimitive.js | 373 +++++++++++++++++++-- Source/Scene/GroundPrimitive.js | 4 +- Source/Scene/Primitive.js | 40 +-- Source/Shaders/PolylineShadowVolumeFS.glsl | 17 +- Source/Shaders/PolylineShadowVolumeVS.glsl | 2 +- Specs/Scene/PrimitiveSpec.js | 5 +- 7 files changed, 376 insertions(+), 71 deletions(-) diff --git a/Source/Core/GroundLineGeometry.js b/Source/Core/GroundLineGeometry.js index 2fa35449c0d5..4752b9bd2a85 100644 --- a/Source/Core/GroundLineGeometry.js +++ b/Source/Core/GroundLineGeometry.js @@ -294,17 +294,17 @@ define([ * texture coordinate local to the line. This texture coordinate then needs to be mapped to the entire line, * which requires an additional set of attributes. * - * TODO: Attributes for 2D - * * @param {GroundLineGeometry} geometry GroundLineGeometry + * @param {MapProjection} projection The MapProjection used for 2D and Columbus View. * @param {Number} lengthSoFar Distance of the segment's start point along the line * @param {Number} segmentLength Length of the segment * @param {Number} totalLength Total length of the entire line * @returns {Object} An object containing GeometryInstanceAttributes for the input geometry */ - GroundLineGeometry.getAttributes = function(geometry, lengthSoFar, segmentLength, totalLength) { + GroundLineGeometry.getAttributes = function(geometry, projection, lengthSoFar, segmentLength, totalLength) { //>>includeStart('debug', pragmas.debug); Check.typeOf.object('geometry', geometry); + Check.typeOf.object('projection', projection); Check.typeOf.number('lengthSoFar', lengthSoFar); Check.typeOf.number('segmentLength', segmentLength); Check.typeOf.number('totalLength', totalLength); diff --git a/Source/Scene/GroundPolylinePrimitive.js b/Source/Scene/GroundPolylinePrimitive.js index 1277c5df19d4..cfab351551c5 100644 --- a/Source/Scene/GroundPolylinePrimitive.js +++ b/Source/Scene/GroundPolylinePrimitive.js @@ -4,36 +4,50 @@ define([ '../Core/defaultValue', '../Core/defined', '../Core/defineProperties', + '../Core/DeveloperError', '../Core/GeometryInstance', '../Core/GeometryInstanceAttribute', '../Core/GroundPolylineGeometry', '../Core/GroundLineGeometry', '../Core/isArray', + '../Core/Matrix4', '../Shaders/PolylineShadowVolumeVS', '../Shaders/PolylineShadowVolumeFS', + '../Renderer/DrawCommand', + '../Renderer/Pass', '../Renderer/RenderState', + '../Renderer/ShaderProgram', + '../Renderer/ShaderSource', './GroundPrimitive', './Material', './MaterialAppearance', - './Primitive' + './Primitive', + './SceneMode' ], function( Check, ComponentDatatype, defaultValue, defined, defineProperties, + DeveloperError, GeometryInstance, GeometryInstanceAttribute, GroundPolylineGeometry, GroundLineGeometry, isArray, + Matrix4, PolylineShadowVolumeVS, PolylineShadowVolumeFS, + DrawCommand, + Pass, RenderState, + ShaderProgram, + ShaderSource, GroundPrimitive, Material, MaterialAppearance, - Primitive) { + Primitive, + SceneMode) { 'use strict'; /** @@ -50,8 +64,7 @@ define([ * @param {Boolean} [options.allowPicking=true] When true, each geometry instance will only be pickable with {@link Scene#pick}. When false, GPU memory is saved. * @param {Boolean} [options.asynchronous=true] Determines if the primitive will be created asynchronously or block until ready. If false GroundPrimitive.initializeTerrainHeights() must be called first. * @param {Boolean} [options.debugShowBoundingVolume=false] For debugging only. Determines if this primitive's commands' bounding spheres are shown. - * @param {Boolean} [options.debugShowShadowVolume=false] For debugging only. Determines if the shadow volume for each geometry in the primitive is drawn. Must be true on - * creation for the volumes to be created before the geometry is released or options.releaseGeometryInstance must be false. + * @param {Boolean} [options.debugShowShadowVolume=false] For debugging only. Determines if the shadow volume for each geometry in the primitive is drawn. * */ function GroundPolylinePrimitive(options) { @@ -73,16 +86,40 @@ define([ this.asynchronous = defaultValue(options.asynchronous, true); + this.allowPicking = defaultValue(options.allowPicking, true); + + /** + * This property is for debugging only; it is not for production use nor is it optimized. + *

+ * Draws the bounding sphere for each draw command in the primitive. + *

+ * + * @type {Boolean} + * + * @default false + */ + this.debugShowBoundingVolume = defaultValue(options.debugShowBoundingVolume, false); + /** - * TODO: other stuff, like debugging thingies + * This property is for debugging only; it is not for production use nor is it optimized. + *

+ * Draws the shadow volume for each geometry in the primitive. + *

+ * TODO: make this read only, set-on-construct + * @type {Boolean} + * + * @default false */ + this.debugShowShadowVolume = defaultValue(options.debugShowShadowVolume, false); this._primitiveOptions = { - geometryInstances : undefined, + geometryInstances : undefined, // TODO: addtl params appearance : undefined, releaseGeometryInstances : this.releaseGeometryInstances, asynchronous : this.asynchronous, - fragmentLogDepth : true + _createShaderProgramFunction : undefined, + _createCommandsFunction : undefined, + _updateAndQueueCommandsFunction : undefined, }; this._primitive = undefined; @@ -91,20 +128,22 @@ define([ this._maxTerrainHeight = GroundPrimitive._defaultMaxTerrainHeight; this._minTerrainHeight = GroundPrimitive._defaultMinTerrainHeight; - // Map for synchronizing geometry instance attributes between polylines and line segments - this._idsToInstanceIndices = {}; - this._attributeSynchronizerCache = {}; - } - - function getColorRenderState() { - return { + this._sp = undefined; + this._spPick = undefined; + this._sp2D = undefined; + this._spPick2D = undefined; + this._renderState = RenderState.fromCache({ depthTest : { enabled : false // Helps prevent problems when viewing very closely } - }; + }); + + // Map for synchronizing geometry instance attributes between polylines and line segments + this._idsToInstanceIndices = {}; + this._attributeSynchronizerCache = {}; } - function generatePolylineAppearance(material) { + function generatePolylineAppearance(material, renderState) { return new MaterialAppearance({ flat : true, translucent : true, @@ -113,7 +152,7 @@ define([ vertexShaderSource : PolylineShadowVolumeVS, fragmentShaderSource : PolylineShadowVolumeFS, material : material, - renderState : RenderState.fromCache(getColorRenderState()) + renderState : renderState }); } @@ -124,12 +163,12 @@ define([ }, set : function(value) { this._material = value; - this._appearance = generatePolylineAppearance(value); + this._appearance = generatePolylineAppearance(value, this._renderState); } } }); - function decompose(geometryInstance, polylineSegmentInstances, idsToInstanceIndices) { + function decompose(geometryInstance, projection, polylineSegmentInstances, idsToInstanceIndices) { var groundPolylineGeometry = geometryInstance.geometry; // TODO: check and throw using instanceof? @@ -148,7 +187,7 @@ define([ for (var i = 0; i < verticesLength - 3; i += 3) { var groundPolylineSegmentGeometry = GroundLineGeometry.fromArrays(i, rightFacingNormals, bottomPositions, topPositions); var segmentLength = groundPolylineSegmentGeometry.segmentBottomLength; - var attributes = GroundLineGeometry.getAttributes(groundPolylineSegmentGeometry, lengthSoFar, segmentLength, totalLength); + var attributes = GroundLineGeometry.getAttributes(groundPolylineSegmentGeometry, projection, lengthSoFar, segmentLength, totalLength); lengthSoFar += segmentLength; attributes.width = new GeometryInstanceAttribute({ @@ -167,6 +206,281 @@ define([ idsToInstanceIndices[commonId] = [segmentIndicesStart, polylineSegmentInstances.length - 1]; } + // TODO: remove + function validateShaderMatching(shaderProgram, attributeLocations) { + // For a VAO and shader program to be compatible, the VAO must have + // all active attribute in the shader program. The VAO may have + // extra attributes with the only concern being a potential + // performance hit due to extra memory bandwidth and cache pollution. + // The shader source could have extra attributes that are not used, + // but there is no guarantee they will be optimized out. + // + // Here, we validate that the VAO has all attributes required + // to match the shader program. + var shaderAttributes = shaderProgram.vertexAttributes; + + //>>includeStart('debug', pragmas.debug); + for (var name in shaderAttributes) { + if (shaderAttributes.hasOwnProperty(name)) { + if (!defined(attributeLocations[name])) { + throw new DeveloperError('Appearance/Geometry mismatch. The appearance requires vertex shader attribute input \'' + name + + '\', which was not computed as part of the Geometry. Use the appearance\'s vertexFormat property when constructing the geometry.'); + } + } + } + //>>includeEnd('debug'); + } + + function modifyForEncodedNormals(compressVertices, vertexShaderSource) { + if (!compressVertices) { + return vertexShaderSource; + } + + var attributeName = 'compressedAttributes'; + var attributeDecl = 'attribute float ' + attributeName + ';'; + + var globalDecl = 'vec3 normal;\n'; + var decode = ' normal = czm_octDecode(' + attributeName + ');\n'; + + var modifiedVS = vertexShaderSource; + modifiedVS = modifiedVS.replace(/attribute\s+vec3\s+normal;/g, ''); + modifiedVS = ShaderSource.replaceMain(modifiedVS, 'czm_non_compressed_main'); + var compressedMain = + 'void main() \n' + + '{ \n' + + decode + + ' czm_non_compressed_main(); \n' + + '}'; + + return [attributeDecl, globalDecl, modifiedVS, compressedMain].join('\n'); + } + + function createShaderProgram(groundPolylinePrimitive, frameState, appearance) { + var context = frameState.context; + var primitive = groundPolylinePrimitive._primitive; + + var attributeLocations = primitive._attributeLocations; + + var vs = primitive._batchTable.getVertexShaderCallback()(PolylineShadowVolumeVS); + + vs = Primitive._appendShowToShader(primitive, vs); + vs = Primitive._appendDistanceDisplayConditionToShader(primitive, vs); + vs = modifyForEncodedNormals(primitive.compressVertices, vs); + vs = Primitive._modifyShaderPosition(groundPolylinePrimitive, vs, frameState.scene3DOnly); + + // Tesselation on these volumes tends to be low, + // which causes problems when interpolating log depth from vertices. + // So force computing and writing logarithmic depth in the fragment shader. + // Re-enable at far distances to avoid z-fighting. + var vsDefines = ['ENABLE_GL_POSITION_LOG_DEPTH_AT_HEIGHT']; + var fsDefines = groundPolylinePrimitive.debugShowShadowVolume ? ['DEBUG_SHOW_VOLUME'] : []; + + var vsColor3D = new ShaderSource({ + defines : vsDefines, + sources : [vs] + }); + var fsColor3D = new ShaderSource({ + defines : fsDefines, + sources : [appearance.material.shaderSource, PolylineShadowVolumeFS] + }); + groundPolylinePrimitive._sp = ShaderProgram.replaceCache({ + context : context, + shaderProgram : primitive._sp, + vertexShaderSource : vsColor3D, + fragmentShaderSource : fsColor3D, + attributeLocations : attributeLocations + }); + validateShaderMatching(groundPolylinePrimitive._sp, attributeLocations); + + // Derive 2D/CV + var colorProgram2D = context.shaderCache.getDerivedShaderProgram(groundPolylinePrimitive._sp, '2dColor'); + if (!defined(colorProgram2D)) { + var vsColor2D = new ShaderSource({ + defines : vsDefines.concat(['COLUMBUS_VIEW_2D']), + sources : [vs] + }); + colorProgram2D = context.shaderCache.createDerivedShaderProgram(groundPolylinePrimitive._sp, '2dColor', { + context : context, + shaderProgram : groundPolylinePrimitive._sp2D, + vertexShaderSource : vsColor2D, + fragmentShaderSource : fsColor3D, + attributeLocations : attributeLocations + }); + } + groundPolylinePrimitive._sp2D = colorProgram2D; + + if (groundPolylinePrimitive.allowPicking) { + var vsPick = ShaderSource.createPickVertexShaderSource(vs); + vsPick = Primitive._updatePickColorAttribute(vsPick); + + var vsPick3D = new ShaderSource({ + defines : vsDefines, + sources : [vsPick] + }); + var fsPick3D = new ShaderSource({ + defines : fsDefines.concat(['PICK']), + sources : [PolylineShadowVolumeFS], + pickColorQualifier : 'varying' + }); + + groundPolylinePrimitive._spPick = ShaderProgram.replaceCache({ + context : context, + shaderProgram : groundPolylinePrimitive._spPick, + vertexShaderSource : vsPick3D, + fragmentShaderSource : fsPick3D, + attributeLocations : attributeLocations + }); + validateShaderMatching(groundPolylinePrimitive._spPick, attributeLocations); + + // Derive 2D/CV + var pickProgram2D = context.shaderCache.getDerivedShaderProgram(groundPolylinePrimitive._spPick, '2dColor'); + if (!defined(pickProgram2D)) { + var vsPick2D = new ShaderSource({ + defines : vsDefines.concat(['COLUMBUS_VIEW_2D']), + sources : [vsPick] + }); + pickProgram2D = context.shaderCache.createDerivedShaderProgram(groundPolylinePrimitive._spPick, '2dColor', { + context : context, + shaderProgram : groundPolylinePrimitive._spPick2D, + vertexShaderSource : vsPick2D, + fragmentShaderSource : fsPick3D, + attributeLocations : attributeLocations + }); + } + groundPolylinePrimitive._spPick2D = pickProgram2D; + } + } + + function createCommands(groundPolylinePrimitive, appearance, material, translucent, colorCommands, pickCommands) { + var primitive = groundPolylinePrimitive._primitive; + var length = primitive._va.length; + colorCommands.length = length; + pickCommands.length = length; + + var i; + var command; + var uniformMap = primitive._batchTable.getUniformMapCallback()(material._uniforms); + var pass = translucent ? Pass.TRANSLUCENT : Pass.OPAQUE; + + for (i = 0; i < length; i++) { + var vertexArray = primitive._va[i]; + + command = colorCommands[i]; + if (!defined(command)) { + command = colorCommands[i] = new DrawCommand({ + owner : groundPolylinePrimitive, + primitiveType : primitive._primitiveType + }); + } + + command.vertexArray = vertexArray; + command.renderState = groundPolylinePrimitive._renderState; + command.shaderProgram = groundPolylinePrimitive._sp; + command.uniformMap = uniformMap; + command.pass = pass; + + // derive for 2D + var derivedColorCommand = command.derivedCommands.color2D; + if (!defined(derivedColorCommand)) { + derivedColorCommand = DrawCommand.shallowClone(command); + command.derivedCommands.color2D = derivedColorCommand; + } + derivedColorCommand.vertexArray = vertexArray; + derivedColorCommand.renderState = groundPolylinePrimitive._renderState; + derivedColorCommand.shaderProgram = groundPolylinePrimitive._sp2D; + derivedColorCommand.uniformMap = uniformMap; + derivedColorCommand.pass = pass; + + // Pick + command = pickCommands[i]; + if (!defined(command)) { + command = pickCommands[i] = new DrawCommand({ + owner : groundPolylinePrimitive, + primitiveType : primitive._primitiveType + }); + } + + command.vertexArray = vertexArray; + command.renderState = groundPolylinePrimitive._renderState; + command.shaderProgram = groundPolylinePrimitive._spPick; + command.uniformMap = uniformMap; + command.pass = pass; + + // derive for 2D + var derivedPickCommand = command.derivedCommands.pick2D; + if (!defined(derivedPickCommand)) { + derivedPickCommand = DrawCommand.shallowClone(command); + command.derivedCommands.pick2D = derivedColorCommand; + } + derivedPickCommand.vertexArray = vertexArray; + derivedPickCommand.renderState = groundPolylinePrimitive._renderState; + derivedPickCommand.shaderProgram = groundPolylinePrimitive._spPick2D; + derivedPickCommand.uniformMap = uniformMap; + derivedPickCommand.pass = pass; + } + } + + function updateAndQueueCommands(groundPolylinePrimitive, frameState, colorCommands, pickCommands, modelMatrix, cull, debugShowBoundingVolume) { + var primitive = groundPolylinePrimitive._primitive; + + //>>includeStart('debug', pragmas.debug); + if (frameState.mode !== SceneMode.SCENE3D && !Matrix4.equals(modelMatrix, Matrix4.IDENTITY)) { + throw new DeveloperError('Primitive.modelMatrix is only supported in 3D mode.'); + } + //>>includeEnd('debug'); + + Primitive._updateBoundingVolumes(primitive, frameState, modelMatrix); + + var boundingSpheres; + if (frameState.mode === SceneMode.SCENE3D) { + boundingSpheres = primitive._boundingSphereWC; + } else if (frameState.mode === SceneMode.COLUMBUS_VIEW) { + boundingSpheres = primitive._boundingSphereCV; + } else if (frameState.mode === SceneMode.SCENE2D && defined(primitive._boundingSphere2D)) { + boundingSpheres = primitive._boundingSphere2D; + } else if (defined(primitive._boundingSphereMorph)) { + boundingSpheres = primitive._boundingSphereMorph; + } + + var commandList = frameState.commandList; + var passes = frameState.passes; + if (passes.render) { + var colorLength = colorCommands.length; + + for (var j = 0; j < colorLength; ++j) { + var colorCommand = colorCommands[j]; + // Use derived appearance command for 2D + if (frameState.mode !== SceneMode.SCENE3D && + colorCommand.shaderProgram === groundPolylinePrimitive._spPick) { + colorCommand = colorCommand.derivedCommands.color2D; + } + colorCommand.modelMatrix = modelMatrix; + colorCommand.boundingVolume = boundingSpheres[j]; + colorCommand.cull = cull; + colorCommand.debugShowBoundingVolume = debugShowBoundingVolume; + + commandList.push(colorCommand); + } + } + + if (passes.pick) { + var pickLength = pickCommands.length; + for (var k = 0; k < pickLength; ++k) { + var pickCommand = pickCommands[k]; + // Used derived pick command for 2D + if (frameState.mode !== SceneMode.SCENE3D && + pickCommand.shaderProgram === groundPolylinePrimitive._spPick) { + pickCommand = pickCommand.derivedCommands.pick2D; + } + pickCommand.modelMatrix = modelMatrix; + pickCommand.boundingVolume = boundingSpheres[k]; + pickCommand.cull = cull; + + commandList.push(pickCommand); + } + } + } + GroundPolylinePrimitive.prototype.update = function(frameState) { if (!defined(this._primitive) && !defined(this.polylineGeometryInstances)) { return; @@ -201,12 +515,22 @@ define([ var geometryInstance = geometryInstances[i]; var id = geometryInstance.id; - decompose(geometryInstance, polylineSegmentInstances, this._idsToInstanceIndices); + decompose(geometryInstance, frameState.mapProjection, polylineSegmentInstances, this._idsToInstanceIndices); } primitiveOptions.geometryInstances = polylineSegmentInstances; primitiveOptions.appearance = this._appearance; + primitiveOptions._createShaderProgramFunction = function(primitive, frameState, appearance) { + createShaderProgram(that, frameState, appearance); + }; + primitiveOptions._createCommandsFunction = function(primitive, appearance, material, translucent, twoPasses, colorCommands, pickCommands) { + createCommands(that, appearance, material, translucent, colorCommands, pickCommands); + }; + primitiveOptions._updateAndQueueCommandsFunction = function(primitive, frameState, colorCommands, pickCommands, modelMatrix, cull, debugShowBoundingVolume, twoPasses) { + updateAndQueueCommands(that, frameState, colorCommands, pickCommands, modelMatrix, cull, debugShowBoundingVolume); + }; + this._primitive = new Primitive(primitiveOptions); this._primitive.readyPromise.then(function(primitive) { that._ready = true; @@ -225,6 +549,7 @@ define([ } this._primitive.appearance = this._appearance; this._primitive.show = this.show; + this._primitive.debugShowBoundingVolume = this.debugShowBoundingVolume; this._primitive.update(frameState); }; @@ -234,6 +559,12 @@ define([ GroundPolylinePrimitive.prototype.destroy = function() { this._primitive = this._primitive && this._primitive.destroy(); + this._sp = this._sp && this._sp.destroy(); + this._spPick = this._spPick && this._spPick.destroy(); + + // Derived programs, destroyed above if they existed. + this._sp2D = undefined; + this._spPick2D = undefined; //These objects may be fairly large and reference other large objects (like Entities) //We explicitly set them to undefined here so that the memory can be freed diff --git a/Source/Scene/GroundPrimitive.js b/Source/Scene/GroundPrimitive.js index e6d1fd2fc149..78e92e404681 100644 --- a/Source/Scene/GroundPrimitive.js +++ b/Source/Scene/GroundPrimitive.js @@ -644,7 +644,7 @@ define([ for (i = 0; i < colorLength; ++i) { colorCommand = colorCommands[i]; - // derive a separate appearance command for 2D if needed + // Use derived appearance command for 2D if needed if (frameState.mode !== SceneMode.SCENE3D && colorCommand.shaderProgram === classificationPrimitive._spColor && classificationPrimitive._needs2DShader) { @@ -690,7 +690,7 @@ define([ for (var j = 0; j < pickLength; ++j) { var pickCommand = pickCommands[j]; - // derive a separate appearance command for 2D if needed + // Use derived pick command for 2D if needed if (frameState.mode !== SceneMode.SCENE3D && pickCommand.shaderProgram === classificationPrimitive._spPick && classificationPrimitive._needs2DShader) { diff --git a/Source/Scene/Primitive.js b/Source/Scene/Primitive.js index 34e3b2a9a7e4..4336730fdc3e 100644 --- a/Source/Scene/Primitive.js +++ b/Source/Scene/Primitive.js @@ -119,7 +119,6 @@ define([ * @param {Boolean} [options.cull=true] When true, the renderer frustum culls and horizon culls the primitive's commands based on their bounding volume. Set this to false for a small performance gain if you are manually culling the primitive. * @param {Boolean} [options.asynchronous=true] Determines if the primitive will be created asynchronously or block until ready. * @param {Boolean} [options.debugShowBoundingVolume=false] For debugging only. Determines if this primitive's commands' bounding spheres are shown. - * @param {Boolean} [options.fragmentLogDepth=false] Set to true when using geometry with large triangles and {@link Scene#logarithmicDepthBuffer} is true. * @param {ShadowMode} [options.shadows=ShadowMode.DISABLED] Determines whether this primitive casts or receives shadows from each light source. * * @example @@ -352,7 +351,6 @@ define([ this._frontFaceRS = undefined; this._backFaceRS = undefined; this._sp = undefined; - this._fragmentLogDepth = defaultValue(options.fragmentLogDepth, false); this._depthFailAppearance = undefined; this._spDepthFail = undefined; @@ -512,22 +510,6 @@ define([ get : function() { return this._readyPromise.promise; } - }, - - /** - * When true, logarithmic depth is computed per-fragment instead of per vertex. - * - * @memberof Primitive.prototype - * - * @type {Boolean} - * @readonly - * - * @default false - */ - fragmentLogDepth : { - get : function() { - return this._fragmentLogDepth; - } } }); @@ -1446,8 +1428,6 @@ define([ vs = Primitive._modifyShaderPosition(primitive, vs, frameState.scene3DOnly); var fs = appearance.getFragmentShaderSource(); - var vertexShaderDefines = primitive.fragmentLogDepth ? ['ENABLE_GL_POSITION_LOG_DEPTH_AT_HEIGHT'] : []; - // Create pick program if (primitive.allowPicking) { var vsPick = ShaderSource.createPickVertexShaderSource(vs); @@ -1456,20 +1436,14 @@ define([ primitive._pickSP = ShaderProgram.replaceCache({ context : context, shaderProgram : primitive._pickSP, - vertexShaderSource : new ShaderSource({ - sources : [vsPick], - defines : vertexShaderDefines - }), + vertexShaderSource : vsPick, fragmentShaderSource : ShaderSource.createPickFragmentShaderSource(fs, 'varying'), attributeLocations : attributeLocations }); } else { primitive._pickSP = ShaderProgram.fromCache({ context : context, - vertexShaderSource : new ShaderSource({ - sources : [vs], - defines : vertexShaderDefines - }), + vertexShaderSource : vs, fragmentShaderSource : fs, attributeLocations : attributeLocations }); @@ -1479,10 +1453,7 @@ define([ primitive._sp = ShaderProgram.replaceCache({ context : context, shaderProgram : primitive._sp, - vertexShaderSource : new ShaderSource({ - sources : [vs], - defines : vertexShaderDefines - }), + vertexShaderSource : vs, fragmentShaderSource : fs, attributeLocations : attributeLocations }); @@ -1502,10 +1473,7 @@ define([ primitive._spDepthFail = ShaderProgram.replaceCache({ context : context, shaderProgram : primitive._spDepthFail, - vertexShaderSource : new ShaderSource({ - sources : [vs], - defines : vertexShaderDefines - }), + vertexShaderSource : vs, fragmentShaderSource : fs, attributeLocations : attributeLocations }); diff --git a/Source/Shaders/PolylineShadowVolumeFS.glsl b/Source/Shaders/PolylineShadowVolumeFS.glsl index 52723788c536..3c9aae100aa3 100644 --- a/Source/Shaders/PolylineShadowVolumeFS.glsl +++ b/Source/Shaders/PolylineShadowVolumeFS.glsl @@ -20,9 +20,7 @@ void main(void) float logDepthOrDepth = czm_unpackDepth(texture2D(czm_globeDepthTexture, gl_FragCoord.xy / czm_viewport.zw)); // Discard for sky - if (logDepthOrDepth == 0.0) { - discard; - } + bool shouldDiscard = logDepthOrDepth == 0.0; vec4 eyeCoordinate = czm_windowToEyeCoordinates(gl_FragCoord.xy, logDepthOrDepth); eyeCoordinate /= eyeCoordinate.w; @@ -35,10 +33,20 @@ void main(void) float distanceFromStart = rayPlaneDistance(eyeCoordinate.xyz, -v_forwardDirectionEC, v_startPlaneEC.xyz, v_startPlaneEC.w); float distanceFromEnd = rayPlaneDistance(eyeCoordinate.xyz, v_forwardDirectionEC, v_endPlaneEC.xyz, v_endPlaneEC.w); - if (abs(width) > halfMaxWidth || distanceFromStart < 0.0 || distanceFromEnd < 0.0) { + shouldDiscard = shouldDiscard || (abs(width) > halfMaxWidth || distanceFromStart < 0.0 || distanceFromEnd < 0.0); + + if (shouldDiscard) { +#ifdef DEBUG_SHOW_VOLUME + gl_FragColor = vec4(1.0, 0.0, 0.0, 0.5); + return; +#else // DEBUG_SHOW_VOLUME discard; +#endif // DEBUG_SHOW_VOLUME } +#ifdef PICK + gl_FragColor.a = 1.0; +#else // PICK // Use distances for planes aligned with segment to prevent skew in dashing distanceFromStart = rayPlaneDistance(eyeCoordinate.xyz, -v_forwardDirectionEC, v_forwardDirectionEC.xyz, v_alignedPlaneDistances.x); distanceFromEnd = rayPlaneDistance(eyeCoordinate.xyz, v_forwardDirectionEC, -v_forwardDirectionEC.xyz, v_alignedPlaneDistances.y); @@ -60,5 +68,6 @@ void main(void) czm_material material = czm_getMaterial(materialInput); gl_FragColor = vec4(material.diffuse + material.emission, material.alpha); +#endif // PICK czm_writeDepthClampedToFarPlane(); } diff --git a/Source/Shaders/PolylineShadowVolumeVS.glsl b/Source/Shaders/PolylineShadowVolumeVS.glsl index 1c39e5cc90c2..e571d1806591 100644 --- a/Source/Shaders/PolylineShadowVolumeVS.glsl +++ b/Source/Shaders/PolylineShadowVolumeVS.glsl @@ -1,7 +1,7 @@ attribute vec3 position3DHigh; attribute vec3 position3DLow; attribute float batchId; -attribute vec3 normal; +attribute vec3 normal; // TODO: "rebuild" the normal in here from planes. This simplifies 2D. varying vec4 v_startPlaneEC; varying vec4 v_endPlaneEC; diff --git a/Specs/Scene/PrimitiveSpec.js b/Specs/Scene/PrimitiveSpec.js index d43a693cc899..d19b70f9e2df 100644 --- a/Specs/Scene/PrimitiveSpec.js +++ b/Specs/Scene/PrimitiveSpec.js @@ -166,7 +166,6 @@ defineSuite([ expect(primitive.cull).toEqual(true); expect(primitive.asynchronous).toEqual(true); expect(primitive.debugShowBoundingVolume).toEqual(false); - expect(primitive.fragmentLogDepth).toEqual(false); }); it('Constructs with options', function() { @@ -188,8 +187,7 @@ defineSuite([ allowPicking : false, cull : false, asynchronous : false, - debugShowBoundingVolume : true, - fragmentLogDepth : true + debugShowBoundingVolume : true }); expect(primitive.geometryInstances).toEqual(geometryInstances); @@ -205,7 +203,6 @@ defineSuite([ expect(primitive.cull).toEqual(false); expect(primitive.asynchronous).toEqual(false); expect(primitive.debugShowBoundingVolume).toEqual(true); - expect(primitive.fragmentLogDepth).toEqual(true); }); it('releases geometry instances when releaseGeometryInstances is true', function() { From bafa5f298d193a154687ac5df1f78025008038c5 Mon Sep 17 00:00:00 2001 From: Kangning Li Date: Mon, 21 May 2018 15:54:58 -0400 Subject: [PATCH 04/39] removed vertex normals for polyline shadow volumes --- Source/Core/GroundLineGeometry.js | 39 +++++++++------------- Source/Scene/GroundPolylinePrimitive.js | 2 +- Source/Shaders/PolylineShadowVolumeVS.glsl | 34 ++++++++++++++++--- 3 files changed, 47 insertions(+), 28 deletions(-) diff --git a/Source/Core/GroundLineGeometry.js b/Source/Core/GroundLineGeometry.js index 4752b9bd2a85..3b3d2530a206 100644 --- a/Source/Core/GroundLineGeometry.js +++ b/Source/Core/GroundLineGeometry.js @@ -201,8 +201,10 @@ define([ return result; } - var startNormalLeftScratch = new Cartesian3(); - var endNormalLeftScratch = new Cartesian3(); + var startBottomScratch = new Cartesian3(); + var endBottomScratch = new Cartesian3(); + var endTopScratch = new Cartesian3(); + var startTopScratch = new Cartesian3(); /** * * @param {GroundLineGeometry} groundPolylineSegmentGeometry @@ -216,30 +218,27 @@ define([ var endNormal = groundPolylineSegmentGeometry._endNormal; var positions = new Float64Array(24); // 8 vertices - var normals = new Float32Array(24); + // Push out by 1.0 in the right direction + var startBottom = Cartesian3.add(groundPolylineSegmentGeometry._startBottom, startNormal, startBottomScratch); + var endBottom = Cartesian3.add(groundPolylineSegmentGeometry._endBottom, endNormal, endBottomScratch); + var endTop = Cartesian3.add(groundPolylineSegmentGeometry._endTop, endNormal, endTopScratch); + var startTop = Cartesian3.add(groundPolylineSegmentGeometry._startTop, startNormal, startTopScratch); Cartesian3.pack(startBottom, positions, 0); Cartesian3.pack(endBottom, positions, 1 * 3); Cartesian3.pack(endTop, positions, 2 * 3); Cartesian3.pack(startTop, positions, 3 * 3); + // Push out by 1.0 in the left direction + startBottom = Cartesian3.subtract(groundPolylineSegmentGeometry._startBottom, startNormal, startBottomScratch); + endBottom = Cartesian3.subtract(groundPolylineSegmentGeometry._endBottom, endNormal, endBottomScratch); + endTop = Cartesian3.subtract(groundPolylineSegmentGeometry._endTop, endNormal, endTopScratch); + startTop = Cartesian3.subtract(groundPolylineSegmentGeometry._startTop, startNormal, startTopScratch); Cartesian3.pack(startBottom, positions, 4 * 3); Cartesian3.pack(endBottom, positions, 5 * 3); Cartesian3.pack(endTop, positions, 6 * 3); Cartesian3.pack(startTop, positions, 7 * 3); - Cartesian3.pack(startNormal, normals, 0); - Cartesian3.pack(endNormal, normals, 1 * 3); - Cartesian3.pack(endNormal, normals, 2 * 3); - Cartesian3.pack(startNormal, normals, 3 * 3); - - startNormal = Cartesian3.multiplyByScalar(startNormal, -1.0, startNormalLeftScratch); - endNormal = Cartesian3.multiplyByScalar(endNormal, -1.0, endNormalLeftScratch); - Cartesian3.pack(startNormal, normals, 4 * 3); - Cartesian3.pack(endNormal, normals, 5 * 3); - Cartesian3.pack(endNormal, normals, 6 * 3); - Cartesian3.pack(startNormal, normals, 7 * 3); - var indices = [ 0, 1, 2, 0, 2, 3, // right 0, 3, 7, 0, 7, 4, // start @@ -254,12 +253,6 @@ define([ componentsPerAttribute : 3, normalize : false, values : positions - }), - normal : new GeometryAttribute({ - componentDatatype : ComponentDatatype.FLOAT, - componentsPerAttribute : 3, - normalize : true, - values : normals }) }); @@ -356,7 +349,7 @@ define([ }); // Normal planes need to miter, so cross startTop - startBottom with geometry normal at start - var startNormal = Cartesian3.cross(startCartesianRightNormal, startUp, normal1Scratch); + var startNormal = Cartesian3.cross(startUp, startCartesianRightNormal, normal1Scratch); startNormal = Cartesian3.normalize(startNormal, startNormal); var startNormal_and_forwardOffsetZ_attribute = new GeometryInstanceAttribute({ @@ -368,7 +361,7 @@ define([ var endUp = Cartesian3.subtract(endTop, endBottom, normal2Scratch); endUp = Cartesian3.normalize(endUp, endUp); - var endNormal = Cartesian3.cross(endUp, endCartesianRightNormal, normal2Scratch); + var endNormal = Cartesian3.cross(endCartesianRightNormal, endUp, normal2Scratch); endNormal = Cartesian3.normalize(endNormal, endNormal); var endNormal_attribute = new GeometryInstanceAttribute({ diff --git a/Source/Scene/GroundPolylinePrimitive.js b/Source/Scene/GroundPolylinePrimitive.js index cfab351551c5..2ab759dd16cb 100644 --- a/Source/Scene/GroundPolylinePrimitive.js +++ b/Source/Scene/GroundPolylinePrimitive.js @@ -265,7 +265,7 @@ define([ vs = Primitive._appendShowToShader(primitive, vs); vs = Primitive._appendDistanceDisplayConditionToShader(primitive, vs); - vs = modifyForEncodedNormals(primitive.compressVertices, vs); + //vs = modifyForEncodedNormals(primitive.compressVertices, vs); vs = Primitive._modifyShaderPosition(groundPolylinePrimitive, vs, frameState.scene3DOnly); // Tesselation on these volumes tends to be low, diff --git a/Source/Shaders/PolylineShadowVolumeVS.glsl b/Source/Shaders/PolylineShadowVolumeVS.glsl index e571d1806591..934de06a34f0 100644 --- a/Source/Shaders/PolylineShadowVolumeVS.glsl +++ b/Source/Shaders/PolylineShadowVolumeVS.glsl @@ -1,7 +1,6 @@ attribute vec3 position3DHigh; attribute vec3 position3DLow; attribute float batchId; -attribute vec3 normal; // TODO: "rebuild" the normal in here from planes. This simplifies 2D. varying vec4 v_startPlaneEC; varying vec4 v_endPlaneEC; @@ -13,6 +12,16 @@ varying float v_halfWidth; varying float v_width; // for materials varying float v_polylineAngle; +float rayPlaneDistance(vec3 origin, vec3 direction, vec3 planeNormal, float planeDistance) { // TODO: move into its own function? + // We don't expect the ray to ever be parallel to the plane + return (-planeDistance - dot(planeNormal, origin)) / dot(planeNormal, direction); +} + +vec3 branchFreeTernary(bool comparison, vec3 a, vec3 b) { // TODO: make branchFreeTernary generic for floats and vec3s + float useA = float(comparison); + return a * useA + b * (1.0 - useA); +} + void main() { vec4 entry1 = czm_batchTable_startHi_and_forwardOffsetX(batchId); @@ -53,14 +62,31 @@ void main() // Position stuff vec4 positionRelativeToEye = czm_computePosition(); + // Compute a normal along which to "push" the position out, extending the miter depending on view distance. + // Position has already been "pushed" by unit length along miter normal, and miter normals are encoded in the planes. + // Decode the normal to use at this specific vertex, push the position back, and then push to where it needs to be. + + // Check distance to the end plane and start plane, pick the plane that is closer + vec4 positionEC = czm_modelViewRelativeToEye * positionRelativeToEye; + vec3 positionEC3 = positionEC.xyz / positionEC.w; + float absStartPlaneDistance = abs(czm_planeDistance(v_startPlaneEC, positionEC3)); + float absEndPlaneDistance = abs(czm_planeDistance(v_endPlaneEC, positionEC3)); + vec3 planeDirection = branchFreeTernary(absStartPlaneDistance < absEndPlaneDistance, v_startPlaneEC.xyz, v_endPlaneEC.xyz); + vec3 upOrDown = normalize(cross(v_rightPlaneEC.xyz, planeDirection)); // Points "up" for start plane, "down" at end plane. + vec3 normalEC = normalize(cross(planeDirection, upOrDown)); // In practice, the opposite seems to work too. + + // Check distance to the right plane to determine if the miter normal points "left" or "right" + normalEC *= sign(czm_planeDistance(v_rightPlaneEC, positionEC3)); + positionEC3 -= normalEC; + // A "perfect" implementation would push along normals according to the angle against forward. - // In practice, just extending the shadow volume a more than needed works for most cases, + // In practice, just extending the shadow volume more than needed works for most cases, // and for very sharp turns we compute attributes to "break" the miter anyway. float width = czm_batchTable_width(batchId); v_width = width; v_halfWidth = width * 0.5; - positionRelativeToEye.xyz += width * 2.0 * czm_metersPerPixel(positionRelativeToEye) * normal; - gl_Position = czm_depthClampFarPlane(czm_modelViewProjectionRelativeToEye * positionRelativeToEye); + positionEC3 += width * 2.0 * czm_metersPerPixel(positionRelativeToEye) * normalEC;; + gl_Position = czm_depthClampFarPlane(czm_projection * vec4(positionEC3, 1.0)); // Approximate relative screen space direction of the line. // This doesn't work great if the view direction is roughly aligned with the line From 03713077b2df3032196cdc34082408e2bc5fefdb Mon Sep 17 00:00:00 2001 From: Kangning Li Date: Mon, 21 May 2018 17:38:17 -0400 Subject: [PATCH 05/39] Support for 2D/CV --- Source/Core/GroundLineGeometry.js | 145 +++++++++++++++++++-- Source/Core/GroundPolylineGeometry.js | 38 ------ Source/Scene/GroundPolylinePrimitive.js | 60 ++++----- Source/Shaders/PolylineShadowVolumeVS.glsl | 54 ++++++-- 4 files changed, 200 insertions(+), 97 deletions(-) diff --git a/Source/Core/GroundLineGeometry.js b/Source/Core/GroundLineGeometry.js index 3b3d2530a206..51eb5846df27 100644 --- a/Source/Core/GroundLineGeometry.js +++ b/Source/Core/GroundLineGeometry.js @@ -1,5 +1,6 @@ define([ './BoundingSphere', + './Cartesian2', './Cartesian3', './Cartographic', './Check', @@ -18,6 +19,7 @@ define([ './Quaternion' ], function( BoundingSphere, + Cartesian2, Cartesian3, Cartographic, Check, @@ -58,11 +60,24 @@ define([ this._endTop = new Cartesian3(); this._endNormal = new Cartesian3(); + this._segmentBottomLength = 0.0; + this._segmentBottomLength2D = 0.0; + this._workerName = 'createGroundLineGeometry'; - this._segmentBottomLength = undefined; } - GroundLineGeometry.fromArrays = function(index, normalsArray, bottomPositionsArray, topPositionsArray) { + var pos1_2dScratch = new Cartesian3(); + var pos2_2dScratch = new Cartesian3(); + function computeDistance2D(projection, carto1, carto2) { + var ellipsoid = projection.ellipsoid; + var pos1_2d = projection.project(carto1, pos1_2dScratch); + var pos2_2d = projection.project(carto2, pos2_2dScratch); + return Cartesian3.distance(pos1_2d, pos2_2d); + } + + var startCartographicScratch = new Cartographic(); + var endCartographicScratch = new Cartographic(); + GroundLineGeometry.fromArrays = function(projection, index, normalsArray, bottomPositionsArray, topPositionsArray) { var geometry = new GroundLineGeometry(); Cartesian3.unpack(bottomPositionsArray, index, geometry._startBottom); @@ -75,11 +90,22 @@ define([ breakMiter(geometry); + geometry._segmentBottomLength = Cartesian3.distance(geometry._startBottom, geometry._endBottom); + + var ellipsoid = projection.ellipsoid; + var startCartographic = ellipsoid.cartesianToCartographic(geometry._startBottom, startCartographicScratch); + var endCartographic = ellipsoid.cartesianToCartographic(geometry._endBottom, endCartographicScratch); + startCartographic.height = 0.0; + endCartographic.height = 0.0; + + geometry._segmentBottomLength2D = computeDistance2D(projection, startCartographic, endCartographic); + + // TODO: slide wall positions to match height and depth. + // Note that this has to happen after the length computations + return geometry; } - // TODO: add function to lower the bottom or raise the top to a specific altitude - function direction(end, start, result) { Cartesian3.subtract(end, start, result); Cartesian3.normalize(result, result); @@ -124,10 +150,12 @@ define([ // TODO: doc segmentBottomLength : { get : function() { - if (!defined(this._segmentBottomLength)) { - this._segmentBottomLength = Cartesian3.distance(this._startBottom, this._endBottom); - } return this._segmentBottomLength; + } + }, + segmentBottomLength2D : { + get : function() { + return this._segmentBottomLength2D; } } }); @@ -136,7 +164,7 @@ define([ * The number of elements used to pack the object into an packArray. * @type {Number} */ - GroundLineGeometry.packedLength = Cartesian3.packedLength * 6; + GroundLineGeometry.packedLength = Cartesian3.packedLength * 6 + 2; /** * Stores the provided instance into the provided packArray. @@ -267,6 +295,90 @@ define([ return Cartesian3.distance(this._startBottom, this._endBottom); } + var positionCartographicScratch = new Cartographic(); + var normalEndpointScratch = new Cartesian3(); + function projectNormal(projection, position, normal, projectedPosition, result) { + var normalEndpoint = Cartesian3.add(position, normal, normalEndpointScratch); + + var ellipsoid = projection.ellipsoid; + var normalEndpointCartographic = ellipsoid.cartesianToCartographic(normalEndpoint, positionCartographicScratch); + normalEndpointCartographic.height = 0.0; + var normalEndpointProjected = projection.project(normalEndpointCartographic, result); + result = Cartesian3.subtract(result, projectedPosition, result); + result.z = 0.0; + result = Cartesian3.normalize(result, result); + return result; + } + + var encodeScratch2D = new EncodedCartesian3(); + var projectedStartPositionScratch = new Cartesian3(); + var projectedEndPositionScratch = new Cartesian3(); + var projectedStartNormalScratch = new Cartesian3(); + var projectedEndNormalScratch = new Cartesian3(); + var forwardOffset2DScratch = new Cartesian3(); + var forwardNormal2DScratch = new Cartesian3(); + function add2DAttributes(attributes, geometry, projection, lengthSoFar2D, segmentLength2D, totalLength2D) { + var startBottom = geometry._startBottom; + var endBottom = geometry._endBottom; + var ellipsoid = projection.ellipsoid; + + // Project positions + var startCartographic = ellipsoid.cartesianToCartographic(startBottom, startCartographicScratch); + var endCartographic = ellipsoid.cartesianToCartographic(endBottom, endCartographicScratch); + startCartographic.height = 0.0; + endCartographic.height = 0.0; + var projectedStartPosition = projection.project(startCartographic, projectedStartPositionScratch); + var projectedEndPosition = projection.project(endCartographic, projectedEndPositionScratch); + + // Project mitering normals + var projectedStartNormal = projectNormal(projection, startBottom, geometry._startNormal, projectedStartPosition, projectedStartNormalScratch); + var projectedEndNormal = projectNormal(projection, endBottom, geometry._endNormal, projectedEndPosition, projectedEndNormalScratch); + + // Right direction is just forward direction rotated by -90 degrees around Z + var forwardOffset = Cartesian3.subtract(projectedEndPosition, projectedStartPosition, forwardOffset2DScratch); + var forwardDirection = Cartesian3.normalize(forwardOffset, forwardNormal2DScratch); + var right2D = [forwardDirection.y, -forwardDirection.x]; + + // Similarly with plane normals + var startPlane2D = [-projectedStartNormal.y, projectedStartNormal.x]; + var endPlane2D = [projectedEndNormal.y, -projectedEndNormal.x]; + + var encodedStart = EncodedCartesian3.fromCartesian(projectedStartPosition, encodeScratch2D); + + var startHighLow2D_attribute = new GeometryInstanceAttribute({ + componentDatatype: ComponentDatatype.FLOAT, + componentsPerAttribute: 4, + normalize: false, + value : [encodedStart.high.x, encodedStart.high.y, encodedStart.low.x, encodedStart.low.y] + }); + + var startEndNormals2D_attribute = new GeometryInstanceAttribute({ + componentDatatype: ComponentDatatype.FLOAT, + componentsPerAttribute: 4, + normalize: false, + value : startPlane2D.concat(endPlane2D) + }); + + var offsetAndRight2D_attribute = new GeometryInstanceAttribute({ + componentDatatype: ComponentDatatype.FLOAT, + componentsPerAttribute: 4, + normalize: false, + value : [forwardOffset.x, forwardOffset.y, right2D[0], right2D[1]] + }); + + var texcoordNormalization2D_attribute = new GeometryInstanceAttribute({ + componentDatatype: ComponentDatatype.FLOAT, + componentsPerAttribute: 3, + normalize: false, + value : [lengthSoFar2D, segmentLength2D, totalLength2D] + }); + + attributes.startHighLow2D = startHighLow2D_attribute; + attributes.startEndNormals2D = startEndNormals2D_attribute; + attributes.offsetAndRight2D = offsetAndRight2D_attribute; + attributes.texcoordNormalization2D = texcoordNormalization2D_attribute; + } + var encodeScratch = new EncodedCartesian3(); var offsetScratch = new Cartesian3(); var offsetScratch = new Cartesian3(); @@ -292,15 +404,21 @@ define([ * @param {Number} lengthSoFar Distance of the segment's start point along the line * @param {Number} segmentLength Length of the segment * @param {Number} totalLength Total length of the entire line + * @param {Number} lengthSoFar2D Distance of the segment's start point along the line in 2D + * @param {Number} segmentLength2D Length of the segment in 2D + * @param {Number} totalLength2D Total length of the entire line in 2D * @returns {Object} An object containing GeometryInstanceAttributes for the input geometry */ - GroundLineGeometry.getAttributes = function(geometry, projection, lengthSoFar, segmentLength, totalLength) { + GroundLineGeometry.getAttributes = function(geometry, projection, lengthSoFar, segmentLength, totalLength, lengthSoFar2D, segmentLength2D, totalLength2D) { //>>includeStart('debug', pragmas.debug); Check.typeOf.object('geometry', geometry); Check.typeOf.object('projection', projection); Check.typeOf.number('lengthSoFar', lengthSoFar); Check.typeOf.number('segmentLength', segmentLength); Check.typeOf.number('totalLength', totalLength); + Check.typeOf.number('lengthSoFar2D', lengthSoFar2D); + Check.typeOf.number('segmentLength2D', segmentLength2D); + Check.typeOf.number('totalLength2D', totalLength2D); //>>includeEnd('debug'); // Unpack values from geometry @@ -334,7 +452,7 @@ define([ var packArray = [0, 0, 0, forwardOffset.z]; var forward = Cartesian3.normalize(forwardOffset, forwardOffset); - // Right vector is computed as cross of startTop - startBottom and direction to segment end end point + // Right vector is computed as cross of (startTop - startBottom) and direction to segment end point var startUp = Cartesian3.subtract(startTop, startBottom, normal1Scratch); startUp = Cartesian3.normalize(startUp, startUp); @@ -348,7 +466,7 @@ define([ value : Cartesian3.pack(right, [0, 0, 0]) }); - // Normal planes need to miter, so cross startTop - startBottom with geometry normal at start + // Normal planes need to miter, so cross (startTop - startBottom) with geometry normal at start var startNormal = Cartesian3.cross(startUp, startCartesianRightNormal, normal1Scratch); startNormal = Cartesian3.normalize(startNormal, startNormal); @@ -379,7 +497,7 @@ define([ value : [lengthSoFar, segmentLength, totalLength] }); - return { + var attributes = { startHi_and_forwardOffsetX : startHi_and_forwardOffsetX_Attribute, startLo_and_forwardOffsetY : startLo_and_forwardOffsetY_Attribute, startNormal_and_forwardOffsetZ : startNormal_and_forwardOffsetZ_attribute, @@ -387,6 +505,9 @@ define([ rightNormal : rightNormal_attribute, texcoordNormalization : texcoordNormalization_attribute }; + + add2DAttributes(attributes, geometry, projection, lengthSoFar2D, segmentLength2D, totalLength2D) + return attributes; }; return GroundLineGeometry; diff --git a/Source/Core/GroundPolylineGeometry.js b/Source/Core/GroundPolylineGeometry.js index 983b7e16e929..b18a174a7a2f 100644 --- a/Source/Core/GroundPolylineGeometry.js +++ b/Source/Core/GroundPolylineGeometry.js @@ -62,8 +62,6 @@ define([ this.ellipsoid = defaultValue(options.ellipsoid, Ellipsoid.WGS84); - this._lengthOnEllipsoid = undefined; - this.width = defaultValue(options.width, 1.0); } @@ -95,14 +93,6 @@ define([ set: function(value) { this._positions = defaultValue(value, []); } - }, - lengthOnEllipsoid : { - get : function() { - if (!defined(this._lengthOnEllipsoid)) { - this._lengthOnEllipsoid = computeLengthOnEllipsoid(this._positions, this.loop, this.ellipsoid); - } - return this._lengthOnEllipsoid; - } } }); @@ -267,33 +257,5 @@ define([ return result; } - var segmentStartScratch = new Cartesian3(); - var segmentEndScratch = new Cartesian3(); - function computeLengthOnEllipsoid(cartographicPositions, loop, ellipsoid) { - var cartographicsLength = cartographicPositions.length; - - var segmentStart = segmentStartScratch; - var segmentEnd = segmentEndScratch; - getPosition(ellipsoid, cartographicPositions[0], 0.0, segmentStart); - getPosition(ellipsoid, cartographicPositions[1], 0.0, segmentEnd); - - var length = Cartesian3.distance(segmentStart, segmentEnd); - - for (var i = 1; i < cartographicsLength - 1; i++) { - segmentStart = Cartesian3.clone(segmentEnd, segmentStart); - getPosition(ellipsoid, cartographicPositions[i + 1], 0.0, segmentEnd); - - length += Cartesian3.distance(segmentStart, segmentEnd); - } - - if (loop) { - getPosition(ellipsoid, cartographicPositions[cartographicsLength - 1], 0.0, segmentStart); - getPosition(ellipsoid, cartographicPositions[0], 0.0, segmentEnd); - - length += Cartesian3.distance(segmentStart, segmentEnd); - } - return length; - } - return GroundPolylineGeometry; }); diff --git a/Source/Scene/GroundPolylinePrimitive.js b/Source/Scene/GroundPolylinePrimitive.js index 2ab759dd16cb..0b65ddd175f5 100644 --- a/Source/Scene/GroundPolylinePrimitive.js +++ b/Source/Scene/GroundPolylinePrimitive.js @@ -179,16 +179,33 @@ define([ var bottomPositions = wallVertices.bottomPositions; var topPositions = wallVertices.topPositions; - var totalLength = groundPolylineGeometry.lengthOnEllipsoid; - var lengthSoFar = 0.0; + var totalLength = 0.0; + var totalLength2D = 0.0; + var i; + var groundPolylineSegmentGeometry; var verticesLength = rightFacingNormals.length; + var lineGeometries = new Array(verticesLength / 3); + var index = 0; + for (i = 0; i < verticesLength - 3; i += 3) { + groundPolylineSegmentGeometry = GroundLineGeometry.fromArrays(projection, i, rightFacingNormals, bottomPositions, topPositions); + totalLength += groundPolylineSegmentGeometry.segmentBottomLength; + totalLength2D += groundPolylineSegmentGeometry.segmentBottomLength2D; + + lineGeometries[index++] = groundPolylineSegmentGeometry; + } + var segmentIndicesStart = polylineSegmentInstances.length; - for (var i = 0; i < verticesLength - 3; i += 3) { - var groundPolylineSegmentGeometry = GroundLineGeometry.fromArrays(i, rightFacingNormals, bottomPositions, topPositions); + index = 0; + var lengthSoFar = 0.0; + var lengthSoFar2D = 0.0; + for (i = 0; i < verticesLength - 3; i += 3) { + groundPolylineSegmentGeometry = lineGeometries[index++]; var segmentLength = groundPolylineSegmentGeometry.segmentBottomLength; - var attributes = GroundLineGeometry.getAttributes(groundPolylineSegmentGeometry, projection, lengthSoFar, segmentLength, totalLength); + var segmentLength2D = groundPolylineSegmentGeometry.segmentBottomLength2D; + var attributes = GroundLineGeometry.getAttributes(groundPolylineSegmentGeometry, projection, lengthSoFar, segmentLength, totalLength, lengthSoFar2D, segmentLength2D, totalLength2D); lengthSoFar += segmentLength; + lengthSoFar2D += segmentLength2D; attributes.width = new GeometryInstanceAttribute({ componentDatatype: ComponentDatatype.UNSIGNED_BYTE, @@ -231,30 +248,6 @@ define([ //>>includeEnd('debug'); } - function modifyForEncodedNormals(compressVertices, vertexShaderSource) { - if (!compressVertices) { - return vertexShaderSource; - } - - var attributeName = 'compressedAttributes'; - var attributeDecl = 'attribute float ' + attributeName + ';'; - - var globalDecl = 'vec3 normal;\n'; - var decode = ' normal = czm_octDecode(' + attributeName + ');\n'; - - var modifiedVS = vertexShaderSource; - modifiedVS = modifiedVS.replace(/attribute\s+vec3\s+normal;/g, ''); - modifiedVS = ShaderSource.replaceMain(modifiedVS, 'czm_non_compressed_main'); - var compressedMain = - 'void main() \n' + - '{ \n' + - decode + - ' czm_non_compressed_main(); \n' + - '}'; - - return [attributeDecl, globalDecl, modifiedVS, compressedMain].join('\n'); - } - function createShaderProgram(groundPolylinePrimitive, frameState, appearance) { var context = frameState.context; var primitive = groundPolylinePrimitive._primitive; @@ -265,7 +258,6 @@ define([ vs = Primitive._appendShowToShader(primitive, vs); vs = Primitive._appendDistanceDisplayConditionToShader(primitive, vs); - //vs = modifyForEncodedNormals(primitive.compressVertices, vs); vs = Primitive._modifyShaderPosition(groundPolylinePrimitive, vs, frameState.scene3DOnly); // Tesselation on these volumes tends to be low, @@ -333,13 +325,13 @@ define([ validateShaderMatching(groundPolylinePrimitive._spPick, attributeLocations); // Derive 2D/CV - var pickProgram2D = context.shaderCache.getDerivedShaderProgram(groundPolylinePrimitive._spPick, '2dColor'); + var pickProgram2D = context.shaderCache.getDerivedShaderProgram(groundPolylinePrimitive._spPick, '2dPick'); if (!defined(pickProgram2D)) { var vsPick2D = new ShaderSource({ defines : vsDefines.concat(['COLUMBUS_VIEW_2D']), sources : [vsPick] }); - pickProgram2D = context.shaderCache.createDerivedShaderProgram(groundPolylinePrimitive._spPick, '2dColor', { + pickProgram2D = context.shaderCache.createDerivedShaderProgram(groundPolylinePrimitive._spPick, '2dPick', { context : context, shaderProgram : groundPolylinePrimitive._spPick2D, vertexShaderSource : vsPick2D, @@ -410,7 +402,7 @@ define([ var derivedPickCommand = command.derivedCommands.pick2D; if (!defined(derivedPickCommand)) { derivedPickCommand = DrawCommand.shallowClone(command); - command.derivedCommands.pick2D = derivedColorCommand; + command.derivedCommands.pick2D = derivedPickCommand; } derivedPickCommand.vertexArray = vertexArray; derivedPickCommand.renderState = groundPolylinePrimitive._renderState; @@ -451,7 +443,7 @@ define([ var colorCommand = colorCommands[j]; // Use derived appearance command for 2D if (frameState.mode !== SceneMode.SCENE3D && - colorCommand.shaderProgram === groundPolylinePrimitive._spPick) { + colorCommand.shaderProgram === groundPolylinePrimitive._sp) { colorCommand = colorCommand.derivedCommands.color2D; } colorCommand.modelMatrix = modelMatrix; diff --git a/Source/Shaders/PolylineShadowVolumeVS.glsl b/Source/Shaders/PolylineShadowVolumeVS.glsl index 934de06a34f0..2b48036e304c 100644 --- a/Source/Shaders/PolylineShadowVolumeVS.glsl +++ b/Source/Shaders/PolylineShadowVolumeVS.glsl @@ -24,6 +24,36 @@ vec3 branchFreeTernary(bool comparison, vec3 a, vec3 b) { // TODO: make branchFr void main() { +#ifdef COLUMBUS_VIEW_2D + vec4 entry = czm_batchTable_startHighLow2D(batchId); + + vec3 ecStart = (czm_modelViewRelativeToEye * czm_translateRelativeToEye(vec3(0.0, entry.xy), vec3(0.0, entry.zw))).xyz; + + entry = czm_batchTable_offsetAndRight2D(batchId); + vec3 forwardDirectionEC = czm_normal * vec3(0.0, entry.xy); + vec3 ecEnd = forwardDirectionEC + ecStart; + forwardDirectionEC = normalize(forwardDirectionEC); + + v_forwardDirectionEC = forwardDirectionEC; + + // Right plane + v_rightPlaneEC.xyz = czm_normal * vec3(0.0, entry.zw); + v_rightPlaneEC.w = -dot(v_rightPlaneEC.xyz, ecStart); + + entry = czm_batchTable_startEndNormals2D(batchId); + + // start plane + v_startPlaneEC.xyz = czm_normal * vec3(0.0, entry.xy); + v_startPlaneEC.w = -dot(v_startPlaneEC.xyz, ecStart); + + // end plane + v_endPlaneEC.xyz = czm_normal * vec3(0.0, entry.zw); + v_endPlaneEC.w = -dot(v_endPlaneEC.xyz, ecEnd); + + v_texcoordNormalization = czm_batchTable_texcoordNormalization2D(batchId); + +#else // COLUMBUS_VIEW_2D + vec4 entry1 = czm_batchTable_startHi_and_forwardOffsetX(batchId); vec4 entry2 = czm_batchTable_startLo_and_forwardOffsetY(batchId); @@ -54,30 +84,28 @@ void main() v_startPlaneEC.xyz = ecStartNormal; v_startPlaneEC.w = -dot(ecStartNormal, ecStart); - v_alignedPlaneDistances.x = -dot(forwardDirectionEC, ecStart); - v_alignedPlaneDistances.y = -dot(-forwardDirectionEC, ecEnd); - v_texcoordNormalization = czm_batchTable_texcoordNormalization(batchId); - // Position stuff - vec4 positionRelativeToEye = czm_computePosition(); +#endif // COLUMBUS_VIEW_2D + + v_alignedPlaneDistances.x = -dot(forwardDirectionEC, ecStart); + v_alignedPlaneDistances.y = -dot(-forwardDirectionEC, ecEnd); // Compute a normal along which to "push" the position out, extending the miter depending on view distance. // Position has already been "pushed" by unit length along miter normal, and miter normals are encoded in the planes. // Decode the normal to use at this specific vertex, push the position back, and then push to where it needs to be. + vec4 positionRelativeToEye = czm_computePosition(); // Check distance to the end plane and start plane, pick the plane that is closer - vec4 positionEC = czm_modelViewRelativeToEye * positionRelativeToEye; - vec3 positionEC3 = positionEC.xyz / positionEC.w; - float absStartPlaneDistance = abs(czm_planeDistance(v_startPlaneEC, positionEC3)); - float absEndPlaneDistance = abs(czm_planeDistance(v_endPlaneEC, positionEC3)); + vec4 positionEC = czm_modelViewRelativeToEye * positionRelativeToEye; // w = 1.0, see czm_computePosition + float absStartPlaneDistance = abs(czm_planeDistance(v_startPlaneEC, positionEC.xyz)); + float absEndPlaneDistance = abs(czm_planeDistance(v_endPlaneEC, positionEC.xyz)); vec3 planeDirection = branchFreeTernary(absStartPlaneDistance < absEndPlaneDistance, v_startPlaneEC.xyz, v_endPlaneEC.xyz); vec3 upOrDown = normalize(cross(v_rightPlaneEC.xyz, planeDirection)); // Points "up" for start plane, "down" at end plane. vec3 normalEC = normalize(cross(planeDirection, upOrDown)); // In practice, the opposite seems to work too. // Check distance to the right plane to determine if the miter normal points "left" or "right" - normalEC *= sign(czm_planeDistance(v_rightPlaneEC, positionEC3)); - positionEC3 -= normalEC; + normalEC *= sign(czm_planeDistance(v_rightPlaneEC, positionEC.xyz)); // A "perfect" implementation would push along normals according to the angle against forward. // In practice, just extending the shadow volume more than needed works for most cases, @@ -85,8 +113,8 @@ void main() float width = czm_batchTable_width(batchId); v_width = width; v_halfWidth = width * 0.5; - positionEC3 += width * 2.0 * czm_metersPerPixel(positionRelativeToEye) * normalEC;; - gl_Position = czm_depthClampFarPlane(czm_projection * vec4(positionEC3, 1.0)); + positionEC.xyz += width * czm_metersPerPixel(positionEC) * normalEC;; + gl_Position = czm_depthClampFarPlane(czm_projection * positionEC); // Approximate relative screen space direction of the line. // This doesn't work great if the view direction is roughly aligned with the line From 0c224561bfad80359a56f5dbeebd0b6d9cd81a69 Mon Sep 17 00:00:00 2001 From: Kangning Li Date: Tue, 22 May 2018 15:36:52 -0400 Subject: [PATCH 06/39] add interpolation for long line segments, fixes for end normals --- ...ometry.js => GroundLineSegmentGeometry.js} | 92 ++++++------ Source/Core/GroundPolylineGeometry.js | 137 ++++++++++++------ Source/Scene/GroundPolylinePrimitive.js | 120 +++++---------- Source/Shaders/PolylineShadowVolumeVS.glsl | 3 - Source/Workers/createGroundLineGeometry.js | 17 --- .../createGroundLineSegmentGeometry.js | 17 +++ 6 files changed, 186 insertions(+), 200 deletions(-) rename Source/Core/{GroundLineGeometry.js => GroundLineSegmentGeometry.js} (87%) delete mode 100644 Source/Workers/createGroundLineGeometry.js create mode 100644 Source/Workers/createGroundLineSegmentGeometry.js diff --git a/Source/Core/GroundLineGeometry.js b/Source/Core/GroundLineSegmentGeometry.js similarity index 87% rename from Source/Core/GroundLineGeometry.js rename to Source/Core/GroundLineSegmentGeometry.js index 51eb5846df27..a288768c91ec 100644 --- a/Source/Core/GroundLineGeometry.js +++ b/Source/Core/GroundLineSegmentGeometry.js @@ -44,14 +44,14 @@ define([ /** * Description of the volume used to draw a line segment on terrain. * - * @alias GroundLineGeometry + * @alias GroundLineSegmentGeometry * @constructor * * @private * - * @see GroundLineGeometry#createGeometry + * @see GroundLineSegmentGeometry#createGeometry */ - function GroundLineGeometry() { + function GroundLineSegmentGeometry() { this._startBottom = new Cartesian3(); this._startTop = new Cartesian3(); this._startNormal = new Cartesian3(); @@ -63,13 +63,12 @@ define([ this._segmentBottomLength = 0.0; this._segmentBottomLength2D = 0.0; - this._workerName = 'createGroundLineGeometry'; + this._workerName = 'createGroundLineSegmentGeometry'; } var pos1_2dScratch = new Cartesian3(); var pos2_2dScratch = new Cartesian3(); function computeDistance2D(projection, carto1, carto2) { - var ellipsoid = projection.ellipsoid; var pos1_2d = projection.project(carto1, pos1_2dScratch); var pos2_2d = projection.project(carto2, pos2_2dScratch); return Cartesian3.distance(pos1_2d, pos2_2d); @@ -77,8 +76,8 @@ define([ var startCartographicScratch = new Cartographic(); var endCartographicScratch = new Cartographic(); - GroundLineGeometry.fromArrays = function(projection, index, normalsArray, bottomPositionsArray, topPositionsArray) { - var geometry = new GroundLineGeometry(); + GroundLineSegmentGeometry.fromArrays = function(projection, index, normalsArray, bottomPositionsArray, topPositionsArray) { + var geometry = new GroundLineSegmentGeometry(); Cartesian3.unpack(bottomPositionsArray, index, geometry._startBottom); Cartesian3.unpack(topPositionsArray, index, geometry._startTop); @@ -104,7 +103,7 @@ define([ // Note that this has to happen after the length computations return geometry; - } + }; function direction(end, start, result) { Cartesian3.subtract(end, start, result); @@ -136,7 +135,7 @@ define([ Matrix3.multiplyByVector(rotationMatrix, geometry._startNormal, geometry._startNormal); } - dot = Cartesian3.dot(lineDirection, geometry._endNormal) + dot = Cartesian3.dot(lineDirection, geometry._endNormal); if (dot > MITER_BREAK_SMALL || dot < MITER_BREAK_LARGE) { vertexUp = direction(geometry._endTop, geometry._endBottom, vertexUpScratch); angle = dot < MITER_BREAK_LARGE ? CesiumMath.PI_OVER_TWO : -CesiumMath.PI_OVER_TWO; @@ -146,7 +145,7 @@ define([ } } - defineProperties(GroundLineGeometry.prototype, { + defineProperties(GroundLineSegmentGeometry.prototype, { // TODO: doc segmentBottomLength : { get : function() { @@ -164,18 +163,18 @@ define([ * The number of elements used to pack the object into an packArray. * @type {Number} */ - GroundLineGeometry.packedLength = Cartesian3.packedLength * 6 + 2; + GroundLineSegmentGeometry.packedLength = Cartesian3.packedLength * 6 + 2; /** * Stores the provided instance into the provided packArray. * - * @param {GroundLineGeometry} value The value to pack. + * @param {GroundLineSegmentGeometry} value The value to pack. * @param {Number[]} packArray The packArray to pack into. * @param {Number} [startingIndex=0] The index into the packArray at which to start packing the elements. * * @returns {Number[]} The packArray that was packed into */ - GroundLineGeometry.pack = function(value, packArray, startingIndex) { + GroundLineSegmentGeometry.pack = function(value, packArray, startingIndex) { //>>includeStart('debug', pragmas.debug); Check.typeOf.object('value', value); Check.defined('packArray', packArray); @@ -196,23 +195,23 @@ define([ Cartesian3.pack(value._endNormal, packArray, startingIndex); return packArray; - } + }; /** * Retrieves an instance from a packed packArray. * * @param {Number[]} packArray The packed packArray. * @param {Number} [startingIndex=0] The starting index of the element to be unpacked. - * @param {GroundLineGeometry} [result] The object into which to store the result. - * @returns {GroundLineGeometry} The modified result parameter or a new RectangleGeometry instance if one was not provided. + * @param {GroundLineSegmentGeometry} [result] The object into which to store the result. + * @returns {GroundLineSegmentGeometry} The modified result parameter or a new RectangleGeometry instance if one was not provided. */ - GroundLineGeometry.unpack = function(packArray, startingIndex, result) { + GroundLineSegmentGeometry.unpack = function(packArray, startingIndex, result) { //>>includeStart('debug', pragmas.debug); Check.defined('packArray', packArray); //>>includeEnd('debug'); startingIndex = defaultValue(startingIndex, 0); - result = defaultValue(result, new GroundLineGeometry()); + result = defaultValue(result, new GroundLineSegmentGeometry()); Cartesian3.unpack(packArray, startingIndex, result._startBottom); startingIndex += Cartesian3.packedLength; @@ -227,7 +226,7 @@ define([ Cartesian3.unpack(packArray, startingIndex, result._endNormal); return result; - } + }; var startBottomScratch = new Cartesian3(); var endBottomScratch = new Cartesian3(); @@ -235,9 +234,9 @@ define([ var startTopScratch = new Cartesian3(); /** * - * @param {GroundLineGeometry} groundPolylineSegmentGeometry + * @param {GroundLineSegmentGeometry} groundPolylineSegmentGeometry */ - GroundLineGeometry.createGeometry = function(groundPolylineSegmentGeometry) { + GroundLineSegmentGeometry.createGeometry = function(groundPolylineSegmentGeometry) { var startBottom = groundPolylineSegmentGeometry._startBottom; var endBottom = groundPolylineSegmentGeometry._endBottom; var endTop = groundPolylineSegmentGeometry._endTop; @@ -248,24 +247,24 @@ define([ var positions = new Float64Array(24); // 8 vertices // Push out by 1.0 in the right direction - var startBottom = Cartesian3.add(groundPolylineSegmentGeometry._startBottom, startNormal, startBottomScratch); - var endBottom = Cartesian3.add(groundPolylineSegmentGeometry._endBottom, endNormal, endBottomScratch); - var endTop = Cartesian3.add(groundPolylineSegmentGeometry._endTop, endNormal, endTopScratch); - var startTop = Cartesian3.add(groundPolylineSegmentGeometry._startTop, startNormal, startTopScratch); - Cartesian3.pack(startBottom, positions, 0); - Cartesian3.pack(endBottom, positions, 1 * 3); - Cartesian3.pack(endTop, positions, 2 * 3); - Cartesian3.pack(startTop, positions, 3 * 3); + var pushedStartBottom = Cartesian3.add(startBottom, startNormal, startBottomScratch); + var pushedEndBottom = Cartesian3.add(endBottom, endNormal, endBottomScratch); + var pushedEndTop = Cartesian3.add(endTop, endNormal, endTopScratch); + var pushedStartTop = Cartesian3.add(startTop, startNormal, startTopScratch); + Cartesian3.pack(pushedStartBottom, positions, 0); + Cartesian3.pack(pushedEndBottom, positions, 1 * 3); + Cartesian3.pack(pushedEndTop, positions, 2 * 3); + Cartesian3.pack(pushedStartTop, positions, 3 * 3); // Push out by 1.0 in the left direction - startBottom = Cartesian3.subtract(groundPolylineSegmentGeometry._startBottom, startNormal, startBottomScratch); - endBottom = Cartesian3.subtract(groundPolylineSegmentGeometry._endBottom, endNormal, endBottomScratch); - endTop = Cartesian3.subtract(groundPolylineSegmentGeometry._endTop, endNormal, endTopScratch); - startTop = Cartesian3.subtract(groundPolylineSegmentGeometry._startTop, startNormal, startTopScratch); - Cartesian3.pack(startBottom, positions, 4 * 3); - Cartesian3.pack(endBottom, positions, 5 * 3); - Cartesian3.pack(endTop, positions, 6 * 3); - Cartesian3.pack(startTop, positions, 7 * 3); + pushedStartBottom = Cartesian3.subtract(startBottom, startNormal, startBottomScratch); + pushedEndBottom = Cartesian3.subtract(endBottom, endNormal, endBottomScratch); + pushedEndTop = Cartesian3.subtract(endTop, endNormal, endTopScratch); + pushedStartTop = Cartesian3.subtract(startTop, startNormal, startTopScratch); + Cartesian3.pack(pushedStartBottom, positions, 4 * 3); + Cartesian3.pack(pushedEndBottom, positions, 5 * 3); + Cartesian3.pack(pushedEndTop, positions, 6 * 3); + Cartesian3.pack(pushedStartTop, positions, 7 * 3); var indices = [ 0, 1, 2, 0, 2, 3, // right @@ -289,11 +288,11 @@ define([ indices : new Uint16Array(indices), boundingSphere : BoundingSphere.fromPoints([startBottom, endBottom, endTop, startTop]) }); - } + }; - GroundLineGeometry.prototype.segmentLength = function() { + GroundLineSegmentGeometry.prototype.segmentLength = function() { return Cartesian3.distance(this._startBottom, this._endBottom); - } + }; var positionCartographicScratch = new Cartographic(); var normalEndpointScratch = new Cartesian3(); @@ -304,7 +303,7 @@ define([ var normalEndpointCartographic = ellipsoid.cartesianToCartographic(normalEndpoint, positionCartographicScratch); normalEndpointCartographic.height = 0.0; var normalEndpointProjected = projection.project(normalEndpointCartographic, result); - result = Cartesian3.subtract(result, projectedPosition, result); + result = Cartesian3.subtract(normalEndpointProjected, projectedPosition, result); result.z = 0.0; result = Cartesian3.normalize(result, result); return result; @@ -370,7 +369,7 @@ define([ componentDatatype: ComponentDatatype.FLOAT, componentsPerAttribute: 3, normalize: false, - value : [lengthSoFar2D, segmentLength2D, totalLength2D] + value : [lengthSoFar2D, segmentLength2D, totalLength2D] // TODO: some floating point problems with this at huge distances! }); attributes.startHighLow2D = startHighLow2D_attribute; @@ -381,7 +380,6 @@ define([ var encodeScratch = new EncodedCartesian3(); var offsetScratch = new Cartesian3(); - var offsetScratch = new Cartesian3(); var normal1Scratch = new Cartesian3(); var normal2Scratch = new Cartesian3(); var rightScratch = new Cartesian3(); @@ -399,7 +397,7 @@ define([ * texture coordinate local to the line. This texture coordinate then needs to be mapped to the entire line, * which requires an additional set of attributes. * - * @param {GroundLineGeometry} geometry GroundLineGeometry + * @param {GroundLineSegmentGeometry} geometry GroundLineSegmentGeometry * @param {MapProjection} projection The MapProjection used for 2D and Columbus View. * @param {Number} lengthSoFar Distance of the segment's start point along the line * @param {Number} segmentLength Length of the segment @@ -409,7 +407,7 @@ define([ * @param {Number} totalLength2D Total length of the entire line in 2D * @returns {Object} An object containing GeometryInstanceAttributes for the input geometry */ - GroundLineGeometry.getAttributes = function(geometry, projection, lengthSoFar, segmentLength, totalLength, lengthSoFar2D, segmentLength2D, totalLength2D) { + GroundLineSegmentGeometry.getAttributes = function(geometry, projection, lengthSoFar, segmentLength, totalLength, lengthSoFar2D, segmentLength2D, totalLength2D) { //>>includeStart('debug', pragmas.debug); Check.typeOf.object('geometry', geometry); Check.typeOf.object('projection', projection); @@ -506,9 +504,9 @@ define([ texcoordNormalization : texcoordNormalization_attribute }; - add2DAttributes(attributes, geometry, projection, lengthSoFar2D, segmentLength2D, totalLength2D) + add2DAttributes(attributes, geometry, projection, lengthSoFar2D, segmentLength2D, totalLength2D); return attributes; }; - return GroundLineGeometry; + return GroundLineSegmentGeometry; }); diff --git a/Source/Core/GroundPolylineGeometry.js b/Source/Core/GroundPolylineGeometry.js index b18a174a7a2f..8694201b7296 100644 --- a/Source/Core/GroundPolylineGeometry.js +++ b/Source/Core/GroundPolylineGeometry.js @@ -7,6 +7,7 @@ define([ './defined', './defineProperties', './Ellipsoid', + './EllipsoidGeodesic', './EncodedCartesian3', './Matrix3', './Plane', @@ -20,6 +21,7 @@ define([ defined, defineProperties, Ellipsoid, + EllipsoidGeodesic, EncodedCartesian3, Matrix3, Plane, @@ -34,7 +36,7 @@ define([ * * @param {Object} [options] Options with the following properties: * @param {Cartographic[]} [options.positions] An array of {@link Cartographic} defining the polyline's points. Heights will be ignored. - * @param {Number} [options.granularity=CesiumMath.RADIANS_PER_DEGREE] The distance interval used for interpolating options.points. Zero indicates no interpolation. + * @param {Number} [options.granularity=9999.0] The distance interval used for interpolating options.points. Defaults to 9999.0 meters. Zero indicates no interpolation. * @param {Boolean} [options.loop=false] Whether during geometry creation a line segment will be added between the last and first line positions to make this Polyline a loop. * @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] Ellipsoid for projecting cartographic coordinates to cartesian * @param {Number} [options.width=1.0] Integer width for the polyline. @@ -44,15 +46,14 @@ define([ function GroundPolylineGeometry(options) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); - var granularity = defaultValue(options.granularity, CesiumMath.RADIANS_PER_DEGREE); - - this._positions = interpolatePoints(defaultValue(options.positions, []), granularity); + this._positions = defaultValue(options.positions, []); /** * The distance interval used for interpolating options.points. Zero indicates no interpolation. + * Default of 9999.0 allows sub-centimeter accuracy with 32 bit floating point. * @type {Boolean} */ - this.granularity = granularity; + this.granularity = defaultValue(options.granularity, 9999.0); /** * Whether during geometry creation a line segment will be added between the last and first line positions to make this Polyline a loop. @@ -65,11 +66,55 @@ define([ this.width = defaultValue(options.width, 1.0); } - function interpolatePoints(positions, granularity) { - var interpolatedPositions = []; + var cart3Scratch1 = new Cartesian3(); + var cart3Scratch2 = new Cartesian3(); + var cart3Scratch3 = new Cartesian3(); + function computeRightNormal(start, end, wallHeight, ellipsoid, result) { + var startBottom = getPosition(ellipsoid, start, 0.0, cart3Scratch1); + var startTop = getPosition(ellipsoid, start, wallHeight, cart3Scratch2); + var endBottom = getPosition(ellipsoid, end, 0.0, cart3Scratch3); + + var up = direction(startTop, startBottom, cart3Scratch2); + var forward = direction(endBottom, startBottom, cart3Scratch3); + + Cartesian3.cross(forward, up, result); + return Cartesian3.normalize(result, result); + } - // TODO: actually interpolate - return positions; + var interpolatedCartographicScratch = new Cartographic(); + var interpolatedBottomScratch = new Cartesian3(); + var interpolatedTopScratch = new Cartesian3(); + var interpolatedNormalScratch = new Cartesian3(); + function interpolateSegment(start, end, wallHeight, granularity, ellipsoid, normalsArray, bottomPositionsArray, topPositionsArray) { + if (granularity === 0.0) { + return; + } + var ellipsoidGeodesic = new EllipsoidGeodesic(start, end, ellipsoid); + var surfaceDistance = ellipsoidGeodesic.surfaceDistance; + if (surfaceDistance < granularity) { + return; + } + + // Compute rightwards normal applicable at all interpolated points + var interpolatedNormal = computeRightNormal(start, end, wallHeight, ellipsoid, interpolatedNormalScratch); + + var segments = Math.ceil(surfaceDistance / granularity); + var interpointDistance = surfaceDistance / segments; + var distanceFromStart = interpointDistance; + var pointsToAdd = segments - 1; + var packIndex = normalsArray.length; + for (var i = 0; i < pointsToAdd; i++) { + var interpolatedCartographic = ellipsoidGeodesic.interpolateUsingSurfaceDistance(distanceFromStart, interpolatedCartographicScratch); + var interpolatedBottom = getPosition(ellipsoid, interpolatedCartographic, 0.0, interpolatedBottomScratch); + var interpolatedTop = getPosition(ellipsoid, interpolatedCartographic, wallHeight, interpolatedTopScratch); + + Cartesian3.pack(interpolatedNormal, normalsArray, packIndex); + Cartesian3.pack(interpolatedBottom, bottomPositionsArray, packIndex); + Cartesian3.pack(interpolatedTop, topPositionsArray, packIndex); + + packIndex += 3; + distanceFromStart += interpointDistance; + } } var heightlessCartographicScratch = new Cartographic(); @@ -96,7 +141,6 @@ define([ } }); - var colinearCartographicScratch = new Cartographic(); var previousBottomScratch = new Cartesian3(); var vertexBottomScratch = new Cartesian3(); var vertexTopScratch = new Cartesian3(); @@ -106,6 +150,7 @@ define([ var cartographics = groundPolylineGeometry.positions; var loop = groundPolylineGeometry.loop; var ellipsoid = groundPolylineGeometry.ellipsoid; + var granularity = groundPolylineGeometry.granularity; // TODO: throw errors/negate loop if not enough points @@ -113,10 +158,9 @@ define([ var index; var i; - var floatCount = (cartographicsLength + (loop ? 1 : 0)) * 3; - var normalsArray = new Float32Array(floatCount); - var bottomPositionsArray = new Float64Array(floatCount); - var topPositionsArray = new Float64Array(floatCount); + var normalsArray = []; + var bottomPositionsArray = []; + var topPositionsArray = []; var previousBottom = previousBottomScratch; var vertexBottom = vertexBottomScratch; @@ -124,28 +168,29 @@ define([ var nextBottom = nextBottomScratch; var vertexNormal = vertexNormalScratch; - // First point - generate fake "previous" position for computing normal + // First point - either loop or attach a "perpendicular" normal var startCartographic = cartographics[0]; var nextCartographic = cartographics[1]; - var prestartCartographic; + + var prestartCartographic = cartographics[cartographicsLength - 1]; + previousBottom = getPosition(ellipsoid, prestartCartographic, 0.0, previousBottom); + nextBottom = getPosition(ellipsoid, nextCartographic, 0.0, nextBottom); + vertexBottom = getPosition(ellipsoid, startCartographic, 0.0, vertexBottom); + vertexTop = getPosition(ellipsoid, startCartographic, wallHeight, vertexTop); + if (loop) { - prestartCartographic = cartographics[cartographicsLength - 1]; + vertexNormal = computeVertexMiterNormal(previousBottom, vertexBottom, vertexTop, nextBottom, vertexNormal); } else { - prestartCartographic = colinearCartographicScratch; - prestartCartographic.longitude = startCartographic.longitude - (nextCartographic.longitude - startCartographic.longitude); - prestartCartographic.latitude = startCartographic.latitude - (nextCartographic.latitude - startCartographic.latitude); + vertexNormal = computeRightNormal(startCartographic, nextCartographic, wallHeight, ellipsoid, vertexNormal); } - getPosition(ellipsoid, prestartCartographic, 0.0, previousBottom); - getPosition(ellipsoid, startCartographic, 0.0, vertexBottom); - getPosition(ellipsoid, startCartographic, wallHeight, vertexTop); - getPosition(ellipsoid, nextCartographic, 0.0, nextBottom); - computeVertexMiterNormal(previousBottom, vertexBottom, vertexTop, nextBottom, vertexNormal); - Cartesian3.pack(vertexNormal, normalsArray, 0); Cartesian3.pack(vertexBottom, bottomPositionsArray, 0); Cartesian3.pack(vertexTop, topPositionsArray, 0); + // Interpolate between start and start + 1 + interpolateSegment(startCartographic, nextCartographic, wallHeight, granularity, ellipsoid, normalsArray, bottomPositionsArray, topPositionsArray); + // All inbetween points for (i = 1; i < cartographicsLength - 1; ++i) { previousBottom = Cartesian3.clone(vertexBottom, previousBottom); @@ -155,38 +200,39 @@ define([ computeVertexMiterNormal(previousBottom, vertexBottom, vertexTop, nextBottom, vertexNormal); - index = i * 3; + index = normalsArray.length; Cartesian3.pack(vertexNormal, normalsArray, index); Cartesian3.pack(vertexBottom, bottomPositionsArray, index); Cartesian3.pack(vertexTop, topPositionsArray, index); + + interpolateSegment(cartographics[i], cartographics[i + 1], wallHeight, granularity, ellipsoid, normalsArray, bottomPositionsArray, topPositionsArray); } - // Last point - generate fake "next" position for computing normal + // Last point - either loop or attach a "perpendicular" normal var endCartographic = cartographics[cartographicsLength - 1]; var preEndCartographic = cartographics[cartographicsLength - 2]; - var postEndCartographic; + vertexBottom = getPosition(ellipsoid, endCartographic, 0.0, vertexBottom); + vertexTop = getPosition(ellipsoid, endCartographic, wallHeight, vertexTop); + if (loop) { - postEndCartographic = cartographics[0]; + var postEndCartographic = cartographics[0]; + previousBottom = getPosition(ellipsoid, preEndCartographic, 0.0, previousBottom); + nextBottom = getPosition(ellipsoid, postEndCartographic, 0.0, nextBottom); + + vertexNormal = computeVertexMiterNormal(previousBottom, vertexBottom, vertexTop, nextBottom, vertexNormal); } else { - postEndCartographic = colinearCartographicScratch; - postEndCartographic.longitude = endCartographic.longitude + (endCartographic.longitude - preEndCartographic.longitude); - postEndCartographic.latitude = endCartographic.latitude + (endCartographic.latitude - preEndCartographic.latitude); + vertexNormal = computeRightNormal(preEndCartographic, endCartographic, wallHeight, ellipsoid, vertexNormal); } - getPosition(ellipsoid, preEndCartographic, 0.0, previousBottom); - getPosition(ellipsoid, endCartographic, 0.0, vertexBottom); - getPosition(ellipsoid, endCartographic, wallHeight, vertexTop); - getPosition(ellipsoid, postEndCartographic, 0.0, nextBottom); - computeVertexMiterNormal(previousBottom, vertexBottom, vertexTop, nextBottom, vertexNormal); - - index = (cartographicsLength - 1) * 3; + index = normalsArray.length; Cartesian3.pack(vertexNormal, normalsArray, index); Cartesian3.pack(vertexBottom, bottomPositionsArray, index); Cartesian3.pack(vertexTop, topPositionsArray, index); if (loop) { - index = cartographicsLength * 3; + interpolateSegment(endCartographic, startCartographic, wallHeight, granularity, ellipsoid, normalsArray, bottomPositionsArray, topPositionsArray); + index = normalsArray.length; // Copy the first vertex for (i = 0; i < 3; ++i) { normalsArray[index + i] = normalsArray[i]; @@ -196,15 +242,12 @@ define([ } return { - rightFacingNormals : normalsArray, - bottomPositions : bottomPositionsArray, - topPositions : topPositionsArray + rightFacingNormals : new Float32Array(normalsArray), + bottomPositions : new Float64Array(bottomPositionsArray), + topPositions : new Float64Array(topPositionsArray) }; }; - var MIN_HEIGHT = 0.0; - var MAX_HEIGHT = 10000.0; - function direction(target, origin, result) { Cartesian3.subtract(target, origin, result); Cartesian3.normalize(result, result); diff --git a/Source/Scene/GroundPolylinePrimitive.js b/Source/Scene/GroundPolylinePrimitive.js index 0b65ddd175f5..ec9f55af5a8f 100644 --- a/Source/Scene/GroundPolylinePrimitive.js +++ b/Source/Scene/GroundPolylinePrimitive.js @@ -4,11 +4,12 @@ define([ '../Core/defaultValue', '../Core/defined', '../Core/defineProperties', + '../Core/destroyObject', '../Core/DeveloperError', '../Core/GeometryInstance', '../Core/GeometryInstanceAttribute', '../Core/GroundPolylineGeometry', - '../Core/GroundLineGeometry', + '../Core/GroundLineSegmentGeometry', '../Core/isArray', '../Core/Matrix4', '../Shaders/PolylineShadowVolumeVS', @@ -20,7 +21,7 @@ define([ '../Renderer/ShaderSource', './GroundPrimitive', './Material', - './MaterialAppearance', + './PolylineMaterialAppearance', './Primitive', './SceneMode' ], function( @@ -29,11 +30,12 @@ define([ defaultValue, defined, defineProperties, + destroyObject, DeveloperError, GeometryInstance, GeometryInstanceAttribute, GroundPolylineGeometry, - GroundLineGeometry, + GroundLineSegmentGeometry, isArray, Matrix4, PolylineShadowVolumeVS, @@ -45,7 +47,7 @@ define([ ShaderSource, GroundPrimitive, Material, - MaterialAppearance, + PolylineMaterialAppearance, Primitive, SceneMode) { 'use strict'; @@ -58,7 +60,7 @@ define([ * * @param {Object} [options] Object with the following properties: * @param {GeometryInstance[]|GeometryInstance} [options.polylineGeometryInstances] GeometryInstances containing GroundPolylineGeometry - * @param {Material} [options.material] The Material used to render the polyline. Defaults to a white color. + * @param {Appearance} [options.appearance] The Appearance used to render the polyline. Defaults to a white color. Only {@link PolylineMaterialAppearance} is supported at this time. * @param {Boolean} [options.show=true] Determines if this primitive will be shown. * @param {Boolean} [options.releaseGeometryInstances=true] When true, the primitive does not keep a reference to generated geometry or input cartographics to save memory. * @param {Boolean} [options.allowPicking=true] When true, each geometry instance will only be pickable with {@link Scene#pick}. When false, GPU memory is saved. @@ -72,13 +74,12 @@ define([ this.polylineGeometryInstances = options.polylineGeometryInstances; - var material = options.material; - if (!defined(material)) { - material = Material.fromType('Color'); + var appearance = options.appearance; + if (!defined(appearance)) { + appearance = new PolylineMaterialAppearance(); } - this._material = material; - this._appearance = generatePolylineAppearance(material); + this.appearance = appearance; this.show = defaultValue(options.show, true); @@ -119,7 +120,7 @@ define([ asynchronous : this.asynchronous, _createShaderProgramFunction : undefined, _createCommandsFunction : undefined, - _updateAndQueueCommandsFunction : undefined, + _updateAndQueueCommandsFunction : undefined }; this._primitive = undefined; @@ -143,31 +144,6 @@ define([ this._attributeSynchronizerCache = {}; } - function generatePolylineAppearance(material, renderState) { - return new MaterialAppearance({ - flat : true, - translucent : true, - closed : false, - materialSupport : MaterialAppearance.MaterialSupport.BASIC, - vertexShaderSource : PolylineShadowVolumeVS, - fragmentShaderSource : PolylineShadowVolumeFS, - material : material, - renderState : renderState - }); - } - - defineProperties(GroundPolylinePrimitive.prototype, { - material : { - get : function() { - return this._material; - }, - set : function(value) { - this._material = value; - this._appearance = generatePolylineAppearance(value, this._renderState); - } - } - }); - function decompose(geometryInstance, projection, polylineSegmentInstances, idsToInstanceIndices) { var groundPolylineGeometry = geometryInstance.geometry; // TODO: check and throw using instanceof? @@ -188,7 +164,7 @@ define([ var lineGeometries = new Array(verticesLength / 3); var index = 0; for (i = 0; i < verticesLength - 3; i += 3) { - groundPolylineSegmentGeometry = GroundLineGeometry.fromArrays(projection, i, rightFacingNormals, bottomPositions, topPositions); + groundPolylineSegmentGeometry = GroundLineSegmentGeometry.fromArrays(projection, i, rightFacingNormals, bottomPositions, topPositions); totalLength += groundPolylineSegmentGeometry.segmentBottomLength; totalLength2D += groundPolylineSegmentGeometry.segmentBottomLength2D; @@ -203,7 +179,7 @@ define([ groundPolylineSegmentGeometry = lineGeometries[index++]; var segmentLength = groundPolylineSegmentGeometry.segmentBottomLength; var segmentLength2D = groundPolylineSegmentGeometry.segmentBottomLength2D; - var attributes = GroundLineGeometry.getAttributes(groundPolylineSegmentGeometry, projection, lengthSoFar, segmentLength, totalLength, lengthSoFar2D, segmentLength2D, totalLength2D); + var attributes = GroundLineSegmentGeometry.getAttributes(groundPolylineSegmentGeometry, projection, lengthSoFar, segmentLength, totalLength, lengthSoFar2D, segmentLength2D, totalLength2D); lengthSoFar += segmentLength; lengthSoFar2D += segmentLength2D; @@ -214,6 +190,13 @@ define([ value : [groundPolylineGeometry.width] }); + attributes.show = new GeometryInstanceAttribute({ + componentDatatype: ComponentDatatype.UNSIGNED_BYTE, + componentsPerAttribute: 1, + normalize : false, + value : [true] + }); + polylineSegmentInstances.push(new GeometryInstance({ geometry : groundPolylineSegmentGeometry, attributes : attributes, @@ -478,40 +461,24 @@ define([ return; } - /* - if (!GroundPrimitive._initialized) { - //>>includeStart('debug', pragmas.debug); - if (!this.asynchronous) { - throw new DeveloperError('For synchronous GroundPolylinePrimitives, you must call GroundPrimitive.initializeTerrainHeights() and wait for the returned promise to resolve.'); - } - //>>includeEnd('debug'); - - GroundPrimitive.initializeTerrainHeights(); - return; - }*/ - var i; var that = this; var primitiveOptions = this._primitiveOptions; if (!defined(this._primitive)) { // Decompose GeometryInstances into an array of GeometryInstances containing GroundPolylineSegmentGeometries. - // Compute the overall bounding volume - // TODO later: compute rectangle for getting min/max heights - var ellipsoid = frameState.mapProjection.ellipsoid; - + // TODO: compute rectangle for getting min/max heights var polylineSegmentInstances = []; var geometryInstances = isArray(this.polylineGeometryInstances) ? this.polylineGeometryInstances : [this.polylineGeometryInstances]; var geometryInstancesLength = geometryInstances.length; for (i = 0; i < geometryInstancesLength; ++i) { var geometryInstance = geometryInstances[i]; - var id = geometryInstance.id; decompose(geometryInstance, frameState.mapProjection, polylineSegmentInstances, this._idsToInstanceIndices); } primitiveOptions.geometryInstances = polylineSegmentInstances; - primitiveOptions.appearance = this._appearance; + primitiveOptions.appearance = this.appearance; primitiveOptions._createShaderProgramFunction = function(primitive, frameState, appearance) { createShaderProgram(that, frameState, appearance); @@ -539,7 +506,7 @@ define([ } }); } - this._primitive.appearance = this._appearance; + this._primitive.appearance = this.appearance; this._primitive.show = this.show; this._primitive.debugShowBoundingVolume = this.debugShowBoundingVolume; this._primitive.update(frameState); @@ -568,44 +535,25 @@ define([ }; // An object that, on setting an attribute, will set all the instances' attributes. + var userAttributeNames = ['width', 'show']; + var userAttributeCount = userAttributeNames.length; function InstanceAttributeSynchronizer(batchTable, firstInstanceIndex, lastInstanceIndex, batchTableAttributeIndices) { var properties = {}; - for (var name in batchTableAttributeIndices) { - if (batchTableAttributeIndices.hasOwnProperty(name)) { - var attributeIndex = batchTableAttributeIndices[name]; - properties[name] = { - get : createGetFunction(batchTable, firstInstanceIndex, attributeIndex), - set : createSetFunction(batchTable, firstInstanceIndex, lastInstanceIndex, attributeIndex) - }; - - // TODO: make some of these read only - } + for (var i = 0; i < userAttributeCount; i++) { + var name = userAttributeNames[i]; + var attributeIndex = batchTableAttributeIndices[name]; + properties[name] = { + get : createGetFunction(batchTable, firstInstanceIndex, attributeIndex), + set : createSetFunction(batchTable, firstInstanceIndex, lastInstanceIndex, attributeIndex) + }; } defineProperties(this, properties); } - function getAttributeValue(value) { - var componentsPerAttribute = value.length; - if (componentsPerAttribute === 1) { - return value[0]; - } else if (componentsPerAttribute === 2) { - return Cartesian2.unpack(value, 0, scratchGetAttributeCartesian2); - } else if (componentsPerAttribute === 3) { - return Cartesian3.unpack(value, 0, scratchGetAttributeCartesian3); - } else if (componentsPerAttribute === 4) { - return Cartesian4.unpack(value, 0, scratchGetAttributeCartesian4); - } - } - function createSetFunction(batchTable, firstInstanceIndex, lastInstanceIndex, attributeIndex) { return function(value) { - //>>includeStart('debug', pragmas.debug); - if (!defined(value) || !defined(value.length) || value.length < 1 || value.length > 4) { - throw new DeveloperError('value must be and array with length between 1 and 4.'); - } - //>>includeEnd('debug'); for (var i = firstInstanceIndex; i <= lastInstanceIndex; i++) { - var attributeValue = getAttributeValue(value); + var attributeValue = value[0]; batchTable.setBatchedAttribute(i, attributeIndex, attributeValue); } }; diff --git a/Source/Shaders/PolylineShadowVolumeVS.glsl b/Source/Shaders/PolylineShadowVolumeVS.glsl index 2b48036e304c..1f0d85472bc1 100644 --- a/Source/Shaders/PolylineShadowVolumeVS.glsl +++ b/Source/Shaders/PolylineShadowVolumeVS.glsl @@ -117,9 +117,6 @@ void main() gl_Position = czm_depthClampFarPlane(czm_projection * positionEC); // Approximate relative screen space direction of the line. - // This doesn't work great if the view direction is roughly aligned with the line - // Directly copying what PolylineCommon.glsl does using ecStart and ecEnd is even worse. - // Might be worth just having this point in some direction if it's almost zero? herm no good... should probably go read that math later. vec2 approxLineDirection = normalize(vec2(forwardDirectionEC.x, -forwardDirectionEC.y)); approxLineDirection.y = czm_branchFreeTernaryFloat(approxLineDirection.x == 0.0 && approxLineDirection.y == 0.0, -1.0, approxLineDirection.y); v_polylineAngle = czm_fastApproximateAtan(approxLineDirection.x, approxLineDirection.y); diff --git a/Source/Workers/createGroundLineGeometry.js b/Source/Workers/createGroundLineGeometry.js deleted file mode 100644 index 690835286619..000000000000 --- a/Source/Workers/createGroundLineGeometry.js +++ /dev/null @@ -1,17 +0,0 @@ -define([ - '../Core/defined', - '../Core/GroundLineGeometry' - ], function( - defined, - GroundLineGeometry) { - 'use strict'; - - function createGroundLineGeometry(groundPolylineSegmentGeometry, offset) { - if (defined(offset)) { - groundPolylineSegmentGeometry = GroundLineGeometry.unpack(groundPolylineSegmentGeometry, offset); - } - return GroundLineGeometry.createGeometry(groundPolylineSegmentGeometry); - } - - return createGroundLineGeometry; -}); diff --git a/Source/Workers/createGroundLineSegmentGeometry.js b/Source/Workers/createGroundLineSegmentGeometry.js new file mode 100644 index 000000000000..d6c8a3d5098d --- /dev/null +++ b/Source/Workers/createGroundLineSegmentGeometry.js @@ -0,0 +1,17 @@ +define([ + '../Core/defined', + '../Core/GroundLineSegmentGeometry' + ], function( + defined, + GroundLineSegmentGeometry) { + 'use strict'; + + function createGroundLineSegmentGeometry(groundPolylineSegmentGeometry, offset) { + if (defined(offset)) { + groundPolylineSegmentGeometry = GroundLineSegmentGeometry.unpack(groundPolylineSegmentGeometry, offset); + } + return GroundLineSegmentGeometry.createGeometry(groundPolylineSegmentGeometry); + } + + return createGroundLineSegmentGeometry; +}); From ac2993451599b291a579d138faec06e23da6a717 Mon Sep 17 00:00:00 2001 From: Kangning Li Date: Tue, 22 May 2018 16:17:19 -0400 Subject: [PATCH 07/39] Sandcastle example for polylines on terrain via Primitive API [ci skip] --- .../development/Polylines On Terrain.html | 229 ++++++++++++++++++ .../development/Polylines On Terrain.jpg | Bin 0 -> 24930 bytes 2 files changed, 229 insertions(+) create mode 100644 Apps/Sandcastle/gallery/development/Polylines On Terrain.html create mode 100644 Apps/Sandcastle/gallery/development/Polylines On Terrain.jpg diff --git a/Apps/Sandcastle/gallery/development/Polylines On Terrain.html b/Apps/Sandcastle/gallery/development/Polylines On Terrain.html new file mode 100644 index 000000000000..96c438c79583 --- /dev/null +++ b/Apps/Sandcastle/gallery/development/Polylines On Terrain.html @@ -0,0 +1,229 @@ + + + + + + + + + Cesium Demo + + + + + + +
+

Loading...

+
+ + + + + +
Width + + +
+
+ + + diff --git a/Apps/Sandcastle/gallery/development/Polylines On Terrain.jpg b/Apps/Sandcastle/gallery/development/Polylines On Terrain.jpg new file mode 100644 index 0000000000000000000000000000000000000000..0b87ebcf318e1b92ec2da7c1fdec81614bd16df0 GIT binary patch literal 24930 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 literal 0 HcmV?d00001 From ad8d711587dcda42dc7bb5ceb7d23433cb69c0c1 Mon Sep 17 00:00:00 2001 From: Kangning Li Date: Thu, 24 May 2018 16:03:25 -0400 Subject: [PATCH 08/39] fix some artifacts, fix doubledraw, add per-instance color support [ci skip] --- .../development/Polylines On Terrain.html | 113 +++++++++++------- Source/Core/GroundLineSegmentGeometry.js | 13 +- Source/Scene/GroundPolylinePrimitive.js | 50 +++++--- Source/Shaders/PolylineShadowVolumeFS.glsl | 12 +- Source/Shaders/PolylineShadowVolumeVS.glsl | 17 ++- 5 files changed, 134 insertions(+), 71 deletions(-) diff --git a/Apps/Sandcastle/gallery/development/Polylines On Terrain.html b/Apps/Sandcastle/gallery/development/Polylines On Terrain.html index 96c438c79583..a590de2ba465 100644 --- a/Apps/Sandcastle/gallery/development/Polylines On Terrain.html +++ b/Apps/Sandcastle/gallery/development/Polylines On Terrain.html @@ -113,7 +113,10 @@ width : 16.0, loop : false }), - id : 'polyline1' + id : 'polyline1', + attributes : { + color : Cesium.ColorGeometryInstanceAttribute.fromColor(Cesium.Color.fromCssColorString('green').withAlpha(0.7)) + } }); var instance2 = new Cesium.GeometryInstance({ @@ -122,7 +125,10 @@ width : 8.0, loop : true }), - id : 'polyline2' + id : 'polyline2', + attributes : { + color : Cesium.ColorGeometryInstanceAttribute.fromColor(Cesium.Color.fromCssColorString('#67ADDF').withAlpha(0.7)) + } }); polylineOnTerrainPrimitive = new Cesium.GroundPolylinePrimitive({ @@ -131,28 +137,39 @@ }); scene.primitives.add(polylineOnTerrainPrimitive); -applyGlowMaterial(); - +function applyPerInstanceColor() { + polylineOnTerrainPrimitive.appearance = new Cesium.PolylineColorAppearance(); +} function applyColorMaterial() { - polylineOnTerrainPrimitive.appearance.material = Cesium.Material.fromType('Color', { - color : new Cesium.Color(0.0, 1.0, 1.0, 1.0) + polylineOnTerrainPrimitive.appearance = new Cesium.PolylineMaterialAppearance({ + material : Cesium.Material.fromType('Color', { + color : new Cesium.Color(1.0, 0.0, 1.0, 1.0) + }) }); } function applyGlowMaterial() { - polylineOnTerrainPrimitive.appearance.material = Cesium.Material.fromType('PolylineGlow', { - innerWidth : 1.0 + polylineOnTerrainPrimitive.appearance = new Cesium.PolylineMaterialAppearance({ + material : Cesium.Material.fromType('PolylineGlow', { + innerWidth : 1.0 + }) }); } function applyArrow() { - polylineOnTerrainPrimitive.appearance.material = Cesium.Material.fromType('PolylineArrow'); + polylineOnTerrainPrimitive.appearance = new Cesium.PolylineMaterialAppearance({ + material : Cesium.Material.fromType('PolylineArrow') + }); } function applyDash() { - polylineOnTerrainPrimitive.appearance.material = Cesium.Material.fromType('PolylineDash', { - color : Cesium.Color.YELLOW + polylineOnTerrainPrimitive.appearance = new Cesium.PolylineMaterialAppearance({ + material : Cesium.Material.fromType('PolylineDash', { + color : Cesium.Color.YELLOW + }) }); } function applyOutline() { - polylineOnTerrainPrimitive.appearance.material = Cesium.Material.fromType('PolylineOutline'); + polylineOnTerrainPrimitive.appearance = new Cesium.PolylineMaterialAppearance({ + material : Cesium.Material.fromType('PolylineOutline') + }); } Sandcastle.addToolbarButton('Toggle instance show', function() { @@ -180,41 +197,47 @@ lookAt(); }); +applyPerInstanceColor(); + lookAt(); - Sandcastle.addToolbarMenu([{ - text : 'Materials' - }, { - text : 'Color', - onselect : function() { - applyColorMaterial(); - Sandcastle.highlight(applyColorMaterial); - } - }, { - text : 'Glow', - onselect : function() { - applyGlowMaterial(); - Sandcastle.highlight(applyGlowMaterial); - } - }, { - text : 'Arrow', - onselect : function() { - applyArrow(); - Sandcastle.highlight(applyArrow); - } - }, { - text : 'Dash', - onselect : function() { - applyDash(); - Sandcastle.highlight(applyDash); - } - }, { - text : 'Outline', - onselect : function() { - applyOutline(); - Sandcastle.highlight(applyOutline); - } - }]); +Sandcastle.addToolbarMenu([{ + text : 'Per Instance Color', + onselect : function() { + applyPerInstanceColor(); + Sandcastle.highlight(applyPerInstanceColor); + } +}, { + text : 'Color', + onselect : function() { + applyColorMaterial(); + Sandcastle.highlight(applyColorMaterial); + } +}, { + text : 'Glow', + onselect : function() { + applyGlowMaterial(); + Sandcastle.highlight(applyGlowMaterial); + } +}, { + text : 'Arrow', + onselect : function() { + applyArrow(); + Sandcastle.highlight(applyArrow); + } +}, { + text : 'Dash', + onselect : function() { + applyDash(); + Sandcastle.highlight(applyDash); + } +}, { + text : 'Outline', + onselect : function() { + applyOutline(); + Sandcastle.highlight(applyOutline); + } +}]); //Sandcastle_End Sandcastle.finishedLoading(); diff --git a/Source/Core/GroundLineSegmentGeometry.js b/Source/Core/GroundLineSegmentGeometry.js index a288768c91ec..afa36ebd8676 100644 --- a/Source/Core/GroundLineSegmentGeometry.js +++ b/Source/Core/GroundLineSegmentGeometry.js @@ -266,13 +266,14 @@ define([ Cartesian3.pack(pushedEndTop, positions, 6 * 3); Cartesian3.pack(pushedStartTop, positions, 7 * 3); + // Winding order is reversed so the volume is inside-out var indices = [ - 0, 1, 2, 0, 2, 3, // right - 0, 3, 7, 0, 7, 4, // start - 0, 4, 5, 0, 5, 1, // bottom - 5, 4, 7, 5, 7, 6, // left - 5, 6, 2, 5, 2, 1, // end - 3, 2, 6, 3, 6, 7 // top + 0, 2, 1, 0, 3, 2, // right + 0, 7, 3, 0, 4, 7, // start + 0, 5, 4, 0, 1, 5, // bottom + 5, 7, 4, 5, 6, 7, // left + 5, 2, 6, 5, 1, 2, // end + 3, 6, 2, 3, 7, 6 // top ]; var geometryAttributes = new GeometryAttributes({ position : new GeometryAttribute({ diff --git a/Source/Scene/GroundPolylinePrimitive.js b/Source/Scene/GroundPolylinePrimitive.js index ec9f55af5a8f..f541d5c6e0b6 100644 --- a/Source/Scene/GroundPolylinePrimitive.js +++ b/Source/Scene/GroundPolylinePrimitive.js @@ -21,6 +21,7 @@ define([ '../Renderer/ShaderSource', './GroundPrimitive', './Material', + './PolylineColorAppearance', './PolylineMaterialAppearance', './Primitive', './SceneMode' @@ -47,6 +48,7 @@ define([ ShaderSource, GroundPrimitive, Material, + PolylineColorAppearance, PolylineMaterialAppearance, Primitive, SceneMode) { @@ -60,7 +62,7 @@ define([ * * @param {Object} [options] Object with the following properties: * @param {GeometryInstance[]|GeometryInstance} [options.polylineGeometryInstances] GeometryInstances containing GroundPolylineGeometry - * @param {Appearance} [options.appearance] The Appearance used to render the polyline. Defaults to a white color. Only {@link PolylineMaterialAppearance} is supported at this time. + * @param {Appearance} [options.appearance] The Appearance used to render the polyline. Defaults to a white color {@link Material} on a {@link PolylineMaterialAppearance}. * @param {Boolean} [options.show=true] Determines if this primitive will be shown. * @param {Boolean} [options.releaseGeometryInstances=true] When true, the primitive does not keep a reference to generated geometry or input cartographics to save memory. * @param {Boolean} [options.allowPicking=true] When true, each geometry instance will only be pickable with {@link Scene#pick}. When false, GPU memory is saved. @@ -134,8 +136,8 @@ define([ this._sp2D = undefined; this._spPick2D = undefined; this._renderState = RenderState.fromCache({ - depthTest : { - enabled : false // Helps prevent problems when viewing very closely + cull : { + enabled : true // prevent double-draw. Geometry is "inverted" (reversed winding order) so we're drawing backfaces. } }); @@ -183,6 +185,10 @@ define([ lengthSoFar += segmentLength; lengthSoFar2D += segmentLength2D; + if (defined(geometryInstance.attributes.color)) { + attributes.color = geometryInstance.attributes.color; + } + attributes.width = new GeometryInstanceAttribute({ componentDatatype: ComponentDatatype.UNSIGNED_BYTE, componentsPerAttribute: 1, @@ -234,6 +240,7 @@ define([ function createShaderProgram(groundPolylinePrimitive, frameState, appearance) { var context = frameState.context; var primitive = groundPolylinePrimitive._primitive; + var isPolylineColorAppearance = appearance instanceof PolylineColorAppearance; var attributeLocations = primitive._attributeLocations; @@ -247,8 +254,9 @@ define([ // which causes problems when interpolating log depth from vertices. // So force computing and writing logarithmic depth in the fragment shader. // Re-enable at far distances to avoid z-fighting. - var vsDefines = ['ENABLE_GL_POSITION_LOG_DEPTH_AT_HEIGHT']; - var fsDefines = groundPolylinePrimitive.debugShowShadowVolume ? ['DEBUG_SHOW_VOLUME'] : []; + var colorDefine = isPolylineColorAppearance ? 'PER_INSTANCE_COLOR' : ''; + var vsDefines = ['ENABLE_GL_POSITION_LOG_DEPTH_AT_HEIGHT', colorDefine]; + var fsDefines = groundPolylinePrimitive.debugShowShadowVolume ? ['DEBUG_SHOW_VOLUME', colorDefine] : [colorDefine]; var vsColor3D = new ShaderSource({ defines : vsDefines, @@ -256,7 +264,7 @@ define([ }); var fsColor3D = new ShaderSource({ defines : fsDefines, - sources : [appearance.material.shaderSource, PolylineShadowVolumeFS] + sources : [isPolylineColorAppearance ? '' : appearance.material.shaderSource, PolylineShadowVolumeFS] }); groundPolylinePrimitive._sp = ShaderProgram.replaceCache({ context : context, @@ -332,9 +340,12 @@ define([ colorCommands.length = length; pickCommands.length = length; + var isPolylineColorAppearance = appearance instanceof PolylineColorAppearance; + var i; var command; - var uniformMap = primitive._batchTable.getUniformMapCallback()(material._uniforms); + var materialUniforms = isPolylineColorAppearance ? {} : material._uniforms; + var uniformMap = primitive._batchTable.getUniformMapCallback()(materialUniforms); var pass = translucent ? Pass.TRANSLUCENT : Pass.OPAQUE; for (i = 0; i < length; i++) { @@ -471,10 +482,17 @@ define([ var polylineSegmentInstances = []; var geometryInstances = isArray(this.polylineGeometryInstances) ? this.polylineGeometryInstances : [this.polylineGeometryInstances]; var geometryInstancesLength = geometryInstances.length; - for (i = 0; i < geometryInstancesLength; ++i) { - var geometryInstance = geometryInstances[i]; - decompose(geometryInstance, frameState.mapProjection, polylineSegmentInstances, this._idsToInstanceIndices); + // If using PolylineColorAppearance, check if each instance has a color attribute. + if (this.appearance instanceof PolylineColorAppearance) { + for (i = 0; i < geometryInstancesLength; ++i) { + if (!defined(geometryInstances[i].attributes.color)) { + throw new DeveloperError('All GeometryInstances must have color attributes to use PolylineColorAppearance with GroundPolylinePrimitive.'); + } + } + } + for (i = 0; i < geometryInstancesLength; ++i) { + decompose(geometryInstances[i], frameState.mapProjection, polylineSegmentInstances, this._idsToInstanceIndices); } primitiveOptions.geometryInstances = polylineSegmentInstances; @@ -535,17 +553,19 @@ define([ }; // An object that, on setting an attribute, will set all the instances' attributes. - var userAttributeNames = ['width', 'show']; + var userAttributeNames = ['width', 'show', 'color']; // TODO: do we let Primitives swallow generic, user-specified attributes? var userAttributeCount = userAttributeNames.length; function InstanceAttributeSynchronizer(batchTable, firstInstanceIndex, lastInstanceIndex, batchTableAttributeIndices) { var properties = {}; for (var i = 0; i < userAttributeCount; i++) { var name = userAttributeNames[i]; var attributeIndex = batchTableAttributeIndices[name]; - properties[name] = { - get : createGetFunction(batchTable, firstInstanceIndex, attributeIndex), - set : createSetFunction(batchTable, firstInstanceIndex, lastInstanceIndex, attributeIndex) - }; + if (defined(attributeIndex)) { + properties[name] = { + get : createGetFunction(batchTable, firstInstanceIndex, attributeIndex), + set : createSetFunction(batchTable, firstInstanceIndex, lastInstanceIndex, attributeIndex) + }; + } } defineProperties(this, properties); } diff --git a/Source/Shaders/PolylineShadowVolumeFS.glsl b/Source/Shaders/PolylineShadowVolumeFS.glsl index 3c9aae100aa3..3a8724f366e5 100644 --- a/Source/Shaders/PolylineShadowVolumeFS.glsl +++ b/Source/Shaders/PolylineShadowVolumeFS.glsl @@ -6,10 +6,15 @@ varying vec4 v_startPlaneEC; varying vec4 v_endPlaneEC; varying vec4 v_rightPlaneEC; varying vec3 v_forwardDirectionEC; -varying vec2 v_alignedPlaneDistances; varying vec3 v_texcoordNormalization; varying float v_halfWidth; +#ifdef PER_INSTANCE_COLOR +varying vec4 v_color; +#else +varying vec2 v_alignedPlaneDistances; +#endif + float rayPlaneDistance(vec3 origin, vec3 direction, vec3 planeNormal, float planeDistance) { // We don't expect the ray to ever be parallel to the plane return (-planeDistance - dot(planeNormal, origin)) / dot(planeNormal, direction); @@ -47,6 +52,9 @@ void main(void) #ifdef PICK gl_FragColor.a = 1.0; #else // PICK +#ifdef PER_INSTANCE_COLOR + gl_FragColor = v_color; +#else // PER_INSTANCE_COLOR // Use distances for planes aligned with segment to prevent skew in dashing distanceFromStart = rayPlaneDistance(eyeCoordinate.xyz, -v_forwardDirectionEC, v_forwardDirectionEC.xyz, v_alignedPlaneDistances.x); distanceFromEnd = rayPlaneDistance(eyeCoordinate.xyz, v_forwardDirectionEC, -v_forwardDirectionEC.xyz, v_alignedPlaneDistances.y); @@ -67,7 +75,7 @@ void main(void) czm_material material = czm_getMaterial(materialInput); gl_FragColor = vec4(material.diffuse + material.emission, material.alpha); +#endif // PER_INSTANCE_COLOR #endif // PICK - czm_writeDepthClampedToFarPlane(); } diff --git a/Source/Shaders/PolylineShadowVolumeVS.glsl b/Source/Shaders/PolylineShadowVolumeVS.glsl index 1f0d85472bc1..15d36747ebc7 100644 --- a/Source/Shaders/PolylineShadowVolumeVS.glsl +++ b/Source/Shaders/PolylineShadowVolumeVS.glsl @@ -6,12 +6,17 @@ varying vec4 v_startPlaneEC; varying vec4 v_endPlaneEC; varying vec4 v_rightPlaneEC; varying vec3 v_forwardDirectionEC; -varying vec2 v_alignedPlaneDistances; varying vec3 v_texcoordNormalization; varying float v_halfWidth; varying float v_width; // for materials varying float v_polylineAngle; +#ifdef PER_INSTANCE_COLOR +varying vec4 v_color; +#else +varying vec2 v_alignedPlaneDistances; +#endif + float rayPlaneDistance(vec3 origin, vec3 direction, vec3 planeNormal, float planeDistance) { // TODO: move into its own function? // We don't expect the ray to ever be parallel to the plane return (-planeDistance - dot(planeNormal, origin)) / dot(planeNormal, direction); @@ -88,8 +93,13 @@ void main() #endif // COLUMBUS_VIEW_2D +#ifdef PER_INSTANCE_COLOR + v_color = czm_batchTable_color(batchId); +#else // PER_INSTANCE_COLOR + // For computing texture coordinates v_alignedPlaneDistances.x = -dot(forwardDirectionEC, ecStart); v_alignedPlaneDistances.y = -dot(-forwardDirectionEC, ecEnd); +#endif // PER_INSTANCE_COLOR // Compute a normal along which to "push" the position out, extending the miter depending on view distance. // Position has already been "pushed" by unit length along miter normal, and miter normals are encoded in the planes. @@ -113,8 +123,9 @@ void main() float width = czm_batchTable_width(batchId); v_width = width; v_halfWidth = width * 0.5; - positionEC.xyz += width * czm_metersPerPixel(positionEC) * normalEC;; - gl_Position = czm_depthClampFarPlane(czm_projection * positionEC); + positionEC.xyz -= normalEC; // undo the unit length push + positionEC.xyz += width * max(0.0, czm_metersPerPixel(positionEC)) * normalEC; // prevent artifacts when czm_metersPerPixel is negative (behind camera) + gl_Position = czm_projection * positionEC; // Approximate relative screen space direction of the line. vec2 approxLineDirection = normalize(vec2(forwardDirectionEC.x, -forwardDirectionEC.y)); From 612152bf79dea071166ee2e39f1ba27810d7b5ee Mon Sep 17 00:00:00 2001 From: Kanging Li Date: Mon, 28 May 2018 18:07:14 -0400 Subject: [PATCH 09/39] switch from batch table and many instances to fat vertices --- .../development/Polylines On Terrain.html | 14 +- Source/Core/GeometryInstanceAttribute.js | 6 +- Source/Core/GroundLineSegmentGeometry.js | 513 --------------- Source/Core/GroundPolylineGeometry.js | 585 ++++++++++++++++-- Source/Scene/GroundPolylinePrimitive.js | 153 +---- Source/Shaders/PolylineShadowVolumeFS.glsl | 4 +- Source/Shaders/PolylineShadowVolumeVS.glsl | 70 +-- .../createGroundLineSegmentGeometry.js | 17 - .../Workers/createGroundPolylineGeometry.js | 17 + 9 files changed, 620 insertions(+), 759 deletions(-) delete mode 100644 Source/Core/GroundLineSegmentGeometry.js delete mode 100644 Source/Workers/createGroundLineSegmentGeometry.js create mode 100644 Source/Workers/createGroundPolylineGeometry.js diff --git a/Apps/Sandcastle/gallery/development/Polylines On Terrain.html b/Apps/Sandcastle/gallery/development/Polylines On Terrain.html index a590de2ba465..5c01d5b8b0be 100644 --- a/Apps/Sandcastle/gallery/development/Polylines On Terrain.html +++ b/Apps/Sandcastle/gallery/development/Polylines On Terrain.html @@ -110,11 +110,16 @@ var instance1 = new Cesium.GeometryInstance({ geometry : new Cesium.GroundPolylineGeometry({ positions : polylineCartographics, - width : 16.0, loop : false }), id : 'polyline1', attributes : { + width : new Cesium.GeometryInstanceAttribute({ + componentDatatype : Cesium.ComponentDatatype.UNSIGNED_BYTE, + componentsPerAttribute : 1.0, + value : [16.0] + }), + show : new Cesium.ShowGeometryInstanceAttribute(), color : Cesium.ColorGeometryInstanceAttribute.fromColor(Cesium.Color.fromCssColorString('green').withAlpha(0.7)) } }); @@ -122,11 +127,16 @@ var instance2 = new Cesium.GeometryInstance({ geometry : new Cesium.GroundPolylineGeometry({ positions : loopCartographics, - width : 8.0, loop : true }), id : 'polyline2', attributes : { + width : new Cesium.GeometryInstanceAttribute({ + componentDatatype : Cesium.ComponentDatatype.UNSIGNED_BYTE, + componentsPerAttribute : 1.0, + value : [8.0] + }), + show : new Cesium.ShowGeometryInstanceAttribute(), color : Cesium.ColorGeometryInstanceAttribute.fromColor(Cesium.Color.fromCssColorString('#67ADDF').withAlpha(0.7)) } }); diff --git a/Source/Core/GeometryInstanceAttribute.js b/Source/Core/GeometryInstanceAttribute.js index 069d27cd0792..7b595f964bcb 100644 --- a/Source/Core/GeometryInstanceAttribute.js +++ b/Source/Core/GeometryInstanceAttribute.js @@ -15,10 +15,10 @@ define([ * @constructor * * @param {Object} options Object with the following properties: - * @param {ComponentDatatype} [options.componentDatatype] The datatype of each component in the attribute, e.g., individual elements in values. - * @param {Number} [options.componentsPerAttribute] A number between 1 and 4 that defines the number of components in an attributes. + * @param {ComponentDatatype} options.componentDatatype The datatype of each component in the attribute, e.g., individual elements in values. + * @param {Number} options.componentsPerAttribute A number between 1 and 4 that defines the number of components in an attributes. * @param {Boolean} [options.normalize=false] When true and componentDatatype is an integer format, indicate that the components should be mapped to the range [0, 1] (unsigned) or [-1, 1] (signed) when they are accessed as floating-point for rendering. - * @param {Number[]} [options.value] The value for the attribute. + * @param {Number[]} options.value The value for the attribute. * * @exception {DeveloperError} options.componentsPerAttribute must be between 1 and 4. * diff --git a/Source/Core/GroundLineSegmentGeometry.js b/Source/Core/GroundLineSegmentGeometry.js deleted file mode 100644 index afa36ebd8676..000000000000 --- a/Source/Core/GroundLineSegmentGeometry.js +++ /dev/null @@ -1,513 +0,0 @@ -define([ - './BoundingSphere', - './Cartesian2', - './Cartesian3', - './Cartographic', - './Check', - './ComponentDatatype', - './Math', - './defaultValue', - './defined', - './defineProperties', - './Ellipsoid', - './EncodedCartesian3', - './Geometry', - './GeometryAttribute', - './GeometryAttributes', - './GeometryInstanceAttribute', - './Matrix3', - './Quaternion' - ], function( - BoundingSphere, - Cartesian2, - Cartesian3, - Cartographic, - Check, - ComponentDatatype, - CesiumMath, - defaultValue, - defined, - defineProperties, - Ellipsoid, - EncodedCartesian3, - Geometry, - GeometryAttribute, - GeometryAttributes, - GeometryInstanceAttribute, - Matrix3, - Quaternion) { - 'use strict'; - - var MITER_BREAK_SMALL = Math.cos(CesiumMath.toRadians(30)); - var MITER_BREAK_LARGE = Math.cos(CesiumMath.toRadians(150)); - - /** - * Description of the volume used to draw a line segment on terrain. - * - * @alias GroundLineSegmentGeometry - * @constructor - * - * @private - * - * @see GroundLineSegmentGeometry#createGeometry - */ - function GroundLineSegmentGeometry() { - this._startBottom = new Cartesian3(); - this._startTop = new Cartesian3(); - this._startNormal = new Cartesian3(); - - this._endBottom = new Cartesian3(); - this._endTop = new Cartesian3(); - this._endNormal = new Cartesian3(); - - this._segmentBottomLength = 0.0; - this._segmentBottomLength2D = 0.0; - - this._workerName = 'createGroundLineSegmentGeometry'; - } - - var pos1_2dScratch = new Cartesian3(); - var pos2_2dScratch = new Cartesian3(); - function computeDistance2D(projection, carto1, carto2) { - var pos1_2d = projection.project(carto1, pos1_2dScratch); - var pos2_2d = projection.project(carto2, pos2_2dScratch); - return Cartesian3.distance(pos1_2d, pos2_2d); - } - - var startCartographicScratch = new Cartographic(); - var endCartographicScratch = new Cartographic(); - GroundLineSegmentGeometry.fromArrays = function(projection, index, normalsArray, bottomPositionsArray, topPositionsArray) { - var geometry = new GroundLineSegmentGeometry(); - - Cartesian3.unpack(bottomPositionsArray, index, geometry._startBottom); - Cartesian3.unpack(topPositionsArray, index, geometry._startTop); - Cartesian3.unpack(normalsArray, index, geometry._startNormal); - - Cartesian3.unpack(bottomPositionsArray, index + 3, geometry._endBottom); - Cartesian3.unpack(topPositionsArray, index + 3, geometry._endTop); - Cartesian3.unpack(normalsArray, index + 3, geometry._endNormal); - - breakMiter(geometry); - - geometry._segmentBottomLength = Cartesian3.distance(geometry._startBottom, geometry._endBottom); - - var ellipsoid = projection.ellipsoid; - var startCartographic = ellipsoid.cartesianToCartographic(geometry._startBottom, startCartographicScratch); - var endCartographic = ellipsoid.cartesianToCartographic(geometry._endBottom, endCartographicScratch); - startCartographic.height = 0.0; - endCartographic.height = 0.0; - - geometry._segmentBottomLength2D = computeDistance2D(projection, startCartographic, endCartographic); - - // TODO: slide wall positions to match height and depth. - // Note that this has to happen after the length computations - - return geometry; - }; - - function direction(end, start, result) { - Cartesian3.subtract(end, start, result); - Cartesian3.normalize(result, result); - return result; - } - - // If either of the normal angles is too steep compared to the direction of the line segment, - // "break" the miter by rotating the normal 90 degrees around the "up" direction at the point - // For ultra precision we would want to project into a plane, but in practice this is sufficient. - var lineDirectionScratch = new Cartesian3(); - var vertexUpScratch = new Cartesian3(); - var matrix3Scratch = new Matrix3(); - var quaternionScratch = new Quaternion(); - function breakMiter(geometry) { - var lineDirection = direction(geometry._endBottom, geometry._startBottom, lineDirectionScratch); - var quaternion; - var rotationMatrix; - var vertexUp; - var dot; - var angle; - - dot = Cartesian3.dot(lineDirection, geometry._startNormal); - if (dot > MITER_BREAK_SMALL || dot < MITER_BREAK_LARGE) { - vertexUp = direction(geometry._startTop, geometry._startBottom, vertexUpScratch); - angle = dot < MITER_BREAK_LARGE ? CesiumMath.PI_OVER_TWO : -CesiumMath.PI_OVER_TWO; - quaternion = Quaternion.fromAxisAngle(vertexUp, angle, quaternionScratch); - rotationMatrix = Matrix3.fromQuaternion(quaternion, matrix3Scratch); - Matrix3.multiplyByVector(rotationMatrix, geometry._startNormal, geometry._startNormal); - } - - dot = Cartesian3.dot(lineDirection, geometry._endNormal); - if (dot > MITER_BREAK_SMALL || dot < MITER_BREAK_LARGE) { - vertexUp = direction(geometry._endTop, geometry._endBottom, vertexUpScratch); - angle = dot < MITER_BREAK_LARGE ? CesiumMath.PI_OVER_TWO : -CesiumMath.PI_OVER_TWO; - quaternion = Quaternion.fromAxisAngle(vertexUp, angle, quaternionScratch); - rotationMatrix = Matrix3.fromQuaternion(quaternion, matrix3Scratch); - Matrix3.multiplyByVector(rotationMatrix, geometry._endNormal, geometry._endNormal); - } - } - - defineProperties(GroundLineSegmentGeometry.prototype, { - // TODO: doc - segmentBottomLength : { - get : function() { - return this._segmentBottomLength; - } - }, - segmentBottomLength2D : { - get : function() { - return this._segmentBottomLength2D; - } - } - }); - - /** - * The number of elements used to pack the object into an packArray. - * @type {Number} - */ - GroundLineSegmentGeometry.packedLength = Cartesian3.packedLength * 6 + 2; - - /** - * Stores the provided instance into the provided packArray. - * - * @param {GroundLineSegmentGeometry} value The value to pack. - * @param {Number[]} packArray The packArray to pack into. - * @param {Number} [startingIndex=0] The index into the packArray at which to start packing the elements. - * - * @returns {Number[]} The packArray that was packed into - */ - GroundLineSegmentGeometry.pack = function(value, packArray, startingIndex) { - //>>includeStart('debug', pragmas.debug); - Check.typeOf.object('value', value); - Check.defined('packArray', packArray); - //>>includeEnd('debug'); - - startingIndex = defaultValue(startingIndex, 0); - - Cartesian3.pack(value._startBottom, packArray, startingIndex); - startingIndex += Cartesian3.packedLength; - Cartesian3.pack(value._endBottom, packArray, startingIndex); - startingIndex += Cartesian3.packedLength; - Cartesian3.pack(value._startTop, packArray, startingIndex); - startingIndex += Cartesian3.packedLength; - Cartesian3.pack(value._endTop, packArray, startingIndex); - startingIndex += Cartesian3.packedLength; - Cartesian3.pack(value._startNormal, packArray, startingIndex); - startingIndex += Cartesian3.packedLength; - Cartesian3.pack(value._endNormal, packArray, startingIndex); - - return packArray; - }; - - /** - * Retrieves an instance from a packed packArray. - * - * @param {Number[]} packArray The packed packArray. - * @param {Number} [startingIndex=0] The starting index of the element to be unpacked. - * @param {GroundLineSegmentGeometry} [result] The object into which to store the result. - * @returns {GroundLineSegmentGeometry} The modified result parameter or a new RectangleGeometry instance if one was not provided. - */ - GroundLineSegmentGeometry.unpack = function(packArray, startingIndex, result) { - //>>includeStart('debug', pragmas.debug); - Check.defined('packArray', packArray); - //>>includeEnd('debug'); - - startingIndex = defaultValue(startingIndex, 0); - result = defaultValue(result, new GroundLineSegmentGeometry()); - - Cartesian3.unpack(packArray, startingIndex, result._startBottom); - startingIndex += Cartesian3.packedLength; - Cartesian3.unpack(packArray, startingIndex, result._endBottom); - startingIndex += Cartesian3.packedLength; - Cartesian3.unpack(packArray, startingIndex, result._startTop); - startingIndex += Cartesian3.packedLength; - Cartesian3.unpack(packArray, startingIndex, result._endTop); - startingIndex += Cartesian3.packedLength; - Cartesian3.unpack(packArray, startingIndex, result._startNormal); - startingIndex += Cartesian3.packedLength; - Cartesian3.unpack(packArray, startingIndex, result._endNormal); - - return result; - }; - - var startBottomScratch = new Cartesian3(); - var endBottomScratch = new Cartesian3(); - var endTopScratch = new Cartesian3(); - var startTopScratch = new Cartesian3(); - /** - * - * @param {GroundLineSegmentGeometry} groundPolylineSegmentGeometry - */ - GroundLineSegmentGeometry.createGeometry = function(groundPolylineSegmentGeometry) { - var startBottom = groundPolylineSegmentGeometry._startBottom; - var endBottom = groundPolylineSegmentGeometry._endBottom; - var endTop = groundPolylineSegmentGeometry._endTop; - var startTop = groundPolylineSegmentGeometry._startTop; - var startNormal = groundPolylineSegmentGeometry._startNormal; - var endNormal = groundPolylineSegmentGeometry._endNormal; - - var positions = new Float64Array(24); // 8 vertices - - // Push out by 1.0 in the right direction - var pushedStartBottom = Cartesian3.add(startBottom, startNormal, startBottomScratch); - var pushedEndBottom = Cartesian3.add(endBottom, endNormal, endBottomScratch); - var pushedEndTop = Cartesian3.add(endTop, endNormal, endTopScratch); - var pushedStartTop = Cartesian3.add(startTop, startNormal, startTopScratch); - Cartesian3.pack(pushedStartBottom, positions, 0); - Cartesian3.pack(pushedEndBottom, positions, 1 * 3); - Cartesian3.pack(pushedEndTop, positions, 2 * 3); - Cartesian3.pack(pushedStartTop, positions, 3 * 3); - - // Push out by 1.0 in the left direction - pushedStartBottom = Cartesian3.subtract(startBottom, startNormal, startBottomScratch); - pushedEndBottom = Cartesian3.subtract(endBottom, endNormal, endBottomScratch); - pushedEndTop = Cartesian3.subtract(endTop, endNormal, endTopScratch); - pushedStartTop = Cartesian3.subtract(startTop, startNormal, startTopScratch); - Cartesian3.pack(pushedStartBottom, positions, 4 * 3); - Cartesian3.pack(pushedEndBottom, positions, 5 * 3); - Cartesian3.pack(pushedEndTop, positions, 6 * 3); - Cartesian3.pack(pushedStartTop, positions, 7 * 3); - - // Winding order is reversed so the volume is inside-out - var indices = [ - 0, 2, 1, 0, 3, 2, // right - 0, 7, 3, 0, 4, 7, // start - 0, 5, 4, 0, 1, 5, // bottom - 5, 7, 4, 5, 6, 7, // left - 5, 2, 6, 5, 1, 2, // end - 3, 6, 2, 3, 7, 6 // top - ]; - var geometryAttributes = new GeometryAttributes({ - position : new GeometryAttribute({ - componentDatatype : ComponentDatatype.DOUBLE, - componentsPerAttribute : 3, - normalize : false, - values : positions - }) - }); - - return new Geometry({ - attributes : geometryAttributes, - indices : new Uint16Array(indices), - boundingSphere : BoundingSphere.fromPoints([startBottom, endBottom, endTop, startTop]) - }); - }; - - GroundLineSegmentGeometry.prototype.segmentLength = function() { - return Cartesian3.distance(this._startBottom, this._endBottom); - }; - - var positionCartographicScratch = new Cartographic(); - var normalEndpointScratch = new Cartesian3(); - function projectNormal(projection, position, normal, projectedPosition, result) { - var normalEndpoint = Cartesian3.add(position, normal, normalEndpointScratch); - - var ellipsoid = projection.ellipsoid; - var normalEndpointCartographic = ellipsoid.cartesianToCartographic(normalEndpoint, positionCartographicScratch); - normalEndpointCartographic.height = 0.0; - var normalEndpointProjected = projection.project(normalEndpointCartographic, result); - result = Cartesian3.subtract(normalEndpointProjected, projectedPosition, result); - result.z = 0.0; - result = Cartesian3.normalize(result, result); - return result; - } - - var encodeScratch2D = new EncodedCartesian3(); - var projectedStartPositionScratch = new Cartesian3(); - var projectedEndPositionScratch = new Cartesian3(); - var projectedStartNormalScratch = new Cartesian3(); - var projectedEndNormalScratch = new Cartesian3(); - var forwardOffset2DScratch = new Cartesian3(); - var forwardNormal2DScratch = new Cartesian3(); - function add2DAttributes(attributes, geometry, projection, lengthSoFar2D, segmentLength2D, totalLength2D) { - var startBottom = geometry._startBottom; - var endBottom = geometry._endBottom; - var ellipsoid = projection.ellipsoid; - - // Project positions - var startCartographic = ellipsoid.cartesianToCartographic(startBottom, startCartographicScratch); - var endCartographic = ellipsoid.cartesianToCartographic(endBottom, endCartographicScratch); - startCartographic.height = 0.0; - endCartographic.height = 0.0; - var projectedStartPosition = projection.project(startCartographic, projectedStartPositionScratch); - var projectedEndPosition = projection.project(endCartographic, projectedEndPositionScratch); - - // Project mitering normals - var projectedStartNormal = projectNormal(projection, startBottom, geometry._startNormal, projectedStartPosition, projectedStartNormalScratch); - var projectedEndNormal = projectNormal(projection, endBottom, geometry._endNormal, projectedEndPosition, projectedEndNormalScratch); - - // Right direction is just forward direction rotated by -90 degrees around Z - var forwardOffset = Cartesian3.subtract(projectedEndPosition, projectedStartPosition, forwardOffset2DScratch); - var forwardDirection = Cartesian3.normalize(forwardOffset, forwardNormal2DScratch); - var right2D = [forwardDirection.y, -forwardDirection.x]; - - // Similarly with plane normals - var startPlane2D = [-projectedStartNormal.y, projectedStartNormal.x]; - var endPlane2D = [projectedEndNormal.y, -projectedEndNormal.x]; - - var encodedStart = EncodedCartesian3.fromCartesian(projectedStartPosition, encodeScratch2D); - - var startHighLow2D_attribute = new GeometryInstanceAttribute({ - componentDatatype: ComponentDatatype.FLOAT, - componentsPerAttribute: 4, - normalize: false, - value : [encodedStart.high.x, encodedStart.high.y, encodedStart.low.x, encodedStart.low.y] - }); - - var startEndNormals2D_attribute = new GeometryInstanceAttribute({ - componentDatatype: ComponentDatatype.FLOAT, - componentsPerAttribute: 4, - normalize: false, - value : startPlane2D.concat(endPlane2D) - }); - - var offsetAndRight2D_attribute = new GeometryInstanceAttribute({ - componentDatatype: ComponentDatatype.FLOAT, - componentsPerAttribute: 4, - normalize: false, - value : [forwardOffset.x, forwardOffset.y, right2D[0], right2D[1]] - }); - - var texcoordNormalization2D_attribute = new GeometryInstanceAttribute({ - componentDatatype: ComponentDatatype.FLOAT, - componentsPerAttribute: 3, - normalize: false, - value : [lengthSoFar2D, segmentLength2D, totalLength2D] // TODO: some floating point problems with this at huge distances! - }); - - attributes.startHighLow2D = startHighLow2D_attribute; - attributes.startEndNormals2D = startEndNormals2D_attribute; - attributes.offsetAndRight2D = offsetAndRight2D_attribute; - attributes.texcoordNormalization2D = texcoordNormalization2D_attribute; - } - - var encodeScratch = new EncodedCartesian3(); - var offsetScratch = new Cartesian3(); - var normal1Scratch = new Cartesian3(); - var normal2Scratch = new Cartesian3(); - var rightScratch = new Cartesian3(); - /** - * Gets GeometrtyInstanceAttributes for culling fragments that aren't part of the line and - * for computing texture coordinates along the line, enabling material support. - * - * Computing whether or not a fragment is part of the line requires: - * - plane at the beginning of the segment (rotated for miter) - * - plane at the end of the segment (rotated for miter) - * - right plane for the segment - * - * We encode the planes as normals, the start position in high precision, and an offset to the end position. - * This also gets us planes normal to the line direction that can be used for computing the linear - * texture coordinate local to the line. This texture coordinate then needs to be mapped to the entire line, - * which requires an additional set of attributes. - * - * @param {GroundLineSegmentGeometry} geometry GroundLineSegmentGeometry - * @param {MapProjection} projection The MapProjection used for 2D and Columbus View. - * @param {Number} lengthSoFar Distance of the segment's start point along the line - * @param {Number} segmentLength Length of the segment - * @param {Number} totalLength Total length of the entire line - * @param {Number} lengthSoFar2D Distance of the segment's start point along the line in 2D - * @param {Number} segmentLength2D Length of the segment in 2D - * @param {Number} totalLength2D Total length of the entire line in 2D - * @returns {Object} An object containing GeometryInstanceAttributes for the input geometry - */ - GroundLineSegmentGeometry.getAttributes = function(geometry, projection, lengthSoFar, segmentLength, totalLength, lengthSoFar2D, segmentLength2D, totalLength2D) { - //>>includeStart('debug', pragmas.debug); - Check.typeOf.object('geometry', geometry); - Check.typeOf.object('projection', projection); - Check.typeOf.number('lengthSoFar', lengthSoFar); - Check.typeOf.number('segmentLength', segmentLength); - Check.typeOf.number('totalLength', totalLength); - Check.typeOf.number('lengthSoFar2D', lengthSoFar2D); - Check.typeOf.number('segmentLength2D', segmentLength2D); - Check.typeOf.number('totalLength2D', totalLength2D); - //>>includeEnd('debug'); - - // Unpack values from geometry - var startBottom = geometry._startBottom; - var endBottom = geometry._endBottom; - var endTop = geometry._endTop; - var startTop = geometry._startTop; - - var startCartesianRightNormal = geometry._startNormal; - var endCartesianRightNormal = geometry._endNormal; - - // Encode start position and end position as high precision point + offset - var encodedStart = EncodedCartesian3.fromCartesian(startBottom, encodeScratch); - - var forwardOffset = Cartesian3.subtract(endBottom, startBottom, offsetScratch); - - var startHi_and_forwardOffsetX_Attribute = new GeometryInstanceAttribute({ - componentDatatype: ComponentDatatype.FLOAT, - componentsPerAttribute: 4, - normalize: false, - value : Cartesian3.pack(encodedStart.high, [0, 0, 0, forwardOffset.x]) - }); - - var startLo_and_forwardOffsetY_Attribute = new GeometryInstanceAttribute({ - componentDatatype: ComponentDatatype.FLOAT, - componentsPerAttribute: 4, - normalize: false, - value : Cartesian3.pack(encodedStart.low, [0, 0, 0, forwardOffset.y]) - }); - - var packArray = [0, 0, 0, forwardOffset.z]; - var forward = Cartesian3.normalize(forwardOffset, forwardOffset); - - // Right vector is computed as cross of (startTop - startBottom) and direction to segment end point - var startUp = Cartesian3.subtract(startTop, startBottom, normal1Scratch); - startUp = Cartesian3.normalize(startUp, startUp); - - var right = Cartesian3.cross(forward, startUp, rightScratch); - right = Cartesian3.normalize(right, right); - - var rightNormal_attribute = new GeometryInstanceAttribute({ - componentDatatype: ComponentDatatype.FLOAT, - componentsPerAttribute: 3, - normalize: false, - value : Cartesian3.pack(right, [0, 0, 0]) - }); - - // Normal planes need to miter, so cross (startTop - startBottom) with geometry normal at start - var startNormal = Cartesian3.cross(startUp, startCartesianRightNormal, normal1Scratch); - startNormal = Cartesian3.normalize(startNormal, startNormal); - - var startNormal_and_forwardOffsetZ_attribute = new GeometryInstanceAttribute({ - componentDatatype: ComponentDatatype.FLOAT, - componentsPerAttribute: 4, - normalize: false, - value : Cartesian3.pack(startNormal, packArray) - }); - - var endUp = Cartesian3.subtract(endTop, endBottom, normal2Scratch); - endUp = Cartesian3.normalize(endUp, endUp); - var endNormal = Cartesian3.cross(endCartesianRightNormal, endUp, normal2Scratch); - endNormal = Cartesian3.normalize(endNormal, endNormal); - - var endNormal_attribute = new GeometryInstanceAttribute({ - componentDatatype: ComponentDatatype.FLOAT, - componentsPerAttribute: 3, - normalize: false, - value : Cartesian3.pack(endNormal, [0, 0, 0]) - }); - - // Texture coordinate localization parameters - var texcoordNormalization_attribute = new GeometryInstanceAttribute({ - componentDatatype: ComponentDatatype.FLOAT, - componentsPerAttribute: 3, - normalize: false, - value : [lengthSoFar, segmentLength, totalLength] - }); - - var attributes = { - startHi_and_forwardOffsetX : startHi_and_forwardOffsetX_Attribute, - startLo_and_forwardOffsetY : startLo_and_forwardOffsetY_Attribute, - startNormal_and_forwardOffsetZ : startNormal_and_forwardOffsetZ_attribute, - endNormal : endNormal_attribute, - rightNormal : rightNormal_attribute, - texcoordNormalization : texcoordNormalization_attribute - }; - - add2DAttributes(attributes, geometry, projection, lengthSoFar2D, segmentLength2D, totalLength2D); - return attributes; - }; - - return GroundLineSegmentGeometry; -}); diff --git a/Source/Core/GroundPolylineGeometry.js b/Source/Core/GroundPolylineGeometry.js index 8694201b7296..c45578347c76 100644 --- a/Source/Core/GroundPolylineGeometry.js +++ b/Source/Core/GroundPolylineGeometry.js @@ -1,7 +1,10 @@ define([ + './ApproximateTerrainHeights', + './BoundingSphere', './Cartesian3', './Cartographic', './Check', + './ComponentDatatype', './Math', './defaultValue', './defined', @@ -9,13 +12,21 @@ define([ './Ellipsoid', './EllipsoidGeodesic', './EncodedCartesian3', + './GeographicProjection', + './Geometry', + './GeometryAttribute', './Matrix3', './Plane', - './Quaternion' + './Quaternion', + './Rectangle', + './WebMercatorProjection' ], function( + ApproximateTerrainHeights, + BoundingSphere, Cartesian3, Cartographic, Check, + ComponentDatatype, CesiumMath, defaultValue, defined, @@ -23,11 +34,22 @@ define([ Ellipsoid, EllipsoidGeodesic, EncodedCartesian3, + GeographicProjection, + Geometry, + GeometryAttribute, Matrix3, Plane, - Quaternion) { + Quaternion, + Rectangle, + WebMercatorProjection) { 'use strict'; + var PROJECTIONS = [GeographicProjection, WebMercatorProjection]; + var PROJECTION_COUNT = PROJECTIONS.length; + + var MITER_BREAK_SMALL = Math.cos(CesiumMath.toRadians(30)); + var MITER_BREAK_LARGE = Math.cos(CesiumMath.toRadians(150)); + /** * A description of a polyline on terrain. Only to be used with GroundPolylinePrimitive. * @@ -39,7 +61,9 @@ define([ * @param {Number} [options.granularity=9999.0] The distance interval used for interpolating options.points. Defaults to 9999.0 meters. Zero indicates no interpolation. * @param {Boolean} [options.loop=false] Whether during geometry creation a line segment will be added between the last and first line positions to make this Polyline a loop. * @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] Ellipsoid for projecting cartographic coordinates to cartesian - * @param {Number} [options.width=1.0] Integer width for the polyline. + * @param {Number} [options.maximumTerrainHeight] + * @param {Number} [options.minimumTerrainHeight] + * @param {MapProjection} [options.projection] * * @see GroundPolylinePrimitive */ @@ -63,15 +87,35 @@ define([ this.ellipsoid = defaultValue(options.ellipsoid, Ellipsoid.WGS84); - this.width = defaultValue(options.width, 1.0); + this.minimumTerrainHeight = defaultValue(options.minimumTerrainHeight, ApproximateTerrainHeights._defaultMinTerrainHeight); + this.maximumTerrainHeight = defaultValue(options.maximumTerrainHeight, ApproximateTerrainHeights._defaultMaxTerrainHeight); + + var projectionIndex = 0; + if (defined(options.projection)) { + for (var i = 0; i < PROJECTION_COUNT; i++) { + if (options.projection instanceof PROJECTIONS[i]) { // TODO: is this ok? + projectionIndex = i; + break; + } + } + } + this.projectionIndex = projectionIndex; + + /** + * The number of elements used to pack the object into an array. + * @type {Number} + */ + this.packedLength = 1.0 + this._positions.length * 2 + 1.0 + 1.0 + Ellipsoid.packedLength + 1.0 + 1.0 + 1.0; + + this._workerName = 'createGroundPolylineGeometry'; } var cart3Scratch1 = new Cartesian3(); var cart3Scratch2 = new Cartesian3(); var cart3Scratch3 = new Cartesian3(); - function computeRightNormal(start, end, wallHeight, ellipsoid, result) { + function computeRightNormal(start, end, maxHeight, ellipsoid, result) { var startBottom = getPosition(ellipsoid, start, 0.0, cart3Scratch1); - var startTop = getPosition(ellipsoid, start, wallHeight, cart3Scratch2); + var startTop = getPosition(ellipsoid, start, maxHeight, cart3Scratch2); var endBottom = getPosition(ellipsoid, end, 0.0, cart3Scratch3); var up = direction(startTop, startBottom, cart3Scratch2); @@ -85,7 +129,7 @@ define([ var interpolatedBottomScratch = new Cartesian3(); var interpolatedTopScratch = new Cartesian3(); var interpolatedNormalScratch = new Cartesian3(); - function interpolateSegment(start, end, wallHeight, granularity, ellipsoid, normalsArray, bottomPositionsArray, topPositionsArray) { + function interpolateSegment(start, end, minHeight, maxHeight, granularity, ellipsoid, normalsArray, bottomPositionsArray, topPositionsArray, cartographicsArray) { if (granularity === 0.0) { return; } @@ -96,7 +140,7 @@ define([ } // Compute rightwards normal applicable at all interpolated points - var interpolatedNormal = computeRightNormal(start, end, wallHeight, ellipsoid, interpolatedNormalScratch); + var interpolatedNormal = computeRightNormal(start, end, maxHeight, ellipsoid, interpolatedNormalScratch); var segments = Math.ceil(surfaceDistance / granularity); var interpointDistance = surfaceDistance / segments; @@ -105,12 +149,14 @@ define([ var packIndex = normalsArray.length; for (var i = 0; i < pointsToAdd; i++) { var interpolatedCartographic = ellipsoidGeodesic.interpolateUsingSurfaceDistance(distanceFromStart, interpolatedCartographicScratch); - var interpolatedBottom = getPosition(ellipsoid, interpolatedCartographic, 0.0, interpolatedBottomScratch); - var interpolatedTop = getPosition(ellipsoid, interpolatedCartographic, wallHeight, interpolatedTopScratch); + var interpolatedBottom = getPosition(ellipsoid, interpolatedCartographic, minHeight, interpolatedBottomScratch); + var interpolatedTop = getPosition(ellipsoid, interpolatedCartographic, maxHeight, interpolatedTopScratch); Cartesian3.pack(interpolatedNormal, normalsArray, packIndex); Cartesian3.pack(interpolatedBottom, bottomPositionsArray, packIndex); Cartesian3.pack(interpolatedTop, topPositionsArray, packIndex); + cartographicsArray.push(interpolatedCartographic.latitude); + cartographicsArray.push(interpolatedCartographic.longitude); packIndex += 3; distanceFromStart += interpointDistance; @@ -134,30 +180,133 @@ define([ get: function() { return this._positions; }, - // TODO: doc, interpolation, etc. + // TODO: doc, packedLength, etc. set: function(value) { this._positions = defaultValue(value, []); } } }); + GroundPolylineGeometry.pack = function(value, packArray, startingIndex) { + startingIndex = defaultValue(startingIndex, 0); + + // Pack position length, then all positions + var positions = value._positions; + var positionsLength = positions.length; + + var index = startingIndex; + packArray[index++] = positionsLength; + + for (var i = 0; i < positionsLength; ++i) { + var cartographic = positions[i]; + packArray[index++] = cartographic.longitude; + packArray[index++] = cartographic.latitude; + } + + packArray[index++] = value.granularity; + packArray[index++] = value.loop ? 1.0 : 0.0; + + Ellipsoid.pack(value.ellipsoid, packArray, index); + index += Ellipsoid.packedLength; + + packArray[index++] = value.minimumTerrainHeight; + packArray[index++] = value.maximumTerrainHeight; + packArray[index++] = value.projectionIndex; + + return packArray; + }; + + GroundPolylineGeometry.unpack = function(packArray, startingIndex, result) { + var index = defaultValue(startingIndex, 0); + var positions = []; + + var positionsLength = packArray[index++]; + for (var i = 0; i < positionsLength; i++) { + var cartographic = new Cartographic(); + cartographic.longitude = packArray[index++]; + cartographic.latitude = packArray[index++]; + positions.push(cartographic); + } + + var granularity = packArray[index++]; + var loop = packArray[index++] === 1.0 ? true : false; + + var ellipsoid = Ellipsoid.unpack(packArray, index); + index += Ellipsoid.packedLength; + + var minimumTerrainHeight = packArray[index++]; + var maximumTerrainHeight = packArray[index++]; + var projectionIndex = packArray[index++]; + + if (!defined(result)) { + return new GroundPolylineGeometry({ + positions : positions, + granularity : granularity, + loop : loop, + ellipsoid : ellipsoid, + maximumTerrainHeight : maximumTerrainHeight, + minimumTerrainHeight : minimumTerrainHeight, + projection : new PROJECTIONS[projectionIndex](ellipsoid) + }); + } + + result._positions = positions; + result.granularity = granularity; + result.loop = loop; + result.ellipsoid = ellipsoid; + result.maximumTerrainHeight = maximumTerrainHeight; + result.minimumTerrainHeight = minimumTerrainHeight; + result.projectionIndex = projectionIndex; + + return result; + }; + + // If the end normal angle is too steep compared to the direction of the line segment, + // "break" the miter by rotating the normal 90 degrees around the "up" direction at the point + // For ultra precision we would want to project into a plane, but in practice this is sufficient. + var lineDirectionScratch = new Cartesian3(); + var vertexUpScratch = new Cartesian3(); + var matrix3Scratch = new Matrix3(); + var quaternionScratch = new Quaternion(); + function breakMiter(endGeometryNormal, startBottom, endBottom, endTop) { + var lineDirection = direction(endBottom, startBottom, lineDirectionScratch); + + var dot = Cartesian3.dot(lineDirection, endGeometryNormal); + if (dot > MITER_BREAK_SMALL || dot < MITER_BREAK_LARGE) { + var vertexUp = direction(endTop, endBottom, vertexUpScratch); + var angle = dot < MITER_BREAK_LARGE ? CesiumMath.PI_OVER_TWO : -CesiumMath.PI_OVER_TWO; + var quaternion = Quaternion.fromAxisAngle(vertexUp, angle, quaternionScratch); + var rotationMatrix = Matrix3.fromQuaternion(quaternion, matrix3Scratch); + Matrix3.multiplyByVector(rotationMatrix, endGeometryNormal, endGeometryNormal); + return true; + } + return false; + } + var previousBottomScratch = new Cartesian3(); var vertexBottomScratch = new Cartesian3(); var vertexTopScratch = new Cartesian3(); var nextBottomScratch = new Cartesian3(); var vertexNormalScratch = new Cartesian3(); - GroundPolylineGeometry.createWallVertices = function(groundPolylineGeometry, wallHeight) { + GroundPolylineGeometry.createGeometry = function(groundPolylineGeometry) { var cartographics = groundPolylineGeometry.positions; var loop = groundPolylineGeometry.loop; var ellipsoid = groundPolylineGeometry.ellipsoid; var granularity = groundPolylineGeometry.granularity; + var projection = new PROJECTIONS[groundPolylineGeometry.projectionIndex](ellipsoid); - // TODO: throw errors/negate loop if not enough points + var maxHeight = groundPolylineGeometry.maximumTerrainHeight; + var minHeight = groundPolylineGeometry.minimumTerrainHeight; var cartographicsLength = cartographics.length; + + // TODO: throw errors/negate loop if not enough points + var index; var i; + /**** Build heap-side arrays of positions, cartographics, and normals from which to compute vertices ****/ + var cartographicsArray = []; var normalsArray = []; var bottomPositionsArray = []; var topPositionsArray = []; @@ -173,30 +322,33 @@ define([ var nextCartographic = cartographics[1]; var prestartCartographic = cartographics[cartographicsLength - 1]; - previousBottom = getPosition(ellipsoid, prestartCartographic, 0.0, previousBottom); - nextBottom = getPosition(ellipsoid, nextCartographic, 0.0, nextBottom); - vertexBottom = getPosition(ellipsoid, startCartographic, 0.0, vertexBottom); - vertexTop = getPosition(ellipsoid, startCartographic, wallHeight, vertexTop); + previousBottom = getPosition(ellipsoid, prestartCartographic, minHeight, previousBottom); + nextBottom = getPosition(ellipsoid, nextCartographic, minHeight, nextBottom); + vertexBottom = getPosition(ellipsoid, startCartographic, minHeight, vertexBottom); + vertexTop = getPosition(ellipsoid, startCartographic, maxHeight, vertexTop); if (loop) { vertexNormal = computeVertexMiterNormal(previousBottom, vertexBottom, vertexTop, nextBottom, vertexNormal); } else { - vertexNormal = computeRightNormal(startCartographic, nextCartographic, wallHeight, ellipsoid, vertexNormal); + vertexNormal = computeRightNormal(startCartographic, nextCartographic, maxHeight, ellipsoid, vertexNormal); } Cartesian3.pack(vertexNormal, normalsArray, 0); Cartesian3.pack(vertexBottom, bottomPositionsArray, 0); Cartesian3.pack(vertexTop, topPositionsArray, 0); + cartographicsArray.push(startCartographic.latitude); + cartographicsArray.push(startCartographic.longitude); // Interpolate between start and start + 1 - interpolateSegment(startCartographic, nextCartographic, wallHeight, granularity, ellipsoid, normalsArray, bottomPositionsArray, topPositionsArray); + interpolateSegment(startCartographic, nextCartographic, minHeight, maxHeight, granularity, ellipsoid, normalsArray, bottomPositionsArray, topPositionsArray, cartographicsArray); // All inbetween points for (i = 1; i < cartographicsLength - 1; ++i) { previousBottom = Cartesian3.clone(vertexBottom, previousBottom); vertexBottom = Cartesian3.clone(nextBottom, vertexBottom); - getPosition(ellipsoid, cartographics[i], wallHeight, vertexTop); - getPosition(ellipsoid, cartographics[i + 1], 0.0, nextBottom); + var vertexCartographic = cartographics[i]; + getPosition(ellipsoid, vertexCartographic, maxHeight, vertexTop); + getPosition(ellipsoid, cartographics[i + 1], minHeight, nextBottom); computeVertexMiterNormal(previousBottom, vertexBottom, vertexTop, nextBottom, vertexNormal); @@ -204,34 +356,38 @@ define([ Cartesian3.pack(vertexNormal, normalsArray, index); Cartesian3.pack(vertexBottom, bottomPositionsArray, index); Cartesian3.pack(vertexTop, topPositionsArray, index); + cartographicsArray.push(vertexCartographic.latitude); + cartographicsArray.push(vertexCartographic.longitude); - interpolateSegment(cartographics[i], cartographics[i + 1], wallHeight, granularity, ellipsoid, normalsArray, bottomPositionsArray, topPositionsArray); + interpolateSegment(cartographics[i], cartographics[i + 1], minHeight, maxHeight, granularity, ellipsoid, normalsArray, bottomPositionsArray, topPositionsArray, cartographicsArray); } // Last point - either loop or attach a "perpendicular" normal var endCartographic = cartographics[cartographicsLength - 1]; var preEndCartographic = cartographics[cartographicsLength - 2]; - vertexBottom = getPosition(ellipsoid, endCartographic, 0.0, vertexBottom); - vertexTop = getPosition(ellipsoid, endCartographic, wallHeight, vertexTop); + vertexBottom = getPosition(ellipsoid, endCartographic, minHeight, vertexBottom); + vertexTop = getPosition(ellipsoid, endCartographic, maxHeight, vertexTop); if (loop) { var postEndCartographic = cartographics[0]; - previousBottom = getPosition(ellipsoid, preEndCartographic, 0.0, previousBottom); - nextBottom = getPosition(ellipsoid, postEndCartographic, 0.0, nextBottom); + previousBottom = getPosition(ellipsoid, preEndCartographic, minHeight, previousBottom); + nextBottom = getPosition(ellipsoid, postEndCartographic, minHeight, nextBottom); vertexNormal = computeVertexMiterNormal(previousBottom, vertexBottom, vertexTop, nextBottom, vertexNormal); } else { - vertexNormal = computeRightNormal(preEndCartographic, endCartographic, wallHeight, ellipsoid, vertexNormal); + vertexNormal = computeRightNormal(preEndCartographic, endCartographic, maxHeight, ellipsoid, vertexNormal); } index = normalsArray.length; Cartesian3.pack(vertexNormal, normalsArray, index); Cartesian3.pack(vertexBottom, bottomPositionsArray, index); Cartesian3.pack(vertexTop, topPositionsArray, index); + cartographicsArray.push(endCartographic.latitude); + cartographicsArray.push(endCartographic.longitude); if (loop) { - interpolateSegment(endCartographic, startCartographic, wallHeight, granularity, ellipsoid, normalsArray, bottomPositionsArray, topPositionsArray); + interpolateSegment(endCartographic, startCartographic, minHeight, maxHeight, granularity, ellipsoid, normalsArray, bottomPositionsArray, topPositionsArray, cartographicsArray); index = normalsArray.length; // Copy the first vertex for (i = 0; i < 3; ++i) { @@ -239,23 +395,382 @@ define([ bottomPositionsArray[index + i] = bottomPositionsArray[i]; topPositionsArray[index + i] = topPositionsArray[i]; } + cartographicsArray.push(startCartographic.latitude); + cartographicsArray.push(startCartographic.longitude); } - return { - rightFacingNormals : new Float32Array(normalsArray), - bottomPositions : new Float64Array(bottomPositionsArray), - topPositions : new Float64Array(topPositionsArray) - }; + return generateGeometryAttributes(projection, bottomPositionsArray, topPositionsArray, normalsArray, cartographicsArray, loop); }; + var positionCartographicScratch = new Cartographic(); + var normalEndpointScratch = new Cartesian3(); + function projectNormal(projection, position, normal, projectedPosition, result) { + var normalEndpoint = Cartesian3.add(position, normal, normalEndpointScratch); + + var ellipsoid = projection.ellipsoid; + var normalEndpointCartographic = ellipsoid.cartesianToCartographic(normalEndpoint, positionCartographicScratch); + normalEndpointCartographic.height = 0.0; + var normalEndpointProjected = projection.project(normalEndpointCartographic, result); + result = Cartesian3.subtract(normalEndpointProjected, projectedPosition, result); + result.z = 0.0; + result = Cartesian3.normalize(result, result); + return result; + } + + var cartographicScratch = new Cartographic(); + var segmentStartTopScratch = new Cartesian3(); + var segmentEndTopScratch = new Cartesian3(); + var segmentStartBottomScratch = new Cartesian3(); + var segmentEndBottomScratch = new Cartesian3(); + var segmentStartNormalScratch = new Cartesian3(); + var segmentEndNormalScratch = new Cartesian3(); + + var segmentStart2DScratch = new Cartesian3(); + var segmentEnd2DScratch = new Cartesian3(); + var segmentStartNormal2DScratch = new Cartesian3(); + var segmentEndNormal2DScratch = new Cartesian3(); + + var offsetScratch = new Cartesian3(); + var startUpScratch = new Cartesian3(); + var endUpScratch = new Cartesian3(); + var rightScratch = new Cartesian3(); + var startPlaneNormalScratch = new Cartesian3(); + var endPlaneNormalScratch = new Cartesian3(); + var encodeScratch = new EncodedCartesian3(); + + var encodeScratch2D = new EncodedCartesian3(); + var forwardOffset2DScratch = new Cartesian3(); + var right2DScratch = new Cartesian3(); + + // Winding order is reversed so each segment's volume is inside-out + var REFERENCE_INDICES = [ + 0, 2, 1, 0, 3, 2, // right + 0, 7, 3, 0, 4, 7, // start + 0, 5, 4, 0, 1, 5, // bottom + 5, 7, 4, 5, 6, 7, // left + 5, 2, 6, 5, 1, 2, // end + 3, 6, 2, 3, 7, 6 // top + ]; + var REFERENCE_INDICES_LENGTH = REFERENCE_INDICES.length; + function generateGeometryAttributes(projection, bottomPositionsArray, topPositionsArray, normalsArray, cartographicsArray, loop) { + var i; + var index; + + // Each segment will have 8 vertices + var segmentCount = (bottomPositionsArray.length / 3) - 1; + var vertexCount = segmentCount * 8; + var arraySizeVec4 = vertexCount * 4; + var indexCount = segmentCount * 36; + + var indices = vertexCount > 65535 ? new Uint32Array(indexCount) : new Uint16Array(indexCount); + var positionsArray = new Float64Array(vertexCount * 3); + + var startHi_and_forwardOffsetX = new Float32Array(arraySizeVec4); + var startLo_and_forwardOffsetY = new Float32Array(arraySizeVec4); + var startNormal_and_forwardOffsetZ = new Float32Array(arraySizeVec4); + var endNormal_andTextureCoordinateNormalizationX = new Float32Array(arraySizeVec4); + var rightNormal_andTextureCoordinateNormalizationY = new Float32Array(arraySizeVec4); + + var startHiLo2D = new Float32Array(arraySizeVec4); + var offsetAndRight2D = new Float32Array(arraySizeVec4); + var startEndNormals2D = new Float32Array(arraySizeVec4); + var texcoordNormalization2D = new Float32Array(vertexCount * 2); + + /*** Compute total lengths for texture coordinate normalization ***/ + // 2D + var cartographicsLength = cartographicsArray.length / 2; + var length2D = 0.0; + var length3D = 0.0; + + var cartographic = cartographicScratch; + cartographic.latitude = cartographicsArray[0]; + cartographic.longitude = cartographicsArray[1]; + cartographic.height = 0.0; + + var segmentStartCartesian = segmentStartTopScratch; + var segmentEndCartesian = projection.project(cartographic, segmentEndTopScratch); + + index = 2; + for (i = 1; i < cartographicsLength; i++) { + cartographic.latitude = cartographicsArray[index]; + cartographic.longitude = cartographicsArray[index + 1]; + + segmentStartCartesian = Cartesian3.clone(segmentEndCartesian, segmentStartCartesian); + segmentEndCartesian = projection.project(cartographic, segmentEndCartesian); + length2D += Cartesian3.distance(segmentStartCartesian, segmentEndCartesian); + index += 2; + } + + // 3D + var positionsLength = topPositionsArray.length / 3; + segmentEndCartesian = Cartesian3.unpack(topPositionsArray, 0, segmentEndCartesian); + + index = 3; + for (i = 1; i < positionsLength; i++) { + segmentStartCartesian = Cartesian3.clone(segmentEndCartesian, segmentStartCartesian); + segmentEndCartesian = Cartesian3.unpack(topPositionsArray, index, segmentEndCartesian); + length3D += Cartesian3.distance(segmentStartCartesian, segmentEndCartesian); + index += 3; + } + + /*** Generate segments ***/ + var j; + index = 3; + var cartographicsIndex = 2; + var vec2sWriteIndex = 0; + var vec3sWriteIndex = 0; + var vec4sWriteIndex = 0; + var miterBroken = false; + + var endBottom = Cartesian3.unpack(bottomPositionsArray, 0, segmentEndBottomScratch); + var endTop = Cartesian3.unpack(topPositionsArray, 0, segmentEndTopScratch); + var endGeometryNormal = Cartesian3.unpack(normalsArray, 0, segmentEndNormalScratch); + + if (loop) { + var preEndBottom = Cartesian3.unpack(bottomPositionsArray, bottomPositionsArray.length - 6, segmentStartBottomScratch); + if (breakMiter(endGeometryNormal, preEndBottom, endBottom, endTop)) { + // Miter broken as if for the last point in the loop, needs to be inverted for first point (clone of endBottom) + endGeometryNormal = Cartesian3.multiplyByScalar(endGeometryNormal, -1.0, endGeometryNormal); + } + } + cartographic.latitude = cartographicsArray[0]; + cartographic.longitude = cartographicsArray[1]; + var end2D = projection.project(cartographic, segmentEnd2DScratch); + var endGeometryNormal2D = projectNormal(projection, endBottom, endGeometryNormal, end2D, segmentEndNormal2DScratch); + + var lengthSoFar3D = 0.0; + var lengthSoFar2D = 0.0; + + var boundingSphere = BoundingSphere.fromPoints([endBottom, endTop]); + for (i = 0; i < segmentCount; i++) { + var startBottom = Cartesian3.clone(endBottom, segmentStartBottomScratch); + var startTop = Cartesian3.clone(endTop, segmentStartTopScratch); + var startGeometryNormal = Cartesian3.clone(endGeometryNormal, segmentStartNormalScratch); + + var start2D = Cartesian3.clone(end2D, segmentStart2DScratch); + var startGeometryNormal2D = Cartesian3.clone(endGeometryNormal2D, segmentStartNormal2DScratch); + + if (miterBroken) { + // If miter was broken for the previous segment's end vertex, flip for this segment's start vertex + // These "normals" are "right facing." + startGeometryNormal = Cartesian3.multiplyByScalar(startGeometryNormal, -1.0, startGeometryNormal); + startGeometryNormal2D = Cartesian3.multiplyByScalar(startGeometryNormal2D, -1.0, startGeometryNormal2D); + } + + endBottom = Cartesian3.unpack(bottomPositionsArray, index, segmentEndBottomScratch); + endTop = Cartesian3.unpack(topPositionsArray, index, segmentEndTopScratch); + endGeometryNormal = Cartesian3.unpack(normalsArray, index, segmentEndNormalScratch); + + BoundingSphere.expand(boundingSphere, endBottom, boundingSphere); + BoundingSphere.expand(boundingSphere, endTop, boundingSphere); + + miterBroken = breakMiter(endGeometryNormal, startBottom, endBottom, endTop); + + cartographic.latitude = cartographicsArray[cartographicsIndex]; + cartographic.longitude = cartographicsArray[cartographicsIndex + 1]; + end2D = projection.project(cartographic, segmentEnd2DScratch); + endGeometryNormal2D = projectNormal(projection, endBottom, endGeometryNormal, end2D, segmentEndNormal2DScratch); + + /**************************************** + * Geometry descriptors: + * - position of start + offset to end + * - start, end, and right-facing planes + * - encoded texture coordinate offsets + ****************************************/ + + /** 3D **/ + var segmentLength3D = Cartesian3.distance(startTop, endTop); + + // Encode start position and end position as high precision point + offset + var encodedStart = EncodedCartesian3.fromCartesian(startBottom, encodeScratch); + var forwardOffset = Cartesian3.subtract(endBottom, startBottom, offsetScratch); + var forward = Cartesian3.normalize(forwardOffset, rightScratch); + + // Right plane + var startUp = Cartesian3.subtract(startTop, startBottom, startUpScratch); + startUp = Cartesian3.normalize(startUp, startUp); + var rightNormal = Cartesian3.cross(forward, startUp, rightScratch); + rightNormal = Cartesian3.normalize(rightNormal, rightNormal); + + // Plane normals perpendicular to "geometry" normals, so cross (startTop - startBottom) with geometry normal at start + var startPlaneNormal = Cartesian3.cross(startUp, startGeometryNormal, startPlaneNormalScratch); + startPlaneNormal = Cartesian3.normalize(startPlaneNormal, startPlaneNormal); + + // Similarly with (endTop - endBottom) + var endUp = Cartesian3.subtract(endTop, endBottom, endUpScratch); + endUp = Cartesian3.normalize(endUp, endUp); + var endPlaneNormal = Cartesian3.cross(endGeometryNormal, endUp, endPlaneNormalScratch); + endPlaneNormal = Cartesian3.normalize(endPlaneNormal, endPlaneNormal); + + var texcoordNormalization3DX = segmentLength3D / length3D; + var texcoordNormalization3DY = lengthSoFar3D / length3D; + + /** 2D **/ + // In 2D case, positions and normals can be done as 2 components + var segmentLength2D = Cartesian3.distance(start2D, end2D); + + var encodedStart2D = EncodedCartesian3.fromCartesian(start2D, encodeScratch2D); + var forwardOffset2D = Cartesian3.subtract(end2D, start2D, forwardOffset2DScratch); + + // Right direction is just forward direction rotated by -90 degrees around Z + // Similarly with plane normals + var right2D = Cartesian3.normalize(forwardOffset2D, right2DScratch); + var swap = right2D.x; + right2D.x = right2D.y; + right2D.y = -swap; + + var texcoordNormalization2DX = segmentLength2D / length2D; + var texcoordNormalization2DY = lengthSoFar2D / length2D; + + /** Pack **/ + for (j = 0; j < 8; j++) { + var vec4Index = vec4sWriteIndex + j * 4; + var vec2Index = vec2sWriteIndex + j * 2; + var wIndex = vec4Index + 3; + + // 3D + Cartesian3.pack(encodedStart.high, startHi_and_forwardOffsetX, vec4Index); + startHi_and_forwardOffsetX[wIndex] = forwardOffset.x; + + Cartesian3.pack(encodedStart.low, startLo_and_forwardOffsetY, vec4Index); + startLo_and_forwardOffsetY[wIndex] = forwardOffset.y; + + Cartesian3.pack(startPlaneNormal, startNormal_and_forwardOffsetZ, vec4Index); + startNormal_and_forwardOffsetZ[wIndex] = forwardOffset.z; + + Cartesian3.pack(endPlaneNormal, endNormal_andTextureCoordinateNormalizationX, vec4Index); + endNormal_andTextureCoordinateNormalizationX[wIndex] = texcoordNormalization3DX; + + Cartesian3.pack(rightNormal, rightNormal_andTextureCoordinateNormalizationY, vec4Index); + rightNormal_andTextureCoordinateNormalizationY[wIndex] = texcoordNormalization3DY; + + // 2D + + startHiLo2D[vec4Index] = encodedStart2D.high.x; + startHiLo2D[vec4Index + 1] = encodedStart2D.high.y; + startHiLo2D[vec4Index + 2] = encodedStart2D.low.x; + startHiLo2D[vec4Index + 3] = encodedStart2D.low.y; + + startEndNormals2D[vec4Index] = -startGeometryNormal2D.y; + startEndNormals2D[vec4Index + 1] = startGeometryNormal2D.x; + startEndNormals2D[vec4Index + 2] = endGeometryNormal2D.y; + startEndNormals2D[vec4Index + 3] = -endGeometryNormal2D.x; + + offsetAndRight2D[vec4Index] = forwardOffset2D.x; + offsetAndRight2D[vec4Index + 1] = forwardOffset2D.y; + offsetAndRight2D[vec4Index + 2] = right2D.x; + offsetAndRight2D[vec4Index + 3] = right2D.y; + + texcoordNormalization2D[vec2Index] = texcoordNormalization2DX; + texcoordNormalization2D[vec2Index + 1] = texcoordNormalization2DY; + } + + /**************************************************************** + * Vertex Positions + * + * Encode which side of the line segment each position is on by + * pushing it "away" by 1 meter along the geometry normal. + * + * This helps when pushing the vertices out by varying amounts to + * help simulate constant screen-space line width. + ****************************************************************/ + // Push out by 1.0 in the "right" direction + var pushedStartBottom = Cartesian3.add(startBottom, startGeometryNormal, startBottom); + var pushedEndBottom = Cartesian3.add(endBottom, endGeometryNormal, endBottom); + var pushedEndTop = Cartesian3.add(endTop, endGeometryNormal, endTop); + var pushedStartTop = Cartesian3.add(startTop, startGeometryNormal, startTop); + Cartesian3.pack(pushedStartBottom, positionsArray, vec3sWriteIndex); + Cartesian3.pack(pushedEndBottom, positionsArray, vec3sWriteIndex + 3); + Cartesian3.pack(pushedEndTop, positionsArray, vec3sWriteIndex + 6); + Cartesian3.pack(pushedStartTop, positionsArray, vec3sWriteIndex + 9); + + // Return to center + pushedStartBottom = Cartesian3.subtract(startBottom, startGeometryNormal, startBottom); + pushedEndBottom = Cartesian3.subtract(endBottom, endGeometryNormal, endBottom); + pushedEndTop = Cartesian3.subtract(endTop, endGeometryNormal, endTop); + pushedStartTop = Cartesian3.subtract(startTop, startGeometryNormal, startTop); + + // Push out by 1.0 in the "left" direction + pushedStartBottom = Cartesian3.subtract(startBottom, startGeometryNormal, startBottom); + pushedEndBottom = Cartesian3.subtract(endBottom, endGeometryNormal, endBottom); + pushedEndTop = Cartesian3.subtract(endTop, endGeometryNormal, endTop); + pushedStartTop = Cartesian3.subtract(startTop, startGeometryNormal, startTop); + Cartesian3.pack(pushedStartBottom, positionsArray, vec3sWriteIndex + 12); + Cartesian3.pack(pushedEndBottom, positionsArray, vec3sWriteIndex + 15); + Cartesian3.pack(pushedEndTop, positionsArray, vec3sWriteIndex + 18); + Cartesian3.pack(pushedStartTop, positionsArray, vec3sWriteIndex + 21); + + // Return next segment's start to center + pushedEndBottom = Cartesian3.add(endBottom, endGeometryNormal, endBottom); + pushedEndTop = Cartesian3.add(endTop, endGeometryNormal, endTop); + + cartographicsIndex += 2; + index += 3; + + vec2sWriteIndex += 16; + vec3sWriteIndex += 24; + vec4sWriteIndex += 32; + + lengthSoFar3D += segmentLength3D; + lengthSoFar2D = segmentLength2D; + } + + /*** Generate indices ***/ + index = 0; + var indexOffset = 0; + for (i = 0; i < segmentCount; i++) { + for (j = 0; j < REFERENCE_INDICES_LENGTH; j++) { + indices[index + j] = REFERENCE_INDICES[j] + indexOffset; + } + indexOffset += 8; + index += REFERENCE_INDICES_LENGTH; + } + + return new Geometry({ + attributes : { + position : new GeometryAttribute({ + componentDatatype : ComponentDatatype.DOUBLE, + componentsPerAttribute : 3, + normalize : false, + values : positionsArray + }), + startHi_and_forwardOffsetX : getVec4Attribute(startHi_and_forwardOffsetX), + startLo_and_forwardOffsetY : getVec4Attribute(startLo_and_forwardOffsetY), + startNormal_and_forwardOffsetZ : getVec4Attribute(startNormal_and_forwardOffsetZ), + endNormal_andTextureCoordinateNormalizationX : getVec4Attribute(endNormal_andTextureCoordinateNormalizationX), + rightNormal_andTextureCoordinateNormalizationY : getVec4Attribute(rightNormal_andTextureCoordinateNormalizationY), + + startHiLo2D : getVec4Attribute(startHiLo2D), + offsetAndRight2D : getVec4Attribute(offsetAndRight2D), + startEndNormals2D : getVec4Attribute(startEndNormals2D), + texcoordNormalization2D : new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 2, + normalize : false, + values : texcoordNormalization2D + }) + }, + indices : indices, + boundingSphere : boundingSphere + }); + } + + function getVec4Attribute(typedArray) { + return new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 4, + normalize : false, + values : typedArray + }); + } + function direction(target, origin, result) { Cartesian3.subtract(target, origin, result); Cartesian3.normalize(result, result); return result; } - // inputs are cartesians - var vertexUpScratch = new Cartesian3(); + // Inputs are cartesians var toPreviousScratch = new Cartesian3(); var toNextScratch = new Cartesian3(); var forwardScratch = new Cartesian3(); diff --git a/Source/Scene/GroundPolylinePrimitive.js b/Source/Scene/GroundPolylinePrimitive.js index f541d5c6e0b6..0c9cbf0db39e 100644 --- a/Source/Scene/GroundPolylinePrimitive.js +++ b/Source/Scene/GroundPolylinePrimitive.js @@ -9,7 +9,6 @@ define([ '../Core/GeometryInstance', '../Core/GeometryInstanceAttribute', '../Core/GroundPolylineGeometry', - '../Core/GroundLineSegmentGeometry', '../Core/isArray', '../Core/Matrix4', '../Shaders/PolylineShadowVolumeVS', @@ -36,7 +35,6 @@ define([ GeometryInstance, GeometryInstanceAttribute, GroundPolylineGeometry, - GroundLineSegmentGeometry, isArray, Matrix4, PolylineShadowVolumeVS, @@ -140,76 +138,6 @@ define([ enabled : true // prevent double-draw. Geometry is "inverted" (reversed winding order) so we're drawing backfaces. } }); - - // Map for synchronizing geometry instance attributes between polylines and line segments - this._idsToInstanceIndices = {}; - this._attributeSynchronizerCache = {}; - } - - function decompose(geometryInstance, projection, polylineSegmentInstances, idsToInstanceIndices) { - var groundPolylineGeometry = geometryInstance.geometry; - // TODO: check and throw using instanceof? - - var commonId = geometryInstance.id; - - var wallVertices = GroundPolylineGeometry.createWallVertices(groundPolylineGeometry, GroundPrimitive._defaultMaxTerrainHeight); - var rightFacingNormals = wallVertices.rightFacingNormals; - var bottomPositions = wallVertices.bottomPositions; - var topPositions = wallVertices.topPositions; - - var totalLength = 0.0; - var totalLength2D = 0.0; - - var i; - var groundPolylineSegmentGeometry; - var verticesLength = rightFacingNormals.length; - var lineGeometries = new Array(verticesLength / 3); - var index = 0; - for (i = 0; i < verticesLength - 3; i += 3) { - groundPolylineSegmentGeometry = GroundLineSegmentGeometry.fromArrays(projection, i, rightFacingNormals, bottomPositions, topPositions); - totalLength += groundPolylineSegmentGeometry.segmentBottomLength; - totalLength2D += groundPolylineSegmentGeometry.segmentBottomLength2D; - - lineGeometries[index++] = groundPolylineSegmentGeometry; - } - - var segmentIndicesStart = polylineSegmentInstances.length; - index = 0; - var lengthSoFar = 0.0; - var lengthSoFar2D = 0.0; - for (i = 0; i < verticesLength - 3; i += 3) { - groundPolylineSegmentGeometry = lineGeometries[index++]; - var segmentLength = groundPolylineSegmentGeometry.segmentBottomLength; - var segmentLength2D = groundPolylineSegmentGeometry.segmentBottomLength2D; - var attributes = GroundLineSegmentGeometry.getAttributes(groundPolylineSegmentGeometry, projection, lengthSoFar, segmentLength, totalLength, lengthSoFar2D, segmentLength2D, totalLength2D); - lengthSoFar += segmentLength; - lengthSoFar2D += segmentLength2D; - - if (defined(geometryInstance.attributes.color)) { - attributes.color = geometryInstance.attributes.color; - } - - attributes.width = new GeometryInstanceAttribute({ - componentDatatype: ComponentDatatype.UNSIGNED_BYTE, - componentsPerAttribute: 1, - normalize : false, - value : [groundPolylineGeometry.width] - }); - - attributes.show = new GeometryInstanceAttribute({ - componentDatatype: ComponentDatatype.UNSIGNED_BYTE, - componentsPerAttribute: 1, - normalize : false, - value : [true] - }); - - polylineSegmentInstances.push(new GeometryInstance({ - geometry : groundPolylineSegmentGeometry, - attributes : attributes, - id : commonId - })); - } - idsToInstanceIndices[commonId] = [segmentIndicesStart, polylineSegmentInstances.length - 1]; } // TODO: remove @@ -477,9 +405,6 @@ define([ var that = this; var primitiveOptions = this._primitiveOptions; if (!defined(this._primitive)) { - // Decompose GeometryInstances into an array of GeometryInstances containing GroundPolylineSegmentGeometries. - // TODO: compute rectangle for getting min/max heights - var polylineSegmentInstances = []; var geometryInstances = isArray(this.polylineGeometryInstances) ? this.polylineGeometryInstances : [this.polylineGeometryInstances]; var geometryInstancesLength = geometryInstances.length; @@ -491,11 +416,8 @@ define([ } } } - for (i = 0; i < geometryInstancesLength; ++i) { - decompose(geometryInstances[i], frameState.mapProjection, polylineSegmentInstances, this._idsToInstanceIndices); - } - primitiveOptions.geometryInstances = polylineSegmentInstances; + primitiveOptions.geometryInstances = geometryInstances; primitiveOptions.appearance = this.appearance; primitiveOptions._createShaderProgramFunction = function(primitive, frameState, appearance) { @@ -543,82 +465,11 @@ define([ this._sp2D = undefined; this._spPick2D = undefined; - //These objects may be fairly large and reference other large objects (like Entities) - //We explicitly set them to undefined here so that the memory can be freed - //even if a reference to the destroyed GroundPolylinePrimitive has been kept around. - this._idsToInstanceIndices = undefined; - this._attributeSynchronizerCache = undefined; - return destroyObject(this); }; - // An object that, on setting an attribute, will set all the instances' attributes. - var userAttributeNames = ['width', 'show', 'color']; // TODO: do we let Primitives swallow generic, user-specified attributes? - var userAttributeCount = userAttributeNames.length; - function InstanceAttributeSynchronizer(batchTable, firstInstanceIndex, lastInstanceIndex, batchTableAttributeIndices) { - var properties = {}; - for (var i = 0; i < userAttributeCount; i++) { - var name = userAttributeNames[i]; - var attributeIndex = batchTableAttributeIndices[name]; - if (defined(attributeIndex)) { - properties[name] = { - get : createGetFunction(batchTable, firstInstanceIndex, attributeIndex), - set : createSetFunction(batchTable, firstInstanceIndex, lastInstanceIndex, attributeIndex) - }; - } - } - defineProperties(this, properties); - } - - function createSetFunction(batchTable, firstInstanceIndex, lastInstanceIndex, attributeIndex) { - return function(value) { - for (var i = firstInstanceIndex; i <= lastInstanceIndex; i++) { - var attributeValue = value[0]; - batchTable.setBatchedAttribute(i, attributeIndex, attributeValue); - } - }; - } - - function createGetFunction(batchTable, instanceIndex, attributeIndex) { - return function() { - var attributeValue = batchTable.getBatchedAttribute(instanceIndex, attributeIndex); - var attribute = batchTable.attributes[attributeIndex]; - var componentsPerAttribute = attribute.componentsPerAttribute; - var value = ComponentDatatype.createTypedArray(attribute.componentDatatype, componentsPerAttribute); - if (defined(attributeValue.constructor.pack)) { - attributeValue.constructor.pack(attributeValue, value, 0); - } else { - value[0] = attributeValue; - } - return value; - }; - } - GroundPolylinePrimitive.prototype.getGeometryInstanceAttributes = function(id) { - //>>includeStart('debug', pragmas.debug); - if (!defined(id)) { - throw new DeveloperError('id is required'); - } - if (!defined(this._primitive) || !defined(this._primitive._batchTable)) { - throw new DeveloperError('must call update before calling getGeometryInstanceAttributes'); - } - //>>includeEnd('debug'); - - // All GeometryInstances generated by decomposing a GroundPolylineGeometry will have - // the same pick ID, so we have to map from their individual instance attributes to a - // master instance attribute and synchronize changes as they happen. - - var instanceIndices = this._idsToInstanceIndices[id]; - if (!defined(instanceIndices)) { - return undefined; - } - var attributeSynchronizer = this._attributeSynchronizerCache[id]; - if (!defined(attributeSynchronizer)) { - attributeSynchronizer = new InstanceAttributeSynchronizer(this._primitive._batchTable, - instanceIndices[0], instanceIndices[1], this._primitive._batchTableAttributeIndices); - this._attributeSynchronizerCache[id] = attributeSynchronizer; - } - return attributeSynchronizer; + return this._primitive.getGeometryInstanceAttributes(id); }; /** diff --git a/Source/Shaders/PolylineShadowVolumeFS.glsl b/Source/Shaders/PolylineShadowVolumeFS.glsl index 3a8724f366e5..04e5fd97d357 100644 --- a/Source/Shaders/PolylineShadowVolumeFS.glsl +++ b/Source/Shaders/PolylineShadowVolumeFS.glsl @@ -6,7 +6,7 @@ varying vec4 v_startPlaneEC; varying vec4 v_endPlaneEC; varying vec4 v_rightPlaneEC; varying vec3 v_forwardDirectionEC; -varying vec3 v_texcoordNormalization; +varying vec2 v_texcoordNormalization; varying float v_halfWidth; #ifdef PER_INSTANCE_COLOR @@ -64,7 +64,7 @@ void main(void) distanceFromEnd = max(0.0, distanceFromEnd); float s = distanceFromStart / (distanceFromStart + distanceFromEnd); - s = ((s * v_texcoordNormalization.y) + v_texcoordNormalization.x) / v_texcoordNormalization.z; + s = (s * v_texcoordNormalization.y) + v_texcoordNormalization.x; float t = (width + halfMaxWidth) / (2.0 * halfMaxWidth); czm_materialInput materialInput; diff --git a/Source/Shaders/PolylineShadowVolumeVS.glsl b/Source/Shaders/PolylineShadowVolumeVS.glsl index 15d36747ebc7..3e20368a213b 100644 --- a/Source/Shaders/PolylineShadowVolumeVS.glsl +++ b/Source/Shaders/PolylineShadowVolumeVS.glsl @@ -1,14 +1,27 @@ attribute vec3 position3DHigh; attribute vec3 position3DLow; + +attribute vec4 startHi_and_forwardOffsetX; +attribute vec4 startLo_and_forwardOffsetY; +attribute vec4 startNormal_and_forwardOffsetZ; +attribute vec4 endNormal_andTextureCoordinateNormalizationX; +attribute vec4 rightNormal_andTextureCoordinateNormalizationY; +attribute vec4 startHiLo2D; +attribute vec4 offsetAndRight2D; +attribute vec4 startEndNormals2D; +attribute vec2 texcoordNormalization2D; + attribute float batchId; varying vec4 v_startPlaneEC; varying vec4 v_endPlaneEC; varying vec4 v_rightPlaneEC; varying vec3 v_forwardDirectionEC; -varying vec3 v_texcoordNormalization; +varying vec2 v_texcoordNormalization; varying float v_halfWidth; -varying float v_width; // for materials + +// For materials +varying float v_width; varying float v_polylineAngle; #ifdef PER_INSTANCE_COLOR @@ -27,69 +40,54 @@ vec3 branchFreeTernary(bool comparison, vec3 a, vec3 b) { // TODO: make branchFr return a * useA + b * (1.0 - useA); } +// TODO: morph? + void main() { #ifdef COLUMBUS_VIEW_2D - vec4 entry = czm_batchTable_startHighLow2D(batchId); - - vec3 ecStart = (czm_modelViewRelativeToEye * czm_translateRelativeToEye(vec3(0.0, entry.xy), vec3(0.0, entry.zw))).xyz; + vec3 ecStart = (czm_modelViewRelativeToEye * czm_translateRelativeToEye(vec3(0.0, startHiLo2D.xy), vec3(0.0, startHiLo2D.zw))).xyz; - entry = czm_batchTable_offsetAndRight2D(batchId); - vec3 forwardDirectionEC = czm_normal * vec3(0.0, entry.xy); + vec3 forwardDirectionEC = czm_normal * vec3(0.0, offsetAndRight2D.xy); vec3 ecEnd = forwardDirectionEC + ecStart; forwardDirectionEC = normalize(forwardDirectionEC); v_forwardDirectionEC = forwardDirectionEC; // Right plane - v_rightPlaneEC.xyz = czm_normal * vec3(0.0, entry.zw); + v_rightPlaneEC.xyz = czm_normal * vec3(0.0, offsetAndRight2D.zw); v_rightPlaneEC.w = -dot(v_rightPlaneEC.xyz, ecStart); - entry = czm_batchTable_startEndNormals2D(batchId); - // start plane - v_startPlaneEC.xyz = czm_normal * vec3(0.0, entry.xy); + v_startPlaneEC.xyz = czm_normal * vec3(0.0, startEndNormals2D.xy); v_startPlaneEC.w = -dot(v_startPlaneEC.xyz, ecStart); // end plane - v_endPlaneEC.xyz = czm_normal * vec3(0.0, entry.zw); + v_endPlaneEC.xyz = czm_normal * vec3(0.0, startEndNormals2D.zw); v_endPlaneEC.w = -dot(v_endPlaneEC.xyz, ecEnd); - v_texcoordNormalization = czm_batchTable_texcoordNormalization2D(batchId); + v_texcoordNormalization = texcoordNormalization2D; #else // COLUMBUS_VIEW_2D - - vec4 entry1 = czm_batchTable_startHi_and_forwardOffsetX(batchId); - vec4 entry2 = czm_batchTable_startLo_and_forwardOffsetY(batchId); - - vec3 ecStart = (czm_modelViewRelativeToEye * czm_translateRelativeToEye(entry1.xyz, entry2.xyz)).xyz; - vec3 offset = vec3(entry1.w, entry2.w, 0.0); - - entry1 = czm_batchTable_startNormal_and_forwardOffsetZ(batchId); - - offset.z = entry1.w; - offset = czm_normal * offset; + vec3 ecStart = (czm_modelViewRelativeToEye * czm_translateRelativeToEye(startHi_and_forwardOffsetX.xyz, startLo_and_forwardOffsetY.xyz)).xyz; + vec3 offset = czm_normal * vec3(startHi_and_forwardOffsetX.w, startLo_and_forwardOffsetY.w, startNormal_and_forwardOffsetZ.w); vec3 ecEnd = ecStart + offset; vec3 forwardDirectionEC = normalize(offset); v_forwardDirectionEC = forwardDirectionEC; + // start plane + v_startPlaneEC.xyz = czm_normal * startNormal_and_forwardOffsetZ.xyz; + v_startPlaneEC.w = -dot(v_startPlaneEC.xyz, ecStart); + // end plane - vec3 ecEndNormal = czm_normal * czm_batchTable_endNormal(batchId); - v_endPlaneEC.xyz = ecEndNormal; - v_endPlaneEC.w = -dot(ecEndNormal, ecEnd); + v_endPlaneEC.xyz = czm_normal * endNormal_andTextureCoordinateNormalizationX.xyz; + v_endPlaneEC.w = -dot(v_endPlaneEC.xyz, ecEnd); // Right plane - vec3 ecRight = czm_normal * czm_batchTable_rightNormal(batchId); - v_rightPlaneEC.xyz = ecRight; - v_rightPlaneEC.w = -dot(ecRight, ecStart); - - // start plane - vec3 ecStartNormal = czm_normal * entry1.xyz; - v_startPlaneEC.xyz = ecStartNormal; - v_startPlaneEC.w = -dot(ecStartNormal, ecStart); + v_rightPlaneEC.xyz = czm_normal * rightNormal_andTextureCoordinateNormalizationY.xyz; + v_rightPlaneEC.w = -dot(v_rightPlaneEC.xyz, ecStart); - v_texcoordNormalization = czm_batchTable_texcoordNormalization(batchId); + v_texcoordNormalization = vec2(endNormal_andTextureCoordinateNormalizationX.w, rightNormal_andTextureCoordinateNormalizationY.w); #endif // COLUMBUS_VIEW_2D diff --git a/Source/Workers/createGroundLineSegmentGeometry.js b/Source/Workers/createGroundLineSegmentGeometry.js deleted file mode 100644 index d6c8a3d5098d..000000000000 --- a/Source/Workers/createGroundLineSegmentGeometry.js +++ /dev/null @@ -1,17 +0,0 @@ -define([ - '../Core/defined', - '../Core/GroundLineSegmentGeometry' - ], function( - defined, - GroundLineSegmentGeometry) { - 'use strict'; - - function createGroundLineSegmentGeometry(groundPolylineSegmentGeometry, offset) { - if (defined(offset)) { - groundPolylineSegmentGeometry = GroundLineSegmentGeometry.unpack(groundPolylineSegmentGeometry, offset); - } - return GroundLineSegmentGeometry.createGeometry(groundPolylineSegmentGeometry); - } - - return createGroundLineSegmentGeometry; -}); diff --git a/Source/Workers/createGroundPolylineGeometry.js b/Source/Workers/createGroundPolylineGeometry.js new file mode 100644 index 000000000000..1ca74743c7b7 --- /dev/null +++ b/Source/Workers/createGroundPolylineGeometry.js @@ -0,0 +1,17 @@ +define([ + '../Core/defined', + '../Core/GroundPolylineGeometry' + ], function( + defined, + GroundPolylineGeometry) { + 'use strict'; + + function createGroundPolylineGeometry(groundPolylineGeometry, offset) { + if (defined(offset)) { + groundPolylineGeometry = GroundPolylineGeometry.unpack(groundPolylineGeometry, offset); + } + return GroundPolylineGeometry.createGeometry(groundPolylineGeometry); + } + + return createGroundPolylineGeometry; +}); From fe4849e8117601e9e561d4c9add22ba18a6a30d7 Mon Sep 17 00:00:00 2001 From: Kangning Li Date: Tue, 29 May 2018 15:31:23 -0400 Subject: [PATCH 10/39] integrate ApproximateTerrainHeights into GroundPolylineGeometry, permit asynchronous createGeometry worker functions [ci skip] --- Source/Core/ApproximateTerrainHeights.js | 5 +- Source/Core/GroundPolylineGeometry.js | 98 +++++++++++++------ Source/Scene/GroundPolylinePrimitive.js | 44 +++++++++ Source/Workers/createGeometry.js | 12 ++- .../Workers/createGroundPolylineGeometry.js | 17 ++-- Source/Workers/createTaskProcessorWorker.js | 95 ++++++++++-------- 6 files changed, 190 insertions(+), 81 deletions(-) diff --git a/Source/Core/ApproximateTerrainHeights.js b/Source/Core/ApproximateTerrainHeights.js index e295abe57a39..fb489798b013 100644 --- a/Source/Core/ApproximateTerrainHeights.js +++ b/Source/Core/ApproximateTerrainHeights.js @@ -49,13 +49,14 @@ define([ * Initializes the minimum and maximum terrain heights * @return {Promise} */ - ApproximateTerrainHeights.initialize = function() { + ApproximateTerrainHeights.initialize = function(url) { var initPromise = ApproximateTerrainHeights._initPromise; if (defined(initPromise)) { return initPromise; } - ApproximateTerrainHeights._initPromise = Resource.fetchJson(buildModuleUrl('Assets/approximateTerrainHeights.json')).then(function(json) { + url = defaultValue(url, 'Assets/approximateTerrainHeights.json'); + ApproximateTerrainHeights._initPromise = Resource.fetchJson(buildModuleUrl(url)).then(function(json) { ApproximateTerrainHeights._terrainHeights = json; }); diff --git a/Source/Core/GroundPolylineGeometry.js b/Source/Core/GroundPolylineGeometry.js index c45578347c76..670ce4e87111 100644 --- a/Source/Core/GroundPolylineGeometry.js +++ b/Source/Core/GroundPolylineGeometry.js @@ -399,7 +399,7 @@ define([ cartographicsArray.push(startCartographic.longitude); } - return generateGeometryAttributes(projection, bottomPositionsArray, topPositionsArray, normalsArray, cartographicsArray, loop); + return generateGeometryAttributes(groundPolylineGeometry, projection, bottomPositionsArray, topPositionsArray, normalsArray, cartographicsArray); }; var positionCartographicScratch = new Cartographic(); @@ -417,7 +417,25 @@ define([ return result; } - var cartographicScratch = new Cartographic(); + var adjustHeightNormalScratch = new Cartesian3(); + var adjustHeightOffsetScratch = new Cartesian3(); + function adjustHeights(groundPolylineGeometry, bottom, top, minHeight, maxHeight, adjustHeightBottom, adjustHeightTop) { + // bottom and top should be at groundPolylineGeometry.minimumTerrainHeight and groundPolylineGeometry.maximumTerrainHeight, respectively + var adjustHeightNormal = Cartesian3.subtract(top, bottom, adjustHeightNormalScratch); + Cartesian3.normalize(adjustHeightNormal, adjustHeightNormal); + + var distanceForBottom = minHeight - groundPolylineGeometry.minimumTerrainHeight; + var adjustHeightOffset = Cartesian3.multiplyByScalar(adjustHeightNormal, distanceForBottom, adjustHeightOffsetScratch); + Cartesian3.add(bottom, adjustHeightOffset, adjustHeightBottom); + + var distanceForTop = maxHeight - groundPolylineGeometry.maximumTerrainHeight; + adjustHeightOffset = Cartesian3.multiplyByScalar(adjustHeightNormal, distanceForTop, adjustHeightOffsetScratch); + Cartesian3.add(top, adjustHeightOffset, adjustHeightTop); + } + + var startCartographicScratch = new Cartographic(); + var endCartographicScratch = new Cartographic(); + var segmentStartTopScratch = new Cartesian3(); var segmentEndTopScratch = new Cartesian3(); var segmentStartBottomScratch = new Cartesian3(); @@ -425,6 +443,14 @@ define([ var segmentStartNormalScratch = new Cartesian3(); var segmentEndNormalScratch = new Cartesian3(); + var getHeightCartographics = [startCartographicScratch, endCartographicScratch]; + var getHeightRectangleScratch = new Rectangle(); + + var adjustHeightStartTopScratch = new Cartesian3(); + var adjustHeightEndTopScratch = new Cartesian3(); + var adjustHeightStartBottomScratch = new Cartesian3(); + var adjustHeightEndBottomScratch = new Cartesian3(); + var segmentStart2DScratch = new Cartesian3(); var segmentEnd2DScratch = new Cartesian3(); var segmentStartNormal2DScratch = new Cartesian3(); @@ -452,9 +478,11 @@ define([ 3, 6, 2, 3, 7, 6 // top ]; var REFERENCE_INDICES_LENGTH = REFERENCE_INDICES.length; - function generateGeometryAttributes(projection, bottomPositionsArray, topPositionsArray, normalsArray, cartographicsArray, loop) { + function generateGeometryAttributes(groundPolylineGeometry, projection, bottomPositionsArray, topPositionsArray, normalsArray, cartographicsArray) { var i; var index; + var loop = groundPolylineGeometry.loop; + var ellipsoid = groundPolylineGeometry.ellipsoid; // Each segment will have 8 vertices var segmentCount = (bottomPositionsArray.length / 3) - 1; @@ -482,7 +510,7 @@ define([ var length2D = 0.0; var length3D = 0.0; - var cartographic = cartographicScratch; + var cartographic = startCartographicScratch; cartographic.latitude = cartographicsArray[0]; cartographic.longitude = cartographicsArray[1]; cartographic.height = 0.0; @@ -533,9 +561,11 @@ define([ endGeometryNormal = Cartesian3.multiplyByScalar(endGeometryNormal, -1.0, endGeometryNormal); } } - cartographic.latitude = cartographicsArray[0]; - cartographic.longitude = cartographicsArray[1]; - var end2D = projection.project(cartographic, segmentEnd2DScratch); + var endCartographic = endCartographicScratch; + var startCartographic = startCartographicScratch; + endCartographic.latitude = cartographicsArray[0]; + endCartographic.longitude = cartographicsArray[1]; + var end2D = projection.project(endCartographic, segmentEnd2DScratch); var endGeometryNormal2D = projectNormal(projection, endBottom, endGeometryNormal, end2D, segmentEndNormal2DScratch); var lengthSoFar3D = 0.0; @@ -549,10 +579,11 @@ define([ var start2D = Cartesian3.clone(end2D, segmentStart2DScratch); var startGeometryNormal2D = Cartesian3.clone(endGeometryNormal2D, segmentStartNormal2DScratch); + startCartographic = Cartographic.clone(endCartographic, startCartographic); if (miterBroken) { // If miter was broken for the previous segment's end vertex, flip for this segment's start vertex - // These "normals" are "right facing." + // These normals are "right facing." startGeometryNormal = Cartesian3.multiplyByScalar(startGeometryNormal, -1.0, startGeometryNormal); startGeometryNormal2D = Cartesian3.multiplyByScalar(startGeometryNormal2D, -1.0, startGeometryNormal2D); } @@ -566,9 +597,9 @@ define([ miterBroken = breakMiter(endGeometryNormal, startBottom, endBottom, endTop); - cartographic.latitude = cartographicsArray[cartographicsIndex]; - cartographic.longitude = cartographicsArray[cartographicsIndex + 1]; - end2D = projection.project(cartographic, segmentEnd2DScratch); + endCartographic.latitude = cartographicsArray[cartographicsIndex]; + endCartographic.longitude = cartographicsArray[cartographicsIndex + 1]; + end2D = projection.project(endCartographic, segmentEnd2DScratch); endGeometryNormal2D = projectNormal(projection, endBottom, endGeometryNormal, end2D, segmentEndNormal2DScratch); /**************************************** @@ -645,7 +676,6 @@ define([ rightNormal_andTextureCoordinateNormalizationY[wIndex] = texcoordNormalization3DY; // 2D - startHiLo2D[vec4Index] = encodedStart2D.high.x; startHiLo2D[vec4Index + 1] = encodedStart2D.high.y; startHiLo2D[vec4Index + 2] = encodedStart2D.low.x; @@ -671,39 +701,49 @@ define([ * Encode which side of the line segment each position is on by * pushing it "away" by 1 meter along the geometry normal. * - * This helps when pushing the vertices out by varying amounts to + * Needed when pushing the vertices out by varying amounts to * help simulate constant screen-space line width. ****************************************************************/ + // Adjust heights of positions in 3D + var adjustHeightStartBottom = adjustHeightStartBottomScratch; + var adjustHeightEndBottom = adjustHeightEndBottomScratch; + var adjustHeightStartTop = adjustHeightStartTopScratch; + var adjustHeightEndTop = adjustHeightEndTopScratch; + + var getHeightsRectangle = Rectangle.fromCartographicArray(getHeightCartographics, getHeightRectangleScratch); + var minMaxHeights = ApproximateTerrainHeights.getApproximateTerrainHeights(getHeightsRectangle, ellipsoid); + var minHeight = minMaxHeights.minimumTerrainHeight; + var maxHeight = minMaxHeights.maximumTerrainHeight; + + adjustHeights(groundPolylineGeometry, startBottom, startTop, minHeight, maxHeight, adjustHeightStartBottom, adjustHeightStartTop); + adjustHeights(groundPolylineGeometry, endBottom, endTop, minHeight, maxHeight, adjustHeightEndBottom, adjustHeightEndTop); + // Push out by 1.0 in the "right" direction - var pushedStartBottom = Cartesian3.add(startBottom, startGeometryNormal, startBottom); - var pushedEndBottom = Cartesian3.add(endBottom, endGeometryNormal, endBottom); - var pushedEndTop = Cartesian3.add(endTop, endGeometryNormal, endTop); - var pushedStartTop = Cartesian3.add(startTop, startGeometryNormal, startTop); + var pushedStartBottom = Cartesian3.add(adjustHeightStartBottom, startGeometryNormal, adjustHeightStartBottom); + var pushedEndBottom = Cartesian3.add(adjustHeightEndBottom, endGeometryNormal, adjustHeightEndBottom); + var pushedEndTop = Cartesian3.add(adjustHeightEndTop, endGeometryNormal, adjustHeightEndTop); + var pushedStartTop = Cartesian3.add(adjustHeightStartTop, startGeometryNormal, adjustHeightStartTop); Cartesian3.pack(pushedStartBottom, positionsArray, vec3sWriteIndex); Cartesian3.pack(pushedEndBottom, positionsArray, vec3sWriteIndex + 3); Cartesian3.pack(pushedEndTop, positionsArray, vec3sWriteIndex + 6); Cartesian3.pack(pushedStartTop, positionsArray, vec3sWriteIndex + 9); // Return to center - pushedStartBottom = Cartesian3.subtract(startBottom, startGeometryNormal, startBottom); - pushedEndBottom = Cartesian3.subtract(endBottom, endGeometryNormal, endBottom); - pushedEndTop = Cartesian3.subtract(endTop, endGeometryNormal, endTop); - pushedStartTop = Cartesian3.subtract(startTop, startGeometryNormal, startTop); + pushedStartBottom = Cartesian3.subtract(adjustHeightStartBottom, startGeometryNormal, adjustHeightStartBottom); + pushedEndBottom = Cartesian3.subtract(adjustHeightEndBottom, endGeometryNormal, adjustHeightEndBottom); + pushedEndTop = Cartesian3.subtract(adjustHeightEndTop, endGeometryNormal, adjustHeightEndTop); + pushedStartTop = Cartesian3.subtract(adjustHeightStartTop, startGeometryNormal, adjustHeightStartTop); // Push out by 1.0 in the "left" direction - pushedStartBottom = Cartesian3.subtract(startBottom, startGeometryNormal, startBottom); - pushedEndBottom = Cartesian3.subtract(endBottom, endGeometryNormal, endBottom); - pushedEndTop = Cartesian3.subtract(endTop, endGeometryNormal, endTop); - pushedStartTop = Cartesian3.subtract(startTop, startGeometryNormal, startTop); + pushedStartBottom = Cartesian3.subtract(adjustHeightStartBottom, startGeometryNormal, adjustHeightStartBottom); + pushedEndBottom = Cartesian3.subtract(adjustHeightEndBottom, endGeometryNormal, adjustHeightEndBottom); + pushedEndTop = Cartesian3.subtract(adjustHeightEndTop, endGeometryNormal, adjustHeightEndTop); + pushedStartTop = Cartesian3.subtract(adjustHeightStartTop, startGeometryNormal, adjustHeightStartTop); Cartesian3.pack(pushedStartBottom, positionsArray, vec3sWriteIndex + 12); Cartesian3.pack(pushedEndBottom, positionsArray, vec3sWriteIndex + 15); Cartesian3.pack(pushedEndTop, positionsArray, vec3sWriteIndex + 18); Cartesian3.pack(pushedStartTop, positionsArray, vec3sWriteIndex + 21); - // Return next segment's start to center - pushedEndBottom = Cartesian3.add(endBottom, endGeometryNormal, endBottom); - pushedEndTop = Cartesian3.add(endTop, endGeometryNormal, endTop); - cartographicsIndex += 2; index += 3; diff --git a/Source/Scene/GroundPolylinePrimitive.js b/Source/Scene/GroundPolylinePrimitive.js index 0c9cbf0db39e..9687a9ef7c6b 100644 --- a/Source/Scene/GroundPolylinePrimitive.js +++ b/Source/Scene/GroundPolylinePrimitive.js @@ -1,4 +1,5 @@ define([ + '../Core/ApproximateTerrainHeights', '../Core/Check', '../Core/ComponentDatatype', '../Core/defaultValue', @@ -25,6 +26,7 @@ define([ './Primitive', './SceneMode' ], function( + ApproximateTerrainHeights, Check, ComponentDatatype, defaultValue, @@ -140,6 +142,37 @@ define([ }); } + GroundPolylinePrimitive._initialized = false; + GroundPolylinePrimitive._initPromise = undefined; + + GroundPolylinePrimitive.initializeTerrainHeights = function() { + var initPromise = GroundPolylinePrimitive._initPromise; + if (defined(initPromise)) { + return initPromise; + } + + GroundPolylinePrimitive._initPromise = ApproximateTerrainHeights.initialize() + .then(function() { + GroundPolylinePrimitive._initialized = true; + }); + + return GroundPolylinePrimitive._initPromise; + }; + + GroundPolylinePrimitive._initializeTerrainHeightsWorker = function() { + var initPromise = GroundPolylinePrimitive._initPromise; + if (defined(initPromise)) { + return initPromise; + } + + GroundPolylinePrimitive._initPromise = ApproximateTerrainHeights.initialize('../Assets/approximateTerrainHeights.json') + .then(function() { + GroundPolylinePrimitive._initialized = true; + }); + + return GroundPolylinePrimitive._initPromise; + }; + // TODO: remove function validateShaderMatching(shaderProgram, attributeLocations) { // For a VAO and shader program to be compatible, the VAO must have @@ -400,6 +433,17 @@ define([ return; } + if (!GroundPolylinePrimitive._initialized) { + //>>includeStart('debug', pragmas.debug); + if (!this.asynchronous) { + throw new DeveloperError('For synchronous GroundPolylinePrimitives, you must call GroundPolylinePrimitives.initializeTerrainHeights() and wait for the returned promise to resolve.'); + } + //>>includeEnd('debug'); + + GroundPolylinePrimitive.initializeTerrainHeights(); + return; + } + var i; var that = this; diff --git a/Source/Workers/createGeometry.js b/Source/Workers/createGeometry.js index f82872a34920..49fb0d2ca04c 100644 --- a/Source/Workers/createGeometry.js +++ b/Source/Workers/createGeometry.js @@ -1,11 +1,13 @@ define([ '../Core/defined', '../Scene/PrimitivePipeline', + '../ThirdParty/when', './createTaskProcessorWorker', 'require' ], function( defined, PrimitivePipeline, + when, createTaskProcessorWorker, require) { 'use strict'; @@ -33,7 +35,7 @@ define([ function createGeometry(parameters, transferableObjects) { var subTasks = parameters.subTasks; var length = subTasks.length; - var results = new Array(length); + var resultsOrPromises = new Array(length); for (var i = 0; i < length; i++) { var task = subTasks[i]; @@ -42,14 +44,16 @@ define([ if (defined(moduleName)) { var createFunction = getModule(moduleName); - results[i] = createFunction(geometry, task.offset); + resultsOrPromises[i] = createFunction(geometry, task.offset); } else { //Already created geometry - results[i] = geometry; + resultsOrPromises[i] = geometry; } } - return PrimitivePipeline.packCreateGeometryResults(results, transferableObjects); + return when.all(resultsOrPromises, function(results) { + return PrimitivePipeline.packCreateGeometryResults(results, transferableObjects); + }); } return createTaskProcessorWorker(createGeometry); diff --git a/Source/Workers/createGroundPolylineGeometry.js b/Source/Workers/createGroundPolylineGeometry.js index 1ca74743c7b7..d7f1a992a7f7 100644 --- a/Source/Workers/createGroundPolylineGeometry.js +++ b/Source/Workers/createGroundPolylineGeometry.js @@ -1,16 +1,21 @@ define([ '../Core/defined', - '../Core/GroundPolylineGeometry' + '../Core/GroundPolylineGeometry', + '../Scene/GroundPolylinePrimitive' ], function( defined, - GroundPolylineGeometry) { + GroundPolylineGeometry, + GroundPolylinePrimitive) { 'use strict'; function createGroundPolylineGeometry(groundPolylineGeometry, offset) { - if (defined(offset)) { - groundPolylineGeometry = GroundPolylineGeometry.unpack(groundPolylineGeometry, offset); - } - return GroundPolylineGeometry.createGeometry(groundPolylineGeometry); + return GroundPolylinePrimitive._initializeTerrainHeightsWorker() + .then(function() { + if (defined(offset)) { + groundPolylineGeometry = GroundPolylineGeometry.unpack(groundPolylineGeometry, offset); + } + return GroundPolylineGeometry.createGeometry(groundPolylineGeometry); + }); } return createGroundPolylineGeometry; diff --git a/Source/Workers/createTaskProcessorWorker.js b/Source/Workers/createTaskProcessorWorker.js index ce0d12a5a249..fc4261f59c76 100644 --- a/Source/Workers/createTaskProcessorWorker.js +++ b/Source/Workers/createTaskProcessorWorker.js @@ -1,13 +1,29 @@ define([ + '../ThirdParty/when', '../Core/defaultValue', '../Core/defined', '../Core/formatError' ], function( + when, defaultValue, defined, formatError) { 'use strict'; + // createXXXGeometry functions may return Geometry or a Promise that resolves to Geometry + // if the function requires access to ApproximateTerrainHeights. + // For fully synchronous functions, just wrapping the function call in a `when` Promise doesn't + // handle errors correctly, hence try-catch + function callAndWrap(workerFunction, parameters, transferableObjects) { + var resultOrPromise; + try { + resultOrPromise = workerFunction(parameters, transferableObjects); + return resultOrPromise; // errors handled by Promise + } catch (e) { + return when.reject(e); + } + } + /** * Creates an adapter function to allow a calculation function to operate as a Web Worker, * paired with TaskProcessor, to receive tasks and return results. @@ -35,54 +51,53 @@ define([ */ function createTaskProcessorWorker(workerFunction) { var postMessage; - var transferableObjects = []; - var responseMessage = { - id : undefined, - result : undefined, - error : undefined - }; return function(event) { /*global self*/ var data = event.data; - transferableObjects.length = 0; - responseMessage.id = data.id; - responseMessage.error = undefined; - responseMessage.result = undefined; - - try { - responseMessage.result = workerFunction(data.parameters, transferableObjects); - } catch (e) { - if (e instanceof Error) { - // Errors can't be posted in a message, copy the properties - responseMessage.error = { - name : e.name, - message : e.message, - stack : e.stack - }; - } else { - responseMessage.error = e; - } - } + var transferableObjects = []; + var responseMessage = { + id : data.id, + result : undefined, + error : undefined + }; - if (!defined(postMessage)) { - postMessage = defaultValue(self.webkitPostMessage, self.postMessage); - } + return when(callAndWrap(workerFunction, data.parameters, transferableObjects)) + .then(function(result) { + responseMessage.result = result; + }) + .otherwise(function(e) { + if (e instanceof Error) { + // Errors can't be posted in a message, copy the properties + responseMessage.error = { + name : e.name, + message : e.message, + stack : e.stack + }; + } else { + responseMessage.error = e; + } + }) + .always(function() { + if (!defined(postMessage)) { + postMessage = defaultValue(self.webkitPostMessage, self.postMessage); + } - if (!data.canTransferArrayBuffer) { - transferableObjects.length = 0; - } + if (!data.canTransferArrayBuffer) { + transferableObjects.length = 0; + } - try { - postMessage(responseMessage, transferableObjects); - } catch (e) { - // something went wrong trying to post the message, post a simpler - // error that we can be sure will be cloneable - responseMessage.result = undefined; - responseMessage.error = 'postMessage failed with error: ' + formatError(e) + '\n with responseMessage: ' + JSON.stringify(responseMessage); - postMessage(responseMessage); - } + try { + postMessage(responseMessage, transferableObjects); + } catch (e) { + // something went wrong trying to post the message, post a simpler + // error that we can be sure will be cloneable + responseMessage.result = undefined; + responseMessage.error = 'postMessage failed with error: ' + formatError(e) + '\n with responseMessage: ' + JSON.stringify(responseMessage); + postMessage(responseMessage); + } + }); }; } From 5de9ded4b2ff1a660fe24ce049ebe1120e8c77e2 Mon Sep 17 00:00:00 2001 From: Kangning Li Date: Tue, 29 May 2018 16:16:24 -0400 Subject: [PATCH 11/39] some shader cleanup, restore automatic width attribute for polylines on terrain [ci skip] --- .../development/Polylines On Terrain.html | 16 ++--- Source/Core/GroundPolylineGeometry.js | 3 + Source/Scene/GroundPolylinePrimitive.js | 13 ++++ .../Builtin/Functions/branchFreeTernary.glsl | 71 +++++++++++++++++++ .../Functions/branchFreeTernaryFloat.glsl | 17 ----- .../Functions/fastApproximateAtan.glsl | 6 +- Source/Shaders/PolylineShadowVolumeFS.glsl | 17 +++-- Source/Shaders/PolylineShadowVolumeVS.glsl | 23 ++---- Specs/Renderer/BuiltinFunctionsSpec.js | 31 +++++++- 9 files changed, 137 insertions(+), 60 deletions(-) create mode 100644 Source/Shaders/Builtin/Functions/branchFreeTernary.glsl delete mode 100644 Source/Shaders/Builtin/Functions/branchFreeTernaryFloat.glsl diff --git a/Apps/Sandcastle/gallery/development/Polylines On Terrain.html b/Apps/Sandcastle/gallery/development/Polylines On Terrain.html index 5c01d5b8b0be..2e75bcea140e 100644 --- a/Apps/Sandcastle/gallery/development/Polylines On Terrain.html +++ b/Apps/Sandcastle/gallery/development/Polylines On Terrain.html @@ -110,15 +110,11 @@ var instance1 = new Cesium.GeometryInstance({ geometry : new Cesium.GroundPolylineGeometry({ positions : polylineCartographics, - loop : false + loop : false, + width : 16.0 }), id : 'polyline1', attributes : { - width : new Cesium.GeometryInstanceAttribute({ - componentDatatype : Cesium.ComponentDatatype.UNSIGNED_BYTE, - componentsPerAttribute : 1.0, - value : [16.0] - }), show : new Cesium.ShowGeometryInstanceAttribute(), color : Cesium.ColorGeometryInstanceAttribute.fromColor(Cesium.Color.fromCssColorString('green').withAlpha(0.7)) } @@ -127,15 +123,11 @@ var instance2 = new Cesium.GeometryInstance({ geometry : new Cesium.GroundPolylineGeometry({ positions : loopCartographics, - loop : true + loop : true, + width : 8.0 }), id : 'polyline2', attributes : { - width : new Cesium.GeometryInstanceAttribute({ - componentDatatype : Cesium.ComponentDatatype.UNSIGNED_BYTE, - componentsPerAttribute : 1.0, - value : [8.0] - }), show : new Cesium.ShowGeometryInstanceAttribute(), color : Cesium.ColorGeometryInstanceAttribute.fromColor(Cesium.Color.fromCssColorString('#67ADDF').withAlpha(0.7)) } diff --git a/Source/Core/GroundPolylineGeometry.js b/Source/Core/GroundPolylineGeometry.js index 670ce4e87111..232eb7d1b875 100644 --- a/Source/Core/GroundPolylineGeometry.js +++ b/Source/Core/GroundPolylineGeometry.js @@ -57,6 +57,7 @@ define([ * @constructor * * @param {Object} [options] Options with the following properties: + * @param {Number} [options.width=1.0] The width in pixels. * @param {Cartographic[]} [options.positions] An array of {@link Cartographic} defining the polyline's points. Heights will be ignored. * @param {Number} [options.granularity=9999.0] The distance interval used for interpolating options.points. Defaults to 9999.0 meters. Zero indicates no interpolation. * @param {Boolean} [options.loop=false] Whether during geometry creation a line segment will be added between the last and first line positions to make this Polyline a loop. @@ -70,6 +71,8 @@ define([ function GroundPolylineGeometry(options) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); + this.width = defaultValue(options.width, 1.0); // Doesn't get packed, not necessary for computing geometry. + this._positions = defaultValue(options.positions, []); /** diff --git a/Source/Scene/GroundPolylinePrimitive.js b/Source/Scene/GroundPolylinePrimitive.js index 9687a9ef7c6b..1e9597b9c1d4 100644 --- a/Source/Scene/GroundPolylinePrimitive.js +++ b/Source/Scene/GroundPolylinePrimitive.js @@ -461,6 +461,19 @@ define([ } } + // Automatically create line width attributes + for (i = 0; i < geometryInstancesLength; ++i) { + var geometryInstance = geometryInstances[i]; + var attributes = geometryInstance.attributes; + if (!defined(attributes.width)) { + attributes.width = new GeometryInstanceAttribute({ + componentDatatype : ComponentDatatype.UNSIGNED_BYTE, + componentsPerAttribute : 1.0, + value : [geometryInstance.geometry.width] + }); + } + } + primitiveOptions.geometryInstances = geometryInstances; primitiveOptions.appearance = this.appearance; diff --git a/Source/Shaders/Builtin/Functions/branchFreeTernary.glsl b/Source/Shaders/Builtin/Functions/branchFreeTernary.glsl new file mode 100644 index 000000000000..5959d1387713 --- /dev/null +++ b/Source/Shaders/Builtin/Functions/branchFreeTernary.glsl @@ -0,0 +1,71 @@ +/** + * Branchless ternary operator to be used when it's inexpensive to explicitly + * evaluate both possibilities for a float expression. + * + * @name czm_branchFreeTernary + * @glslFunction + * + * @param {bool} comparison A comparison statement + * @param {float} a Value to return if the comparison is true. + * @param {float} b Value to return if the comparison is false. + * + * @returns {float} equivalent of comparison ? a : b + */ +float czm_branchFreeTernary(bool comparison, float a, float b) { + float useA = float(comparison); + return a * useA + b * (1.0 - useA); +} + +/** + * Branchless ternary operator to be used when it's inexpensive to explicitly + * evaluate both possibilities for a vec2 expression. + * + * @name czm_branchFreeTernary + * @glslFunction + * + * @param {bool} comparison A comparison statement + * @param {vec2} a Value to return if the comparison is true. + * @param {vec2} b Value to return if the comparison is false. + * + * @returns {vec2} equivalent of comparison ? a : b + */ +vec2 czm_branchFreeTernary(bool comparison, vec2 a, vec2 b) { + float useA = float(comparison); + return a * useA + b * (1.0 - useA); +} + +/** + * Branchless ternary operator to be used when it's inexpensive to explicitly + * evaluate both possibilities for a vec3 expression. + * + * @name czm_branchFreeTernary + * @glslFunction + * + * @param {bool} comparison A comparison statement + * @param {vec3} a Value to return if the comparison is true. + * @param {vec3} b Value to return if the comparison is false. + * + * @returns {vec3} equivalent of comparison ? a : b + */ +vec3 czm_branchFreeTernary(bool comparison, vec3 a, vec3 b) { + float useA = float(comparison); + return a * useA + b * (1.0 - useA); +} + +/** + * Branchless ternary operator to be used when it's inexpensive to explicitly + * evaluate both possibilities for a vec4 expression. + * + * @name czm_branchFreeTernary + * @glslFunction + * + * @param {bool} comparison A comparison statement + * @param {vec3} a Value to return if the comparison is true. + * @param {vec3} b Value to return if the comparison is false. + * + * @returns {vec3} equivalent of comparison ? a : b + */ +vec4 czm_branchFreeTernary(bool comparison, vec4 a, vec4 b) { + float useA = float(comparison); + return a * useA + b * (1.0 - useA); +} diff --git a/Source/Shaders/Builtin/Functions/branchFreeTernaryFloat.glsl b/Source/Shaders/Builtin/Functions/branchFreeTernaryFloat.glsl deleted file mode 100644 index 951f2b155cee..000000000000 --- a/Source/Shaders/Builtin/Functions/branchFreeTernaryFloat.glsl +++ /dev/null @@ -1,17 +0,0 @@ -/** - * Branchless ternary operator to be used when it's inexpensive to explicitly - * evaluate both possibilities for a float expression. - * - * @name czm_branchFreeTernaryFloat - * @glslFunction - * - * @param {bool} comparison A comparison statement - * @param {float} a Value to return if the comparison is true. - * @param {float} b Value to return if the comparison is false. - * - * @returns {float} equivalent of comparison ? a : b - */ -float czm_branchFreeTernaryFloat(bool comparison, float a, float b) { - float useA = float(comparison); - return a * useA + b * (1.0 - useA); -} diff --git a/Source/Shaders/Builtin/Functions/fastApproximateAtan.glsl b/Source/Shaders/Builtin/Functions/fastApproximateAtan.glsl index 56dd587019b5..09d639a99a43 100644 --- a/Source/Shaders/Builtin/Functions/fastApproximateAtan.glsl +++ b/Source/Shaders/Builtin/Functions/fastApproximateAtan.glsl @@ -48,8 +48,8 @@ float czm_fastApproximateAtan(float x, float y) { t = czm_fastApproximateAtan(opposite / adjacent); // Undo range reduction - t = czm_branchFreeTernaryFloat(abs(y) > abs(x), czm_piOverTwo - t, t); - t = czm_branchFreeTernaryFloat(x < 0.0, czm_pi - t, t); - t = czm_branchFreeTernaryFloat(y < 0.0, -t, t); + t = czm_branchFreeTernary(abs(y) > abs(x), czm_piOverTwo - t, t); + t = czm_branchFreeTernary(x < 0.0, czm_pi - t, t); + t = czm_branchFreeTernary(y < 0.0, -t, t); return t; } diff --git a/Source/Shaders/PolylineShadowVolumeFS.glsl b/Source/Shaders/PolylineShadowVolumeFS.glsl index 04e5fd97d357..b6fda8bb1dfa 100644 --- a/Source/Shaders/PolylineShadowVolumeFS.glsl +++ b/Source/Shaders/PolylineShadowVolumeFS.glsl @@ -6,8 +6,7 @@ varying vec4 v_startPlaneEC; varying vec4 v_endPlaneEC; varying vec4 v_rightPlaneEC; varying vec3 v_forwardDirectionEC; -varying vec2 v_texcoordNormalization; -varying float v_halfWidth; +varying vec3 v_texcoordNormalization_and_halfWidth; #ifdef PER_INSTANCE_COLOR varying vec4 v_color; @@ -15,7 +14,7 @@ varying vec4 v_color; varying vec2 v_alignedPlaneDistances; #endif -float rayPlaneDistance(vec3 origin, vec3 direction, vec3 planeNormal, float planeDistance) { +float rayPlaneDistanceUnsafe(vec3 origin, vec3 direction, vec3 planeNormal, float planeDistance) { // We don't expect the ray to ever be parallel to the plane return (-planeDistance - dot(planeNormal, origin)) / dot(planeNormal, direction); } @@ -30,13 +29,13 @@ void main(void) vec4 eyeCoordinate = czm_windowToEyeCoordinates(gl_FragCoord.xy, logDepthOrDepth); eyeCoordinate /= eyeCoordinate.w; - float halfMaxWidth = v_halfWidth * czm_metersPerPixel(eyeCoordinate); + float halfMaxWidth = v_texcoordNormalization_and_halfWidth.z * czm_metersPerPixel(eyeCoordinate); // Check distance of the eye coordinate against the right-facing plane float width = czm_planeDistance(v_rightPlaneEC, eyeCoordinate.xyz); // Check distance of the eye coordinate against the forward-facing plane - float distanceFromStart = rayPlaneDistance(eyeCoordinate.xyz, -v_forwardDirectionEC, v_startPlaneEC.xyz, v_startPlaneEC.w); - float distanceFromEnd = rayPlaneDistance(eyeCoordinate.xyz, v_forwardDirectionEC, v_endPlaneEC.xyz, v_endPlaneEC.w); + float distanceFromStart = rayPlaneDistanceUnsafe(eyeCoordinate.xyz, -v_forwardDirectionEC, v_startPlaneEC.xyz, v_startPlaneEC.w); + float distanceFromEnd = rayPlaneDistanceUnsafe(eyeCoordinate.xyz, v_forwardDirectionEC, v_endPlaneEC.xyz, v_endPlaneEC.w); shouldDiscard = shouldDiscard || (abs(width) > halfMaxWidth || distanceFromStart < 0.0 || distanceFromEnd < 0.0); @@ -56,15 +55,15 @@ void main(void) gl_FragColor = v_color; #else // PER_INSTANCE_COLOR // Use distances for planes aligned with segment to prevent skew in dashing - distanceFromStart = rayPlaneDistance(eyeCoordinate.xyz, -v_forwardDirectionEC, v_forwardDirectionEC.xyz, v_alignedPlaneDistances.x); - distanceFromEnd = rayPlaneDistance(eyeCoordinate.xyz, v_forwardDirectionEC, -v_forwardDirectionEC.xyz, v_alignedPlaneDistances.y); + distanceFromStart = rayPlaneDistanceUnsafe(eyeCoordinate.xyz, -v_forwardDirectionEC, v_forwardDirectionEC.xyz, v_alignedPlaneDistances.x); + distanceFromEnd = rayPlaneDistanceUnsafe(eyeCoordinate.xyz, v_forwardDirectionEC, -v_forwardDirectionEC.xyz, v_alignedPlaneDistances.y); // Clamp - distance to aligned planes may be negative due to mitering distanceFromStart = max(0.0, distanceFromStart); distanceFromEnd = max(0.0, distanceFromEnd); float s = distanceFromStart / (distanceFromStart + distanceFromEnd); - s = (s * v_texcoordNormalization.y) + v_texcoordNormalization.x; + s = (s * v_texcoordNormalization_and_halfWidth.y) + v_texcoordNormalization_and_halfWidth.x; float t = (width + halfMaxWidth) / (2.0 * halfMaxWidth); czm_materialInput materialInput; diff --git a/Source/Shaders/PolylineShadowVolumeVS.glsl b/Source/Shaders/PolylineShadowVolumeVS.glsl index 3e20368a213b..d840f9964647 100644 --- a/Source/Shaders/PolylineShadowVolumeVS.glsl +++ b/Source/Shaders/PolylineShadowVolumeVS.glsl @@ -17,8 +17,7 @@ varying vec4 v_startPlaneEC; varying vec4 v_endPlaneEC; varying vec4 v_rightPlaneEC; varying vec3 v_forwardDirectionEC; -varying vec2 v_texcoordNormalization; -varying float v_halfWidth; +varying vec3 v_texcoordNormalization_and_halfWidth; // For materials varying float v_width; @@ -30,16 +29,6 @@ varying vec4 v_color; varying vec2 v_alignedPlaneDistances; #endif -float rayPlaneDistance(vec3 origin, vec3 direction, vec3 planeNormal, float planeDistance) { // TODO: move into its own function? - // We don't expect the ray to ever be parallel to the plane - return (-planeDistance - dot(planeNormal, origin)) / dot(planeNormal, direction); -} - -vec3 branchFreeTernary(bool comparison, vec3 a, vec3 b) { // TODO: make branchFreeTernary generic for floats and vec3s - float useA = float(comparison); - return a * useA + b * (1.0 - useA); -} - // TODO: morph? void main() @@ -65,7 +54,7 @@ void main() v_endPlaneEC.xyz = czm_normal * vec3(0.0, startEndNormals2D.zw); v_endPlaneEC.w = -dot(v_endPlaneEC.xyz, ecEnd); - v_texcoordNormalization = texcoordNormalization2D; + v_texcoordNormalization_and_halfWidth.xy = texcoordNormalization2D; #else // COLUMBUS_VIEW_2D vec3 ecStart = (czm_modelViewRelativeToEye * czm_translateRelativeToEye(startHi_and_forwardOffsetX.xyz, startLo_and_forwardOffsetY.xyz)).xyz; @@ -87,7 +76,7 @@ void main() v_rightPlaneEC.xyz = czm_normal * rightNormal_andTextureCoordinateNormalizationY.xyz; v_rightPlaneEC.w = -dot(v_rightPlaneEC.xyz, ecStart); - v_texcoordNormalization = vec2(endNormal_andTextureCoordinateNormalizationX.w, rightNormal_andTextureCoordinateNormalizationY.w); + v_texcoordNormalization_and_halfWidth.xy = vec2(endNormal_andTextureCoordinateNormalizationX.w, rightNormal_andTextureCoordinateNormalizationY.w); #endif // COLUMBUS_VIEW_2D @@ -108,7 +97,7 @@ void main() vec4 positionEC = czm_modelViewRelativeToEye * positionRelativeToEye; // w = 1.0, see czm_computePosition float absStartPlaneDistance = abs(czm_planeDistance(v_startPlaneEC, positionEC.xyz)); float absEndPlaneDistance = abs(czm_planeDistance(v_endPlaneEC, positionEC.xyz)); - vec3 planeDirection = branchFreeTernary(absStartPlaneDistance < absEndPlaneDistance, v_startPlaneEC.xyz, v_endPlaneEC.xyz); + vec3 planeDirection = czm_branchFreeTernary(absStartPlaneDistance < absEndPlaneDistance, v_startPlaneEC.xyz, v_endPlaneEC.xyz); vec3 upOrDown = normalize(cross(v_rightPlaneEC.xyz, planeDirection)); // Points "up" for start plane, "down" at end plane. vec3 normalEC = normalize(cross(planeDirection, upOrDown)); // In practice, the opposite seems to work too. @@ -120,13 +109,13 @@ void main() // and for very sharp turns we compute attributes to "break" the miter anyway. float width = czm_batchTable_width(batchId); v_width = width; - v_halfWidth = width * 0.5; + v_texcoordNormalization_and_halfWidth.z = width * 0.5; positionEC.xyz -= normalEC; // undo the unit length push positionEC.xyz += width * max(0.0, czm_metersPerPixel(positionEC)) * normalEC; // prevent artifacts when czm_metersPerPixel is negative (behind camera) gl_Position = czm_projection * positionEC; // Approximate relative screen space direction of the line. vec2 approxLineDirection = normalize(vec2(forwardDirectionEC.x, -forwardDirectionEC.y)); - approxLineDirection.y = czm_branchFreeTernaryFloat(approxLineDirection.x == 0.0 && approxLineDirection.y == 0.0, -1.0, approxLineDirection.y); + approxLineDirection.y = czm_branchFreeTernary(approxLineDirection.x == 0.0 && approxLineDirection.y == 0.0, -1.0, approxLineDirection.y); v_polylineAngle = czm_fastApproximateAtan(approxLineDirection.x, approxLineDirection.y); } diff --git a/Specs/Renderer/BuiltinFunctionsSpec.js b/Specs/Renderer/BuiltinFunctionsSpec.js index a96845a61456..ddcb2e50bb3f 100644 --- a/Specs/Renderer/BuiltinFunctionsSpec.js +++ b/Specs/Renderer/BuiltinFunctionsSpec.js @@ -457,10 +457,37 @@ defineSuite([ }).contextToRender(); }); - it('has czm_branchFreeTernaryFloat', function() { + it('has czm_branchFreeTernary', function() { var fs = 'void main() { ' + - ' gl_FragColor = vec4(czm_branchFreeTernaryFloat(true, 1.0, 0.0));' + + ' gl_FragColor = vec4(czm_branchFreeTernary(true, 1.0, 0.0));' + + '}'; + expect({ + context : context, + fragmentShader : fs + }).contextToRender(); + + fs = + 'void main() { ' + + ' gl_FragColor = vec4(czm_branchFreeTernary(true, vec2(1.0), vec2(0.0)), 1.0, 1.0);' + + '}'; + expect({ + context : context, + fragmentShader : fs + }).contextToRender(); + + fs = + 'void main() { ' + + ' gl_FragColor = vec4(czm_branchFreeTernary(true, vec3(1.0), vec3(0.0)), 1.0);' + + '}'; + expect({ + context : context, + fragmentShader : fs + }).contextToRender(); + + fs = + 'void main() { ' + + ' gl_FragColor = czm_branchFreeTernary(true, vec4(1.0), vec4(0.0));' + '}'; expect({ context : context, From 9abaa8c7b9b6074ab226874acfc6085059ae4a66 Mon Sep 17 00:00:00 2001 From: Kangning Li Date: Wed, 30 May 2018 14:05:13 -0400 Subject: [PATCH 12/39] simulate morph [ci skip] --- Source/Scene/GroundPolylinePrimitive.js | 110 ++++++++++++- .../Shaders/PolylineShadowVolumeMorphFS.glsl | 53 +++++++ .../Shaders/PolylineShadowVolumeMorphVS.glsl | 144 ++++++++++++++++++ Source/Shaders/PolylineShadowVolumeVS.glsl | 5 +- 4 files changed, 303 insertions(+), 9 deletions(-) create mode 100644 Source/Shaders/PolylineShadowVolumeMorphFS.glsl create mode 100644 Source/Shaders/PolylineShadowVolumeMorphVS.glsl diff --git a/Source/Scene/GroundPolylinePrimitive.js b/Source/Scene/GroundPolylinePrimitive.js index 1e9597b9c1d4..7251cd8e9a63 100644 --- a/Source/Scene/GroundPolylinePrimitive.js +++ b/Source/Scene/GroundPolylinePrimitive.js @@ -14,11 +14,14 @@ define([ '../Core/Matrix4', '../Shaders/PolylineShadowVolumeVS', '../Shaders/PolylineShadowVolumeFS', + '../Shaders/PolylineShadowVolumeMorphVS', + '../Shaders/PolylineShadowVolumeMorphFS', '../Renderer/DrawCommand', '../Renderer/Pass', '../Renderer/RenderState', '../Renderer/ShaderProgram', '../Renderer/ShaderSource', + './CullFace', './GroundPrimitive', './Material', './PolylineColorAppearance', @@ -41,11 +44,14 @@ define([ Matrix4, PolylineShadowVolumeVS, PolylineShadowVolumeFS, + PolylineShadowVolumeMorphVS, + PolylineShadowVolumeMorphFS, DrawCommand, Pass, RenderState, ShaderProgram, ShaderSource, + CullFace, GroundPrimitive, Material, PolylineColorAppearance, @@ -135,11 +141,23 @@ define([ this._spPick = undefined; this._sp2D = undefined; this._spPick2D = undefined; + this._spMorph = undefined; + this._spPickMorph = undefined; this._renderState = RenderState.fromCache({ cull : { enabled : true // prevent double-draw. Geometry is "inverted" (reversed winding order) so we're drawing backfaces. } }); + + this._renderStateMorph = RenderState.fromCache({ + cull : { + enabled : true, + face : CullFace.FRONT // Geometry is "inverted" (reversed winding order), uninvert for morph (drawing volume instead of terrain) + }, + depthTest : { + enabled : true + } + }); } GroundPolylinePrimitive._initialized = false; @@ -206,11 +224,15 @@ define([ var attributeLocations = primitive._attributeLocations; var vs = primitive._batchTable.getVertexShaderCallback()(PolylineShadowVolumeVS); - vs = Primitive._appendShowToShader(primitive, vs); vs = Primitive._appendDistanceDisplayConditionToShader(primitive, vs); vs = Primitive._modifyShaderPosition(groundPolylinePrimitive, vs, frameState.scene3DOnly); + var vsMorph = primitive._batchTable.getVertexShaderCallback()(PolylineShadowVolumeMorphVS); + vsMorph = Primitive._appendShowToShader(primitive, vsMorph); + vsMorph = Primitive._appendDistanceDisplayConditionToShader(primitive, vsMorph); + vsMorph = Primitive._modifyShaderPosition(groundPolylinePrimitive, vsMorph, frameState.scene3DOnly); + // Tesselation on these volumes tends to be low, // which causes problems when interpolating log depth from vertices. // So force computing and writing logarithmic depth in the fragment shader. @@ -253,10 +275,34 @@ define([ } groundPolylinePrimitive._sp2D = colorProgram2D; + // Derive Morph + var colorProgramMorph = context.shaderCache.getDerivedShaderProgram(groundPolylinePrimitive._sp, 'MorphColor'); + if (!defined(colorProgramMorph)) { + var vsColorMorph = new ShaderSource({ + defines : vsDefines, + sources : [vsMorph] + }); + var fsColorMorph = new ShaderSource({ + defines : fsDefines, + sources : [isPolylineColorAppearance ? '' : appearance.material.shaderSource, PolylineShadowVolumeMorphFS] + }); + colorProgramMorph = context.shaderCache.createDerivedShaderProgram(groundPolylinePrimitive._sp, 'MorphColor', { + context : context, + shaderProgram : groundPolylinePrimitive._spMorph, + vertexShaderSource : vsColorMorph, + fragmentShaderSource : fsColorMorph, + attributeLocations : attributeLocations + }); + } + groundPolylinePrimitive._spMorph = colorProgramMorph; + if (groundPolylinePrimitive.allowPicking) { var vsPick = ShaderSource.createPickVertexShaderSource(vs); vsPick = Primitive._updatePickColorAttribute(vsPick); + var vsPickMorphSource = ShaderSource.createPickVertexShaderSource(vsMorph); + vsPickMorphSource = Primitive._updatePickColorAttribute(vsPick); + var vsPick3D = new ShaderSource({ defines : vsDefines, sources : [vsPick] @@ -292,6 +338,28 @@ define([ }); } groundPolylinePrimitive._spPick2D = pickProgram2D; + + // Derive Morph + var pickProgramMorph = context.shaderCache.getDerivedShaderProgram(groundPolylinePrimitive._spPick, 'MorphPick'); + if (!defined(pickProgramMorph)) { + var vsPickMorph = new ShaderSource({ + defines : vsDefines, + sources : [vsPickMorphSource] + }); + var fsPickMorph = new ShaderSource({ + defines : ['PICK'], + sources : [PolylineShadowVolumeMorphFS], + pickColorQualifier : 'varying' + }); + pickProgramMorph = context.shaderCache.createDerivedShaderProgram(groundPolylinePrimitive._spPick, 'MorphPick', { + context : context, + shaderProgram : groundPolylinePrimitive._spPickMorph, + vertexShaderSource : vsPickMorph, + fragmentShaderSource : fsPickMorph, + attributeLocations : attributeLocations + }); + } + groundPolylinePrimitive._spPickMorph = pickProgramMorph; } } @@ -338,6 +406,18 @@ define([ derivedColorCommand.uniformMap = uniformMap; derivedColorCommand.pass = pass; + // derive for Morph + derivedColorCommand = command.derivedCommands.colorMorph; + if (!defined(derivedColorCommand)) { + derivedColorCommand = DrawCommand.shallowClone(command); + command.derivedCommands.colorMorph = derivedColorCommand; + } + derivedColorCommand.vertexArray = vertexArray; + derivedColorCommand.renderState = groundPolylinePrimitive._renderStateMorph; + derivedColorCommand.shaderProgram = groundPolylinePrimitive._spMorph; + derivedColorCommand.uniformMap = uniformMap; + derivedColorCommand.pass = pass; + // Pick command = pickCommands[i]; if (!defined(command)) { @@ -364,6 +444,18 @@ define([ derivedPickCommand.shaderProgram = groundPolylinePrimitive._spPick2D; derivedPickCommand.uniformMap = uniformMap; derivedPickCommand.pass = pass; + + // derive for Morph + derivedPickCommand = command.derivedCommands.pickMorph; + if (!defined(derivedPickCommand)) { + derivedPickCommand = DrawCommand.shallowClone(command); + command.derivedCommands.pickMorph = derivedPickCommand; + } + derivedPickCommand.vertexArray = vertexArray; + derivedPickCommand.renderState = groundPolylinePrimitive._renderStateMorph; + derivedPickCommand.shaderProgram = groundPolylinePrimitive._spPickMorph; + derivedPickCommand.uniformMap = uniformMap; + derivedPickCommand.pass = pass; } } @@ -396,9 +488,10 @@ define([ for (var j = 0; j < colorLength; ++j) { var colorCommand = colorCommands[j]; - // Use derived appearance command for 2D - if (frameState.mode !== SceneMode.SCENE3D && - colorCommand.shaderProgram === groundPolylinePrimitive._sp) { + // Use derived appearance command for morph and 2D + if (frameState.mode === SceneMode.MORPHING && colorCommand.shaderProgram !== groundPolylinePrimitive._spMorph) { + colorCommand = colorCommand.derivedCommands.colorMorph; + } else if (frameState.mode !== SceneMode.SCENE3D && colorCommand.shaderProgram !== groundPolylinePrimitive._sp2D) { colorCommand = colorCommand.derivedCommands.color2D; } colorCommand.modelMatrix = modelMatrix; @@ -414,9 +507,10 @@ define([ var pickLength = pickCommands.length; for (var k = 0; k < pickLength; ++k) { var pickCommand = pickCommands[k]; - // Used derived pick command for 2D - if (frameState.mode !== SceneMode.SCENE3D && - pickCommand.shaderProgram === groundPolylinePrimitive._spPick) { + // Use derived pick command for morph and 2D + if (frameState.mode === SceneMode.MORPHING && pickCommand.shaderProgram !== groundPolylinePrimitive._spMorph) { + pickCommand = pickCommand.derivedCommands.pickMorph; + } else if (frameState.mode !== SceneMode.SCENE3D && pickCommand.shaderProgram !== groundPolylinePrimitive._spPick2D) { pickCommand = pickCommand.derivedCommands.pick2D; } pickCommand.modelMatrix = modelMatrix; @@ -521,6 +615,8 @@ define([ // Derived programs, destroyed above if they existed. this._sp2D = undefined; this._spPick2D = undefined; + this._spMorph = undefined; + this._spPickMorph = undefined; return destroyObject(this); }; diff --git a/Source/Shaders/PolylineShadowVolumeMorphFS.glsl b/Source/Shaders/PolylineShadowVolumeMorphFS.glsl new file mode 100644 index 000000000000..f32e7b958c48 --- /dev/null +++ b/Source/Shaders/PolylineShadowVolumeMorphFS.glsl @@ -0,0 +1,53 @@ +#ifdef GL_EXT_frag_depth +#extension GL_EXT_frag_depth : enable +#endif + +varying vec3 v_forwardDirectionEC; +varying vec3 v_texcoordNormalization_and_halfWidth; + +#ifdef PER_INSTANCE_COLOR +varying vec4 v_color; +#else +varying vec2 v_alignedPlaneDistances; +varying float v_texcoordT; +#endif + +float rayPlaneDistanceUnsafe(vec3 origin, vec3 direction, vec3 planeNormal, float planeDistance) { + // We don't expect the ray to ever be parallel to the plane + return (-planeDistance - dot(planeNormal, origin)) / dot(planeNormal, direction); +} + +void main(void) +{ + vec4 eyeCoordinate = gl_FragCoord; + eyeCoordinate /= eyeCoordinate.w; + +#ifdef PICK + gl_FragColor.a = 1.0; +#else // PICK +#ifdef PER_INSTANCE_COLOR + gl_FragColor = v_color; +#else // PER_INSTANCE_COLOR + // Use distances for planes aligned with segment to prevent skew in dashing + float distanceFromStart = rayPlaneDistanceUnsafe(eyeCoordinate.xyz, -v_forwardDirectionEC, v_forwardDirectionEC.xyz, v_alignedPlaneDistances.x); + float distanceFromEnd = rayPlaneDistanceUnsafe(eyeCoordinate.xyz, v_forwardDirectionEC, -v_forwardDirectionEC.xyz, v_alignedPlaneDistances.y); + + // Clamp - distance to aligned planes may be negative due to mitering + distanceFromStart = max(0.0, distanceFromStart); + distanceFromEnd = max(0.0, distanceFromEnd); + + float s = distanceFromStart / (distanceFromStart + distanceFromEnd); + s = (s * v_texcoordNormalization_and_halfWidth.y) + v_texcoordNormalization_and_halfWidth.x; + + czm_materialInput materialInput; + + materialInput.s = s; + materialInput.st = vec2(s, v_texcoordT); + materialInput.str = vec3(s, v_texcoordT, 0.0); + + czm_material material = czm_getMaterial(materialInput); + gl_FragColor = vec4(material.diffuse + material.emission, material.alpha); +#endif // PER_INSTANCE_COLOR + +#endif // PICK +} diff --git a/Source/Shaders/PolylineShadowVolumeMorphVS.glsl b/Source/Shaders/PolylineShadowVolumeMorphVS.glsl new file mode 100644 index 000000000000..a0979e8628d7 --- /dev/null +++ b/Source/Shaders/PolylineShadowVolumeMorphVS.glsl @@ -0,0 +1,144 @@ +attribute vec3 position3DHigh; +attribute vec3 position3DLow; + +attribute vec4 startHi_and_forwardOffsetX; +attribute vec4 startLo_and_forwardOffsetY; +attribute vec4 startNormal_and_forwardOffsetZ; +attribute vec4 endNormal_andTextureCoordinateNormalizationX; +attribute vec4 rightNormal_andTextureCoordinateNormalizationY; +attribute vec4 startHiLo2D; +attribute vec4 offsetAndRight2D; +attribute vec4 startEndNormals2D; +attribute vec2 texcoordNormalization2D; + +attribute float batchId; + +varying vec3 v_forwardDirectionEC; +varying vec3 v_texcoordNormalization_and_halfWidth; + +// For materials +varying float v_width; +varying float v_polylineAngle; + +#ifdef PER_INSTANCE_COLOR +varying vec4 v_color; +#else +varying vec2 v_alignedPlaneDistances; +varying float v_texcoordT; +#endif + +// Morphing planes using SLERP or NLERP doesn't seem to work, so instead draw the material directly on the shadow volume. +// Morph views are from very far away and aren't meant to be used precisely, so this should be sufficient. +void main() +{ + // Start position + vec4 posRelativeToEye2D = czm_translateRelativeToEye(vec3(0.0, startHiLo2D.xy), vec3(0.0, startHiLo2D.zw)); + vec4 posRelativeToEye3D = czm_translateRelativeToEye(startHi_and_forwardOffsetX.xyz, startLo_and_forwardOffsetY.xyz); + vec4 posRelativeToEye = czm_columbusViewMorph(posRelativeToEye2D, posRelativeToEye3D, czm_morphTime); + vec3 ecPos2D = (czm_modelViewRelativeToEye * posRelativeToEye2D).xyz; + vec3 ecPos3D = (czm_modelViewRelativeToEye * posRelativeToEye3D).xyz; + vec3 ecStart = (czm_modelViewRelativeToEye * posRelativeToEye).xyz; + + // Start plane + vec4 startPlane2D; + vec4 startPlane3D; + startPlane2D.xyz = czm_normal * vec3(0.0, startEndNormals2D.xy); + startPlane3D.xyz = czm_normal * startNormal_and_forwardOffsetZ.xyz; + startPlane2D.w = -dot(startPlane2D.xyz, ecPos2D); + startPlane3D.w = -dot(startPlane3D.xyz, ecPos3D); + + // Right plane + vec4 rightPlane2D; + vec4 rightPlane3D; + rightPlane2D.xyz = czm_normal * vec3(0.0, offsetAndRight2D.zw); + rightPlane3D.xyz = czm_normal * rightNormal_andTextureCoordinateNormalizationY.xyz; + rightPlane2D.w = -dot(rightPlane2D.xyz, ecPos2D); + rightPlane3D.w = -dot(rightPlane3D.xyz, ecPos3D); + + // End position + posRelativeToEye2D = posRelativeToEye2D + vec4(0.0, offsetAndRight2D.xy, 0.0); + posRelativeToEye3D = posRelativeToEye3D + vec4(startHi_and_forwardOffsetX.w, startLo_and_forwardOffsetY.w, startNormal_and_forwardOffsetZ.w, 0.0); + posRelativeToEye = czm_columbusViewMorph(posRelativeToEye2D, posRelativeToEye3D, czm_morphTime); + ecPos2D = (czm_modelViewRelativeToEye * posRelativeToEye2D).xyz; + ecPos3D = (czm_modelViewRelativeToEye * posRelativeToEye3D).xyz; + vec3 ecEnd = (czm_modelViewRelativeToEye * posRelativeToEye).xyz; + + // End plane + vec4 endPlane2D; + vec4 endPlane3D; + endPlane2D.xyz = czm_normal * vec3(0.0, startEndNormals2D.zw); + endPlane3D.xyz = czm_normal * endNormal_andTextureCoordinateNormalizationX.xyz; + endPlane2D.w = -dot(endPlane2D.xyz, ecPos2D); + endPlane3D.w = -dot(endPlane3D.xyz, ecPos3D); + + // Forward direction + v_forwardDirectionEC = normalize(ecEnd - ecStart); + + v_texcoordNormalization_and_halfWidth.xy = mix(texcoordNormalization2D, vec2(endNormal_andTextureCoordinateNormalizationX.w, rightNormal_andTextureCoordinateNormalizationY.w), czm_morphTime); + +#ifdef PER_INSTANCE_COLOR + v_color = czm_batchTable_color(batchId); +#else // PER_INSTANCE_COLOR + // For computing texture coordinates + + v_alignedPlaneDistances.x = -dot(v_forwardDirectionEC, ecStart); + v_alignedPlaneDistances.y = -dot(-v_forwardDirectionEC, ecEnd); +#endif // PER_INSTANCE_COLOR + + float width = czm_batchTable_width(batchId); + float halfWidth = width * 0.5; + v_width = width; + v_texcoordNormalization_and_halfWidth.z = halfWidth; + + // Compute a normal along which to "push" the position out, extending the miter depending on view distance. + // Position has already been "pushed" by unit length along miter normal, and miter normals are encoded in the planes. + // Decode the normal to use at this specific vertex, push the position back, and then push to where it needs to be. + // Since this is morphing, compute both 3D and 2D positions and then blend. + + // 3D + // Check distance to the end plane and start plane, pick the plane that is closer + vec4 positionEC3D = czm_modelViewRelativeToEye * czm_translateRelativeToEye(position3DHigh, position3DLow); // w = 1.0, see czm_computePosition + float absStartPlaneDistance = abs(czm_planeDistance(startPlane3D, positionEC3D.xyz)); + float absEndPlaneDistance = abs(czm_planeDistance(endPlane3D, positionEC3D.xyz)); + vec3 planeDirection = czm_branchFreeTernary(absStartPlaneDistance < absEndPlaneDistance, startPlane3D.xyz, endPlane3D.xyz); + vec3 upOrDown = normalize(cross(rightPlane3D.xyz, planeDirection)); // Points "up" for start plane, "down" at end plane. + vec3 normalEC = normalize(cross(planeDirection, upOrDown)); // In practice, the opposite seems to work too. + + // Check distance to the right plane to determine if the miter normal points "left" or "right" + normalEC *= sign(czm_planeDistance(rightPlane3D, positionEC3D.xyz)); + + // A "perfect" implementation would push along normals according to the angle against forward. + // In practice, just pushing the normal out by halfWidth is sufficient for morph views. + positionEC3D.xyz -= normalEC; // undo the unit length push + positionEC3D.xyz += halfWidth * max(0.0, czm_metersPerPixel(positionEC3D)) * normalEC; // prevent artifacts when czm_metersPerPixel is negative (behind camera) + + // 2D + // Check distance to the end plane and start plane, pick the plane that is closer + vec4 positionEC2D = czm_modelViewRelativeToEye * czm_translateRelativeToEye(position2DHigh.zxy, position2DLow.zxy); // w = 1.0, see czm_computePosition + absStartPlaneDistance = abs(czm_planeDistance(startPlane2D, positionEC2D.xyz)); + absEndPlaneDistance = abs(czm_planeDistance(endPlane2D, positionEC2D.xyz)); + planeDirection = czm_branchFreeTernary(absStartPlaneDistance < absEndPlaneDistance, startPlane2D.xyz, endPlane2D.xyz); + upOrDown = normalize(cross(rightPlane2D.xyz, planeDirection)); // Points "up" for start plane, "down" at end plane. + normalEC = normalize(cross(planeDirection, upOrDown)); // In practice, the opposite seems to work too. + + // Check distance to the right plane to determine if the miter normal points "left" or "right." + // Also use this check to determine this vertex's horizontal texture coordinate. + float leftOrRight = sign(czm_planeDistance(rightPlane2D, positionEC2D.xyz)); + normalEC *= leftOrRight; +#ifndef PER_INSTANCE_COLOR + v_texcoordT = clamp(leftOrRight, 0.0, 1.0); +#endif + + // A "perfect" implementation would push along normals according to the angle against forward. + // In practice, just pushing the normal out by halfWidth is sufficient for morph views. + positionEC2D.xyz -= normalEC; // undo the unit length push + positionEC2D.xyz += halfWidth * max(0.0, czm_metersPerPixel(positionEC2D)) * normalEC; // prevent artifacts when czm_metersPerPixel is negative (behind camera) + + // Blend for actual position + gl_Position = czm_projection * mix(positionEC2D, positionEC3D, czm_morphTime); + + // Approximate relative screen space direction of the line. + vec2 approxLineDirection = normalize(vec2(v_forwardDirectionEC.x, -v_forwardDirectionEC.y)); + approxLineDirection.y = czm_branchFreeTernary(approxLineDirection.x == 0.0 && approxLineDirection.y == 0.0, -1.0, approxLineDirection.y); + v_polylineAngle = czm_fastApproximateAtan(approxLineDirection.x, approxLineDirection.y); +} diff --git a/Source/Shaders/PolylineShadowVolumeVS.glsl b/Source/Shaders/PolylineShadowVolumeVS.glsl index d840f9964647..55f456440e17 100644 --- a/Source/Shaders/PolylineShadowVolumeVS.glsl +++ b/Source/Shaders/PolylineShadowVolumeVS.glsl @@ -1,15 +1,18 @@ attribute vec3 position3DHigh; attribute vec3 position3DLow; +#ifndef COLUMBUS_VIEW_2D attribute vec4 startHi_and_forwardOffsetX; attribute vec4 startLo_and_forwardOffsetY; attribute vec4 startNormal_and_forwardOffsetZ; attribute vec4 endNormal_andTextureCoordinateNormalizationX; attribute vec4 rightNormal_andTextureCoordinateNormalizationY; +#else attribute vec4 startHiLo2D; attribute vec4 offsetAndRight2D; attribute vec4 startEndNormals2D; attribute vec2 texcoordNormalization2D; +#endif attribute float batchId; @@ -29,8 +32,6 @@ varying vec4 v_color; varying vec2 v_alignedPlaneDistances; #endif -// TODO: morph? - void main() { #ifdef COLUMBUS_VIEW_2D From e6e2c18ba3a0bf9cff9468c6006bbc9e587ec713 Mon Sep 17 00:00:00 2001 From: Kangning Li Date: Wed, 30 May 2018 16:32:08 -0400 Subject: [PATCH 13/39] additional doc and Primitive parameters, fixed bounding volume computation [ci skip] --- Source/Core/GroundPolylineGeometry.js | 324 +++++++++++---------- Source/Scene/GroundPolylinePrimitive.js | 356 +++++++++++++++++++----- Source/Scene/GroundPrimitive.js | 20 +- 3 files changed, 474 insertions(+), 226 deletions(-) diff --git a/Source/Core/GroundPolylineGeometry.js b/Source/Core/GroundPolylineGeometry.js index 232eb7d1b875..16e7d385eaf6 100644 --- a/Source/Core/GroundPolylineGeometry.js +++ b/Source/Core/GroundPolylineGeometry.js @@ -5,6 +5,7 @@ define([ './Cartographic', './Check', './ComponentDatatype', + './DeveloperError', './Math', './defaultValue', './defined', @@ -27,6 +28,7 @@ define([ Cartographic, Check, ComponentDatatype, + DeveloperError, CesiumMath, defaultValue, defined, @@ -57,61 +59,78 @@ define([ * @constructor * * @param {Object} [options] Options with the following properties: - * @param {Number} [options.width=1.0] The width in pixels. + * @param {Number} [options.width=1.0] The screen space width in pixels. * @param {Cartographic[]} [options.positions] An array of {@link Cartographic} defining the polyline's points. Heights will be ignored. * @param {Number} [options.granularity=9999.0] The distance interval used for interpolating options.points. Defaults to 9999.0 meters. Zero indicates no interpolation. * @param {Boolean} [options.loop=false] Whether during geometry creation a line segment will be added between the last and first line positions to make this Polyline a loop. - * @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] Ellipsoid for projecting cartographic coordinates to cartesian - * @param {Number} [options.maximumTerrainHeight] - * @param {Number} [options.minimumTerrainHeight] - * @param {MapProjection} [options.projection] + * @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] Ellipsoid for projecting cartographic coordinates to 3D. + * @param {MapProjection} [options.projection] Map Projection for projecting cartographic coordinates to 2D. * * @see GroundPolylinePrimitive */ function GroundPolylineGeometry(options) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); + /** + * The screen space width in pixels. + * @type {Number} + */ this.width = defaultValue(options.width, 1.0); // Doesn't get packed, not necessary for computing geometry. - this._positions = defaultValue(options.positions, []); + /** + * An array of {@link Cartographic} defining the polyline's points. Heights will be ignored. + * @type {Cartographic[]} + */ + this.positions = defaultValue(options.positions, []); /** * The distance interval used for interpolating options.points. Zero indicates no interpolation. - * Default of 9999.0 allows sub-centimeter accuracy with 32 bit floating point. + * Default of 9999.0 allows centimeter accuracy with 32 bit floating point. * @type {Boolean} */ this.granularity = defaultValue(options.granularity, 9999.0); /** * Whether during geometry creation a line segment will be added between the last and first line positions to make this Polyline a loop. + * If the geometry has two positions this parameter will be ignored. * @type {Boolean} */ this.loop = defaultValue(options.loop, false); + /** + * Ellipsoid for projecting cartographic coordinates to 3D. + * @type {Ellipsoid} + */ this.ellipsoid = defaultValue(options.ellipsoid, Ellipsoid.WGS84); - this.minimumTerrainHeight = defaultValue(options.minimumTerrainHeight, ApproximateTerrainHeights._defaultMinTerrainHeight); - this.maximumTerrainHeight = defaultValue(options.maximumTerrainHeight, ApproximateTerrainHeights._defaultMaxTerrainHeight); - + // MapProjections can't be packed, so store the index to a known MapProjection. var projectionIndex = 0; if (defined(options.projection)) { for (var i = 0; i < PROJECTION_COUNT; i++) { - if (options.projection instanceof PROJECTIONS[i]) { // TODO: is this ok? + if (options.projection instanceof PROJECTIONS[i]) { projectionIndex = i; break; } } } - this.projectionIndex = projectionIndex; + this._projectionIndex = projectionIndex; + this._workerName = 'createGroundPolylineGeometry'; + } + defineProperties(GroundPolylineGeometry.prototype, { /** * The number of elements used to pack the object into an array. + * @memberof GroundPolylineGeometry.prototype * @type {Number} + * @readonly + * @private */ - this.packedLength = 1.0 + this._positions.length * 2 + 1.0 + 1.0 + Ellipsoid.packedLength + 1.0 + 1.0 + 1.0; - - this._workerName = 'createGroundPolylineGeometry'; - } + packedLength: { + get: function() { + return 1.0 + this.positions.length * 2 + 1.0 + 1.0 + Ellipsoid.packedLength + 1.0; + } + } + }); var cart3Scratch1 = new Cartesian3(); var cart3Scratch2 = new Cartesian3(); @@ -173,28 +192,20 @@ define([ return Cartographic.toCartesian(heightlessCartographicScratch, ellipsoid, result); } - defineProperties(GroundPolylineGeometry.prototype, { - /** - * Gets the interpolated Cartographic positions describing the Ground Polyline - * @memberof GroundPolylineGeometry.prototype - * @type {Cartographic[]} - */ - positions: { - get: function() { - return this._positions; - }, - // TODO: doc, packedLength, etc. - set: function(value) { - this._positions = defaultValue(value, []); - } - } - }); - + /** + * Stores the provided instance into the provided array. + * + * @param {PolygonGeometry} 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 + */ GroundPolylineGeometry.pack = function(value, packArray, startingIndex) { startingIndex = defaultValue(startingIndex, 0); // Pack position length, then all positions - var positions = value._positions; + var positions = value.positions; var positionsLength = positions.length; var index = startingIndex; @@ -212,13 +223,18 @@ define([ Ellipsoid.pack(value.ellipsoid, packArray, index); index += Ellipsoid.packedLength; - packArray[index++] = value.minimumTerrainHeight; - packArray[index++] = value.maximumTerrainHeight; - packArray[index++] = value.projectionIndex; + packArray[index++] = value._projectionIndex; return packArray; }; + /** + * 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 {PolygonGeometry} [result] The object into which to store the result. + */ GroundPolylineGeometry.unpack = function(packArray, startingIndex, result) { var index = defaultValue(startingIndex, 0); var positions = []; @@ -237,8 +253,6 @@ define([ var ellipsoid = Ellipsoid.unpack(packArray, index); index += Ellipsoid.packedLength; - var minimumTerrainHeight = packArray[index++]; - var maximumTerrainHeight = packArray[index++]; var projectionIndex = packArray[index++]; if (!defined(result)) { @@ -247,43 +261,67 @@ define([ granularity : granularity, loop : loop, ellipsoid : ellipsoid, - maximumTerrainHeight : maximumTerrainHeight, - minimumTerrainHeight : minimumTerrainHeight, projection : new PROJECTIONS[projectionIndex](ellipsoid) }); } - result._positions = positions; + result.positions = positions; result.granularity = granularity; result.loop = loop; result.ellipsoid = ellipsoid; - result.maximumTerrainHeight = maximumTerrainHeight; - result.minimumTerrainHeight = minimumTerrainHeight; - result.projectionIndex = projectionIndex; + result._projectionIndex = projectionIndex; return result; }; - // If the end normal angle is too steep compared to the direction of the line segment, - // "break" the miter by rotating the normal 90 degrees around the "up" direction at the point - // For ultra precision we would want to project into a plane, but in practice this is sufficient. - var lineDirectionScratch = new Cartesian3(); + function direction(target, origin, result) { + Cartesian3.subtract(target, origin, result); + Cartesian3.normalize(result, result); + return result; + } + + // Inputs are cartesians + var toPreviousScratch = new Cartesian3(); + var toNextScratch = new Cartesian3(); + var forwardScratch = new Cartesian3(); + var coplanarNormalScratch = new Cartesian3(); + var coplanarPlaneScratch = new Plane(Cartesian3.UNIT_X, 0.0); var vertexUpScratch = new Cartesian3(); - var matrix3Scratch = new Matrix3(); - var quaternionScratch = new Quaternion(); - function breakMiter(endGeometryNormal, startBottom, endBottom, endTop) { - var lineDirection = direction(endBottom, startBottom, lineDirectionScratch); + var cosine90 = 0.0; + function computeVertexMiterNormal(previousBottom, vertexBottom, vertexTop, nextBottom, result) { + var up = direction(vertexTop, vertexBottom, vertexUpScratch); + var toPrevious = direction(previousBottom, vertexBottom, toPreviousScratch); + var toNext = direction(nextBottom, vertexBottom, toNextScratch); - var dot = Cartesian3.dot(lineDirection, endGeometryNormal); - if (dot > MITER_BREAK_SMALL || dot < MITER_BREAK_LARGE) { - var vertexUp = direction(endTop, endBottom, vertexUpScratch); - var angle = dot < MITER_BREAK_LARGE ? CesiumMath.PI_OVER_TWO : -CesiumMath.PI_OVER_TWO; - var quaternion = Quaternion.fromAxisAngle(vertexUp, angle, quaternionScratch); - var rotationMatrix = Matrix3.fromQuaternion(quaternion, matrix3Scratch); - Matrix3.multiplyByVector(rotationMatrix, endGeometryNormal, endGeometryNormal); - return true; + // Check if points are coplanar in a right-side-pointing plane that contains "up." + // This is roughly equivalent to the points being colinear in cartographic space. + var coplanarNormal = Cartesian3.cross(up, toPrevious, coplanarNormalScratch); + coplanarNormal = Cartesian3.normalize(coplanarNormal, coplanarNormal); + var coplanarPlane = Plane.fromPointNormal(vertexBottom, coplanarNormal, coplanarPlaneScratch); + var nextBottomDistance = Plane.getPointDistance(coplanarPlane, nextBottom); + if (CesiumMath.equalsEpsilon(nextBottomDistance, 0.0, CesiumMath.EPSILON7)) { + // If the points are coplanar, point the normal in the direction of the plane + Cartesian3.clone(coplanarNormal, result); + return result; } - return false; + + // Average directions to previous and to next + result = Cartesian3.add(toNext, toPrevious, result); + result = Cartesian3.multiplyByScalar(result, 0.5, result); + result = Cartesian3.normalize(result, result); + + // Rotate this direction to be orthogonal to up + var forward = Cartesian3.cross(up, result, forwardScratch); + Cartesian3.normalize(forward, forward); + Cartesian3.cross(forward, up, result); + Cartesian3.normalize(result, result); + + // Flip the normal if it isn't pointing roughly bound right (aka if forward is pointing more "backwards") + if (Cartesian3.dot(toNext, forward) < cosine90) { + result = Cartesian3.multiplyByScalar(result, -1.0, result); + } + + return result; } var previousBottomScratch = new Cartesian3(); @@ -291,24 +329,42 @@ define([ var vertexTopScratch = new Cartesian3(); var nextBottomScratch = new Cartesian3(); var vertexNormalScratch = new Cartesian3(); + /** + * Computes shadow volumes for the ground polyline, consisting of its vertices, indices, and a bounding sphere. + * Vertices are "fat," packing all the data needed in each volume to describe a line on terrain. + * + * @param {GroundPolylineGeometry} groundPolylineGeometry + * @private + */ GroundPolylineGeometry.createGeometry = function(groundPolylineGeometry) { var cartographics = groundPolylineGeometry.positions; var loop = groundPolylineGeometry.loop; var ellipsoid = groundPolylineGeometry.ellipsoid; var granularity = groundPolylineGeometry.granularity; - var projection = new PROJECTIONS[groundPolylineGeometry.projectionIndex](ellipsoid); + var projection = new PROJECTIONS[groundPolylineGeometry._projectionIndex](ellipsoid); - var maxHeight = groundPolylineGeometry.maximumTerrainHeight; - var minHeight = groundPolylineGeometry.minimumTerrainHeight; + var minHeight = ApproximateTerrainHeights._defaultMinTerrainHeight; + var maxHeight = ApproximateTerrainHeights._defaultMaxTerrainHeight; var cartographicsLength = cartographics.length; - // TODO: throw errors/negate loop if not enough points + //>>includeStart('debug', pragmas.debug); + if (cartographicsLength < 2) { + throw new DeveloperError('GroundPolylineGeometry must contain two or more cartographics'); + } + //>>includeEnd('debug'); + if (cartographicsLength === 2) { + loop = false; + } var index; var i; - /**** Build heap-side arrays of positions, cartographics, and normals from which to compute vertices ****/ + /**** Build heap-side arrays of positions, interpolated cartographics, and normals from which to compute vertices ****/ + // We build a "wall" and then decompose it into separately connected component "volumes" because we need a lot + // of information about the wall. Also, this simplifies interpolation. + // Convention: "next" and "end" are locally forward to each segment of the wall, + // and we are computing normals pointing towards the local right side of the vertices in each segment. var cartographicsArray = []; var normalsArray = []; var bottomPositionsArray = []; @@ -365,7 +421,7 @@ define([ interpolateSegment(cartographics[i], cartographics[i + 1], minHeight, maxHeight, granularity, ellipsoid, normalsArray, bottomPositionsArray, topPositionsArray, cartographicsArray); } - // Last point - either loop or attach a "perpendicular" normal + // Last point - either loop or attach a normal "perpendicular" to the wall. var endCartographic = cartographics[cartographicsLength - 1]; var preEndCartographic = cartographics[cartographicsLength - 2]; @@ -402,9 +458,30 @@ define([ cartographicsArray.push(startCartographic.longitude); } - return generateGeometryAttributes(groundPolylineGeometry, projection, bottomPositionsArray, topPositionsArray, normalsArray, cartographicsArray); + return generateGeometryAttributes(loop, projection, bottomPositionsArray, topPositionsArray, normalsArray, cartographicsArray); }; + // If the end normal angle is too steep compared to the direction of the line segment, + // "break" the miter by rotating the normal 90 degrees around the "up" direction at the point + // For ultra precision we would want to project into a plane, but in practice this is sufficient. + var lineDirectionScratch = new Cartesian3(); + var matrix3Scratch = new Matrix3(); + var quaternionScratch = new Quaternion(); + function breakMiter(endGeometryNormal, startBottom, endBottom, endTop) { + var lineDirection = direction(endBottom, startBottom, lineDirectionScratch); + + var dot = Cartesian3.dot(lineDirection, endGeometryNormal); + if (dot > MITER_BREAK_SMALL || dot < MITER_BREAK_LARGE) { + var vertexUp = direction(endTop, endBottom, vertexUpScratch); + var angle = dot < MITER_BREAK_LARGE ? CesiumMath.PI_OVER_TWO : -CesiumMath.PI_OVER_TWO; + var quaternion = Quaternion.fromAxisAngle(vertexUp, angle, quaternionScratch); + var rotationMatrix = Matrix3.fromQuaternion(quaternion, matrix3Scratch); + Matrix3.multiplyByVector(rotationMatrix, endGeometryNormal, endGeometryNormal); + return true; + } + return false; + } + var positionCartographicScratch = new Cartographic(); var normalEndpointScratch = new Cartesian3(); function projectNormal(projection, position, normal, projectedPosition, result) { @@ -422,16 +499,16 @@ define([ var adjustHeightNormalScratch = new Cartesian3(); var adjustHeightOffsetScratch = new Cartesian3(); - function adjustHeights(groundPolylineGeometry, bottom, top, minHeight, maxHeight, adjustHeightBottom, adjustHeightTop) { - // bottom and top should be at groundPolylineGeometry.minimumTerrainHeight and groundPolylineGeometry.maximumTerrainHeight, respectively + function adjustHeights(bottom, top, minHeight, maxHeight, adjustHeightBottom, adjustHeightTop) { + // bottom and top should be at ApproximateTerrainHeights._defaultMinTerrainHeight and ApproximateTerrainHeights._defaultMaxTerrainHeight, respectively var adjustHeightNormal = Cartesian3.subtract(top, bottom, adjustHeightNormalScratch); Cartesian3.normalize(adjustHeightNormal, adjustHeightNormal); - var distanceForBottom = minHeight - groundPolylineGeometry.minimumTerrainHeight; + var distanceForBottom = minHeight - ApproximateTerrainHeights._defaultMinTerrainHeight; var adjustHeightOffset = Cartesian3.multiplyByScalar(adjustHeightNormal, distanceForBottom, adjustHeightOffsetScratch); Cartesian3.add(bottom, adjustHeightOffset, adjustHeightBottom); - var distanceForTop = maxHeight - groundPolylineGeometry.maximumTerrainHeight; + var distanceForTop = maxHeight - ApproximateTerrainHeights._defaultMaxTerrainHeight; adjustHeightOffset = Cartesian3.multiplyByScalar(adjustHeightNormal, distanceForTop, adjustHeightOffsetScratch); Cartesian3.add(top, adjustHeightOffset, adjustHeightTop); } @@ -471,6 +548,8 @@ define([ var forwardOffset2DScratch = new Cartesian3(); var right2DScratch = new Cartesian3(); + var scratchBoundingSpheres = [new BoundingSphere(), new BoundingSphere()]; + // Winding order is reversed so each segment's volume is inside-out var REFERENCE_INDICES = [ 0, 2, 1, 0, 3, 2, // right @@ -481,11 +560,15 @@ define([ 3, 6, 2, 3, 7, 6 // top ]; var REFERENCE_INDICES_LENGTH = REFERENCE_INDICES.length; - function generateGeometryAttributes(groundPolylineGeometry, projection, bottomPositionsArray, topPositionsArray, normalsArray, cartographicsArray) { + + // Decompose the "wall" into a series of shadow volumes. + // Each shadow volume's vertices encode a description of the line it contains, + // including mitering planes at the end points, a plane along the line itself, + // and attributes for computing length-wise texture coordinates. + function generateGeometryAttributes(loop, projection, bottomPositionsArray, topPositionsArray, normalsArray, cartographicsArray) { var i; var index; - var loop = groundPolylineGeometry.loop; - var ellipsoid = groundPolylineGeometry.ellipsoid; + var ellipsoid = projection.ellipsoid; // Each segment will have 8 vertices var segmentCount = (bottomPositionsArray.length / 3) - 1; @@ -574,7 +657,6 @@ define([ var lengthSoFar3D = 0.0; var lengthSoFar2D = 0.0; - var boundingSphere = BoundingSphere.fromPoints([endBottom, endTop]); for (i = 0; i < segmentCount; i++) { var startBottom = Cartesian3.clone(endBottom, segmentStartBottomScratch); var startTop = Cartesian3.clone(endTop, segmentStartTopScratch); @@ -595,9 +677,6 @@ define([ endTop = Cartesian3.unpack(topPositionsArray, index, segmentEndTopScratch); endGeometryNormal = Cartesian3.unpack(normalsArray, index, segmentEndNormalScratch); - BoundingSphere.expand(boundingSphere, endBottom, boundingSphere); - BoundingSphere.expand(boundingSphere, endTop, boundingSphere); - miterBroken = breakMiter(endGeometryNormal, startBottom, endBottom, endTop); endCartographic.latitude = cartographicsArray[cartographicsIndex]; @@ -606,7 +685,9 @@ define([ endGeometryNormal2D = projectNormal(projection, endBottom, endGeometryNormal, end2D, segmentEndNormal2DScratch); /**************************************** - * Geometry descriptors: + * Geometry descriptors of a "line on terrain," + * as opposed to the "shadow volume used to draw + * the line on terrain": * - position of start + offset to end * - start, end, and right-facing planes * - encoded texture coordinate offsets @@ -718,8 +799,8 @@ define([ var minHeight = minMaxHeights.minimumTerrainHeight; var maxHeight = minMaxHeights.maximumTerrainHeight; - adjustHeights(groundPolylineGeometry, startBottom, startTop, minHeight, maxHeight, adjustHeightStartBottom, adjustHeightStartTop); - adjustHeights(groundPolylineGeometry, endBottom, endTop, minHeight, maxHeight, adjustHeightEndBottom, adjustHeightEndTop); + adjustHeights(startBottom, startTop, minHeight, maxHeight, adjustHeightStartBottom, adjustHeightStartTop); + adjustHeights(endBottom, endTop, minHeight, maxHeight, adjustHeightEndBottom, adjustHeightEndTop); // Push out by 1.0 in the "right" direction var pushedStartBottom = Cartesian3.add(adjustHeightStartBottom, startGeometryNormal, adjustHeightStartBottom); @@ -769,6 +850,12 @@ define([ index += REFERENCE_INDICES_LENGTH; } + // Generate bounding sphere + var boundingSpheres = scratchBoundingSpheres; + BoundingSphere.fromVertices(bottomPositionsArray, Cartesian3.ZERO, 3, boundingSpheres[0]); + BoundingSphere.fromVertices(topPositionsArray, Cartesian3.ZERO, 3, boundingSpheres[1]); + var boundingSphere = BoundingSphere.fromBoundingSpheres(boundingSpheres); + return new Geometry({ attributes : { position : new GeometryAttribute({ @@ -777,15 +864,15 @@ define([ normalize : false, values : positionsArray }), - startHi_and_forwardOffsetX : getVec4Attribute(startHi_and_forwardOffsetX), - startLo_and_forwardOffsetY : getVec4Attribute(startLo_and_forwardOffsetY), - startNormal_and_forwardOffsetZ : getVec4Attribute(startNormal_and_forwardOffsetZ), - endNormal_andTextureCoordinateNormalizationX : getVec4Attribute(endNormal_andTextureCoordinateNormalizationX), - rightNormal_andTextureCoordinateNormalizationY : getVec4Attribute(rightNormal_andTextureCoordinateNormalizationY), - - startHiLo2D : getVec4Attribute(startHiLo2D), - offsetAndRight2D : getVec4Attribute(offsetAndRight2D), - startEndNormals2D : getVec4Attribute(startEndNormals2D), + startHi_and_forwardOffsetX : getVec4GeometryAttribute(startHi_and_forwardOffsetX), + startLo_and_forwardOffsetY : getVec4GeometryAttribute(startLo_and_forwardOffsetY), + startNormal_and_forwardOffsetZ : getVec4GeometryAttribute(startNormal_and_forwardOffsetZ), + endNormal_andTextureCoordinateNormalizationX : getVec4GeometryAttribute(endNormal_andTextureCoordinateNormalizationX), + rightNormal_andTextureCoordinateNormalizationY : getVec4GeometryAttribute(rightNormal_andTextureCoordinateNormalizationY), + + startHiLo2D : getVec4GeometryAttribute(startHiLo2D), + offsetAndRight2D : getVec4GeometryAttribute(offsetAndRight2D), + startEndNormals2D : getVec4GeometryAttribute(startEndNormals2D), texcoordNormalization2D : new GeometryAttribute({ componentDatatype : ComponentDatatype.FLOAT, componentsPerAttribute : 2, @@ -798,7 +885,7 @@ define([ }); } - function getVec4Attribute(typedArray) { + function getVec4GeometryAttribute(typedArray) { return new GeometryAttribute({ componentDatatype : ComponentDatatype.FLOAT, componentsPerAttribute : 4, @@ -807,56 +894,5 @@ define([ }); } - function direction(target, origin, result) { - Cartesian3.subtract(target, origin, result); - Cartesian3.normalize(result, result); - return result; - } - - // Inputs are cartesians - var toPreviousScratch = new Cartesian3(); - var toNextScratch = new Cartesian3(); - var forwardScratch = new Cartesian3(); - var coplanarNormalScratch = new Cartesian3(); - var coplanarPlaneScratch = new Plane(Cartesian3.UNIT_X, 0.0); - var cosine90 = 0.0; - function computeVertexMiterNormal(previousBottom, vertexBottom, vertexTop, nextBottom, result) { - // Convention: "next" is locally forward and we are computing a normal pointing towards the local right side of the vertices. - - var up = direction(vertexTop, vertexBottom, vertexUpScratch); - var toPrevious = direction(previousBottom, vertexBottom, toPreviousScratch); - var toNext = direction(nextBottom, vertexBottom, toNextScratch); - - // Check if points are coplanar in a right-side-pointing plane that contains "up." - // This is roughly equivalent to the points being colinear in cartographic space. - var coplanarNormal = Cartesian3.cross(up, toPrevious, coplanarNormalScratch); - coplanarNormal = Cartesian3.normalize(coplanarNormal, coplanarNormal); - var coplanarPlane = Plane.fromPointNormal(vertexBottom, coplanarNormal, coplanarPlaneScratch); - var nextBottomDistance = Plane.getPointDistance(coplanarPlane, nextBottom); - if (CesiumMath.equalsEpsilon(nextBottomDistance, 0.0, CesiumMath.EPSILON7)) { - // If the points are coplanar, point the normal in the direction of the plane - Cartesian3.clone(coplanarNormal, result); - return result; - } - - // Average directions to previous and to next - result = Cartesian3.add(toNext, toPrevious, result); - result = Cartesian3.multiplyByScalar(result, 0.5, result); - result = Cartesian3.normalize(result, result); - - // Rotate this direction to be orthogonal to up - var forward = Cartesian3.cross(up, result, forwardScratch); - Cartesian3.normalize(forward, forward); - Cartesian3.cross(forward, up, result); - Cartesian3.normalize(result, result); - - // Flip the normal if it isn't pointing roughly bound right (aka if forward is pointing more "backwards") - if (Cartesian3.dot(toNext, forward) < cosine90) { - result = Cartesian3.multiplyByScalar(result, -1.0, result); - } - - return result; - } - return GroundPolylineGeometry; }); diff --git a/Source/Scene/GroundPolylinePrimitive.js b/Source/Scene/GroundPolylinePrimitive.js index 7251cd8e9a63..b20a3078e067 100644 --- a/Source/Scene/GroundPolylinePrimitive.js +++ b/Source/Scene/GroundPolylinePrimitive.js @@ -21,8 +21,8 @@ define([ '../Renderer/RenderState', '../Renderer/ShaderProgram', '../Renderer/ShaderSource', + '../ThirdParty/when', './CullFace', - './GroundPrimitive', './Material', './PolylineColorAppearance', './PolylineMaterialAppearance', @@ -51,8 +51,8 @@ define([ RenderState, ShaderProgram, ShaderSource, + when, CullFace, - GroundPrimitive, Material, PolylineColorAppearance, PolylineMaterialAppearance, @@ -67,75 +67,100 @@ define([ * Only to be used with GeometryInstances containing GroundPolylineGeometries * * @param {Object} [options] Object with the following properties: - * @param {GeometryInstance[]|GeometryInstance} [options.polylineGeometryInstances] GeometryInstances containing GroundPolylineGeometry + * @param {Array|GeometryInstance} [options.polylineGeometryInstances] GeometryInstances containing GroundPolylineGeometry * @param {Appearance} [options.appearance] The Appearance used to render the polyline. Defaults to a white color {@link Material} on a {@link PolylineMaterialAppearance}. * @param {Boolean} [options.show=true] Determines if this primitive will be shown. - * @param {Boolean} [options.releaseGeometryInstances=true] When true, the primitive does not keep a reference to generated geometry or input cartographics to save memory. + * @param {Boolean} [options.vertexCacheOptimize=false] When true, geometry vertices are optimized for the pre and post-vertex-shader caches. + * @param {Boolean} [options.interleave=false] When true, geometry vertex attributes are interleaved, which can slightly improve rendering performance but increases load time. + * @param {Boolean} [options.compressVertices=true] When true, the geometry vertices are compressed, which will save memory. + * @param {Boolean} [options.releaseGeometryInstances=true] When true, the primitive does not keep a reference to the input geometryInstances to save memory. * @param {Boolean} [options.allowPicking=true] When true, each geometry instance will only be pickable with {@link Scene#pick}. When false, GPU memory is saved. - * @param {Boolean} [options.asynchronous=true] Determines if the primitive will be created asynchronously or block until ready. If false GroundPrimitive.initializeTerrainHeights() must be called first. + * @param {Boolean} [options.asynchronous=true] Determines if the primitive will be created asynchronously or block until ready. If false initializeTerrainHeights() must be called first. + * @param {ClassificationType} [options.classificationType=ClassificationType.TERRAIN] Determines whether terrain, 3D Tiles or both will be classified. * @param {Boolean} [options.debugShowBoundingVolume=false] For debugging only. Determines if this primitive's commands' bounding spheres are shown. - * @param {Boolean} [options.debugShowShadowVolume=false] For debugging only. Determines if the shadow volume for each geometry in the primitive is drawn. + * @param {Boolean} [options.debugShowShadowVolume=false] For debugging only. Determines if the shadow volume for each geometry in the primitive is drawn. Must be true on + * creation for the volumes to be created before the geometry is released or options.releaseGeometryInstance must be false. * */ function GroundPolylinePrimitive(options) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); + /** + * The geometry instances rendered with this primitive. This may + * be undefined if options.releaseGeometryInstances + * is true when the primitive is constructed. + *

+ * Changing this property after the primitive is rendered has no effect. + *

+ * + * @readonly + * @type {Array|GeometryInstance} + * + * @default undefined + */ this.polylineGeometryInstances = options.polylineGeometryInstances; var appearance = options.appearance; if (!defined(appearance)) { appearance = new PolylineMaterialAppearance(); } - + /** + * The {@link Appearance} used to shade this primitive. Each geometry + * instance is shaded with the same appearance. Some appearances, like + * {@link PolylineColorAppearance} allow giving each instance unique + * properties. + * + * @type Appearance + * + * @default undefined + */ this.appearance = appearance; - this.show = defaultValue(options.show, true); - - this.releaseGeometryInstances = defaultValue(options.releaseGeometryInstances, true); - - this.asynchronous = defaultValue(options.asynchronous, true); - - this.allowPicking = defaultValue(options.allowPicking, true); - /** - * This property is for debugging only; it is not for production use nor is it optimized. - *

- * Draws the bounding sphere for each draw command in the primitive. - *

+ * Determines if the primitive will be shown. This affects all geometry + * instances in the primitive. * * @type {Boolean} * - * @default false + * @default true */ - this.debugShowBoundingVolume = defaultValue(options.debugShowBoundingVolume, false); + this.show = defaultValue(options.show, true); /** * This property is for debugging only; it is not for production use nor is it optimized. *

- * Draws the shadow volume for each geometry in the primitive. + * Draws the bounding sphere for each draw command in the primitive. *

- * TODO: make this read only, set-on-construct + * * @type {Boolean} * * @default false */ - this.debugShowShadowVolume = defaultValue(options.debugShowShadowVolume, false); + this.debugShowBoundingVolume = defaultValue(options.debugShowBoundingVolume, false); + + this._debugShowShadowVolume = defaultValue(options.debugShowShadowVolume, false); this._primitiveOptions = { - geometryInstances : undefined, // TODO: addtl params + geometryInstances : undefined, appearance : undefined, - releaseGeometryInstances : this.releaseGeometryInstances, - asynchronous : this.asynchronous, + vertexCacheOptimize : defaultValue(options.vertexCacheOptimize, false), + interleave : defaultValue(options.interleave, false), + releaseGeometryInstances : defaultValue(options.releaseGeometryInstances, true), + allowPicking : defaultValue(options.allowPicking, true), + asynchronous : defaultValue(options.asynchronous, true), + compressVertices : defaultValue(options.compressVertices, true), _createShaderProgramFunction : undefined, _createCommandsFunction : undefined, _updateAndQueueCommandsFunction : undefined }; - this._primitive = undefined; + // Used when inserting in an OrderedPrimitiveCollection + this._zIndex = undefined; + this._ready = false; + this._readyPromise = when.defer(); - this._maxTerrainHeight = GroundPrimitive._defaultMaxTerrainHeight; - this._minTerrainHeight = GroundPrimitive._defaultMinTerrainHeight; + this._primitive = undefined; this._sp = undefined; this._spPick = undefined; @@ -152,7 +177,7 @@ define([ this._renderStateMorph = RenderState.fromCache({ cull : { enabled : true, - face : CullFace.FRONT // Geometry is "inverted" (reversed winding order), uninvert for morph (drawing volume instead of terrain) + face : CullFace.FRONT // Geometry is "inverted," so cull front when materials on volume instead of on terrain (morph) }, depthTest : { enabled : true @@ -160,9 +185,161 @@ define([ }); } + defineProperties(GroundPolylinePrimitive.prototype, { + /** + * When true, geometry vertices are optimized for the pre and post-vertex-shader caches. + * + * @memberof GroundPolylinePrimitive.prototype + * + * @type {Boolean} + * @readonly + * + * @default true + */ + vertexCacheOptimize : { + get : function() { + return this._primitiveOptions.vertexCacheOptimize; + } + }, + + /** + * Determines if geometry vertex attributes are interleaved, which can slightly improve rendering performance. + * + * @memberof GroundPolylinePrimitive.prototype + * + * @type {Boolean} + * @readonly + * + * @default false + */ + interleave : { + get : function() { + return this._primitiveOptions.interleave; + } + }, + + /** + * When true, the primitive does not keep a reference to the input geometryInstances to save memory. + * + * @memberof GroundPolylinePrimitive.prototype + * + * @type {Boolean} + * @readonly + * + * @default true + */ + releaseGeometryInstances : { + get : function() { + return this._primitiveOptions.releaseGeometryInstances; + } + }, + + /** + * When true, each geometry instance will only be pickable with {@link Scene#pick}. When false, GPU memory is saved. + * + * @memberof GroundPolylinePrimitive.prototype + * + * @type {Boolean} + * @readonly + * + * @default true + */ + allowPicking : { + get : function() { + return this._primitiveOptions.allowPicking; + } + }, + + /** + * Determines if the geometry instances will be created and batched on a web worker. + * + * @memberof GroundPolylinePrimitive.prototype + * + * @type {Boolean} + * @readonly + * + * @default true + */ + asynchronous : { + get : function() { + return this._primitiveOptions.asynchronous; + } + }, + + /** + * When true, geometry vertices are compressed, which will save memory. + * + * @memberof GroundPolylinePrimitive.prototype + * + * @type {Boolean} + * @readonly + * + * @default true + */ + compressVertices : { + get : function() { + return this._primitiveOptions.compressVertices; + } + }, + + /** + * Determines if the primitive is complete and ready to render. If this property is + * true, the primitive will be rendered the next time that {@link GroundPolylinePrimitive#update} + * is called. + * + * @memberof GroundPolylinePrimitive.prototype + * + * @type {Boolean} + * @readonly + */ + ready : { + get : function() { + return this._ready; + } + }, + + /** + * Gets a promise that resolves when the primitive is ready to render. + * @memberof GroundPolylinePrimitive.prototype + * @type {Promise.} + * @readonly + */ + readyPromise : { + get : function() { + return this._readyPromise.promise; + } + }, + + /** + * This property is for debugging only; it is not for production use nor is it optimized. + *

+ * If true, draws the shadow volume for each geometry in the primitive. + *

+ * + * @memberof GroundPolylinePrimitive.prototype + * + * @type {Boolean} + * @readonly + * + * @default false + */ + debugShowShadowVolume : { + get : function() { + return this._debugShowShadowVolume; + } + } + }); + GroundPolylinePrimitive._initialized = false; GroundPolylinePrimitive._initPromise = undefined; + /** + * Initializes the minimum and maximum terrain heights. This only needs to be called if you are creating the + * GroundPolylinePrimitive synchronously. + * + * @returns {Promise} A promise that will resolve once the terrain heights have been loaded. + * + */ GroundPolylinePrimitive.initializeTerrainHeights = function() { var initPromise = GroundPolylinePrimitive._initPromise; if (defined(initPromise)) { @@ -177,6 +354,7 @@ define([ return GroundPolylinePrimitive._initPromise; }; + // For use with web workers. GroundPolylinePrimitive._initializeTerrainHeightsWorker = function() { var initPromise = GroundPolylinePrimitive._initPromise; if (defined(initPromise)) { @@ -191,31 +369,6 @@ define([ return GroundPolylinePrimitive._initPromise; }; - // TODO: remove - function validateShaderMatching(shaderProgram, attributeLocations) { - // For a VAO and shader program to be compatible, the VAO must have - // all active attribute in the shader program. The VAO may have - // extra attributes with the only concern being a potential - // performance hit due to extra memory bandwidth and cache pollution. - // The shader source could have extra attributes that are not used, - // but there is no guarantee they will be optimized out. - // - // Here, we validate that the VAO has all attributes required - // to match the shader program. - var shaderAttributes = shaderProgram.vertexAttributes; - - //>>includeStart('debug', pragmas.debug); - for (var name in shaderAttributes) { - if (shaderAttributes.hasOwnProperty(name)) { - if (!defined(attributeLocations[name])) { - throw new DeveloperError('Appearance/Geometry mismatch. The appearance requires vertex shader attribute input \'' + name + - '\', which was not computed as part of the Geometry. Use the appearance\'s vertexFormat property when constructing the geometry.'); - } - } - } - //>>includeEnd('debug'); - } - function createShaderProgram(groundPolylinePrimitive, frameState, appearance) { var context = frameState.context; var primitive = groundPolylinePrimitive._primitive; @@ -235,7 +388,7 @@ define([ // Tesselation on these volumes tends to be low, // which causes problems when interpolating log depth from vertices. - // So force computing and writing logarithmic depth in the fragment shader. + // So force computing and writing log depth in the fragment shader. // Re-enable at far distances to avoid z-fighting. var colorDefine = isPolylineColorAppearance ? 'PER_INSTANCE_COLOR' : ''; var vsDefines = ['ENABLE_GL_POSITION_LOG_DEPTH_AT_HEIGHT', colorDefine]; @@ -256,7 +409,6 @@ define([ fragmentShaderSource : fsColor3D, attributeLocations : attributeLocations }); - validateShaderMatching(groundPolylinePrimitive._sp, attributeLocations); // Derive 2D/CV var colorProgram2D = context.shaderCache.getDerivedShaderProgram(groundPolylinePrimitive._sp, '2dColor'); @@ -320,7 +472,6 @@ define([ fragmentShaderSource : fsPick3D, attributeLocations : attributeLocations }); - validateShaderMatching(groundPolylinePrimitive._spPick, attributeLocations); // Derive 2D/CV var pickProgram2D = context.shaderCache.getDerivedShaderProgram(groundPolylinePrimitive._spPick, '2dPick'); @@ -522,6 +673,18 @@ define([ } } + /** + * Called when {@link Viewer} or {@link CesiumWidget} render the scene to + * get the draw commands needed to render this primitive. + *

+ * Do not call this function directly. This is documented just to + * list the exceptions that may be propagated when the scene is rendered: + *

+ * + * @exception {DeveloperError} For synchronous GroundPolylinePrimitives, you must call GroundPolylinePrimitives.initializeTerrainHeights() and wait for the returned promise to resolve. + * @exception {DeveloperError} All instance geometries must have the same primitiveType. + * @exception {DeveloperError} Appearance and material have a uniform with the same name. + */ GroundPolylinePrimitive.prototype.update = function(frameState) { if (!defined(this._primitive) && !defined(this.polylineGeometryInstances)) { return; @@ -603,10 +766,70 @@ define([ this._primitive.update(frameState); }; + /** + * Returns the modifiable per-instance attributes for a {@link GeometryInstance}. + * + * @param {*} id The id of the {@link GeometryInstance}. + * @returns {Object} The typed array in the attribute's format or undefined if the is no instance with id. + * + * @exception {DeveloperError} must call update before calling getGeometryInstanceAttributes. + * + * @example + * var attributes = primitive.getGeometryInstanceAttributes('an id'); + * attributes.color = Cesium.ColorGeometryInstanceAttribute.toValue(Cesium.Color.AQUA); + * attributes.show = Cesium.ShowGeometryInstanceAttribute.toValue(true); + */ + GroundPolylinePrimitive.prototype.getGeometryInstanceAttributes = function(id) { + //>>includeStart('debug', pragmas.debug); + if (!defined(this._primitive)) { + throw new DeveloperError('must call update before calling getGeometryInstanceAttributes'); + } + //>>includeEnd('debug'); + return this._primitive.getGeometryInstanceAttributes(id); + }; + + /** + * Checks if the given Scene supports GroundPolylinePrimitives. + * GroundPolylinePrimitives require support for the WEBGL_depth_texture extension. + * + * @param {Scene} scene The current scene. + * @returns {Boolean} Whether or not the current scene supports GroundPolylinePrimitives. + */ + GroundPolylinePrimitive.isSupported = function(scene) { + return scene.frameState.context.depthTexture; + }; + + /** + * Returns true if this object was destroyed; otherwise, false. + *

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

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

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

+ * + * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called. + * + * @example + * e = e && e.destroy(); + * + * @see GroundPolylinePrimitive#isDestroyed + */ GroundPolylinePrimitive.prototype.destroy = function() { this._primitive = this._primitive && this._primitive.destroy(); this._sp = this._sp && this._sp.destroy(); @@ -621,20 +844,5 @@ define([ return destroyObject(this); }; - GroundPolylinePrimitive.prototype.getGeometryInstanceAttributes = function(id) { - return this._primitive.getGeometryInstanceAttributes(id); - }; - - /** - * Checks if the given Scene supports GroundPolylinePrimitives. - * GroundPolylinePrimitives require support for the WEBGL_depth_texture extension. - * - * @param {Scene} scene The current scene. - * @returns {Boolean} Whether or not the current scene supports GroundPolylinePrimitives. - */ - GroundPolylinePrimitive.isSupported = function(scene) { - return scene.frameState.context.depthTexture; - }; - return GroundPolylinePrimitive; }); diff --git a/Source/Scene/GroundPrimitive.js b/Source/Scene/GroundPrimitive.js index 7bca828e6175..de6c371b6f71 100644 --- a/Source/Scene/GroundPrimitive.js +++ b/Source/Scene/GroundPrimitive.js @@ -174,21 +174,25 @@ define([ } } } - + /** + * The {@link Appearance} used to shade this primitive. Each geometry + * instance is shaded with the same appearance. Some appearances, like + * {@link PerInstanceColorAppearance} allow giving each instance unique + * properties. + * + * @type Appearance + * + * @default undefined + */ this.appearance = appearance; /** - * The geometry instance rendered with this primitive. This may + * The geometry instances rendered with this primitive. This may * be undefined if options.releaseGeometryInstances * is true when the primitive is constructed. *

* Changing this property after the primitive is rendered has no effect. *

- *

- * Because of the rendering technique used, all geometry instances must be the same color. - * If there is an instance with a differing color, a DeveloperError will be thrown - * on the first attempt to render. - *

* * @readonly * @type {Array|GeometryInstance} @@ -642,9 +646,9 @@ define([ * list the exceptions that may be propagated when the scene is rendered: *

* + * @exception {DeveloperError} For synchronous GroundPrimitive, you must call GroundPrimitive.initializeTerrainHeights() and wait for the returned promise to resolve. * @exception {DeveloperError} All instance geometries must have the same primitiveType. * @exception {DeveloperError} Appearance and material have a uniform with the same name. - * @exception {DeveloperError} Not all of the geometry instances have the same color attribute. */ GroundPrimitive.prototype.update = function(frameState) { if (!defined(this._primitive) && !defined(this.geometryInstances)) { From fe3531f85428cd38b4af161470e73ea9b18293f9 Mon Sep 17 00:00:00 2001 From: Kangning Li Date: Thu, 31 May 2018 16:10:07 -0400 Subject: [PATCH 14/39] switch GroundPolylineGeometry to take cartesian positions --- .../development/Polylines On Terrain.html | 44 +++++++++---------- Source/Core/GroundPolylineGeometry.js | 44 +++++++++++-------- 2 files changed, 47 insertions(+), 41 deletions(-) diff --git a/Apps/Sandcastle/gallery/development/Polylines On Terrain.html b/Apps/Sandcastle/gallery/development/Polylines On Terrain.html index 2e75bcea140e..a94c26106769 100644 --- a/Apps/Sandcastle/gallery/development/Polylines On Terrain.html +++ b/Apps/Sandcastle/gallery/development/Polylines On Terrain.html @@ -87,29 +87,29 @@ } }, Cesium.ScreenSpaceEventType.LEFT_CLICK); -var polylineCartographics = [ - Cesium.Cartographic.fromDegrees(-112.1340164450331, 36.05494287836128), - Cesium.Cartographic.fromDegrees(-112.08821010582645, 36.097804071380715), - Cesium.Cartographic.fromDegrees(-112.13296079730024, 36.168769146801104), - Cesium.Cartographic.fromDegrees(-112.10828895143331, 36.20031318533197), - Cesium.Cartographic.fromDegrees(-112.138548165717, 36.1691100215289), // hairpins - Cesium.Cartographic.fromDegrees(-112.11482556496543, 36.20127524083297), - Cesium.Cartographic.fromDegrees(-112.15921333464016, 36.17876011207708), - Cesium.Cartographic.fromDegrees(-112.14700151155604, 36.21683132404626), - Cesium.Cartographic.fromDegrees(-112.20919883052926, 36.19475754001766) -]; - -var loopCartographics = [ - Cesium.Cartographic.fromDegrees(-111.94500779274114, 36.27638678884143), - Cesium.Cartographic.fromDegrees(-111.90983004392696, 36.07985366173454), - Cesium.Cartographic.fromDegrees(-111.80360100637773, 36.13694878292542), - Cesium.Cartographic.fromDegrees(-111.85510122419183, 36.26029588763386), - Cesium.Cartographic.fromDegrees(-111.69141601804614, 36.05128770351902) -]; +var polylinePositions = Cesium.Cartesian3.fromDegreesArray([ + -112.1340164450331, 36.05494287836128, + -112.08821010582645, 36.097804071380715, + -112.13296079730024, 36.168769146801104, + -112.10828895143331, 36.20031318533197, + -112.138548165717, 36.1691100215289, // hairpins + -112.11482556496543, 36.20127524083297, + -112.15921333464016, 36.17876011207708, + -112.14700151155604, 36.21683132404626, + -112.20919883052926, 36.19475754001766 +]); + +var loopPositions = Cesium.Cartesian3.fromDegreesArray([ + -111.94500779274114, 36.27638678884143, + -111.90983004392696, 36.07985366173454, + -111.80360100637773, 36.13694878292542, + -111.85510122419183, 36.26029588763386, + -111.69141601804614, 36.05128770351902 +]); var instance1 = new Cesium.GeometryInstance({ geometry : new Cesium.GroundPolylineGeometry({ - positions : polylineCartographics, + positions : polylinePositions, loop : false, width : 16.0 }), @@ -122,7 +122,7 @@ var instance2 = new Cesium.GeometryInstance({ geometry : new Cesium.GroundPolylineGeometry({ - positions : loopCartographics, + positions : loopPositions, loop : true, width : 8.0 }), @@ -191,7 +191,7 @@ }); function lookAt() { - viewer.camera.lookAt(Cesium.Cartographic.toCartesian(polylineCartographics[1]), new Cesium.Cartesian3(50000.0, 50000.0, 50000.0)); + viewer.camera.lookAt(polylinePositions[1], new Cesium.Cartesian3(50000.0, 50000.0, 50000.0)); viewer.camera.lookAtTransform(Cesium.Matrix4.IDENTITY); } diff --git a/Source/Core/GroundPolylineGeometry.js b/Source/Core/GroundPolylineGeometry.js index 16e7d385eaf6..6c7c7a305b86 100644 --- a/Source/Core/GroundPolylineGeometry.js +++ b/Source/Core/GroundPolylineGeometry.js @@ -60,11 +60,11 @@ define([ * * @param {Object} [options] Options with the following properties: * @param {Number} [options.width=1.0] The screen space width in pixels. - * @param {Cartographic[]} [options.positions] An array of {@link Cartographic} defining the polyline's points. Heights will be ignored. + * @param {Cartesian3[]} [options.positions] An array of {@link Cartesian3} defining the polyline's points. Heights above the ellipsoid will be ignored. * @param {Number} [options.granularity=9999.0] The distance interval used for interpolating options.points. Defaults to 9999.0 meters. Zero indicates no interpolation. * @param {Boolean} [options.loop=false] Whether during geometry creation a line segment will be added between the last and first line positions to make this Polyline a loop. - * @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] Ellipsoid for projecting cartographic coordinates to 3D. - * @param {MapProjection} [options.projection] Map Projection for projecting cartographic coordinates to 2D. + * @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] Ellipsoid that input positions will be clamped to. + * @param {MapProjection} [options.projection] Map Projection for projecting coordinates to 2D. * * @see GroundPolylinePrimitive */ @@ -78,8 +78,8 @@ define([ this.width = defaultValue(options.width, 1.0); // Doesn't get packed, not necessary for computing geometry. /** - * An array of {@link Cartographic} defining the polyline's points. Heights will be ignored. - * @type {Cartographic[]} + * An array of {@link Cartesian3} defining the polyline's points. Heights above the ellipsoid will be ignored. + * @type {Cartesian3[]} */ this.positions = defaultValue(options.positions, []); @@ -127,7 +127,7 @@ define([ */ packedLength: { get: function() { - return 1.0 + this.positions.length * 2 + 1.0 + 1.0 + Ellipsoid.packedLength + 1.0; + return 1.0 + this.positions.length * 3 + 1.0 + 1.0 + Ellipsoid.packedLength + 1.0; } } }); @@ -212,9 +212,9 @@ define([ packArray[index++] = positionsLength; for (var i = 0; i < positionsLength; ++i) { - var cartographic = positions[i]; - packArray[index++] = cartographic.longitude; - packArray[index++] = cartographic.latitude; + var cartesian = positions[i]; + Cartesian3.pack(cartesian, packArray, index); + index += 3; } packArray[index++] = value.granularity; @@ -241,10 +241,8 @@ define([ var positionsLength = packArray[index++]; for (var i = 0; i < positionsLength; i++) { - var cartographic = new Cartographic(); - cartographic.longitude = packArray[index++]; - cartographic.latitude = packArray[index++]; - positions.push(cartographic); + positions.push(Cartesian3.unpack(packArray, index)); + index += 3; } var granularity = packArray[index++]; @@ -337,7 +335,6 @@ define([ * @private */ GroundPolylineGeometry.createGeometry = function(groundPolylineGeometry) { - var cartographics = groundPolylineGeometry.positions; var loop = groundPolylineGeometry.loop; var ellipsoid = groundPolylineGeometry.ellipsoid; var granularity = groundPolylineGeometry.granularity; @@ -346,21 +343,30 @@ define([ var minHeight = ApproximateTerrainHeights._defaultMinTerrainHeight; var maxHeight = ApproximateTerrainHeights._defaultMaxTerrainHeight; - var cartographicsLength = cartographics.length; + var index; + var i; + + var positions = groundPolylineGeometry.positions; + var cartographicsLength = positions.length; //>>includeStart('debug', pragmas.debug); if (cartographicsLength < 2) { - throw new DeveloperError('GroundPolylineGeometry must contain two or more cartographics'); + throw new DeveloperError('GroundPolylineGeometry must contain two or more positions'); } //>>includeEnd('debug'); if (cartographicsLength === 2) { loop = false; } - var index; - var i; + // Squash all cartesians to cartographic coordinates + var cartographics = new Array(cartographicsLength); + for (i = 0; i < cartographicsLength; i++) { + var cartographic = Cartographic.fromCartesian(positions[i], ellipsoid); + cartographic.height = 0.0; + cartographics[i] = cartographic; + } - /**** Build heap-side arrays of positions, interpolated cartographics, and normals from which to compute vertices ****/ + /**** Build heap-side arrays for positions, interpolated cartographics, and normals from which to compute vertices ****/ // We build a "wall" and then decompose it into separately connected component "volumes" because we need a lot // of information about the wall. Also, this simplifies interpolation. // Convention: "next" and "end" are locally forward to each segment of the wall, From 02e7f228b5be5de57b0d6b4daaa1b617a11e34f2 Mon Sep 17 00:00:00 2001 From: Kangning Li Date: Thu, 31 May 2018 18:11:25 -0400 Subject: [PATCH 15/39] split polyline shadow volumes across IDL and PM [ci skip] --- .../development/Polylines On Terrain.html | 2 +- Source/Core/GroundPolylineGeometry.js | 104 +++++++++++------- .../Shaders/PolylineShadowVolumeMorphVS.glsl | 30 ++--- Source/Shaders/PolylineShadowVolumeVS.glsl | 21 ++-- 4 files changed, 94 insertions(+), 63 deletions(-) diff --git a/Apps/Sandcastle/gallery/development/Polylines On Terrain.html b/Apps/Sandcastle/gallery/development/Polylines On Terrain.html index a94c26106769..28663a354048 100644 --- a/Apps/Sandcastle/gallery/development/Polylines On Terrain.html +++ b/Apps/Sandcastle/gallery/development/Polylines On Terrain.html @@ -135,7 +135,7 @@ polylineOnTerrainPrimitive = new Cesium.GroundPolylinePrimitive({ polylineGeometryInstances : [instance1, instance2], - debugShowShadowVolume : false + debugShowShadowVolume : true }); scene.primitives.add(polylineOnTerrainPrimitive); diff --git a/Source/Core/GroundPolylineGeometry.js b/Source/Core/GroundPolylineGeometry.js index 6c7c7a305b86..8e94960b0b6c 100644 --- a/Source/Core/GroundPolylineGeometry.js +++ b/Source/Core/GroundPolylineGeometry.js @@ -16,6 +16,7 @@ define([ './GeographicProjection', './Geometry', './GeometryAttribute', + './IntersectionTests', './Matrix3', './Plane', './Quaternion', @@ -39,6 +40,7 @@ define([ GeographicProjection, Geometry, GeometryAttribute, + IntersectionTests, Matrix3, Plane, Quaternion, @@ -322,11 +324,14 @@ define([ return result; } + var XZ_PLANE = Plane.fromPointNormal(Cartesian3.ZERO, Cartesian3.UNIT_Y); + var previousBottomScratch = new Cartesian3(); var vertexBottomScratch = new Cartesian3(); var vertexTopScratch = new Cartesian3(); var nextBottomScratch = new Cartesian3(); var vertexNormalScratch = new Cartesian3(); + var intersectionScratch = new Cartesian3(); /** * Computes shadow volumes for the ground polyline, consisting of its vertices, indices, and a bounding sphere. * Vertices are "fat," packing all the data needed in each volume to describe a line on terrain. @@ -347,21 +352,36 @@ define([ var i; var positions = groundPolylineGeometry.positions; - var cartographicsLength = positions.length; + var positionsLength = positions.length; //>>includeStart('debug', pragmas.debug); - if (cartographicsLength < 2) { + if (positionsLength < 2) { throw new DeveloperError('GroundPolylineGeometry must contain two or more positions'); } //>>includeEnd('debug'); - if (cartographicsLength === 2) { + if (positionsLength === 2) { loop = false; } + // Split positions across the IDL and the Prime Meridian as well. + // Split across prime meridian because very large geometries crossing the Prime Meridian but not the IDL + // may get split by the plane of IDL + Prime Meridian. + var splitPositions = [positions[0]]; + for (i = 0; i < positionsLength - 1; i++) { + var p0 = positions[i]; + var p1 = positions[i + 1]; + var intersection = IntersectionTests.lineSegmentPlane(p0, p1, XZ_PLANE, intersectionScratch); + if (defined(intersection)) { + splitPositions.push(Cartesian3.clone(intersection)); + } + splitPositions.push(p1); + } + var cartographicsLength = splitPositions.length; + // Squash all cartesians to cartographic coordinates var cartographics = new Array(cartographicsLength); for (i = 0; i < cartographicsLength; i++) { - var cartographic = Cartographic.fromCartesian(positions[i], ellipsoid); + var cartographic = Cartographic.fromCartesian(splitPositions[i], ellipsoid); cartographic.height = 0.0; cartographics[i] = cartographic; } @@ -519,6 +539,23 @@ define([ Cartesian3.add(top, adjustHeightOffset, adjustHeightTop); } + var nudgeDirectionScratch = new Cartesian3(); + function nudgeXZ(start, end) { + var startToXZdistance = Plane.getPointDistance(XZ_PLANE, start); + var endToXZdistance = Plane.getPointDistance(XZ_PLANE, end); + var offset = nudgeDirectionScratch; + // Same epsilon used in GeometryPipeline + if (CesiumMath.equalsEpsilon(startToXZdistance, 0.0, CesiumMath.EPSILON6)) { + offset = direction(end, start, offset); + Cartesian3.multiplyByScalar(offset, CesiumMath.EPSILON6, offset); + Cartesian3.add(start, offset, start); + } else if (CesiumMath.equalsEpsilon(endToXZdistance, 0.0, CesiumMath.EPSILON6)) { + offset = direction(start, end, offset); + Cartesian3.multiplyByScalar(offset, CesiumMath.EPSILON6, offset); + Cartesian3.add(end, offset, end); + } + } + var startCartographicScratch = new Cartographic(); var endCartographicScratch = new Cartographic(); @@ -588,8 +625,8 @@ define([ var startHi_and_forwardOffsetX = new Float32Array(arraySizeVec4); var startLo_and_forwardOffsetY = new Float32Array(arraySizeVec4); var startNormal_and_forwardOffsetZ = new Float32Array(arraySizeVec4); - var endNormal_andTextureCoordinateNormalizationX = new Float32Array(arraySizeVec4); - var rightNormal_andTextureCoordinateNormalizationY = new Float32Array(arraySizeVec4); + var endNormal_and_textureCoordinateNormalizationX = new Float32Array(arraySizeVec4); + var rightNormal_and_textureCoordinateNormalizationY = new Float32Array(arraySizeVec4); var startHiLo2D = new Float32Array(arraySizeVec4); var offsetAndRight2D = new Float32Array(arraySizeVec4); @@ -749,6 +786,9 @@ define([ var vec2Index = vec2sWriteIndex + j * 2; var wIndex = vec4Index + 3; + // Encode sidedness of vertex in texture coordinate normalization X + var sidedness = j < 3 ? 1.0 : -1.0; + // 3D Cartesian3.pack(encodedStart.high, startHi_and_forwardOffsetX, vec4Index); startHi_and_forwardOffsetX[wIndex] = forwardOffset.x; @@ -759,11 +799,11 @@ define([ Cartesian3.pack(startPlaneNormal, startNormal_and_forwardOffsetZ, vec4Index); startNormal_and_forwardOffsetZ[wIndex] = forwardOffset.z; - Cartesian3.pack(endPlaneNormal, endNormal_andTextureCoordinateNormalizationX, vec4Index); - endNormal_andTextureCoordinateNormalizationX[wIndex] = texcoordNormalization3DX; + Cartesian3.pack(endPlaneNormal, endNormal_and_textureCoordinateNormalizationX, vec4Index); + endNormal_and_textureCoordinateNormalizationX[wIndex] = texcoordNormalization3DX * sidedness; - Cartesian3.pack(rightNormal, rightNormal_andTextureCoordinateNormalizationY, vec4Index); - rightNormal_andTextureCoordinateNormalizationY[wIndex] = texcoordNormalization3DY; + Cartesian3.pack(rightNormal, rightNormal_and_textureCoordinateNormalizationY, vec4Index); + rightNormal_and_textureCoordinateNormalizationY[wIndex] = texcoordNormalization3DY; // 2D startHiLo2D[vec4Index] = encodedStart2D.high.x; @@ -781,7 +821,7 @@ define([ offsetAndRight2D[vec4Index + 2] = right2D.x; offsetAndRight2D[vec4Index + 3] = right2D.y; - texcoordNormalization2D[vec2Index] = texcoordNormalization2DX; + texcoordNormalization2D[vec2Index] = texcoordNormalization2DX * sidedness; texcoordNormalization2D[vec2Index + 1] = texcoordNormalization2DY; } @@ -808,31 +848,19 @@ define([ adjustHeights(startBottom, startTop, minHeight, maxHeight, adjustHeightStartBottom, adjustHeightStartTop); adjustHeights(endBottom, endTop, minHeight, maxHeight, adjustHeightEndBottom, adjustHeightEndTop); - // Push out by 1.0 in the "right" direction - var pushedStartBottom = Cartesian3.add(adjustHeightStartBottom, startGeometryNormal, adjustHeightStartBottom); - var pushedEndBottom = Cartesian3.add(adjustHeightEndBottom, endGeometryNormal, adjustHeightEndBottom); - var pushedEndTop = Cartesian3.add(adjustHeightEndTop, endGeometryNormal, adjustHeightEndTop); - var pushedStartTop = Cartesian3.add(adjustHeightStartTop, startGeometryNormal, adjustHeightStartTop); - Cartesian3.pack(pushedStartBottom, positionsArray, vec3sWriteIndex); - Cartesian3.pack(pushedEndBottom, positionsArray, vec3sWriteIndex + 3); - Cartesian3.pack(pushedEndTop, positionsArray, vec3sWriteIndex + 6); - Cartesian3.pack(pushedStartTop, positionsArray, vec3sWriteIndex + 9); - - // Return to center - pushedStartBottom = Cartesian3.subtract(adjustHeightStartBottom, startGeometryNormal, adjustHeightStartBottom); - pushedEndBottom = Cartesian3.subtract(adjustHeightEndBottom, endGeometryNormal, adjustHeightEndBottom); - pushedEndTop = Cartesian3.subtract(adjustHeightEndTop, endGeometryNormal, adjustHeightEndTop); - pushedStartTop = Cartesian3.subtract(adjustHeightStartTop, startGeometryNormal, adjustHeightStartTop); - - // Push out by 1.0 in the "left" direction - pushedStartBottom = Cartesian3.subtract(adjustHeightStartBottom, startGeometryNormal, adjustHeightStartBottom); - pushedEndBottom = Cartesian3.subtract(adjustHeightEndBottom, endGeometryNormal, adjustHeightEndBottom); - pushedEndTop = Cartesian3.subtract(adjustHeightEndTop, endGeometryNormal, adjustHeightEndTop); - pushedStartTop = Cartesian3.subtract(adjustHeightStartTop, startGeometryNormal, adjustHeightStartTop); - Cartesian3.pack(pushedStartBottom, positionsArray, vec3sWriteIndex + 12); - Cartesian3.pack(pushedEndBottom, positionsArray, vec3sWriteIndex + 15); - Cartesian3.pack(pushedEndTop, positionsArray, vec3sWriteIndex + 18); - Cartesian3.pack(pushedStartTop, positionsArray, vec3sWriteIndex + 21); + // If the segment is very close to the XZ plane, nudge the vertices slightly to avoid touching it. + nudgeXZ(adjustHeightStartBottom, adjustHeightEndBottom); + nudgeXZ(adjustHeightStartTop, adjustHeightEndTop); + + Cartesian3.pack(adjustHeightStartBottom, positionsArray, vec3sWriteIndex); + Cartesian3.pack(adjustHeightEndBottom, positionsArray, vec3sWriteIndex + 3); + Cartesian3.pack(adjustHeightEndTop, positionsArray, vec3sWriteIndex + 6); + Cartesian3.pack(adjustHeightStartTop, positionsArray, vec3sWriteIndex + 9); + + Cartesian3.pack(adjustHeightStartBottom, positionsArray, vec3sWriteIndex + 12); + Cartesian3.pack(adjustHeightEndBottom, positionsArray, vec3sWriteIndex + 15); + Cartesian3.pack(adjustHeightEndTop, positionsArray, vec3sWriteIndex + 18); + Cartesian3.pack(adjustHeightStartTop, positionsArray, vec3sWriteIndex + 21); cartographicsIndex += 2; index += 3; @@ -873,8 +901,8 @@ define([ startHi_and_forwardOffsetX : getVec4GeometryAttribute(startHi_and_forwardOffsetX), startLo_and_forwardOffsetY : getVec4GeometryAttribute(startLo_and_forwardOffsetY), startNormal_and_forwardOffsetZ : getVec4GeometryAttribute(startNormal_and_forwardOffsetZ), - endNormal_andTextureCoordinateNormalizationX : getVec4GeometryAttribute(endNormal_andTextureCoordinateNormalizationX), - rightNormal_andTextureCoordinateNormalizationY : getVec4GeometryAttribute(rightNormal_andTextureCoordinateNormalizationY), + endNormal_and_textureCoordinateNormalizationX : getVec4GeometryAttribute(endNormal_and_textureCoordinateNormalizationX), + rightNormal_and_textureCoordinateNormalizationY : getVec4GeometryAttribute(rightNormal_and_textureCoordinateNormalizationY), startHiLo2D : getVec4GeometryAttribute(startHiLo2D), offsetAndRight2D : getVec4GeometryAttribute(offsetAndRight2D), diff --git a/Source/Shaders/PolylineShadowVolumeMorphVS.glsl b/Source/Shaders/PolylineShadowVolumeMorphVS.glsl index a0979e8628d7..070cc4c00eea 100644 --- a/Source/Shaders/PolylineShadowVolumeMorphVS.glsl +++ b/Source/Shaders/PolylineShadowVolumeMorphVS.glsl @@ -4,8 +4,8 @@ attribute vec3 position3DLow; attribute vec4 startHi_and_forwardOffsetX; attribute vec4 startLo_and_forwardOffsetY; attribute vec4 startNormal_and_forwardOffsetZ; -attribute vec4 endNormal_andTextureCoordinateNormalizationX; -attribute vec4 rightNormal_andTextureCoordinateNormalizationY; +attribute vec4 endNormal_and_textureCoordinateNormalizationX; +attribute vec4 rightNormal_and_textureCoordinateNormalizationY; attribute vec4 startHiLo2D; attribute vec4 offsetAndRight2D; attribute vec4 startEndNormals2D; @@ -51,7 +51,7 @@ void main() vec4 rightPlane2D; vec4 rightPlane3D; rightPlane2D.xyz = czm_normal * vec3(0.0, offsetAndRight2D.zw); - rightPlane3D.xyz = czm_normal * rightNormal_andTextureCoordinateNormalizationY.xyz; + rightPlane3D.xyz = czm_normal * rightNormal_and_textureCoordinateNormalizationY.xyz; rightPlane2D.w = -dot(rightPlane2D.xyz, ecPos2D); rightPlane3D.w = -dot(rightPlane3D.xyz, ecPos3D); @@ -67,14 +67,16 @@ void main() vec4 endPlane2D; vec4 endPlane3D; endPlane2D.xyz = czm_normal * vec3(0.0, startEndNormals2D.zw); - endPlane3D.xyz = czm_normal * endNormal_andTextureCoordinateNormalizationX.xyz; + endPlane3D.xyz = czm_normal * endNormal_and_textureCoordinateNormalizationX.xyz; endPlane2D.w = -dot(endPlane2D.xyz, ecPos2D); endPlane3D.w = -dot(endPlane3D.xyz, ecPos3D); // Forward direction v_forwardDirectionEC = normalize(ecEnd - ecStart); - v_texcoordNormalization_and_halfWidth.xy = mix(texcoordNormalization2D, vec2(endNormal_andTextureCoordinateNormalizationX.w, rightNormal_andTextureCoordinateNormalizationY.w), czm_morphTime); + v_texcoordNormalization_and_halfWidth.xy = mix( + vec2(abs(texcoordNormalization2D.x), texcoordNormalization2D.y), + vec2(abs(endNormal_and_textureCoordinateNormalizationX.w), rightNormal_and_textureCoordinateNormalizationY.w), czm_morphTime); #ifdef PER_INSTANCE_COLOR v_color = czm_batchTable_color(batchId); @@ -95,7 +97,7 @@ void main() // Decode the normal to use at this specific vertex, push the position back, and then push to where it needs to be. // Since this is morphing, compute both 3D and 2D positions and then blend. - // 3D + // ****** 3D ****** // Check distance to the end plane and start plane, pick the plane that is closer vec4 positionEC3D = czm_modelViewRelativeToEye * czm_translateRelativeToEye(position3DHigh, position3DLow); // w = 1.0, see czm_computePosition float absStartPlaneDistance = abs(czm_planeDistance(startPlane3D, positionEC3D.xyz)); @@ -104,15 +106,14 @@ void main() vec3 upOrDown = normalize(cross(rightPlane3D.xyz, planeDirection)); // Points "up" for start plane, "down" at end plane. vec3 normalEC = normalize(cross(planeDirection, upOrDown)); // In practice, the opposite seems to work too. - // Check distance to the right plane to determine if the miter normal points "left" or "right" - normalEC *= sign(czm_planeDistance(rightPlane3D, positionEC3D.xyz)); + // Determine if this vertex is on the "left" or "right" + normalEC *= sign(endNormal_and_textureCoordinateNormalizationX.w); // A "perfect" implementation would push along normals according to the angle against forward. // In practice, just pushing the normal out by halfWidth is sufficient for morph views. - positionEC3D.xyz -= normalEC; // undo the unit length push positionEC3D.xyz += halfWidth * max(0.0, czm_metersPerPixel(positionEC3D)) * normalEC; // prevent artifacts when czm_metersPerPixel is negative (behind camera) - // 2D + // ****** 2D ****** // Check distance to the end plane and start plane, pick the plane that is closer vec4 positionEC2D = czm_modelViewRelativeToEye * czm_translateRelativeToEye(position2DHigh.zxy, position2DLow.zxy); // w = 1.0, see czm_computePosition absStartPlaneDistance = abs(czm_planeDistance(startPlane2D, positionEC2D.xyz)); @@ -121,12 +122,11 @@ void main() upOrDown = normalize(cross(rightPlane2D.xyz, planeDirection)); // Points "up" for start plane, "down" at end plane. normalEC = normalize(cross(planeDirection, upOrDown)); // In practice, the opposite seems to work too. - // Check distance to the right plane to determine if the miter normal points "left" or "right." - // Also use this check to determine this vertex's horizontal texture coordinate. - float leftOrRight = sign(czm_planeDistance(rightPlane2D, positionEC2D.xyz)); - normalEC *= leftOrRight; + // Determine if this vertex is on the "left" or "right" + normalEC *= sign(texcoordNormalization2D.x); #ifndef PER_INSTANCE_COLOR - v_texcoordT = clamp(leftOrRight, 0.0, 1.0); + // Use vertex's sidedness to compute its texture coordinate. + v_texcoordT = clamp(sign(texcoordNormalization2D.x), 0.0, 1.0); #endif // A "perfect" implementation would push along normals according to the angle against forward. diff --git a/Source/Shaders/PolylineShadowVolumeVS.glsl b/Source/Shaders/PolylineShadowVolumeVS.glsl index 55f456440e17..c55c80cd2c70 100644 --- a/Source/Shaders/PolylineShadowVolumeVS.glsl +++ b/Source/Shaders/PolylineShadowVolumeVS.glsl @@ -5,8 +5,8 @@ attribute vec3 position3DLow; attribute vec4 startHi_and_forwardOffsetX; attribute vec4 startLo_and_forwardOffsetY; attribute vec4 startNormal_and_forwardOffsetZ; -attribute vec4 endNormal_andTextureCoordinateNormalizationX; -attribute vec4 rightNormal_andTextureCoordinateNormalizationY; +attribute vec4 endNormal_and_textureCoordinateNormalizationX; +attribute vec4 rightNormal_and_textureCoordinateNormalizationY; #else attribute vec4 startHiLo2D; attribute vec4 offsetAndRight2D; @@ -55,7 +55,7 @@ void main() v_endPlaneEC.xyz = czm_normal * vec3(0.0, startEndNormals2D.zw); v_endPlaneEC.w = -dot(v_endPlaneEC.xyz, ecEnd); - v_texcoordNormalization_and_halfWidth.xy = texcoordNormalization2D; + v_texcoordNormalization_and_halfWidth.xy = vec2(abs(texcoordNormalization2D.x), texcoordNormalization2D.y); #else // COLUMBUS_VIEW_2D vec3 ecStart = (czm_modelViewRelativeToEye * czm_translateRelativeToEye(startHi_and_forwardOffsetX.xyz, startLo_and_forwardOffsetY.xyz)).xyz; @@ -70,14 +70,14 @@ void main() v_startPlaneEC.w = -dot(v_startPlaneEC.xyz, ecStart); // end plane - v_endPlaneEC.xyz = czm_normal * endNormal_andTextureCoordinateNormalizationX.xyz; + v_endPlaneEC.xyz = czm_normal * endNormal_and_textureCoordinateNormalizationX.xyz; v_endPlaneEC.w = -dot(v_endPlaneEC.xyz, ecEnd); // Right plane - v_rightPlaneEC.xyz = czm_normal * rightNormal_andTextureCoordinateNormalizationY.xyz; + v_rightPlaneEC.xyz = czm_normal * rightNormal_and_textureCoordinateNormalizationY.xyz; v_rightPlaneEC.w = -dot(v_rightPlaneEC.xyz, ecStart); - v_texcoordNormalization_and_halfWidth.xy = vec2(endNormal_andTextureCoordinateNormalizationX.w, rightNormal_andTextureCoordinateNormalizationY.w); + v_texcoordNormalization_and_halfWidth.xy = vec2(abs(endNormal_and_textureCoordinateNormalizationX.w), rightNormal_and_textureCoordinateNormalizationY.w); #endif // COLUMBUS_VIEW_2D @@ -102,8 +102,12 @@ void main() vec3 upOrDown = normalize(cross(v_rightPlaneEC.xyz, planeDirection)); // Points "up" for start plane, "down" at end plane. vec3 normalEC = normalize(cross(planeDirection, upOrDown)); // In practice, the opposite seems to work too. - // Check distance to the right plane to determine if the miter normal points "left" or "right" - normalEC *= sign(czm_planeDistance(v_rightPlaneEC, positionEC.xyz)); + // Determine if this vertex is on the "left" or "right" +#ifdef COLUMBUS_VIEW_2D + normalEC *= sign(texcoordNormalization2D.x); +#else + normalEC *= sign(endNormal_and_textureCoordinateNormalizationX.w); +#endif // A "perfect" implementation would push along normals according to the angle against forward. // In practice, just extending the shadow volume more than needed works for most cases, @@ -111,7 +115,6 @@ void main() float width = czm_batchTable_width(batchId); v_width = width; v_texcoordNormalization_and_halfWidth.z = width * 0.5; - positionEC.xyz -= normalEC; // undo the unit length push positionEC.xyz += width * max(0.0, czm_metersPerPixel(positionEC)) * normalEC; // prevent artifacts when czm_metersPerPixel is negative (behind camera) gl_Position = czm_projection * positionEC; From 6a51e44086566c77b6dd025dad1865b0130d02e4 Mon Sep 17 00:00:00 2001 From: Kanging Li Date: Thu, 31 May 2018 21:11:44 -0400 Subject: [PATCH 16/39] handle looping and normal projection for IDL/PM split [ci skip] --- Source/Core/GroundPolylineGeometry.js | 37 ++++++++++++++++++++++----- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/Source/Core/GroundPolylineGeometry.js b/Source/Core/GroundPolylineGeometry.js index 8e94960b0b6c..bca57854e481 100644 --- a/Source/Core/GroundPolylineGeometry.js +++ b/Source/Core/GroundPolylineGeometry.js @@ -366,16 +366,28 @@ define([ // Split positions across the IDL and the Prime Meridian as well. // Split across prime meridian because very large geometries crossing the Prime Meridian but not the IDL // may get split by the plane of IDL + Prime Meridian. + var p0; + var p1; + var intersection; var splitPositions = [positions[0]]; for (i = 0; i < positionsLength - 1; i++) { - var p0 = positions[i]; - var p1 = positions[i + 1]; - var intersection = IntersectionTests.lineSegmentPlane(p0, p1, XZ_PLANE, intersectionScratch); + p0 = positions[i]; + p1 = positions[i + 1]; + intersection = IntersectionTests.lineSegmentPlane(p0, p1, XZ_PLANE, intersectionScratch); if (defined(intersection)) { splitPositions.push(Cartesian3.clone(intersection)); } splitPositions.push(p1); } + // Check if loop also crosses IDL/Prime Meridian + if (loop) { + p0 = positions[positionsLength - 1]; + p1 = positions[0]; + intersection = IntersectionTests.lineSegmentPlane(p0, p1, XZ_PLANE, intersectionScratch); + if (defined(intersection)) { + splitPositions.push(Cartesian3.clone(intersection)); + } + } var cartographicsLength = splitPositions.length; // Squash all cartesians to cartographic coordinates @@ -510,16 +522,27 @@ define([ var positionCartographicScratch = new Cartographic(); var normalEndpointScratch = new Cartesian3(); - function projectNormal(projection, position, normal, projectedPosition, result) { + function projectNormal(projection, position, positionLongitude, normal, projectedPosition, result) { var normalEndpoint = Cartesian3.add(position, normal, normalEndpointScratch); + var flipNormal = false; var ellipsoid = projection.ellipsoid; var normalEndpointCartographic = ellipsoid.cartesianToCartographic(normalEndpoint, positionCartographicScratch); + // If normal crosses the IDL, go the other way and flip the result + if (Math.abs(positionLongitude - normalEndpointCartographic.longitude) > CesiumMath.PI_OVER_TWO) { + flipNormal = true; + normalEndpoint = Cartesian3.subtract(position, normal, normalEndpointScratch); + normalEndpointCartographic = ellipsoid.cartesianToCartographic(normalEndpoint, positionCartographicScratch); + } + normalEndpointCartographic.height = 0.0; var normalEndpointProjected = projection.project(normalEndpointCartographic, result); result = Cartesian3.subtract(normalEndpointProjected, projectedPosition, result); result.z = 0.0; result = Cartesian3.normalize(result, result); + if (flipNormal) { + Cartesian3.multiplyByScalar(result, -1.0, result); + } return result; } @@ -695,7 +718,7 @@ define([ endCartographic.latitude = cartographicsArray[0]; endCartographic.longitude = cartographicsArray[1]; var end2D = projection.project(endCartographic, segmentEnd2DScratch); - var endGeometryNormal2D = projectNormal(projection, endBottom, endGeometryNormal, end2D, segmentEndNormal2DScratch); + var endGeometryNormal2D = projectNormal(projection, endBottom, endCartographic.longitude, endGeometryNormal, end2D, segmentEndNormal2DScratch); var lengthSoFar3D = 0.0; var lengthSoFar2D = 0.0; @@ -725,7 +748,7 @@ define([ endCartographic.latitude = cartographicsArray[cartographicsIndex]; endCartographic.longitude = cartographicsArray[cartographicsIndex + 1]; end2D = projection.project(endCartographic, segmentEnd2DScratch); - endGeometryNormal2D = projectNormal(projection, endBottom, endGeometryNormal, end2D, segmentEndNormal2DScratch); + endGeometryNormal2D = projectNormal(projection, endBottom, endCartographic.longitude, endGeometryNormal, end2D, segmentEndNormal2DScratch); /**************************************** * Geometry descriptors of a "line on terrain," @@ -787,7 +810,7 @@ define([ var wIndex = vec4Index + 3; // Encode sidedness of vertex in texture coordinate normalization X - var sidedness = j < 3 ? 1.0 : -1.0; + var sidedness = j < 4 ? 1.0 : -1.0; // 3D Cartesian3.pack(encodedStart.high, startHi_and_forwardOffsetX, vec4Index); From 63db595ddc26f9d927ae474defa4438988995757 Mon Sep 17 00:00:00 2001 From: Kangning Li Date: Fri, 1 Jun 2018 11:14:43 -0400 Subject: [PATCH 17/39] fix additional IDL bugs [ci skip] --- .../development/Polylines On Terrain.html | 2 +- Source/Core/GroundPolylineGeometry.js | 133 +++++++++++++----- 2 files changed, 95 insertions(+), 40 deletions(-) diff --git a/Apps/Sandcastle/gallery/development/Polylines On Terrain.html b/Apps/Sandcastle/gallery/development/Polylines On Terrain.html index 28663a354048..a94c26106769 100644 --- a/Apps/Sandcastle/gallery/development/Polylines On Terrain.html +++ b/Apps/Sandcastle/gallery/development/Polylines On Terrain.html @@ -135,7 +135,7 @@ polylineOnTerrainPrimitive = new Cesium.GroundPolylinePrimitive({ polylineGeometryInstances : [instance1, instance2], - debugShowShadowVolume : true + debugShowShadowVolume : false }); scene.primitives.add(polylineOnTerrainPrimitive); diff --git a/Source/Core/GroundPolylineGeometry.js b/Source/Core/GroundPolylineGeometry.js index bca57854e481..4630f167bec2 100644 --- a/Source/Core/GroundPolylineGeometry.js +++ b/Source/Core/GroundPolylineGeometry.js @@ -520,19 +520,21 @@ define([ return false; } - var positionCartographicScratch = new Cartographic(); + var endPosCartographicScratch = new Cartographic(); + var normalStartpointScratch = new Cartesian3(); var normalEndpointScratch = new Cartesian3(); - function projectNormal(projection, position, positionLongitude, normal, projectedPosition, result) { + function projectNormal(projection, cartographic, normal, projectedPosition, result) { + var position = Cartographic.toCartesian(cartographic, projection.ellipsoid, normalStartpointScratch); var normalEndpoint = Cartesian3.add(position, normal, normalEndpointScratch); var flipNormal = false; var ellipsoid = projection.ellipsoid; - var normalEndpointCartographic = ellipsoid.cartesianToCartographic(normalEndpoint, positionCartographicScratch); + var normalEndpointCartographic = ellipsoid.cartesianToCartographic(normalEndpoint, endPosCartographicScratch); // If normal crosses the IDL, go the other way and flip the result - if (Math.abs(positionLongitude - normalEndpointCartographic.longitude) > CesiumMath.PI_OVER_TWO) { + if (Math.abs(cartographic.longitude - normalEndpointCartographic.longitude) > CesiumMath.PI_OVER_TWO) { flipNormal = true; normalEndpoint = Cartesian3.subtract(position, normal, normalEndpointScratch); - normalEndpointCartographic = ellipsoid.cartesianToCartographic(normalEndpoint, positionCartographicScratch); + normalEndpointCartographic = ellipsoid.cartesianToCartographic(normalEndpoint, endPosCartographicScratch); } normalEndpointCartographic.height = 0.0; @@ -567,18 +569,33 @@ define([ var startToXZdistance = Plane.getPointDistance(XZ_PLANE, start); var endToXZdistance = Plane.getPointDistance(XZ_PLANE, end); var offset = nudgeDirectionScratch; - // Same epsilon used in GeometryPipeline - if (CesiumMath.equalsEpsilon(startToXZdistance, 0.0, CesiumMath.EPSILON6)) { + // Larger epsilon than what's used in GeometryPipeline, less than a centimeter in world space + if (CesiumMath.equalsEpsilon(startToXZdistance, 0.0, CesiumMath.EPSILON4)) { offset = direction(end, start, offset); - Cartesian3.multiplyByScalar(offset, CesiumMath.EPSILON6, offset); + Cartesian3.multiplyByScalar(offset, CesiumMath.EPSILON4, offset); Cartesian3.add(start, offset, start); - } else if (CesiumMath.equalsEpsilon(endToXZdistance, 0.0, CesiumMath.EPSILON6)) { + } else if (CesiumMath.equalsEpsilon(endToXZdistance, 0.0, CesiumMath.EPSILON4)) { offset = direction(start, end, offset); - Cartesian3.multiplyByScalar(offset, CesiumMath.EPSILON6, offset); + Cartesian3.multiplyByScalar(offset, CesiumMath.EPSILON4, offset); Cartesian3.add(end, offset, end); } } + function nudgeCartographic(start, end) { + var absStartLon = Math.abs(start.longitude); + var absEndLon = Math.abs(end.longitude); + if (CesiumMath.equalsEpsilon(absStartLon, CesiumMath.PI, CesiumMath.EPSILON7)) { + var endSign = Math.sign(end.longitude); + start.longitude = endSign * (absStartLon - CesiumMath.EPSILON7); + return 1; + } else if (CesiumMath.equalsEpsilon(absEndLon, CesiumMath.PI, CesiumMath.EPSILON7)) { + var startSign = Math.sign(start.longitude); + end.longitude = startSign * (absEndLon - CesiumMath.EPSILON7); + return 2; + } + return 0; + } + var startCartographicScratch = new Cartographic(); var endCartographicScratch = new Cartographic(); @@ -614,6 +631,8 @@ define([ var forwardOffset2DScratch = new Cartesian3(); var right2DScratch = new Cartesian3(); + var normalNudgeScratch = new Cartesian3(); + var scratchBoundingSpheres = [new BoundingSphere(), new BoundingSphere()]; // Winding order is reversed so each segment's volume is inside-out @@ -662,21 +681,26 @@ define([ var length2D = 0.0; var length3D = 0.0; - var cartographic = startCartographicScratch; - cartographic.latitude = cartographicsArray[0]; - cartographic.longitude = cartographicsArray[1]; - cartographic.height = 0.0; + var startCartographic = startCartographicScratch; + startCartographic.height = 0.0; + var endCartographic = endCartographicScratch; + endCartographic.height = 0.0; var segmentStartCartesian = segmentStartTopScratch; - var segmentEndCartesian = projection.project(cartographic, segmentEndTopScratch); + var segmentEndCartesian = segmentEndTopScratch; - index = 2; + index = 0; for (i = 1; i < cartographicsLength; i++) { - cartographic.latitude = cartographicsArray[index]; - cartographic.longitude = cartographicsArray[index + 1]; + // don't clone anything from previous segment b/c possible IDL touch + startCartographic.latitude = cartographicsArray[index]; + startCartographic.longitude = cartographicsArray[index + 1]; + endCartographic.latitude = cartographicsArray[index + 2]; + endCartographic.longitude = cartographicsArray[index + 3]; - segmentStartCartesian = Cartesian3.clone(segmentEndCartesian, segmentStartCartesian); - segmentEndCartesian = projection.project(cartographic, segmentEndCartesian); + nudgeCartographic(startCartographic, endCartographic); + + segmentStartCartesian = projection.project(startCartographic, segmentStartCartesian); + segmentEndCartesian = projection.project(endCartographic, segmentEndCartesian); length2D += Cartesian3.distance(segmentStartCartesian, segmentEndCartesian); index += 2; } @@ -696,7 +720,7 @@ define([ /*** Generate segments ***/ var j; index = 3; - var cartographicsIndex = 2; + var cartographicsIndex = 0; var vec2sWriteIndex = 0; var vec3sWriteIndex = 0; var vec4sWriteIndex = 0; @@ -713,12 +737,6 @@ define([ endGeometryNormal = Cartesian3.multiplyByScalar(endGeometryNormal, -1.0, endGeometryNormal); } } - var endCartographic = endCartographicScratch; - var startCartographic = startCartographicScratch; - endCartographic.latitude = cartographicsArray[0]; - endCartographic.longitude = cartographicsArray[1]; - var end2D = projection.project(endCartographic, segmentEnd2DScratch); - var endGeometryNormal2D = projectNormal(projection, endBottom, endCartographic.longitude, endGeometryNormal, end2D, segmentEndNormal2DScratch); var lengthSoFar3D = 0.0; var lengthSoFar2D = 0.0; @@ -728,15 +746,8 @@ define([ var startTop = Cartesian3.clone(endTop, segmentStartTopScratch); var startGeometryNormal = Cartesian3.clone(endGeometryNormal, segmentStartNormalScratch); - var start2D = Cartesian3.clone(end2D, segmentStart2DScratch); - var startGeometryNormal2D = Cartesian3.clone(endGeometryNormal2D, segmentStartNormal2DScratch); - startCartographic = Cartographic.clone(endCartographic, startCartographic); - if (miterBroken) { - // If miter was broken for the previous segment's end vertex, flip for this segment's start vertex - // These normals are "right facing." startGeometryNormal = Cartesian3.multiplyByScalar(startGeometryNormal, -1.0, startGeometryNormal); - startGeometryNormal2D = Cartesian3.multiplyByScalar(startGeometryNormal2D, -1.0, startGeometryNormal2D); } endBottom = Cartesian3.unpack(bottomPositionsArray, index, segmentEndBottomScratch); @@ -745,10 +756,36 @@ define([ miterBroken = breakMiter(endGeometryNormal, startBottom, endBottom, endTop); - endCartographic.latitude = cartographicsArray[cartographicsIndex]; - endCartographic.longitude = cartographicsArray[cartographicsIndex + 1]; - end2D = projection.project(endCartographic, segmentEnd2DScratch); - endGeometryNormal2D = projectNormal(projection, endBottom, endCartographic.longitude, endGeometryNormal, end2D, segmentEndNormal2DScratch); + // 2D - don't clone anything from previous segment b/c possible IDL touch + startCartographic.latitude = cartographicsArray[cartographicsIndex]; + startCartographic.longitude = cartographicsArray[cartographicsIndex + 1]; + endCartographic.latitude = cartographicsArray[cartographicsIndex + 2]; + endCartographic.longitude = cartographicsArray[cartographicsIndex + 3]; + + var nudgeResult = nudgeCartographic(startCartographic, endCartographic); + var start2D = projection.project(startCartographic, segmentStart2DScratch); + var end2D = projection.project(endCartographic, segmentEnd2DScratch); + + var startGeometryNormal2D = segmentStartNormal2DScratch; + var endGeometryNormal2D = segmentEndNormal2DScratch; + if (nudgeResult === 0) { + startGeometryNormal2D = projectNormal(projection, startCartographic, startGeometryNormal, start2D, segmentStartNormal2DScratch); + endGeometryNormal2D = projectNormal(projection, endCartographic, endGeometryNormal, end2D, segmentEndNormal2DScratch); + } else if (nudgeResult === 1) { + // start is close to IDL + endGeometryNormal2D = projectNormal(projection, endCartographic, endGeometryNormal, end2D, segmentEndNormal2DScratch); + startGeometryNormal2D.x = 0.0; + // If start longitude is negative and end longitude is less negative, "right" is unit -Y + // If start longitude is positive and end longitude is less positive, "right" is unit +Y + startGeometryNormal2D.y = Math.sign(startCartographic.longitude - Math.abs(endCartographic.longitude)); + startGeometryNormal2D.z = 0.0; + } else { + // end is close to IDL + startGeometryNormal2D = projectNormal(projection, startCartographic, startGeometryNormal, start2D, segmentStartNormal2DScratch); + endGeometryNormal2D.x = 0.0; + endGeometryNormal2D.y = Math.sign(endCartographic.longitude - Math.abs(startCartographic.longitude)); + endGeometryNormal2D.z = 0.0; + } /**************************************** * Geometry descriptors of a "line on terrain," @@ -871,6 +908,13 @@ define([ adjustHeights(startBottom, startTop, minHeight, maxHeight, adjustHeightStartBottom, adjustHeightStartTop); adjustHeights(endBottom, endTop, minHeight, maxHeight, adjustHeightEndBottom, adjustHeightEndTop); + // Nudge the positions away from the "polyline" a little bit to prevent errors in GeometryPipeline + var normalNudge = Cartesian3.multiplyByScalar(rightNormal, CesiumMath.EPSILON5, normalNudgeScratch); + Cartesian3.add(adjustHeightStartBottom, normalNudge, adjustHeightStartBottom); + Cartesian3.add(adjustHeightEndBottom, normalNudge, adjustHeightEndBottom); + Cartesian3.add(adjustHeightStartTop, normalNudge, adjustHeightStartTop); + Cartesian3.add(adjustHeightEndTop, normalNudge, adjustHeightEndTop); + // If the segment is very close to the XZ plane, nudge the vertices slightly to avoid touching it. nudgeXZ(adjustHeightStartBottom, adjustHeightEndBottom); nudgeXZ(adjustHeightStartTop, adjustHeightEndTop); @@ -880,6 +924,17 @@ define([ Cartesian3.pack(adjustHeightEndTop, positionsArray, vec3sWriteIndex + 6); Cartesian3.pack(adjustHeightStartTop, positionsArray, vec3sWriteIndex + 9); + // Nudge in opposite direction + normalNudge = Cartesian3.multiplyByScalar(rightNormal, -2.0 * CesiumMath.EPSILON5, normalNudgeScratch); + Cartesian3.add(adjustHeightStartBottom, normalNudge, adjustHeightStartBottom); + Cartesian3.add(adjustHeightEndBottom, normalNudge, adjustHeightEndBottom); + Cartesian3.add(adjustHeightStartTop, normalNudge, adjustHeightStartTop); + Cartesian3.add(adjustHeightEndTop, normalNudge, adjustHeightEndTop); + + // Check against XZ plane again + nudgeXZ(adjustHeightStartBottom, adjustHeightEndBottom); + nudgeXZ(adjustHeightStartTop, adjustHeightEndTop); + Cartesian3.pack(adjustHeightStartBottom, positionsArray, vec3sWriteIndex + 12); Cartesian3.pack(adjustHeightEndBottom, positionsArray, vec3sWriteIndex + 15); Cartesian3.pack(adjustHeightEndTop, positionsArray, vec3sWriteIndex + 18); @@ -893,7 +948,7 @@ define([ vec4sWriteIndex += 32; lengthSoFar3D += segmentLength3D; - lengthSoFar2D = segmentLength2D; + lengthSoFar2D += segmentLength2D; } /*** Generate indices ***/ From 9e39ecc24696b2b33e1f82fb427e369c12779a0a Mon Sep 17 00:00:00 2001 From: Kangning Li Date: Fri, 1 Jun 2018 16:20:10 -0400 Subject: [PATCH 18/39] specs for GroundPolylineGeometry --- Source/Core/GroundPolylineGeometry.js | 47 ++- Specs/Core/GroundPolylineGeometrySpec.js | 400 +++++++++++++++++++++++ 2 files changed, 429 insertions(+), 18 deletions(-) create mode 100644 Specs/Core/GroundPolylineGeometrySpec.js diff --git a/Source/Core/GroundPolylineGeometry.js b/Source/Core/GroundPolylineGeometry.js index 4630f167bec2..9ebbba8992a7 100644 --- a/Source/Core/GroundPolylineGeometry.js +++ b/Source/Core/GroundPolylineGeometry.js @@ -203,31 +203,35 @@ define([ * * @returns {Number[]} The array that was packed into */ - GroundPolylineGeometry.pack = function(value, packArray, startingIndex) { - startingIndex = defaultValue(startingIndex, 0); + GroundPolylineGeometry.pack = function(value, array, startingIndex) { + //>>includeStart('debug', pragmas.debug); + Check.typeOf.object('value', value); + Check.defined('array', array); + //>>includeEnd('debug'); + + var index = defaultValue(startingIndex, 0); // Pack position length, then all positions var positions = value.positions; var positionsLength = positions.length; - var index = startingIndex; - packArray[index++] = positionsLength; + array[index++] = positionsLength; for (var i = 0; i < positionsLength; ++i) { var cartesian = positions[i]; - Cartesian3.pack(cartesian, packArray, index); + Cartesian3.pack(cartesian, array, index); index += 3; } - packArray[index++] = value.granularity; - packArray[index++] = value.loop ? 1.0 : 0.0; + array[index++] = value.granularity; + array[index++] = value.loop ? 1.0 : 0.0; - Ellipsoid.pack(value.ellipsoid, packArray, index); + Ellipsoid.pack(value.ellipsoid, array, index); index += Ellipsoid.packedLength; - packArray[index++] = value._projectionIndex; + array[index++] = value._projectionIndex; - return packArray; + return array; }; /** @@ -237,23 +241,27 @@ define([ * @param {Number} [startingIndex=0] The starting index of the element to be unpacked. * @param {PolygonGeometry} [result] The object into which to store the result. */ - GroundPolylineGeometry.unpack = function(packArray, startingIndex, result) { + GroundPolylineGeometry.unpack = function(array, startingIndex, result) { + //>>includeStart('debug', pragmas.debug); + Check.defined('array', array); + //>>includeEnd('debug'); + var index = defaultValue(startingIndex, 0); var positions = []; - var positionsLength = packArray[index++]; + var positionsLength = array[index++]; for (var i = 0; i < positionsLength; i++) { - positions.push(Cartesian3.unpack(packArray, index)); + positions.push(Cartesian3.unpack(array, index)); index += 3; } - var granularity = packArray[index++]; - var loop = packArray[index++] === 1.0 ? true : false; + var granularity = array[index++]; + var loop = array[index++] === 1.0 ? true : false; - var ellipsoid = Ellipsoid.unpack(packArray, index); + var ellipsoid = Ellipsoid.unpack(array, index); index += Ellipsoid.packedLength; - var projectionIndex = packArray[index++]; + var projectionIndex = array[index++]; if (!defined(result)) { return new GroundPolylineGeometry({ @@ -530,7 +538,10 @@ define([ var ellipsoid = projection.ellipsoid; var normalEndpointCartographic = ellipsoid.cartesianToCartographic(normalEndpoint, endPosCartographicScratch); - // If normal crosses the IDL, go the other way and flip the result + // If normal crosses the IDL, go the other way and flip the result. + // In practice this almost never happens because normals for points + // very close to the IDL get snapped directly north or directly south, + // but this is here in case something sneaks past the epsilon check. if (Math.abs(cartographic.longitude - normalEndpointCartographic.longitude) > CesiumMath.PI_OVER_TWO) { flipNormal = true; normalEndpoint = Cartesian3.subtract(position, normal, normalEndpointScratch); diff --git a/Specs/Core/GroundPolylineGeometrySpec.js b/Specs/Core/GroundPolylineGeometrySpec.js new file mode 100644 index 000000000000..d6328857a784 --- /dev/null +++ b/Specs/Core/GroundPolylineGeometrySpec.js @@ -0,0 +1,400 @@ +defineSuite([ + 'Core/GroundPolylineGeometry', + 'Core/ApproximateTerrainHeights', + 'Core/Cartesian3', + 'Core/Cartographic', + 'Core/Math', + 'Core/Ellipsoid', + 'Core/GeographicProjection', + 'Specs/createPackableSpecs' + ], function( + GroundPolylineGeometry, + ApproximateTerrainHeights, + Cartesian3, + Cartographic, + CesiumMath, + Ellipsoid, + GeographicProjection, + createPackableSpecs) { + 'use strict'; + + beforeAll(function() { + return ApproximateTerrainHeights.initialize(); + }); + + afterAll(function() { + ApproximateTerrainHeights._initPromise = undefined; + ApproximateTerrainHeights._terrainHeights = undefined; + }); + + function verifyAttributeValuesIdentical(attribute) { + var values = attribute.values; + var componentsPerAttribute = attribute.componentsPerAttribute; + var vertexCount = values.length / componentsPerAttribute; + var firstVertex = values.slice(0, componentsPerAttribute); + var identical = true; + for (var i = 1; i < vertexCount; i++) { + var index = i * componentsPerAttribute; + var vertex = values.slice(index, index + componentsPerAttribute); + for (var j = 0; j < componentsPerAttribute; j++) { + if (vertex[j] !== firstVertex[j]) { + identical = false; + break; + } + } + } + expect(identical).toBe(true); + } + + it('computes positions and additional attributes for polylines', function() { + var startCartographic = Cartographic.fromDegrees(0.01, 0.0); + var endCartographic = Cartographic.fromDegrees(0.02, 0.0); + var groundPolylineGeometry = new GroundPolylineGeometry({ + positions : Cartesian3.fromRadiansArray([ + startCartographic.longitude, startCartographic.latitude, + endCartographic.longitude, endCartographic.latitude + ]), + granularity : 0.0 + }); + + var geometry = GroundPolylineGeometry.createGeometry(groundPolylineGeometry); + + expect(geometry.indices.length).toEqual(36); + expect(geometry.attributes.position.values.length).toEqual(24); + + var startHi_and_forwardOffsetX = geometry.attributes.startHi_and_forwardOffsetX; + var startLo_and_forwardOffsetY = geometry.attributes.startLo_and_forwardOffsetY; + var startNormal_and_forwardOffsetZ = geometry.attributes.startNormal_and_forwardOffsetZ; + var endNormal_and_textureCoordinateNormalizationX = geometry.attributes.endNormal_and_textureCoordinateNormalizationX; + var rightNormal_and_textureCoordinateNormalizationY = geometry.attributes.rightNormal_and_textureCoordinateNormalizationY; + var startHiLo2D = geometry.attributes.startHiLo2D; + var offsetAndRight2D = geometry.attributes.offsetAndRight2D; + var startEndNormals2D = geometry.attributes.startEndNormals2D; + var texcoordNormalization2D = geometry.attributes.texcoordNormalization2D; + + // Expect each entry in the additional attributes to be identical across all vertices since this is a single segment, + // except endNormal_and_textureCoordinateNormalizationX and texcoordNormalization2D, which should be "sided" + verifyAttributeValuesIdentical(startHi_and_forwardOffsetX); + verifyAttributeValuesIdentical(startLo_and_forwardOffsetY); + verifyAttributeValuesIdentical(startNormal_and_forwardOffsetZ); + verifyAttributeValuesIdentical(rightNormal_and_textureCoordinateNormalizationY); + verifyAttributeValuesIdentical(startHiLo2D); + verifyAttributeValuesIdentical(offsetAndRight2D); + verifyAttributeValuesIdentical(startEndNormals2D); + + // Expect endNormal_and_textureCoordinateNormalizationX and texcoordNormalization2D.x to encode the "side" of the geometry + var i; + var index; + var values = endNormal_and_textureCoordinateNormalizationX.values; + for (i = 0; i < 4; i++) { + index = i * 4 + 3; + expect(Math.sign(values[index])).toEqual(1.0); + } + for (i = 4; i < 8; i++) { + index = i * 4 + 3; + expect(Math.sign(values[index])).toEqual(-1.0); + } + + values = texcoordNormalization2D.values; + for (i = 0; i < 4; i++) { + index = i * 2; + expect(Math.sign(values[index])).toEqual(1.0); + } + for (i = 4; i < 8; i++) { + index = i * 2; + expect(Math.sign(values[index])).toEqual(-1.0); + } + + // Line segment geometry is encoded as: + // - start position + // - offset to the end position + // - normal for a mitered plane at each end + // - a right-facing normal + // - parameters for localizing the position along the line to texture coordinates + var startPosition3D = new Cartesian3(); + startPosition3D.x = startHi_and_forwardOffsetX.values[0] + startLo_and_forwardOffsetY.values[0]; + startPosition3D.y = startHi_and_forwardOffsetX.values[1] + startLo_and_forwardOffsetY.values[1]; + startPosition3D.z = startHi_and_forwardOffsetX.values[2] + startLo_and_forwardOffsetY.values[2]; + var reconstructedCarto = Cartographic.fromCartesian(startPosition3D); + reconstructedCarto.height = 0.0; + expect(Cartographic.equalsEpsilon(reconstructedCarto, startCartographic, CesiumMath.EPSILON7)).toBe(true); + + var endPosition3D = new Cartesian3(); + endPosition3D.x = startPosition3D.x + startHi_and_forwardOffsetX.values[3]; + endPosition3D.y = startPosition3D.y + startLo_and_forwardOffsetY.values[3]; + endPosition3D.z = startPosition3D.z + startNormal_and_forwardOffsetZ.values[3]; + reconstructedCarto = Cartographic.fromCartesian(endPosition3D); + reconstructedCarto.height = 0.0; + expect(Cartographic.equalsEpsilon(reconstructedCarto, endCartographic, CesiumMath.EPSILON7)).toBe(true); + + var startNormal3D = Cartesian3.unpack(startNormal_and_forwardOffsetZ.values); + expect(Cartesian3.equalsEpsilon(startNormal3D, new Cartesian3(0.0, 1.0, 0.0), CesiumMath.EPSILON2)).toBe(true); + + var endNormal3D = Cartesian3.unpack(endNormal_and_textureCoordinateNormalizationX.values); + expect(Cartesian3.equalsEpsilon(endNormal3D, new Cartesian3(0.0, -1.0, 0.0), CesiumMath.EPSILON2)).toBe(true); + + var rightNormal3D = Cartesian3.unpack(rightNormal_and_textureCoordinateNormalizationY.values); + expect(Cartesian3.equalsEpsilon(rightNormal3D, new Cartesian3(0.0, 0.0, -1.0), CesiumMath.EPSILON2)).toBe(true); + + var texcoordNormalizationX = endNormal_and_textureCoordinateNormalizationX.values[3]; + var texcoordNormalizationY = rightNormal_and_textureCoordinateNormalizationY.values[3]; + expect(texcoordNormalizationX).toEqualEpsilon(1.0, CesiumMath.EPSILON3); + expect(texcoordNormalizationY).toEqualEpsilon(0.0, CesiumMath.EPSILON3); + + // 2D + var projection = new GeographicProjection(); + + var startPosition2D = new Cartesian3(); + startPosition2D.x = startHiLo2D.values[0] + startHiLo2D.values[2]; + startPosition2D.y = startHiLo2D.values[1] + startHiLo2D.values[3]; + reconstructedCarto = projection.unproject(startPosition2D); + reconstructedCarto.height = 0.0; + expect(Cartographic.equalsEpsilon(reconstructedCarto, startCartographic, CesiumMath.EPSILON7)).toBe(true); + + var endPosition2D = new Cartesian3(); + endPosition2D.x = startPosition2D.x + offsetAndRight2D.values[0]; + endPosition2D.y = startPosition2D.y + offsetAndRight2D.values[1]; + reconstructedCarto = projection.unproject(endPosition2D); + reconstructedCarto.height = 0.0; + expect(Cartographic.equalsEpsilon(reconstructedCarto, endCartographic, CesiumMath.EPSILON7)).toBe(true); + + var startNormal2D = new Cartesian3(); + startNormal2D.x = startEndNormals2D.values[0]; + startNormal2D.y = startEndNormals2D.values[1]; + expect(Cartesian3.equalsEpsilon(startNormal2D, new Cartesian3(1.0, 0.0, 0.0), CesiumMath.EPSILON2)).toBe(true); + + var endNormal2D = new Cartesian3(); + endNormal2D.x = startEndNormals2D.values[2]; + endNormal2D.y = startEndNormals2D.values[3]; + expect(Cartesian3.equalsEpsilon(endNormal2D, new Cartesian3(-1.0, 0.0, 0.0), CesiumMath.EPSILON2)).toBe(true); + + var rightNormal2D = new Cartesian3(); + rightNormal2D.x = offsetAndRight2D.values[2]; + rightNormal2D.y = offsetAndRight2D.values[3]; + expect(Cartesian3.equalsEpsilon(rightNormal2D, new Cartesian3(0.0, -1.0, 0.0), CesiumMath.EPSILON2)).toBe(true); + + texcoordNormalizationX = texcoordNormalization2D.values[0]; + texcoordNormalizationY = texcoordNormalization2D.values[1]; + expect(texcoordNormalizationX).toEqualEpsilon(1.0, CesiumMath.EPSILON3); + expect(texcoordNormalizationY).toEqualEpsilon(0.0, CesiumMath.EPSILON3); + }); + + it('miters turns', function() { + var groundPolylineGeometry = new GroundPolylineGeometry({ + positions : Cartesian3.fromDegreesArray([ + 0.01, 0.0, + 0.02, 0.0, + 0.02, 0.01 + ]), + granularity : 0.0 + }); + + var geometry = GroundPolylineGeometry.createGeometry(groundPolylineGeometry); + expect(geometry.indices.length).toEqual(72); + expect(geometry.attributes.position.values.length).toEqual(48); + + var startNormal_and_forwardOffsetZvalues = geometry.attributes.startNormal_and_forwardOffsetZ.values; + var endNormal_and_textureCoordinateNormalizationXvalues = geometry.attributes.endNormal_and_textureCoordinateNormalizationX.values; + + var miteredStartNormal = Cartesian3.unpack(startNormal_and_forwardOffsetZvalues, 32); + var miteredEndNormal = Cartesian3.unpack(endNormal_and_textureCoordinateNormalizationXvalues, 0); + var reverseMiteredEndNormal = Cartesian3.multiplyByScalar(miteredEndNormal, -1.0, new Cartesian3()); + + expect(Cartesian3.equalsEpsilon(miteredStartNormal, reverseMiteredEndNormal, CesiumMath.EPSILON7)).toBe(true); + + var approximateExpectedMiterNormal = new Cartesian3(0.0, 1.0, 1.0); + Cartesian3.normalize(approximateExpectedMiterNormal, approximateExpectedMiterNormal); + expect(Cartesian3.equalsEpsilon(approximateExpectedMiterNormal, miteredStartNormal, CesiumMath.EPSILON2)).toBe(true); + }); + + it('breaks miters for tight turns', function() { + var groundPolylineGeometry = new GroundPolylineGeometry({ + positions : Cartesian3.fromDegreesArray([ + 0.01, 0.0, + 0.02, 0.0, + 0.01, 0.0 + ]), + granularity : 0.0 + }); + + var geometry = GroundPolylineGeometry.createGeometry(groundPolylineGeometry); + + var startNormal_and_forwardOffsetZvalues = geometry.attributes.startNormal_and_forwardOffsetZ.values; + var endNormal_and_textureCoordinateNormalizationXvalues = geometry.attributes.endNormal_and_textureCoordinateNormalizationX.values; + + var miteredStartNormal = Cartesian3.unpack(startNormal_and_forwardOffsetZvalues, 32); + var miteredEndNormal = Cartesian3.unpack(endNormal_and_textureCoordinateNormalizationXvalues, 0); + var reverseMiteredEndNormal = Cartesian3.multiplyByScalar(miteredEndNormal, -1.0, new Cartesian3()); + + expect(Cartesian3.equalsEpsilon(miteredStartNormal, reverseMiteredEndNormal, CesiumMath.EPSILON7)).toBe(true); + + var approximateExpectedMiterNormal = new Cartesian3(0.0, 1.0, 0.0); + + Cartesian3.normalize(approximateExpectedMiterNormal, approximateExpectedMiterNormal); + expect(Cartesian3.equalsEpsilon(approximateExpectedMiterNormal, miteredStartNormal, CesiumMath.EPSILON2)).toBe(true); + }); + + it('interpolates long polyline segments', function() { + var groundPolylineGeometry = new GroundPolylineGeometry({ + positions : Cartesian3.fromDegreesArray([ + 0.01, 0.0, + 0.02, 0.0 + ]), + granularity : 600.0 // 0.01 to 0.02 is about 1113 meters with default ellipsoid, expect two segments + }); + + var geometry = GroundPolylineGeometry.createGeometry(groundPolylineGeometry); + + expect(geometry.indices.length).toEqual(72); + expect(geometry.attributes.position.values.length).toEqual(48); + + // Interpolate one segment but not the other + groundPolylineGeometry = new GroundPolylineGeometry({ + positions : Cartesian3.fromDegreesArray([ + 0.01, 0.0, + 0.02, 0.0, + 0.0201, 0.0 + ]), + granularity : 600.0 + }); + + geometry = GroundPolylineGeometry.createGeometry(groundPolylineGeometry); + + expect(geometry.indices.length).toEqual(36 * 3); + expect(geometry.attributes.position.values.length).toEqual(24 * 3); + }); + + it('loops when there are enough positions and loop is specified', function() { + var groundPolylineGeometry = new GroundPolylineGeometry({ + positions : Cartesian3.fromDegreesArray([ + 0.01, 0.0, + 0.02, 0.0 + ]), + granularity : 0.0, + loop : true + }); + + // Not enough positions to loop, should still be a single segment + var geometry = GroundPolylineGeometry.createGeometry(groundPolylineGeometry); + expect(geometry.indices.length).toEqual(36); + + groundPolylineGeometry = new GroundPolylineGeometry({ + positions : Cartesian3.fromDegreesArray([ + 0.01, 0.0, + 0.02, 0.0, + 0.02, 0.02 + ]), + granularity : 0.0, + loop : true + }); + + // Loop should produce 3 segments + geometry = GroundPolylineGeometry.createGeometry(groundPolylineGeometry); + expect(geometry.indices.length).toEqual(108); + }); + + it('subdivides geometry across the IDL and Prime Meridian', function() { + // Cross PM + var groundPolylineGeometry = new GroundPolylineGeometry({ + positions : Cartesian3.fromDegreesArray([ + -1.0, 0.0, + 1.0, 0.0 + ]), + granularity : 0.0 // no interpolative subdivision + }); + + var geometry = GroundPolylineGeometry.createGeometry(groundPolylineGeometry); + + expect(geometry.indices.length).toEqual(72); + expect(geometry.attributes.position.values.length).toEqual(48); + + // Cross IDL + groundPolylineGeometry = new GroundPolylineGeometry({ + positions : Cartesian3.fromDegreesArray([ + -179.0, 0.0, + 179.0, 0.0 + ]), + granularity : 0.0 // no interpolative subdivision + }); + + geometry = GroundPolylineGeometry.createGeometry(groundPolylineGeometry); + + expect(geometry.indices.length).toEqual(72); + expect(geometry.attributes.position.values.length).toEqual(48); + + // Cross IDL going opposite direction and loop + groundPolylineGeometry = new GroundPolylineGeometry({ + positions : Cartesian3.fromDegreesArray([ + 179.0, 0.0, + 179.0, 1.0, + -179.0, 1.0, + -179.0, 0.0 + ]), + granularity : 0.0, // no interpolative subdivision + loop : true + }); + + geometry = GroundPolylineGeometry.createGeometry(groundPolylineGeometry); + }); + + it('throws errors if not enough positions have been provided', function() { + var groundPolylineGeometry = new GroundPolylineGeometry({ + positions : Cartesian3.fromDegreesArray([ + 0.01, 0.0 + ]), + granularity : 0.0, + loop : true + }); + + expect(function() { + GroundPolylineGeometry.createGeometry(groundPolylineGeometry); + }).toThrowError(); + }); + + it('can unpack onto an existing instance', function() { + var groundPolylineGeometry = new GroundPolylineGeometry({ + positions : Cartesian3.fromDegreesArray([ + -1.0, 0.0, + 1.0, 0.0 + ]), + loop : true, + granularity : 10.0 // no interpolative subdivision + }); + + var packedArray = [0]; + GroundPolylineGeometry.pack(groundPolylineGeometry, packedArray, 1); + var scratch = new GroundPolylineGeometry(); + GroundPolylineGeometry.unpack(packedArray, 1, scratch); + + var scratchPositions = scratch.positions; + expect(scratchPositions.length).toEqual(2); + expect(Cartesian3.equals(scratchPositions[0], groundPolylineGeometry.positions[0])).toBe(true); + expect(Cartesian3.equals(scratchPositions[1], groundPolylineGeometry.positions[1])).toBe(true); + expect(scratch.loop).toBe(true); + expect(scratch.granularity).toEqual(10.0); + expect(scratch.ellipsoid.equals(Ellipsoid.WGS84)).toBe(true); + }); + + var positions = Cartesian3.fromDegreesArray([ + 0.01, 0.0, + 0.02, 0.0, + 0.02, 0.1 + ]); + var polyline = new GroundPolylineGeometry({ + positions : positions, + granularity : 1000.0, + loop : true + }); + + var packedInstance = [positions.length]; + Cartesian3.pack(positions[0], packedInstance, packedInstance.length); + Cartesian3.pack(positions[1], packedInstance, packedInstance.length); + Cartesian3.pack(positions[2], packedInstance, packedInstance.length); + packedInstance.push(polyline.granularity); + packedInstance.push(polyline.loop ? 1.0 : 0.0); + + Ellipsoid.pack(Ellipsoid.WGS84, packedInstance, packedInstance.length); + + packedInstance.push(0.0); // projection index for Geographic (default) + createPackableSpecs(GroundPolylineGeometry, polyline, packedInstance); +}); From bed6ecf2f3523e22b166bb300f7e9590c8e4ec91 Mon Sep 17 00:00:00 2001 From: Kanging Li Date: Sun, 3 Jun 2018 21:26:33 -0400 Subject: [PATCH 19/39] special handling for lines near-parallel to IDL --- Source/Core/GroundPolylineGeometry.js | 52 ++++++++++++++++-------- Specs/Core/GroundPolylineGeometrySpec.js | 28 +++++++++++++ 2 files changed, 64 insertions(+), 16 deletions(-) diff --git a/Source/Core/GroundPolylineGeometry.js b/Source/Core/GroundPolylineGeometry.js index 9ebbba8992a7..b9279a1f785b 100644 --- a/Source/Core/GroundPolylineGeometry.js +++ b/Source/Core/GroundPolylineGeometry.js @@ -539,9 +539,9 @@ define([ var ellipsoid = projection.ellipsoid; var normalEndpointCartographic = ellipsoid.cartesianToCartographic(normalEndpoint, endPosCartographicScratch); // If normal crosses the IDL, go the other way and flip the result. - // In practice this almost never happens because normals for points - // very close to the IDL get snapped directly north or directly south, - // but this is here in case something sneaks past the epsilon check. + // In practice this almost never happens because the cartographic start + // and end points of each segment are "nudged" to be on the same side + // of the IDL and slightly away from the IDL. if (Math.abs(cartographic.longitude - normalEndpointCartographic.longitude) > CesiumMath.PI_OVER_TWO) { flipNormal = true; normalEndpoint = Cartesian3.subtract(position, normal, normalEndpointScratch); @@ -581,27 +581,30 @@ define([ var endToXZdistance = Plane.getPointDistance(XZ_PLANE, end); var offset = nudgeDirectionScratch; // Larger epsilon than what's used in GeometryPipeline, less than a centimeter in world space - if (CesiumMath.equalsEpsilon(startToXZdistance, 0.0, CesiumMath.EPSILON4)) { + if (CesiumMath.equalsEpsilon(startToXZdistance, 0.0, CesiumMath.EPSILON3)) { offset = direction(end, start, offset); - Cartesian3.multiplyByScalar(offset, CesiumMath.EPSILON4, offset); + Cartesian3.multiplyByScalar(offset, CesiumMath.EPSILON3, offset); Cartesian3.add(start, offset, start); - } else if (CesiumMath.equalsEpsilon(endToXZdistance, 0.0, CesiumMath.EPSILON4)) { + } else if (CesiumMath.equalsEpsilon(endToXZdistance, 0.0, CesiumMath.EPSILON3)) { offset = direction(start, end, offset); - Cartesian3.multiplyByScalar(offset, CesiumMath.EPSILON4, offset); + Cartesian3.multiplyByScalar(offset, CesiumMath.EPSILON3, offset); Cartesian3.add(end, offset, end); } } + // "Nudge" cartographic coordinates so start and end are on the same side of the IDL. + // Nudge amounts are tiny, basically just an IDL flip. + // Only used for 2D/CV. function nudgeCartographic(start, end) { var absStartLon = Math.abs(start.longitude); var absEndLon = Math.abs(end.longitude); - if (CesiumMath.equalsEpsilon(absStartLon, CesiumMath.PI, CesiumMath.EPSILON7)) { + if (CesiumMath.equalsEpsilon(absStartLon, CesiumMath.PI, CesiumMath.EPSILON11)) { var endSign = Math.sign(end.longitude); - start.longitude = endSign * (absStartLon - CesiumMath.EPSILON7); + start.longitude = endSign * (absStartLon - CesiumMath.EPSILON11); return 1; - } else if (CesiumMath.equalsEpsilon(absEndLon, CesiumMath.PI, CesiumMath.EPSILON7)) { + } else if (CesiumMath.equalsEpsilon(absEndLon, CesiumMath.PI, CesiumMath.EPSILON11)) { var startSign = Math.sign(start.longitude); - end.longitude = startSign * (absEndLon - CesiumMath.EPSILON7); + end.longitude = startSign * (absEndLon - CesiumMath.EPSILON11); return 2; } return 0; @@ -708,8 +711,6 @@ define([ endCartographic.latitude = cartographicsArray[index + 2]; endCartographic.longitude = cartographicsArray[index + 3]; - nudgeCartographic(startCartographic, endCartographic); - segmentStartCartesian = projection.project(startCartographic, segmentStartCartesian); segmentEndCartesian = projection.project(endCartographic, segmentEndCartesian); length2D += Cartesian3.distance(segmentStartCartesian, segmentEndCartesian); @@ -776,14 +777,19 @@ define([ var nudgeResult = nudgeCartographic(startCartographic, endCartographic); var start2D = projection.project(startCartographic, segmentStart2DScratch); var end2D = projection.project(endCartographic, segmentEnd2DScratch); + var direction2D = direction(end2D, start2D, forwardOffset2DScratch); + direction2D.y = Math.abs(direction2D.y); var startGeometryNormal2D = segmentStartNormal2DScratch; var endGeometryNormal2D = segmentEndNormal2DScratch; - if (nudgeResult === 0) { + if (nudgeResult === 0 || Cartesian3.dot(direction2D, Cartesian3.UNIT_Y) > MITER_BREAK_SMALL) { + // No nudge - project the original normal + // Or, if the line's angle relative to the IDL is very acute, + // in which case snapping will produce oddly shaped volumes. startGeometryNormal2D = projectNormal(projection, startCartographic, startGeometryNormal, start2D, segmentStartNormal2DScratch); endGeometryNormal2D = projectNormal(projection, endCartographic, endGeometryNormal, end2D, segmentEndNormal2DScratch); } else if (nudgeResult === 1) { - // start is close to IDL + // Start is close to IDL - snap start normal to align with IDL endGeometryNormal2D = projectNormal(projection, endCartographic, endGeometryNormal, end2D, segmentEndNormal2DScratch); startGeometryNormal2D.x = 0.0; // If start longitude is negative and end longitude is less negative, "right" is unit -Y @@ -791,7 +797,7 @@ define([ startGeometryNormal2D.y = Math.sign(startCartographic.longitude - Math.abs(endCartographic.longitude)); startGeometryNormal2D.z = 0.0; } else { - // end is close to IDL + // End is close to IDL - snap end normal to align with IDL startGeometryNormal2D = projectNormal(projection, startCartographic, startGeometryNormal, start2D, segmentStartNormal2DScratch); endGeometryNormal2D.x = 0.0; endGeometryNormal2D.y = Math.sign(endCartographic.longitude - Math.abs(startCartographic.longitude)); @@ -1017,5 +1023,19 @@ define([ }); } + /** + * Approximates an ellipsoid-tangent vector in 2D by projecting the end point into 2D. + * Exposed for testing. + * + * @param {MapProjection} projection Map Projection for projecting coordinates to 2D. + * @param {Cartographic} cartographic The cartographic origin point of the normal. + * Used to check if the normal crosses the IDL during projection. + * @param {Cartesian3} normal The normal in 3D. + * @param {Cartesian3} projectedPosition The projected origin point of the normal in 2D. + * @param {Cartesian3} result Result parameter on which to store the projected normal. + * @private + */ + GroundPolylineGeometry._projectNormal = projectNormal; + return GroundPolylineGeometry; }); diff --git a/Specs/Core/GroundPolylineGeometrySpec.js b/Specs/Core/GroundPolylineGeometrySpec.js index d6328857a784..67b271cfb3b0 100644 --- a/Specs/Core/GroundPolylineGeometrySpec.js +++ b/Specs/Core/GroundPolylineGeometrySpec.js @@ -335,6 +335,23 @@ defineSuite([ }); geometry = GroundPolylineGeometry.createGeometry(groundPolylineGeometry); + + expect(geometry.indices.length).toEqual(6 * 36); + expect(geometry.attributes.position.values.length).toEqual(6 * 24); + + // Near-IDL case + groundPolylineGeometry = new GroundPolylineGeometry({ + positions : Cartesian3.fromDegreesArray([ + 179.999, 80.0, + -179.999, 80.0 + ]), + granularity : 0.0 // no interpolative subdivision + }); + + geometry = GroundPolylineGeometry.createGeometry(groundPolylineGeometry); + + expect(geometry.indices.length).toEqual(72); + expect(geometry.attributes.position.values.length).toEqual(48); }); it('throws errors if not enough positions have been provided', function() { @@ -386,6 +403,17 @@ defineSuite([ loop : true }); + it('projects normals that cross the IDL', function() { + var projection = new GeographicProjection(); + var cartographic = new Cartographic(CesiumMath.PI - CesiumMath.EPSILON11, 0.0); + var normal = new Cartesian3(0.0, -1.0, 0.0); + var projectedPosition = projection.project(cartographic, new Cartesian3()); + var result = new Cartesian3(); + + GroundPolylineGeometry._projectNormal(projection, cartographic, normal, projectedPosition, result); + expect(Cartesian3.equalsEpsilon(result, new Cartesian3(1.0, 0.0, 0.0), CesiumMath.EPSILON7)).toBe(true); + }); + var packedInstance = [positions.length]; Cartesian3.pack(positions[0], packedInstance, packedInstance.length); Cartesian3.pack(positions[1], packedInstance, packedInstance.length); From fd903d2d5414838157335b7d12194c9d9cd0967f Mon Sep 17 00:00:00 2001 From: Kangning Li Date: Mon, 4 Jun 2018 10:01:33 -0400 Subject: [PATCH 20/39] fix GroundPolylineGeometry test failure for built Cesium --- Specs/Core/GroundPolylineGeometrySpec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Specs/Core/GroundPolylineGeometrySpec.js b/Specs/Core/GroundPolylineGeometrySpec.js index 67b271cfb3b0..0cb7fc9bccc2 100644 --- a/Specs/Core/GroundPolylineGeometrySpec.js +++ b/Specs/Core/GroundPolylineGeometrySpec.js @@ -365,7 +365,7 @@ defineSuite([ expect(function() { GroundPolylineGeometry.createGeometry(groundPolylineGeometry); - }).toThrowError(); + }).toThrowDeveloperError(); }); it('can unpack onto an existing instance', function() { From 071a636c965a8be788d6c6d2b6442c115dad4403 Mon Sep 17 00:00:00 2001 From: Kangning Li Date: Mon, 4 Jun 2018 12:29:15 -0400 Subject: [PATCH 21/39] fix discontinuity for GroundPolylineGeometry due to plane origins being really far away --- Source/Core/GroundPolylineGeometry.js | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/Source/Core/GroundPolylineGeometry.js b/Source/Core/GroundPolylineGeometry.js index b9279a1f785b..3cfe3b4266b5 100644 --- a/Source/Core/GroundPolylineGeometry.js +++ b/Source/Core/GroundPolylineGeometry.js @@ -54,6 +54,19 @@ define([ var MITER_BREAK_SMALL = Math.cos(CesiumMath.toRadians(30)); var MITER_BREAK_LARGE = Math.cos(CesiumMath.toRadians(150)); + // Initial heights for constructing the wall. + // Keeping WALL_INITIAL_MIN_HEIGHT near the ellipsoid surface helps + // prevent precision problems with planes in the shader. + // Putting the start point of a plane at ApproximateTerrainHeights._defaultMinTerrainHeight, + // which is a highly conservative bound, usually puts the plane origin several thousands + // of meters away from the actual terrain, causing floating point problems when checking + // fragments on terrain against the plane. + // Ellipsoid height is generally much closer. + // The initial max height is arbitrary. + // Both heights are corrected using ApproximateTerrainHeights for computing the actual volume geometry. + var WALL_INITIAL_MIN_HEIGHT = 0.0; + var WALL_INITIAL_MAX_HEIGHT = 1000.0; + /** * A description of a polyline on terrain. Only to be used with GroundPolylinePrimitive. * @@ -353,8 +366,8 @@ define([ var granularity = groundPolylineGeometry.granularity; var projection = new PROJECTIONS[groundPolylineGeometry._projectionIndex](ellipsoid); - var minHeight = ApproximateTerrainHeights._defaultMinTerrainHeight; - var maxHeight = ApproximateTerrainHeights._defaultMaxTerrainHeight; + var minHeight = WALL_INITIAL_MIN_HEIGHT; + var maxHeight = WALL_INITIAL_MAX_HEIGHT; var index; var i; @@ -562,15 +575,15 @@ define([ var adjustHeightNormalScratch = new Cartesian3(); var adjustHeightOffsetScratch = new Cartesian3(); function adjustHeights(bottom, top, minHeight, maxHeight, adjustHeightBottom, adjustHeightTop) { - // bottom and top should be at ApproximateTerrainHeights._defaultMinTerrainHeight and ApproximateTerrainHeights._defaultMaxTerrainHeight, respectively + // bottom and top should be at WALL_INITIAL_MIN_HEIGHT and WALL_INITIAL_MAX_HEIGHT, respectively var adjustHeightNormal = Cartesian3.subtract(top, bottom, adjustHeightNormalScratch); Cartesian3.normalize(adjustHeightNormal, adjustHeightNormal); - var distanceForBottom = minHeight - ApproximateTerrainHeights._defaultMinTerrainHeight; + var distanceForBottom = minHeight - WALL_INITIAL_MIN_HEIGHT; var adjustHeightOffset = Cartesian3.multiplyByScalar(adjustHeightNormal, distanceForBottom, adjustHeightOffsetScratch); Cartesian3.add(bottom, adjustHeightOffset, adjustHeightBottom); - var distanceForTop = maxHeight - ApproximateTerrainHeights._defaultMaxTerrainHeight; + var distanceForTop = maxHeight - WALL_INITIAL_MAX_HEIGHT; adjustHeightOffset = Cartesian3.multiplyByScalar(adjustHeightNormal, distanceForTop, adjustHeightOffsetScratch); Cartesian3.add(top, adjustHeightOffset, adjustHeightTop); } From 4aebfa13859221efb049a5dd5bc05ca8a8b8f1d3 Mon Sep 17 00:00:00 2001 From: Kanging Li Date: Mon, 4 Jun 2018 21:51:59 -0400 Subject: [PATCH 22/39] specs for GroundPolylinePrimitive --- .../development/Polylines On Terrain.html | 2 +- Source/Scene/GroundPolylinePrimitive.js | 29 +- Specs/Core/GroundPolylineGeometrySpec.js | 27 + Specs/Scene/GroundPolylinePrimitiveSpec.js | 886 ++++++++++++++++++ Specs/Scene/GroundPrimitiveSpec.js | 2 - 5 files changed, 934 insertions(+), 12 deletions(-) create mode 100644 Specs/Scene/GroundPolylinePrimitiveSpec.js diff --git a/Apps/Sandcastle/gallery/development/Polylines On Terrain.html b/Apps/Sandcastle/gallery/development/Polylines On Terrain.html index a94c26106769..1f47e1e3268c 100644 --- a/Apps/Sandcastle/gallery/development/Polylines On Terrain.html +++ b/Apps/Sandcastle/gallery/development/Polylines On Terrain.html @@ -134,7 +134,7 @@ }); polylineOnTerrainPrimitive = new Cesium.GroundPolylinePrimitive({ - polylineGeometryInstances : [instance1, instance2], + geometryInstances : [instance1, instance2], debugShowShadowVolume : false }); scene.primitives.add(polylineOnTerrainPrimitive); diff --git a/Source/Scene/GroundPolylinePrimitive.js b/Source/Scene/GroundPolylinePrimitive.js index b20a3078e067..3714d7cb0204 100644 --- a/Source/Scene/GroundPolylinePrimitive.js +++ b/Source/Scene/GroundPolylinePrimitive.js @@ -22,6 +22,7 @@ define([ '../Renderer/ShaderProgram', '../Renderer/ShaderSource', '../ThirdParty/when', + './BlendingState', './CullFace', './Material', './PolylineColorAppearance', @@ -52,6 +53,7 @@ define([ ShaderProgram, ShaderSource, when, + BlendingState, CullFace, Material, PolylineColorAppearance, @@ -67,7 +69,7 @@ define([ * Only to be used with GeometryInstances containing GroundPolylineGeometries * * @param {Object} [options] Object with the following properties: - * @param {Array|GeometryInstance} [options.polylineGeometryInstances] GeometryInstances containing GroundPolylineGeometry + * @param {Array|GeometryInstance} [options.geometryInstances] GeometryInstances containing GroundPolylineGeometry * @param {Appearance} [options.appearance] The Appearance used to render the polyline. Defaults to a white color {@link Material} on a {@link PolylineMaterialAppearance}. * @param {Boolean} [options.show=true] Determines if this primitive will be shown. * @param {Boolean} [options.vertexCacheOptimize=false] When true, geometry vertices are optimized for the pre and post-vertex-shader caches. @@ -98,7 +100,7 @@ define([ * * @default undefined */ - this.polylineGeometryInstances = options.polylineGeometryInstances; + this.geometryInstances = options.geometryInstances; var appearance = options.appearance; if (!defined(appearance)) { @@ -171,7 +173,8 @@ define([ this._renderState = RenderState.fromCache({ cull : { enabled : true // prevent double-draw. Geometry is "inverted" (reversed winding order) so we're drawing backfaces. - } + }, + blending : BlendingState.ALPHA_BLEND }); this._renderStateMorph = RenderState.fromCache({ @@ -181,7 +184,8 @@ define([ }, depthTest : { enabled : true - } + }, + blending : BlendingState.ALPHA_BLEND }); } @@ -448,7 +452,7 @@ define([ } groundPolylinePrimitive._spMorph = colorProgramMorph; - if (groundPolylinePrimitive.allowPicking) { + if (groundPolylinePrimitive._primitive.allowPicking) { var vsPick = ShaderSource.createPickVertexShaderSource(vs); vsPick = Primitive._updatePickColorAttribute(vsPick); @@ -511,6 +515,13 @@ define([ }); } groundPolylinePrimitive._spPickMorph = pickProgramMorph; + } else { + groundPolylinePrimitive._spPick = ShaderProgram.fromCache({ + context : context, + vertexShaderSource : vsColor3D, + fragmentShaderSource : fsColor3D, + attributeLocations : attributeLocations + }); } } @@ -526,7 +537,7 @@ define([ var command; var materialUniforms = isPolylineColorAppearance ? {} : material._uniforms; var uniformMap = primitive._batchTable.getUniformMapCallback()(materialUniforms); - var pass = translucent ? Pass.TRANSLUCENT : Pass.OPAQUE; + var pass = Pass.TERRAIN_CLASSIFICATION; for (i = 0; i < length; i++) { var vertexArray = primitive._va[i]; @@ -686,7 +697,7 @@ define([ * @exception {DeveloperError} Appearance and material have a uniform with the same name. */ GroundPolylinePrimitive.prototype.update = function(frameState) { - if (!defined(this._primitive) && !defined(this.polylineGeometryInstances)) { + if (!defined(this._primitive) && !defined(this.geometryInstances)) { return; } @@ -706,7 +717,7 @@ define([ var that = this; var primitiveOptions = this._primitiveOptions; if (!defined(this._primitive)) { - var geometryInstances = isArray(this.polylineGeometryInstances) ? this.polylineGeometryInstances : [this.polylineGeometryInstances]; + var geometryInstances = isArray(this.geometryInstances) ? this.geometryInstances : [this.geometryInstances]; var geometryInstancesLength = geometryInstances.length; // If using PolylineColorAppearance, check if each instance has a color attribute. @@ -749,7 +760,7 @@ define([ that._ready = true; if (that.releaseGeometryInstances) { - that.polylineGeometryInstances = undefined; + that.geometryInstances = undefined; } var error = primitive._error; diff --git a/Specs/Core/GroundPolylineGeometrySpec.js b/Specs/Core/GroundPolylineGeometrySpec.js index 0cb7fc9bccc2..ed3671c0f873 100644 --- a/Specs/Core/GroundPolylineGeometrySpec.js +++ b/Specs/Core/GroundPolylineGeometrySpec.js @@ -232,6 +232,33 @@ defineSuite([ Cartesian3.normalize(approximateExpectedMiterNormal, approximateExpectedMiterNormal); expect(Cartesian3.equalsEpsilon(approximateExpectedMiterNormal, miteredStartNormal, CesiumMath.EPSILON2)).toBe(true); + + // Break miter on loop end + groundPolylineGeometry = new GroundPolylineGeometry({ + positions : Cartesian3.fromDegreesArray([ + 0.01, 0.0, + 0.02, 0.0, + 0.015, CesiumMath.EPSILON7 + ]), + granularity : 0.0, + loop : true + }); + + geometry = GroundPolylineGeometry.createGeometry(groundPolylineGeometry); + + startNormal_and_forwardOffsetZvalues = geometry.attributes.startNormal_and_forwardOffsetZ.values; + endNormal_and_textureCoordinateNormalizationXvalues = geometry.attributes.endNormal_and_textureCoordinateNormalizationX.values; + + // Check normals at loop end + miteredStartNormal = Cartesian3.unpack(startNormal_and_forwardOffsetZvalues, 0); + miteredEndNormal = Cartesian3.unpack(endNormal_and_textureCoordinateNormalizationXvalues, 32 * 2); + + expect(Cartesian3.equalsEpsilon(miteredStartNormal, miteredEndNormal, CesiumMath.EPSILON7)).toBe(true); + + approximateExpectedMiterNormal = new Cartesian3(0.0, 1.0, 0.0); + + Cartesian3.normalize(approximateExpectedMiterNormal, approximateExpectedMiterNormal); + expect(Cartesian3.equalsEpsilon(approximateExpectedMiterNormal, miteredStartNormal, CesiumMath.EPSILON2)).toBe(true); }); it('interpolates long polyline segments', function() { diff --git a/Specs/Scene/GroundPolylinePrimitiveSpec.js b/Specs/Scene/GroundPolylinePrimitiveSpec.js new file mode 100644 index 000000000000..e8a086804bd0 --- /dev/null +++ b/Specs/Scene/GroundPolylinePrimitiveSpec.js @@ -0,0 +1,886 @@ +defineSuite([ + 'Scene/GroundPolylinePrimitive', + 'Core/ApproximateTerrainHeights', + 'Core/Color', + 'Core/ColorGeometryInstanceAttribute', + 'Core/Cartesian3', + 'Core/destroyObject', + 'Core/DistanceDisplayConditionGeometryInstanceAttribute', + 'Core/Ellipsoid', + 'Core/GeometryInstance', + 'Core/GroundPolylineGeometry', + 'Core/Rectangle', + 'Core/RectangleGeometry', + 'Core/ShowGeometryInstanceAttribute', + 'Renderer/Pass', + 'Scene/PerInstanceColorAppearance', + 'Scene/PolylineColorAppearance', + 'Scene/PolylineMaterialAppearance', + 'Scene/Primitive', + 'Specs/createScene', + 'Specs/pollToPromise' + ], function( + GroundPolylinePrimitive, + ApproximateTerrainHeights, + Color, + ColorGeometryInstanceAttribute, + Cartesian3, + destroyObject, + DistanceDisplayConditionGeometryInstanceAttribute, + Ellipsoid, + GeometryInstance, + GroundPolylineGeometry, + Rectangle, + RectangleGeometry, + ShowGeometryInstanceAttribute, + Pass, + PerInstanceColorAppearance, + PolylineColorAppearance, + PolylineMaterialAppearance, + Primitive, + createScene, + pollToPromise) { + 'use strict'; + + var scene; + var context; + + var ellipsoid; + + var depthColor; + var polylineColor; + + var groundPolylineInstance; + var groundPolylinePrimitive; + var depthRectanglePrimitive; + + var positions = Cartesian3.fromDegreesArray([ + 0.01, 0.0, + 0.03, 0.0 + ]); + + var lookPosition = Cartesian3.fromDegrees(0.02, 0.0); + + beforeAll(function() { + scene = createScene(); + scene.postProcessStages.fxaa.enabled = false; + + context = scene.context; + + ellipsoid = Ellipsoid.WGS84; + return GroundPolylinePrimitive.initializeTerrainHeights(); + }); + + afterAll(function() { + scene.destroyForSpecs(); + + // Leave ground primitive uninitialized + GroundPolylinePrimitive._initialized = false; + GroundPolylinePrimitive._initPromise = undefined; + }); + + function MockGlobePrimitive(primitive) { + this._primitive = primitive; + this.pass = Pass.GLOBE; + } + + MockGlobePrimitive.prototype.update = function(frameState) { + var commandList = frameState.commandList; + var startLength = commandList.length; + this._primitive.update(frameState); + + for (var i = startLength; i < commandList.length; ++i) { + var command = commandList[i]; + command.pass = this.pass; + } + }; + + MockGlobePrimitive.prototype.isDestroyed = function() { + return false; + }; + + MockGlobePrimitive.prototype.destroy = function() { + this._primitive.destroy(); + return destroyObject(this); + }; + + beforeEach(function() { + scene.morphTo3D(0); + scene.render(); // clear any afterRender commands + + var depthColorAttribute = ColorGeometryInstanceAttribute.fromColor(new Color(0.0, 0.0, 1.0, 1.0)); + depthColor = depthColorAttribute.value; + var primitive = new Primitive({ + geometryInstances : new GeometryInstance({ + geometry : new RectangleGeometry({ + ellipsoid : ellipsoid, + rectangle : Rectangle.fromDegrees(-1.0, -1.0, 1.0, 1.0) + }), + id : 'depth rectangle', + attributes : { + color : depthColorAttribute + } + }), + appearance : new PerInstanceColorAppearance({ + translucent : false, + flat : true + }), + asynchronous : false + }); + + // wrap rectangle primitive so it gets executed during the globe pass to lay down depth + depthRectanglePrimitive = new MockGlobePrimitive(primitive); + + var colorAttribute = ColorGeometryInstanceAttribute.fromColor(new Color(0.0, 1.0, 1.0, 1.0)); + polylineColor = colorAttribute.value; + groundPolylineInstance = new GeometryInstance({ + geometry : new GroundPolylineGeometry({ + positions : positions, + granularity : 0.0, + width : 1.0, + loop : false, + ellipsoid : ellipsoid + }), + id : 'polyline on terrain', + attributes : { + color : colorAttribute + } + }); + }); + + afterEach(function() { + scene.groundPrimitives.removeAll(); + groundPolylinePrimitive = groundPolylinePrimitive && !groundPolylinePrimitive.isDestroyed() && groundPolylinePrimitive.destroy(); + depthRectanglePrimitive = depthRectanglePrimitive && !depthRectanglePrimitive.isDestroyed() && depthRectanglePrimitive.destroy(); + }); + + it('default constructs', function() { + groundPolylinePrimitive = new GroundPolylinePrimitive(); + expect(groundPolylinePrimitive.geometryInstances).not.toBeDefined(); + expect(groundPolylinePrimitive.appearance instanceof PolylineMaterialAppearance).toBe(true); + expect(groundPolylinePrimitive.show).toEqual(true); + expect(groundPolylinePrimitive.vertexCacheOptimize).toEqual(false); + expect(groundPolylinePrimitive.interleave).toEqual(false); + expect(groundPolylinePrimitive.compressVertices).toEqual(true); + expect(groundPolylinePrimitive.releaseGeometryInstances).toEqual(true); + expect(groundPolylinePrimitive.allowPicking).toEqual(true); + expect(groundPolylinePrimitive.asynchronous).toEqual(true); + expect(groundPolylinePrimitive.debugShowBoundingVolume).toEqual(false); + expect(groundPolylinePrimitive.debugShowShadowVolume).toEqual(false); + }); + + it('constructs with options', function() { + var geometryInstances = []; + + groundPolylinePrimitive = new GroundPolylinePrimitive({ + geometryInstances : geometryInstances, + show : false, + vertexCacheOptimize : true, + interleave : true, + compressVertices : false, + releaseGeometryInstances : false, + allowPicking : false, + asynchronous : false, + debugShowBoundingVolume : true, + debugShowShadowVolume : true + }); + + expect(groundPolylinePrimitive.geometryInstances).toEqual(geometryInstances); + expect(groundPolylinePrimitive.show).toEqual(false); + expect(groundPolylinePrimitive.vertexCacheOptimize).toEqual(true); + expect(groundPolylinePrimitive.interleave).toEqual(true); + expect(groundPolylinePrimitive.compressVertices).toEqual(false); + expect(groundPolylinePrimitive.releaseGeometryInstances).toEqual(false); + expect(groundPolylinePrimitive.allowPicking).toEqual(false); + expect(groundPolylinePrimitive.asynchronous).toEqual(false); + expect(groundPolylinePrimitive.debugShowBoundingVolume).toEqual(true); + expect(groundPolylinePrimitive.debugShowShadowVolume).toEqual(true); + }); + + it('releases geometry instances when releaseGeometryInstances is true', function() { + if (!GroundPolylinePrimitive.isSupported(scene)) { + return; + } + + groundPolylinePrimitive = new GroundPolylinePrimitive({ + geometryInstances : groundPolylineInstance, + releaseGeometryInstances : true, + asynchronous : false + }); + + expect(groundPolylinePrimitive.geometryInstances).toBeDefined(); + scene.groundPrimitives.add(groundPolylinePrimitive); + scene.renderForSpecs(); + expect(groundPolylinePrimitive.geometryInstances).not.toBeDefined(); + }); + + it('does not release geometry instances when releaseGeometryInstances is false', function() { + if (!GroundPolylinePrimitive.isSupported(scene)) { + return; + } + + groundPolylinePrimitive = new GroundPolylinePrimitive({ + geometryInstances : groundPolylineInstance, + releaseGeometryInstances : false, + asynchronous : false + }); + + expect(groundPolylinePrimitive.geometryInstances).toBeDefined(); + scene.groundPrimitives.add(groundPolylinePrimitive); + scene.renderForSpecs(); + expect(groundPolylinePrimitive.geometryInstances).toBeDefined(); + }); + + it('adds afterRender promise to frame state', function() { + if (!GroundPolylinePrimitive.isSupported(scene)) { + return; + } + + groundPolylinePrimitive = new GroundPolylinePrimitive({ + geometryInstances : groundPolylineInstance, + releaseGeometryInstances : false, + asynchronous : false + }); + + scene.groundPrimitives.add(groundPolylinePrimitive); + scene.renderForSpecs(); + + return groundPolylinePrimitive.readyPromise.then(function(param) { + expect(param.ready).toBe(true); + }); + }); + + it('does not render when geometryInstances is undefined', function() { + if (!GroundPolylinePrimitive.isSupported(scene)) { + return; + } + + groundPolylinePrimitive = new GroundPolylinePrimitive({ + geometryInstances : undefined, + asynchronous : false + }); + + var frameState = scene.frameState; + frameState.commandList.length = 0; + + groundPolylinePrimitive.update(frameState); + expect(frameState.commandList.length).toEqual(0); + }); + + it('does not render when show is false', function() { + if (!GroundPolylinePrimitive.isSupported(scene)) { + return; + } + + groundPolylinePrimitive = new GroundPolylinePrimitive({ + geometryInstances : groundPolylineInstance, + asynchronous : false + }); + + var frameState = scene.frameState; + + frameState.commandList.length = 0; + groundPolylinePrimitive.update(frameState); + expect(frameState.afterRender.length).toEqual(1); + + frameState.afterRender[0](); + frameState.commandList.length = 0; + groundPolylinePrimitive.update(frameState); + expect(frameState.commandList.length).toBeGreaterThan(0); + + frameState.commandList.length = 0; + groundPolylinePrimitive.show = false; + groundPolylinePrimitive.update(frameState); + expect(frameState.commandList.length).toEqual(0); + }); + + it('becomes ready when show is false', function() { + if (!GroundPolylinePrimitive.isSupported(scene)) { + return; + } + + groundPolylinePrimitive = scene.groundPrimitives.add(new GroundPolylinePrimitive({ + geometryInstances : groundPolylineInstance + })); + groundPolylinePrimitive.show = false; + + var ready = false; + groundPolylinePrimitive.readyPromise.then(function() { + ready = true; + }); + + return pollToPromise(function() { + scene.render(); + return ready; + }).then(function() { + expect(ready).toEqual(true); + }); + }); + + it('does not render other than for the color or pick pass', function() { + if (!GroundPolylinePrimitive.isSupported(scene)) { + return; + } + + groundPolylinePrimitive = new GroundPolylinePrimitive({ + geometryInstances : groundPolylineInstance, + asynchronous : false + }); + + var frameState = scene.frameState; + frameState.passes.render = false; + frameState.passes.pick = false; + + groundPolylinePrimitive.update(frameState); + expect(frameState.commandList.length).toEqual(0); + }); + + function verifyGroundPolylinePrimitiveRender(primitive, color) { + scene.camera.lookAt(lookPosition, Cartesian3.UNIT_Z); + + scene.groundPrimitives.add(depthRectanglePrimitive); + expect(scene).toRenderAndCall(function(rgba) { + expect(rgba).not.toEqual([0, 0, 0, 255]); + expect(rgba[0]).toEqual(0); + }); + + scene.groundPrimitives.add(primitive); + expect(scene).toRender(color); + } + + it('renders in 3D', function() { + if (!GroundPolylinePrimitive.isSupported(scene)) { + return; + } + + groundPolylinePrimitive = new GroundPolylinePrimitive({ + geometryInstances : groundPolylineInstance, + asynchronous : false, + appearance : new PolylineColorAppearance() + }); + + verifyGroundPolylinePrimitiveRender(groundPolylinePrimitive, polylineColor); + }); + + it('renders in Columbus view when scene3DOnly is false', function() { + if (!GroundPolylinePrimitive.isSupported(scene)) { + return; + } + + groundPolylinePrimitive = new GroundPolylinePrimitive({ + geometryInstances : groundPolylineInstance, + asynchronous : false, + appearance : new PolylineColorAppearance() + }); + + scene.morphToColumbusView(0); + verifyGroundPolylinePrimitiveRender(groundPolylinePrimitive, polylineColor); + }); + + it('renders in 2D when scene3DOnly is false', function() { + if (!GroundPolylinePrimitive.isSupported(scene)) { + return; + } + + groundPolylinePrimitive = new GroundPolylinePrimitive({ + geometryInstances : groundPolylineInstance, + asynchronous : false, + appearance : new PolylineColorAppearance() + }); + + scene.morphTo2D(0); + verifyGroundPolylinePrimitiveRender(groundPolylinePrimitive, polylineColor); + }); + + it('renders batched instances', function() { + if (!GroundPolylinePrimitive.isSupported(scene)) { + return; + } + + var instance1 = new GeometryInstance({ + geometry : new GroundPolylineGeometry({ + positions : positions, + granularity : 0.0, + width : 1.0, + loop : false, + ellipsoid : ellipsoid + }), + id : 'polyline on terrain', + attributes : { + color : ColorGeometryInstanceAttribute.fromColor(new Color(1.0, 1.0, 1.0, 0.5)) + } + }); + + var instance2 = new GeometryInstance({ + geometry : new GroundPolylineGeometry({ + positions : positions, + granularity : 0.0, + width : 1.0, + loop : false, + ellipsoid : ellipsoid + }), + id : 'polyline on terrain', + attributes : { + color : ColorGeometryInstanceAttribute.fromColor(new Color(1.0, 1.0, 1.0, 0.5)) + } + }); + + groundPolylinePrimitive = new GroundPolylinePrimitive({ + geometryInstances : [instance1, instance2], + asynchronous : false, + appearance : new PolylineColorAppearance() + }); + + verifyGroundPolylinePrimitiveRender(groundPolylinePrimitive, [192, 192, 255, 255]); + }); + + it('renders bounding volume with debugShowBoundingVolume', function() { + if (!GroundPolylinePrimitive.isSupported(scene)) { + return; + } + + groundPolylinePrimitive = new GroundPolylinePrimitive({ + geometryInstances : groundPolylineInstance, + asynchronous : false, + appearance : new PolylineColorAppearance(), + debugShowBoundingVolume : true + }); + + scene.groundPrimitives.add(groundPolylinePrimitive); + + scene.camera.lookAt(lookPosition, Cartesian3.UNIT_Z); + expect(scene).toRenderAndCall(function(rgba) { + expect(rgba[1]).toBeGreaterThanOrEqualTo(0); + expect(rgba[1]).toBeGreaterThanOrEqualTo(0); + expect(rgba[2]).toBeGreaterThanOrEqualTo(0); + expect(rgba[3]).toEqual(255); + }); + }); + + it('renders shadow volume with debugShowShadowVolume', function() { + if (!GroundPolylinePrimitive.isSupported(scene)) { + return; + } + + groundPolylinePrimitive = new GroundPolylinePrimitive({ + geometryInstances : groundPolylineInstance, + asynchronous : false, + appearance : new PolylineColorAppearance(), + debugShowShadowVolume : true + }); + + scene.groundPrimitives.add(groundPolylinePrimitive); + + scene.camera.lookAt(lookPosition, Cartesian3.UNIT_Z); + expect(scene).toRenderAndCall(function(rgba) { + expect(rgba[1]).toBeGreaterThanOrEqualTo(0); + expect(rgba[1]).toBeGreaterThanOrEqualTo(0); + expect(rgba[2]).toBeGreaterThanOrEqualTo(0); + expect(rgba[3]).toEqual(255); + }); + }); + + it('get per instance attributes', function() { + if (!GroundPolylinePrimitive.isSupported(scene)) { + return; + } + + groundPolylinePrimitive = new GroundPolylinePrimitive({ + geometryInstances : groundPolylineInstance, + asynchronous : false, + appearance : new PolylineColorAppearance() + }); + + verifyGroundPolylinePrimitiveRender(groundPolylinePrimitive, polylineColor); + + var attributes = groundPolylinePrimitive.getGeometryInstanceAttributes('polyline on terrain'); + expect(attributes.color).toBeDefined(); + }); + + it('modify color instance attribute', function() { + if (!GroundPolylinePrimitive.isSupported(scene)) { + return; + } + + groundPolylinePrimitive = new GroundPolylinePrimitive({ + geometryInstances : groundPolylineInstance, + asynchronous : false, + appearance : new PolylineColorAppearance() + }); + + verifyGroundPolylinePrimitiveRender(groundPolylinePrimitive, polylineColor); + + // Remove so it can be re-added, but don't destroy. + scene.groundPrimitives.destroyPrimitives = false; + scene.groundPrimitives.removeAll(); + scene.groundPrimitives.destroyPrimitives = true; + + var newColor = [255, 255, 255, 255]; + var attributes = groundPolylinePrimitive.getGeometryInstanceAttributes('polyline on terrain'); + expect(attributes.color).toBeDefined(); + attributes.color = newColor; + + verifyGroundPolylinePrimitiveRender(groundPolylinePrimitive, newColor); + }); + + it('adds width instance attribute', function() { + if (!GroundPolylinePrimitive.isSupported(scene)) { + return; + } + + groundPolylinePrimitive = new GroundPolylinePrimitive({ + geometryInstances : groundPolylineInstance, + asynchronous : false, + appearance : new PolylineColorAppearance() + }); + + verifyGroundPolylinePrimitiveRender(groundPolylinePrimitive, polylineColor); + + scene.groundPrimitives.destroyPrimitives = false; + scene.groundPrimitives.removeAll(); + scene.groundPrimitives.destroyPrimitives = true; + + var attributes = groundPolylinePrimitive.getGeometryInstanceAttributes('polyline on terrain'); + expect(attributes.width).toBeDefined(); + attributes.width = [0]; + + verifyGroundPolylinePrimitiveRender(groundPolylinePrimitive, depthColor); + }); + + it('modify show instance attribute', function() { + if (!GroundPolylinePrimitive.isSupported(scene)) { + return; + } + + groundPolylineInstance.attributes.show = new ShowGeometryInstanceAttribute(true); + + groundPolylinePrimitive = new GroundPolylinePrimitive({ + geometryInstances : groundPolylineInstance, + asynchronous : false, + appearance : new PolylineColorAppearance() + }); + + verifyGroundPolylinePrimitiveRender(groundPolylinePrimitive, polylineColor); + + scene.groundPrimitives.destroyPrimitives = false; + scene.groundPrimitives.removeAll(); + scene.groundPrimitives.destroyPrimitives = true; + + var attributes = groundPolylinePrimitive.getGeometryInstanceAttributes('polyline on terrain'); + expect(attributes.show).toBeDefined(); + attributes.show = [0]; + + verifyGroundPolylinePrimitiveRender(groundPolylinePrimitive, depthColor); + }); + + it('renders with distance display condition per instance attribute', function() { + if (!context.floatingPointTexture) { + return; + } + + if (!GroundPolylinePrimitive.isSupported(scene)) { + return; + } + + var near = 10000.0; + var far = 1000000.0; + + var geometryInstance = new GeometryInstance({ + geometry : new GroundPolylineGeometry({ + positions : positions, + granularity : 0.0, + width : 1.0, + loop : false, + ellipsoid : ellipsoid + }), + id : 'polyline on terrain', + attributes : { + distanceDisplayCondition : new DistanceDisplayConditionGeometryInstanceAttribute(near, far) + } + }); + + groundPolylinePrimitive = new GroundPolylinePrimitive({ + geometryInstances : geometryInstance, + asynchronous : false + }); + + scene.groundPrimitives.add(depthRectanglePrimitive); + scene.groundPrimitives.add(groundPolylinePrimitive); + scene.camera.lookAt(lookPosition, Cartesian3.UNIT_Z); + scene.renderForSpecs(); + + var boundingSphere = groundPolylinePrimitive.getGeometryInstanceAttributes('polyline on terrain').boundingSphere; + var center = boundingSphere.center; + var radius = boundingSphere.radius; + + scene.camera.lookAt(center, new Cartesian3(0.0, 0.0, radius)); + expect(scene).toRender(depthColor); + + scene.camera.lookAt(center, new Cartesian3(0.0, 0.0, radius + near + 1.0)); + expect(scene).not.toRender(depthColor); + + scene.camera.lookAt(center, new Cartesian3(0.0, 0.0, radius + far + 1.0)); + expect(scene).toRender(depthColor); + }); + + it('getGeometryInstanceAttributes returns same object each time', function() { + if (!GroundPolylinePrimitive.isSupported(scene)) { + return; + } + + groundPolylineInstance.attributes.show = new ShowGeometryInstanceAttribute(true); + + groundPolylinePrimitive = new GroundPolylinePrimitive({ + geometryInstances : groundPolylineInstance, + asynchronous : false, + appearance : new PolylineColorAppearance() + }); + + verifyGroundPolylinePrimitiveRender(groundPolylinePrimitive, polylineColor); + + var attributes = groundPolylinePrimitive.getGeometryInstanceAttributes('polyline on terrain'); + var attributes2 = groundPolylinePrimitive.getGeometryInstanceAttributes('polyline on terrain'); + expect(attributes).toBe(attributes2); + }); + + it('picking in 3D', function() { + if (!GroundPolylinePrimitive.isSupported(scene)) { + return; + } + + groundPolylinePrimitive = new GroundPolylinePrimitive({ + geometryInstances : groundPolylineInstance, + asynchronous : false, + appearance : new PolylineColorAppearance() + }); + + verifyGroundPolylinePrimitiveRender(groundPolylinePrimitive, polylineColor); + + expect(scene).toPickAndCall(function(result) { + expect(result.id).toEqual('polyline on terrain'); + }); + }); + + it('picking in 2D', function() { + if (!GroundPolylinePrimitive.isSupported(scene)) { + return; + } + + groundPolylinePrimitive = new GroundPolylinePrimitive({ + geometryInstances : groundPolylineInstance, + asynchronous : false, + appearance : new PolylineColorAppearance() + }); + + scene.morphTo2D(0); + verifyGroundPolylinePrimitiveRender(groundPolylinePrimitive, polylineColor); + + expect(scene).toPickAndCall(function(result) { + expect(result.id).toEqual('polyline on terrain'); + }); + }); + + it('picking in Columbus View', function() { + if (!GroundPolylinePrimitive.isSupported(scene)) { + return; + } + + groundPolylinePrimitive = new GroundPolylinePrimitive({ + geometryInstances : groundPolylineInstance, + asynchronous : false, + appearance : new PolylineColorAppearance() + }); + + scene.morphToColumbusView(0); + verifyGroundPolylinePrimitiveRender(groundPolylinePrimitive, polylineColor); + + expect(scene).toPickAndCall(function(result) { + expect(result.id).toEqual('polyline on terrain'); + }); + }); + + it('does not pick when allowPicking is false', function() { + if (!GroundPolylinePrimitive.isSupported(scene)) { + return; + } + + groundPolylinePrimitive = new GroundPolylinePrimitive({ + geometryInstances : groundPolylineInstance, + asynchronous : false, + allowPicking : false, + appearance : new PolylineColorAppearance() + }); + + verifyGroundPolylinePrimitiveRender(groundPolylinePrimitive, polylineColor); + + expect(scene).notToPick(); + }); + + it('update throws when batched instance colors are missing', function() { + if (!GroundPolylinePrimitive.isSupported(scene)) { + return; + } + groundPolylinePrimitive = new GroundPolylinePrimitive({ + geometryInstances : new GeometryInstance({ + geometry : new GroundPolylineGeometry({ + positions : positions + }) + }), + appearance : new PolylineColorAppearance(), + asynchronous : false + }); + + expect(function() { + verifyGroundPolylinePrimitiveRender(groundPolylinePrimitive, polylineColor); + }).toThrowDeveloperError(); + }); + + it('setting per instance attribute throws when value is undefined', function() { + if (!GroundPolylinePrimitive.isSupported(scene)) { + return; + } + + groundPolylinePrimitive = new GroundPolylinePrimitive({ + geometryInstances : groundPolylineInstance, + asynchronous : false, + appearance : new PolylineColorAppearance() + }); + + verifyGroundPolylinePrimitiveRender(groundPolylinePrimitive, polylineColor); + + var attributes = groundPolylinePrimitive.getGeometryInstanceAttributes('polyline on terrain'); + + expect(function() { + attributes.color = undefined; + }).toThrowDeveloperError(); + }); + + it('can disable picking when asynchronous', function() { + if (!GroundPolylinePrimitive.isSupported(scene)) { + return; + } + + groundPolylinePrimitive = new GroundPolylinePrimitive({ + geometryInstances : groundPolylineInstance, + asynchronous : true, + allowPicking : false, + appearance : new PolylineColorAppearance() + }); + + var frameState = scene.frameState; + + return pollToPromise(function() { + groundPolylinePrimitive.update(frameState); + for (var i = 0; i < frameState.afterRender.length; ++i) { + frameState.afterRender[i](); + } + return groundPolylinePrimitive.ready; + }).then(function() { + var attributes = groundPolylinePrimitive.getGeometryInstanceAttributes('polyline on terrain'); + expect(function() { + attributes.color = undefined; + }).toThrowDeveloperError(); + }); + }); + + it('getGeometryInstanceAttributes throws without id', function() { + if (!GroundPolylinePrimitive.isSupported(scene)) { + return; + } + + groundPolylinePrimitive = new GroundPolylinePrimitive({ + geometryInstances : groundPolylineInstance, + asynchronous : false, + appearance : new PolylineColorAppearance() + }); + + verifyGroundPolylinePrimitiveRender(groundPolylinePrimitive, polylineColor); + + expect(function() { + groundPolylinePrimitive.getGeometryInstanceAttributes(); + }).toThrowDeveloperError(); + }); + + it('getGeometryInstanceAttributes returns undefined if id does not exist', function() { + if (!GroundPolylinePrimitive.isSupported(scene)) { + return; + } + + groundPolylinePrimitive = new GroundPolylinePrimitive({ + geometryInstances : groundPolylineInstance, + asynchronous : false, + appearance : new PolylineColorAppearance() + }); + + expect(function() { + groundPolylinePrimitive.getGeometryInstanceAttributes('unknown'); + }).toThrowDeveloperError(); + }); + + it('isDestroyed', function() { + groundPolylinePrimitive = new GroundPolylinePrimitive(); + expect(groundPolylinePrimitive.isDestroyed()).toEqual(false); + groundPolylinePrimitive.destroy(); + expect(groundPolylinePrimitive.isDestroyed()).toEqual(true); + }); + + it('renders when using asynchronous pipeline', function() { + if (!GroundPolylinePrimitive.isSupported(scene)) { + return; + } + + groundPolylinePrimitive = new GroundPolylinePrimitive({ + geometryInstances : groundPolylineInstance, + asynchronous : true, + appearance : new PolylineColorAppearance() + }); + + var frameState = scene.frameState; + + return pollToPromise(function() { + groundPolylinePrimitive.update(frameState); + for (var i = 0; i < frameState.afterRender.length; ++i) { + frameState.afterRender[i](); + } + return groundPolylinePrimitive.ready; + }).then(function() { + verifyGroundPolylinePrimitiveRender(groundPolylinePrimitive, polylineColor); + }); + }); + + it('destroy before asynchronous pipeline is complete', function() { + groundPolylinePrimitive = new GroundPolylinePrimitive({ + geometryInstances : groundPolylineInstance, + asynchronous : true, + appearance : new PolylineColorAppearance() + }); + + var frameState = scene.frameState; + groundPolylinePrimitive.update(frameState); + + groundPolylinePrimitive.destroy(); + expect(groundPolylinePrimitive.isDestroyed()).toEqual(true); + }); + + it('creating a synchronous primitive throws if initializeTerrainHeights wasn\'t called', function() { + // Make it seem like initializeTerrainHeights was never called + var initPromise = GroundPolylinePrimitive._initPromise; + GroundPolylinePrimitive._initPromise = undefined; + GroundPolylinePrimitive._initialized = false; + + groundPolylinePrimitive = new GroundPolylinePrimitive({ + geometryInstances : groundPolylineInstance, + asynchronous : false + }); + + if (GroundPolylinePrimitive.isSupported(scene)) { + expect(function() { + groundPolylinePrimitive.update(scene.frameState); + }).toThrowDeveloperError(); + } + + // Set back to initialized state + GroundPolylinePrimitive._initPromise = initPromise; + GroundPolylinePrimitive._initialized = true; + }); +}, 'WebGL'); diff --git a/Specs/Scene/GroundPrimitiveSpec.js b/Specs/Scene/GroundPrimitiveSpec.js index bc5effd5f25c..dff18f65ca86 100644 --- a/Specs/Scene/GroundPrimitiveSpec.js +++ b/Specs/Scene/GroundPrimitiveSpec.js @@ -79,8 +79,6 @@ defineSuite([ // Leave ground primitive uninitialized GroundPrimitive._initialized = false; GroundPrimitive._initPromise = undefined; - ApproximateTerrainHeights._initPromise = undefined; - ApproximateTerrainHeights._terrainHeights = undefined; }); function MockGlobePrimitive(primitive) { From 6ac0dd5e84fec0aec59e9bb58f05f528cd2b16c1 Mon Sep 17 00:00:00 2001 From: Kangning Li Date: Tue, 5 Jun 2018 11:27:39 -0400 Subject: [PATCH 23/39] update Sandcastle example with Z-indexing demonstration --- .../development/Polylines On Terrain.html | 120 ++++++++++++------ Source/Scene/GroundPolylinePrimitive.js | 1 + 2 files changed, 85 insertions(+), 36 deletions(-) diff --git a/Apps/Sandcastle/gallery/development/Polylines On Terrain.html b/Apps/Sandcastle/gallery/development/Polylines On Terrain.html index 1f47e1e3268c..458b2a48c75a 100644 --- a/Apps/Sandcastle/gallery/development/Polylines On Terrain.html +++ b/Apps/Sandcastle/gallery/development/Polylines On Terrain.html @@ -46,10 +46,17 @@ terrainProvider: worldTerrain }); -var selectedId = 'polyline1'; +if (!Cesium.GroundPolylinePrimitive.isSupported(viewer.scene)) { + throw new Cesium.RuntimeError('Polylines on terrain are not supported on this platform.'); +} + +var polylineIds = ['polyline1', 'polyline2']; +var groundPrimitiveId = 'ground primitive'; +var selectedId = polylineIds[0]; var polylineOnTerrainPrimitive; +var groundPrimitiveOnTop = false; -var lineWidth = 16.0; +var lineWidth = 4.0; var viewModel = { lineWidth : lineWidth }; @@ -72,16 +79,37 @@ var scene = viewer.scene; +// Add a Rectangle GroundPrimitive to demonstrate Z-indexing with GroundPrimitives +var rectangleGroundPrimitive = scene.groundPrimitives.add(new Cesium.GroundPrimitive({ + geometryInstances : new Cesium.GeometryInstance({ + geometry : new Cesium.RectangleGeometry({ + rectangle : Cesium.Rectangle.fromDegrees(-112.1340164450331, 36.05494287836128, -112.0840164450331, 36.10494287836128), + vertexFormat : Cesium.EllipsoidSurfaceAppearance.VERTEX_FORMAT + }), + id : groundPrimitiveId + }), + appearance : new Cesium.EllipsoidSurfaceAppearance({ + aboveGround : false, + material : Cesium.Material.fromType('Color') + }), + classificationType : Cesium.ClassificationType.TERRAIN +})); + var leftHandler = new Cesium.ScreenSpaceEventHandler(scene.canvas); leftHandler.setInputAction(function(movement) { var pickedObject = viewer.scene.pick(movement.position); if (Cesium.defined(pickedObject)) { console.log(pickedObject.id); - selectedId = pickedObject.id; + // If picked the ground primitive, don't do anything + if (pickedObject.id === groundPrimitiveId) { + return; + } else { + selectedId = pickedObject.id; - // Sync line width in toolbar with selected - var attributes = polylineOnTerrainPrimitive.getGeometryInstanceAttributes(selectedId); - viewModel.lineWidth = attributes.width[0]; + // Sync line width in toolbar with selected + var attributes = polylineOnTerrainPrimitive.getGeometryInstanceAttributes(selectedId); + viewModel.lineWidth = attributes.width[0]; + } } else { selectedId = undefined; } @@ -107,37 +135,39 @@ -111.69141601804614, 36.05128770351902 ]); -var instance1 = new Cesium.GeometryInstance({ - geometry : new Cesium.GroundPolylineGeometry({ - positions : polylinePositions, - loop : false, - width : 16.0 - }), - id : 'polyline1', - attributes : { - show : new Cesium.ShowGeometryInstanceAttribute(), - color : Cesium.ColorGeometryInstanceAttribute.fromColor(Cesium.Color.fromCssColorString('green').withAlpha(0.7)) - } -}); +function createPolylines(debugShowShadowVolume) { + var instance1 = new Cesium.GeometryInstance({ + geometry : new Cesium.GroundPolylineGeometry({ + positions : polylinePositions, + loop : false, + width : 4.0 + }), + id : polylineIds[0], + attributes : { + show : new Cesium.ShowGeometryInstanceAttribute(), + color : Cesium.ColorGeometryInstanceAttribute.fromColor(Cesium.Color.fromCssColorString('green').withAlpha(0.7)) + } + }); -var instance2 = new Cesium.GeometryInstance({ - geometry : new Cesium.GroundPolylineGeometry({ - positions : loopPositions, - loop : true, - width : 8.0 - }), - id : 'polyline2', - attributes : { - show : new Cesium.ShowGeometryInstanceAttribute(), - color : Cesium.ColorGeometryInstanceAttribute.fromColor(Cesium.Color.fromCssColorString('#67ADDF').withAlpha(0.7)) - } -}); + var instance2 = new Cesium.GeometryInstance({ + geometry : new Cesium.GroundPolylineGeometry({ + positions : loopPositions, + loop : true, + width : 8.0 + }), + id : polylineIds[1], + attributes : { + show : new Cesium.ShowGeometryInstanceAttribute(), + color : Cesium.ColorGeometryInstanceAttribute.fromColor(Cesium.Color.fromCssColorString('#67ADDF').withAlpha(0.7)) + } + }); -polylineOnTerrainPrimitive = new Cesium.GroundPolylinePrimitive({ - geometryInstances : [instance1, instance2], - debugShowShadowVolume : false -}); -scene.primitives.add(polylineOnTerrainPrimitive); + polylineOnTerrainPrimitive = new Cesium.GroundPolylinePrimitive({ + geometryInstances : [instance1, instance2], + debugShowShadowVolume : debugShowShadowVolume + }); + scene.groundPrimitives.add(polylineOnTerrainPrimitive); +} function applyPerInstanceColor() { polylineOnTerrainPrimitive.appearance = new Cesium.PolylineColorAppearance(); @@ -145,7 +175,7 @@ function applyColorMaterial() { polylineOnTerrainPrimitive.appearance = new Cesium.PolylineMaterialAppearance({ material : Cesium.Material.fromType('Color', { - color : new Cesium.Color(1.0, 0.0, 1.0, 1.0) + color : new Cesium.Color(0.7, 0.0, 1.0, 1.0) }) }); } @@ -190,6 +220,23 @@ } }); +Sandcastle.addToolbarButton('Toggle debugShowShadowVolume', function() { + var debugShowShadowVolume = !polylineOnTerrainPrimitive.debugShowShadowVolume; + var appearance = polylineOnTerrainPrimitive.appearance; + scene.groundPrimitives.remove(polylineOnTerrainPrimitive); + createPolylines(debugShowShadowVolume); + polylineOnTerrainPrimitive.appearance = appearance; +}); + +Sandcastle.addToolbarButton('Toggle z-index', function() { + if (groundPrimitiveOnTop) { + scene.groundPrimitives.raiseToTop(polylineOnTerrainPrimitive); + } else { + scene.groundPrimitives.lowerToBottom(polylineOnTerrainPrimitive); + } + groundPrimitiveOnTop = !groundPrimitiveOnTop; +}); + function lookAt() { viewer.camera.lookAt(polylinePositions[1], new Cesium.Cartesian3(50000.0, 50000.0, 50000.0)); viewer.camera.lookAtTransform(Cesium.Matrix4.IDENTITY); @@ -199,6 +246,7 @@ lookAt(); }); +createPolylines(false); applyPerInstanceColor(); lookAt(); diff --git a/Source/Scene/GroundPolylinePrimitive.js b/Source/Scene/GroundPolylinePrimitive.js index 3714d7cb0204..91950e80be5c 100644 --- a/Source/Scene/GroundPolylinePrimitive.js +++ b/Source/Scene/GroundPolylinePrimitive.js @@ -140,6 +140,7 @@ define([ */ this.debugShowBoundingVolume = defaultValue(options.debugShowBoundingVolume, false); + // Shadow volume is shown by removing a discard in the shader, so this isn't toggleable. this._debugShowShadowVolume = defaultValue(options.debugShowShadowVolume, false); this._primitiveOptions = { From 5ee4a4744ff1bbee88b13c61fecfd3c464ad25c5 Mon Sep 17 00:00:00 2001 From: Kangning Li Date: Tue, 5 Jun 2018 12:26:32 -0400 Subject: [PATCH 24/39] additional specs for GroundPolylinePrimitive, fix eslint errors --- .../development/Polylines On Terrain.html | 4 +- Source/Scene/GroundPolylinePrimitive.js | 14 ++- Specs/Scene/GroundPolylinePrimitiveSpec.js | 86 +++++++++++++++++-- 3 files changed, 86 insertions(+), 18 deletions(-) diff --git a/Apps/Sandcastle/gallery/development/Polylines On Terrain.html b/Apps/Sandcastle/gallery/development/Polylines On Terrain.html index 458b2a48c75a..714121e15252 100644 --- a/Apps/Sandcastle/gallery/development/Polylines On Terrain.html +++ b/Apps/Sandcastle/gallery/development/Polylines On Terrain.html @@ -101,9 +101,7 @@ if (Cesium.defined(pickedObject)) { console.log(pickedObject.id); // If picked the ground primitive, don't do anything - if (pickedObject.id === groundPrimitiveId) { - return; - } else { + if (pickedObject.id !== groundPrimitiveId) { selectedId = pickedObject.id; // Sync line width in toolbar with selected diff --git a/Source/Scene/GroundPolylinePrimitive.js b/Source/Scene/GroundPolylinePrimitive.js index 91950e80be5c..5d649f113dff 100644 --- a/Source/Scene/GroundPolylinePrimitive.js +++ b/Source/Scene/GroundPolylinePrimitive.js @@ -625,13 +625,7 @@ define([ function updateAndQueueCommands(groundPolylinePrimitive, frameState, colorCommands, pickCommands, modelMatrix, cull, debugShowBoundingVolume) { var primitive = groundPolylinePrimitive._primitive; - //>>includeStart('debug', pragmas.debug); - if (frameState.mode !== SceneMode.SCENE3D && !Matrix4.equals(modelMatrix, Matrix4.IDENTITY)) { - throw new DeveloperError('Primitive.modelMatrix is only supported in 3D mode.'); - } - //>>includeEnd('debug'); - - Primitive._updateBoundingVolumes(primitive, frameState, modelMatrix); + Primitive._updateBoundingVolumes(primitive, frameState, modelMatrix); // Expected to be identity - GroundPrimitives don't support other model matrices var boundingSpheres; if (frameState.mode === SceneMode.SCENE3D) { @@ -720,11 +714,13 @@ define([ if (!defined(this._primitive)) { var geometryInstances = isArray(this.geometryInstances) ? this.geometryInstances : [this.geometryInstances]; var geometryInstancesLength = geometryInstances.length; + var attributes; // If using PolylineColorAppearance, check if each instance has a color attribute. if (this.appearance instanceof PolylineColorAppearance) { for (i = 0; i < geometryInstancesLength; ++i) { - if (!defined(geometryInstances[i].attributes.color)) { + attributes = geometryInstances[i].attributes; + if (!defined(attributes) || !defined(attributes.color)) { throw new DeveloperError('All GeometryInstances must have color attributes to use PolylineColorAppearance with GroundPolylinePrimitive.'); } } @@ -733,7 +729,7 @@ define([ // Automatically create line width attributes for (i = 0; i < geometryInstancesLength; ++i) { var geometryInstance = geometryInstances[i]; - var attributes = geometryInstance.attributes; + attributes = geometryInstance.attributes; if (!defined(attributes.width)) { attributes.width = new GeometryInstanceAttribute({ componentDatatype : ComponentDatatype.UNSIGNED_BYTE, diff --git a/Specs/Scene/GroundPolylinePrimitiveSpec.js b/Specs/Scene/GroundPolylinePrimitiveSpec.js index e8a086804bd0..cd01b5c97093 100644 --- a/Specs/Scene/GroundPolylinePrimitiveSpec.js +++ b/Specs/Scene/GroundPolylinePrimitiveSpec.js @@ -49,6 +49,7 @@ defineSuite([ var depthColor; var polylineColor; + var polylineColorAttribute; var groundPolylineInstance; var groundPolylinePrimitive; @@ -108,8 +109,8 @@ defineSuite([ scene.morphTo3D(0); scene.render(); // clear any afterRender commands - var depthColorAttribute = ColorGeometryInstanceAttribute.fromColor(new Color(0.0, 0.0, 1.0, 1.0)); - depthColor = depthColorAttribute.value; + var depthpolylineColorAttribute = ColorGeometryInstanceAttribute.fromColor(new Color(0.0, 0.0, 1.0, 1.0)); + depthColor = depthpolylineColorAttribute.value; var primitive = new Primitive({ geometryInstances : new GeometryInstance({ geometry : new RectangleGeometry({ @@ -118,7 +119,7 @@ defineSuite([ }), id : 'depth rectangle', attributes : { - color : depthColorAttribute + color : depthpolylineColorAttribute } }), appearance : new PerInstanceColorAppearance({ @@ -131,8 +132,8 @@ defineSuite([ // wrap rectangle primitive so it gets executed during the globe pass to lay down depth depthRectanglePrimitive = new MockGlobePrimitive(primitive); - var colorAttribute = ColorGeometryInstanceAttribute.fromColor(new Color(0.0, 1.0, 1.0, 1.0)); - polylineColor = colorAttribute.value; + polylineColorAttribute = ColorGeometryInstanceAttribute.fromColor(new Color(0.0, 1.0, 1.0, 1.0)); + polylineColor = polylineColorAttribute.value; groundPolylineInstance = new GeometryInstance({ geometry : new GroundPolylineGeometry({ positions : positions, @@ -143,7 +144,7 @@ defineSuite([ }), id : 'polyline on terrain', attributes : { - color : colorAttribute + color : polylineColorAttribute } }); }); @@ -392,6 +393,40 @@ defineSuite([ verifyGroundPolylinePrimitiveRender(groundPolylinePrimitive, polylineColor); }); + it('renders during morph when scene3DOnly is false', function() { + if (!GroundPolylinePrimitive.isSupported(scene)) { + return; + } + + groundPolylinePrimitive = new GroundPolylinePrimitive({ + geometryInstances : new GeometryInstance({ + geometry : new GroundPolylineGeometry({ + positions : Cartesian3.fromDegreesArray([ + -30, 0.0, + 30, 0.0 + ]), + granularity : 0.0, + width : 1000.0, + loop : false, + ellipsoid : ellipsoid + }), + attributes : { + color : polylineColorAttribute + } + }), + asynchronous : false, + appearance : new PolylineColorAppearance() + }); + + // Morph to 2D first because 3D -> 2D/CV morph is difficult in single-pixel + scene.morphTo2D(0); + scene.render(); + + scene.morphToColumbusView(1); + verifyGroundPolylinePrimitiveRender(groundPolylinePrimitive, polylineColor); + scene.completeMorph(); + }); + it('renders batched instances', function() { if (!GroundPolylinePrimitive.isSupported(scene)) { return; @@ -699,6 +734,45 @@ defineSuite([ }); }); + it('picking in Morph', function() { + if (!GroundPolylinePrimitive.isSupported(scene)) { + return; + } + + groundPolylinePrimitive = new GroundPolylinePrimitive({ + geometryInstances : new GeometryInstance({ + geometry : new GroundPolylineGeometry({ + positions : Cartesian3.fromDegreesArray([ + -30, 0.0, + 30, 0.0 + ]), + granularity : 0.0, + width : 1000.0, + loop : false, + ellipsoid : ellipsoid + }), + attributes : { + color : polylineColorAttribute + }, + id : 'big polyline on terrain' + }), + asynchronous : false, + appearance : new PolylineColorAppearance() + }); + + // Morph to 2D first because 3D -> 2D/CV morph is difficult in single-pixel + scene.morphTo2D(0); + scene.render(); + + scene.morphToColumbusView(1); + verifyGroundPolylinePrimitiveRender(groundPolylinePrimitive, polylineColor); + + expect(scene).toPickAndCall(function(result) { + expect(result.id).toEqual('big polyline on terrain'); + }); + scene.completeMorph(); + }); + it('does not pick when allowPicking is false', function() { if (!GroundPolylinePrimitive.isSupported(scene)) { return; From 436631aece2b5c5af1cb2e12d978f51a5209954d Mon Sep 17 00:00:00 2001 From: Kangning Li Date: Thu, 7 Jun 2018 13:56:29 -0400 Subject: [PATCH 25/39] reduce overdraw for tight corners --- Source/Core/GroundPolylineGeometry.js | 4 +- Source/Shaders/PolylineShadowVolumeFS.glsl | 52 +++++++++----- Source/Shaders/PolylineShadowVolumeVS.glsl | 80 +++++++++++++--------- 3 files changed, 85 insertions(+), 51 deletions(-) diff --git a/Source/Core/GroundPolylineGeometry.js b/Source/Core/GroundPolylineGeometry.js index 3cfe3b4266b5..8c272c62d596 100644 --- a/Source/Core/GroundPolylineGeometry.js +++ b/Source/Core/GroundPolylineGeometry.js @@ -51,8 +51,8 @@ define([ var PROJECTIONS = [GeographicProjection, WebMercatorProjection]; var PROJECTION_COUNT = PROJECTIONS.length; - var MITER_BREAK_SMALL = Math.cos(CesiumMath.toRadians(30)); - var MITER_BREAK_LARGE = Math.cos(CesiumMath.toRadians(150)); + var MITER_BREAK_SMALL = Math.cos(CesiumMath.toRadians(1)); + var MITER_BREAK_LARGE = Math.cos(CesiumMath.toRadians(179)); // Initial heights for constructing the wall. // Keeping WALL_INITIAL_MIN_HEIGHT near the ellipsoid surface helps diff --git a/Source/Shaders/PolylineShadowVolumeFS.glsl b/Source/Shaders/PolylineShadowVolumeFS.glsl index b6fda8bb1dfa..2f70435aaabd 100644 --- a/Source/Shaders/PolylineShadowVolumeFS.glsl +++ b/Source/Shaders/PolylineShadowVolumeFS.glsl @@ -2,16 +2,14 @@ #extension GL_EXT_frag_depth : enable #endif -varying vec4 v_startPlaneEC; +varying vec4 v_startPlaneEC_lengthHalfWidth; varying vec4 v_endPlaneEC; varying vec4 v_rightPlaneEC; -varying vec3 v_forwardDirectionEC; -varying vec3 v_texcoordNormalization_and_halfWidth; +varying vec4 v_ecEnd_and_ecStart_X; +varying vec4 v_texcoordNormalization_and_ecStart_YZ; #ifdef PER_INSTANCE_COLOR varying vec4 v_color; -#else -varying vec2 v_alignedPlaneDistances; #endif float rayPlaneDistanceUnsafe(vec3 origin, vec3 direction, vec3 planeNormal, float planeDistance) { @@ -22,6 +20,8 @@ float rayPlaneDistanceUnsafe(vec3 origin, vec3 direction, vec3 planeNormal, floa void main(void) { float logDepthOrDepth = czm_unpackDepth(texture2D(czm_globeDepthTexture, gl_FragCoord.xy / czm_viewport.zw)); + vec3 ecStart = vec3(v_ecEnd_and_ecStart_X.w, v_texcoordNormalization_and_ecStart_YZ.zw); + vec3 forwardDirection = normalize(v_ecEnd_and_ecStart_X.xyz - ecStart); // Discard for sky bool shouldDiscard = logDepthOrDepth == 0.0; @@ -29,15 +29,37 @@ void main(void) vec4 eyeCoordinate = czm_windowToEyeCoordinates(gl_FragCoord.xy, logDepthOrDepth); eyeCoordinate /= eyeCoordinate.w; - float halfMaxWidth = v_texcoordNormalization_and_halfWidth.z * czm_metersPerPixel(eyeCoordinate); + float halfWidth = length(v_startPlaneEC_lengthHalfWidth.xyz); + float halfMaxWidth = halfWidth * czm_metersPerPixel(eyeCoordinate); // Check distance of the eye coordinate against the right-facing plane - float width = czm_planeDistance(v_rightPlaneEC, eyeCoordinate.xyz); + float widthwiseDistance = czm_planeDistance(v_rightPlaneEC, eyeCoordinate.xyz); - // Check distance of the eye coordinate against the forward-facing plane - float distanceFromStart = rayPlaneDistanceUnsafe(eyeCoordinate.xyz, -v_forwardDirectionEC, v_startPlaneEC.xyz, v_startPlaneEC.w); - float distanceFromEnd = rayPlaneDistanceUnsafe(eyeCoordinate.xyz, v_forwardDirectionEC, v_endPlaneEC.xyz, v_endPlaneEC.w); + // Check distance of the eye coordinate against the mitering planes + vec3 startPlaneNormal = v_startPlaneEC_lengthHalfWidth.xyz / halfWidth; + float distanceFromStart = rayPlaneDistanceUnsafe(eyeCoordinate.xyz, -forwardDirection, startPlaneNormal, v_startPlaneEC_lengthHalfWidth.w); + float distanceFromEnd = rayPlaneDistanceUnsafe(eyeCoordinate.xyz, forwardDirection, v_endPlaneEC.xyz, v_endPlaneEC.w); - shouldDiscard = shouldDiscard || (abs(width) > halfMaxWidth || distanceFromStart < 0.0 || distanceFromEnd < 0.0); + shouldDiscard = shouldDiscard || (abs(widthwiseDistance) > halfMaxWidth || distanceFromStart < 0.0 || distanceFromEnd < 0.0); + + // Check distance of the eye coordinate against start and end planes with normals in the right plane. + // For computing unskewed linear texture coordinate and for clipping extremely pointy miters + + // aligned plane: cross the right plane normal with miter plane normal, then cross the result with right again to point it more "forward" + vec4 alignedPlane; + + // start aligned plane + alignedPlane.xyz = cross(v_rightPlaneEC.xyz, startPlaneNormal); + alignedPlane.xyz = cross(alignedPlane.xyz, v_rightPlaneEC.xyz); + alignedPlane.w = -dot(alignedPlane.xyz, ecStart); + distanceFromStart = rayPlaneDistanceUnsafe(eyeCoordinate.xyz, -forwardDirection, alignedPlane.xyz, alignedPlane.w); + + // end aligned plane + alignedPlane.xyz = cross(v_rightPlaneEC.xyz, v_endPlaneEC.xyz); + alignedPlane.xyz = cross(alignedPlane.xyz, v_rightPlaneEC.xyz); + alignedPlane.w = -dot(alignedPlane.xyz, v_ecEnd_and_ecStart_X.xyz); + distanceFromEnd = rayPlaneDistanceUnsafe(eyeCoordinate.xyz, forwardDirection, alignedPlane.xyz, alignedPlane.w); + + shouldDiscard = shouldDiscard || distanceFromStart < -halfMaxWidth || distanceFromEnd < -halfMaxWidth; if (shouldDiscard) { #ifdef DEBUG_SHOW_VOLUME @@ -54,17 +76,13 @@ void main(void) #ifdef PER_INSTANCE_COLOR gl_FragColor = v_color; #else // PER_INSTANCE_COLOR - // Use distances for planes aligned with segment to prevent skew in dashing - distanceFromStart = rayPlaneDistanceUnsafe(eyeCoordinate.xyz, -v_forwardDirectionEC, v_forwardDirectionEC.xyz, v_alignedPlaneDistances.x); - distanceFromEnd = rayPlaneDistanceUnsafe(eyeCoordinate.xyz, v_forwardDirectionEC, -v_forwardDirectionEC.xyz, v_alignedPlaneDistances.y); - // Clamp - distance to aligned planes may be negative due to mitering distanceFromStart = max(0.0, distanceFromStart); distanceFromEnd = max(0.0, distanceFromEnd); float s = distanceFromStart / (distanceFromStart + distanceFromEnd); - s = (s * v_texcoordNormalization_and_halfWidth.y) + v_texcoordNormalization_and_halfWidth.x; - float t = (width + halfMaxWidth) / (2.0 * halfMaxWidth); + s = (s * v_texcoordNormalization_and_ecStart_YZ.y) + v_texcoordNormalization_and_ecStart_YZ.x; + float t = (widthwiseDistance + halfMaxWidth) / (2.0 * halfMaxWidth); czm_materialInput materialInput; diff --git a/Source/Shaders/PolylineShadowVolumeVS.glsl b/Source/Shaders/PolylineShadowVolumeVS.glsl index c55c80cd2c70..1974d74d8930 100644 --- a/Source/Shaders/PolylineShadowVolumeVS.glsl +++ b/Source/Shaders/PolylineShadowVolumeVS.glsl @@ -16,11 +16,11 @@ attribute vec2 texcoordNormalization2D; attribute float batchId; -varying vec4 v_startPlaneEC; +varying vec4 v_startPlaneEC_lengthHalfWidth; varying vec4 v_endPlaneEC; varying vec4 v_rightPlaneEC; -varying vec3 v_forwardDirectionEC; -varying vec3 v_texcoordNormalization_and_halfWidth; +varying vec4 v_ecEnd_and_ecStart_X; +varying vec4 v_texcoordNormalization_and_ecStart_YZ; // For materials varying float v_width; @@ -28,8 +28,6 @@ varying float v_polylineAngle; #ifdef PER_INSTANCE_COLOR varying vec4 v_color; -#else -varying vec2 v_alignedPlaneDistances; #endif void main() @@ -41,21 +39,20 @@ void main() vec3 ecEnd = forwardDirectionEC + ecStart; forwardDirectionEC = normalize(forwardDirectionEC); - v_forwardDirectionEC = forwardDirectionEC; - // Right plane v_rightPlaneEC.xyz = czm_normal * vec3(0.0, offsetAndRight2D.zw); v_rightPlaneEC.w = -dot(v_rightPlaneEC.xyz, ecStart); // start plane - v_startPlaneEC.xyz = czm_normal * vec3(0.0, startEndNormals2D.xy); - v_startPlaneEC.w = -dot(v_startPlaneEC.xyz, ecStart); + vec4 startPlaneEC; + startPlaneEC.xyz = czm_normal * vec3(0.0, startEndNormals2D.xy); + startPlaneEC.w = -dot(startPlaneEC.xyz, ecStart); // end plane v_endPlaneEC.xyz = czm_normal * vec3(0.0, startEndNormals2D.zw); v_endPlaneEC.w = -dot(v_endPlaneEC.xyz, ecEnd); - v_texcoordNormalization_and_halfWidth.xy = vec2(abs(texcoordNormalization2D.x), texcoordNormalization2D.y); + v_texcoordNormalization_and_ecStart_YZ.xy = vec2(abs(texcoordNormalization2D.x), texcoordNormalization2D.y); #else // COLUMBUS_VIEW_2D vec3 ecStart = (czm_modelViewRelativeToEye * czm_translateRelativeToEye(startHi_and_forwardOffsetX.xyz, startLo_and_forwardOffsetY.xyz)).xyz; @@ -63,11 +60,11 @@ void main() vec3 ecEnd = ecStart + offset; vec3 forwardDirectionEC = normalize(offset); - v_forwardDirectionEC = forwardDirectionEC; // start plane - v_startPlaneEC.xyz = czm_normal * startNormal_and_forwardOffsetZ.xyz; - v_startPlaneEC.w = -dot(v_startPlaneEC.xyz, ecStart); + vec4 startPlaneEC; + startPlaneEC.xyz = czm_normal * startNormal_and_forwardOffsetZ.xyz; + startPlaneEC.w = -dot(startPlaneEC.xyz, ecStart); // end plane v_endPlaneEC.xyz = czm_normal * endNormal_and_textureCoordinateNormalizationX.xyz; @@ -77,16 +74,16 @@ void main() v_rightPlaneEC.xyz = czm_normal * rightNormal_and_textureCoordinateNormalizationY.xyz; v_rightPlaneEC.w = -dot(v_rightPlaneEC.xyz, ecStart); - v_texcoordNormalization_and_halfWidth.xy = vec2(abs(endNormal_and_textureCoordinateNormalizationX.w), rightNormal_and_textureCoordinateNormalizationY.w); + v_texcoordNormalization_and_ecStart_YZ.xy = vec2(abs(endNormal_and_textureCoordinateNormalizationX.w), rightNormal_and_textureCoordinateNormalizationY.w); #endif // COLUMBUS_VIEW_2D + v_ecEnd_and_ecStart_X.xyz = ecEnd; + v_ecEnd_and_ecStart_X.w = ecStart.x; + v_texcoordNormalization_and_ecStart_YZ.zw = ecStart.yz; + #ifdef PER_INSTANCE_COLOR v_color = czm_batchTable_color(batchId); -#else // PER_INSTANCE_COLOR - // For computing texture coordinates - v_alignedPlaneDistances.x = -dot(forwardDirectionEC, ecStart); - v_alignedPlaneDistances.y = -dot(-forwardDirectionEC, ecEnd); #endif // PER_INSTANCE_COLOR // Compute a normal along which to "push" the position out, extending the miter depending on view distance. @@ -96,26 +93,45 @@ void main() // Check distance to the end plane and start plane, pick the plane that is closer vec4 positionEC = czm_modelViewRelativeToEye * positionRelativeToEye; // w = 1.0, see czm_computePosition - float absStartPlaneDistance = abs(czm_planeDistance(v_startPlaneEC, positionEC.xyz)); + float absStartPlaneDistance = abs(czm_planeDistance(startPlaneEC, positionEC.xyz)); float absEndPlaneDistance = abs(czm_planeDistance(v_endPlaneEC, positionEC.xyz)); - vec3 planeDirection = czm_branchFreeTernary(absStartPlaneDistance < absEndPlaneDistance, v_startPlaneEC.xyz, v_endPlaneEC.xyz); + vec3 planeDirection = czm_branchFreeTernary(absStartPlaneDistance < absEndPlaneDistance, startPlaneEC.xyz, v_endPlaneEC.xyz); vec3 upOrDown = normalize(cross(v_rightPlaneEC.xyz, planeDirection)); // Points "up" for start plane, "down" at end plane. vec3 normalEC = normalize(cross(planeDirection, upOrDown)); // In practice, the opposite seems to work too. - // Determine if this vertex is on the "left" or "right" -#ifdef COLUMBUS_VIEW_2D - normalEC *= sign(texcoordNormalization2D.x); -#else - normalEC *= sign(endNormal_and_textureCoordinateNormalizationX.w); -#endif - - // A "perfect" implementation would push along normals according to the angle against forward. - // In practice, just extending the shadow volume more than needed works for most cases, - // and for very sharp turns we compute attributes to "break" the miter anyway. + // Determine distance along normalEC to push for a volume of appropriate width. + // Make volumes about double pixel width for a conservative fit - in practice the + // extra cost here is minimal compared to the loose volume heights. + // + // N = normalEC (guaranteed "right-facing") + // R = rightEC + // p = angle between N and R + // w = distance to push along R if R == N + // d = distance to push along N + // + // N R + // { \ p| } * cos(p) = dot(N, R) = w / d + // d\ \ | |w * d = w / dot(N, R) + // { \| } + // o---------- polyline segment ----> + // float width = czm_batchTable_width(batchId); v_width = width; - v_texcoordNormalization_and_halfWidth.z = width * 0.5; - positionEC.xyz += width * max(0.0, czm_metersPerPixel(positionEC)) * normalEC; // prevent artifacts when czm_metersPerPixel is negative (behind camera) + + v_startPlaneEC_lengthHalfWidth.xyz = startPlaneEC.xyz * width * 0.5; + v_startPlaneEC_lengthHalfWidth.w = startPlaneEC.w; + + width = width * max(0.0, czm_metersPerPixel(positionEC)); // width = distance to push along R + width = width / dot(normalEC, v_rightPlaneEC.xyz); // width = distance to push along N + + // Determine if this vertex is on the "left" or "right" + #ifdef COLUMBUS_VIEW_2D + normalEC *= sign(texcoordNormalization2D.x); + #else + normalEC *= sign(endNormal_and_textureCoordinateNormalizationX.w); + #endif + + positionEC.xyz += width * normalEC; gl_Position = czm_projection * positionEC; // Approximate relative screen space direction of the line. From d11829d7ecff29480b7f84ec7f57f6c694f25236 Mon Sep 17 00:00:00 2001 From: Kangning Li Date: Thu, 7 Jun 2018 15:06:34 -0400 Subject: [PATCH 26/39] add defines to enable/disable varyings --- Source/Scene/GroundPolylinePrimitive.js | 26 ++++++++++++------- .../Shaders/PolylineShadowVolumeMorphVS.glsl | 11 ++++++++ Source/Shaders/PolylineShadowVolumeVS.glsl | 14 +++++++--- 3 files changed, 39 insertions(+), 12 deletions(-) diff --git a/Source/Scene/GroundPolylinePrimitive.js b/Source/Scene/GroundPolylinePrimitive.js index 5d649f113dff..17018081c756 100644 --- a/Source/Scene/GroundPolylinePrimitive.js +++ b/Source/Scene/GroundPolylinePrimitive.js @@ -377,8 +377,6 @@ define([ function createShaderProgram(groundPolylinePrimitive, frameState, appearance) { var context = frameState.context; var primitive = groundPolylinePrimitive._primitive; - var isPolylineColorAppearance = appearance instanceof PolylineColorAppearance; - var attributeLocations = primitive._attributeLocations; var vs = primitive._batchTable.getVertexShaderCallback()(PolylineShadowVolumeVS); @@ -395,9 +393,19 @@ define([ // which causes problems when interpolating log depth from vertices. // So force computing and writing log depth in the fragment shader. // Re-enable at far distances to avoid z-fighting. - var colorDefine = isPolylineColorAppearance ? 'PER_INSTANCE_COLOR' : ''; + var colorDefine = appearance instanceof PolylineColorAppearance ? 'PER_INSTANCE_COLOR' : ''; var vsDefines = ['ENABLE_GL_POSITION_LOG_DEPTH_AT_HEIGHT', colorDefine]; var fsDefines = groundPolylinePrimitive.debugShowShadowVolume ? ['DEBUG_SHOW_VOLUME', colorDefine] : [colorDefine]; + var materialShaderSource = defined(appearance.material) ? appearance.material.shaderSource : ''; + + // Check for use of v_width_yzw and v_polylineAngle_yzw in material shader + // to determine whether these varyings should be active in the vertex shader. + if (materialShaderSource.search(/varying\s+vec4\s+v_polylineAngle_yzw;/g) === -1) { + vsDefines.push('ANGLE_VARYING'); + } + if (materialShaderSource.search(/varying\s+vec4\s+v_width_yzw;/g) === -1) { + vsDefines.push('WIDTH_VARYING'); + } var vsColor3D = new ShaderSource({ defines : vsDefines, @@ -405,7 +413,7 @@ define([ }); var fsColor3D = new ShaderSource({ defines : fsDefines, - sources : [isPolylineColorAppearance ? '' : appearance.material.shaderSource, PolylineShadowVolumeFS] + sources : [materialShaderSource, PolylineShadowVolumeFS] }); groundPolylinePrimitive._sp = ShaderProgram.replaceCache({ context : context, @@ -441,7 +449,7 @@ define([ }); var fsColorMorph = new ShaderSource({ defines : fsDefines, - sources : [isPolylineColorAppearance ? '' : appearance.material.shaderSource, PolylineShadowVolumeMorphFS] + sources : [materialShaderSource, PolylineShadowVolumeMorphFS] }); colorProgramMorph = context.shaderCache.createDerivedShaderProgram(groundPolylinePrimitive._sp, 'MorphColor', { context : context, @@ -461,11 +469,11 @@ define([ vsPickMorphSource = Primitive._updatePickColorAttribute(vsPick); var vsPick3D = new ShaderSource({ - defines : vsDefines, + defines : ['ENABLE_GL_POSITION_LOG_DEPTH_AT_HEIGHT'], sources : [vsPick] }); var fsPick3D = new ShaderSource({ - defines : fsDefines.concat(['PICK']), + defines : ['PICK'], sources : [PolylineShadowVolumeFS], pickColorQualifier : 'varying' }); @@ -482,7 +490,7 @@ define([ var pickProgram2D = context.shaderCache.getDerivedShaderProgram(groundPolylinePrimitive._spPick, '2dPick'); if (!defined(pickProgram2D)) { var vsPick2D = new ShaderSource({ - defines : vsDefines.concat(['COLUMBUS_VIEW_2D']), + defines : ['COLUMBUS_VIEW_2D', 'ENABLE_GL_POSITION_LOG_DEPTH_AT_HEIGHT'], sources : [vsPick] }); pickProgram2D = context.shaderCache.createDerivedShaderProgram(groundPolylinePrimitive._spPick, '2dPick', { @@ -499,7 +507,7 @@ define([ var pickProgramMorph = context.shaderCache.getDerivedShaderProgram(groundPolylinePrimitive._spPick, 'MorphPick'); if (!defined(pickProgramMorph)) { var vsPickMorph = new ShaderSource({ - defines : vsDefines, + defines : ['ENABLE_GL_POSITION_LOG_DEPTH_AT_HEIGHT'], sources : [vsPickMorphSource] }); var fsPickMorph = new ShaderSource({ diff --git a/Source/Shaders/PolylineShadowVolumeMorphVS.glsl b/Source/Shaders/PolylineShadowVolumeMorphVS.glsl index 070cc4c00eea..1db21705fec8 100644 --- a/Source/Shaders/PolylineShadowVolumeMorphVS.glsl +++ b/Source/Shaders/PolylineShadowVolumeMorphVS.glsl @@ -17,8 +17,12 @@ varying vec3 v_forwardDirectionEC; varying vec3 v_texcoordNormalization_and_halfWidth; // For materials +#ifdef WIDTH_VARYING varying float v_width; +#endif +#ifdef ANGLE_VARYING varying float v_polylineAngle; +#endif #ifdef PER_INSTANCE_COLOR varying vec4 v_color; @@ -87,10 +91,15 @@ void main() v_alignedPlaneDistances.y = -dot(-v_forwardDirectionEC, ecEnd); #endif // PER_INSTANCE_COLOR +#ifdef WIDTH_VARYING float width = czm_batchTable_width(batchId); float halfWidth = width * 0.5; v_width = width; v_texcoordNormalization_and_halfWidth.z = halfWidth; +#else + float halfWidth = 0.5 * czm_batchTable_width(batchId); + v_texcoordNormalization_and_halfWidth.z = halfWidth; +#endif // Compute a normal along which to "push" the position out, extending the miter depending on view distance. // Position has already been "pushed" by unit length along miter normal, and miter normals are encoded in the planes. @@ -137,8 +146,10 @@ void main() // Blend for actual position gl_Position = czm_projection * mix(positionEC2D, positionEC3D, czm_morphTime); +#ifdef ANGLE_VARYING // Approximate relative screen space direction of the line. vec2 approxLineDirection = normalize(vec2(v_forwardDirectionEC.x, -v_forwardDirectionEC.y)); approxLineDirection.y = czm_branchFreeTernary(approxLineDirection.x == 0.0 && approxLineDirection.y == 0.0, -1.0, approxLineDirection.y); v_polylineAngle = czm_fastApproximateAtan(approxLineDirection.x, approxLineDirection.y); +#endif } diff --git a/Source/Shaders/PolylineShadowVolumeVS.glsl b/Source/Shaders/PolylineShadowVolumeVS.glsl index 1974d74d8930..8ac27f3b7aa2 100644 --- a/Source/Shaders/PolylineShadowVolumeVS.glsl +++ b/Source/Shaders/PolylineShadowVolumeVS.glsl @@ -23,8 +23,12 @@ varying vec4 v_ecEnd_and_ecStart_X; varying vec4 v_texcoordNormalization_and_ecStart_YZ; // For materials +#ifdef WIDTH_VARYING varying float v_width; +#endif +#ifdef ANGLE_VARYING varying float v_polylineAngle; +#endif #ifdef PER_INSTANCE_COLOR varying vec4 v_color; @@ -116,7 +120,9 @@ void main() // o---------- polyline segment ----> // float width = czm_batchTable_width(batchId); +#ifdef WIDTH_VARYING v_width = width; +#endif v_startPlaneEC_lengthHalfWidth.xyz = startPlaneEC.xyz * width * 0.5; v_startPlaneEC_lengthHalfWidth.w = startPlaneEC.w; @@ -125,17 +131,19 @@ void main() width = width / dot(normalEC, v_rightPlaneEC.xyz); // width = distance to push along N // Determine if this vertex is on the "left" or "right" - #ifdef COLUMBUS_VIEW_2D +#ifdef COLUMBUS_VIEW_2D normalEC *= sign(texcoordNormalization2D.x); - #else +#else normalEC *= sign(endNormal_and_textureCoordinateNormalizationX.w); - #endif +#endif positionEC.xyz += width * normalEC; gl_Position = czm_projection * positionEC; +#ifdef ANGLE_VARYING // Approximate relative screen space direction of the line. vec2 approxLineDirection = normalize(vec2(forwardDirectionEC.x, -forwardDirectionEC.y)); approxLineDirection.y = czm_branchFreeTernary(approxLineDirection.x == 0.0 && approxLineDirection.y == 0.0, -1.0, approxLineDirection.y); v_polylineAngle = czm_fastApproximateAtan(approxLineDirection.x, approxLineDirection.y); +#endif } From 1d1850ab479eea8049843d3215602363b807b6a4 Mon Sep 17 00:00:00 2001 From: Kangning Li Date: Thu, 7 Jun 2018 17:15:36 -0400 Subject: [PATCH 27/39] PR feedback --- Source/Core/GroundPolylineGeometry.js | 90 +++++++++++----------- Source/Scene/GroundPolylinePrimitive.js | 59 ++++++++------ Specs/Core/GroundPolylineGeometrySpec.js | 29 +++---- Specs/Scene/GroundPolylinePrimitiveSpec.js | 4 +- 4 files changed, 100 insertions(+), 82 deletions(-) diff --git a/Source/Core/GroundPolylineGeometry.js b/Source/Core/GroundPolylineGeometry.js index 8c272c62d596..329399f5167c 100644 --- a/Source/Core/GroundPolylineGeometry.js +++ b/Source/Core/GroundPolylineGeometry.js @@ -73,18 +73,28 @@ define([ * @alias GroundPolylineGeometry * @constructor * - * @param {Object} [options] Options with the following properties: + * @param {Object} options Options with the following properties: * @param {Number} [options.width=1.0] The screen space width in pixels. - * @param {Cartesian3[]} [options.positions] An array of {@link Cartesian3} defining the polyline's points. Heights above the ellipsoid will be ignored. + * @param {Cartesian3[]} options.positions An array of {@link Cartesian3} defining the polyline's points. Heights above the ellipsoid will be ignored. * @param {Number} [options.granularity=9999.0] The distance interval used for interpolating options.points. Defaults to 9999.0 meters. Zero indicates no interpolation. * @param {Boolean} [options.loop=false] Whether during geometry creation a line segment will be added between the last and first line positions to make this Polyline a loop. * @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] Ellipsoid that input positions will be clamped to. * @param {MapProjection} [options.projection] Map Projection for projecting coordinates to 2D. * + * @exception {DeveloperError} At least two positions are required. + * + * @see GroundPolylineGeometry#createGeometry * @see GroundPolylinePrimitive */ function GroundPolylineGeometry(options) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); + var positions = options.positions; + + //>>includeStart('debug', pragmas.debug); + if ((!defined(positions)) || (positions.length < 2)) { + throw new DeveloperError('At least two positions are required.'); + } + //>>includeEnd('debug'); /** * The screen space width in pixels. @@ -92,11 +102,7 @@ define([ */ this.width = defaultValue(options.width, 1.0); // Doesn't get packed, not necessary for computing geometry. - /** - * An array of {@link Cartesian3} defining the polyline's points. Heights above the ellipsoid will be ignored. - * @type {Cartesian3[]} - */ - this.positions = defaultValue(options.positions, []); + this._positions = positions; /** * The distance interval used for interpolating options.points. Zero indicates no interpolation. @@ -142,7 +148,7 @@ define([ */ packedLength: { get: function() { - return 1.0 + this.positions.length * 3 + 1.0 + 1.0 + Ellipsoid.packedLength + 1.0; + return 1.0 + this._positions.length * 3 + 1.0 + 1.0 + Ellipsoid.packedLength + 1.0; } } }); @@ -225,7 +231,7 @@ define([ var index = defaultValue(startingIndex, 0); // Pack position length, then all positions - var positions = value.positions; + var positions = value._positions; var positionsLength = positions.length; array[index++] = positionsLength; @@ -260,16 +266,16 @@ define([ //>>includeEnd('debug'); var index = defaultValue(startingIndex, 0); - var positions = []; - var positionsLength = array[index++]; + var positions = new Array(positionsLength); + for (var i = 0; i < positionsLength; i++) { - positions.push(Cartesian3.unpack(array, index)); + positions[i] = Cartesian3.unpack(array, index); index += 3; } var granularity = array[index++]; - var loop = array[index++] === 1.0 ? true : false; + var loop = array[index++] === 1.0; var ellipsoid = Ellipsoid.unpack(array, index); index += Ellipsoid.packedLength; @@ -286,7 +292,7 @@ define([ }); } - result.positions = positions; + result._positions = positions; result.granularity = granularity; result.loop = loop; result.ellipsoid = ellipsoid; @@ -328,7 +334,6 @@ define([ // Average directions to previous and to next result = Cartesian3.add(toNext, toPrevious, result); - result = Cartesian3.multiplyByScalar(result, 0.5, result); result = Cartesian3.normalize(result, result); // Rotate this direction to be orthogonal to up @@ -339,7 +344,7 @@ define([ // Flip the normal if it isn't pointing roughly bound right (aka if forward is pointing more "backwards") if (Cartesian3.dot(toNext, forward) < cosine90) { - result = Cartesian3.multiplyByScalar(result, -1.0, result); + result = Cartesian3.negate(result, result); } return result; @@ -372,14 +377,9 @@ define([ var index; var i; - var positions = groundPolylineGeometry.positions; + var positions = groundPolylineGeometry._positions; var positionsLength = positions.length; - //>>includeStart('debug', pragmas.debug); - if (positionsLength < 2) { - throw new DeveloperError('GroundPolylineGeometry must contain two or more positions'); - } - //>>includeEnd('debug'); if (positionsLength === 2) { loop = false; } @@ -567,7 +567,7 @@ define([ result.z = 0.0; result = Cartesian3.normalize(result, result); if (flipNormal) { - Cartesian3.multiplyByScalar(result, -1.0, result); + Cartesian3.negate(result, result); } return result; } @@ -759,7 +759,7 @@ define([ var preEndBottom = Cartesian3.unpack(bottomPositionsArray, bottomPositionsArray.length - 6, segmentStartBottomScratch); if (breakMiter(endGeometryNormal, preEndBottom, endBottom, endTop)) { // Miter broken as if for the last point in the loop, needs to be inverted for first point (clone of endBottom) - endGeometryNormal = Cartesian3.multiplyByScalar(endGeometryNormal, -1.0, endGeometryNormal); + endGeometryNormal = Cartesian3.negate(endGeometryNormal, endGeometryNormal); } } @@ -772,7 +772,7 @@ define([ var startGeometryNormal = Cartesian3.clone(endGeometryNormal, segmentStartNormalScratch); if (miterBroken) { - startGeometryNormal = Cartesian3.multiplyByScalar(startGeometryNormal, -1.0, startGeometryNormal); + startGeometryNormal = Cartesian3.negate(startGeometryNormal, startGeometryNormal); } endBottom = Cartesian3.unpack(bottomPositionsArray, index, segmentEndBottomScratch); @@ -949,26 +949,26 @@ define([ nudgeXZ(adjustHeightStartBottom, adjustHeightEndBottom); nudgeXZ(adjustHeightStartTop, adjustHeightEndTop); - Cartesian3.pack(adjustHeightStartBottom, positionsArray, vec3sWriteIndex); - Cartesian3.pack(adjustHeightEndBottom, positionsArray, vec3sWriteIndex + 3); - Cartesian3.pack(adjustHeightEndTop, positionsArray, vec3sWriteIndex + 6); - Cartesian3.pack(adjustHeightStartTop, positionsArray, vec3sWriteIndex + 9); - - // Nudge in opposite direction - normalNudge = Cartesian3.multiplyByScalar(rightNormal, -2.0 * CesiumMath.EPSILON5, normalNudgeScratch); - Cartesian3.add(adjustHeightStartBottom, normalNudge, adjustHeightStartBottom); - Cartesian3.add(adjustHeightEndBottom, normalNudge, adjustHeightEndBottom); - Cartesian3.add(adjustHeightStartTop, normalNudge, adjustHeightStartTop); - Cartesian3.add(adjustHeightEndTop, normalNudge, adjustHeightEndTop); - - // Check against XZ plane again - nudgeXZ(adjustHeightStartBottom, adjustHeightEndBottom); - nudgeXZ(adjustHeightStartTop, adjustHeightEndTop); - - Cartesian3.pack(adjustHeightStartBottom, positionsArray, vec3sWriteIndex + 12); - Cartesian3.pack(adjustHeightEndBottom, positionsArray, vec3sWriteIndex + 15); - Cartesian3.pack(adjustHeightEndTop, positionsArray, vec3sWriteIndex + 18); - Cartesian3.pack(adjustHeightStartTop, positionsArray, vec3sWriteIndex + 21); + Cartesian3.pack(adjustHeightStartBottom, positionsArray, vec3sWriteIndex); + Cartesian3.pack(adjustHeightEndBottom, positionsArray, vec3sWriteIndex + 3); + Cartesian3.pack(adjustHeightEndTop, positionsArray, vec3sWriteIndex + 6); + Cartesian3.pack(adjustHeightStartTop, positionsArray, vec3sWriteIndex + 9); + + // Nudge in opposite direction + normalNudge = Cartesian3.multiplyByScalar(rightNormal, -2.0 * CesiumMath.EPSILON5, normalNudgeScratch); + Cartesian3.add(adjustHeightStartBottom, normalNudge, adjustHeightStartBottom); + Cartesian3.add(adjustHeightEndBottom, normalNudge, adjustHeightEndBottom); + Cartesian3.add(adjustHeightStartTop, normalNudge, adjustHeightStartTop); + Cartesian3.add(adjustHeightEndTop, normalNudge, adjustHeightEndTop); + + // Check against XZ plane again + nudgeXZ(adjustHeightStartBottom, adjustHeightEndBottom); + nudgeXZ(adjustHeightStartTop, adjustHeightEndTop); + + Cartesian3.pack(adjustHeightStartBottom, positionsArray, vec3sWriteIndex + 12); + Cartesian3.pack(adjustHeightEndBottom, positionsArray, vec3sWriteIndex + 15); + Cartesian3.pack(adjustHeightEndTop, positionsArray, vec3sWriteIndex + 18); + Cartesian3.pack(adjustHeightStartTop, positionsArray, vec3sWriteIndex + 21); cartographicsIndex += 2; index += 3; diff --git a/Source/Scene/GroundPolylinePrimitive.js b/Source/Scene/GroundPolylinePrimitive.js index 17018081c756..b31ef3e62809 100644 --- a/Source/Scene/GroundPolylinePrimitive.js +++ b/Source/Scene/GroundPolylinePrimitive.js @@ -78,7 +78,6 @@ define([ * @param {Boolean} [options.releaseGeometryInstances=true] When true, the primitive does not keep a reference to the input geometryInstances to save memory. * @param {Boolean} [options.allowPicking=true] When true, each geometry instance will only be pickable with {@link Scene#pick}. When false, GPU memory is saved. * @param {Boolean} [options.asynchronous=true] Determines if the primitive will be created asynchronously or block until ready. If false initializeTerrainHeights() must be called first. - * @param {ClassificationType} [options.classificationType=ClassificationType.TERRAIN] Determines whether terrain, 3D Tiles or both will be classified. * @param {Boolean} [options.debugShowBoundingVolume=false] For debugging only. Determines if this primitive's commands' bounding spheres are shown. * @param {Boolean} [options.debugShowShadowVolume=false] For debugging only. Determines if the shadow volume for each geometry in the primitive is drawn. Must be true on * creation for the volumes to be created before the geometry is released or options.releaseGeometryInstance must be false. @@ -101,6 +100,7 @@ define([ * @default undefined */ this.geometryInstances = options.geometryInstances; + this._hasPerInstanceColors = true; var appearance = options.appearance; if (!defined(appearance)) { @@ -175,7 +175,8 @@ define([ cull : { enabled : true // prevent double-draw. Geometry is "inverted" (reversed winding order) so we're drawing backfaces. }, - blending : BlendingState.ALPHA_BLEND + blending : BlendingState.ALPHA_BLEND, + depthMask : false }); this._renderStateMorph = RenderState.fromCache({ @@ -186,7 +187,8 @@ define([ depthTest : { enabled : true }, - blending : BlendingState.ALPHA_BLEND + blending : BlendingState.ALPHA_BLEND, + depthMask : false }); } @@ -524,13 +526,6 @@ define([ }); } groundPolylinePrimitive._spPickMorph = pickProgramMorph; - } else { - groundPolylinePrimitive._spPick = ShaderProgram.fromCache({ - context : context, - vertexShaderSource : vsColor3D, - fragmentShaderSource : fsColor3D, - attributeLocations : attributeLocations - }); } } @@ -668,7 +663,7 @@ define([ } } - if (passes.pick) { + if (passes.pick && primitive.allowPicking) { var pickLength = pickCommands.length; for (var k = 0; k < pickLength; ++k) { var pickCommand = pickCommands[k]; @@ -696,8 +691,7 @@ define([ *

* * @exception {DeveloperError} For synchronous GroundPolylinePrimitives, you must call GroundPolylinePrimitives.initializeTerrainHeights() and wait for the returned promise to resolve. - * @exception {DeveloperError} All instance geometries must have the same primitiveType. - * @exception {DeveloperError} Appearance and material have a uniform with the same name. + * @exception {DeveloperError} All GeometryInstances must have color attributes to use PolylineColorAppearance with GroundPolylinePrimitive. */ GroundPolylinePrimitive.prototype.update = function(frameState) { if (!defined(this._primitive) && !defined(this.geometryInstances)) { @@ -722,22 +716,30 @@ define([ if (!defined(this._primitive)) { var geometryInstances = isArray(this.geometryInstances) ? this.geometryInstances : [this.geometryInstances]; var geometryInstancesLength = geometryInstances.length; + var groundInstances = new Array(geometryInstancesLength); + var attributes; - // If using PolylineColorAppearance, check if each instance has a color attribute. - if (this.appearance instanceof PolylineColorAppearance) { - for (i = 0; i < geometryInstancesLength; ++i) { - attributes = geometryInstances[i].attributes; - if (!defined(attributes) || !defined(attributes.color)) { - throw new DeveloperError('All GeometryInstances must have color attributes to use PolylineColorAppearance with GroundPolylinePrimitive.'); - } + // Check if each instance has a color attribute. + for (i = 0; i < geometryInstancesLength; ++i) { + attributes = geometryInstances[i].attributes; + if (!defined(attributes) || !defined(attributes.color)) { + this._hasPerInstanceColors = false; + break; } } - // Automatically create line width attributes for (i = 0; i < geometryInstancesLength; ++i) { var geometryInstance = geometryInstances[i]; - attributes = geometryInstance.attributes; + attributes = {}; + var instanceAttributes = geometryInstance.attributes; + for (var attributeKey in instanceAttributes) { + if (instanceAttributes.hasOwnProperty(attributeKey)) { + attributes[attributeKey] = instanceAttributes[attributeKey]; + } + } + + // Automatically create line width attribute if not already given if (!defined(attributes.width)) { attributes.width = new GeometryInstanceAttribute({ componentDatatype : ComponentDatatype.UNSIGNED_BYTE, @@ -745,9 +747,15 @@ define([ value : [geometryInstance.geometry.width] }); } + + groundInstances[i] = new GeometryInstance({ + geometry : geometryInstance.geometry, + attributes : attributes, + id : geometryInstance.id + }); } - primitiveOptions.geometryInstances = geometryInstances; + primitiveOptions.geometryInstances = groundInstances; primitiveOptions.appearance = this.appearance; primitiveOptions._createShaderProgramFunction = function(primitive, frameState, appearance) { @@ -776,6 +784,11 @@ define([ } }); } + + if (this.appearance instanceof PolylineColorAppearance && !this._hasPerInstanceColors) { + throw new DeveloperError('All GeometryInstances must have color attributes to use PolylineColorAppearance with GroundPolylinePrimitive.'); + } + this._primitive.appearance = this.appearance; this._primitive.show = this.show; this._primitive.debugShowBoundingVolume = this.debugShowBoundingVolume; diff --git a/Specs/Core/GroundPolylineGeometrySpec.js b/Specs/Core/GroundPolylineGeometrySpec.js index ed3671c0f873..c0e318175d32 100644 --- a/Specs/Core/GroundPolylineGeometrySpec.js +++ b/Specs/Core/GroundPolylineGeometrySpec.js @@ -382,16 +382,14 @@ defineSuite([ }); it('throws errors if not enough positions have been provided', function() { - var groundPolylineGeometry = new GroundPolylineGeometry({ - positions : Cartesian3.fromDegreesArray([ - 0.01, 0.0 - ]), - granularity : 0.0, - loop : true - }); - expect(function() { - GroundPolylineGeometry.createGeometry(groundPolylineGeometry); + return new GroundPolylineGeometry({ + positions : Cartesian3.fromDegreesArray([ + 0.01, 0.0 + ]), + granularity : 0.0, + loop : true + }); }).toThrowDeveloperError(); }); @@ -407,13 +405,18 @@ defineSuite([ var packedArray = [0]; GroundPolylineGeometry.pack(groundPolylineGeometry, packedArray, 1); - var scratch = new GroundPolylineGeometry(); + var scratch = new GroundPolylineGeometry({ + positions : Cartesian3.fromDegreesArray([ + -1.0, 0.0, + 1.0, 0.0 + ]) + }); GroundPolylineGeometry.unpack(packedArray, 1, scratch); - var scratchPositions = scratch.positions; + var scratchPositions = scratch._positions; expect(scratchPositions.length).toEqual(2); - expect(Cartesian3.equals(scratchPositions[0], groundPolylineGeometry.positions[0])).toBe(true); - expect(Cartesian3.equals(scratchPositions[1], groundPolylineGeometry.positions[1])).toBe(true); + expect(Cartesian3.equals(scratchPositions[0], groundPolylineGeometry._positions[0])).toBe(true); + expect(Cartesian3.equals(scratchPositions[1], groundPolylineGeometry._positions[1])).toBe(true); expect(scratch.loop).toBe(true); expect(scratch.granularity).toEqual(10.0); expect(scratch.ellipsoid.equals(Ellipsoid.WGS84)).toBe(true); diff --git a/Specs/Scene/GroundPolylinePrimitiveSpec.js b/Specs/Scene/GroundPolylinePrimitiveSpec.js index cd01b5c97093..d6806506ea59 100644 --- a/Specs/Scene/GroundPolylinePrimitiveSpec.js +++ b/Specs/Scene/GroundPolylinePrimitiveSpec.js @@ -787,7 +787,9 @@ defineSuite([ verifyGroundPolylinePrimitiveRender(groundPolylinePrimitive, polylineColor); - expect(scene).notToPick(); + expect(scene).toPickAndCall(function(result) { + expect(result.id).toEqual('depth rectangle'); + }); }); it('update throws when batched instance colors are missing', function() { From 6147990cab9b01749c326de3d4dc1db35d4c32d7 Mon Sep 17 00:00:00 2001 From: Kangning Li Date: Thu, 7 Jun 2018 19:15:41 -0400 Subject: [PATCH 28/39] remove custom pick commands for GroundPolylinePrimitive, picking what-you-see-is-what-you-get --- Source/Scene/DerivedCommand.js | 3 +- Source/Scene/GroundPolylinePrimitive.js | 144 ++---------------- Source/Shaders/PolylineShadowVolumeFS.glsl | 23 +-- .../Shaders/PolylineShadowVolumeMorphFS.glsl | 1 + .../Shaders/PolylineShadowVolumeMorphVS.glsl | 3 + Source/Shaders/PolylineShadowVolumeVS.glsl | 25 +-- 6 files changed, 42 insertions(+), 157 deletions(-) diff --git a/Source/Scene/DerivedCommand.js b/Source/Scene/DerivedCommand.js index b3bbe671599c..21ffc6c53e7c 100644 --- a/Source/Scene/DerivedCommand.js +++ b/Source/Scene/DerivedCommand.js @@ -239,7 +239,8 @@ define([ } newSources[length] = newMain; fs = new ShaderSource({ - sources : newSources + sources : newSources, + defines : fs.defines }); shader = context.shaderCache.createDerivedShaderProgram(shaderProgram, 'pick', { vertexShaderSource : shaderProgram.vertexShaderSource, diff --git a/Source/Scene/GroundPolylinePrimitive.js b/Source/Scene/GroundPolylinePrimitive.js index b31ef3e62809..c564d1e6e8b2 100644 --- a/Source/Scene/GroundPolylinePrimitive.js +++ b/Source/Scene/GroundPolylinePrimitive.js @@ -166,11 +166,9 @@ define([ this._primitive = undefined; this._sp = undefined; - this._spPick = undefined; this._sp2D = undefined; - this._spPick2D = undefined; this._spMorph = undefined; - this._spPickMorph = undefined; + this._renderState = RenderState.fromCache({ cull : { enabled : true // prevent double-draw. Geometry is "inverted" (reversed winding order) so we're drawing backfaces. @@ -391,6 +389,10 @@ define([ vsMorph = Primitive._appendDistanceDisplayConditionToShader(primitive, vsMorph); vsMorph = Primitive._modifyShaderPosition(groundPolylinePrimitive, vsMorph, frameState.scene3DOnly); + // Access pick color from fragment shader. + // Helps with varying budget. + var fs = primitive._batchTable.getVertexShaderCallback()(PolylineShadowVolumeFS); + // Tesselation on these volumes tends to be low, // which causes problems when interpolating log depth from vertices. // So force computing and writing log depth in the fragment shader. @@ -415,7 +417,7 @@ define([ }); var fsColor3D = new ShaderSource({ defines : fsDefines, - sources : [materialShaderSource, PolylineShadowVolumeFS] + sources : [materialShaderSource, fs] }); groundPolylinePrimitive._sp = ShaderProgram.replaceCache({ context : context, @@ -449,9 +451,11 @@ define([ defines : vsDefines, sources : [vsMorph] }); + + fs = primitive._batchTable.getVertexShaderCallback()(PolylineShadowVolumeMorphFS); var fsColorMorph = new ShaderSource({ defines : fsDefines, - sources : [materialShaderSource, PolylineShadowVolumeMorphFS] + sources : [materialShaderSource, fs] }); colorProgramMorph = context.shaderCache.createDerivedShaderProgram(groundPolylinePrimitive._sp, 'MorphColor', { context : context, @@ -462,71 +466,6 @@ define([ }); } groundPolylinePrimitive._spMorph = colorProgramMorph; - - if (groundPolylinePrimitive._primitive.allowPicking) { - var vsPick = ShaderSource.createPickVertexShaderSource(vs); - vsPick = Primitive._updatePickColorAttribute(vsPick); - - var vsPickMorphSource = ShaderSource.createPickVertexShaderSource(vsMorph); - vsPickMorphSource = Primitive._updatePickColorAttribute(vsPick); - - var vsPick3D = new ShaderSource({ - defines : ['ENABLE_GL_POSITION_LOG_DEPTH_AT_HEIGHT'], - sources : [vsPick] - }); - var fsPick3D = new ShaderSource({ - defines : ['PICK'], - sources : [PolylineShadowVolumeFS], - pickColorQualifier : 'varying' - }); - - groundPolylinePrimitive._spPick = ShaderProgram.replaceCache({ - context : context, - shaderProgram : groundPolylinePrimitive._spPick, - vertexShaderSource : vsPick3D, - fragmentShaderSource : fsPick3D, - attributeLocations : attributeLocations - }); - - // Derive 2D/CV - var pickProgram2D = context.shaderCache.getDerivedShaderProgram(groundPolylinePrimitive._spPick, '2dPick'); - if (!defined(pickProgram2D)) { - var vsPick2D = new ShaderSource({ - defines : ['COLUMBUS_VIEW_2D', 'ENABLE_GL_POSITION_LOG_DEPTH_AT_HEIGHT'], - sources : [vsPick] - }); - pickProgram2D = context.shaderCache.createDerivedShaderProgram(groundPolylinePrimitive._spPick, '2dPick', { - context : context, - shaderProgram : groundPolylinePrimitive._spPick2D, - vertexShaderSource : vsPick2D, - fragmentShaderSource : fsPick3D, - attributeLocations : attributeLocations - }); - } - groundPolylinePrimitive._spPick2D = pickProgram2D; - - // Derive Morph - var pickProgramMorph = context.shaderCache.getDerivedShaderProgram(groundPolylinePrimitive._spPick, 'MorphPick'); - if (!defined(pickProgramMorph)) { - var vsPickMorph = new ShaderSource({ - defines : ['ENABLE_GL_POSITION_LOG_DEPTH_AT_HEIGHT'], - sources : [vsPickMorphSource] - }); - var fsPickMorph = new ShaderSource({ - defines : ['PICK'], - sources : [PolylineShadowVolumeMorphFS], - pickColorQualifier : 'varying' - }); - pickProgramMorph = context.shaderCache.createDerivedShaderProgram(groundPolylinePrimitive._spPick, 'MorphPick', { - context : context, - shaderProgram : groundPolylinePrimitive._spPickMorph, - vertexShaderSource : vsPickMorph, - fragmentShaderSource : fsPickMorph, - attributeLocations : attributeLocations - }); - } - groundPolylinePrimitive._spPickMorph = pickProgramMorph; - } } function createCommands(groundPolylinePrimitive, appearance, material, translucent, colorCommands, pickCommands) { @@ -559,6 +498,7 @@ define([ command.shaderProgram = groundPolylinePrimitive._sp; command.uniformMap = uniformMap; command.pass = pass; + command.pickId = 'czm_batchTable_pickColor(v_endPlaneNormalEC_and_batchId.w)'; // derive for 2D var derivedColorCommand = command.derivedCommands.color2D; @@ -571,6 +511,7 @@ define([ derivedColorCommand.shaderProgram = groundPolylinePrimitive._sp2D; derivedColorCommand.uniformMap = uniformMap; derivedColorCommand.pass = pass; + derivedColorCommand.pickId = 'czm_batchTable_pickColor(v_endPlaneNormalEC_and_batchId.w)'; // derive for Morph derivedColorCommand = command.derivedCommands.colorMorph; @@ -583,45 +524,7 @@ define([ derivedColorCommand.shaderProgram = groundPolylinePrimitive._spMorph; derivedColorCommand.uniformMap = uniformMap; derivedColorCommand.pass = pass; - - // Pick - command = pickCommands[i]; - if (!defined(command)) { - command = pickCommands[i] = new DrawCommand({ - owner : groundPolylinePrimitive, - primitiveType : primitive._primitiveType - }); - } - - command.vertexArray = vertexArray; - command.renderState = groundPolylinePrimitive._renderState; - command.shaderProgram = groundPolylinePrimitive._spPick; - command.uniformMap = uniformMap; - command.pass = pass; - - // derive for 2D - var derivedPickCommand = command.derivedCommands.pick2D; - if (!defined(derivedPickCommand)) { - derivedPickCommand = DrawCommand.shallowClone(command); - command.derivedCommands.pick2D = derivedPickCommand; - } - derivedPickCommand.vertexArray = vertexArray; - derivedPickCommand.renderState = groundPolylinePrimitive._renderState; - derivedPickCommand.shaderProgram = groundPolylinePrimitive._spPick2D; - derivedPickCommand.uniformMap = uniformMap; - derivedPickCommand.pass = pass; - - // derive for Morph - derivedPickCommand = command.derivedCommands.pickMorph; - if (!defined(derivedPickCommand)) { - derivedPickCommand = DrawCommand.shallowClone(command); - command.derivedCommands.pickMorph = derivedPickCommand; - } - derivedPickCommand.vertexArray = vertexArray; - derivedPickCommand.renderState = groundPolylinePrimitive._renderStateMorph; - derivedPickCommand.shaderProgram = groundPolylinePrimitive._spPickMorph; - derivedPickCommand.uniformMap = uniformMap; - derivedPickCommand.pass = pass; + derivedColorCommand.pickId = 'czm_batchTable_pickColor(v_batchId)'; } } @@ -643,7 +546,7 @@ define([ var commandList = frameState.commandList; var passes = frameState.passes; - if (passes.render) { + if (passes.render || (passes.pick && primitive.allowPicking)) { var colorLength = colorCommands.length; for (var j = 0; j < colorLength; ++j) { @@ -662,24 +565,6 @@ define([ commandList.push(colorCommand); } } - - if (passes.pick && primitive.allowPicking) { - var pickLength = pickCommands.length; - for (var k = 0; k < pickLength; ++k) { - var pickCommand = pickCommands[k]; - // Use derived pick command for morph and 2D - if (frameState.mode === SceneMode.MORPHING && pickCommand.shaderProgram !== groundPolylinePrimitive._spMorph) { - pickCommand = pickCommand.derivedCommands.pickMorph; - } else if (frameState.mode !== SceneMode.SCENE3D && pickCommand.shaderProgram !== groundPolylinePrimitive._spPick2D) { - pickCommand = pickCommand.derivedCommands.pick2D; - } - pickCommand.modelMatrix = modelMatrix; - pickCommand.boundingVolume = boundingSpheres[k]; - pickCommand.cull = cull; - - commandList.push(pickCommand); - } - } } /** @@ -862,13 +747,10 @@ define([ GroundPolylinePrimitive.prototype.destroy = function() { this._primitive = this._primitive && this._primitive.destroy(); this._sp = this._sp && this._sp.destroy(); - this._spPick = this._spPick && this._spPick.destroy(); // Derived programs, destroyed above if they existed. this._sp2D = undefined; - this._spPick2D = undefined; this._spMorph = undefined; - this._spPickMorph = undefined; return destroyObject(this); }; diff --git a/Source/Shaders/PolylineShadowVolumeFS.glsl b/Source/Shaders/PolylineShadowVolumeFS.glsl index 2f70435aaabd..1ec3065157ea 100644 --- a/Source/Shaders/PolylineShadowVolumeFS.glsl +++ b/Source/Shaders/PolylineShadowVolumeFS.glsl @@ -2,9 +2,9 @@ #extension GL_EXT_frag_depth : enable #endif -varying vec4 v_startPlaneEC_lengthHalfWidth; -varying vec4 v_endPlaneEC; -varying vec4 v_rightPlaneEC; +varying vec4 v_startPlaneNormalEC_and_halfWidth; +varying vec4 v_endPlaneNormalEC_and_batchId; +varying vec4 v_rightPlaneEC; // Technically can compute distance for this here varying vec4 v_ecEnd_and_ecStart_X; varying vec4 v_texcoordNormalization_and_ecStart_YZ; @@ -29,15 +29,13 @@ void main(void) vec4 eyeCoordinate = czm_windowToEyeCoordinates(gl_FragCoord.xy, logDepthOrDepth); eyeCoordinate /= eyeCoordinate.w; - float halfWidth = length(v_startPlaneEC_lengthHalfWidth.xyz); - float halfMaxWidth = halfWidth * czm_metersPerPixel(eyeCoordinate); + float halfMaxWidth = v_startPlaneNormalEC_and_halfWidth.w * czm_metersPerPixel(eyeCoordinate); // Check distance of the eye coordinate against the right-facing plane float widthwiseDistance = czm_planeDistance(v_rightPlaneEC, eyeCoordinate.xyz); // Check distance of the eye coordinate against the mitering planes - vec3 startPlaneNormal = v_startPlaneEC_lengthHalfWidth.xyz / halfWidth; - float distanceFromStart = rayPlaneDistanceUnsafe(eyeCoordinate.xyz, -forwardDirection, startPlaneNormal, v_startPlaneEC_lengthHalfWidth.w); - float distanceFromEnd = rayPlaneDistanceUnsafe(eyeCoordinate.xyz, forwardDirection, v_endPlaneEC.xyz, v_endPlaneEC.w); + float distanceFromStart = rayPlaneDistanceUnsafe(eyeCoordinate.xyz, -forwardDirection, v_startPlaneNormalEC_and_halfWidth.xyz, -dot(ecStart, v_startPlaneNormalEC_and_halfWidth.xyz)); + float distanceFromEnd = rayPlaneDistanceUnsafe(eyeCoordinate.xyz, forwardDirection, v_endPlaneNormalEC_and_batchId.xyz, -dot(v_ecEnd_and_ecStart_X.xyz, v_endPlaneNormalEC_and_batchId.xyz)); shouldDiscard = shouldDiscard || (abs(widthwiseDistance) > halfMaxWidth || distanceFromStart < 0.0 || distanceFromEnd < 0.0); @@ -48,13 +46,13 @@ void main(void) vec4 alignedPlane; // start aligned plane - alignedPlane.xyz = cross(v_rightPlaneEC.xyz, startPlaneNormal); + alignedPlane.xyz = cross(v_rightPlaneEC.xyz, v_startPlaneNormalEC_and_halfWidth.xyz); alignedPlane.xyz = cross(alignedPlane.xyz, v_rightPlaneEC.xyz); alignedPlane.w = -dot(alignedPlane.xyz, ecStart); distanceFromStart = rayPlaneDistanceUnsafe(eyeCoordinate.xyz, -forwardDirection, alignedPlane.xyz, alignedPlane.w); // end aligned plane - alignedPlane.xyz = cross(v_rightPlaneEC.xyz, v_endPlaneEC.xyz); + alignedPlane.xyz = cross(v_rightPlaneEC.xyz, v_endPlaneNormalEC_and_batchId.xyz); alignedPlane.xyz = cross(alignedPlane.xyz, v_rightPlaneEC.xyz); alignedPlane.w = -dot(alignedPlane.xyz, v_ecEnd_and_ecStart_X.xyz); distanceFromEnd = rayPlaneDistanceUnsafe(eyeCoordinate.xyz, forwardDirection, alignedPlane.xyz, alignedPlane.w); @@ -70,9 +68,6 @@ void main(void) #endif // DEBUG_SHOW_VOLUME } -#ifdef PICK - gl_FragColor.a = 1.0; -#else // PICK #ifdef PER_INSTANCE_COLOR gl_FragColor = v_color; #else // PER_INSTANCE_COLOR @@ -93,6 +88,4 @@ void main(void) czm_material material = czm_getMaterial(materialInput); gl_FragColor = vec4(material.diffuse + material.emission, material.alpha); #endif // PER_INSTANCE_COLOR - -#endif // PICK } diff --git a/Source/Shaders/PolylineShadowVolumeMorphFS.glsl b/Source/Shaders/PolylineShadowVolumeMorphFS.glsl index f32e7b958c48..1efd4eeb4619 100644 --- a/Source/Shaders/PolylineShadowVolumeMorphFS.glsl +++ b/Source/Shaders/PolylineShadowVolumeMorphFS.glsl @@ -4,6 +4,7 @@ varying vec3 v_forwardDirectionEC; varying vec3 v_texcoordNormalization_and_halfWidth; +varying float v_batchId; #ifdef PER_INSTANCE_COLOR varying vec4 v_color; diff --git a/Source/Shaders/PolylineShadowVolumeMorphVS.glsl b/Source/Shaders/PolylineShadowVolumeMorphVS.glsl index 1db21705fec8..0d670af7aff4 100644 --- a/Source/Shaders/PolylineShadowVolumeMorphVS.glsl +++ b/Source/Shaders/PolylineShadowVolumeMorphVS.glsl @@ -15,6 +15,7 @@ attribute float batchId; varying vec3 v_forwardDirectionEC; varying vec3 v_texcoordNormalization_and_halfWidth; +varying float v_batchId; // For materials #ifdef WIDTH_VARYING @@ -35,6 +36,8 @@ varying float v_texcoordT; // Morph views are from very far away and aren't meant to be used precisely, so this should be sufficient. void main() { + v_batchId = batchId; + // Start position vec4 posRelativeToEye2D = czm_translateRelativeToEye(vec3(0.0, startHiLo2D.xy), vec3(0.0, startHiLo2D.zw)); vec4 posRelativeToEye3D = czm_translateRelativeToEye(startHi_and_forwardOffsetX.xyz, startLo_and_forwardOffsetY.xyz); diff --git a/Source/Shaders/PolylineShadowVolumeVS.glsl b/Source/Shaders/PolylineShadowVolumeVS.glsl index 8ac27f3b7aa2..87566e9b02ad 100644 --- a/Source/Shaders/PolylineShadowVolumeVS.glsl +++ b/Source/Shaders/PolylineShadowVolumeVS.glsl @@ -16,8 +16,8 @@ attribute vec2 texcoordNormalization2D; attribute float batchId; -varying vec4 v_startPlaneEC_lengthHalfWidth; -varying vec4 v_endPlaneEC; +varying vec4 v_startPlaneNormalEC_and_halfWidth; +varying vec4 v_endPlaneNormalEC_and_batchId; varying vec4 v_rightPlaneEC; varying vec4 v_ecEnd_and_ecStart_X; varying vec4 v_texcoordNormalization_and_ecStart_YZ; @@ -53,8 +53,9 @@ void main() startPlaneEC.w = -dot(startPlaneEC.xyz, ecStart); // end plane - v_endPlaneEC.xyz = czm_normal * vec3(0.0, startEndNormals2D.zw); - v_endPlaneEC.w = -dot(v_endPlaneEC.xyz, ecEnd); + vec4 endPlaneEC; + endPlaneEC.xyz = czm_normal * vec3(0.0, startEndNormals2D.zw); + endPlaneEC.w = -dot(endPlaneEC.xyz, ecEnd); v_texcoordNormalization_and_ecStart_YZ.xy = vec2(abs(texcoordNormalization2D.x), texcoordNormalization2D.y); @@ -71,8 +72,9 @@ void main() startPlaneEC.w = -dot(startPlaneEC.xyz, ecStart); // end plane - v_endPlaneEC.xyz = czm_normal * endNormal_and_textureCoordinateNormalizationX.xyz; - v_endPlaneEC.w = -dot(v_endPlaneEC.xyz, ecEnd); + vec4 endPlaneEC; + endPlaneEC.xyz = czm_normal * endNormal_and_textureCoordinateNormalizationX.xyz; + endPlaneEC.w = -dot(endPlaneEC.xyz, ecEnd); // Right plane v_rightPlaneEC.xyz = czm_normal * rightNormal_and_textureCoordinateNormalizationY.xyz; @@ -98,8 +100,8 @@ void main() // Check distance to the end plane and start plane, pick the plane that is closer vec4 positionEC = czm_modelViewRelativeToEye * positionRelativeToEye; // w = 1.0, see czm_computePosition float absStartPlaneDistance = abs(czm_planeDistance(startPlaneEC, positionEC.xyz)); - float absEndPlaneDistance = abs(czm_planeDistance(v_endPlaneEC, positionEC.xyz)); - vec3 planeDirection = czm_branchFreeTernary(absStartPlaneDistance < absEndPlaneDistance, startPlaneEC.xyz, v_endPlaneEC.xyz); + float absEndPlaneDistance = abs(czm_planeDistance(endPlaneEC, positionEC.xyz)); + vec3 planeDirection = czm_branchFreeTernary(absStartPlaneDistance < absEndPlaneDistance, startPlaneEC.xyz, endPlaneEC.xyz); vec3 upOrDown = normalize(cross(v_rightPlaneEC.xyz, planeDirection)); // Points "up" for start plane, "down" at end plane. vec3 normalEC = normalize(cross(planeDirection, upOrDown)); // In practice, the opposite seems to work too. @@ -124,8 +126,11 @@ void main() v_width = width; #endif - v_startPlaneEC_lengthHalfWidth.xyz = startPlaneEC.xyz * width * 0.5; - v_startPlaneEC_lengthHalfWidth.w = startPlaneEC.w; + v_startPlaneNormalEC_and_halfWidth.xyz = startPlaneEC.xyz; + v_startPlaneNormalEC_and_halfWidth.w = width * 0.5; + + v_endPlaneNormalEC_and_batchId.xyz = endPlaneEC.xyz; + v_endPlaneNormalEC_and_batchId.w = batchId; width = width * max(0.0, czm_metersPerPixel(positionEC)); // width = distance to push along R width = width / dot(normalEC, v_rightPlaneEC.xyz); // width = distance to push along N From 43dbae2147c810fbdb66366522e59221124c3849 Mon Sep 17 00:00:00 2001 From: Kangning Li Date: Fri, 8 Jun 2018 09:07:41 -0400 Subject: [PATCH 29/39] performance tweak for polylines on terrain shader --- .../Builtin/Functions/planeDistance.glsl | 15 ++++++++ Source/Shaders/PolylineShadowVolumeFS.glsl | 38 +++++++------------ .../Shaders/PolylineShadowVolumeMorphFS.glsl | 2 +- Specs/Renderer/BuiltinFunctionsSpec.js | 13 +++++++ 4 files changed, 43 insertions(+), 25 deletions(-) diff --git a/Source/Shaders/Builtin/Functions/planeDistance.glsl b/Source/Shaders/Builtin/Functions/planeDistance.glsl index 38db05a173c6..82d713cd5fc9 100644 --- a/Source/Shaders/Builtin/Functions/planeDistance.glsl +++ b/Source/Shaders/Builtin/Functions/planeDistance.glsl @@ -11,3 +11,18 @@ float czm_planeDistance(vec4 plane, vec3 point) { return (dot(plane.xyz, point) + plane.w); } + +/** + * Computes distance from an point to a plane, typically in eye space. + * + * @name czm_planeDistance + * @glslFunction + * + * param {vec3} planeNormal Normal for a plane in Hessian Normal Form. See Plane.js + * param {float} planeDistance Distance for a plane in Hessian Normal form. See Plane.js + * param {vec3} point A point in the same space as the plane. + * returns {float} The distance from the point to the plane. + */ +float czm_planeDistance(vec3 planeNormal, float planeDistance, vec3 point) { + return (dot(planeNormal, point) + planeDistance); +} diff --git a/Source/Shaders/PolylineShadowVolumeFS.glsl b/Source/Shaders/PolylineShadowVolumeFS.glsl index 1ec3065157ea..61c52fd8af61 100644 --- a/Source/Shaders/PolylineShadowVolumeFS.glsl +++ b/Source/Shaders/PolylineShadowVolumeFS.glsl @@ -12,16 +12,10 @@ varying vec4 v_texcoordNormalization_and_ecStart_YZ; varying vec4 v_color; #endif -float rayPlaneDistanceUnsafe(vec3 origin, vec3 direction, vec3 planeNormal, float planeDistance) { - // We don't expect the ray to ever be parallel to the plane - return (-planeDistance - dot(planeNormal, origin)) / dot(planeNormal, direction); -} - void main(void) { float logDepthOrDepth = czm_unpackDepth(texture2D(czm_globeDepthTexture, gl_FragCoord.xy / czm_viewport.zw)); vec3 ecStart = vec3(v_ecEnd_and_ecStart_X.w, v_texcoordNormalization_and_ecStart_YZ.zw); - vec3 forwardDirection = normalize(v_ecEnd_and_ecStart_X.xyz - ecStart); // Discard for sky bool shouldDiscard = logDepthOrDepth == 0.0; @@ -33,9 +27,9 @@ void main(void) // Check distance of the eye coordinate against the right-facing plane float widthwiseDistance = czm_planeDistance(v_rightPlaneEC, eyeCoordinate.xyz); - // Check distance of the eye coordinate against the mitering planes - float distanceFromStart = rayPlaneDistanceUnsafe(eyeCoordinate.xyz, -forwardDirection, v_startPlaneNormalEC_and_halfWidth.xyz, -dot(ecStart, v_startPlaneNormalEC_and_halfWidth.xyz)); - float distanceFromEnd = rayPlaneDistanceUnsafe(eyeCoordinate.xyz, forwardDirection, v_endPlaneNormalEC_and_batchId.xyz, -dot(v_ecEnd_and_ecStart_X.xyz, v_endPlaneNormalEC_and_batchId.xyz)); + // Check eye coordinate against the mitering planes + float distanceFromStart = czm_planeDistance(v_startPlaneNormalEC_and_halfWidth.xyz, -dot(ecStart, v_startPlaneNormalEC_and_halfWidth.xyz), eyeCoordinate.xyz); + float distanceFromEnd = czm_planeDistance(v_endPlaneNormalEC_and_batchId.xyz, -dot(v_ecEnd_and_ecStart_X.xyz, v_endPlaneNormalEC_and_batchId.xyz), eyeCoordinate.xyz); shouldDiscard = shouldDiscard || (abs(widthwiseDistance) > halfMaxWidth || distanceFromStart < 0.0 || distanceFromEnd < 0.0); @@ -43,19 +37,17 @@ void main(void) // For computing unskewed linear texture coordinate and for clipping extremely pointy miters // aligned plane: cross the right plane normal with miter plane normal, then cross the result with right again to point it more "forward" - vec4 alignedPlane; + vec3 alignedPlaneNormal; // start aligned plane - alignedPlane.xyz = cross(v_rightPlaneEC.xyz, v_startPlaneNormalEC_and_halfWidth.xyz); - alignedPlane.xyz = cross(alignedPlane.xyz, v_rightPlaneEC.xyz); - alignedPlane.w = -dot(alignedPlane.xyz, ecStart); - distanceFromStart = rayPlaneDistanceUnsafe(eyeCoordinate.xyz, -forwardDirection, alignedPlane.xyz, alignedPlane.w); + alignedPlaneNormal = cross(v_rightPlaneEC.xyz, v_startPlaneNormalEC_and_halfWidth.xyz); + alignedPlaneNormal = normalize(cross(alignedPlaneNormal, v_rightPlaneEC.xyz)); + distanceFromStart = czm_planeDistance(alignedPlaneNormal, -dot(alignedPlaneNormal, ecStart), eyeCoordinate.xyz); // end aligned plane - alignedPlane.xyz = cross(v_rightPlaneEC.xyz, v_endPlaneNormalEC_and_batchId.xyz); - alignedPlane.xyz = cross(alignedPlane.xyz, v_rightPlaneEC.xyz); - alignedPlane.w = -dot(alignedPlane.xyz, v_ecEnd_and_ecStart_X.xyz); - distanceFromEnd = rayPlaneDistanceUnsafe(eyeCoordinate.xyz, forwardDirection, alignedPlane.xyz, alignedPlane.w); + alignedPlaneNormal = cross(v_rightPlaneEC.xyz, v_endPlaneNormalEC_and_batchId.xyz); + alignedPlaneNormal = normalize(cross(alignedPlaneNormal, v_rightPlaneEC.xyz)); + distanceFromEnd = czm_planeDistance(alignedPlaneNormal, -dot(alignedPlaneNormal, v_ecEnd_and_ecStart_X.xyz), eyeCoordinate.xyz); shouldDiscard = shouldDiscard || distanceFromStart < -halfMaxWidth || distanceFromEnd < -halfMaxWidth; @@ -71,12 +63,10 @@ void main(void) #ifdef PER_INSTANCE_COLOR gl_FragColor = v_color; #else // PER_INSTANCE_COLOR - // Clamp - distance to aligned planes may be negative due to mitering - distanceFromStart = max(0.0, distanceFromStart); - distanceFromEnd = max(0.0, distanceFromEnd); - - float s = distanceFromStart / (distanceFromStart + distanceFromEnd); - s = (s * v_texcoordNormalization_and_ecStart_YZ.y) + v_texcoordNormalization_and_ecStart_YZ.x; + // Clamp - distance to aligned planes may be negative due to mitering, + // so fragment texture coordinate might be out-of-bounds. + float s = clamp(distanceFromStart / (distanceFromStart + distanceFromEnd), 0.0, 1.0); + s = (s * v_texcoordNormalization_and_ecStart_YZ.x) + v_texcoordNormalization_and_ecStart_YZ.y; float t = (widthwiseDistance + halfMaxWidth) / (2.0 * halfMaxWidth); czm_materialInput materialInput; diff --git a/Source/Shaders/PolylineShadowVolumeMorphFS.glsl b/Source/Shaders/PolylineShadowVolumeMorphFS.glsl index 1efd4eeb4619..354fe28befba 100644 --- a/Source/Shaders/PolylineShadowVolumeMorphFS.glsl +++ b/Source/Shaders/PolylineShadowVolumeMorphFS.glsl @@ -38,7 +38,7 @@ void main(void) distanceFromEnd = max(0.0, distanceFromEnd); float s = distanceFromStart / (distanceFromStart + distanceFromEnd); - s = (s * v_texcoordNormalization_and_halfWidth.y) + v_texcoordNormalization_and_halfWidth.x; + s = (s * v_texcoordNormalization_and_halfWidth.x) + v_texcoordNormalization_and_halfWidth.y; czm_materialInput materialInput; diff --git a/Specs/Renderer/BuiltinFunctionsSpec.js b/Specs/Renderer/BuiltinFunctionsSpec.js index ddcb2e50bb3f..3ff6aae764ab 100644 --- a/Specs/Renderer/BuiltinFunctionsSpec.js +++ b/Specs/Renderer/BuiltinFunctionsSpec.js @@ -157,6 +157,19 @@ defineSuite([ context : context, fragmentShader : fs }).contextToRender(); + + fs = + 'void main() { ' + + ' vec4 plane = vec4(1.0, 0.0, 0.0, 0.0); ' + + ' vec3 point = vec3(1.0, 0.0, 0.0); ' + + ' float expected = 1.0; ' + + ' float actual = czm_planeDistance(plane.xyz, plane.w, point); ' + + ' gl_FragColor = vec4(actual == expected); ' + + '}'; + expect({ + context : context, + fragmentShader : fs + }).contextToRender(); }); it('has czm_lineDistance', function() { From 11d7bc467a277379af53f59573796e6fc270824e Mon Sep 17 00:00:00 2001 From: Kangning Li Date: Fri, 8 Jun 2018 12:11:47 -0400 Subject: [PATCH 30/39] adjust epsilons for polylines on terrain IDL handling --- Source/Core/GroundPolylineGeometry.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Source/Core/GroundPolylineGeometry.js b/Source/Core/GroundPolylineGeometry.js index 329399f5167c..dc911b7c2de4 100644 --- a/Source/Core/GroundPolylineGeometry.js +++ b/Source/Core/GroundPolylineGeometry.js @@ -593,14 +593,14 @@ define([ var startToXZdistance = Plane.getPointDistance(XZ_PLANE, start); var endToXZdistance = Plane.getPointDistance(XZ_PLANE, end); var offset = nudgeDirectionScratch; - // Larger epsilon than what's used in GeometryPipeline, less than a centimeter in world space - if (CesiumMath.equalsEpsilon(startToXZdistance, 0.0, CesiumMath.EPSILON3)) { + // Larger epsilon than what's used in GeometryPipeline, a centimeter in world space + if (CesiumMath.equalsEpsilon(startToXZdistance, 0.0, CesiumMath.EPSILON2)) { offset = direction(end, start, offset); - Cartesian3.multiplyByScalar(offset, CesiumMath.EPSILON3, offset); + Cartesian3.multiplyByScalar(offset, CesiumMath.EPSILON2, offset); Cartesian3.add(start, offset, start); - } else if (CesiumMath.equalsEpsilon(endToXZdistance, 0.0, CesiumMath.EPSILON3)) { + } else if (CesiumMath.equalsEpsilon(endToXZdistance, 0.0, CesiumMath.EPSILON2)) { offset = direction(start, end, offset); - Cartesian3.multiplyByScalar(offset, CesiumMath.EPSILON3, offset); + Cartesian3.multiplyByScalar(offset, CesiumMath.EPSILON2, offset); Cartesian3.add(end, offset, end); } } From 7869029ab90086886ccde5798380cce84d726dbf Mon Sep 17 00:00:00 2001 From: Kangning Li Date: Fri, 8 Jun 2018 16:09:22 -0400 Subject: [PATCH 31/39] typo fix --- Source/Scene/GroundPolylinePrimitive.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Source/Scene/GroundPolylinePrimitive.js b/Source/Scene/GroundPolylinePrimitive.js index c564d1e6e8b2..5742385c33b4 100644 --- a/Source/Scene/GroundPolylinePrimitive.js +++ b/Source/Scene/GroundPolylinePrimitive.js @@ -402,12 +402,12 @@ define([ var fsDefines = groundPolylinePrimitive.debugShowShadowVolume ? ['DEBUG_SHOW_VOLUME', colorDefine] : [colorDefine]; var materialShaderSource = defined(appearance.material) ? appearance.material.shaderSource : ''; - // Check for use of v_width_yzw and v_polylineAngle_yzw in material shader + // Check for use of v_width and v_polylineAngle in material shader // to determine whether these varyings should be active in the vertex shader. - if (materialShaderSource.search(/varying\s+vec4\s+v_polylineAngle_yzw;/g) === -1) { + if (materialShaderSource.search(/varying\s+float\s+v_polylineAngle;/g) === -1) { vsDefines.push('ANGLE_VARYING'); } - if (materialShaderSource.search(/varying\s+vec4\s+v_width_yzw;/g) === -1) { + if (materialShaderSource.search(/varying\s+float\s+v_width;/g) === -1) { vsDefines.push('WIDTH_VARYING'); } From eb950736494e39ef627abe77a56d1e072ca53511 Mon Sep 17 00:00:00 2001 From: Kangning Li Date: Mon, 11 Jun 2018 10:56:45 -0400 Subject: [PATCH 32/39] style/doc fixes, special geometry handling for sceneMode3D --- Source/Core/GroundPolylineGeometry.js | 307 ++++++++++-------- Source/Scene/GroundPolylinePrimitive.js | 40 ++- Source/Scene/PolylineColorAppearance.js | 3 +- .../Builtin/Functions/planeDistance.glsl | 4 +- Source/Shaders/PolylineShadowVolumeFS.glsl | 30 +- .../Shaders/PolylineShadowVolumeMorphFS.glsl | 9 - .../Shaders/PolylineShadowVolumeMorphVS.glsl | 24 +- Source/Shaders/PolylineShadowVolumeVS.glsl | 48 +-- Specs/Core/GroundPolylineGeometrySpec.js | 101 ++++-- 9 files changed, 323 insertions(+), 243 deletions(-) diff --git a/Source/Core/GroundPolylineGeometry.js b/Source/Core/GroundPolylineGeometry.js index dc911b7c2de4..473f64b8f9eb 100644 --- a/Source/Core/GroundPolylineGeometry.js +++ b/Source/Core/GroundPolylineGeometry.js @@ -51,8 +51,8 @@ define([ var PROJECTIONS = [GeographicProjection, WebMercatorProjection]; var PROJECTION_COUNT = PROJECTIONS.length; - var MITER_BREAK_SMALL = Math.cos(CesiumMath.toRadians(1)); - var MITER_BREAK_LARGE = Math.cos(CesiumMath.toRadians(179)); + var MITER_BREAK_SMALL = Math.cos(CesiumMath.toRadians(1.0)); + var MITER_BREAK_LARGE = Math.cos(CesiumMath.toRadians(179.0)); // Initial heights for constructing the wall. // Keeping WALL_INITIAL_MIN_HEIGHT near the ellipsoid surface helps @@ -68,23 +68,33 @@ define([ var WALL_INITIAL_MAX_HEIGHT = 1000.0; /** - * A description of a polyline on terrain. Only to be used with GroundPolylinePrimitive. + * A description of a polyline on terrain. Only to be used with {@link GroundPolylinePrimitive}. * * @alias GroundPolylineGeometry * @constructor * * @param {Object} options Options with the following properties: - * @param {Number} [options.width=1.0] The screen space width in pixels. * @param {Cartesian3[]} options.positions An array of {@link Cartesian3} defining the polyline's points. Heights above the ellipsoid will be ignored. - * @param {Number} [options.granularity=9999.0] The distance interval used for interpolating options.points. Defaults to 9999.0 meters. Zero indicates no interpolation. + * @param {Number} [options.width=1.0] The screen space width in pixels. + * @param {Number} [options.granularity=9999.0] The distance interval in meters used for interpolating options.points. Defaults to 9999.0 meters. Zero indicates no interpolation. * @param {Boolean} [options.loop=false] Whether during geometry creation a line segment will be added between the last and first line positions to make this Polyline a loop. * @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] Ellipsoid that input positions will be clamped to. * @param {MapProjection} [options.projection] Map Projection for projecting coordinates to 2D. * * @exception {DeveloperError} At least two positions are required. * - * @see GroundPolylineGeometry#createGeometry * @see GroundPolylinePrimitive + * + * @example + * var positions = Cesium.Cartesian3.fromDegreesArray([ + * -112.1340164450331, 36.05494287836128, + * -112.08821010582645, 36.097804071380715, + * -112.13296079730024, 36.168769146801104 + * ]); + * + * var geometry = new Cesium.GroundPolylineGeometry({ + * positions : positions + * }); */ function GroundPolylineGeometry(options) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); @@ -108,6 +118,7 @@ define([ * The distance interval used for interpolating options.points. Zero indicates no interpolation. * Default of 9999.0 allows centimeter accuracy with 32 bit floating point. * @type {Boolean} + * @default 9999.0 */ this.granularity = defaultValue(options.granularity, 9999.0); @@ -115,12 +126,14 @@ define([ * Whether during geometry creation a line segment will be added between the last and first line positions to make this Polyline a loop. * If the geometry has two positions this parameter will be ignored. * @type {Boolean} + * @default false */ this.loop = defaultValue(options.loop, false); /** * Ellipsoid for projecting cartographic coordinates to 3D. * @type {Ellipsoid} + * @default Ellipsoid.WGS84 */ this.ellipsoid = defaultValue(options.ellipsoid, Ellipsoid.WGS84); @@ -136,6 +149,9 @@ define([ } this._projectionIndex = projectionIndex; this._workerName = 'createGroundPolylineGeometry'; + + // Used by GroundPolylinePrimitive to signal worker that scenemode is 3D only. + this._scene3DOnly = false; } defineProperties(GroundPolylineGeometry.prototype, { @@ -148,7 +164,7 @@ define([ */ packedLength: { get: function() { - return 1.0 + this._positions.length * 3 + 1.0 + 1.0 + Ellipsoid.packedLength + 1.0; + return 1.0 + this._positions.length * 3 + 1.0 + 1.0 + Ellipsoid.packedLength + 1.0 + 1.0; } } }); @@ -249,6 +265,7 @@ define([ index += Ellipsoid.packedLength; array[index++] = value._projectionIndex; + array[index++] = value._scene3DOnly ? 1.0 : 0.0; return array; }; @@ -281,15 +298,18 @@ define([ index += Ellipsoid.packedLength; var projectionIndex = array[index++]; + var scene3DOnly = (array[index++] === 1.0); if (!defined(result)) { - return new GroundPolylineGeometry({ + var geometry = new GroundPolylineGeometry({ positions : positions, granularity : granularity, loop : loop, ellipsoid : ellipsoid, projection : new PROJECTIONS[projectionIndex](ellipsoid) }); + geometry._scene3DOnly = scene3DOnly; + return geometry; } result._positions = positions; @@ -297,6 +317,7 @@ define([ result.loop = loop; result.ellipsoid = ellipsoid; result._projectionIndex = projectionIndex; + result._scene3DOnly = scene3DOnly; return result; }; @@ -361,11 +382,13 @@ define([ /** * Computes shadow volumes for the ground polyline, consisting of its vertices, indices, and a bounding sphere. * Vertices are "fat," packing all the data needed in each volume to describe a line on terrain. + * Should not be called independent of {@link GroundPolylinePrimitive}. * * @param {GroundPolylineGeometry} groundPolylineGeometry * @private */ GroundPolylineGeometry.createGeometry = function(groundPolylineGeometry) { + var compute2dAttributes = !groundPolylineGeometry._scene3DOnly; var loop = groundPolylineGeometry.loop; var ellipsoid = groundPolylineGeometry.ellipsoid; var granularity = groundPolylineGeometry.granularity; @@ -517,7 +540,7 @@ define([ cartographicsArray.push(startCartographic.longitude); } - return generateGeometryAttributes(loop, projection, bottomPositionsArray, topPositionsArray, normalsArray, cartographicsArray); + return generateGeometryAttributes(loop, projection, bottomPositionsArray, topPositionsArray, normalsArray, cartographicsArray, compute2dAttributes); }; // If the end normal angle is too steep compared to the direction of the line segment, @@ -677,7 +700,7 @@ define([ // Each shadow volume's vertices encode a description of the line it contains, // including mitering planes at the end points, a plane along the line itself, // and attributes for computing length-wise texture coordinates. - function generateGeometryAttributes(loop, projection, bottomPositionsArray, topPositionsArray, normalsArray, cartographicsArray) { + function generateGeometryAttributes(loop, projection, bottomPositionsArray, topPositionsArray, normalsArray, cartographicsArray, compute2dAttributes) { var i; var index; var ellipsoid = projection.ellipsoid; @@ -691,22 +714,28 @@ define([ var indices = vertexCount > 65535 ? new Uint32Array(indexCount) : new Uint16Array(indexCount); var positionsArray = new Float64Array(vertexCount * 3); - var startHi_and_forwardOffsetX = new Float32Array(arraySizeVec4); - var startLo_and_forwardOffsetY = new Float32Array(arraySizeVec4); - var startNormal_and_forwardOffsetZ = new Float32Array(arraySizeVec4); - var endNormal_and_textureCoordinateNormalizationX = new Float32Array(arraySizeVec4); - var rightNormal_and_textureCoordinateNormalizationY = new Float32Array(arraySizeVec4); - - var startHiLo2D = new Float32Array(arraySizeVec4); - var offsetAndRight2D = new Float32Array(arraySizeVec4); - var startEndNormals2D = new Float32Array(arraySizeVec4); - var texcoordNormalization2D = new Float32Array(vertexCount * 2); + var startHiAndForwardOffsetX = new Float32Array(arraySizeVec4); + var startLoAndForwardOffsetY = new Float32Array(arraySizeVec4); + var startNormalAndForwardOffsetZ = new Float32Array(arraySizeVec4); + var endNormalAndTextureCoordinateNormalizationX = new Float32Array(arraySizeVec4); + var rightNormalAndTextureCoordinateNormalizationY = new Float32Array(arraySizeVec4); + + var startHiLo2D; + var offsetAndRight2D; + var startEndNormals2D; + var texcoordNormalization2D; + + if (compute2dAttributes) { + startHiLo2D = new Float32Array(arraySizeVec4); + offsetAndRight2D = new Float32Array(arraySizeVec4); + startEndNormals2D = new Float32Array(arraySizeVec4); + texcoordNormalization2D = new Float32Array(vertexCount * 2); + } /*** Compute total lengths for texture coordinate normalization ***/ // 2D var cartographicsLength = cartographicsArray.length / 2; var length2D = 0.0; - var length3D = 0.0; var startCartographic = startCartographicScratch; startCartographic.height = 0.0; @@ -716,23 +745,26 @@ define([ var segmentStartCartesian = segmentStartTopScratch; var segmentEndCartesian = segmentEndTopScratch; - index = 0; - for (i = 1; i < cartographicsLength; i++) { - // don't clone anything from previous segment b/c possible IDL touch - startCartographic.latitude = cartographicsArray[index]; - startCartographic.longitude = cartographicsArray[index + 1]; - endCartographic.latitude = cartographicsArray[index + 2]; - endCartographic.longitude = cartographicsArray[index + 3]; - - segmentStartCartesian = projection.project(startCartographic, segmentStartCartesian); - segmentEndCartesian = projection.project(endCartographic, segmentEndCartesian); - length2D += Cartesian3.distance(segmentStartCartesian, segmentEndCartesian); - index += 2; + if (compute2dAttributes) { + index = 0; + for (i = 1; i < cartographicsLength; i++) { + // don't clone anything from previous segment b/c possible IDL touch + startCartographic.latitude = cartographicsArray[index]; + startCartographic.longitude = cartographicsArray[index + 1]; + endCartographic.latitude = cartographicsArray[index + 2]; + endCartographic.longitude = cartographicsArray[index + 3]; + + segmentStartCartesian = projection.project(startCartographic, segmentStartCartesian); + segmentEndCartesian = projection.project(endCartographic, segmentEndCartesian); + length2D += Cartesian3.distance(segmentStartCartesian, segmentEndCartesian); + index += 2; + } } // 3D var positionsLength = topPositionsArray.length / 3; segmentEndCartesian = Cartesian3.unpack(topPositionsArray, 0, segmentEndCartesian); + var length3D = 0.0; index = 3; for (i = 1; i < positionsLength; i++) { @@ -786,35 +818,41 @@ define([ startCartographic.longitude = cartographicsArray[cartographicsIndex + 1]; endCartographic.latitude = cartographicsArray[cartographicsIndex + 2]; endCartographic.longitude = cartographicsArray[cartographicsIndex + 3]; - - var nudgeResult = nudgeCartographic(startCartographic, endCartographic); - var start2D = projection.project(startCartographic, segmentStart2DScratch); - var end2D = projection.project(endCartographic, segmentEnd2DScratch); - var direction2D = direction(end2D, start2D, forwardOffset2DScratch); - direction2D.y = Math.abs(direction2D.y); - - var startGeometryNormal2D = segmentStartNormal2DScratch; - var endGeometryNormal2D = segmentEndNormal2DScratch; - if (nudgeResult === 0 || Cartesian3.dot(direction2D, Cartesian3.UNIT_Y) > MITER_BREAK_SMALL) { - // No nudge - project the original normal - // Or, if the line's angle relative to the IDL is very acute, - // in which case snapping will produce oddly shaped volumes. - startGeometryNormal2D = projectNormal(projection, startCartographic, startGeometryNormal, start2D, segmentStartNormal2DScratch); - endGeometryNormal2D = projectNormal(projection, endCartographic, endGeometryNormal, end2D, segmentEndNormal2DScratch); - } else if (nudgeResult === 1) { - // Start is close to IDL - snap start normal to align with IDL - endGeometryNormal2D = projectNormal(projection, endCartographic, endGeometryNormal, end2D, segmentEndNormal2DScratch); - startGeometryNormal2D.x = 0.0; - // If start longitude is negative and end longitude is less negative, "right" is unit -Y - // If start longitude is positive and end longitude is less positive, "right" is unit +Y - startGeometryNormal2D.y = Math.sign(startCartographic.longitude - Math.abs(endCartographic.longitude)); - startGeometryNormal2D.z = 0.0; - } else { - // End is close to IDL - snap end normal to align with IDL - startGeometryNormal2D = projectNormal(projection, startCartographic, startGeometryNormal, start2D, segmentStartNormal2DScratch); - endGeometryNormal2D.x = 0.0; - endGeometryNormal2D.y = Math.sign(endCartographic.longitude - Math.abs(startCartographic.longitude)); - endGeometryNormal2D.z = 0.0; + var start2D; + var end2D; + var startGeometryNormal2D; + var endGeometryNormal2D; + + if (compute2dAttributes) { + var nudgeResult = nudgeCartographic(startCartographic, endCartographic); + start2D = projection.project(startCartographic, segmentStart2DScratch); + end2D = projection.project(endCartographic, segmentEnd2DScratch); + var direction2D = direction(end2D, start2D, forwardOffset2DScratch); + direction2D.y = Math.abs(direction2D.y); + + startGeometryNormal2D = segmentStartNormal2DScratch; + endGeometryNormal2D = segmentEndNormal2DScratch; + if (nudgeResult === 0 || Cartesian3.dot(direction2D, Cartesian3.UNIT_Y) > MITER_BREAK_SMALL) { + // No nudge - project the original normal + // Or, if the line's angle relative to the IDL is very acute, + // in which case snapping will produce oddly shaped volumes. + startGeometryNormal2D = projectNormal(projection, startCartographic, startGeometryNormal, start2D, segmentStartNormal2DScratch); + endGeometryNormal2D = projectNormal(projection, endCartographic, endGeometryNormal, end2D, segmentEndNormal2DScratch); + } else if (nudgeResult === 1) { + // Start is close to IDL - snap start normal to align with IDL + endGeometryNormal2D = projectNormal(projection, endCartographic, endGeometryNormal, end2D, segmentEndNormal2DScratch); + startGeometryNormal2D.x = 0.0; + // If start longitude is negative and end longitude is less negative, "right" is unit -Y + // If start longitude is positive and end longitude is less positive, "right" is unit +Y + startGeometryNormal2D.y = Math.sign(startCartographic.longitude - Math.abs(endCartographic.longitude)); + startGeometryNormal2D.z = 0.0; + } else { + // End is close to IDL - snap end normal to align with IDL + startGeometryNormal2D = projectNormal(projection, startCartographic, startGeometryNormal, start2D, segmentStartNormal2DScratch); + endGeometryNormal2D.x = 0.0; + endGeometryNormal2D.y = Math.sign(endCartographic.longitude - Math.abs(startCartographic.longitude)); + endGeometryNormal2D.z = 0.0; + } } /**************************************** @@ -854,22 +892,29 @@ define([ var texcoordNormalization3DY = lengthSoFar3D / length3D; /** 2D **/ - // In 2D case, positions and normals can be done as 2 components - var segmentLength2D = Cartesian3.distance(start2D, end2D); - - var encodedStart2D = EncodedCartesian3.fromCartesian(start2D, encodeScratch2D); - var forwardOffset2D = Cartesian3.subtract(end2D, start2D, forwardOffset2DScratch); - - // Right direction is just forward direction rotated by -90 degrees around Z - // Similarly with plane normals - var right2D = Cartesian3.normalize(forwardOffset2D, right2DScratch); - var swap = right2D.x; - right2D.x = right2D.y; - right2D.y = -swap; - - var texcoordNormalization2DX = segmentLength2D / length2D; - var texcoordNormalization2DY = lengthSoFar2D / length2D; - + var segmentLength2D = 0.0; + var encodedStart2D; + var forwardOffset2D; + var right2D; + var texcoordNormalization2DX = 0.0; + var texcoordNormalization2DY = 0.0; + if (compute2dAttributes) { + // In 2D case, positions and normals can be done as 2 components + segmentLength2D = Cartesian3.distance(start2D, end2D); + + encodedStart2D = EncodedCartesian3.fromCartesian(start2D, encodeScratch2D); + forwardOffset2D = Cartesian3.subtract(end2D, start2D, forwardOffset2DScratch); + + // Right direction is just forward direction rotated by -90 degrees around Z + // Similarly with plane normals + right2D = Cartesian3.normalize(forwardOffset2D, right2DScratch); + var swap = right2D.x; + right2D.x = right2D.y; + right2D.y = -swap; + + texcoordNormalization2DX = segmentLength2D / length2D; + texcoordNormalization2DY = lengthSoFar2D / length2D; + } /** Pack **/ for (j = 0; j < 8; j++) { var vec4Index = vec4sWriteIndex + j * 4; @@ -880,39 +925,41 @@ define([ var sidedness = j < 4 ? 1.0 : -1.0; // 3D - Cartesian3.pack(encodedStart.high, startHi_and_forwardOffsetX, vec4Index); - startHi_and_forwardOffsetX[wIndex] = forwardOffset.x; + Cartesian3.pack(encodedStart.high, startHiAndForwardOffsetX, vec4Index); + startHiAndForwardOffsetX[wIndex] = forwardOffset.x; - Cartesian3.pack(encodedStart.low, startLo_and_forwardOffsetY, vec4Index); - startLo_and_forwardOffsetY[wIndex] = forwardOffset.y; + Cartesian3.pack(encodedStart.low, startLoAndForwardOffsetY, vec4Index); + startLoAndForwardOffsetY[wIndex] = forwardOffset.y; - Cartesian3.pack(startPlaneNormal, startNormal_and_forwardOffsetZ, vec4Index); - startNormal_and_forwardOffsetZ[wIndex] = forwardOffset.z; + Cartesian3.pack(startPlaneNormal, startNormalAndForwardOffsetZ, vec4Index); + startNormalAndForwardOffsetZ[wIndex] = forwardOffset.z; - Cartesian3.pack(endPlaneNormal, endNormal_and_textureCoordinateNormalizationX, vec4Index); - endNormal_and_textureCoordinateNormalizationX[wIndex] = texcoordNormalization3DX * sidedness; + Cartesian3.pack(endPlaneNormal, endNormalAndTextureCoordinateNormalizationX, vec4Index); + endNormalAndTextureCoordinateNormalizationX[wIndex] = texcoordNormalization3DX * sidedness; - Cartesian3.pack(rightNormal, rightNormal_and_textureCoordinateNormalizationY, vec4Index); - rightNormal_and_textureCoordinateNormalizationY[wIndex] = texcoordNormalization3DY; + Cartesian3.pack(rightNormal, rightNormalAndTextureCoordinateNormalizationY, vec4Index); + rightNormalAndTextureCoordinateNormalizationY[wIndex] = texcoordNormalization3DY; // 2D - startHiLo2D[vec4Index] = encodedStart2D.high.x; - startHiLo2D[vec4Index + 1] = encodedStart2D.high.y; - startHiLo2D[vec4Index + 2] = encodedStart2D.low.x; - startHiLo2D[vec4Index + 3] = encodedStart2D.low.y; - - startEndNormals2D[vec4Index] = -startGeometryNormal2D.y; - startEndNormals2D[vec4Index + 1] = startGeometryNormal2D.x; - startEndNormals2D[vec4Index + 2] = endGeometryNormal2D.y; - startEndNormals2D[vec4Index + 3] = -endGeometryNormal2D.x; - - offsetAndRight2D[vec4Index] = forwardOffset2D.x; - offsetAndRight2D[vec4Index + 1] = forwardOffset2D.y; - offsetAndRight2D[vec4Index + 2] = right2D.x; - offsetAndRight2D[vec4Index + 3] = right2D.y; - - texcoordNormalization2D[vec2Index] = texcoordNormalization2DX * sidedness; - texcoordNormalization2D[vec2Index + 1] = texcoordNormalization2DY; + if (compute2dAttributes) { + startHiLo2D[vec4Index] = encodedStart2D.high.x; + startHiLo2D[vec4Index + 1] = encodedStart2D.high.y; + startHiLo2D[vec4Index + 2] = encodedStart2D.low.x; + startHiLo2D[vec4Index + 3] = encodedStart2D.low.y; + + startEndNormals2D[vec4Index] = -startGeometryNormal2D.y; + startEndNormals2D[vec4Index + 1] = startGeometryNormal2D.x; + startEndNormals2D[vec4Index + 2] = endGeometryNormal2D.y; + startEndNormals2D[vec4Index + 3] = -endGeometryNormal2D.x; + + offsetAndRight2D[vec4Index] = forwardOffset2D.x; + offsetAndRight2D[vec4Index + 1] = forwardOffset2D.y; + offsetAndRight2D[vec4Index + 2] = right2D.x; + offsetAndRight2D[vec4Index + 3] = right2D.y; + + texcoordNormalization2D[vec2Index] = texcoordNormalization2DX * sidedness; + texcoordNormalization2D[vec2Index + 1] = texcoordNormalization2DY; + } } /**************************************************************** @@ -998,30 +1045,34 @@ define([ BoundingSphere.fromVertices(topPositionsArray, Cartesian3.ZERO, 3, boundingSpheres[1]); var boundingSphere = BoundingSphere.fromBoundingSpheres(boundingSpheres); + var attributes = { + position : new GeometryAttribute({ + componentDatatype : ComponentDatatype.DOUBLE, + componentsPerAttribute : 3, + normalize : false, + values : positionsArray + }), + startHiAndForwardOffsetX : getVec4GeometryAttribute(startHiAndForwardOffsetX), + startLoAndForwardOffsetY : getVec4GeometryAttribute(startLoAndForwardOffsetY), + startNormalAndForwardOffsetZ : getVec4GeometryAttribute(startNormalAndForwardOffsetZ), + endNormalAndTextureCoordinateNormalizationX : getVec4GeometryAttribute(endNormalAndTextureCoordinateNormalizationX), + rightNormalAndTextureCoordinateNormalizationY : getVec4GeometryAttribute(rightNormalAndTextureCoordinateNormalizationY) + }; + + if (compute2dAttributes) { + attributes.startHiLo2D = getVec4GeometryAttribute(startHiLo2D); + attributes.offsetAndRight2D = getVec4GeometryAttribute(offsetAndRight2D); + attributes.startEndNormals2D = getVec4GeometryAttribute(startEndNormals2D); + attributes.texcoordNormalization2D = new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 2, + normalize : false, + values : texcoordNormalization2D + }); + } + return new Geometry({ - attributes : { - position : new GeometryAttribute({ - componentDatatype : ComponentDatatype.DOUBLE, - componentsPerAttribute : 3, - normalize : false, - values : positionsArray - }), - startHi_and_forwardOffsetX : getVec4GeometryAttribute(startHi_and_forwardOffsetX), - startLo_and_forwardOffsetY : getVec4GeometryAttribute(startLo_and_forwardOffsetY), - startNormal_and_forwardOffsetZ : getVec4GeometryAttribute(startNormal_and_forwardOffsetZ), - endNormal_and_textureCoordinateNormalizationX : getVec4GeometryAttribute(endNormal_and_textureCoordinateNormalizationX), - rightNormal_and_textureCoordinateNormalizationY : getVec4GeometryAttribute(rightNormal_and_textureCoordinateNormalizationY), - - startHiLo2D : getVec4GeometryAttribute(startHiLo2D), - offsetAndRight2D : getVec4GeometryAttribute(offsetAndRight2D), - startEndNormals2D : getVec4GeometryAttribute(startEndNormals2D), - texcoordNormalization2D : new GeometryAttribute({ - componentDatatype : ComponentDatatype.FLOAT, - componentsPerAttribute : 2, - normalize : false, - values : texcoordNormalization2D - }) - }, + attributes : attributes, indices : indices, boundingSphere : boundingSphere }); diff --git a/Source/Scene/GroundPolylinePrimitive.js b/Source/Scene/GroundPolylinePrimitive.js index 5742385c33b4..5ad99f543610 100644 --- a/Source/Scene/GroundPolylinePrimitive.js +++ b/Source/Scene/GroundPolylinePrimitive.js @@ -66,7 +66,7 @@ define([ * A GroundPolylinePrimitive represents a polyline draped over the terrain in the {@link Scene}. *

* - * Only to be used with GeometryInstances containing GroundPolylineGeometries + * Only to be used with GeometryInstances containing {@link GroundPolylineGeometry} * * @param {Object} [options] Object with the following properties: * @param {Array|GeometryInstance} [options.geometryInstances] GeometryInstances containing GroundPolylineGeometry @@ -397,20 +397,27 @@ define([ // which causes problems when interpolating log depth from vertices. // So force computing and writing log depth in the fragment shader. // Re-enable at far distances to avoid z-fighting. - var colorDefine = appearance instanceof PolylineColorAppearance ? 'PER_INSTANCE_COLOR' : ''; - var vsDefines = ['ENABLE_GL_POSITION_LOG_DEPTH_AT_HEIGHT', colorDefine]; - var fsDefines = groundPolylinePrimitive.debugShowShadowVolume ? ['DEBUG_SHOW_VOLUME', colorDefine] : [colorDefine]; - var materialShaderSource = defined(appearance.material) ? appearance.material.shaderSource : ''; - - // Check for use of v_width and v_polylineAngle in material shader - // to determine whether these varyings should be active in the vertex shader. - if (materialShaderSource.search(/varying\s+float\s+v_polylineAngle;/g) === -1) { - vsDefines.push('ANGLE_VARYING'); - } - if (materialShaderSource.search(/varying\s+float\s+v_width;/g) === -1) { - vsDefines.push('WIDTH_VARYING'); + var vsDefines = ['ENABLE_GL_POSITION_LOG_DEPTH_AT_HEIGHT']; + var colorDefine = ''; + var materialShaderSource = ''; + if (defined(appearance.material)) { + materialShaderSource = defined(appearance.material) ? appearance.material.shaderSource : ''; + + // Check for use of v_width and v_polylineAngle in material shader + // to determine whether these varyings should be active in the vertex shader. + if (materialShaderSource.search(/varying\s+float\s+v_polylineAngle;/g) === -1) { + vsDefines.push('ANGLE_VARYING'); + } + if (materialShaderSource.search(/varying\s+float\s+v_width;/g) === -1) { + vsDefines.push('WIDTH_VARYING'); + } + } else { + colorDefine = 'PER_INSTANCE_COLOR'; } + vsDefines.push(colorDefine); + var fsDefines = groundPolylinePrimitive.debugShowShadowVolume ? ['DEBUG_SHOW_VOLUME', colorDefine] : [colorDefine]; + var vsColor3D = new ShaderSource({ defines : vsDefines, sources : [vs] @@ -498,7 +505,7 @@ define([ command.shaderProgram = groundPolylinePrimitive._sp; command.uniformMap = uniformMap; command.pass = pass; - command.pickId = 'czm_batchTable_pickColor(v_endPlaneNormalEC_and_batchId.w)'; + command.pickId = 'czm_batchTable_pickColor(v_endPlaneNormalEcAndBatchId.w)'; // derive for 2D var derivedColorCommand = command.derivedCommands.color2D; @@ -511,7 +518,7 @@ define([ derivedColorCommand.shaderProgram = groundPolylinePrimitive._sp2D; derivedColorCommand.uniformMap = uniformMap; derivedColorCommand.pass = pass; - derivedColorCommand.pickId = 'czm_batchTable_pickColor(v_endPlaneNormalEC_and_batchId.w)'; + derivedColorCommand.pickId = 'czm_batchTable_pickColor(v_endPlaneNormalEcAndBatchId.w)'; // derive for Morph derivedColorCommand = command.derivedCommands.colorMorph; @@ -633,6 +640,9 @@ define([ }); } + // Update each geometry for framestate.scene3DOnly = true + geometryInstance.geometry._scene3DOnly = frameState.scene3DOnly; + groundInstances[i] = new GeometryInstance({ geometry : geometryInstance.geometry, attributes : attributes, diff --git a/Source/Scene/PolylineColorAppearance.js b/Source/Scene/PolylineColorAppearance.js index ceafe597913f..1d774cb031c6 100644 --- a/Source/Scene/PolylineColorAppearance.js +++ b/Source/Scene/PolylineColorAppearance.js @@ -20,7 +20,8 @@ define([ var defaultFragmentShaderSource = PerInstanceFlatColorAppearanceFS; /** - * An appearance for {@link GeometryInstance} instances with color attributes and {@link PolylineGeometry}. + * An appearance for {@link GeometryInstance} instances with color attributes and + * {@link PolylineGeometry} or {@link GroundPolylineGeometry}. * This allows several geometry instances, each with a different color, to * be drawn with the same {@link Primitive}. * diff --git a/Source/Shaders/Builtin/Functions/planeDistance.glsl b/Source/Shaders/Builtin/Functions/planeDistance.glsl index 82d713cd5fc9..7ebb5f46f2f5 100644 --- a/Source/Shaders/Builtin/Functions/planeDistance.glsl +++ b/Source/Shaders/Builtin/Functions/planeDistance.glsl @@ -1,5 +1,5 @@ /** - * Computes distance from an point to a plane, typically in eye space. + * Computes distance from a point to a plane. * * @name czm_planeDistance * @glslFunction @@ -13,7 +13,7 @@ float czm_planeDistance(vec4 plane, vec3 point) { } /** - * Computes distance from an point to a plane, typically in eye space. + * Computes distance from a point to a plane. * * @name czm_planeDistance * @glslFunction diff --git a/Source/Shaders/PolylineShadowVolumeFS.glsl b/Source/Shaders/PolylineShadowVolumeFS.glsl index 61c52fd8af61..ce583d3da4cd 100644 --- a/Source/Shaders/PolylineShadowVolumeFS.glsl +++ b/Source/Shaders/PolylineShadowVolumeFS.glsl @@ -1,12 +1,8 @@ -#ifdef GL_EXT_frag_depth -#extension GL_EXT_frag_depth : enable -#endif - -varying vec4 v_startPlaneNormalEC_and_halfWidth; -varying vec4 v_endPlaneNormalEC_and_batchId; +varying vec4 v_startPlaneNormalEcAndHalfWidth; +varying vec4 v_endPlaneNormalEcAndBatchId; varying vec4 v_rightPlaneEC; // Technically can compute distance for this here -varying vec4 v_ecEnd_and_ecStart_X; -varying vec4 v_texcoordNormalization_and_ecStart_YZ; +varying vec4 v_endEcAndStartEcX; +varying vec4 v_texcoordNormalizationAndStartEcYZ; #ifdef PER_INSTANCE_COLOR varying vec4 v_color; @@ -15,21 +11,21 @@ varying vec4 v_color; void main(void) { float logDepthOrDepth = czm_unpackDepth(texture2D(czm_globeDepthTexture, gl_FragCoord.xy / czm_viewport.zw)); - vec3 ecStart = vec3(v_ecEnd_and_ecStart_X.w, v_texcoordNormalization_and_ecStart_YZ.zw); + vec3 ecStart = vec3(v_endEcAndStartEcX.w, v_texcoordNormalizationAndStartEcYZ.zw); // Discard for sky - bool shouldDiscard = logDepthOrDepth == 0.0; + bool shouldDiscard = (logDepthOrDepth == 0.0); vec4 eyeCoordinate = czm_windowToEyeCoordinates(gl_FragCoord.xy, logDepthOrDepth); eyeCoordinate /= eyeCoordinate.w; - float halfMaxWidth = v_startPlaneNormalEC_and_halfWidth.w * czm_metersPerPixel(eyeCoordinate); + float halfMaxWidth = v_startPlaneNormalEcAndHalfWidth.w * czm_metersPerPixel(eyeCoordinate); // Check distance of the eye coordinate against the right-facing plane float widthwiseDistance = czm_planeDistance(v_rightPlaneEC, eyeCoordinate.xyz); // Check eye coordinate against the mitering planes - float distanceFromStart = czm_planeDistance(v_startPlaneNormalEC_and_halfWidth.xyz, -dot(ecStart, v_startPlaneNormalEC_and_halfWidth.xyz), eyeCoordinate.xyz); - float distanceFromEnd = czm_planeDistance(v_endPlaneNormalEC_and_batchId.xyz, -dot(v_ecEnd_and_ecStart_X.xyz, v_endPlaneNormalEC_and_batchId.xyz), eyeCoordinate.xyz); + float distanceFromStart = czm_planeDistance(v_startPlaneNormalEcAndHalfWidth.xyz, -dot(ecStart, v_startPlaneNormalEcAndHalfWidth.xyz), eyeCoordinate.xyz); + float distanceFromEnd = czm_planeDistance(v_endPlaneNormalEcAndBatchId.xyz, -dot(v_endEcAndStartEcX.xyz, v_endPlaneNormalEcAndBatchId.xyz), eyeCoordinate.xyz); shouldDiscard = shouldDiscard || (abs(widthwiseDistance) > halfMaxWidth || distanceFromStart < 0.0 || distanceFromEnd < 0.0); @@ -40,14 +36,14 @@ void main(void) vec3 alignedPlaneNormal; // start aligned plane - alignedPlaneNormal = cross(v_rightPlaneEC.xyz, v_startPlaneNormalEC_and_halfWidth.xyz); + alignedPlaneNormal = cross(v_rightPlaneEC.xyz, v_startPlaneNormalEcAndHalfWidth.xyz); alignedPlaneNormal = normalize(cross(alignedPlaneNormal, v_rightPlaneEC.xyz)); distanceFromStart = czm_planeDistance(alignedPlaneNormal, -dot(alignedPlaneNormal, ecStart), eyeCoordinate.xyz); // end aligned plane - alignedPlaneNormal = cross(v_rightPlaneEC.xyz, v_endPlaneNormalEC_and_batchId.xyz); + alignedPlaneNormal = cross(v_rightPlaneEC.xyz, v_endPlaneNormalEcAndBatchId.xyz); alignedPlaneNormal = normalize(cross(alignedPlaneNormal, v_rightPlaneEC.xyz)); - distanceFromEnd = czm_planeDistance(alignedPlaneNormal, -dot(alignedPlaneNormal, v_ecEnd_and_ecStart_X.xyz), eyeCoordinate.xyz); + distanceFromEnd = czm_planeDistance(alignedPlaneNormal, -dot(alignedPlaneNormal, v_endEcAndStartEcX.xyz), eyeCoordinate.xyz); shouldDiscard = shouldDiscard || distanceFromStart < -halfMaxWidth || distanceFromEnd < -halfMaxWidth; @@ -66,7 +62,7 @@ void main(void) // Clamp - distance to aligned planes may be negative due to mitering, // so fragment texture coordinate might be out-of-bounds. float s = clamp(distanceFromStart / (distanceFromStart + distanceFromEnd), 0.0, 1.0); - s = (s * v_texcoordNormalization_and_ecStart_YZ.x) + v_texcoordNormalization_and_ecStart_YZ.y; + s = (s * v_texcoordNormalizationAndStartEcYZ.x) + v_texcoordNormalizationAndStartEcYZ.y; float t = (widthwiseDistance + halfMaxWidth) / (2.0 * halfMaxWidth); czm_materialInput materialInput; diff --git a/Source/Shaders/PolylineShadowVolumeMorphFS.glsl b/Source/Shaders/PolylineShadowVolumeMorphFS.glsl index 354fe28befba..9cf2f6b6dec8 100644 --- a/Source/Shaders/PolylineShadowVolumeMorphFS.glsl +++ b/Source/Shaders/PolylineShadowVolumeMorphFS.glsl @@ -1,7 +1,3 @@ -#ifdef GL_EXT_frag_depth -#extension GL_EXT_frag_depth : enable -#endif - varying vec3 v_forwardDirectionEC; varying vec3 v_texcoordNormalization_and_halfWidth; varying float v_batchId; @@ -23,9 +19,6 @@ void main(void) vec4 eyeCoordinate = gl_FragCoord; eyeCoordinate /= eyeCoordinate.w; -#ifdef PICK - gl_FragColor.a = 1.0; -#else // PICK #ifdef PER_INSTANCE_COLOR gl_FragColor = v_color; #else // PER_INSTANCE_COLOR @@ -49,6 +42,4 @@ void main(void) czm_material material = czm_getMaterial(materialInput); gl_FragColor = vec4(material.diffuse + material.emission, material.alpha); #endif // PER_INSTANCE_COLOR - -#endif // PICK } diff --git a/Source/Shaders/PolylineShadowVolumeMorphVS.glsl b/Source/Shaders/PolylineShadowVolumeMorphVS.glsl index 0d670af7aff4..f3fd713fefff 100644 --- a/Source/Shaders/PolylineShadowVolumeMorphVS.glsl +++ b/Source/Shaders/PolylineShadowVolumeMorphVS.glsl @@ -1,11 +1,11 @@ attribute vec3 position3DHigh; attribute vec3 position3DLow; -attribute vec4 startHi_and_forwardOffsetX; -attribute vec4 startLo_and_forwardOffsetY; -attribute vec4 startNormal_and_forwardOffsetZ; -attribute vec4 endNormal_and_textureCoordinateNormalizationX; -attribute vec4 rightNormal_and_textureCoordinateNormalizationY; +attribute vec4 startHiAndForwardOffsetX; +attribute vec4 startLoAndForwardOffsetY; +attribute vec4 startNormalAndForwardOffsetZ; +attribute vec4 endNormalAndTextureCoordinateNormalizationX; +attribute vec4 rightNormalAndTextureCoordinateNormalizationY; attribute vec4 startHiLo2D; attribute vec4 offsetAndRight2D; attribute vec4 startEndNormals2D; @@ -40,7 +40,7 @@ void main() // Start position vec4 posRelativeToEye2D = czm_translateRelativeToEye(vec3(0.0, startHiLo2D.xy), vec3(0.0, startHiLo2D.zw)); - vec4 posRelativeToEye3D = czm_translateRelativeToEye(startHi_and_forwardOffsetX.xyz, startLo_and_forwardOffsetY.xyz); + vec4 posRelativeToEye3D = czm_translateRelativeToEye(startHiAndForwardOffsetX.xyz, startLoAndForwardOffsetY.xyz); vec4 posRelativeToEye = czm_columbusViewMorph(posRelativeToEye2D, posRelativeToEye3D, czm_morphTime); vec3 ecPos2D = (czm_modelViewRelativeToEye * posRelativeToEye2D).xyz; vec3 ecPos3D = (czm_modelViewRelativeToEye * posRelativeToEye3D).xyz; @@ -50,7 +50,7 @@ void main() vec4 startPlane2D; vec4 startPlane3D; startPlane2D.xyz = czm_normal * vec3(0.0, startEndNormals2D.xy); - startPlane3D.xyz = czm_normal * startNormal_and_forwardOffsetZ.xyz; + startPlane3D.xyz = czm_normal * startNormalAndForwardOffsetZ.xyz; startPlane2D.w = -dot(startPlane2D.xyz, ecPos2D); startPlane3D.w = -dot(startPlane3D.xyz, ecPos3D); @@ -58,13 +58,13 @@ void main() vec4 rightPlane2D; vec4 rightPlane3D; rightPlane2D.xyz = czm_normal * vec3(0.0, offsetAndRight2D.zw); - rightPlane3D.xyz = czm_normal * rightNormal_and_textureCoordinateNormalizationY.xyz; + rightPlane3D.xyz = czm_normal * rightNormalAndTextureCoordinateNormalizationY.xyz; rightPlane2D.w = -dot(rightPlane2D.xyz, ecPos2D); rightPlane3D.w = -dot(rightPlane3D.xyz, ecPos3D); // End position posRelativeToEye2D = posRelativeToEye2D + vec4(0.0, offsetAndRight2D.xy, 0.0); - posRelativeToEye3D = posRelativeToEye3D + vec4(startHi_and_forwardOffsetX.w, startLo_and_forwardOffsetY.w, startNormal_and_forwardOffsetZ.w, 0.0); + posRelativeToEye3D = posRelativeToEye3D + vec4(startHiAndForwardOffsetX.w, startLoAndForwardOffsetY.w, startNormalAndForwardOffsetZ.w, 0.0); posRelativeToEye = czm_columbusViewMorph(posRelativeToEye2D, posRelativeToEye3D, czm_morphTime); ecPos2D = (czm_modelViewRelativeToEye * posRelativeToEye2D).xyz; ecPos3D = (czm_modelViewRelativeToEye * posRelativeToEye3D).xyz; @@ -74,7 +74,7 @@ void main() vec4 endPlane2D; vec4 endPlane3D; endPlane2D.xyz = czm_normal * vec3(0.0, startEndNormals2D.zw); - endPlane3D.xyz = czm_normal * endNormal_and_textureCoordinateNormalizationX.xyz; + endPlane3D.xyz = czm_normal * endNormalAndTextureCoordinateNormalizationX.xyz; endPlane2D.w = -dot(endPlane2D.xyz, ecPos2D); endPlane3D.w = -dot(endPlane3D.xyz, ecPos3D); @@ -83,7 +83,7 @@ void main() v_texcoordNormalization_and_halfWidth.xy = mix( vec2(abs(texcoordNormalization2D.x), texcoordNormalization2D.y), - vec2(abs(endNormal_and_textureCoordinateNormalizationX.w), rightNormal_and_textureCoordinateNormalizationY.w), czm_morphTime); + vec2(abs(endNormalAndTextureCoordinateNormalizationX.w), rightNormalAndTextureCoordinateNormalizationY.w), czm_morphTime); #ifdef PER_INSTANCE_COLOR v_color = czm_batchTable_color(batchId); @@ -119,7 +119,7 @@ void main() vec3 normalEC = normalize(cross(planeDirection, upOrDown)); // In practice, the opposite seems to work too. // Determine if this vertex is on the "left" or "right" - normalEC *= sign(endNormal_and_textureCoordinateNormalizationX.w); + normalEC *= sign(endNormalAndTextureCoordinateNormalizationX.w); // A "perfect" implementation would push along normals according to the angle against forward. // In practice, just pushing the normal out by halfWidth is sufficient for morph views. diff --git a/Source/Shaders/PolylineShadowVolumeVS.glsl b/Source/Shaders/PolylineShadowVolumeVS.glsl index 87566e9b02ad..e603d815c984 100644 --- a/Source/Shaders/PolylineShadowVolumeVS.glsl +++ b/Source/Shaders/PolylineShadowVolumeVS.glsl @@ -2,11 +2,11 @@ attribute vec3 position3DHigh; attribute vec3 position3DLow; #ifndef COLUMBUS_VIEW_2D -attribute vec4 startHi_and_forwardOffsetX; -attribute vec4 startLo_and_forwardOffsetY; -attribute vec4 startNormal_and_forwardOffsetZ; -attribute vec4 endNormal_and_textureCoordinateNormalizationX; -attribute vec4 rightNormal_and_textureCoordinateNormalizationY; +attribute vec4 startHiAndForwardOffsetX; +attribute vec4 startLoAndForwardOffsetY; +attribute vec4 startNormalAndForwardOffsetZ; +attribute vec4 endNormalAndTextureCoordinateNormalizationX; +attribute vec4 rightNormalAndTextureCoordinateNormalizationY; #else attribute vec4 startHiLo2D; attribute vec4 offsetAndRight2D; @@ -16,11 +16,11 @@ attribute vec2 texcoordNormalization2D; attribute float batchId; -varying vec4 v_startPlaneNormalEC_and_halfWidth; -varying vec4 v_endPlaneNormalEC_and_batchId; +varying vec4 v_startPlaneNormalEcAndHalfWidth; +varying vec4 v_endPlaneNormalEcAndBatchId; varying vec4 v_rightPlaneEC; -varying vec4 v_ecEnd_and_ecStart_X; -varying vec4 v_texcoordNormalization_and_ecStart_YZ; +varying vec4 v_endEcAndStartEcX; +varying vec4 v_texcoordNormalizationAndStartEcYZ; // For materials #ifdef WIDTH_VARYING @@ -57,36 +57,36 @@ void main() endPlaneEC.xyz = czm_normal * vec3(0.0, startEndNormals2D.zw); endPlaneEC.w = -dot(endPlaneEC.xyz, ecEnd); - v_texcoordNormalization_and_ecStart_YZ.xy = vec2(abs(texcoordNormalization2D.x), texcoordNormalization2D.y); + v_texcoordNormalizationAndStartEcYZ.xy = vec2(abs(texcoordNormalization2D.x), texcoordNormalization2D.y); #else // COLUMBUS_VIEW_2D - vec3 ecStart = (czm_modelViewRelativeToEye * czm_translateRelativeToEye(startHi_and_forwardOffsetX.xyz, startLo_and_forwardOffsetY.xyz)).xyz; - vec3 offset = czm_normal * vec3(startHi_and_forwardOffsetX.w, startLo_and_forwardOffsetY.w, startNormal_and_forwardOffsetZ.w); + vec3 ecStart = (czm_modelViewRelativeToEye * czm_translateRelativeToEye(startHiAndForwardOffsetX.xyz, startLoAndForwardOffsetY.xyz)).xyz; + vec3 offset = czm_normal * vec3(startHiAndForwardOffsetX.w, startLoAndForwardOffsetY.w, startNormalAndForwardOffsetZ.w); vec3 ecEnd = ecStart + offset; vec3 forwardDirectionEC = normalize(offset); // start plane vec4 startPlaneEC; - startPlaneEC.xyz = czm_normal * startNormal_and_forwardOffsetZ.xyz; + startPlaneEC.xyz = czm_normal * startNormalAndForwardOffsetZ.xyz; startPlaneEC.w = -dot(startPlaneEC.xyz, ecStart); // end plane vec4 endPlaneEC; - endPlaneEC.xyz = czm_normal * endNormal_and_textureCoordinateNormalizationX.xyz; + endPlaneEC.xyz = czm_normal * endNormalAndTextureCoordinateNormalizationX.xyz; endPlaneEC.w = -dot(endPlaneEC.xyz, ecEnd); // Right plane - v_rightPlaneEC.xyz = czm_normal * rightNormal_and_textureCoordinateNormalizationY.xyz; + v_rightPlaneEC.xyz = czm_normal * rightNormalAndTextureCoordinateNormalizationY.xyz; v_rightPlaneEC.w = -dot(v_rightPlaneEC.xyz, ecStart); - v_texcoordNormalization_and_ecStart_YZ.xy = vec2(abs(endNormal_and_textureCoordinateNormalizationX.w), rightNormal_and_textureCoordinateNormalizationY.w); + v_texcoordNormalizationAndStartEcYZ.xy = vec2(abs(endNormalAndTextureCoordinateNormalizationX.w), rightNormalAndTextureCoordinateNormalizationY.w); #endif // COLUMBUS_VIEW_2D - v_ecEnd_and_ecStart_X.xyz = ecEnd; - v_ecEnd_and_ecStart_X.w = ecStart.x; - v_texcoordNormalization_and_ecStart_YZ.zw = ecStart.yz; + v_endEcAndStartEcX.xyz = ecEnd; + v_endEcAndStartEcX.w = ecStart.x; + v_texcoordNormalizationAndStartEcYZ.zw = ecStart.yz; #ifdef PER_INSTANCE_COLOR v_color = czm_batchTable_color(batchId); @@ -126,11 +126,11 @@ void main() v_width = width; #endif - v_startPlaneNormalEC_and_halfWidth.xyz = startPlaneEC.xyz; - v_startPlaneNormalEC_and_halfWidth.w = width * 0.5; + v_startPlaneNormalEcAndHalfWidth.xyz = startPlaneEC.xyz; + v_startPlaneNormalEcAndHalfWidth.w = width * 0.5; - v_endPlaneNormalEC_and_batchId.xyz = endPlaneEC.xyz; - v_endPlaneNormalEC_and_batchId.w = batchId; + v_endPlaneNormalEcAndBatchId.xyz = endPlaneEC.xyz; + v_endPlaneNormalEcAndBatchId.w = batchId; width = width * max(0.0, czm_metersPerPixel(positionEC)); // width = distance to push along R width = width / dot(normalEC, v_rightPlaneEC.xyz); // width = distance to push along N @@ -139,7 +139,7 @@ void main() #ifdef COLUMBUS_VIEW_2D normalEC *= sign(texcoordNormalization2D.x); #else - normalEC *= sign(endNormal_and_textureCoordinateNormalizationX.w); + normalEC *= sign(endNormalAndTextureCoordinateNormalizationX.w); #endif positionEC.xyz += width * normalEC; diff --git a/Specs/Core/GroundPolylineGeometrySpec.js b/Specs/Core/GroundPolylineGeometrySpec.js index c0e318175d32..a656200f1950 100644 --- a/Specs/Core/GroundPolylineGeometrySpec.js +++ b/Specs/Core/GroundPolylineGeometrySpec.js @@ -62,30 +62,30 @@ defineSuite([ expect(geometry.indices.length).toEqual(36); expect(geometry.attributes.position.values.length).toEqual(24); - var startHi_and_forwardOffsetX = geometry.attributes.startHi_and_forwardOffsetX; - var startLo_and_forwardOffsetY = geometry.attributes.startLo_and_forwardOffsetY; - var startNormal_and_forwardOffsetZ = geometry.attributes.startNormal_and_forwardOffsetZ; - var endNormal_and_textureCoordinateNormalizationX = geometry.attributes.endNormal_and_textureCoordinateNormalizationX; - var rightNormal_and_textureCoordinateNormalizationY = geometry.attributes.rightNormal_and_textureCoordinateNormalizationY; + var startHiAndForwardOffsetX = geometry.attributes.startHiAndForwardOffsetX; + var startLoAndForwardOffsetY = geometry.attributes.startLoAndForwardOffsetY; + var startNormalAndForwardOffsetZ = geometry.attributes.startNormalAndForwardOffsetZ; + var endNormalAndTextureCoordinateNormalizationX = geometry.attributes.endNormalAndTextureCoordinateNormalizationX; + var rightNormalAndTextureCoordinateNormalizationY = geometry.attributes.rightNormalAndTextureCoordinateNormalizationY; var startHiLo2D = geometry.attributes.startHiLo2D; var offsetAndRight2D = geometry.attributes.offsetAndRight2D; var startEndNormals2D = geometry.attributes.startEndNormals2D; var texcoordNormalization2D = geometry.attributes.texcoordNormalization2D; // Expect each entry in the additional attributes to be identical across all vertices since this is a single segment, - // except endNormal_and_textureCoordinateNormalizationX and texcoordNormalization2D, which should be "sided" - verifyAttributeValuesIdentical(startHi_and_forwardOffsetX); - verifyAttributeValuesIdentical(startLo_and_forwardOffsetY); - verifyAttributeValuesIdentical(startNormal_and_forwardOffsetZ); - verifyAttributeValuesIdentical(rightNormal_and_textureCoordinateNormalizationY); + // except endNormalAndTextureCoordinateNormalizationX and texcoordNormalization2D, which should be "sided" + verifyAttributeValuesIdentical(startHiAndForwardOffsetX); + verifyAttributeValuesIdentical(startLoAndForwardOffsetY); + verifyAttributeValuesIdentical(startNormalAndForwardOffsetZ); + verifyAttributeValuesIdentical(rightNormalAndTextureCoordinateNormalizationY); verifyAttributeValuesIdentical(startHiLo2D); verifyAttributeValuesIdentical(offsetAndRight2D); verifyAttributeValuesIdentical(startEndNormals2D); - // Expect endNormal_and_textureCoordinateNormalizationX and texcoordNormalization2D.x to encode the "side" of the geometry + // Expect endNormalAndTextureCoordinateNormalizationX and texcoordNormalization2D.x to encode the "side" of the geometry var i; var index; - var values = endNormal_and_textureCoordinateNormalizationX.values; + var values = endNormalAndTextureCoordinateNormalizationX.values; for (i = 0; i < 4; i++) { index = i * 4 + 3; expect(Math.sign(values[index])).toEqual(1.0); @@ -112,32 +112,32 @@ defineSuite([ // - a right-facing normal // - parameters for localizing the position along the line to texture coordinates var startPosition3D = new Cartesian3(); - startPosition3D.x = startHi_and_forwardOffsetX.values[0] + startLo_and_forwardOffsetY.values[0]; - startPosition3D.y = startHi_and_forwardOffsetX.values[1] + startLo_and_forwardOffsetY.values[1]; - startPosition3D.z = startHi_and_forwardOffsetX.values[2] + startLo_and_forwardOffsetY.values[2]; + startPosition3D.x = startHiAndForwardOffsetX.values[0] + startLoAndForwardOffsetY.values[0]; + startPosition3D.y = startHiAndForwardOffsetX.values[1] + startLoAndForwardOffsetY.values[1]; + startPosition3D.z = startHiAndForwardOffsetX.values[2] + startLoAndForwardOffsetY.values[2]; var reconstructedCarto = Cartographic.fromCartesian(startPosition3D); reconstructedCarto.height = 0.0; expect(Cartographic.equalsEpsilon(reconstructedCarto, startCartographic, CesiumMath.EPSILON7)).toBe(true); var endPosition3D = new Cartesian3(); - endPosition3D.x = startPosition3D.x + startHi_and_forwardOffsetX.values[3]; - endPosition3D.y = startPosition3D.y + startLo_and_forwardOffsetY.values[3]; - endPosition3D.z = startPosition3D.z + startNormal_and_forwardOffsetZ.values[3]; + endPosition3D.x = startPosition3D.x + startHiAndForwardOffsetX.values[3]; + endPosition3D.y = startPosition3D.y + startLoAndForwardOffsetY.values[3]; + endPosition3D.z = startPosition3D.z + startNormalAndForwardOffsetZ.values[3]; reconstructedCarto = Cartographic.fromCartesian(endPosition3D); reconstructedCarto.height = 0.0; expect(Cartographic.equalsEpsilon(reconstructedCarto, endCartographic, CesiumMath.EPSILON7)).toBe(true); - var startNormal3D = Cartesian3.unpack(startNormal_and_forwardOffsetZ.values); + var startNormal3D = Cartesian3.unpack(startNormalAndForwardOffsetZ.values); expect(Cartesian3.equalsEpsilon(startNormal3D, new Cartesian3(0.0, 1.0, 0.0), CesiumMath.EPSILON2)).toBe(true); - var endNormal3D = Cartesian3.unpack(endNormal_and_textureCoordinateNormalizationX.values); + var endNormal3D = Cartesian3.unpack(endNormalAndTextureCoordinateNormalizationX.values); expect(Cartesian3.equalsEpsilon(endNormal3D, new Cartesian3(0.0, -1.0, 0.0), CesiumMath.EPSILON2)).toBe(true); - var rightNormal3D = Cartesian3.unpack(rightNormal_and_textureCoordinateNormalizationY.values); + var rightNormal3D = Cartesian3.unpack(rightNormalAndTextureCoordinateNormalizationY.values); expect(Cartesian3.equalsEpsilon(rightNormal3D, new Cartesian3(0.0, 0.0, -1.0), CesiumMath.EPSILON2)).toBe(true); - var texcoordNormalizationX = endNormal_and_textureCoordinateNormalizationX.values[3]; - var texcoordNormalizationY = rightNormal_and_textureCoordinateNormalizationY.values[3]; + var texcoordNormalizationX = endNormalAndTextureCoordinateNormalizationX.values[3]; + var texcoordNormalizationY = rightNormalAndTextureCoordinateNormalizationY.values[3]; expect(texcoordNormalizationX).toEqualEpsilon(1.0, CesiumMath.EPSILON3); expect(texcoordNormalizationY).toEqualEpsilon(0.0, CesiumMath.EPSILON3); @@ -179,6 +179,33 @@ defineSuite([ expect(texcoordNormalizationY).toEqualEpsilon(0.0, CesiumMath.EPSILON3); }); + it('does not generate 2D attributes when scene3DOnly is true', function() { + var startCartographic = Cartographic.fromDegrees(0.01, 0.0); + var endCartographic = Cartographic.fromDegrees(0.02, 0.0); + var groundPolylineGeometry = new GroundPolylineGeometry({ + positions : Cartesian3.fromRadiansArray([ + startCartographic.longitude, startCartographic.latitude, + endCartographic.longitude, endCartographic.latitude + ]), + granularity : 0.0 + }); + + groundPolylineGeometry._scene3DOnly = true; + + var geometry = GroundPolylineGeometry.createGeometry(groundPolylineGeometry); + + expect(geometry.attributes.startHiAndForwardOffsetX).toBeDefined(); + expect(geometry.attributes.startLoAndForwardOffsetY).toBeDefined(); + expect(geometry.attributes.startNormalAndForwardOffsetZ).toBeDefined(); + expect(geometry.attributes.endNormalAndTextureCoordinateNormalizationX).toBeDefined(); + expect(geometry.attributes.rightNormalAndTextureCoordinateNormalizationY).toBeDefined(); + + expect(geometry.attributes.startHiLo2D).not.toBeDefined(); + expect(geometry.attributes.offsetAndRight2D).not.toBeDefined(); + expect(geometry.attributes.startEndNormals2D).not.toBeDefined(); + expect(geometry.attributes.texcoordNormalization2D).not.toBeDefined(); + }); + it('miters turns', function() { var groundPolylineGeometry = new GroundPolylineGeometry({ positions : Cartesian3.fromDegreesArray([ @@ -193,11 +220,11 @@ defineSuite([ expect(geometry.indices.length).toEqual(72); expect(geometry.attributes.position.values.length).toEqual(48); - var startNormal_and_forwardOffsetZvalues = geometry.attributes.startNormal_and_forwardOffsetZ.values; - var endNormal_and_textureCoordinateNormalizationXvalues = geometry.attributes.endNormal_and_textureCoordinateNormalizationX.values; + var startNormalAndForwardOffsetZvalues = geometry.attributes.startNormalAndForwardOffsetZ.values; + var endNormalAndTextureCoordinateNormalizationXvalues = geometry.attributes.endNormalAndTextureCoordinateNormalizationX.values; - var miteredStartNormal = Cartesian3.unpack(startNormal_and_forwardOffsetZvalues, 32); - var miteredEndNormal = Cartesian3.unpack(endNormal_and_textureCoordinateNormalizationXvalues, 0); + var miteredStartNormal = Cartesian3.unpack(startNormalAndForwardOffsetZvalues, 32); + var miteredEndNormal = Cartesian3.unpack(endNormalAndTextureCoordinateNormalizationXvalues, 0); var reverseMiteredEndNormal = Cartesian3.multiplyByScalar(miteredEndNormal, -1.0, new Cartesian3()); expect(Cartesian3.equalsEpsilon(miteredStartNormal, reverseMiteredEndNormal, CesiumMath.EPSILON7)).toBe(true); @@ -219,11 +246,11 @@ defineSuite([ var geometry = GroundPolylineGeometry.createGeometry(groundPolylineGeometry); - var startNormal_and_forwardOffsetZvalues = geometry.attributes.startNormal_and_forwardOffsetZ.values; - var endNormal_and_textureCoordinateNormalizationXvalues = geometry.attributes.endNormal_and_textureCoordinateNormalizationX.values; + var startNormalAndForwardOffsetZvalues = geometry.attributes.startNormalAndForwardOffsetZ.values; + var endNormalAndTextureCoordinateNormalizationXvalues = geometry.attributes.endNormalAndTextureCoordinateNormalizationX.values; - var miteredStartNormal = Cartesian3.unpack(startNormal_and_forwardOffsetZvalues, 32); - var miteredEndNormal = Cartesian3.unpack(endNormal_and_textureCoordinateNormalizationXvalues, 0); + var miteredStartNormal = Cartesian3.unpack(startNormalAndForwardOffsetZvalues, 32); + var miteredEndNormal = Cartesian3.unpack(endNormalAndTextureCoordinateNormalizationXvalues, 0); var reverseMiteredEndNormal = Cartesian3.multiplyByScalar(miteredEndNormal, -1.0, new Cartesian3()); expect(Cartesian3.equalsEpsilon(miteredStartNormal, reverseMiteredEndNormal, CesiumMath.EPSILON7)).toBe(true); @@ -246,12 +273,12 @@ defineSuite([ geometry = GroundPolylineGeometry.createGeometry(groundPolylineGeometry); - startNormal_and_forwardOffsetZvalues = geometry.attributes.startNormal_and_forwardOffsetZ.values; - endNormal_and_textureCoordinateNormalizationXvalues = geometry.attributes.endNormal_and_textureCoordinateNormalizationX.values; + startNormalAndForwardOffsetZvalues = geometry.attributes.startNormalAndForwardOffsetZ.values; + endNormalAndTextureCoordinateNormalizationXvalues = geometry.attributes.endNormalAndTextureCoordinateNormalizationX.values; // Check normals at loop end - miteredStartNormal = Cartesian3.unpack(startNormal_and_forwardOffsetZvalues, 0); - miteredEndNormal = Cartesian3.unpack(endNormal_and_textureCoordinateNormalizationXvalues, 32 * 2); + miteredStartNormal = Cartesian3.unpack(startNormalAndForwardOffsetZvalues, 0); + miteredEndNormal = Cartesian3.unpack(endNormalAndTextureCoordinateNormalizationXvalues, 32 * 2); expect(Cartesian3.equalsEpsilon(miteredStartNormal, miteredEndNormal, CesiumMath.EPSILON7)).toBe(true); @@ -402,6 +429,7 @@ defineSuite([ loop : true, granularity : 10.0 // no interpolative subdivision }); + groundPolylineGeometry._scene3DOnly = true; var packedArray = [0]; GroundPolylineGeometry.pack(groundPolylineGeometry, packedArray, 1); @@ -420,6 +448,7 @@ defineSuite([ expect(scratch.loop).toBe(true); expect(scratch.granularity).toEqual(10.0); expect(scratch.ellipsoid.equals(Ellipsoid.WGS84)).toBe(true); + expect(scratch._scene3DOnly).toBe(true); }); var positions = Cartesian3.fromDegreesArray([ @@ -454,5 +483,7 @@ defineSuite([ Ellipsoid.pack(Ellipsoid.WGS84, packedInstance, packedInstance.length); packedInstance.push(0.0); // projection index for Geographic (default) + packedInstance.push(0.0); // scene3DModeOnly = false + createPackableSpecs(GroundPolylineGeometry, polyline, packedInstance); }); From 361c4cddac43a1899e65aaa2d8a06346be93c8f1 Mon Sep 17 00:00:00 2001 From: Kangning Li Date: Mon, 11 Jun 2018 12:27:05 -0400 Subject: [PATCH 33/39] fix IDL split bug for CV and normalization error IDL/PM split bug --- Source/Core/GroundPolylineGeometry.js | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/Source/Core/GroundPolylineGeometry.js b/Source/Core/GroundPolylineGeometry.js index 473f64b8f9eb..84a758fd719e 100644 --- a/Source/Core/GroundPolylineGeometry.js +++ b/Source/Core/GroundPolylineGeometry.js @@ -418,7 +418,9 @@ define([ p0 = positions[i]; p1 = positions[i + 1]; intersection = IntersectionTests.lineSegmentPlane(p0, p1, XZ_PLANE, intersectionScratch); - if (defined(intersection)) { + if (defined(intersection) && + !Cartesian3.equalsEpsilon(intersection, p0, CesiumMath.EPSILON7) && + !Cartesian3.equalsEpsilon(intersection, p1, CesiumMath.EPSILON7)) { splitPositions.push(Cartesian3.clone(intersection)); } splitPositions.push(p1); @@ -428,7 +430,9 @@ define([ p0 = positions[positionsLength - 1]; p1 = positions[0]; intersection = IntersectionTests.lineSegmentPlane(p0, p1, XZ_PLANE, intersectionScratch); - if (defined(intersection)) { + if (defined(intersection) && + !Cartesian3.equalsEpsilon(intersection, p0, CesiumMath.EPSILON7) && + !Cartesian3.equalsEpsilon(intersection, p1, CesiumMath.EPSILON7)) { splitPositions.push(Cartesian3.clone(intersection)); } } @@ -842,15 +846,17 @@ define([ // Start is close to IDL - snap start normal to align with IDL endGeometryNormal2D = projectNormal(projection, endCartographic, endGeometryNormal, end2D, segmentEndNormal2DScratch); startGeometryNormal2D.x = 0.0; - // If start longitude is negative and end longitude is less negative, "right" is unit -Y - // If start longitude is positive and end longitude is less positive, "right" is unit +Y + // If start longitude is negative and end longitude is less negative, relative right is unit -Y + // If start longitude is positive and end longitude is less positive, relative right is unit +Y startGeometryNormal2D.y = Math.sign(startCartographic.longitude - Math.abs(endCartographic.longitude)); startGeometryNormal2D.z = 0.0; } else { // End is close to IDL - snap end normal to align with IDL startGeometryNormal2D = projectNormal(projection, startCartographic, startGeometryNormal, start2D, segmentStartNormal2DScratch); endGeometryNormal2D.x = 0.0; - endGeometryNormal2D.y = Math.sign(endCartographic.longitude - Math.abs(startCartographic.longitude)); + // If end longitude is negative and start longitude is less negative, relative right is unit Y + // If end longitude is positive and start longitude is less positive, relative right is unit -Y + endGeometryNormal2D.y = Math.sign(startCartographic.longitude - endCartographic.longitude); endGeometryNormal2D.z = 0.0; } } From fa7075fa6256b37827b796d8724c3fa111df2559 Mon Sep 17 00:00:00 2001 From: Kangning Li Date: Mon, 11 Jun 2018 15:10:42 -0400 Subject: [PATCH 34/39] push bottom vertices of volumes at far view distances --- Source/Core/GroundPolylineGeometry.js | 24 ++++++++++++++++------ Source/Scene/GroundPolylinePrimitive.js | 2 +- Source/Shaders/PolylineShadowVolumeVS.glsl | 18 ++++++++++++++-- Specs/Core/GroundPolylineGeometrySpec.js | 18 +++++++++++----- 4 files changed, 48 insertions(+), 14 deletions(-) diff --git a/Source/Core/GroundPolylineGeometry.js b/Source/Core/GroundPolylineGeometry.js index 84a758fd719e..3965a2dca598 100644 --- a/Source/Core/GroundPolylineGeometry.js +++ b/Source/Core/GroundPolylineGeometry.js @@ -927,8 +927,10 @@ define([ var vec2Index = vec2sWriteIndex + j * 2; var wIndex = vec4Index + 3; - // Encode sidedness of vertex in texture coordinate normalization X - var sidedness = j < 4 ? 1.0 : -1.0; + // Encode sidedness of vertex relative to right plane in texture coordinate normalization X, + // whether vertex is top or bottom of volume in sign/magnitude of normalization Y. + var rightPlaneSide = j < 4 ? 1.0 : -1.0; + var topBottomSide = (j === 2 || j === 3 || j === 6 || j === 7) ? 1.0 : -1.0; // 3D Cartesian3.pack(encodedStart.high, startHiAndForwardOffsetX, vec4Index); @@ -941,10 +943,15 @@ define([ startNormalAndForwardOffsetZ[wIndex] = forwardOffset.z; Cartesian3.pack(endPlaneNormal, endNormalAndTextureCoordinateNormalizationX, vec4Index); - endNormalAndTextureCoordinateNormalizationX[wIndex] = texcoordNormalization3DX * sidedness; + endNormalAndTextureCoordinateNormalizationX[wIndex] = texcoordNormalization3DX * rightPlaneSide; Cartesian3.pack(rightNormal, rightNormalAndTextureCoordinateNormalizationY, vec4Index); - rightNormalAndTextureCoordinateNormalizationY[wIndex] = texcoordNormalization3DY; + + var texcoordNormalization = texcoordNormalization3DY * topBottomSide; + if (texcoordNormalization === 0.0 && topBottomSide < 0.0) { + texcoordNormalization = Number.POSITIVE_INFINITY; + } + rightNormalAndTextureCoordinateNormalizationY[wIndex] = texcoordNormalization; // 2D if (compute2dAttributes) { @@ -963,8 +970,13 @@ define([ offsetAndRight2D[vec4Index + 2] = right2D.x; offsetAndRight2D[vec4Index + 3] = right2D.y; - texcoordNormalization2D[vec2Index] = texcoordNormalization2DX * sidedness; - texcoordNormalization2D[vec2Index + 1] = texcoordNormalization2DY; + texcoordNormalization2D[vec2Index] = texcoordNormalization2DX * rightPlaneSide; + + texcoordNormalization = texcoordNormalization2DY * topBottomSide; + if (texcoordNormalization === 0.0 && topBottomSide < 0.0) { + texcoordNormalization = Number.POSITIVE_INFINITY; + } + texcoordNormalization2D[vec2Index + 1] = texcoordNormalization; } } diff --git a/Source/Scene/GroundPolylinePrimitive.js b/Source/Scene/GroundPolylinePrimitive.js index 5ad99f543610..40b844fb24f5 100644 --- a/Source/Scene/GroundPolylinePrimitive.js +++ b/Source/Scene/GroundPolylinePrimitive.js @@ -397,7 +397,7 @@ define([ // which causes problems when interpolating log depth from vertices. // So force computing and writing log depth in the fragment shader. // Re-enable at far distances to avoid z-fighting. - var vsDefines = ['ENABLE_GL_POSITION_LOG_DEPTH_AT_HEIGHT']; + var vsDefines = ['ENABLE_GL_POSITION_LOG_DEPTH_AT_HEIGHT', 'GLOBE_MINIMUM_ALTITUDE ' + frameState.mapProjection.ellipsoid.minimumRadius.toFixed(1)]; var colorDefine = ''; var materialShaderSource = ''; if (defined(appearance.material)) { diff --git a/Source/Shaders/PolylineShadowVolumeVS.glsl b/Source/Shaders/PolylineShadowVolumeVS.glsl index e603d815c984..c0ae4e2d1bee 100644 --- a/Source/Shaders/PolylineShadowVolumeVS.glsl +++ b/Source/Shaders/PolylineShadowVolumeVS.glsl @@ -1,6 +1,9 @@ attribute vec3 position3DHigh; attribute vec3 position3DLow; +// In 2D and in 3D, texture coordinate normalization component signs encodes: +// * X sign - sidedness relative to right plane +// * Y sign - is negative OR magnitude is greater than 1.0 if vertex is on bottom of volume #ifndef COLUMBUS_VIEW_2D attribute vec4 startHiAndForwardOffsetX; attribute vec4 startLoAndForwardOffsetY; @@ -57,7 +60,8 @@ void main() endPlaneEC.xyz = czm_normal * vec3(0.0, startEndNormals2D.zw); endPlaneEC.w = -dot(endPlaneEC.xyz, ecEnd); - v_texcoordNormalizationAndStartEcYZ.xy = vec2(abs(texcoordNormalization2D.x), texcoordNormalization2D.y); + v_texcoordNormalizationAndStartEcYZ.x = abs(texcoordNormalization2D.x); + v_texcoordNormalizationAndStartEcYZ.y = texcoordNormalization2D.y; #else // COLUMBUS_VIEW_2D vec3 ecStart = (czm_modelViewRelativeToEye * czm_translateRelativeToEye(startHiAndForwardOffsetX.xyz, startLoAndForwardOffsetY.xyz)).xyz; @@ -80,7 +84,8 @@ void main() v_rightPlaneEC.xyz = czm_normal * rightNormalAndTextureCoordinateNormalizationY.xyz; v_rightPlaneEC.w = -dot(v_rightPlaneEC.xyz, ecStart); - v_texcoordNormalizationAndStartEcYZ.xy = vec2(abs(endNormalAndTextureCoordinateNormalizationX.w), rightNormalAndTextureCoordinateNormalizationY.w); + v_texcoordNormalizationAndStartEcYZ.x = abs(endNormalAndTextureCoordinateNormalizationX.w); + v_texcoordNormalizationAndStartEcYZ.y = rightNormalAndTextureCoordinateNormalizationY.w; #endif // COLUMBUS_VIEW_2D @@ -105,6 +110,15 @@ void main() vec3 upOrDown = normalize(cross(v_rightPlaneEC.xyz, planeDirection)); // Points "up" for start plane, "down" at end plane. vec3 normalEC = normalize(cross(planeDirection, upOrDown)); // In practice, the opposite seems to work too. + // Extrude bottom vertices downward for far view distances, like for GroundPrimitives + upOrDown = cross(forwardDirectionEC, normalEC); + upOrDown = float(czm_sceneMode == czm_sceneMode3D) * upOrDown; + upOrDown = float(v_texcoordNormalizationAndStartEcYZ.y > 1.0 || v_texcoordNormalizationAndStartEcYZ.y < 0.0) * upOrDown; + upOrDown = min(GLOBE_MINIMUM_ALTITUDE, czm_geometricToleranceOverMeter * length(positionRelativeToEye.xyz)) * upOrDown; + positionEC.xyz += upOrDown; + + v_texcoordNormalizationAndStartEcYZ.y = czm_branchFreeTernary(v_texcoordNormalizationAndStartEcYZ.y > 1.0, 0.0, abs(v_texcoordNormalizationAndStartEcYZ.y)); + // Determine distance along normalEC to push for a volume of appropriate width. // Make volumes about double pixel width for a conservative fit - in practice the // extra cost here is minimal compared to the loose volume heights. diff --git a/Specs/Core/GroundPolylineGeometrySpec.js b/Specs/Core/GroundPolylineGeometrySpec.js index a656200f1950..4f15ca97543c 100644 --- a/Specs/Core/GroundPolylineGeometrySpec.js +++ b/Specs/Core/GroundPolylineGeometrySpec.js @@ -77,7 +77,6 @@ defineSuite([ verifyAttributeValuesIdentical(startHiAndForwardOffsetX); verifyAttributeValuesIdentical(startLoAndForwardOffsetY); verifyAttributeValuesIdentical(startNormalAndForwardOffsetZ); - verifyAttributeValuesIdentical(rightNormalAndTextureCoordinateNormalizationY); verifyAttributeValuesIdentical(startHiLo2D); verifyAttributeValuesIdentical(offsetAndRight2D); verifyAttributeValuesIdentical(startEndNormals2D); @@ -105,6 +104,19 @@ defineSuite([ expect(Math.sign(values[index])).toEqual(-1.0); } + // Expect rightNormalAndTextureCoordinateNormalizationY and texcoordNormalization2D.y to encode if the vertex is on the bottom + values = rightNormalAndTextureCoordinateNormalizationY.values; + expect(values[3] > 1.0).toBe(true); + expect(values[1 * 4 + 3] > 1.0).toBe(true); + expect(values[4 * 4 + 3] > 1.0).toBe(true); + expect(values[5 * 4 + 3] > 1.0).toBe(true); + + values = texcoordNormalization2D.values; + expect(values[1] > 1.0).toBe(true); + expect(values[1 * 2 + 1] > 1.0).toBe(true); + expect(values[4 * 2 + 1] > 1.0).toBe(true); + expect(values[5 * 2 + 1] > 1.0).toBe(true); + // Line segment geometry is encoded as: // - start position // - offset to the end position @@ -137,9 +149,7 @@ defineSuite([ expect(Cartesian3.equalsEpsilon(rightNormal3D, new Cartesian3(0.0, 0.0, -1.0), CesiumMath.EPSILON2)).toBe(true); var texcoordNormalizationX = endNormalAndTextureCoordinateNormalizationX.values[3]; - var texcoordNormalizationY = rightNormalAndTextureCoordinateNormalizationY.values[3]; expect(texcoordNormalizationX).toEqualEpsilon(1.0, CesiumMath.EPSILON3); - expect(texcoordNormalizationY).toEqualEpsilon(0.0, CesiumMath.EPSILON3); // 2D var projection = new GeographicProjection(); @@ -174,9 +184,7 @@ defineSuite([ expect(Cartesian3.equalsEpsilon(rightNormal2D, new Cartesian3(0.0, -1.0, 0.0), CesiumMath.EPSILON2)).toBe(true); texcoordNormalizationX = texcoordNormalization2D.values[0]; - texcoordNormalizationY = texcoordNormalization2D.values[1]; expect(texcoordNormalizationX).toEqualEpsilon(1.0, CesiumMath.EPSILON3); - expect(texcoordNormalizationY).toEqualEpsilon(0.0, CesiumMath.EPSILON3); }); it('does not generate 2D attributes when scene3DOnly is true', function() { From 81a23c8d08ef951d32d819cefcc857ecd6bae0e7 Mon Sep 17 00:00:00 2001 From: Kangning Li Date: Mon, 11 Jun 2018 16:43:42 -0400 Subject: [PATCH 35/39] fix artifacts in 2D and morph --- Source/Scene/GroundPolylinePrimitive.js | 6 +- Source/Shaders/PolylineShadowVolumeFS.glsl | 2 +- .../Shaders/PolylineShadowVolumeMorphVS.glsl | 75 ++++++++++++------- 3 files changed, 51 insertions(+), 32 deletions(-) diff --git a/Source/Scene/GroundPolylinePrimitive.js b/Source/Scene/GroundPolylinePrimitive.js index 40b844fb24f5..a595d4a65529 100644 --- a/Source/Scene/GroundPolylinePrimitive.js +++ b/Source/Scene/GroundPolylinePrimitive.js @@ -405,10 +405,10 @@ define([ // Check for use of v_width and v_polylineAngle in material shader // to determine whether these varyings should be active in the vertex shader. - if (materialShaderSource.search(/varying\s+float\s+v_polylineAngle;/g) === -1) { + if (materialShaderSource.search(/varying\s+float\s+v_polylineAngle;/g) !== -1) { vsDefines.push('ANGLE_VARYING'); } - if (materialShaderSource.search(/varying\s+float\s+v_width;/g) === -1) { + if (materialShaderSource.search(/varying\s+float\s+v_width;/g) !== -1) { vsDefines.push('WIDTH_VARYING'); } } else { @@ -455,7 +455,7 @@ define([ var colorProgramMorph = context.shaderCache.getDerivedShaderProgram(groundPolylinePrimitive._sp, 'MorphColor'); if (!defined(colorProgramMorph)) { var vsColorMorph = new ShaderSource({ - defines : vsDefines, + defines : vsDefines.concat(['MAX_TERRAIN_HEIGHT ' + ApproximateTerrainHeights._defaultMaxTerrainHeight.toFixed(1)]), sources : [vsMorph] }); diff --git a/Source/Shaders/PolylineShadowVolumeFS.glsl b/Source/Shaders/PolylineShadowVolumeFS.glsl index ce583d3da4cd..6ef10480d863 100644 --- a/Source/Shaders/PolylineShadowVolumeFS.glsl +++ b/Source/Shaders/PolylineShadowVolumeFS.glsl @@ -10,7 +10,7 @@ varying vec4 v_color; void main(void) { - float logDepthOrDepth = czm_unpackDepth(texture2D(czm_globeDepthTexture, gl_FragCoord.xy / czm_viewport.zw)); + float logDepthOrDepth = czm_branchFreeTernary(czm_sceneMode == czm_sceneMode2D, gl_FragCoord.z, czm_unpackDepth(texture2D(czm_globeDepthTexture, gl_FragCoord.xy / czm_viewport.zw))); vec3 ecStart = vec3(v_endEcAndStartEcX.w, v_texcoordNormalizationAndStartEcYZ.zw); // Discard for sky diff --git a/Source/Shaders/PolylineShadowVolumeMorphVS.glsl b/Source/Shaders/PolylineShadowVolumeMorphVS.glsl index f3fd713fefff..29e82a6b73bf 100644 --- a/Source/Shaders/PolylineShadowVolumeMorphVS.glsl +++ b/Source/Shaders/PolylineShadowVolumeMorphVS.glsl @@ -42,56 +42,64 @@ void main() vec4 posRelativeToEye2D = czm_translateRelativeToEye(vec3(0.0, startHiLo2D.xy), vec3(0.0, startHiLo2D.zw)); vec4 posRelativeToEye3D = czm_translateRelativeToEye(startHiAndForwardOffsetX.xyz, startLoAndForwardOffsetY.xyz); vec4 posRelativeToEye = czm_columbusViewMorph(posRelativeToEye2D, posRelativeToEye3D, czm_morphTime); - vec3 ecPos2D = (czm_modelViewRelativeToEye * posRelativeToEye2D).xyz; - vec3 ecPos3D = (czm_modelViewRelativeToEye * posRelativeToEye3D).xyz; - vec3 ecStart = (czm_modelViewRelativeToEye * posRelativeToEye).xyz; + vec3 posEc2D = (czm_modelViewRelativeToEye * posRelativeToEye2D).xyz; + vec3 posEc3D = (czm_modelViewRelativeToEye * posRelativeToEye3D).xyz; + vec3 startEC = (czm_modelViewRelativeToEye * posRelativeToEye).xyz; // Start plane vec4 startPlane2D; vec4 startPlane3D; startPlane2D.xyz = czm_normal * vec3(0.0, startEndNormals2D.xy); startPlane3D.xyz = czm_normal * startNormalAndForwardOffsetZ.xyz; - startPlane2D.w = -dot(startPlane2D.xyz, ecPos2D); - startPlane3D.w = -dot(startPlane3D.xyz, ecPos3D); + startPlane2D.w = -dot(startPlane2D.xyz, posEc2D); + startPlane3D.w = -dot(startPlane3D.xyz, posEc3D); // Right plane vec4 rightPlane2D; vec4 rightPlane3D; rightPlane2D.xyz = czm_normal * vec3(0.0, offsetAndRight2D.zw); rightPlane3D.xyz = czm_normal * rightNormalAndTextureCoordinateNormalizationY.xyz; - rightPlane2D.w = -dot(rightPlane2D.xyz, ecPos2D); - rightPlane3D.w = -dot(rightPlane3D.xyz, ecPos3D); + rightPlane2D.w = -dot(rightPlane2D.xyz, posEc2D); + rightPlane3D.w = -dot(rightPlane3D.xyz, posEc3D); // End position posRelativeToEye2D = posRelativeToEye2D + vec4(0.0, offsetAndRight2D.xy, 0.0); posRelativeToEye3D = posRelativeToEye3D + vec4(startHiAndForwardOffsetX.w, startLoAndForwardOffsetY.w, startNormalAndForwardOffsetZ.w, 0.0); posRelativeToEye = czm_columbusViewMorph(posRelativeToEye2D, posRelativeToEye3D, czm_morphTime); - ecPos2D = (czm_modelViewRelativeToEye * posRelativeToEye2D).xyz; - ecPos3D = (czm_modelViewRelativeToEye * posRelativeToEye3D).xyz; - vec3 ecEnd = (czm_modelViewRelativeToEye * posRelativeToEye).xyz; + posEc2D = (czm_modelViewRelativeToEye * posRelativeToEye2D).xyz; + posEc3D = (czm_modelViewRelativeToEye * posRelativeToEye3D).xyz; + vec3 endEC = (czm_modelViewRelativeToEye * posRelativeToEye).xyz; + vec3 forwardEc3D = czm_normal * normalize(vec3(startHiAndForwardOffsetX.w, startLoAndForwardOffsetY.w, startNormalAndForwardOffsetZ.w)); + vec3 forwardEc2D = czm_normal * normalize(vec3(0.0, offsetAndRight2D.xy)); // End plane vec4 endPlane2D; vec4 endPlane3D; endPlane2D.xyz = czm_normal * vec3(0.0, startEndNormals2D.zw); endPlane3D.xyz = czm_normal * endNormalAndTextureCoordinateNormalizationX.xyz; - endPlane2D.w = -dot(endPlane2D.xyz, ecPos2D); - endPlane3D.w = -dot(endPlane3D.xyz, ecPos3D); + endPlane2D.w = -dot(endPlane2D.xyz, posEc2D); + endPlane3D.w = -dot(endPlane3D.xyz, posEc3D); // Forward direction - v_forwardDirectionEC = normalize(ecEnd - ecStart); + v_forwardDirectionEC = normalize(endEC - startEC); - v_texcoordNormalization_and_halfWidth.xy = mix( - vec2(abs(texcoordNormalization2D.x), texcoordNormalization2D.y), - vec2(abs(endNormalAndTextureCoordinateNormalizationX.w), rightNormalAndTextureCoordinateNormalizationY.w), czm_morphTime); + vec2 cleanTexcoordNormalization2D; + cleanTexcoordNormalization2D.x = abs(texcoordNormalization2D.x); + cleanTexcoordNormalization2D.y = czm_branchFreeTernary(texcoordNormalization2D.y > 1.0, 0.0, abs(texcoordNormalization2D.y)); + vec2 cleanTexcoordNormalization3D; + cleanTexcoordNormalization3D.x = abs(endNormalAndTextureCoordinateNormalizationX.w); + cleanTexcoordNormalization3D.y = rightNormalAndTextureCoordinateNormalizationY.w; + cleanTexcoordNormalization3D.y = czm_branchFreeTernary(cleanTexcoordNormalization3D.y > 1.0, 0.0, abs(cleanTexcoordNormalization3D.y)); + + v_texcoordNormalization_and_halfWidth.xy = mix(cleanTexcoordNormalization2D, cleanTexcoordNormalization3D, czm_morphTime); #ifdef PER_INSTANCE_COLOR v_color = czm_batchTable_color(batchId); #else // PER_INSTANCE_COLOR // For computing texture coordinates - v_alignedPlaneDistances.x = -dot(v_forwardDirectionEC, ecStart); - v_alignedPlaneDistances.y = -dot(-v_forwardDirectionEC, ecEnd); + v_alignedPlaneDistances.x = -dot(v_forwardDirectionEC, startEC); + v_alignedPlaneDistances.y = -dot(-v_forwardDirectionEC, endEC); #endif // PER_INSTANCE_COLOR #ifdef WIDTH_VARYING @@ -111,29 +119,41 @@ void main() // ****** 3D ****** // Check distance to the end plane and start plane, pick the plane that is closer - vec4 positionEC3D = czm_modelViewRelativeToEye * czm_translateRelativeToEye(position3DHigh, position3DLow); // w = 1.0, see czm_computePosition - float absStartPlaneDistance = abs(czm_planeDistance(startPlane3D, positionEC3D.xyz)); - float absEndPlaneDistance = abs(czm_planeDistance(endPlane3D, positionEC3D.xyz)); + vec4 positionEc3D = czm_modelViewRelativeToEye * czm_translateRelativeToEye(position3DHigh, position3DLow); // w = 1.0, see czm_computePosition + float absStartPlaneDistance = abs(czm_planeDistance(startPlane3D, positionEc3D.xyz)); + float absEndPlaneDistance = abs(czm_planeDistance(endPlane3D, positionEc3D.xyz)); vec3 planeDirection = czm_branchFreeTernary(absStartPlaneDistance < absEndPlaneDistance, startPlane3D.xyz, endPlane3D.xyz); vec3 upOrDown = normalize(cross(rightPlane3D.xyz, planeDirection)); // Points "up" for start plane, "down" at end plane. vec3 normalEC = normalize(cross(planeDirection, upOrDown)); // In practice, the opposite seems to work too. + // Nudge the top vertex upwards to prevent flickering + vec3 geodeticSurfaceNormal = normalize(cross(normalEC, forwardEc3D)); + geodeticSurfaceNormal *= float(0.0 <= rightNormalAndTextureCoordinateNormalizationY.w && rightNormalAndTextureCoordinateNormalizationY.w <= 1.0); + geodeticSurfaceNormal *= MAX_TERRAIN_HEIGHT; + positionEc3D.xyz += geodeticSurfaceNormal; + // Determine if this vertex is on the "left" or "right" normalEC *= sign(endNormalAndTextureCoordinateNormalizationX.w); // A "perfect" implementation would push along normals according to the angle against forward. // In practice, just pushing the normal out by halfWidth is sufficient for morph views. - positionEC3D.xyz += halfWidth * max(0.0, czm_metersPerPixel(positionEC3D)) * normalEC; // prevent artifacts when czm_metersPerPixel is negative (behind camera) + positionEc3D.xyz += halfWidth * max(0.0, czm_metersPerPixel(positionEc3D)) * normalEC; // prevent artifacts when czm_metersPerPixel is negative (behind camera) // ****** 2D ****** // Check distance to the end plane and start plane, pick the plane that is closer - vec4 positionEC2D = czm_modelViewRelativeToEye * czm_translateRelativeToEye(position2DHigh.zxy, position2DLow.zxy); // w = 1.0, see czm_computePosition - absStartPlaneDistance = abs(czm_planeDistance(startPlane2D, positionEC2D.xyz)); - absEndPlaneDistance = abs(czm_planeDistance(endPlane2D, positionEC2D.xyz)); + vec4 positionEc2D = czm_modelViewRelativeToEye * czm_translateRelativeToEye(position2DHigh.zxy, position2DLow.zxy); // w = 1.0, see czm_computePosition + absStartPlaneDistance = abs(czm_planeDistance(startPlane2D, positionEc2D.xyz)); + absEndPlaneDistance = abs(czm_planeDistance(endPlane2D, positionEc2D.xyz)); planeDirection = czm_branchFreeTernary(absStartPlaneDistance < absEndPlaneDistance, startPlane2D.xyz, endPlane2D.xyz); upOrDown = normalize(cross(rightPlane2D.xyz, planeDirection)); // Points "up" for start plane, "down" at end plane. normalEC = normalize(cross(planeDirection, upOrDown)); // In practice, the opposite seems to work too. + // Nudge the top vertex upwards to prevent flickering + geodeticSurfaceNormal = normalize(cross(normalEC, forwardEc2D)); + geodeticSurfaceNormal *= float(0.0 <= texcoordNormalization2D.y && texcoordNormalization2D.y <= 1.0); + geodeticSurfaceNormal *= MAX_TERRAIN_HEIGHT; + positionEc2D.xyz += geodeticSurfaceNormal; + // Determine if this vertex is on the "left" or "right" normalEC *= sign(texcoordNormalization2D.x); #ifndef PER_INSTANCE_COLOR @@ -143,11 +163,10 @@ void main() // A "perfect" implementation would push along normals according to the angle against forward. // In practice, just pushing the normal out by halfWidth is sufficient for morph views. - positionEC2D.xyz -= normalEC; // undo the unit length push - positionEC2D.xyz += halfWidth * max(0.0, czm_metersPerPixel(positionEC2D)) * normalEC; // prevent artifacts when czm_metersPerPixel is negative (behind camera) + positionEc2D.xyz += halfWidth * max(0.0, czm_metersPerPixel(positionEc2D)) * normalEC; // prevent artifacts when czm_metersPerPixel is negative (behind camera) // Blend for actual position - gl_Position = czm_projection * mix(positionEC2D, positionEC3D, czm_morphTime); + gl_Position = czm_projection * mix(positionEc2D, positionEc3D, czm_morphTime); #ifdef ANGLE_VARYING // Approximate relative screen space direction of the line. From 163d254d39868d78db7a8ea4160f689d2186a450 Mon Sep 17 00:00:00 2001 From: Kangning Li Date: Mon, 11 Jun 2018 18:51:12 -0400 Subject: [PATCH 36/39] early discards --- Source/Shaders/PolylineShadowVolumeFS.glsl | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/Source/Shaders/PolylineShadowVolumeFS.glsl b/Source/Shaders/PolylineShadowVolumeFS.glsl index 6ef10480d863..f1d0a8028d92 100644 --- a/Source/Shaders/PolylineShadowVolumeFS.glsl +++ b/Source/Shaders/PolylineShadowVolumeFS.glsl @@ -14,7 +14,14 @@ void main(void) vec3 ecStart = vec3(v_endEcAndStartEcX.w, v_texcoordNormalizationAndStartEcYZ.zw); // Discard for sky - bool shouldDiscard = (logDepthOrDepth == 0.0); + if (logDepthOrDepth == 0.0) { +#ifdef DEBUG_SHOW_VOLUME + gl_FragColor = vec4(1.0, 0.0, 0.0, 0.5); + return; +#else // DEBUG_SHOW_VOLUME + discard; +#endif // DEBUG_SHOW_VOLUME + } vec4 eyeCoordinate = czm_windowToEyeCoordinates(gl_FragCoord.xy, logDepthOrDepth); eyeCoordinate /= eyeCoordinate.w; @@ -27,7 +34,14 @@ void main(void) float distanceFromStart = czm_planeDistance(v_startPlaneNormalEcAndHalfWidth.xyz, -dot(ecStart, v_startPlaneNormalEcAndHalfWidth.xyz), eyeCoordinate.xyz); float distanceFromEnd = czm_planeDistance(v_endPlaneNormalEcAndBatchId.xyz, -dot(v_endEcAndStartEcX.xyz, v_endPlaneNormalEcAndBatchId.xyz), eyeCoordinate.xyz); - shouldDiscard = shouldDiscard || (abs(widthwiseDistance) > halfMaxWidth || distanceFromStart < 0.0 || distanceFromEnd < 0.0); + if (abs(widthwiseDistance) > halfMaxWidth || distanceFromStart < 0.0 || distanceFromEnd < 0.0) { +#ifdef DEBUG_SHOW_VOLUME + gl_FragColor = vec4(1.0, 0.0, 0.0, 0.5); + return; +#else // DEBUG_SHOW_VOLUME + discard; +#endif // DEBUG_SHOW_VOLUME + } // Check distance of the eye coordinate against start and end planes with normals in the right plane. // For computing unskewed linear texture coordinate and for clipping extremely pointy miters @@ -45,9 +59,7 @@ void main(void) alignedPlaneNormal = normalize(cross(alignedPlaneNormal, v_rightPlaneEC.xyz)); distanceFromEnd = czm_planeDistance(alignedPlaneNormal, -dot(alignedPlaneNormal, v_endEcAndStartEcX.xyz), eyeCoordinate.xyz); - shouldDiscard = shouldDiscard || distanceFromStart < -halfMaxWidth || distanceFromEnd < -halfMaxWidth; - - if (shouldDiscard) { + if (distanceFromStart < -halfMaxWidth || distanceFromEnd < -halfMaxWidth) { #ifdef DEBUG_SHOW_VOLUME gl_FragColor = vec4(1.0, 0.0, 0.0, 0.5); return; From 403e5009b2250538e06bc04058fc57a46b0e978d Mon Sep 17 00:00:00 2001 From: Kangning Li Date: Tue, 12 Jun 2018 10:26:44 -0400 Subject: [PATCH 37/39] remove unneeded options, add example doc for GroundPolylinePrimitive, add simple Sandcastle example for Ground Polyline materials --- .../development/Ground Polyline Material.html | 96 +++++++++++++++++ .../development/Ground Polyline Material.jpg | Bin 0 -> 24837 bytes Source/Scene/GroundPolylinePrimitive.js | 97 +++++++++--------- Specs/Scene/GroundPolylinePrimitiveSpec.js | 8 -- 4 files changed, 146 insertions(+), 55 deletions(-) create mode 100644 Apps/Sandcastle/gallery/development/Ground Polyline Material.html create mode 100644 Apps/Sandcastle/gallery/development/Ground Polyline Material.jpg diff --git a/Apps/Sandcastle/gallery/development/Ground Polyline Material.html b/Apps/Sandcastle/gallery/development/Ground Polyline Material.html new file mode 100644 index 000000000000..5d6348eb4ce4 --- /dev/null +++ b/Apps/Sandcastle/gallery/development/Ground Polyline Material.html @@ -0,0 +1,96 @@ + + + + + + + + + Cesium Demo + + + + + + +

+

Loading...

+
+ + + diff --git a/Apps/Sandcastle/gallery/development/Ground Polyline Material.jpg b/Apps/Sandcastle/gallery/development/Ground Polyline Material.jpg new file mode 100644 index 0000000000000000000000000000000000000000..f83e97e704f59a137a0d8246f637a37857c155bf GIT binary patch literal 24837 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 literal 0 HcmV?d00001 diff --git a/Source/Scene/GroundPolylinePrimitive.js b/Source/Scene/GroundPolylinePrimitive.js index a595d4a65529..8fe301428bb5 100644 --- a/Source/Scene/GroundPolylinePrimitive.js +++ b/Source/Scene/GroundPolylinePrimitive.js @@ -1,6 +1,5 @@ define([ '../Core/ApproximateTerrainHeights', - '../Core/Check', '../Core/ComponentDatatype', '../Core/defaultValue', '../Core/defined', @@ -9,9 +8,7 @@ define([ '../Core/DeveloperError', '../Core/GeometryInstance', '../Core/GeometryInstanceAttribute', - '../Core/GroundPolylineGeometry', '../Core/isArray', - '../Core/Matrix4', '../Shaders/PolylineShadowVolumeVS', '../Shaders/PolylineShadowVolumeFS', '../Shaders/PolylineShadowVolumeMorphVS', @@ -24,14 +21,12 @@ define([ '../ThirdParty/when', './BlendingState', './CullFace', - './Material', './PolylineColorAppearance', './PolylineMaterialAppearance', './Primitive', './SceneMode' ], function( ApproximateTerrainHeights, - Check, ComponentDatatype, defaultValue, defined, @@ -40,9 +35,7 @@ define([ DeveloperError, GeometryInstance, GeometryInstanceAttribute, - GroundPolylineGeometry, isArray, - Matrix4, PolylineShadowVolumeVS, PolylineShadowVolumeFS, PolylineShadowVolumeMorphVS, @@ -55,7 +48,6 @@ define([ when, BlendingState, CullFace, - Material, PolylineColorAppearance, PolylineMaterialAppearance, Primitive, @@ -66,22 +58,65 @@ define([ * A GroundPolylinePrimitive represents a polyline draped over the terrain in the {@link Scene}. *

* - * Only to be used with GeometryInstances containing {@link GroundPolylineGeometry} + * Only to be used with GeometryInstances containing {@link GroundPolylineGeometry}. * * @param {Object} [options] Object with the following properties: * @param {Array|GeometryInstance} [options.geometryInstances] GeometryInstances containing GroundPolylineGeometry * @param {Appearance} [options.appearance] The Appearance used to render the polyline. Defaults to a white color {@link Material} on a {@link PolylineMaterialAppearance}. * @param {Boolean} [options.show=true] Determines if this primitive will be shown. - * @param {Boolean} [options.vertexCacheOptimize=false] When true, geometry vertices are optimized for the pre and post-vertex-shader caches. * @param {Boolean} [options.interleave=false] When true, geometry vertex attributes are interleaved, which can slightly improve rendering performance but increases load time. - * @param {Boolean} [options.compressVertices=true] When true, the geometry vertices are compressed, which will save memory. * @param {Boolean} [options.releaseGeometryInstances=true] When true, the primitive does not keep a reference to the input geometryInstances to save memory. * @param {Boolean} [options.allowPicking=true] When true, each geometry instance will only be pickable with {@link Scene#pick}. When false, GPU memory is saved. * @param {Boolean} [options.asynchronous=true] Determines if the primitive will be created asynchronously or block until ready. If false initializeTerrainHeights() must be called first. * @param {Boolean} [options.debugShowBoundingVolume=false] For debugging only. Determines if this primitive's commands' bounding spheres are shown. - * @param {Boolean} [options.debugShowShadowVolume=false] For debugging only. Determines if the shadow volume for each geometry in the primitive is drawn. Must be true on - * creation for the volumes to be created before the geometry is released or options.releaseGeometryInstance must be false. + * @param {Boolean} [options.debugShowShadowVolume=false] For debugging only. Determines if the shadow volume for each geometry in the primitive is drawn. Must be true on creation to have effect. * + * @example + * // 1. Draw a polyline on terrain with a basic color material + * + * var instance = new Cesium.GeometryInstance({ + * geometry : new Cesium.GroundPolylineGeometry({ + * positions : Cesium.Cartesian3.fromDegreesArray([ + * -112.1340164450331, 36.05494287836128, + * -112.08821010582645, 36.097804071380715 + * ]), + * width : 4.0 + * }), + * id : 'object returned when this instance is picked and to get/set per-instance attributes' + * }); + * + * scene.groundPrimitives.add(new Cesium.GroundPolylinePrimitive({ + * geometryInstances : instance, + * appearance : new Cesium.PolylineMaterialAppearance({ + * material : Cesium.Material.fromType('Color') + * }) + * })); + * + * // 2. Draw a looped polyline on terrain with per-instance color and a distance display condition. + * // Distance display conditions for polylines on terrain are based on an approximate terrain height + * // instead of true terrain height. + * + * var instance = new Cesium.GeometryInstance({ + * geometry : new Cesium.GroundPolylineGeometry({ + * positions : Cesium.Cartesian3.fromDegreesArray([ + * -112.1340164450331, 36.05494287836128, + * -112.08821010582645, 36.097804071380715, + * -112.13296079730024, 36.168769146801104 + * ]), + * loop : true, + * width : 4.0 + * }), + * attributes : { + * color : Cesium.ColorGeometryInstanceAttribute.fromColor(Cesium.Color.fromCssColorString('green').withAlpha(0.7)), + distanceDisplayCondition : new Cesium.DistanceDisplayConditionGeometryInstanceAttribute(1000, 30000) + * }, + * id : 'object returned when this instance is picked and to get/set per-instance attributes' + * }); + * + * scene.groundPrimitives.add(new Cesium.GroundPolylinePrimitive({ + * geometryInstances : instance, + * appearance : Cesium.PolylineColorAppearance() + * })); */ function GroundPolylinePrimitive(options) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); @@ -146,12 +181,12 @@ define([ this._primitiveOptions = { geometryInstances : undefined, appearance : undefined, - vertexCacheOptimize : defaultValue(options.vertexCacheOptimize, false), + vertexCacheOptimize : false, interleave : defaultValue(options.interleave, false), releaseGeometryInstances : defaultValue(options.releaseGeometryInstances, true), allowPicking : defaultValue(options.allowPicking, true), asynchronous : defaultValue(options.asynchronous, true), - compressVertices : defaultValue(options.compressVertices, true), + compressVertices : false, _createShaderProgramFunction : undefined, _createCommandsFunction : undefined, _updateAndQueueCommandsFunction : undefined @@ -191,22 +226,6 @@ define([ } defineProperties(GroundPolylinePrimitive.prototype, { - /** - * When true, geometry vertices are optimized for the pre and post-vertex-shader caches. - * - * @memberof GroundPolylinePrimitive.prototype - * - * @type {Boolean} - * @readonly - * - * @default true - */ - vertexCacheOptimize : { - get : function() { - return this._primitiveOptions.vertexCacheOptimize; - } - }, - /** * Determines if geometry vertex attributes are interleaved, which can slightly improve rendering performance. * @@ -271,22 +290,6 @@ define([ } }, - /** - * When true, geometry vertices are compressed, which will save memory. - * - * @memberof GroundPolylinePrimitive.prototype - * - * @type {Boolean} - * @readonly - * - * @default true - */ - compressVertices : { - get : function() { - return this._primitiveOptions.compressVertices; - } - }, - /** * Determines if the primitive is complete and ready to render. If this property is * true, the primitive will be rendered the next time that {@link GroundPolylinePrimitive#update} diff --git a/Specs/Scene/GroundPolylinePrimitiveSpec.js b/Specs/Scene/GroundPolylinePrimitiveSpec.js index d6806506ea59..4386e892259d 100644 --- a/Specs/Scene/GroundPolylinePrimitiveSpec.js +++ b/Specs/Scene/GroundPolylinePrimitiveSpec.js @@ -1,6 +1,5 @@ defineSuite([ 'Scene/GroundPolylinePrimitive', - 'Core/ApproximateTerrainHeights', 'Core/Color', 'Core/ColorGeometryInstanceAttribute', 'Core/Cartesian3', @@ -21,7 +20,6 @@ defineSuite([ 'Specs/pollToPromise' ], function( GroundPolylinePrimitive, - ApproximateTerrainHeights, Color, ColorGeometryInstanceAttribute, Cartesian3, @@ -160,9 +158,7 @@ defineSuite([ expect(groundPolylinePrimitive.geometryInstances).not.toBeDefined(); expect(groundPolylinePrimitive.appearance instanceof PolylineMaterialAppearance).toBe(true); expect(groundPolylinePrimitive.show).toEqual(true); - expect(groundPolylinePrimitive.vertexCacheOptimize).toEqual(false); expect(groundPolylinePrimitive.interleave).toEqual(false); - expect(groundPolylinePrimitive.compressVertices).toEqual(true); expect(groundPolylinePrimitive.releaseGeometryInstances).toEqual(true); expect(groundPolylinePrimitive.allowPicking).toEqual(true); expect(groundPolylinePrimitive.asynchronous).toEqual(true); @@ -176,9 +172,7 @@ defineSuite([ groundPolylinePrimitive = new GroundPolylinePrimitive({ geometryInstances : geometryInstances, show : false, - vertexCacheOptimize : true, interleave : true, - compressVertices : false, releaseGeometryInstances : false, allowPicking : false, asynchronous : false, @@ -188,9 +182,7 @@ defineSuite([ expect(groundPolylinePrimitive.geometryInstances).toEqual(geometryInstances); expect(groundPolylinePrimitive.show).toEqual(false); - expect(groundPolylinePrimitive.vertexCacheOptimize).toEqual(true); expect(groundPolylinePrimitive.interleave).toEqual(true); - expect(groundPolylinePrimitive.compressVertices).toEqual(false); expect(groundPolylinePrimitive.releaseGeometryInstances).toEqual(false); expect(groundPolylinePrimitive.allowPicking).toEqual(false); expect(groundPolylinePrimitive.asynchronous).toEqual(false); From a822e1577367440909eb97c118487ce16a92aa43 Mon Sep 17 00:00:00 2001 From: Kangning Li Date: Tue, 12 Jun 2018 11:07:31 -0400 Subject: [PATCH 38/39] update CHANGES.md --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index bb1826a35bc4..bcb2c494b48c 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -6,6 +6,7 @@ Change Log ##### Additions :tada: * `PostProcessStage` has a `selectedFeatures` property which is an array of primitives used for selectively applying a post-process stage. In the fragment shader, use the function `bool czm_selected(vec2 textureCoordinates` to determine whether or not the stage should be applied at that fragment. * The black-and-white and silhouette stages have per-feature support. +* Added `GroundPolylinePrimitive` and `GroundPolylineGeometry` for rendering polylines on terrain via the `Primitive` API. [#6615](https://github.com/AnalyticalGraphicsInc/cesium/pull/6615) ##### Fixes :wrench: * Fixed a bug causing crashes with custom vertex attributes on `Geometry` crossing the IDL. Attributes will be barycentrically interpolated. [#6644](https://github.com/AnalyticalGraphicsInc/cesium/pull/6644) From 3ec30e3935b712d0e32098c55995e48a347b313d Mon Sep 17 00:00:00 2001 From: Kangning Li Date: Tue, 12 Jun 2018 18:56:54 -0400 Subject: [PATCH 39/39] PR comments, back to breaking turns with larger angles --- .../development/Ground Polyline Material.html | 4 ++ Source/Core/GroundPolylineGeometry.js | 57 ++++++++++--------- Source/Scene/GroundPolylinePrimitive.js | 5 +- Source/Shaders/PolylineShadowVolumeFS.glsl | 12 +--- .../Shaders/PolylineShadowVolumeMorphFS.glsl | 4 +- .../Shaders/PolylineShadowVolumeMorphVS.glsl | 8 +-- Specs/Core/GroundPolylineGeometrySpec.js | 20 ++++++- 7 files changed, 66 insertions(+), 44 deletions(-) diff --git a/Apps/Sandcastle/gallery/development/Ground Polyline Material.html b/Apps/Sandcastle/gallery/development/Ground Polyline Material.html index 5d6348eb4ce4..ee5c8a489f78 100644 --- a/Apps/Sandcastle/gallery/development/Ground Polyline Material.html +++ b/Apps/Sandcastle/gallery/development/Ground Polyline Material.html @@ -32,6 +32,10 @@ }); var scene = viewer.scene; +if (!Cesium.GroundPolylinePrimitive.isSupported(scene)) { + throw new Cesium.RuntimeError('Polylines on terrain are not supported on this platform.'); +} + // Polyline Glow scene.groundPrimitives.add(new Cesium.GroundPolylinePrimitive({ geometryInstances : new Cesium.GeometryInstance({ diff --git a/Source/Core/GroundPolylineGeometry.js b/Source/Core/GroundPolylineGeometry.js index 3965a2dca598..de9f2485b6e2 100644 --- a/Source/Core/GroundPolylineGeometry.js +++ b/Source/Core/GroundPolylineGeometry.js @@ -51,8 +51,8 @@ define([ var PROJECTIONS = [GeographicProjection, WebMercatorProjection]; var PROJECTION_COUNT = PROJECTIONS.length; - var MITER_BREAK_SMALL = Math.cos(CesiumMath.toRadians(1.0)); - var MITER_BREAK_LARGE = Math.cos(CesiumMath.toRadians(179.0)); + var MITER_BREAK_SMALL = Math.cos(CesiumMath.toRadians(30.0)); + var MITER_BREAK_LARGE = Math.cos(CesiumMath.toRadians(150.0)); // Initial heights for constructing the wall. // Keeping WALL_INITIAL_MIN_HEIGHT near the ellipsoid surface helps @@ -78,8 +78,6 @@ define([ * @param {Number} [options.width=1.0] The screen space width in pixels. * @param {Number} [options.granularity=9999.0] The distance interval in meters used for interpolating options.points. Defaults to 9999.0 meters. Zero indicates no interpolation. * @param {Boolean} [options.loop=false] Whether during geometry creation a line segment will be added between the last and first line positions to make this Polyline a loop. - * @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] Ellipsoid that input positions will be clamped to. - * @param {MapProjection} [options.projection] Map Projection for projecting coordinates to 2D. * * @exception {DeveloperError} At least two positions are required. * @@ -130,24 +128,10 @@ define([ */ this.loop = defaultValue(options.loop, false); - /** - * Ellipsoid for projecting cartographic coordinates to 3D. - * @type {Ellipsoid} - * @default Ellipsoid.WGS84 - */ - this.ellipsoid = defaultValue(options.ellipsoid, Ellipsoid.WGS84); + this._ellipsoid = Ellipsoid.WGS84; // MapProjections can't be packed, so store the index to a known MapProjection. - var projectionIndex = 0; - if (defined(options.projection)) { - for (var i = 0; i < PROJECTION_COUNT; i++) { - if (options.projection instanceof PROJECTIONS[i]) { - projectionIndex = i; - break; - } - } - } - this._projectionIndex = projectionIndex; + this._projectionIndex = 0; this._workerName = 'createGroundPolylineGeometry'; // Used by GroundPolylinePrimitive to signal worker that scenemode is 3D only. @@ -169,6 +153,27 @@ define([ } }); + /** + * Set the GroundPolylineGeometry's projection and ellipsoid. + * Used by GroundPolylinePrimitive to signal scene information to the geometry for generating 2D attributes. + * + * @param {GroundPolylineGeometry} groundPolylineGeometry GroundPolylinGeometry describing a polyline on terrain. + * @param {Projection} mapProjection A MapProjection used for projecting cartographic coordinates to 2D. + * @private + */ + GroundPolylineGeometry.setProjectionAndEllipsoid = function(groundPolylineGeometry, mapProjection) { + var projectionIndex = 0; + for (var i = 0; i < PROJECTION_COUNT; i++) { + if (mapProjection instanceof PROJECTIONS[i]) { + projectionIndex = i; + break; + } + } + + groundPolylineGeometry._projectionIndex = projectionIndex; + groundPolylineGeometry._ellipsoid = mapProjection.ellipsoid; + }; + var cart3Scratch1 = new Cartesian3(); var cart3Scratch2 = new Cartesian3(); var cart3Scratch3 = new Cartesian3(); @@ -261,7 +266,7 @@ define([ array[index++] = value.granularity; array[index++] = value.loop ? 1.0 : 0.0; - Ellipsoid.pack(value.ellipsoid, array, index); + Ellipsoid.pack(value._ellipsoid, array, index); index += Ellipsoid.packedLength; array[index++] = value._projectionIndex; @@ -315,7 +320,7 @@ define([ result._positions = positions; result.granularity = granularity; result.loop = loop; - result.ellipsoid = ellipsoid; + result._ellipsoid = ellipsoid; result._projectionIndex = projectionIndex; result._scene3DOnly = scene3DOnly; @@ -390,7 +395,7 @@ define([ GroundPolylineGeometry.createGeometry = function(groundPolylineGeometry) { var compute2dAttributes = !groundPolylineGeometry._scene3DOnly; var loop = groundPolylineGeometry.loop; - var ellipsoid = groundPolylineGeometry.ellipsoid; + var ellipsoid = groundPolylineGeometry._ellipsoid; var granularity = groundPolylineGeometry.granularity; var projection = new PROJECTIONS[groundPolylineGeometry._projectionIndex](ellipsoid); @@ -572,11 +577,11 @@ define([ var normalStartpointScratch = new Cartesian3(); var normalEndpointScratch = new Cartesian3(); function projectNormal(projection, cartographic, normal, projectedPosition, result) { - var position = Cartographic.toCartesian(cartographic, projection.ellipsoid, normalStartpointScratch); + var position = Cartographic.toCartesian(cartographic, projection._ellipsoid, normalStartpointScratch); var normalEndpoint = Cartesian3.add(position, normal, normalEndpointScratch); var flipNormal = false; - var ellipsoid = projection.ellipsoid; + var ellipsoid = projection._ellipsoid; var normalEndpointCartographic = ellipsoid.cartesianToCartographic(normalEndpoint, endPosCartographicScratch); // If normal crosses the IDL, go the other way and flip the result. // In practice this almost never happens because the cartographic start @@ -707,7 +712,7 @@ define([ function generateGeometryAttributes(loop, projection, bottomPositionsArray, topPositionsArray, normalsArray, cartographicsArray, compute2dAttributes) { var i; var index; - var ellipsoid = projection.ellipsoid; + var ellipsoid = projection._ellipsoid; // Each segment will have 8 vertices var segmentCount = (bottomPositionsArray.length / 3) - 1; diff --git a/Source/Scene/GroundPolylinePrimitive.js b/Source/Scene/GroundPolylinePrimitive.js index 8fe301428bb5..72b2df985065 100644 --- a/Source/Scene/GroundPolylinePrimitive.js +++ b/Source/Scene/GroundPolylinePrimitive.js @@ -8,6 +8,7 @@ define([ '../Core/DeveloperError', '../Core/GeometryInstance', '../Core/GeometryInstanceAttribute', + '../Core/GroundPolylineGeometry', '../Core/isArray', '../Shaders/PolylineShadowVolumeVS', '../Shaders/PolylineShadowVolumeFS', @@ -35,6 +36,7 @@ define([ DeveloperError, GeometryInstance, GeometryInstanceAttribute, + GroundPolylineGeometry, isArray, PolylineShadowVolumeVS, PolylineShadowVolumeFS, @@ -643,8 +645,9 @@ define([ }); } - // Update each geometry for framestate.scene3DOnly = true + // Update each geometry for framestate.scene3DOnly = true and projection geometryInstance.geometry._scene3DOnly = frameState.scene3DOnly; + GroundPolylineGeometry.setProjectionAndEllipsoid(geometryInstance.geometry, frameState.mapProjection); groundInstances[i] = new GeometryInstance({ geometry : geometryInstance.geometry, diff --git a/Source/Shaders/PolylineShadowVolumeFS.glsl b/Source/Shaders/PolylineShadowVolumeFS.glsl index f1d0a8028d92..1042fe7f2c2f 100644 --- a/Source/Shaders/PolylineShadowVolumeFS.glsl +++ b/Source/Shaders/PolylineShadowVolumeFS.glsl @@ -44,7 +44,8 @@ void main(void) } // Check distance of the eye coordinate against start and end planes with normals in the right plane. - // For computing unskewed linear texture coordinate and for clipping extremely pointy miters + // For computing unskewed lengthwise texture coordinate. + // Can also be used for clipping extremely pointy miters, but in practice unnecessary because of miter breaking. // aligned plane: cross the right plane normal with miter plane normal, then cross the result with right again to point it more "forward" vec3 alignedPlaneNormal; @@ -59,15 +60,6 @@ void main(void) alignedPlaneNormal = normalize(cross(alignedPlaneNormal, v_rightPlaneEC.xyz)); distanceFromEnd = czm_planeDistance(alignedPlaneNormal, -dot(alignedPlaneNormal, v_endEcAndStartEcX.xyz), eyeCoordinate.xyz); - if (distanceFromStart < -halfMaxWidth || distanceFromEnd < -halfMaxWidth) { -#ifdef DEBUG_SHOW_VOLUME - gl_FragColor = vec4(1.0, 0.0, 0.0, 0.5); - return; -#else // DEBUG_SHOW_VOLUME - discard; -#endif // DEBUG_SHOW_VOLUME - } - #ifdef PER_INSTANCE_COLOR gl_FragColor = v_color; #else // PER_INSTANCE_COLOR diff --git a/Source/Shaders/PolylineShadowVolumeMorphFS.glsl b/Source/Shaders/PolylineShadowVolumeMorphFS.glsl index 9cf2f6b6dec8..a5995bea035b 100644 --- a/Source/Shaders/PolylineShadowVolumeMorphFS.glsl +++ b/Source/Shaders/PolylineShadowVolumeMorphFS.glsl @@ -1,5 +1,5 @@ varying vec3 v_forwardDirectionEC; -varying vec3 v_texcoordNormalization_and_halfWidth; +varying vec3 v_texcoordNormalizationAndHalfWidth; varying float v_batchId; #ifdef PER_INSTANCE_COLOR @@ -31,7 +31,7 @@ void main(void) distanceFromEnd = max(0.0, distanceFromEnd); float s = distanceFromStart / (distanceFromStart + distanceFromEnd); - s = (s * v_texcoordNormalization_and_halfWidth.x) + v_texcoordNormalization_and_halfWidth.y; + s = (s * v_texcoordNormalizationAndHalfWidth.x) + v_texcoordNormalizationAndHalfWidth.y; czm_materialInput materialInput; diff --git a/Source/Shaders/PolylineShadowVolumeMorphVS.glsl b/Source/Shaders/PolylineShadowVolumeMorphVS.glsl index 29e82a6b73bf..a2a618919953 100644 --- a/Source/Shaders/PolylineShadowVolumeMorphVS.glsl +++ b/Source/Shaders/PolylineShadowVolumeMorphVS.glsl @@ -14,7 +14,7 @@ attribute vec2 texcoordNormalization2D; attribute float batchId; varying vec3 v_forwardDirectionEC; -varying vec3 v_texcoordNormalization_and_halfWidth; +varying vec3 v_texcoordNormalizationAndHalfWidth; varying float v_batchId; // For materials @@ -91,7 +91,7 @@ void main() cleanTexcoordNormalization3D.y = rightNormalAndTextureCoordinateNormalizationY.w; cleanTexcoordNormalization3D.y = czm_branchFreeTernary(cleanTexcoordNormalization3D.y > 1.0, 0.0, abs(cleanTexcoordNormalization3D.y)); - v_texcoordNormalization_and_halfWidth.xy = mix(cleanTexcoordNormalization2D, cleanTexcoordNormalization3D, czm_morphTime); + v_texcoordNormalizationAndHalfWidth.xy = mix(cleanTexcoordNormalization2D, cleanTexcoordNormalization3D, czm_morphTime); #ifdef PER_INSTANCE_COLOR v_color = czm_batchTable_color(batchId); @@ -106,10 +106,10 @@ void main() float width = czm_batchTable_width(batchId); float halfWidth = width * 0.5; v_width = width; - v_texcoordNormalization_and_halfWidth.z = halfWidth; + v_texcoordNormalizationAndHalfWidth.z = halfWidth; #else float halfWidth = 0.5 * czm_batchTable_width(batchId); - v_texcoordNormalization_and_halfWidth.z = halfWidth; + v_texcoordNormalizationAndHalfWidth.z = halfWidth; #endif // Compute a normal along which to "push" the position out, extending the miter depending on view distance. diff --git a/Specs/Core/GroundPolylineGeometrySpec.js b/Specs/Core/GroundPolylineGeometrySpec.js index 4f15ca97543c..5a95fc1d3ff2 100644 --- a/Specs/Core/GroundPolylineGeometrySpec.js +++ b/Specs/Core/GroundPolylineGeometrySpec.js @@ -6,6 +6,7 @@ defineSuite([ 'Core/Math', 'Core/Ellipsoid', 'Core/GeographicProjection', + 'Core/WebMercatorProjection', 'Specs/createPackableSpecs' ], function( GroundPolylineGeometry, @@ -15,6 +16,7 @@ defineSuite([ CesiumMath, Ellipsoid, GeographicProjection, + WebMercatorProjection, createPackableSpecs) { 'use strict'; @@ -455,10 +457,26 @@ defineSuite([ expect(Cartesian3.equals(scratchPositions[1], groundPolylineGeometry._positions[1])).toBe(true); expect(scratch.loop).toBe(true); expect(scratch.granularity).toEqual(10.0); - expect(scratch.ellipsoid.equals(Ellipsoid.WGS84)).toBe(true); + expect(scratch._ellipsoid.equals(Ellipsoid.WGS84)).toBe(true); expect(scratch._scene3DOnly).toBe(true); }); + it('provides a method for setting projection and ellipsoid', function() { + var groundPolylineGeometry = new GroundPolylineGeometry({ + positions : Cartesian3.fromDegreesArray([ + -1.0, 0.0, + 1.0, 0.0 + ]), + loop : true, + granularity : 10.0 // no interpolative subdivision + }); + + GroundPolylineGeometry.setProjectionAndEllipsoid(groundPolylineGeometry, new WebMercatorProjection(Ellipsoid.UNIT_SPHERE)); + + expect(groundPolylineGeometry._projectionIndex).toEqual(1); + expect(groundPolylineGeometry._ellipsoid.equals(Ellipsoid.UNIT_SPHERE)).toBe(true); + }); + var positions = Cartesian3.fromDegreesArray([ 0.01, 0.0, 0.02, 0.0,