diff --git a/Apps/Sandcastle/gallery/development/Ground Polygon.html b/Apps/Sandcastle/gallery/development/Ground Polygon.html new file mode 100644 index 000000000000..275240ca6bce --- /dev/null +++ b/Apps/Sandcastle/gallery/development/Ground Polygon.html @@ -0,0 +1,65 @@ + + + + + + + + + Cesium Demo + + + + + + +
+

Loading...

+
+ + + diff --git a/Source/Core/PolygonGeometry.js b/Source/Core/PolygonGeometry.js index 915c81832ea7..81167788ec16 100644 --- a/Source/Core/PolygonGeometry.js +++ b/Source/Core/PolygonGeometry.js @@ -20,9 +20,7 @@ define([ './Matrix3', './PolygonGeometryLibrary', './PolygonPipeline', - './PrimitiveType', './Quaternion', - './Queue', './VertexFormat', './WindingOrder' ], function( @@ -46,9 +44,7 @@ define([ Matrix3, PolygonGeometryLibrary, PolygonPipeline, - PrimitiveType, Quaternion, - Queue, VertexFormat, WindingOrder) { "use strict"; @@ -89,55 +85,6 @@ define([ return result; } - var createGeometryFromPositionsPositions = []; - - function createGeometryFromPositions(ellipsoid, positions, granularity, perPositionHeight) { - var tangentPlane = EllipsoidTangentPlane.fromPoints(positions, ellipsoid); - var positions2D = tangentPlane.projectPointsOntoPlane(positions, createGeometryFromPositionsPositions); - - var originalWindingOrder = PolygonPipeline.computeWindingOrder2D(positions2D); - if (originalWindingOrder === WindingOrder.CLOCKWISE) { - positions2D.reverse(); - positions.reverse(); - } - - var indices = PolygonPipeline.triangulate(positions2D); - /* If polygon is completely unrenderable, just use the first three vertices */ - if (indices.length < 3) { - indices = [0, 1, 2]; - } - - var geo; - if (!perPositionHeight) { - geo = PolygonPipeline.computeSubdivision(ellipsoid, positions, indices, granularity); - } else { - var length = positions.length; - var flattenedPositions = new Array(length * 3); - var index = 0; - for ( var i = 0; i < length; i++) { - var p = positions[i]; - flattenedPositions[index++] = p.x; - flattenedPositions[index++] = p.y; - flattenedPositions[index++] = p.z; - } - geo = new Geometry({ - attributes : { - position : new GeometryAttribute({ - componentDatatype : ComponentDatatype.DOUBLE, - componentsPerAttribute : 3, - values : flattenedPositions - }) - }, - indices : indices, - primitiveType : PrimitiveType.TRIANGLES - }); - } - - return new GeometryInstance({ - geometry : geo - }); - } - var scratchBoundingRectangle = new BoundingRectangle(); var scratchPosition = new Cartesian3(); var scratchNormal = new Cartesian3(); @@ -329,114 +276,10 @@ define([ return geometry; } - var computeWallIndicesSubdivided = []; - - function computeWallIndices(positions, ellipsoid, granularity, perPositionHeight){ - var edgePositions; - var topEdgeLength; - var i; - var p1; - var p2; - - var length = positions.length; - var index = 0; - - if (!perPositionHeight) { - var minDistance = CesiumMath.chordLength(granularity, ellipsoid.maximumRadius); - - var numVertices = 0; - for (i = 0; i < length; i++) { - numVertices += PolygonGeometryLibrary.subdivideLineCount(positions[i], positions[(i + 1) % length], minDistance); - } - - topEdgeLength = (numVertices + length) * 3; - edgePositions = new Array(topEdgeLength * 2); - for (i = 0; i < length; i++) { - p1 = positions[i]; - p2 = positions[(i + 1) % length]; - - var tempPositions = PolygonGeometryLibrary.subdivideLine(p1, p2, minDistance, computeWallIndicesSubdivided); - var tempPositionsLength = tempPositions.length; - for (var j = 0; j < tempPositionsLength; ++j, ++index) { - edgePositions[index] = tempPositions[j]; - edgePositions[index + topEdgeLength] = tempPositions[j]; - } - - edgePositions[index] = p2.x; - edgePositions[index + topEdgeLength] = p2.x; - ++index; - - edgePositions[index] = p2.y; - edgePositions[index + topEdgeLength] = p2.y; - ++index; - - edgePositions[index] = p2.z; - edgePositions[index + topEdgeLength] = p2.z; - ++index; - } - } else { - topEdgeLength = length * 3 * 2; - edgePositions = new Array(topEdgeLength * 2); - for (i = 0; i < length; i++) { - p1 = positions[i]; - p2 = positions[(i + 1) % length]; - edgePositions[index] = edgePositions[index + topEdgeLength] = p1.x; - ++index; - edgePositions[index] = edgePositions[index + topEdgeLength] = p1.y; - ++index; - edgePositions[index] = edgePositions[index + topEdgeLength] = p1.z; - ++index; - edgePositions[index] = edgePositions[index + topEdgeLength] = p2.x; - ++index; - edgePositions[index] = edgePositions[index + topEdgeLength] = p2.y; - ++index; - edgePositions[index] = edgePositions[index + topEdgeLength] = p2.z; - ++index; - } - } - - length = edgePositions.length; - var indices = IndexDatatype.createTypedArray(length / 3, length - positions.length * 6); - var edgeIndex = 0; - length /= 6; - - for (i = 0; i < length; i++) { - var UL = i; - var UR = UL + 1; - var LL = UL + length; - var LR = LL + 1; - - p1 = Cartesian3.fromArray(edgePositions, UL * 3, p1Scratch); - p2 = Cartesian3.fromArray(edgePositions, UR * 3, p2Scratch); - if (Cartesian3.equalsEpsilon(p1, p2, CesiumMath.EPSILON14)) { - continue; - } - - indices[edgeIndex++] = UL; - indices[edgeIndex++] = LL; - indices[edgeIndex++] = UR; - indices[edgeIndex++] = UR; - indices[edgeIndex++] = LL; - indices[edgeIndex++] = LR; - } - - return new Geometry({ - attributes : new GeometryAttributes({ - position : new GeometryAttribute({ - componentDatatype : ComponentDatatype.DOUBLE, - componentsPerAttribute : 3, - values : edgePositions - }) - }), - indices : indices, - primitiveType : PrimitiveType.TRIANGLES - }); - } - var createGeometryFromPositionsExtrudedPositions = []; function createGeometryFromPositionsExtruded(ellipsoid, positions, granularity, hierarchy, perPositionHeight) { - var topGeo = createGeometryFromPositions(ellipsoid, positions, granularity, perPositionHeight).geometry; + var topGeo = PolygonGeometryLibrary.createGeometryFromPositions(ellipsoid, positions, granularity, perPositionHeight); var edgePoints = topGeo.attributes.position.values; var indices = topGeo.indices; @@ -489,7 +332,7 @@ define([ outerRing.reverse(); } - var wallGeo = computeWallIndices(outerRing, ellipsoid, granularity, perPositionHeight); + var wallGeo = PolygonGeometryLibrary.computeWallGeometry(outerRing, ellipsoid, granularity, perPositionHeight); geos.walls.push(new GeometryInstance({ geometry : wallGeo })); @@ -506,7 +349,7 @@ define([ hole.reverse(); } - wallGeo = computeWallIndices(hole, ellipsoid, granularity); + wallGeo = PolygonGeometryLibrary.computeWallGeometry(hole, ellipsoid, granularity); geos.walls.push(new GeometryInstance({ geometry : wallGeo })); @@ -812,50 +655,9 @@ define([ var topAndBottom; var outerPositions; - // create from a polygon hierarchy - // Algorithm adapted from http://www.geometrictools.com/Documentation/TriangulationByEarClipping.pdf - var polygons = []; - var queue = new Queue(); - queue.enqueue(polygonHierarchy); - polygonHierarchy = []; - var i; - while (queue.length !== 0) { - var outerNode = queue.dequeue(); - var outerRing = outerNode.positions; - var holes = outerNode.holes; - outerRing = PolygonPipeline.removeDuplicates(outerRing); - if (outerRing.length < 3) { - continue; - } - - var numChildren = defined(holes) ? holes.length : 0; - var polygonHoles = []; - for (i = 0; i < numChildren; i++) { - var hole = holes[i]; - hole.positions = PolygonPipeline.removeDuplicates(hole.positions); - if (hole.positions.length < 3) { - continue; - } - polygonHoles.push(hole.positions); - - var numGrandchildren = 0; - if (defined(hole.holes)) { - numGrandchildren = hole.holes.length; - } - - for ( var j = 0; j < numGrandchildren; j++) { - queue.enqueue(hole.holes[j]); - } - } - - polygonHierarchy.push({ - outerRing : outerRing, - holes : polygonHoles - }); - - var combinedPolygon = polygonHoles.length > 0 ? PolygonPipeline.eliminateHoles(outerRing, polygonHoles) : outerRing; - polygons.push(combinedPolygon); - } + var results = PolygonGeometryLibrary.polygonsFromHierarchy(polygonHierarchy); + var hierarchy = results.hierarchy; + var polygons = results.polygons; if (polygons.length === 0) { return undefined; @@ -865,10 +667,11 @@ define([ var geometry; var geometries = []; + var i; if (extrude) { for (i = 0; i < polygons.length; i++) { - geometry = createGeometryFromPositionsExtruded(ellipsoid, polygons[i], granularity, polygonHierarchy[i], perPositionHeight); + geometry = createGeometryFromPositionsExtruded(ellipsoid, polygons[i], granularity, hierarchy[i], perPositionHeight); topAndBottom = geometry.topAndBottom; topAndBottom.geometry = PolygonGeometryLibrary.scaleToGeodeticHeightExtruded(topAndBottom.geometry, height, extrudedHeight, ellipsoid, perPositionHeight); topAndBottom.geometry = computeAttributes(vertexFormat, topAndBottom.geometry, outerPositions, ellipsoid, stRotation, true, false); @@ -884,7 +687,9 @@ define([ } } else { for (i = 0; i < polygons.length; i++) { - geometry = createGeometryFromPositions(ellipsoid, polygons[i], granularity, perPositionHeight); + geometry = new GeometryInstance({ + geometry : PolygonGeometryLibrary.createGeometryFromPositions(ellipsoid, polygons[i], granularity, perPositionHeight) + }); geometry.geometry = PolygonPipeline.scaleToGeodeticHeight(geometry.geometry, height, ellipsoid, !perPositionHeight); geometry.geometry = computeAttributes(vertexFormat, geometry.geometry, outerPositions, ellipsoid, stRotation, false, false); geometries.push(geometry); diff --git a/Source/Core/PolygonGeometryLibrary.js b/Source/Core/PolygonGeometryLibrary.js index f3014bbe82ce..855b73d66810 100644 --- a/Source/Core/PolygonGeometryLibrary.js +++ b/Source/Core/PolygonGeometryLibrary.js @@ -1,14 +1,36 @@ /*global define*/ define([ './Cartesian3', + './ComponentDatatype', './defaultValue', './defined', - './Ellipsoid' + './Ellipsoid', + './EllipsoidTangentPlane', + './Geometry', + './GeometryAttribute', + './GeometryAttributes', + './IndexDatatype', + './Math', + './PolygonPipeline', + './PrimitiveType', + './Queue', + './WindingOrder' ], function( Cartesian3, + ComponentDatatype, defaultValue, defined, - Ellipsoid) { + Ellipsoid, + EllipsoidTangentPlane, + Geometry, + GeometryAttribute, + GeometryAttributes, + IndexDatatype, + CesiumMath, + PolygonPipeline, + PrimitiveType, + Queue, + WindingOrder) { "use strict"; /** @@ -109,9 +131,6 @@ define([ return [distanceScratch.x, distanceScratch.y, distanceScratch.z]; } - /** - * @private - */ PolygonGeometryLibrary.subdivideLineCount = function(p0, p1, minDistance) { var distance = Cartesian3.distance(p0, p1); var n = distance / minDistance; @@ -119,9 +138,6 @@ define([ return Math.pow(2, countDivide); }; - /** - * @private - */ PolygonGeometryLibrary.subdivideLine = function(p0, p1, minDistance, result) { var numVertices = PolygonGeometryLibrary.subdivideLineCount(p0, p1, minDistance); var length = Cartesian3.distance(p0, p1); @@ -149,9 +165,7 @@ define([ var scaleToGeodeticHeightN2 = new Cartesian3(); var scaleToGeodeticHeightP1 = new Cartesian3(); var scaleToGeodeticHeightP2 = new Cartesian3(); - /** - * @private - */ + PolygonGeometryLibrary.scaleToGeodeticHeightExtruded = function(geometry, maxHeight, minHeight, ellipsoid, perPositionHeight) { ellipsoid = defaultValue(ellipsoid, Ellipsoid.WGS84); @@ -188,5 +202,211 @@ define([ return geometry; }; + PolygonGeometryLibrary.polygonsFromHierarchy = function(polygonHierarchy) { + // create from a polygon hierarchy + // Algorithm adapted from http://www.geometrictools.com/Documentation/TriangulationByEarClipping.pdf + var polygons = []; + var hierarchy = []; + + var queue = new Queue(); + queue.enqueue(polygonHierarchy); + + while (queue.length !== 0) { + var outerNode = queue.dequeue(); + var outerRing = outerNode.positions; + var holes = outerNode.holes; + + outerRing = PolygonPipeline.removeDuplicates(outerRing); + if (outerRing.length < 3) { + continue; + } + + var numChildren = defined(holes) ? holes.length : 0; + var polygonHoles = []; + + for (var i = 0; i < numChildren; i++) { + var hole = holes[i]; + hole.positions = PolygonPipeline.removeDuplicates(hole.positions); + if (hole.positions.length < 3) { + continue; + } + polygonHoles.push(hole.positions); + + var numGrandchildren = 0; + if (defined(hole.holes)) { + numGrandchildren = hole.holes.length; + } + + for ( var j = 0; j < numGrandchildren; j++) { + queue.enqueue(hole.holes[j]); + } + } + + hierarchy.push({ + outerRing : outerRing, + holes : polygonHoles + }); + + var combinedPolygon = polygonHoles.length > 0 ? PolygonPipeline.eliminateHoles(outerRing, polygonHoles) : outerRing; + polygons.push(combinedPolygon); + } + + return { + hierarchy : hierarchy, + polygons : polygons + }; + }; + + var createGeometryFromPositionsPositions = []; + + PolygonGeometryLibrary.createGeometryFromPositions = function(ellipsoid, positions, granularity, perPositionHeight) { + var tangentPlane = EllipsoidTangentPlane.fromPoints(positions, ellipsoid); + var positions2D = tangentPlane.projectPointsOntoPlane(positions, createGeometryFromPositionsPositions); + + var originalWindingOrder = PolygonPipeline.computeWindingOrder2D(positions2D); + if (originalWindingOrder === WindingOrder.CLOCKWISE) { + positions2D.reverse(); + positions.reverse(); + } + + var indices = PolygonPipeline.triangulate(positions2D); + /* If polygon is completely unrenderable, just use the first three vertices */ + if (indices.length < 3) { + indices = [0, 1, 2]; + } + + if (perPositionHeight) { + var length = positions.length; + var flattenedPositions = new Array(length * 3); + var index = 0; + for ( var i = 0; i < length; i++) { + var p = positions[i]; + flattenedPositions[index++] = p.x; + flattenedPositions[index++] = p.y; + flattenedPositions[index++] = p.z; + } + + return new Geometry({ + attributes : { + position : new GeometryAttribute({ + componentDatatype : ComponentDatatype.DOUBLE, + componentsPerAttribute : 3, + values : flattenedPositions + }) + }, + indices : indices, + primitiveType : PrimitiveType.TRIANGLES + }); + } + + return PolygonPipeline.computeSubdivision(ellipsoid, positions, indices, granularity); + }; + + var computeWallIndicesSubdivided = []; + var p1Scratch = new Cartesian3(); + var p2Scratch = new Cartesian3(); + + PolygonGeometryLibrary.computeWallGeometry = function(positions, ellipsoid, granularity, perPositionHeight) { + var edgePositions; + var topEdgeLength; + var i; + var p1; + var p2; + + var length = positions.length; + var index = 0; + + if (!perPositionHeight) { + var minDistance = CesiumMath.chordLength(granularity, ellipsoid.maximumRadius); + + var numVertices = 0; + for (i = 0; i < length; i++) { + numVertices += PolygonGeometryLibrary.subdivideLineCount(positions[i], positions[(i + 1) % length], minDistance); + } + + topEdgeLength = (numVertices + length) * 3; + edgePositions = new Array(topEdgeLength * 2); + for (i = 0; i < length; i++) { + p1 = positions[i]; + p2 = positions[(i + 1) % length]; + + var tempPositions = PolygonGeometryLibrary.subdivideLine(p1, p2, minDistance, computeWallIndicesSubdivided); + var tempPositionsLength = tempPositions.length; + for (var j = 0; j < tempPositionsLength; ++j, ++index) { + edgePositions[index] = tempPositions[j]; + edgePositions[index + topEdgeLength] = tempPositions[j]; + } + + edgePositions[index] = p2.x; + edgePositions[index + topEdgeLength] = p2.x; + ++index; + + edgePositions[index] = p2.y; + edgePositions[index + topEdgeLength] = p2.y; + ++index; + + edgePositions[index] = p2.z; + edgePositions[index + topEdgeLength] = p2.z; + ++index; + } + } else { + topEdgeLength = length * 3 * 2; + edgePositions = new Array(topEdgeLength * 2); + for (i = 0; i < length; i++) { + p1 = positions[i]; + p2 = positions[(i + 1) % length]; + edgePositions[index] = edgePositions[index + topEdgeLength] = p1.x; + ++index; + edgePositions[index] = edgePositions[index + topEdgeLength] = p1.y; + ++index; + edgePositions[index] = edgePositions[index + topEdgeLength] = p1.z; + ++index; + edgePositions[index] = edgePositions[index + topEdgeLength] = p2.x; + ++index; + edgePositions[index] = edgePositions[index + topEdgeLength] = p2.y; + ++index; + edgePositions[index] = edgePositions[index + topEdgeLength] = p2.z; + ++index; + } + } + + length = edgePositions.length; + var indices = IndexDatatype.createTypedArray(length / 3, length - positions.length * 6); + var edgeIndex = 0; + length /= 6; + + for (i = 0; i < length; i++) { + var UL = i; + var UR = UL + 1; + var LL = UL + length; + var LR = LL + 1; + + p1 = Cartesian3.fromArray(edgePositions, UL * 3, p1Scratch); + p2 = Cartesian3.fromArray(edgePositions, UR * 3, p2Scratch); + if (Cartesian3.equalsEpsilon(p1, p2, CesiumMath.EPSILON14)) { + continue; + } + + indices[edgeIndex++] = UL; + indices[edgeIndex++] = LL; + indices[edgeIndex++] = UR; + indices[edgeIndex++] = UR; + indices[edgeIndex++] = LL; + indices[edgeIndex++] = LR; + } + + return new Geometry({ + attributes : new GeometryAttributes({ + position : new GeometryAttribute({ + componentDatatype : ComponentDatatype.DOUBLE, + componentsPerAttribute : 3, + values : edgePositions + }) + }), + indices : indices, + primitiveType : PrimitiveType.TRIANGLES + }); + }; + return PolygonGeometryLibrary; }); \ No newline at end of file diff --git a/Source/Renderer/RenderState.js b/Source/Renderer/RenderState.js index 3e3e360d7055..424587d566ba 100644 --- a/Source/Renderer/RenderState.js +++ b/Source/Renderer/RenderState.js @@ -77,7 +77,7 @@ define([ (stencilOperation === WebGLRenderingContext.INCR) || (stencilOperation === WebGLRenderingContext.DECR) || (stencilOperation === WebGLRenderingContext.INVERT) || - (stencilOperation === WebGLRenderingContext.INCREMENT_WRAP) || + (stencilOperation === WebGLRenderingContext.INCR_WRAP) || (stencilOperation === WebGLRenderingContext.DECR_WRAP)); } diff --git a/Source/Scene/GlobeDepth.js b/Source/Scene/GlobeDepth.js index 444d35c887d5..7b2c040548bd 100644 --- a/Source/Scene/GlobeDepth.js +++ b/Source/Scene/GlobeDepth.js @@ -163,6 +163,7 @@ define([ if (!defined(globeDepth._clearColorCommand)) { globeDepth._clearColorCommand = new ClearCommand({ color : new Color(0.0, 0.0, 0.0, 0.0), + stencil : 0.0, owner : globeDepth }); } diff --git a/Source/Scene/GroundPolygon.js b/Source/Scene/GroundPolygon.js new file mode 100644 index 000000000000..de2f737c16d9 --- /dev/null +++ b/Source/Scene/GroundPolygon.js @@ -0,0 +1,424 @@ +/*global define*/ +define([ + '../Core/BoundingSphere', + '../Core/Cartesian3', + '../Core/ComponentDatatype', + '../Core/defaultValue', + '../Core/defined', + '../Core/Ellipsoid', + '../Core/EllipsoidTangentPlane', + '../Core/EncodedCartesian3', + '../Core/IndexDatatype', + '../Core/Math', + '../Core/Matrix4', + '../Core/PolygonGeometryLibrary', + '../Core/PolygonPipeline', + '../Core/PrimitiveType', + '../Core/WindingOrder', + '../Renderer/BufferUsage', + '../Renderer/DrawCommand', + '../Renderer/ShaderSource', + '../Shaders/ShadowVolumeFS', + '../Shaders/ShadowVolumeVS', + './BlendingState', + './CullFace', + './Pass', + './StencilFunction', + './StencilOperation' + ], function( + BoundingSphere, + Cartesian3, + ComponentDatatype, + defaultValue, + defined, + Ellipsoid, + EllipsoidTangentPlane, + EncodedCartesian3, + IndexDatatype, + CesiumMath, + Matrix4, + PolygonGeometryLibrary, + PolygonPipeline, + PrimitiveType, + WindingOrder, + BufferUsage, + DrawCommand, + ShaderSource, + ShadowVolumeFS, + ShadowVolumeVS, + BlendingState, + CullFace, + Pass, + StencilFunction, + StencilOperation) { + "use strict"; + + var GroundPolygon = function(options) { + options = defaultValue(options, defaultValue.EMPTY_OBJECT); + + var ellipsoid = defaultValue(options.ellipsoid, Ellipsoid.WGS84); + var granularity = defaultValue(options.granularity, CesiumMath.RADIANS_PER_DEGREE); + var polygonHierarchy = options.polygonHierarchy; + + this._ellipsoid = ellipsoid; + this._granularity = granularity; + this._polygonHierarchy = polygonHierarchy; + + this._boundingSphere = undefined; + + this._va = undefined; + this._sp = undefined; + this._rs = undefined; + + this.debugVolume = defaultValue(options.debugVolume, false); + this._debugVolumeCommand = undefined; + + var that = this; + this._uniformMap = { + centralBodyMinimumAltitude : function() { + return -8500.0; + }, + LODNegativeToleranceOverDistance : function() { + return -0.01; + } + }; + + this._stencilPassCommand = undefined; + this._colorPassCommand = undefined; + }; + + var attributeLocations = { + positionHigh : 0, + positionLow : 1, + normal : 2 + }; + + function getSurfaceDelta(ellipsoid, granularity) { + var refDistance = ellipsoid.maximumRadius; + return refDistance - (refDistance * Math.cos(granularity / 2.0)); + } + + var scratchPosition = new Cartesian3(); + var scratchNormal = new Cartesian3(); + var scratchDeltaNormal = new Cartesian3(); + + // TODO: More than one level of the polygon hierarchy + function createShadowVolume(polygon, context) { + var polygonHierarchy = polygon._polygonHierarchy; + var ellipsoid = polygon._ellipsoid; + var granularity = polygon._granularity; + + var results = PolygonGeometryLibrary.polygonsFromHierarchy(polygonHierarchy); + var positions = results.polygons[0]; + var hierarchy = results.hierarchy[0]; + + var bottomCap = PolygonGeometryLibrary.createGeometryFromPositions(ellipsoid, positions, granularity, false); + var bottomPositions = bottomCap.attributes.position.values; + var numBottomCapVertices = bottomPositions.length / 3; + var numCapVertices = numBottomCapVertices + numBottomCapVertices; + var bottomIndices = bottomCap.indices; + var numBottomIndices = bottomIndices.length; + var numCapIndices = numBottomIndices + numBottomIndices; + + var outerRing = hierarchy.outerRing; + var tangentPlane = EllipsoidTangentPlane.fromPoints(outerRing, ellipsoid); + var positions2D = tangentPlane.projectPointsOntoPlane(outerRing); + + var windingOrder = PolygonPipeline.computeWindingOrder2D(positions2D); + if (windingOrder === WindingOrder.CLOCKWISE) { + outerRing.reverse(); + } + + var wall = PolygonGeometryLibrary.computeWallGeometry(outerRing, ellipsoid, granularity, false); + var walls = [wall]; + var numWallVertices = wall.attributes.position.values.length / 3; + var numWallIndices = wall.indices.length; + + var holes = hierarchy.holes; + var i; + var j; + + for (i = 0; i < holes.length; i++) { + var hole = holes[i]; + + tangentPlane = EllipsoidTangentPlane.fromPoints(hole, ellipsoid); + positions2D = tangentPlane.projectPointsOntoPlane(hole); + + windingOrder = PolygonPipeline.computeWindingOrder2D(positions2D); + if (windingOrder === WindingOrder.COUNTER_CLOCKWISE) { + hole.reverse(); + } + + walls.push(PolygonGeometryLibrary.computeWallGeometry(hole, ellipsoid, granularity, false)); + numWallVertices += wall.attributes.position.values.length / 3; + numWallIndices += wall.indices.length; + walls.push(wall); + } + + var maxAlt = 8500.0; // TODO: get max alt of terrain + var surfaceDelta = getSurfaceDelta(ellipsoid, granularity); + var upDelta = maxAlt + surfaceDelta; + + var numVertices = numCapVertices + numWallVertices; + + var vbPositions = new Float32Array(numVertices * 3 * 2); + var vbNormals = new Float32Array(numVertices * 3); + + var position; + var normal; + var topPosition; + + var index = 0; + var normalIndex = 0; + + for (i = 0; i < numBottomCapVertices * 3; i += 3) { + position = Cartesian3.unpack(bottomPositions, i, scratchPosition); + ellipsoid.scaleToGeodeticSurface(position, position); + + normal = ellipsoid.geodeticSurfaceNormal(position, scratchNormal); + Cartesian3.multiplyByScalar(normal, upDelta, normal); + + EncodedCartesian3.writeElements(position, vbPositions, index); + EncodedCartesian3.writeElements(position, vbPositions, index + 6); + index += 12; + + Cartesian3.pack(Cartesian3.ZERO, vbNormals, normalIndex); + Cartesian3.pack(normal, vbNormals, normalIndex + 3); + normalIndex += 6; + } + + var numWalls = walls.length; + var wallPositions; + var wallLength; + + for (i = 0; i < numWalls; ++i) { + wall = walls[i]; + wallPositions = wall.attributes.position.values; + wallLength = wallPositions.length / 2; + + for (j = 0; j < wallLength; j += 3) { + position = Cartesian3.unpack(wallPositions, j, scratchPosition); + ellipsoid.scaleToGeodeticSurface(position, position); + + normal = ellipsoid.geodeticSurfaceNormal(position, scratchNormal); + Cartesian3.multiplyByScalar(normal, upDelta, normal); + + EncodedCartesian3.writeElements(position, vbPositions, index); + EncodedCartesian3.writeElements(position, vbPositions, index + wallLength * 2); + index += 6; + + Cartesian3.pack(normal, vbNormals, normalIndex); + Cartesian3.pack(Cartesian3.ZERO, vbNormals, normalIndex + wallLength); + normalIndex += 3; + } + + index += wallLength * 2; + normalIndex += wallLength; + } + + var numIndices = numCapIndices + numWallIndices; + var ibIndices = IndexDatatype.createTypedArray(numVertices, numIndices); + + var i0; + var i1; + var i2; + + index = 0; + for (i = 0; i < numBottomIndices; i += 3) { + i0 = bottomIndices[i] * 2; + i1 = bottomIndices[i + 1] * 2; + i2 = bottomIndices[i + 2] * 2; + + ibIndices[index++] = i2; + ibIndices[index++] = i1; + ibIndices[index++] = i0; + } + + for (i = 0; i < numBottomIndices; i += 3) { + i0 = bottomIndices[i] * 2; + i1 = bottomIndices[i + 1] * 2; + i2 = bottomIndices[i + 2] * 2; + + ibIndices[index++] = i0 + 1; + ibIndices[index++] = i1 + 1; + ibIndices[index++] = i2 + 1; + } + + var offset = numCapVertices; + for (i = 0; i < numWalls; ++i) { + wall = walls[i]; + var wallIndices = wall.indices; + wallLength = wallIndices.length; + + for (j = 0; j < wallLength; ++j) { + ibIndices[index++] = wallIndices[j] + offset; + } + offset += wall.attributes.position.values.length / 3; + } + + var positionBuffer = context.createVertexBuffer(vbPositions, BufferUsage.STATIC_DRAW); + var normalBuffer = context.createVertexBuffer(vbNormals, BufferUsage.STATIC_DRAW); + + var indexDatatype = (ibIndices.BYTES_PER_ELEMENT === 2) ? IndexDatatype.UNSIGNED_SHORT : IndexDatatype.UNSIGNED_INT; + var indexBuffer = context.createIndexBuffer(ibIndices, BufferUsage.STATIC_DRAW, indexDatatype); + + var attributes = [{ + index : attributeLocations.positionHigh, + vertexBuffer : positionBuffer, + componentsPerAttribute : 3, + componentDatatype : ComponentDatatype.FLOAT, + offsetInBytes : 0, + strideInBytes : ComponentDatatype.getSizeInBytes(ComponentDatatype.FLOAT) * 3 * 2 + }, { + index : attributeLocations.positionLow, + vertexBuffer : positionBuffer, + componentsPerAttribute : 3, + componentDatatype : ComponentDatatype.FLOAT, + offsetInBytes : ComponentDatatype.getSizeInBytes(ComponentDatatype.FLOAT) * 3, + strideInBytes : ComponentDatatype.getSizeInBytes(ComponentDatatype.FLOAT) * 3 * 2 + }, { + index : attributeLocations.normal, + vertexBuffer : normalBuffer, + componentsPerAttribute : 3, + componentDatatype : ComponentDatatype.FLOAT + }]; + + polygon._va = context.createVertexArray(attributes, indexBuffer); + + polygon._boundingSphere = BoundingSphere.fromPoints(outerRing); + polygon._boundingSphere.radius = upDelta; // TODO: Correct bounding sphere + } + + GroundPolygon.prototype.update = function(context, frameState, commandList) { + if (!defined(this._va)) { + createShadowVolume(this, context); + } + + if (!defined(this._sp)) { + this._sp = context.createShaderProgram(ShadowVolumeVS, ShadowVolumeFS, attributeLocations); + } + + if (!defined(this._stencilPassCommand)) { + var stencilPassRenderState = context.createRenderState({ + colorMask : { + red : false, + green : false, + blue : false, + alpha : false + }, + stencilTest : { + enabled : true, + frontFunction : StencilFunction.ALWAYS, + frontOperation : { + fail : StencilOperation.KEEP, + zFail : StencilOperation.KEEP, + zPass : StencilOperation.INCREMENT_WRAP + }, + backFunction : StencilFunction.ALWAYS, + backOperation : { + fail : StencilOperation.KEEP, + zFail : StencilOperation.KEEP, + zPass : StencilOperation.DECREMENT_WRAP + }, + reference : 0, + mask : ~0 + }, + depthTest : { + enabled : true + }, + depthMask : false + }); + + this._stencilPassCommand = new DrawCommand({ + primitiveType : PrimitiveType.TRIANGLES, + vertexArray : this._va, + renderState : stencilPassRenderState, + shaderProgram : this._sp, + uniformMap : this._uniformMap, + boundingVolume : this._boundingSphere, + owner : this, + modelMatrix : Matrix4.IDENTITY, + pass : Pass.GROUND + }); + + var colorRenderState = context.createRenderState({ + stencilTest : { + enabled : true, + frontFunction : StencilFunction.NOT_EQUAL, + frontOperation : { + fail : StencilOperation.KEEP, + zFail : StencilOperation.KEEP, + zPass : StencilOperation.DECREMENT + }, + backFunction : StencilFunction.NOT_EQUAL, + backOperation : { + fail : StencilOperation.KEEP, + zFail : StencilOperation.KEEP, + zPass : StencilOperation.DECREMENT + }, + reference : 0, + mask : ~0 + }, + cull : { + enabled : true, + face : CullFace.BACK + }, + depthTest : { + enabled : true + }, + depthMask : false, + blending : BlendingState.ALPHA_BLEND + }); + + this._colorPassCommand = new DrawCommand({ + primitiveType : PrimitiveType.TRIANGLES, + vertexArray : this._va, + renderState : colorRenderState, + shaderProgram : this._sp, + uniformMap : this._uniformMap, + boundingVolume : this._boundingSphere, + owner : this, + modelMatrix : Matrix4.IDENTITY, + pass : Pass.GROUND + }); + } + + if (this.debugVolume && !defined(this._debugVolumeCommand)) { + var pauseRS = context.createRenderState({ + cull : { + enabled : false + }, + depthTest : { + enabled : true + }, + depthMask : false, + blending : BlendingState.ALPHA_BLEND + }); + + var sp = context.createShaderProgram(ShadowVolumeVS, ShadowVolumeFS, attributeLocations); + + this._debugVolumeCommand = new DrawCommand({ + primitiveType : PrimitiveType.TRIANGLES, + vertexArray : this._va, + renderState : pauseRS, + shaderProgram : sp, + uniformMap : this._uniformMap, + boundingVolume : this._boundingSphere, + owner : this, + modelMatrix : Matrix4.IDENTITY, + pass : Pass.GROUND + }); + } + + var pass = frameState.passes; + if (pass.render) { + if (this.debugVolume) { + commandList.push(this._debugVolumeCommand); + } else { + commandList.push(this._stencilPassCommand, this._colorPassCommand); + } + } + }; + + return GroundPolygon; +}); diff --git a/Source/Scene/Pass.js b/Source/Scene/Pass.js index 4469bbd6c019..60be1a2d6741 100644 --- a/Source/Scene/Pass.js +++ b/Source/Scene/Pass.js @@ -12,13 +12,14 @@ define([ */ var Pass = { GLOBE : 0, - OPAQUE : 1, + GROUND : 1, + OPAQUE : 2, // Commands are executed in order by pass up to the translucent pass. // Translucent geometry needs special handling (sorting/OIT). Overlays // are also special (they're executed last, they're not sorted by frustum). - TRANSLUCENT : 2, - OVERLAY : 3, - NUMBER_OF_PASSES : 4 + TRANSLUCENT : 3, + OVERLAY : 4, + NUMBER_OF_PASSES : 5 }; return freezeObject(Pass); diff --git a/Source/Scene/Scene.js b/Source/Scene/Scene.js index c20f3c5d9eb1..f6186c644850 100644 --- a/Source/Scene/Scene.js +++ b/Source/Scene/Scene.js @@ -232,6 +232,7 @@ define([ this._clearColorCommand = new ClearCommand({ color : new Color(), + stencil : 0.0, owner : this }); this._depthClearCommand = new ClearCommand({ @@ -1294,10 +1295,9 @@ define([ frustum.near = frustumCommands.near; frustum.far = frustumCommands.far; - if (index !== 0) { - // Avoid tearing artifacts between adjacent frustums - frustum.near *= 0.99; - } + // Avoid tearing artifacts between adjacent frustums + var overlappedNear = index !== 0 ? frustum.near * 0.99 : frustum.near; + frustum.near = overlappedNear; us.updateFrustum(frustum); clearDepth.execute(context, passState); @@ -1310,9 +1310,21 @@ define([ scene._globeDepth.executeCopyDepth(context, passState); + frustum.near = frustumCommands.near; + us.updateFrustum(frustum); + + commands = frustumCommands.commands[Pass.GROUND]; + length = frustumCommands.indices[Pass.GROUND]; + for (j = 0; j < length; ++j) { + executeCommand(commands[j], scene, context, passState); + } + + frustum.near = overlappedNear; + us.updateFrustum(frustum); + // Execute commands in order by pass up to the translucent pass. // Translucent geometry needs special handling (sorting/OIT). - var startPass = Pass.GLOBE + 1; + var startPass = Pass.GROUND + 1; var endPass = Pass.TRANSLUCENT; for (var pass = startPass; pass < endPass; ++pass) { commands = frustumCommands.commands[pass]; diff --git a/Source/Shaders/ShadowVolumeFS.glsl b/Source/Shaders/ShadowVolumeFS.glsl new file mode 100644 index 000000000000..5df05c448392 --- /dev/null +++ b/Source/Shaders/ShadowVolumeFS.glsl @@ -0,0 +1,18 @@ +#ifdef GL_EXT_frag_depth +#extension GL_EXT_frag_depth : enable +#endif + +varying float v_z; + +void czm_writeDepthClampedToFarPlane() +{ + // That is really 1/w + //gl_FragDepthEXT = min(v_z * gl_FragCoord.w, 1.0); + //gl_FragDepthEXT = clamp(v_z * gl_FragCoord.w, 0.0, 1.0); +} + +void main(void) +{ + gl_FragColor = vec4(1.0, 1.0, 0.0, 0.5); + czm_writeDepthClampedToFarPlane(); +} \ No newline at end of file diff --git a/Source/Shaders/ShadowVolumeVS.glsl b/Source/Shaders/ShadowVolumeVS.glsl new file mode 100644 index 000000000000..4e840c05ce55 --- /dev/null +++ b/Source/Shaders/ShadowVolumeVS.glsl @@ -0,0 +1,85 @@ +attribute vec3 positionHigh; +attribute vec3 positionLow; +attribute vec3 normal; + +uniform float centralBodyMinimumAltitude; +uniform float LODNegativeToleranceOverDistance; + +varying float v_z; + +vec4 czm_depthClampNearFarPlane(vec4 vertexInClipCoordinates) +{ + //v_z = (0.5 * (vertexInClipCoordinates.z / vertexInClipCoordinates.w) + 0.5) * vertexInClipCoordinates.w; + //vertexInClipCoordinates.z = min(vertexInClipCoordinates.z, vertexInClipCoordinates.w); + return vertexInClipCoordinates; +} + +vec4 clipPointToPlane(vec3 p0, vec3 p1, bool nearPlane) +{ + float planeDistance = nearPlane ? czm_entireFrustum.x : czm_entireFrustum.y; + //float offset = nearPlane ? 0.001 : -0.001; + float offset = 0.001; + + p0 = (czm_modelViewRelativeToEye * vec4(p0, 1.0)).xyz; + p1 = (czm_modelViewRelativeToEye * vec4(p1, 1.0)).xyz; + + vec3 diff = p1 - p0; + float magnitude = length(diff); + vec3 direction = normalize(diff); + float denominator = -direction.z; + float pointDistance = -(planeDistance + p0.z); + bool behindPlane = nearPlane ? pointDistance < 0.0 : pointDistance > 0.0; + + bool culledByPlane = false; + + if (behindPlane && abs(denominator) < czm_epsilon7) + { + // point is behind and parallel to the plane + culledByPlane = true; + } + else if (behindPlane && abs(denominator) > czm_epsilon7) + { + // find intersection of ray and the plane + // t = (-dot(plane normal, point on plane) - dot(plane normal, ray origin)) / dot(plane normal, ray direction) + float t = (planeDistance + p0.z) / denominator; + if (t < 0.0 || t > magnitude) + { + // entire segment is behind the plane + culledByPlane = true; + } + else + { + // compute intersection with plane slightly offset + // to prevent precision artifacts + t += offset; + p0 = p0 + t * direction; + } + } + + if (culledByPlane) { + // the segment is behind the plane. push to plane and + // slightly offset to prevent precision artifacts + //p0.z = min(p0.z, -(planeDistance + offset)); + } + + return czm_projection * vec4(p0, 1.0); +} + +void main() +{ + vec4 position = czm_translateRelativeToEye(positionHigh, positionLow); + + float delta = 1.0; // TODO: moving the vertex is a function of the view + + vec3 eyePosition = position.xyz; + vec3 movedPosition = position.xyz + normal * delta; + + if (all(equal(normal, vec3(0.0)))) + { + gl_Position = czm_depthClampNearFarPlane(clipPointToPlane(eyePosition, movedPosition, false)); + } + else + { + gl_Position = czm_depthClampNearFarPlane(clipPointToPlane(movedPosition, eyePosition, true)); + } +}